From 932b3d9723b07baf967a278d4ed0389ff9af67c1 Mon Sep 17 00:00:00 2001 From: sbs44 <83440025+sbs44@users.noreply.github.com> Date: Sat, 27 Jun 2026 04:10:43 -0400 Subject: [PATCH 1/5] fix(patches): rebase 00-brand-remove-branding for VS Code 1.126.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The apiProposalNotListed context drifted (a new apiProposalVersionNotSupported line was added between relativeBadgeUrlRequiresHttpsRepository and apiProposalNotListed), and the apiProposalMismatch1/2 strings in extensionValidator.ts were removed upstream — drop those hunks since the rebrand target no longer exists. Refs #21 --- patches/00-brand-remove-branding.patch | 528 ++++++++++++------------- upstream/stable.json | 4 +- 2 files changed, 258 insertions(+), 274 deletions(-) diff --git a/patches/00-brand-remove-branding.patch b/patches/00-brand-remove-branding.patch index 8be3244..1c017cd 100644 --- a/patches/00-brand-remove-branding.patch +++ b/patches/00-brand-remove-branding.patch @@ -7,7 +7,7 @@ index 6255123d..6e6013e7 100644 SourceFile=%1 Source File OpenWithCodeContextMenu=Open w&ith %1 -UpdatingVisualStudioCode=Updating Visual Studio Code... -+UpdatingVisualStudioCode=Updating !!APP_NAME!!... ++UpdatingVisualStudioCode=Updating BradfordCode... diff --git a/build/win32/i18n/messages.es.isl b/build/win32/i18n/messages.es.isl index 7f6f1bd0..f508cf3a 100644 --- a/build/win32/i18n/messages.es.isl @@ -18,7 +18,7 @@ index 7f6f1bd0..f508cf3a 100644 OpenWithCodeContextMenu=Abrir &con %1 -UpdatingVisualStudioCode=Actualizando Visual Studio Code... \ No newline at end of file -+UpdatingVisualStudioCode=Actualizando !!APP_NAME!!... ++UpdatingVisualStudioCode=Actualizando BradfordCode... \ No newline at end of file diff --git a/build/win32/i18n/messages.it.isl b/build/win32/i18n/messages.it.isl index ac64aae7..7184230e 100644 @@ -30,7 +30,7 @@ index ac64aae7..7184230e 100644 OpenWithCodeContextMenu=Apri con %1 -UpdatingVisualStudioCode=Aggiornamento di Visual Studio Code... \ No newline at end of file -+UpdatingVisualStudioCode=Aggiornamento di !!APP_NAME!!... ++UpdatingVisualStudioCode=Aggiornamento di BradfordCode... \ No newline at end of file diff --git a/build/win32/i18n/messages.pt-br.isl b/build/win32/i18n/messages.pt-br.isl index 08c77290..550d805d 100644 @@ -42,7 +42,7 @@ index 08c77290..550d805d 100644 OpenWithCodeContextMenu=Abrir com %1 -UpdatingVisualStudioCode=Atualizando o Visual Studio Code... \ No newline at end of file -+UpdatingVisualStudioCode=Atualizando o !!APP_NAME!!... ++UpdatingVisualStudioCode=Atualizando o BradfordCode... \ No newline at end of file diff --git a/extensions/configuration-editing/src/configurationEditingMain.ts b/extensions/configuration-editing/src/configurationEditingMain.ts index 2578270c..9589db2a 100644 @@ -54,8 +54,8 @@ index 2578270c..9589db2a 100644 return [ - { label: 'workspaceFolder', detail: vscode.l10n.t("The path of the folder opened in VS Code") }, - { label: 'workspaceFolderBasename', detail: vscode.l10n.t("The name of the folder opened in VS Code without any slashes (/)") }, -+ { label: 'workspaceFolder', detail: vscode.l10n.t("The path of the folder opened in !!APP_NAME!!") }, -+ { label: 'workspaceFolderBasename', detail: vscode.l10n.t("The name of the folder opened in !!APP_NAME!! without any slashes (/)") }, ++ { label: 'workspaceFolder', detail: vscode.l10n.t("The path of the folder opened in BradfordCode") }, ++ { label: 'workspaceFolderBasename', detail: vscode.l10n.t("The name of the folder opened in BradfordCode without any slashes (/)") }, { label: 'fileWorkspaceFolderBasename', detail: vscode.l10n.t("The current opened file workspace folder name without any slashes (/)") }, { label: 'relativeFile', detail: vscode.l10n.t("The current opened file relative to ${workspaceFolder}") }, { label: 'relativeFileDirname', detail: vscode.l10n.t("The current opened file's dirname relative to ${workspaceFolder}") }, @@ -68,7 +68,7 @@ index 12b50f31..19e38322 100644 completions.push(this.newSimpleCompletionItem(getText('folderName'), range, vscode.l10n.t("name of the workspace folder the file is contained in (e.g. myFolder)"))); completions.push(this.newSimpleCompletionItem(getText('folderPath'), range, vscode.l10n.t("file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)"))); - completions.push(this.newSimpleCompletionItem(getText('appName'), range, vscode.l10n.t("e.g. VS Code"))); -+ completions.push(this.newSimpleCompletionItem(getText('appName'), range, vscode.l10n.t("e.g. !!APP_NAME!!"))); ++ completions.push(this.newSimpleCompletionItem(getText('appName'), range, vscode.l10n.t("e.g. BradfordCode"))); completions.push(this.newSimpleCompletionItem(getText('remoteName'), range, vscode.l10n.t("e.g. SSH"))); completions.push(this.newSimpleCompletionItem(getText('dirty'), range, vscode.l10n.t("an indicator for when the active editor has unsaved changes"))); completions.push(this.newSimpleCompletionItem(getText('separator'), range, vscode.l10n.t("a conditional separator (' - ') that only shows when surrounded by variables with values"))); @@ -82,8 +82,8 @@ index d3de2241..91f278e6 100644 "css.title": "CSS", - "css.customData.desc": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-css-languageservice/blob/master/docs/customData.md).\n\nVS Code loads custom data on startup to enhance its CSS support for CSS custom properties (variables), at-rules, pseudo-classes, and pseudo-elements you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", - "css.completion.triggerPropertyValueCompletion.desc": "By default, VS Code triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", -+ "css.customData.desc": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-css-languageservice/blob/master/docs/customData.md).\n\n!!APP_NAME!! loads custom data on startup to enhance its CSS support for CSS custom properties (variables), at-rules, pseudo-classes, and pseudo-elements you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", -+ "css.completion.triggerPropertyValueCompletion.desc": "By default, !!APP_NAME!! triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", ++ "css.customData.desc": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-css-languageservice/blob/master/docs/customData.md).\n\nBradfordCode loads custom data on startup to enhance its CSS support for CSS custom properties (variables), at-rules, pseudo-classes, and pseudo-elements you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", ++ "css.completion.triggerPropertyValueCompletion.desc": "By default, BradfordCode triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", "css.completion.completePropertyWithSemicolon.desc": "Insert semicolon at end of line when completing CSS properties.", "css.lint.argumentsInColorFunction.desc": "Invalid number of parameters.", "css.lint.boxModel.desc": "Do not use `width` or `height` when using `padding` or `border`.", @@ -92,7 +92,7 @@ index d3de2241..91f278e6 100644 "css.lint.vendorPrefix.desc": "When using a vendor-specific prefix, also include the standard property.", "css.lint.zeroUnits.desc": "No unit for zero needed.", - "css.trace.server.desc": "Traces the communication between VS Code and the CSS language server.", -+ "css.trace.server.desc": "Traces the communication between !!APP_NAME!! and the CSS language server.", ++ "css.trace.server.desc": "Traces the communication between BradfordCode and the CSS language server.", "css.validate.title": "Controls CSS validation and problem severities.", "css.validate.desc": "Enables or disables all validations.", "css.hover.documentation": "Show property and value documentation in CSS hovers.", @@ -101,7 +101,7 @@ index d3de2241..91f278e6 100644 "css.format.maxPreserveNewLines.desc": "Maximum number of line breaks to be preserved in one chunk, when `#css.format.preserveNewLines#` is enabled.", "less.title": "LESS", - "less.completion.triggerPropertyValueCompletion.desc": "By default, VS Code triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", -+ "less.completion.triggerPropertyValueCompletion.desc": "By default, !!APP_NAME!! triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", ++ "less.completion.triggerPropertyValueCompletion.desc": "By default, BradfordCode triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", "less.completion.completePropertyWithSemicolon.desc": "Insert semicolon at end of line when completing CSS properties.", "less.lint.argumentsInColorFunction.desc": "Invalid number of parameters.", "less.lint.boxModel.desc": "Do not use `width` or `height` when using `padding` or `border`.", @@ -110,7 +110,7 @@ index d3de2241..91f278e6 100644 "less.format.maxPreserveNewLines.desc": "Maximum number of line breaks to be preserved in one chunk, when `#less.format.preserveNewLines#` is enabled.", "scss.title": "SCSS (Sass)", - "scss.completion.triggerPropertyValueCompletion.desc": "By default, VS Code triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", -+ "scss.completion.triggerPropertyValueCompletion.desc": "By default, !!APP_NAME!! triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", ++ "scss.completion.triggerPropertyValueCompletion.desc": "By default, BradfordCode triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", "scss.completion.completePropertyWithSemicolon.desc": "Insert semicolon at end of line when completing CSS properties.", "scss.lint.argumentsInColorFunction.desc": "Invalid number of parameters.", "scss.lint.boxModel.desc": "Do not use `width` or `height` when using `padding` or `border`.", @@ -121,7 +121,7 @@ index 683bcc7f..fb6b0523 100644 @@ -1,5 +1,5 @@ { - "description": "Emmet support for VS Code", -+ "description": "Emmet support for !!APP_NAME!!", ++ "description": "Emmet support for BradfordCode", "command.wrapWithAbbreviation": "Wrap with Abbreviation", "command.removeTag": "Remove Tag", "command.updateTag": "Update Tag", @@ -134,9 +134,9 @@ index 1be4d0e1..d60d8357 100644 export const implicitActivationEvent = l10n.t("This activation event cannot be explicitly listed by your extension."); -export const redundantImplicitActivationEvent = l10n.t("This activation event can be removed as VS Code generates these automatically from your package.json contribution declarations."); -+export const redundantImplicitActivationEvent = l10n.t("This activation event can be removed as !!APP_NAME!! generates these automatically from your package.json contribution declarations."); ++export const redundantImplicitActivationEvent = l10n.t("This activation event can be removed as BradfordCode generates these automatically from your package.json contribution declarations."); diff --git a/extensions/extension-editing/src/extensionLinter.ts b/extensions/extension-editing/src/extensionLinter.ts -index 00b153a4..b81b6428 100644 +index a6ab287a..dc928226 100644 --- a/extensions/extension-editing/src/extensionLinter.ts +++ b/extensions/extension-editing/src/extensionLinter.ts @@ -32,7 +32,7 @@ const embeddedSvgsNotValid = l10n.t("Embedded SVGs are not a valid image source. @@ -144,10 +144,10 @@ index 00b153a4..b81b6428 100644 const relativeUrlRequiresHttpsRepository = l10n.t("Relative image URLs require a repository with HTTPS protocol to be specified in the package.json."); const relativeBadgeUrlRequiresHttpsRepository = l10n.t("Relative badge URLs require a repository with HTTPS protocol to be specified in this package.json."); -const apiProposalNotListed = l10n.t("This proposal cannot be used because for this extension the product defines a fixed set of API proposals. You can test your extension but before publishing you MUST reach out to the VS Code team."); -+const apiProposalNotListed = l10n.t("This proposal cannot be used because for this extension the product defines a fixed set of API proposals. You can test your extension but before publishing you MUST reach out to the !!APP_NAME!! team."); ++const apiProposalNotListed = l10n.t("This proposal cannot be used because for this extension the product defines a fixed set of API proposals. You can test your extension but before publishing you MUST reach out to the BradfordCode team."); + const apiProposalVersionNotSupported = l10n.t("API proposal versions are no longer supported. Remove the '@' suffix."); const starActivation = l10n.t("Using '*' activation is usually a bad idea as it impacts performance."); - const parsingErrorHeader = l10n.t("Error parsing the when-clause:"); diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 147a75f9..33aeb0d4 100644 --- a/extensions/git/package.nls.json @@ -157,7 +157,7 @@ index 147a75f9..33aeb0d4 100644 "comment": [ "{Locked='](command:git.showOutput'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -167,8 +167,8 @@ index 147a75f9..33aeb0d4 100644 "config.showCommitInput": "Controls whether to show the commit input in the Git source control panel.", - "config.terminalAuthentication": "Controls whether to enable VS Code to be the authentication handler for Git processes spawned in the Integrated Terminal. Note: Terminals need to be restarted to pick up a change in this setting.", - "config.terminalGitEditor": "Controls whether to enable VS Code to be the Git editor for Git processes spawned in the integrated terminal. Note: Terminals need to be restarted to pick up a change in this setting.", -+ "config.terminalAuthentication": "Controls whether to enable !!APP_NAME!! to be the authentication handler for Git processes spawned in the Integrated Terminal. Note: Terminals need to be restarted to pick up a change in this setting.", -+ "config.terminalGitEditor": "Controls whether to enable !!APP_NAME!! to be the Git editor for Git processes spawned in the integrated terminal. Note: Terminals need to be restarted to pick up a change in this setting.", ++ "config.terminalAuthentication": "Controls whether to enable BradfordCode to be the authentication handler for Git processes spawned in the Integrated Terminal. Note: Terminals need to be restarted to pick up a change in this setting.", ++ "config.terminalGitEditor": "Controls whether to enable BradfordCode to be the Git editor for Git processes spawned in the integrated terminal. Note: Terminals need to be restarted to pick up a change in this setting.", "config.timeline.showAuthor": "Controls whether to show the commit author in the Timeline view.", "config.timeline.showUncommitted": "Controls whether to show uncommitted changes in the Timeline view.", "config.timeline.date": "Controls which date to use for items in the Timeline view.", @@ -177,7 +177,7 @@ index 147a75f9..33aeb0d4 100644 "{Locked='](command:git.showOutput'}", "{Locked='](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -186,7 +186,7 @@ index 147a75f9..33aeb0d4 100644 "{Locked='](command:git.showOutput'}", "{Locked='](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -195,7 +195,7 @@ index 147a75f9..33aeb0d4 100644 "{Locked='](command:git.showOutput'}", "{Locked='](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -204,51 +204,51 @@ index 147a75f9..33aeb0d4 100644 }, "view.workbench.scm.disabled": { - "message": "If you would like to use Git features, please enable Git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "If you would like to use Git features, please enable Git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in !!APP_NAME!! [read our docs](https://aka.ms/vscode-scm).", ++ "message": "If you would like to use Git features, please enable Git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in BradfordCode [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:workbench.action.openSettings?%5B%22git.enabled%22%5D'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.empty": { - "message": "In order to use Git features, you can open a folder containing a Git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.cloneRecursive)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "In order to use Git features, you can open a folder containing a Git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.cloneRecursive)\nTo learn more about how to use Git and source control in !!APP_NAME!! [read our docs](https://aka.ms/vscode-scm).", ++ "message": "In order to use Git features, you can open a folder containing a Git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.cloneRecursive)\nTo learn more about how to use Git and source control in BradfordCode [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:vscode.openFolder'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.folder": { - "message": "The folder currently open doesn't have a Git repository. You can initialize a repository which will enable source control features powered by Git.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "The folder currently open doesn't have a Git repository. You can initialize a repository which will enable source control features powered by Git.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use Git and source control in !!APP_NAME!! [read our docs](https://aka.ms/vscode-scm).", ++ "message": "The folder currently open doesn't have a Git repository. You can initialize a repository which will enable source control features powered by Git.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use Git and source control in BradfordCode [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:git.init?%5Btrue%5D'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.workspace": { - "message": "The workspace currently open doesn't have any folders containing Git repositories. You can initialize a repository on a folder which will enable source control features powered by Git.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "The workspace currently open doesn't have any folders containing Git repositories. You can initialize a repository on a folder which will enable source control features powered by Git.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in !!APP_NAME!! [read our docs](https://aka.ms/vscode-scm).", ++ "message": "The workspace currently open doesn't have any folders containing Git repositories. You can initialize a repository on a folder which will enable source control features powered by Git.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in BradfordCode [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:git.init'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.emptyWorkspace": { - "message": "The workspace currently open doesn't have any folders containing Git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "The workspace currently open doesn't have any folders containing Git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use Git and source control in !!APP_NAME!! [read our docs](https://aka.ms/vscode-scm).", ++ "message": "The workspace currently open doesn't have any folders containing Git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use Git and source control in BradfordCode [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:workbench.action.addRootFolder'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -257,7 +257,7 @@ index 147a75f9..33aeb0d4 100644 "{Locked='](command:git.openRepositoriesInParentFolders'}", "{Locked='](command:workbench.action.openSettings?%5B%22git.openRepositoryInParentFolders%22%5D'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -266,7 +266,7 @@ index 147a75f9..33aeb0d4 100644 "{Locked='](command:git.openRepositoriesInParentFolders'}", "{Locked='](command:workbench.action.openSettings?%5B%22git.openRepositoryInParentFolders%22%5D'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -275,7 +275,7 @@ index 147a75f9..33aeb0d4 100644 "comment": [ "{Locked='](command:git.manageUnsafeRepositories'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -284,27 +284,27 @@ index 147a75f9..33aeb0d4 100644 "comment": [ "{Locked='](command:git.manageUnsafeRepositories'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.closedRepository": { - "message": "A Git repository was found that was previously closed.\n[Reopen Closed Repository](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "A Git repository was found that was previously closed.\n[Reopen Closed Repository](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in !!APP_NAME!! [read our docs](https://aka.ms/vscode-scm).", ++ "message": "A Git repository was found that was previously closed.\n[Reopen Closed Repository](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in BradfordCode [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:git.reopenClosedRepositories'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.closedRepositories": { - "message": "Git repositories were found that were previously closed.\n[Reopen Closed Repositories](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "Git repositories were found that were previously closed.\n[Reopen Closed Repositories](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in !!APP_NAME!! [read our docs](https://aka.ms/vscode-scm).", ++ "message": "Git repositories were found that were previously closed.\n[Reopen Closed Repositories](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in BradfordCode [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:git.reopenClosedRepositories'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -313,12 +313,12 @@ index 147a75f9..33aeb0d4 100644 "comment": [ "{Locked='](command:git.clone'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, - "view.workbench.learnMore": "To learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm)." -+ "view.workbench.learnMore": "To learn more about how to use Git and source control in !!APP_NAME!! [read our docs](https://aka.ms/vscode-scm)." ++ "view.workbench.learnMore": "To learn more about how to use Git and source control in BradfordCode [read our docs](https://aka.ms/vscode-scm)." } diff --git a/extensions/github/package.nls.json b/extensions/github/package.nls.json index 04497595..aa29d00c 100644 @@ -328,7 +328,7 @@ index 04497595..aa29d00c 100644 { "displayName": "GitHub", - "description": "GitHub features for VS Code", -+ "description": "GitHub features for !!APP_NAME!!", ++ "description": "GitHub features for BradfordCode", "command.copyVscodeDevLink": "Copy vscode.dev Link", "command.publish": "Publish to GitHub", "command.openOnGitHub": "Open on GitHub", @@ -337,7 +337,7 @@ index 04497595..aa29d00c 100644 "command.openPullRequest": "Open PR", "config.branchProtection": "Controls whether to query repository rules for GitHub repositories", - "config.gitAuthentication": "Controls whether to enable automatic GitHub authentication for git commands within VS Code.", -+ "config.gitAuthentication": "Controls whether to enable automatic GitHub authentication for git commands within !!APP_NAME!!.", ++ "config.gitAuthentication": "Controls whether to enable automatic GitHub authentication for git commands within BradfordCode.", "config.gitProtocol": "Controls which protocol is used to clone a GitHub repository", "config.showAvatar": "Controls whether to show the GitHub avatar of the commit author in various hovers (ex: Git blame, Timeline, Source Control Graph, etc.)", "welcome.publishFolder": { @@ -346,7 +346,7 @@ index 04497595..aa29d00c 100644 "Do not translate '$(github)'. It will be rendered as an icon", "{Locked='](command:github.publish'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -355,7 +355,7 @@ index 04497595..aa29d00c 100644 "Do not translate '$(github)'. It will be rendered as an icon", "{Locked='](command:github.publish'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] } @@ -367,8 +367,8 @@ index 789a579c..7dae48d2 100644 { - "description": "Extension to add Grunt capabilities to VS Code.", - "displayName": "Grunt support for VS Code", -+ "description": "Extension to add Grunt capabilities to !!APP_NAME!!.", -+ "displayName": "Grunt support for !!APP_NAME!!", ++ "description": "Extension to add Grunt capabilities to BradfordCode.", ++ "displayName": "Grunt support for BradfordCode", "config.grunt.autoDetect": "Controls enablement of Grunt task detection. Grunt task detection can cause files in any open workspace to be executed.", "grunt.taskDefinition.type.description": "The Grunt task to customize.", "grunt.taskDefinition.args.description": "Command line arguments to pass to the grunt task", @@ -381,7 +381,7 @@ index cfb91c1f..ba6e0733 100644 activeEditorListener.dispose(); const configure = l10n.t('Configure'); - const res = await window.showInformationMessage(l10n.t('VS Code now has built-in support for auto-renaming tags. Do you want to enable it?'), configure); -+ const res = await window.showInformationMessage(l10n.t('!!APP_NAME!! now has built-in support for auto-renaming tags. Do you want to enable it?'), configure); ++ const res = await window.showInformationMessage(l10n.t('BradfordCode now has built-in support for auto-renaming tags. Do you want to enable it?'), configure); if (res === configure) { commands.executeCommand('workbench.action.openSettings', SettingIds.linkedEditing); } @@ -394,7 +394,7 @@ index d8390703..b4185c22 100644 "displayName": "HTML Language Features", "description": "Provides rich language support for HTML and Handlebar files", - "html.customData.desc": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-html-languageservice/blob/master/docs/customData.md).\n\nVS Code loads custom data on startup to enhance its HTML support for the custom HTML tags, attributes and attribute values you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", -+ "html.customData.desc": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-html-languageservice/blob/master/docs/customData.md).\n\n!!APP_NAME!! loads custom data on startup to enhance its HTML support for the custom HTML tags, attributes and attribute values you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", ++ "html.customData.desc": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-html-languageservice/blob/master/docs/customData.md).\n\nBradfordCode loads custom data on startup to enhance its HTML support for the custom HTML tags, attributes and attribute values you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", "html.format.enable.desc": "Enable/disable default HTML formatter.", "html.format.wrapLineLength.desc": "Maximum amount of characters per line (0 = disable).", "html.format.unformatted.desc": "List of tags, comma separated, that shouldn't be reformatted. `null` defaults to all tags listed at https://www.w3.org/TR/html5/dom.html#phrasing-content.", @@ -403,7 +403,7 @@ index d8390703..b4185c22 100644 "html.suggest.html5.desc": "Controls whether the built-in HTML language support suggests HTML5 tags, properties and values.", "html.suggest.hideEndTagSuggestions.desc": "Controls whether the built-in HTML language support suggests closing tags. When disabled, end tag completions like `` will not be shown.", - "html.trace.server.desc": "Traces the communication between VS Code and the HTML language server.", -+ "html.trace.server.desc": "Traces the communication between !!APP_NAME!! and the HTML language server.", ++ "html.trace.server.desc": "Traces the communication between BradfordCode and the HTML language server.", "html.validate.scripts": "Controls whether the built-in HTML language support validates embedded scripts.", "html.validate.styles": "Controls whether the built-in HTML language support validates embedded styles.", "html.autoCreateQuotes": "Enable/disable auto creation of quotes for HTML attribute assignment. The type of quotes can be configured by `#html.completion.attributeDefaultValue#`.", @@ -416,7 +416,7 @@ index 205143c3..cd246290 100644 "html.customData": { "type": "array", - "markdownDescription": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-html-languageservice/blob/master/docs/customData.md).\n\nVS Code loads custom data on startup to enhance its HTML support for the custom HTML tags, attributes and attribute values you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", -+ "markdownDescription": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-html-languageservice/blob/master/docs/customData.md).\n\n!!APP_NAME!! loads custom data on startup to enhance its HTML support for the custom HTML tags, attributes and attribute values you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", ++ "markdownDescription": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-html-languageservice/blob/master/docs/customData.md).\n\nBradfordCode loads custom data on startup to enhance its HTML support for the custom HTML tags, attributes and attribute values you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", "items": { "type": "string", "description": "Relative path to a HTML custom data file" @@ -428,8 +428,8 @@ index e82030ef..d9706343 100644 { - "description": "Extension to add Jake capabilities to VS Code.", - "displayName": "Jake support for VS Code", -+ "description": "Extension to add Jake capabilities to !!APP_NAME!!.", -+ "displayName": "Jake support for !!APP_NAME!!", ++ "description": "Extension to add Jake capabilities to BradfordCode.", ++ "displayName": "Jake support for BradfordCode", "jake.taskDefinition.type.description": "The Jake task to customize.", "jake.taskDefinition.file.description": "The Jake file that provides the task. Can be omitted.", "config.jake.autoDetect": "Controls enablement of Jake task detection. Jake task detection can cause files in any open workspace to be executed." @@ -442,7 +442,7 @@ index 30199b2b..6e3820d5 100644 "json.format.keepLines.desc": "Keep all existing new lines when formatting.", "json.validate.enable.desc": "Enable/disable JSON validation.", - "json.tracing.desc": "Traces the communication between VS Code and the JSON language server.", -+ "json.tracing.desc": "Traces the communication between !!APP_NAME!! and the JSON language server.", ++ "json.tracing.desc": "Traces the communication between BradfordCode and the JSON language server.", "json.colorDecorators.enable.desc": "Enables or disables color decorators", "json.colorDecorators.enable.deprecationMessage": "The setting `json.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`.", "json.schemaResolutionErrorMessage": "Unable to resolve schema.", @@ -455,7 +455,7 @@ index cb292ba5..0855e6a0 100644 "markdown.showPreviewSecuritySelector.title": "Change Preview Security Settings", "markdown.trace.extension.desc": "Enable debug logging for the Markdown extension.", - "markdown.trace.server.desc": "Traces the communication between VS Code and the Markdown language server.", -+ "markdown.trace.server.desc": "Traces the communication between !!APP_NAME!! and the Markdown language server.", ++ "markdown.trace.server.desc": "Traces the communication between BradfordCode and the Markdown language server.", "markdown.server.log.desc": "Controls the logging level of the Markdown language server.", "markdown.preview.refresh.title": "Refresh Preview", "markdown.preview.toggleLock.title": "Toggle Preview Locking", @@ -464,7 +464,7 @@ index cb292ba5..0855e6a0 100644 "message": "Configures the path and file name of files created by copy/paste or drag and drop. This is a map of globs that match against a Markdown document path to the destination path where the new file should be created.\n\nThe destination path may use the following variables:\n\n- `${documentDirName}` — Absolute parent directory path of the Markdown document, e.g. `/Users/me/myProject/docs`.\n- `${documentRelativeDirName}` — Relative parent directory path of the Markdown document, e.g. `docs`. This is the same as `${documentDirName}` if the file is not part of a workspace.\n- `${documentFileName}` — The full filename of the Markdown document, e.g. `README.md`.\n- `${documentBaseName}` — The basename of the Markdown document, e.g. `README`.\n- `${documentExtName}` — The extension of the Markdown document, e.g. `md`.\n- `${documentFilePath}` — Absolute path of the Markdown document, e.g. `/Users/me/myProject/docs/README.md`.\n- `${documentRelativeFilePath}` — Relative path of the Markdown document, e.g. `docs/README.md`. This is the same as `${documentFilePath}` if the file is not part of a workspace.\n- `${documentWorkspaceFolder}` — The workspace folder for the Markdown document, e.g. `/Users/me/myProject`. This is the same as `${documentDirName}` if the file is not part of a workspace.\n- `${fileName}` — The file name of the dropped file, e.g. `image.png`.\n- `${fileExtName}` — The extension of the dropped file, e.g. `png`.\n- `${unixTime}` — The current Unix timestamp in milliseconds.\n- `${isoTime}` — The current time in ISO 8601 format, e.g. '2025-06-06T08:40:32.123Z'.", "comment": [ - "This setting is use the user drops or pastes image data into the editor. In this case, VS Code automatically creates a new image file in the workspace containing the dropped/pasted image.", -+ "This setting is use the user drops or pastes image data into the editor. In this case, !!APP_NAME!! automatically creates a new image file in the workspace containing the dropped/pasted image.", ++ "This setting is use the user drops or pastes image data into the editor. In this case, BradfordCode automatically creates a new image file in the workspace containing the dropped/pasted image.", "It's easier to explain this setting with an example. For example, let's say the setting value was:", "", "{ 'docs/*.md': '${documentDirName}/images/${fileName}' }", @@ -476,7 +476,7 @@ index 920ced76..f7ca6a48 100644 { "displayName": "Media Preview", - "description": "Provides VS Code's built-in previews for images, audio, and video", -+ "description": "Provides !!APP_NAME!!'s built-in previews for images, audio, and video", ++ "description": "Provides BradfordCode's built-in previews for images, audio, and video", "customEditor.audioPreview.displayName": "Audio Preview", "customEditor.imagePreview.displayName": "Image Preview", "customEditor.videoPreview.displayName": "Video Preview", @@ -489,12 +489,12 @@ index 01aaf33a..639d9ba9 100644

${vscode.l10n.t("An error occurred while loading the audio file.")}

- ${vscode.l10n.t("Open file using VS Code's standard text/binary editor?")} -+ ${vscode.l10n.t("Open file using !!APP_NAME!!'s standard text/binary editor?")} ++ ${vscode.l10n.t("Open file using BradfordCode's standard text/binary editor?")}

${vscode.l10n.t("The audio file is stored with Git LFS and is not available for preview.")}

- ${vscode.l10n.t("Open file using VS Code's standard text/binary editor?")} -+ ${vscode.l10n.t("Open file using !!APP_NAME!!'s standard text/binary editor?")} ++ ${vscode.l10n.t("Open file using BradfordCode's standard text/binary editor?")}
@@ -507,12 +507,12 @@ index 79cb70c9..cac55284 100644

${vscode.l10n.t("An error occurred while loading the image.")}

- ${vscode.l10n.t("Open file using VS Code's standard text/binary editor?")} -+ ${vscode.l10n.t("Open file using !!APP_NAME!!'s standard text/binary editor?")} ++ ${vscode.l10n.t("Open file using BradfordCode's standard text/binary editor?")}

${vscode.l10n.t("The image is stored with Git LFS and is not available for preview.")}

- ${vscode.l10n.t("Open file using VS Code's standard text/binary editor?")} -+ ${vscode.l10n.t("Open file using !!APP_NAME!!'s standard text/binary editor?")} ++ ${vscode.l10n.t("Open file using BradfordCode's standard text/binary editor?")}
@@ -525,12 +525,12 @@ index 2ee1169f..79c54036 100644

${vscode.l10n.t("An error occurred while loading the video file.")}

- ${vscode.l10n.t("Open file using VS Code's standard text/binary editor?")} -+ ${vscode.l10n.t("Open file using !!APP_NAME!!'s standard text/binary editor?")} ++ ${vscode.l10n.t("Open file using BradfordCode's standard text/binary editor?")}

${vscode.l10n.t("The video file is stored with Git LFS and is not available for preview.")}

- ${vscode.l10n.t("Open file using VS Code's standard text/binary editor?")} -+ ${vscode.l10n.t("Open file using !!APP_NAME!!'s standard text/binary editor?")} ++ ${vscode.l10n.t("Open file using BradfordCode's standard text/binary editor?")}
@@ -543,19 +543,19 @@ index 032dd35d..0b893063 100644 "id": "vscode.builtin-renderer", "entrypoint": "./renderer-out/index.js", - "displayName": "VS Code Builtin Notebook Output Renderer", -+ "displayName": "!!APP_NAME!! Builtin Notebook Output Renderer", ++ "displayName": "BradfordCode Builtin Notebook Output Renderer", "requiresMessaging": "never", "mimeTypes": [ "image/gif", diff --git a/extensions/npm/package.nls.json b/extensions/npm/package.nls.json -index 1235a551..451c90f0 100644 +index 5c77708d..8a988891 100644 --- a/extensions/npm/package.nls.json +++ b/extensions/npm/package.nls.json @@ -1,6 +1,6 @@ { "description": "Extension to add task support for npm scripts.", - "displayName": "NPM support for VS Code", -+ "displayName": "NPM support for !!APP_NAME!!", ++ "displayName": "NPM support for BradfordCode", "workspaceTrust": "This extension executes tasks, which require trust to run.", "virtualWorkspaces": "Functionality that requires running the 'npm' command is not available in virtual workspaces.", "config.npm.autoDetect": "Controls whether npm scripts should be automatically detected.", @@ -568,7 +568,7 @@ index d52cabb8..f4f26d79 100644 "patterns": [ { - "comment": "The simpler (?<=\\bProcess\\.|\\bCommandLine\\.) breaks VS Code / Atom, see https://github.com/textmate/swift.tmbundle/issues/29", -+ "comment": "The simpler (?<=\\bProcess\\.|\\bCommandLine\\.) breaks !!APP_NAME!! / Atom, see https://github.com/textmate/swift.tmbundle/issues/29", ++ "comment": "The simpler (?<=\\bProcess\\.|\\bCommandLine\\.) breaks BradfordCode / Atom, see https://github.com/textmate/swift.tmbundle/issues/29", "name": "support.variable.swift", "match": "(?<=^Process\\.|\\WProcess\\.|^CommandLine\\.|\\WCommandLine\\.)(arguments|argc|unsafeArgv)" }, @@ -581,10 +581,10 @@ index a697f8c0..f55ecc02 100644 "configuration.tsserver.experimental.enableProjectDiagnostics": "Enables project wide error reporting.", "configuration.tsserver.experimental.enableProjectDiagnostics.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.tsserver.experimental.enableProjectDiagnostics#` instead.", - "typescript.locale": "Sets the locale used to report JavaScript and TypeScript errors. Defaults to use VS Code's locale.", -+ "typescript.locale": "Sets the locale used to report JavaScript and TypeScript errors. Defaults to use !!APP_NAME!!'s locale.", ++ "typescript.locale": "Sets the locale used to report JavaScript and TypeScript errors. Defaults to use BradfordCode's locale.", "typescript.locale.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.locale#` instead.", - "typescript.locale.auto": "Use VS Code's configured display language.", -+ "typescript.locale.auto": "Use !!APP_NAME!!'s configured display language.", ++ "typescript.locale.auto": "Use BradfordCode's configured display language.", "configuration.implicitProjectConfig.module": "Sets the module system for the program. See more: https://www.typescriptlang.org/tsconfig#module.", "configuration.implicitProjectConfig.target": "Set target JavaScript language version for emitted JavaScript and include library declarations. See more: https://www.typescriptlang.org/tsconfig#target.", "configuration.implicitProjectConfig.checkJs": "Enable/disable semantic checking of JavaScript files. Existing `jsconfig.json` or `tsconfig.json` files override this setting.", @@ -593,7 +593,7 @@ index a697f8c0..f55ecc02 100644 "typescript.workspaceSymbols.excludeLibrarySymbols": "Exclude symbols that come from library files in `Go to Symbol in Workspace` results. Requires using TypeScript 5.3+ in the workspace.", "configuration.workspaceSymbols.excludeLibrarySymbols.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.workspaceSymbols.excludeLibrarySymbols#` instead.", - "typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in VS Code.", -+ "typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in !!APP_NAME!!.", ++ "typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in BradfordCode.", "typescript.updateImportsOnFileMove.enabled.prompt": "Prompt on each rename.", "typescript.updateImportsOnFileMove.enabled.always": "Always update paths automatically.", "typescript.updateImportsOnFileMove.enabled.never": "Never rename paths and don't prompt.", @@ -602,10 +602,10 @@ index a697f8c0..f55ecc02 100644 "configuration.suggest.jsdoc.enabled": "Enable/disable suggestion to complete JSDoc comments.", "configuration.suggest.completeJSDocs.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.suggest.jsdoc.enabled#` instead.", - "configuration.tsserver.useVsCodeWatcher": "Use VS Code's file watchers instead of TypeScript's. Requires using TypeScript 5.4+ in the workspace.", -+ "configuration.tsserver.useVsCodeWatcher": "Use !!APP_NAME!!'s file watchers instead of TypeScript's. Requires using TypeScript 5.4+ in the workspace.", ++ "configuration.tsserver.useVsCodeWatcher": "Use BradfordCode's file watchers instead of TypeScript's. Requires using TypeScript 5.4+ in the workspace.", "configuration.tsserver.watchOptions": "Configure which watching strategies should be used to keep track of files and directories.", - "configuration.tsserver.watchOptions.vscode": "Use VS Code's file watchers instead of TypeScript's. Requires using TypeScript 5.4+ in the workspace.", -+ "configuration.tsserver.watchOptions.vscode": "Use !!APP_NAME!!'s file watchers instead of TypeScript's. Requires using TypeScript 5.4+ in the workspace.", ++ "configuration.tsserver.watchOptions.vscode": "Use BradfordCode's file watchers instead of TypeScript's. Requires using TypeScript 5.4+ in the workspace.", "configuration.tsserver.watchOptions.watchFile": "Strategy for how individual files are watched.", "configuration.tsserver.watchOptions.watchFile.fixedChunkSizePolling": "Polls files in chunks at regular interval.", "configuration.tsserver.watchOptions.watchFile.fixedPollingInterval": "Check every file for changes several times a second at a fixed interval.", @@ -614,14 +614,14 @@ index a697f8c0..f55ecc02 100644 "configuration.suggest.objectLiteralMethodSnippets.enabled": "Enable/disable snippet completions for methods in object literals.", "configuration.suggest.objectLiteralMethodSnippets.enabled.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.suggest.objectLiteralMethodSnippets.enabled#` instead.", - "configuration.tsserver.web.projectWideIntellisense.enabled": "Enable/disable project-wide IntelliSense on web. Requires that VS Code is running in a trusted context.", -+ "configuration.tsserver.web.projectWideIntellisense.enabled": "Enable/disable project-wide IntelliSense on web. Requires that !!APP_NAME!! is running in a trusted context.", ++ "configuration.tsserver.web.projectWideIntellisense.enabled": "Enable/disable project-wide IntelliSense on web. Requires that BradfordCode is running in a trusted context.", "configuration.tsserver.web.projectWideIntellisense.enabled.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.tsserver.web.projectWideIntellisense.enabled#` instead.", "configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors": "Suppresses semantic errors on web even when project wide IntelliSense is enabled. This is always on when project wide IntelliSense is not enabled or available. See `#js/ts.tsserver.web.projectWideIntellisense.enabled#`", "configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.tsserver.web.projectWideIntellisense.suppressSemanticErrors#` instead.", "configuration.tsserver.web.typeAcquisition.enabled": "Enable/disable package acquisition on the web. This enables IntelliSense for imported packages. Requires `#js/ts.tsserver.web.projectWideIntellisense.enabled#`. Currently not supported for Safari.", "configuration.tsserver.web.typeAcquisition.enabled.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.tsserver.web.typeAcquisition.enabled#` instead.", - "configuration.tsserver.nodePath": "Run TS Server on a custom Node installation. This can be a path to a Node executable, or `node` if you want VS Code to detect a Node installation.", -+ "configuration.tsserver.nodePath": "Run TS Server on a custom Node installation. This can be a path to a Node executable, or `node` if you want !!APP_NAME!! to detect a Node installation.", ++ "configuration.tsserver.nodePath": "Run TS Server on a custom Node installation. This can be a path to a Node executable, or `node` if you want BradfordCode to detect a Node installation.", "configuration.tsserver.nodePath.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.tsserver.node.path#` instead.", "configuration.tsserver.watchOptions.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.tsserver.watchOptions#` instead.", "configuration.updateImportsOnPaste": "Automatically update imports when pasting code. Requires TypeScript 5.6+.", @@ -631,16 +631,16 @@ index a697f8c0..f55ecc02 100644 "walkthroughs.nodejsWelcome.debugJsFile.title": "Run and Debug your JavaScript", - "walkthroughs.nodejsWelcome.debugJsFile.description": "Once you've installed Node.js, you can run JavaScript programs at a terminal by entering ``node your-file-name.js``\nAnother easy way to run Node.js programs is by using VS Code's debugger which lets you run your code, pause at different points, and help you understand what's going on step-by-step.\n[Start Debugging](command:javascript-walkthrough.commands.debugJsFile)", - "walkthroughs.nodejsWelcome.debugJsFile.altText": "Debug and run your JavaScript code in Node.js with Visual Studio Code.", -+ "walkthroughs.nodejsWelcome.debugJsFile.description": "Once you've installed Node.js, you can run JavaScript programs at a terminal by entering ``node your-file-name.js``\nAnother easy way to run Node.js programs is by using !!APP_NAME!!'s debugger which lets you run your code, pause at different points, and help you understand what's going on step-by-step.\n[Start Debugging](command:javascript-walkthrough.commands.debugJsFile)", -+ "walkthroughs.nodejsWelcome.debugJsFile.altText": "Debug and run your JavaScript code in Node.js with !!APP_NAME!!.", ++ "walkthroughs.nodejsWelcome.debugJsFile.description": "Once you've installed Node.js, you can run JavaScript programs at a terminal by entering ``node your-file-name.js``\nAnother easy way to run Node.js programs is by using BradfordCode's debugger which lets you run your code, pause at different points, and help you understand what's going on step-by-step.\n[Start Debugging](command:javascript-walkthrough.commands.debugJsFile)", ++ "walkthroughs.nodejsWelcome.debugJsFile.altText": "Debug and run your JavaScript code in Node.js with BradfordCode.", "walkthroughs.nodejsWelcome.learnMoreAboutJs.title": "Explore More", - "walkthroughs.nodejsWelcome.learnMoreAboutJs.description": "Want to get more comfortable with JavaScript, Node.js, and VS Code? Be sure to check out our docs!\nWe've got lots of resources for learning [JavaScript](https://code.visualstudio.com/docs/nodejs/working-with-javascript) and [Node.js](https://code.visualstudio.com/docs/nodejs/nodejs-tutorial).\n\n[Learn More](https://code.visualstudio.com/docs/nodejs/nodejs-tutorial)", - "walkthroughs.nodejsWelcome.learnMoreAboutJs.altText": "Learn more about JavaScript and Node.js in Visual Studio Code." -+ "walkthroughs.nodejsWelcome.learnMoreAboutJs.description": "Want to get more comfortable with JavaScript, Node.js, and !!APP_NAME!!? Be sure to check out our docs!\nWe've got lots of resources for learning [JavaScript](https://code.visualstudio.com/docs/nodejs/working-with-javascript) and [Node.js](https://code.visualstudio.com/docs/nodejs/nodejs-tutorial).\n\n[Learn More](https://code.visualstudio.com/docs/nodejs/nodejs-tutorial)", -+ "walkthroughs.nodejsWelcome.learnMoreAboutJs.altText": "Learn more about JavaScript and Node.js in !!APP_NAME!!." ++ "walkthroughs.nodejsWelcome.learnMoreAboutJs.description": "Want to get more comfortable with JavaScript, Node.js, and BradfordCode? Be sure to check out our docs!\nWe've got lots of resources for learning [JavaScript](https://code.visualstudio.com/docs/nodejs/working-with-javascript) and [Node.js](https://code.visualstudio.com/docs/nodejs/nodejs-tutorial).\n\n[Learn More](https://code.visualstudio.com/docs/nodejs/nodejs-tutorial)", ++ "walkthroughs.nodejsWelcome.learnMoreAboutJs.altText": "Learn more about JavaScript and Node.js in BradfordCode." } diff --git a/extensions/typescript-language-features/src/tsServer/versionManager.ts b/extensions/typescript-language-features/src/tsServer/versionManager.ts -index 8d99637d..5114c543 100644 +index 1154a29c..a42a1d25 100644 --- a/extensions/typescript-language-features/src/tsServer/versionManager.ts +++ b/extensions/typescript-language-features/src/tsServer/versionManager.ts @@ -110,7 +110,7 @@ export class TypeScriptVersionManager extends Disposable { @@ -648,7 +648,7 @@ index 8d99637d..5114c543 100644 label: (!this.useWorkspaceTsdkSetting || !vscode.workspace.isTrusted ? '• ' - : '') + vscode.l10n.t("Use VS Code's Version"), -+ : '') + vscode.l10n.t("Use !!APP_NAME!!'s Version"), ++ : '') + vscode.l10n.t("Use BradfordCode's Version"), description: bundledVersion.displayName, detail: bundledVersion.pathLabel, run: async () => { @@ -661,7 +661,7 @@ index 12cb1cca..6b681ae1 100644 } - vscode.window.showErrorMessage(vscode.l10n.t("VS Code\'s tsserver was deleted by another application such as a misbehaving virus detection tool. Please reinstall VS Code.")); -+ vscode.window.showErrorMessage(vscode.l10n.t("!!APP_NAME!!\'s tsserver was deleted by another application such as a misbehaving virus detection tool. Please reinstall !!APP_NAME!!.")); ++ vscode.window.showErrorMessage(vscode.l10n.t("BradfordCode\'s tsserver was deleted by another application such as a misbehaving virus detection tool. Please reinstall BradfordCode.")); throw new Error('Could not find bundled tsserver.js'); } @@ -674,7 +674,7 @@ index 823985e9..a4a18aa8 100644 if (!rootPath) { vscode.window.showInformationMessage( - vscode.l10n.t("Please open a folder in VS Code to use a TypeScript or JavaScript project")); -+ vscode.l10n.t("Please open a folder in !!APP_NAME!! to use a TypeScript or JavaScript project")); ++ vscode.l10n.t("Please open a folder in BradfordCode to use a TypeScript or JavaScript project")); return; } @@ -687,7 +687,7 @@ index ae18dff7..fb9a8791 100644 if (this.pluginManager.plugins.length) { prompt = vscode.window.showErrorMessage( - vscode.l10n.t("The JS/TS language service immediately crashed 5 times. The service will not be restarted.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against VS Code.", pluginExtensionList)); -+ vscode.l10n.t("The JS/TS language service immediately crashed 5 times. The service will not be restarted.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against !!APP_NAME!!.", pluginExtensionList)); ++ vscode.l10n.t("The JS/TS language service immediately crashed 5 times. The service will not be restarted.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against BradfordCode.", pluginExtensionList)); } else { prompt = vscode.window.showErrorMessage( vscode.l10n.t("The JS/TS language service immediately crashed 5 times. The service will not be restarted."), @@ -696,7 +696,7 @@ index ae18dff7..fb9a8791 100644 if (this.pluginManager.plugins.length) { prompt = vscode.window.showWarningMessage( - vscode.l10n.t("The JS/TS language service crashed 5 times in the last 5 Minutes.\nThis may be caused by a plugin contributed by one of these extensions: {0}\nPlease try disabling these extensions before filing an issue against VS Code.", pluginExtensionList)); -+ vscode.l10n.t("The JS/TS language service crashed 5 times in the last 5 Minutes.\nThis may be caused by a plugin contributed by one of these extensions: {0}\nPlease try disabling these extensions before filing an issue against !!APP_NAME!!.", pluginExtensionList)); ++ vscode.l10n.t("The JS/TS language service crashed 5 times in the last 5 Minutes.\nThis may be caused by a plugin contributed by one of these extensions: {0}\nPlease try disabling these extensions before filing an issue against BradfordCode.", pluginExtensionList)); } else { prompt = vscode.window.showWarningMessage( vscode.l10n.t("The JS/TS language service crashed 5 times in the last 5 Minutes."), @@ -705,7 +705,7 @@ index ae18dff7..fb9a8791 100644 if (this.pluginManager.plugins.length) { prompt = vscode.window.showWarningMessage( - vscode.l10n.t("The JS/TS language service crashed.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against VS Code.", pluginExtensionList)); -+ vscode.l10n.t("The JS/TS language service crashed.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against !!APP_NAME!!.", pluginExtensionList)); ++ vscode.l10n.t("The JS/TS language service crashed.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against BradfordCode.", pluginExtensionList)); } else { prompt = vscode.window.showWarningMessage( vscode.l10n.t("The JS/TS language service crashed."), @@ -717,7 +717,7 @@ index d56cbf59..705fca8b 100644 { "name": "vscode-api-tests", - "description": "API tests for VS Code", -+ "description": "API tests for !!APP_NAME!!", ++ "description": "API tests for BradfordCode", "version": "0.0.1", "publisher": "vscode", "license": "MIT", @@ -729,7 +729,7 @@ index f3253b70..af413e79 100644 { "name": "vscode-colorize-tests", - "description": "Colorize tests for VS Code", -+ "description": "Colorize tests for !!APP_NAME!!", ++ "description": "Colorize tests for BradfordCode", "version": "0.0.1", "publisher": "vscode", "license": "MIT", @@ -739,7 +739,7 @@ index a0006d85..3947d588 100644 +++ b/extensions/vscode-colorize-tests/test/colorize-fixtures/14119.less @@ -1,3 +1,3 @@ -#f(@hm: "broken highlighting in VS Code") { -+#f(@hm: "broken highlighting in !!APP_NAME!!") { ++#f(@hm: "broken highlighting in BradfordCode") { content: ""; } \ No newline at end of file @@ -752,7 +752,7 @@ index 313fe87c..a9608805 100644 }, { - "c": "broken highlighting in VS Code", -+ "c": "broken highlighting in !!APP_NAME!!", ++ "c": "broken highlighting in BradfordCode", "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less string.quoted.double.less", "r": { "dark_plus": "string: #CE9178", @@ -764,7 +764,7 @@ index 9b0de50d..27b109e8 100644 { "name": "vscode-test-resolver", - "description": "Test resolver for VS Code", -+ "description": "Test resolver for !!APP_NAME!!", ++ "description": "Test resolver for BradfordCode", "version": "0.0.1", "publisher": "vscode", "license": "MIT", @@ -777,20 +777,20 @@ index a351aa77..8d4e933d 100644 return new Promise((resolve, reject) => { - log(`Downloading VS Code Server from: ${downloadUrl}`); -+ log(`Downloading !!APP_NAME!! Server from: ${downloadUrl}`); ++ log(`Downloading BradfordCode Server from: ${downloadUrl}`); const requestOptions: https.RequestOptions = parseUrl(downloadUrl); https.get(requestOptions, res => { if (res.statusCode !== 302) { - reject('Failed to get VS Code server archive location'); -+ reject('Failed to get !!APP_NAME!! server archive location'); ++ reject('Failed to get BradfordCode server archive location'); res.resume(); // read the rest of the response data and discard it return; } const archiveUrl = res.headers.location; if (!archiveUrl) { - reject('Failed to get VS Code server archive location'); -+ reject('Failed to get !!APP_NAME!! server archive location'); ++ reject('Failed to get BradfordCode server archive location'); res.resume(); // read the rest of the response data and discard it return; } @@ -799,7 +799,7 @@ index a351aa77..8d4e933d 100644 /** - * Unzip a .zip or .tar.gz VS Code archive -+ * Unzip a .zip or .tar.gz !!APP_NAME!! archive ++ * Unzip a .zip or .tar.gz BradfordCode archive */ function unzipVSCodeServer(vscodeArchivePath: string, extractDir: string, destDir: string, log: (messsage: string) => void) { log(`Extracting ${vscodeArchivePath}`); @@ -808,7 +808,7 @@ index a351aa77..8d4e933d 100644 log(`Found ${extractDir}. Skipping download.`); } else { - log(`Downloading VS Code Server ${quality} - ${commit} into ${extractDir}.`); -+ log(`Downloading !!APP_NAME!! Server ${quality} - ${commit} into ${extractDir}.`); ++ log(`Downloading BradfordCode Server ${quality} - ${commit} into ${extractDir}.`); try { const vscodeArchivePath = await downloadVSCodeServerArchive(updateUrl, commit, quality, destDir, log); if (fs.existsSync(vscodeArchivePath)) { @@ -817,57 +817,57 @@ index a351aa77..8d4e933d 100644 } } catch (err) { - throw Error(`Failed to download and unzip VS Code ${quality} - ${commit}`); -+ throw Error(`Failed to download and unzip !!APP_NAME!! ${quality} - ${commit}`); ++ throw Error(`Failed to download and unzip BradfordCode ${quality} - ${commit}`); } } return Promise.resolve(extractDir); diff --git a/extensions/vscode-test-resolver/src/extension.ts b/extensions/vscode-test-resolver/src/extension.ts -index 373c8809..a8af4574 100644 +index 5c9b4785..dfe21380 100644 --- a/extensions/vscode-test-resolver/src/extension.ts +++ b/extensions/vscode-test-resolver/src/extension.ts -@@ -209,7 +209,7 @@ export function activate(context: vscode.ExtensionContext) { +@@ -213,7 +213,7 @@ export function activate(context: vscode.ExtensionContext) { let serverLocation = env['VSCODE_REMOTE_SERVER_PATH']; // support environment variable to specify location of server on disk if (!serverLocation) { const serverBin = path.join(remoteDataDir, 'bin'); - progress.report({ message: 'Installing VSCode Server' }); -+ progress.report({ message: 'Installing !!APP_NAME!! Server' }); ++ progress.report({ message: 'Installing BradfordCode Server' }); serverLocation = await downloadAndUnzipVSCodeServer(updateUrl, commit, quality, serverBin, m => outputChannel.appendLine(m)); } diff --git a/src/main.ts b/src/main.ts -index e92fe6af..48d1734c 100644 +index f70cf87e..a3aa84bf 100644 --- a/src/main.ts +++ b/src/main.ts -@@ -415,16 +415,16 @@ function createDefaultArgvConfigSync(argvConfigPath: string): void { +@@ -419,16 +419,16 @@ function createDefaultArgvConfigSync(argvConfigPath: string): void { // Default argv content const defaultArgvConfigContent = [ - '// This configuration file allows you to pass permanent command line arguments to VS Code.', -+ '// This configuration file allows you to pass permanent command line arguments to !!APP_NAME!!.', ++ '// This configuration file allows you to pass permanent command line arguments to BradfordCode.', '// Only a subset of arguments is currently supported to reduce the likelihood of breaking', '// the installation.', '//', '// PLEASE DO NOT CHANGE WITHOUT UNDERSTANDING THE IMPACT', '//', - '// NOTE: Changing this file requires a restart of VS Code.', -+ '// NOTE: Changing this file requires a restart of !!APP_NAME!!.', ++ '// NOTE: Changing this file requires a restart of BradfordCode.', '{', ' // Use software rendering instead of hardware accelerated rendering.', - ' // This can help in cases where you see rendering issues in VS Code.', -+ ' // This can help in cases where you see rendering issues in !!APP_NAME!!.', ++ ' // This can help in cases where you see rendering issues in BradfordCode.', ' // "disable-hardware-acceleration": true', '}' ]; diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts -index 9a1041f1..50934b0c 100644 +index 811673d0..6860fa57 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts -@@ -604,7 +604,7 @@ export class CodeApplication extends Disposable { +@@ -608,7 +608,7 @@ export class CodeApplication extends Disposable { } async startup(): Promise { - this.logService.debug('Starting VS Code'); -+ this.logService.debug('Starting !!APP_NAME!!'); ++ this.logService.debug('Starting BradfordCode'); this.logService.debug(`from: ${this.environmentMainService.appRoot}`); this.logService.debug('args:', this.environmentMainService.args); @@ -880,7 +880,7 @@ index a200bf81..7572f34b 100644 super({ id: ToggleTabFocusModeAction.ID, - title: nls.localize2({ key: 'toggle.tabMovesFocus', comment: ['Turn on/off use of tab key for moving focus around VS Code'] }, 'Toggle Tab Key Moves Focus'), -+ title: nls.localize2({ key: 'toggle.tabMovesFocus', comment: ['Turn on/off use of tab key for moving focus around !!APP_NAME!!'] }, 'Toggle Tab Key Moves Focus'), ++ title: nls.localize2({ key: 'toggle.tabMovesFocus', comment: ['Turn on/off use of tab key for moving focus around BradfordCode'] }, 'Toggle Tab Key Moves Focus'), precondition: undefined, keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyM, @@ -893,7 +893,7 @@ index c256dba0..9ca1305c 100644 export const IsDevelopmentContext = new RawContextKey('isDevelopment', false, true); -export const ProductQualityContext = new RawContextKey('productQualityType', '', localize('productQualityType', "Quality type of VS Code")); -+export const ProductQualityContext = new RawContextKey('productQualityType', '', localize('productQualityType', "Quality type of !!APP_NAME!!")); ++export const ProductQualityContext = new RawContextKey('productQualityType', '', localize('productQualityType', "Quality type of BradfordCode")); export const InputFocusedContextKey = 'inputFocus'; export const InputFocusedContext = new RawContextKey(InputFocusedContextKey, false, localize('inputFocus', "Whether keyboard focus is inside an input box")); @@ -906,7 +906,7 @@ index 6eefd716..4e8cb6af 100644 const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, this.productService.version, this.productService.date)) { - throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extensionId, this.productService.version)); -+ throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with !!APP_NAME!! '{1}'.", extensionId, this.productService.version)); ++ throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with BradfordCode '{1}'.", extensionId, this.productService.version)); } const allowedToInstall = this.allowedExtensionsService.isAllowed({ id: extensionId, version: manifest.version, publisherDisplayName: undefined }); @@ -915,7 +915,7 @@ index 6eefd716..4e8cb6af 100644 await this.extensionsScanner.deleteExtension(existingExtension, 'existing'); } catch (e) { - throw new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", this.manifest.displayName || this.manifest.name)); -+ throw new Error(nls.localize('restartCode', "Please restart !!APP_NAME!! before reinstalling {0}.", this.manifest.displayName || this.manifest.name)); ++ throw new Error(nls.localize('restartCode', "Please restart BradfordCode before reinstalling {0}.", this.manifest.displayName || this.manifest.name)); } } } @@ -924,26 +924,10 @@ index 6eefd716..4e8cb6af 100644 await this.extensionsScanner.deleteExtension(existingWithSameVersion, 'existing'); } catch (e) { - throw new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", this.manifest.displayName || this.manifest.name)); -+ throw new Error(nls.localize('restartCode', "Please restart !!APP_NAME!! before reinstalling {0}.", this.manifest.displayName || this.manifest.name)); ++ throw new Error(nls.localize('restartCode', "Please restart BradfordCode before reinstalling {0}.", this.manifest.displayName || this.manifest.name)); } } -diff --git a/src/vs/platform/extensions/common/extensionValidator.ts b/src/vs/platform/extensions/common/extensionValidator.ts -index 6346b9d4..7b2c68de 100644 ---- a/src/vs/platform/extensions/common/extensionValidator.ts -+++ b/src/vs/platform/extensions/common/extensionValidator.ts -@@ -378,9 +378,9 @@ export function areApiProposalsCompatible(apiProposals: string[], arg1?: string[ - if (incompatibleProposals.length) { - if (notices) { - if (incompatibleProposals.length === 1) { -- notices.push(nls.localize('apiProposalMismatch1', "This extension is using the API proposal '{0}' that is not compatible with the current version of VS Code.", incompatibleProposals[0])); -+ notices.push(nls.localize('apiProposalMismatch1', "This extension is using the API proposal '{0}' that is not compatible with the current version of !!APP_NAME!!.", incompatibleProposals[0])); - } else { -- notices.push(nls.localize('apiProposalMismatch2', "This extension is using the API proposals {0} and '{1}' that are not compatible with the current version of VS Code.", -+ notices.push(nls.localize('apiProposalMismatch2', "This extension is using the API proposals {0} and '{1}' that are not compatible with the current version of !!APP_NAME!!.", - incompatibleProposals.slice(0, incompatibleProposals.length - 1).map(p => `'${p}'`).join(', '), - incompatibleProposals[incompatibleProposals.length - 1])); - } diff --git a/src/vs/platform/externalTerminal/node/externalTerminalService.ts b/src/vs/platform/externalTerminal/node/externalTerminalService.ts index e7cf3f54..ac295f0a 100644 --- a/src/vs/platform/externalTerminal/node/externalTerminalService.ts @@ -953,7 +937,7 @@ index e7cf3f54..ac295f0a 100644 import { ITerminalEnvironment } from '../../terminal/common/terminal.js'; -const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console"); -+const TERMINAL_TITLE = nls.localize('console.title', "!!APP_NAME!! Console"); ++const TERMINAL_TITLE = nls.localize('console.title', "BradfordCode Console"); abstract class ExternalTerminalService { public _serviceBrand: undefined; @@ -966,7 +950,7 @@ index cf059eab..86b46ab6 100644 [TerminalSettingId.InheritEnv]: { scope: ConfigurationScope.APPLICATION, - description: localize('terminal.integrated.inheritEnv', "Whether new shells should inherit their environment from VS Code, which may source a login shell to ensure $PATH and other development variables are initialized. This has no effect on Windows."), -+ description: localize('terminal.integrated.inheritEnv', "Whether new shells should inherit their environment from !!APP_NAME!!, which may source a login shell to ensure $PATH and other development variables are initialized. This has no effect on Windows."), ++ description: localize('terminal.integrated.inheritEnv', "Whether new shells should inherit their environment from BradfordCode, which may source a login shell to ensure $PATH and other development variables are initialized. This has no effect on Windows."), type: 'boolean', default: true }, @@ -979,7 +963,7 @@ index 2c63e9e6..eba02c07 100644 scope: ConfigurationScope.APPLICATION, title: localize('enableWindowsBackgroundUpdatesTitle', "Enable Background Updates"), - description: localize('enableWindowsBackgroundUpdates', "Enable to download and install new VS Code versions in the background."), -+ description: localize('enableWindowsBackgroundUpdates', "Enable to download and install new !!APP_NAME!! versions in the background."), ++ description: localize('enableWindowsBackgroundUpdates', "Enable to download and install new BradfordCode versions in the background."), included: isWindows && !isWeb }, 'update.showReleaseNotes': { @@ -992,7 +976,7 @@ index 8cf5dceb..5860d520 100644 owner: 'joaomoreno'; messageHash: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The hash of the error message.' }; - comment: 'This is used to know how often VS Code updates have failed.'; -+ comment: 'This is used to know how often !!APP_NAME!! updates have failed.'; ++ comment: 'This is used to know how often BradfordCode updates have failed.'; }; export abstract class AbstractUpdateService implements IUpdateService { @@ -1005,7 +989,7 @@ index 58ff362b..24124df3 100644 export async function main(desc: ProductDescription, args: string[]): Promise { if (!cliPipe && !cliCommand) { - console.log('Command is only available in WSL or inside a Visual Studio Code terminal.'); -+ console.log('Command is only available in WSL or inside a !!APP_NAME!! terminal.'); ++ console.log('Command is only available in WSL or inside a BradfordCode terminal.'); return; } @@ -1014,7 +998,7 @@ index 58ff362b..24124df3 100644 function fatal(message: string, err: unknown): void { - console.error('Unable to connect to VS Code server: ' + message); -+ console.error('Unable to connect to !!APP_NAME!! server: ' + message); ++ console.error('Unable to connect to BradfordCode server: ' + message); console.error(err); process.exit(1); } @@ -1027,7 +1011,7 @@ index 710009f8..f76af8b6 100644 type ClientIdUsageClassification = { owner: 'TylerLeonhardt'; - comment: 'Used to see which extensions are using the VSCode client id override'; -+ comment: 'Used to see which extensions are using the !!APP_NAME!! client id override'; ++ comment: 'Used to see which extensions are using the BradfordCode client id override'; extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension id.' }; }; this.telemetryService.publicLog2<{ extensionId: string }, ClientIdUsageClassification>('authentication.clientIdUsage', { extensionId }); @@ -1040,7 +1024,7 @@ index 83378707..67f454a2 100644 properties: { id: { - description: localize({ key: 'vscode.extension.contributes.views.containers.id', comment: ['Contribution refers to those that an extension contributes to VS Code through an extension/contribution point. '] }, "Unique id used to identify the container in which views can be contributed using 'views' contribution point"), -+ description: localize({ key: 'vscode.extension.contributes.views.containers.id', comment: ['Contribution refers to those that an extension contributes to !!APP_NAME!! through an extension/contribution point. '] }, "Unique id used to identify the container in which views can be contributed using 'views' contribution point"), ++ description: localize({ key: 'vscode.extension.contributes.views.containers.id', comment: ['Contribution refers to those that an extension contributes to BradfordCode through an extension/contribution point. '] }, "Unique id used to identify the container in which views can be contributed using 'views' contribution point"), type: 'string', pattern: '^[a-zA-Z0-9_-]+$' }, @@ -1053,7 +1037,7 @@ index 2785bbd7..c6d79962 100644 [ ApiCommandArgument.Uri.with('resource', 'Resource to open'), - ApiCommandArgument.String.with('viewId', 'Custom editor view id. This should be the viewType string for custom editors or the notebookType string for notebooks. Use \'default\' to use VS Code\'s default text editor'), -+ ApiCommandArgument.String.with('viewId', 'Custom editor view id. This should be the viewType string for custom editors or the notebookType string for notebooks. Use \'default\' to use !!APP_NAME!!\'s default text editor'), ++ ApiCommandArgument.String.with('viewId', 'Custom editor view id. This should be the viewType string for custom editors or the notebookType string for notebooks. Use \'default\' to use BradfordCode\'s default text editor'), new ApiCommandArgument('columnOrOptions', 'Either the column in which to open or editor options, see vscode.TextDocumentShowOptions', v => v === undefined || typeof v === 'number' || typeof v === 'object', v => !v ? v : typeof v === 'number' ? [typeConverters.ViewColumn.from(v), undefined] : [typeConverters.ViewColumn.from(v.viewColumn), typeConverters.TextEditorOpenOptions.from(v)], @@ -1067,8 +1051,8 @@ index 92e874dc..b34e04b5 100644 static readonly TypeHierarchyItem = new ApiCommandArgument('item', 'A type hierarchy item', v => v instanceof extHostTypes.TypeHierarchyItem, extHostTypeConverter.TypeHierarchyItem.from); - static readonly TestItem = new ApiCommandArgument('testItem', 'A VS Code TestItem', v => v instanceof TestItemImpl, extHostTypeConverter.TestItem.from); - static readonly TestProfile = new ApiCommandArgument('testProfile', 'A VS Code test profile', v => v instanceof extHostTypes.TestRunProfileBase, extHostTypeConverter.TestRunProfile.from); -+ static readonly TestItem = new ApiCommandArgument('testItem', 'A !!APP_NAME!! TestItem', v => v instanceof TestItemImpl, extHostTypeConverter.TestItem.from); -+ static readonly TestProfile = new ApiCommandArgument('testProfile', 'A !!APP_NAME!! test profile', v => v instanceof extHostTypes.TestRunProfileBase, extHostTypeConverter.TestRunProfile.from); ++ static readonly TestItem = new ApiCommandArgument('testItem', 'A BradfordCode TestItem', v => v instanceof TestItemImpl, extHostTypeConverter.TestItem.from); ++ static readonly TestProfile = new ApiCommandArgument('testProfile', 'A BradfordCode test profile', v => v instanceof extHostTypes.TestRunProfileBase, extHostTypeConverter.TestRunProfile.from); constructor( readonly name: string, @@ -1097,12 +1081,12 @@ index 0d713847..33749645 100644 - test('Opening a notebook results in VS Code firing the event onDidChangeActiveNotebookEditor twice #118470', function () { -+ test('Opening a notebook results in !!APP_NAME!! firing the event onDidChangeActiveNotebookEditor twice #118470', function () { ++ test('Opening a notebook results in BradfordCode firing the event onDidChangeActiveNotebookEditor twice #118470', function () { let count = 0; disposables.add(extHostNotebooks.onDidChangeActiveNotebookEditor(() => count += 1)); diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts -index cdc59743..b4e742f9 100644 +index ee85640c..bddb01c5 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -688,7 +688,7 @@ class PolicyDiagnosticsAction extends Action2 { @@ -1110,7 +1094,7 @@ index cdc59743..b4e742f9 100644 const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); - let content = '# VS Code Policy Diagnostics\n\n'; -+ let content = '# !!APP_NAME!! Policy Diagnostics\n\n'; ++ let content = '# BradfordCode Policy Diagnostics\n\n'; content += '*WARNING: This file may contain sensitive information.*\n\n'; content += '## System Information\n\n'; content += '| Property | Value |\n'; @@ -1123,7 +1107,7 @@ index 8251c4a4..19bee6b0 100644 super({ id: OpenNewsletterSignupUrlAction.ID, - title: localize2('newsletterSignup', 'Signup for the VS Code Newsletter'), -+ title: localize2('newsletterSignup', 'Signup for the !!APP_NAME!! Newsletter'), ++ title: localize2('newsletterSignup', 'Signup for the BradfordCode Newsletter'), category: Categories.Help, f1: true }); @@ -1136,25 +1120,25 @@ index e342f838..98e500bc 100644 // do not support this and require a full context switch to clean-up. if (created) { - throw new Error('Unable to create the VSCode workbench more than once.'); -+ throw new Error('Unable to create the !!APP_NAME!! workbench more than once.'); ++ throw new Error('Unable to create the BradfordCode workbench more than once.'); } else { created = true; } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts -index eeddba00..fd4e677f 100644 +index f2a3b35e..1fecb536 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts -@@ -827,7 +827,7 @@ const registry = Registry.as(ConfigurationExtensions.Con +@@ -833,7 +833,7 @@ const registry = Registry.as(ConfigurationExtensions.Con localize('rootNameShort', "`${rootNameShort}`: shortened name of the workspace without suffixes (e.g. myFolder, myRemoteFolder or myWorkspace)."), localize('rootPath', "`${rootPath}`: file path of the opened workspace or folder (e.g. /Users/Development/myWorkspace)."), localize('profileName', "`${profileName}`: name of the profile in which the workspace is opened (e.g. Data Science (Profile)). Ignored if default profile is used."), - localize('appName', "`${appName}`: e.g. VS Code."), -+ localize('appName', "`${appName}`: e.g. !!APP_NAME!!."), ++ localize('appName', "`${appName}`: e.g. BradfordCode."), localize('remoteName', "`${remoteName}`: e.g. SSH"), localize('dirty', "`${dirty}`: an indicator for when the active editor has unsaved changes."), localize('focusedView', "`${focusedView}`: the name of the view that is currently focused."), diff --git a/src/vs/workbench/common/contextkeys.ts b/src/vs/workbench/common/contextkeys.ts -index c650e128..802a5f21 100644 +index 51a4a3f2..0e3b27dc 100644 --- a/src/vs/workbench/common/contextkeys.ts +++ b/src/vs/workbench/common/contextkeys.ts @@ -38,7 +38,7 @@ export const HasWebFileSystemAccess = new RawContextKey('hasWebFileSyst @@ -1162,29 +1146,29 @@ index c650e128..802a5f21 100644 export const EmbedderIdentifierContext = new RawContextKey('embedderIdentifier', undefined, localize('embedderIdentifier', 'The identifier of the embedder according to the product service, if one is defined')); -export const InAutomationContext = new RawContextKey('inAutomation', false, localize('inAutomation', "Whether VS Code is running under automation/smoke test")); -+export const InAutomationContext = new RawContextKey('inAutomation', false, localize('inAutomation', "Whether !!APP_NAME!! is running under automation/smoke test")); ++export const InAutomationContext = new RawContextKey('inAutomation', false, localize('inAutomation', "Whether BradfordCode is running under automation/smoke test")); //#endregion diff --git a/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts -index f97bccf1..bb326157 100644 +index 883dab9e..3fcf5140 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts -@@ -734,7 +734,7 @@ configurationRegistry.registerConfiguration({ +@@ -802,7 +802,7 @@ configurationRegistry.registerConfiguration({ ], enumDescriptions: [ nls.localize('chat.mcp.access.none', "No access to MCP servers."), - nls.localize('chat.mcp.access.registry', "Allows access to MCP servers installed from the registry that VS Code is connected to."), -+ nls.localize('chat.mcp.access.registry', "Allows access to MCP servers installed from the registry that !!APP_NAME!! is connected to."), ++ nls.localize('chat.mcp.access.registry', "Allows access to MCP servers installed from the registry that BradfordCode is connected to."), nls.localize('chat.mcp.access.any', "Allow access to any installed MCP server.") ], default: McpAccessValue.All, -@@ -761,7 +761,7 @@ configurationRegistry.registerConfiguration({ +@@ -829,7 +829,7 @@ configurationRegistry.registerConfiguration({ key: 'chat.mcp.access.none', value: nls.localize('chat.mcp.access.none', "No access to MCP servers."), }, { - key: 'chat.mcp.access.registry', value: nls.localize('chat.mcp.access.registry', "Allows access to MCP servers installed from the registry that VS Code is connected to."), -+ key: 'chat.mcp.access.registry', value: nls.localize('chat.mcp.access.registry', "Allows access to MCP servers installed from the registry that !!APP_NAME!! is connected to."), ++ key: 'chat.mcp.access.registry', value: nls.localize('chat.mcp.access.registry', "Allows access to MCP servers installed from the registry that BradfordCode is connected to."), }, { key: 'chat.mcp.access.any', value: nls.localize('chat.mcp.access.any', "Allow access to any installed MCP server.") @@ -1197,7 +1181,7 @@ index 1417e0dc..cc064c82 100644 // Register VSCode agent - const { disposable: vscodeDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.vscode', 'vscode', false, localize2('vscodeAgentDescription', "Ask questions about VS Code").value, ChatAgentLocation.Chat, ChatModeKind.Agent, context, controller); -+ const { disposable: vscodeDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.vscode', 'vscode', false, localize2('vscodeAgentDescription', "Ask questions about !!APP_NAME!!").value, ChatAgentLocation.Chat, ChatModeKind.Agent, context, controller); ++ const { disposable: vscodeDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.vscode', 'vscode', false, localize2('vscodeAgentDescription', "Ask questions about BradfordCode").value, ChatAgentLocation.Chat, ChatModeKind.Agent, context, controller); disposables.add(vscodeDisposable); // Register workspace agent @@ -1207,8 +1191,8 @@ index 1417e0dc..cc064c82 100644 displayName: localize('setupToolDisplayName', "New Workspace"), - modelDescription: 'Scaffold a new workspace in VS Code', - userDescription: localize('setupToolsDescription', "Scaffold a new workspace in VS Code"), -+ modelDescription: 'Scaffold a new workspace in !!APP_NAME!!', -+ userDescription: localize('setupToolsDescription', "Scaffold a new workspace in !!APP_NAME!!"), ++ modelDescription: 'Scaffold a new workspace in BradfordCode', ++ userDescription: localize('setupToolsDescription', "Scaffold a new workspace in BradfordCode"), canBeReferencedInPrompt: true, toolReferenceName: 'new', when: ContextKeyExpr.true(), @@ -1221,24 +1205,24 @@ index d3be4e1e..60e06e85 100644 } else { if (validGithubCopilotAttributeNames.value.has(attribute.key)) { - report(toMarker(localize('promptValidator.ignoredAttribute.vscode-agent', "Attribute '{0}' is ignored when running locally in VS Code.", attribute.key), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); -+ report(toMarker(localize('promptValidator.ignoredAttribute.vscode-agent', "Attribute '{0}' is ignored when running locally in !!APP_NAME!!.", attribute.key), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); ++ report(toMarker(localize('promptValidator.ignoredAttribute.vscode-agent', "Attribute '{0}' is ignored when running locally in BradfordCode.", attribute.key), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); } else { - report(toMarker(localize('promptValidator.unknownAttribute.vscode-agent', "Attribute '{0}' is not supported in VS Code agent files. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); -+ report(toMarker(localize('promptValidator.unknownAttribute.vscode-agent', "Attribute '{0}' is not supported in !!APP_NAME!! agent files. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); ++ report(toMarker(localize('promptValidator.unknownAttribute.vscode-agent', "Attribute '{0}' is not supported in BradfordCode agent files. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); } } break; case PromptsType.instructions: if (target === Target.Claude) { - report(toMarker(localize('promptValidator.unknownAttribute.rules', "Attribute '{0}' is not supported in rules files by VS Code agents. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); -+ report(toMarker(localize('promptValidator.unknownAttribute.rules', "Attribute '{0}' is not supported in rules files by !!APP_NAME!! agents. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); ++ report(toMarker(localize('promptValidator.unknownAttribute.rules', "Attribute '{0}' is not supported in rules files by BradfordCode agents. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); } else { report(toMarker(localize('promptValidator.unknownAttribute.instructions', "Attribute '{0}' is not supported in instructions files. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); } break; case PromptsType.skill: - report(toMarker(localize('promptValidator.unknownAttribute.skill', "Attribute '{0}' is not supported by VS Code agents. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); -+ report(toMarker(localize('promptValidator.unknownAttribute.skill', "Attribute '{0}' is not supported by !!APP_NAME!! agents. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); ++ report(toMarker(localize('promptValidator.unknownAttribute.skill', "Attribute '{0}' is not supported by BradfordCode agents. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); break; } } @@ -1247,7 +1231,7 @@ index d3be4e1e..60e06e85 100644 /** - * Maps Claude tool names to their VS Code tool equivalents. -+ * Maps Claude tool names to their !!APP_NAME!! tool equivalents. ++ * Maps Claude tool names to their BradfordCode tool equivalents. */ export function mapClaudeTools(claudeToolNames: readonly string[]): string[] { const result: string[] = []; @@ -1260,20 +1244,20 @@ index 0b9ced32..b5ed7134 100644 'debugServer': { type: 'number', - description: nls.localize('debugServer', "For debug extension development only: if a port is specified VS Code tries to connect to a debug adapter running in server mode"), -+ description: nls.localize('debugServer', "For debug extension development only: if a port is specified !!APP_NAME!! tries to connect to a debug adapter running in server mode"), ++ description: nls.localize('debugServer', "For debug extension development only: if a port is specified BradfordCode tries to connect to a debug adapter running in server mode"), default: 4711 }, 'preLaunchTask': { diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts -index 695b1ed1..d886ff59 100644 +index 0c0965d3..f4b0faef 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts -@@ -380,24 +380,24 @@ CommandsRegistry.registerCommand({ +@@ -417,24 +417,24 @@ CommandsRegistry.registerCommand({ { name: 'options', description: '(optional) Options for installing the extension. Object with the following properties: ' + - '`installOnlyNewlyAddedFromExtensionPackVSIX`: When enabled, VS Code installs only newly added extensions from the extension pack VSIX. This option is considered only when installing VSIX. ', -+ '`installOnlyNewlyAddedFromExtensionPackVSIX`: When enabled, !!APP_NAME!! installs only newly added extensions from the extension pack VSIX. This option is considered only when installing VSIX. ', ++ '`installOnlyNewlyAddedFromExtensionPackVSIX`: When enabled, BradfordCode installs only newly added extensions from the extension pack VSIX. This option is considered only when installing VSIX. ', isOptional: true, schema: { 'type': 'object', @@ -1281,35 +1265,35 @@ index 695b1ed1..d886ff59 100644 'installOnlyNewlyAddedFromExtensionPackVSIX': { 'type': 'boolean', - 'description': localize('workbench.extensions.installExtension.option.installOnlyNewlyAddedFromExtensionPackVSIX', "When enabled, VS Code installs only newly added extensions from the extension pack VSIX. This option is considered only while installing a VSIX."), -+ 'description': localize('workbench.extensions.installExtension.option.installOnlyNewlyAddedFromExtensionPackVSIX', "When enabled, !!APP_NAME!! installs only newly added extensions from the extension pack VSIX. This option is considered only while installing a VSIX."), ++ 'description': localize('workbench.extensions.installExtension.option.installOnlyNewlyAddedFromExtensionPackVSIX', "When enabled, BradfordCode installs only newly added extensions from the extension pack VSIX. This option is considered only while installing a VSIX."), default: false }, 'installPreReleaseVersion': { 'type': 'boolean', - 'description': localize('workbench.extensions.installExtension.option.installPreReleaseVersion', "When enabled, VS Code installs the pre-release version of the extension if available."), -+ 'description': localize('workbench.extensions.installExtension.option.installPreReleaseVersion', "When enabled, !!APP_NAME!! installs the pre-release version of the extension if available."), ++ 'description': localize('workbench.extensions.installExtension.option.installPreReleaseVersion', "When enabled, BradfordCode installs the pre-release version of the extension if available."), default: false }, 'donotSync': { 'type': 'boolean', - 'description': localize('workbench.extensions.installExtension.option.donotSync', "When enabled, VS Code do not sync this extension when Settings Sync is on."), -+ 'description': localize('workbench.extensions.installExtension.option.donotSync', "When enabled, !!APP_NAME!! do not sync this extension when Settings Sync is on."), ++ 'description': localize('workbench.extensions.installExtension.option.donotSync', "When enabled, BradfordCode do not sync this extension when Settings Sync is on."), default: false }, 'justification': { -@@ -922,8 +922,8 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi +@@ -959,8 +959,8 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi if (requireReload) { notificationService.prompt( Severity.Info, - vsixs.length > 1 ? localize('InstallVSIXs.successReload', "Completed installing extensions. Please reload Visual Studio Code to enable them.") - : localize('InstallVSIXAction.successReload', "Completed installing extension. Please reload Visual Studio Code to enable it."), -+ vsixs.length > 1 ? localize('InstallVSIXs.successReload', "Completed installing extensions. Please reload !!APP_NAME!! to enable them.") -+ : localize('InstallVSIXAction.successReload', "Completed installing extension. Please reload !!APP_NAME!! to enable it."), ++ vsixs.length > 1 ? localize('InstallVSIXs.successReload', "Completed installing extensions. Please reload BradfordCode to enable them.") ++ : localize('InstallVSIXAction.successReload', "Completed installing extension. Please reload BradfordCode to enable it."), [{ label: localize('InstallVSIXAction.reloadNow', "Reload Now"), run: () => hostService.reload() diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts -index 37b94354..3d1916fd 100644 +index 452db88d..282c9b5a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -107,7 +107,7 @@ export class PromptExtensionInstallFailureAction extends Action { @@ -1317,7 +1301,7 @@ index 37b94354..3d1916fd 100644 if (this.error.name === ExtensionManagementErrorCode.Unsupported) { - const productName = isWeb ? localize('VS Code for Web', "{0} for the Web", this.productService.nameLong) : this.productService.nameLong; -+ const productName = isWeb ? localize('!!APP_NAME!! for Web', "{0} for the Web", this.productService.nameLong) : this.productService.nameLong; ++ const productName = isWeb ? localize('BradfordCode for Web', "{0} for the Web", this.productService.nameLong) : this.productService.nameLong; const message = localize('cannot be installed', "The '{0}' extension is not available in {1}. Click 'More Information' to learn more.", this.extension.displayName || this.extension.identifier.id, productName); const { confirmed } = await this.dialogService.confirm({ type: Severity.Info, @@ -1326,7 +1310,7 @@ index 37b94354..3d1916fd 100644 }); } else if (this.extension.deprecationInfo.settings) { - detail = localize('deprecated with alternate settings message', "This extension is deprecated as this functionality is now built-in to VS Code."); -+ detail = localize('deprecated with alternate settings message', "This extension is deprecated as this functionality is now built-in to !!APP_NAME!!."); ++ detail = localize('deprecated with alternate settings message', "This extension is deprecated as this functionality is now built-in to BradfordCode."); const settings = this.extension.deprecationInfo.settings; buttons.push({ @@ -1335,7 +1319,7 @@ index 37b94354..3d1916fd 100644 try { await this.extensionsWorkbenchService.uninstall(this.extension); - alert(localize('uninstallExtensionComplete', "Please reload Visual Studio Code to complete the uninstallation of the extension {0}.", this.extension.displayName)); -+ alert(localize('uninstallExtensionComplete', "Please reload !!APP_NAME!! to complete the uninstallation of the extension {0}.", this.extension.displayName)); ++ alert(localize('uninstallExtensionComplete', "Please reload BradfordCode to complete the uninstallation of the extension {0}.", this.extension.displayName)); } catch (error) { if (!isCancellationError(error)) { this.dialogService.error(getErrorMessage(error)); @@ -1344,29 +1328,29 @@ index 37b94354..3d1916fd 100644 } else if (this.extension.deprecationInfo.settings) { const link = `[${localize('settings', "settings")}](${createCommandUri('workbench.action.openSettings', this.extension.deprecationInfo.settings.map(setting => `@id:${setting}`).join(' '))}})`; - this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated with alternate settings tooltip', "This extension is deprecated as this functionality is now built-in to VS Code. Configure these {0} to use this functionality.", link)) }, true); -+ this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated with alternate settings tooltip', "This extension is deprecated as this functionality is now built-in to !!APP_NAME!!. Configure these {0} to use this functionality.", link)) }, true); ++ this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated with alternate settings tooltip', "This extension is deprecated as this functionality is now built-in to BradfordCode. Configure these {0} to use this functionality.", link)) }, true); } else { const message = new MarkdownString(localize('deprecated tooltip', "This extension is deprecated as it is no longer being maintained.")); if (this.extension.deprecationInfo.additionalInfo) { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts -index 43d2b947..2a89a908 100644 +index a58d77d1..cc5004c7 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts -@@ -478,7 +478,7 @@ export class Extension implements IExtension { +@@ -477,7 +477,7 @@ export class Extension implements IExtension { if (this.type === ExtensionType.System) { return Promise.resolve(`# ${this.displayName || this.name} -**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled. -+**Notice:** This extension is bundled with !!APP_NAME!!. It can be disabled but not uninstalled. ++**Notice:** This extension is bundled with BradfordCode. It can be disabled but not uninstalled. ## Features ${this.description} `); -@@ -516,7 +516,7 @@ ${this.description} +@@ -515,7 +515,7 @@ ${this.description} } if (this.type === ExtensionType.System) { - return Promise.resolve(`Please check the [VS Code Release Notes](command:${ShowCurrentReleaseNotesActionId}) for changes to the built-in extensions.`); -+ return Promise.resolve(`Please check the [!!APP_NAME!! Release Notes](command:${ShowCurrentReleaseNotesActionId}) for changes to the built-in extensions.`); ++ return Promise.resolve(`Please check the [BradfordCode Release Notes](command:${ShowCurrentReleaseNotesActionId}) for changes to the built-in extensions.`); } return Promise.reject(new Error('not available')); @@ -1379,7 +1363,7 @@ index 818e6628..19722f3f 100644 unwantedRecommendations: { type: 'array', - description: localize('app.extensions.json.unwantedRecommendations', "List of extensions recommended by VS Code that should not be recommended for users of this workspace. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."), -+ description: localize('app.extensions.json.unwantedRecommendations', "List of extensions recommended by !!APP_NAME!! that should not be recommended for users of this workspace. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."), ++ description: localize('app.extensions.json.unwantedRecommendations', "List of extensions recommended by BradfordCode that should not be recommended for users of this workspace. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."), items: { type: 'string', pattern: EXTENSION_IDENTIFIER_PATTERN, @@ -1388,7 +1372,7 @@ index 818e6628..19722f3f 100644 '\t\t', '\t],', - '\t// List of extensions recommended by VS Code that should not be recommended for users of this workspace.', -+ '\t// List of extensions recommended by !!APP_NAME!! that should not be recommended for users of this workspace.', ++ '\t// List of extensions recommended by BradfordCode that should not be recommended for users of this workspace.', '\t"unwantedRecommendations": [', '\t\t', '\t]', @@ -1401,7 +1385,7 @@ index d174c8c9..69d8d43e 100644 canBeReferencedInPrompt: true, displayName: localize('installExtensionsTool.displayName', 'Install Extensions'), - modelDescription: 'This is a tool for installing extensions in Visual Studio Code. You should provide the list of extension ids to install. The identifier of an extension is \'\${ publisher }.\${ name }\' for example: \'vscode.csharp\'.', -+ modelDescription: 'This is a tool for installing extensions in !!APP_NAME!!. You should provide the list of extension ids to install. The identifier of an extension is \'\${ publisher }.\${ name }\' for example: \'vscode.csharp\'.', ++ modelDescription: 'This is a tool for installing extensions in BradfordCode. You should provide the list of extension ids to install. The identifier of an extension is \'\${ publisher }.\${ name }\' for example: \'vscode.csharp\'.', userDescription: localize('installExtensionsTool.userDescription', 'Tool for installing extensions'), source: ToolDataSource.Internal, inputSchema: { @@ -1416,7 +1400,7 @@ index 91541b6a..3d4d5f05 100644 - modelDescription: 'This is a tool for browsing Visual Studio Code Extensions Marketplace. It allows the model to search for extensions and retrieve detailed information about them. The model should use this tool whenever it needs to discover extensions or resolve information about known ones. To use the tool, the model has to provide the category of the extensions, relevant search keywords, or known extension IDs. Note that search results may include false positives, so reviewing and filtering is recommended.', - userDescription: localize('searchExtensionsTool.userDescription', 'Search for VS Code extensions'), + modelDescription: 'This is a tool for browsing OpenVSX. It allows the model to search for extensions and retrieve detailed information about them. The model should use this tool whenever it needs to discover extensions or resolve information about known ones. To use the tool, the model has to provide the category of the extensions, relevant search keywords, or known extension IDs. Note that search results may include false positives, so reviewing and filtering is recommended.', -+ userDescription: localize('searchExtensionsTool.userDescription', 'Search for !!APP_NAME!! extensions'), ++ userDescription: localize('searchExtensionsTool.userDescription', 'Search for BradfordCode extensions'), source: ToolDataSource.Internal, inputSchema: { type: 'object', @@ -1429,7 +1413,7 @@ index f54ddfe2..2c524b07 100644 markdownDescription: nls.localize('externalUriOpeners.uri', "Map URI pattern to an opener id.\nExample patterns: \n{0}", exampleUriPatterns), enum: [defaultExternalUriOpenerId], - enumDescriptions: [nls.localize('externalUriOpeners.defaultId', "Open using VS Code's standard opener.")], -+ enumDescriptions: [nls.localize('externalUriOpeners.defaultId', "Open using !!APP_NAME!!'s standard opener.")], ++ enumDescriptions: [nls.localize('externalUriOpeners.defaultId', "Open using BradfordCode's standard opener.")], }, externalUriOpenerIdSchemaAddition ] @@ -1442,10 +1426,10 @@ index bd739953..bae23895 100644 id: { type: 'string', - description: localize('vscode.extension.contributes.localizations.translations.id', "Id of VS Code or Extension for which this translation is contributed to. Id of VS Code is always `vscode` and of extension should be in format `publisherId.extensionName`."), -+ description: localize('vscode.extension.contributes.localizations.translations.id', "Id of !!APP_NAME!! or Extension for which this translation is contributed to. Id of !!APP_NAME!! is always `vscode` and of extension should be in format `publisherId.extensionName`."), ++ description: localize('vscode.extension.contributes.localizations.translations.id', "Id of BradfordCode or Extension for which this translation is contributed to. Id of BradfordCode is always `vscode` and of extension should be in format `publisherId.extensionName`."), pattern: '^((vscode)|([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*))$', - patternErrorMessage: localize('vscode.extension.contributes.localizations.translations.id.pattern', "Id should be `vscode` or in format `publisherId.extensionName` for translating VS code or an extension respectively.") -+ patternErrorMessage: localize('vscode.extension.contributes.localizations.translations.id.pattern', "Id should be `vscode` or in format `publisherId.extensionName` for translating !!APP_NAME!! or an extension respectively.") ++ patternErrorMessage: localize('vscode.extension.contributes.localizations.translations.id.pattern', "Id should be `vscode` or in format `publisherId.extensionName` for translating BradfordCode or an extension respectively.") }, path: { type: 'string', @@ -1458,7 +1442,7 @@ index 050dde41..edf24a48 100644 }, metadata: { - description: localize2('configureLocaleDescription', "Changes the locale of VS Code based on installed language packs. Common languages include French, Chinese, Spanish, Japanese, German, Korean, and more.") -+ description: localize2('configureLocaleDescription', "Changes the locale of !!APP_NAME!! based on installed language packs. Common languages include French, Chinese, Spanish, Japanese, German, Korean, and more.") ++ description: localize2('configureLocaleDescription', "Changes the locale of BradfordCode based on installed language packs. Common languages include French, Chinese, Spanish, Japanese, German, Korean, and more.") } }); } @@ -1471,7 +1455,7 @@ index dc231e44..c983379d 100644 const markdownResult = this._register(this.markdownRendererService.render( new MarkdownString( - localize('mcp.welcome.descriptionWithLink', "Browse and install [Model Context Protocol (MCP) servers](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) directly from VS Code to extend agent mode with extra tools for connecting to databases, invoking APIs and performing specialized tasks."), -+ localize('mcp.welcome.descriptionWithLink', "Browse and install [Model Context Protocol (MCP) servers](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) directly from !!APP_NAME!! to extend agent mode with extra tools for connecting to databases, invoking APIs and performing specialized tasks."), ++ localize('mcp.welcome.descriptionWithLink', "Browse and install [Model Context Protocol (MCP) servers](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) directly from BradfordCode to extend agent mode with extra tools for connecting to databases, invoking APIs and performing specialized tasks."), { isTrusted: { enabledCommands: ['workbench.action.openSettings'] } }, ) .appendMarkdown('\n\n') @@ -1485,8 +1469,8 @@ index b533feb1..42539bde 100644 const DEFAULT_CONTENT: string = [ - `// ${nls.localize('displayLanguage', 'Defines the keyboard layout used in VS Code in the browser environment.')}`, - `// ${nls.localize('doc', 'Open VS Code and run "Developer: Inspect Key Mappings (JSON)" from Command Palette.')}`, -+ `// ${nls.localize('displayLanguage', 'Defines the keyboard layout used in !!APP_NAME!! in the browser environment.')}`, -+ `// ${nls.localize('doc', 'Open !!APP_NAME!! and run "Developer: Inspect Key Mappings (JSON)" from Command Palette.')}`, ++ `// ${nls.localize('displayLanguage', 'Defines the keyboard layout used in BradfordCode in the browser environment.')}`, ++ `// ${nls.localize('doc', 'Open BradfordCode and run "Developer: Inspect Key Mappings (JSON)" from Command Palette.')}`, ``, `// Once you have the keyboard layout info, please paste it below.`, '\n' @@ -1499,7 +1483,7 @@ index 36209197..8b4cc4a0 100644 } if (response.code && response.code === TerminateResponseCode.ProcessNotFound) { - this._notificationService.error(nls.localize('TerminateAction.noProcess', 'The launched process doesn\'t exist anymore. If the task spawned background tasks exiting VS Code might result in orphaned processes.')); -+ this._notificationService.error(nls.localize('TerminateAction.noProcess', 'The launched process doesn\'t exist anymore. If the task spawned background tasks exiting !!APP_NAME!! might result in orphaned processes.')); ++ this._notificationService.error(nls.localize('TerminateAction.noProcess', 'The launched process doesn\'t exist anymore. If the task spawned background tasks exiting BradfordCode might result in orphaned processes.')); } else { this._notificationService.error(nls.localize('TerminateAction.failed', 'Failed to terminate running task')); } @@ -1512,7 +1496,7 @@ index 9db6b8ad..1ec00756 100644 promptOnClose: { type: 'boolean', - description: nls.localize('JsonSchema.tasks.promptOnClose', 'Whether the user is prompted when VS Code closes with a running task.'), -+ description: nls.localize('JsonSchema.tasks.promptOnClose', 'Whether the user is prompted when !!APP_NAME!! closes with a running task.'), ++ description: nls.localize('JsonSchema.tasks.promptOnClose', 'Whether the user is prompted when BradfordCode closes with a running task.'), default: false }, isBuildCommand: { @@ -1521,7 +1505,7 @@ index 9db6b8ad..1ec00756 100644 promptOnClose: { type: 'boolean', - description: nls.localize('JsonSchema.promptOnClose', 'Whether the user is prompted when VS Code closes with a running background task.'), -+ description: nls.localize('JsonSchema.promptOnClose', 'Whether the user is prompted when !!APP_NAME!! closes with a running background task.'), ++ description: nls.localize('JsonSchema.promptOnClose', 'Whether the user is prompted when BradfordCode closes with a running background task.'), default: false }, echoCommand: { @@ -1534,7 +1518,7 @@ index f063bfc0..f17ee737 100644 promptOnClose: { type: 'boolean', - description: nls.localize('JsonSchema.tasks.promptOnClose', 'Whether the user is prompted when VS Code closes with a running task.'), -+ description: nls.localize('JsonSchema.tasks.promptOnClose', 'Whether the user is prompted when !!APP_NAME!! closes with a running task.'), ++ description: nls.localize('JsonSchema.tasks.promptOnClose', 'Whether the user is prompted when BradfordCode closes with a running task.'), default: false }, presentation: Objects.deepClone(presentation), @@ -1547,7 +1531,7 @@ index 9a745727..7c9fb358 100644 run: () => this.configurationService.updateValue(TerminalSettingId.FontFamily, 'monospace'), }]; - this._notificationService.prompt(Severity.Warning, nls.localize('terminal.monospaceOnly', "The terminal only supports monospace fonts. Be sure to restart VS Code if this is a newly installed font."), choices); -+ this._notificationService.prompt(Severity.Warning, nls.localize('terminal.monospaceOnly', "The terminal only supports monospace fonts. Be sure to restart !!APP_NAME!! if this is a newly installed font."), choices); ++ this._notificationService.prompt(Severity.Warning, nls.localize('terminal.monospaceOnly', "The terminal only supports monospace fonts. Be sure to restart BradfordCode if this is a newly installed font."), choices); } } })); @@ -1560,7 +1544,7 @@ index c5729c39..ccd535ba 100644 if ! builtin eval "$VSCODE_PYTHON_BASH_ACTIVATE"; then __vsc_activation_status=$? - builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m VS Code Python bash activation failed with exit code %d \x1b[0m' "$__vsc_activation_status" -+ builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m !!APP_NAME!! Python bash activation failed with exit code %d \x1b[0m' "$__vsc_activation_status" ++ builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m BradfordCode Python bash activation failed with exit code %d \x1b[0m' "$__vsc_activation_status" fi fi # Remove any leftover Python activation env vars. @@ -1573,12 +1557,12 @@ index c43718e3..ca17a601 100644 if ! builtin eval "$VSCODE_PYTHON_ZSH_ACTIVATE"; then __vsc_activation_status=$? - builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m VS Code Python zsh activation failed with exit code %d \x1b[0m' "$__vsc_activation_status" -+ builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m !!APP_NAME!! Python zsh activation failed with exit code %d \x1b[0m' "$__vsc_activation_status" ++ builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m BradfordCode Python zsh activation failed with exit code %d \x1b[0m' "$__vsc_activation_status" fi fi # Remove any leftover Python activation env vars. diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish -index 6cff7487..37c8342b 100644 +index f87fc762..15ffe678 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish @@ -92,7 +92,7 @@ if not set -q VSCODE_PYTHON_AUTOACTIVATE_GUARD @@ -1586,7 +1570,7 @@ index 6cff7487..37c8342b 100644 if test $__vsc_activation_status -ne 0 - builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m VS Code Python fish activation failed with exit code %d \x1b[0m \n' "$__vsc_activation_status" -+ builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m !!APP_NAME!! Python fish activation failed with exit code %d \x1b[0m \n' "$__vsc_activation_status" ++ builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m BradfordCode Python fish activation failed with exit code %d \x1b[0m \n' "$__vsc_activation_status" end end # Remove any leftover Python activation env vars. @@ -1595,7 +1579,7 @@ index 6cff7487..37c8342b 100644 # Helper function -function __vsc_esc -d "Emit escape sequences for VS Code shell integration" -+function __vsc_esc -d "Emit escape sequences for !!APP_NAME!! shell integration" ++function __vsc_esc -d "Emit escape sequences for BradfordCode shell integration" builtin printf "\e]633;%s\a" (string join ";" -- $argv) end @@ -1608,7 +1592,7 @@ index 72a329b8..2399d553 100644 catch { $activationError = $_ - Write-Host "`e[0m`e[7m * `e[0;103m VS Code Python powershell activation failed with exit code $($activationError.Exception.Message) `e[0m" -+ Write-Host "`e[0m`e[7m * `e[0;103m !!APP_NAME!! Python powershell activation failed with exit code $($activationError.Exception.Message) `e[0m" ++ Write-Host "`e[0m`e[7m * `e[0;103m BradfordCode Python powershell activation failed with exit code $($activationError.Exception.Message) `e[0m" } } # Remove any leftover Python activation env vars. @@ -1621,7 +1605,7 @@ index 3d3e8f2d..5cbfc9ad 100644 }, [TerminalSettingId.DetectLocale]: { - markdownDescription: localize('terminal.integrated.detectLocale', "Controls whether to detect and set the `$LANG` environment variable to a UTF-8 compliant option since VS Code's terminal only supports UTF-8 encoded data coming from the shell."), -+ markdownDescription: localize('terminal.integrated.detectLocale', "Controls whether to detect and set the `$LANG` environment variable to a UTF-8 compliant option since !!APP_NAME!!'s terminal only supports UTF-8 encoded data coming from the shell."), ++ markdownDescription: localize('terminal.integrated.detectLocale', "Controls whether to detect and set the `$LANG` environment variable to a UTF-8 compliant option since BradfordCode's terminal only supports UTF-8 encoded data coming from the shell."), type: 'string', enum: ['auto', 'off', 'on'], markdownEnumDescriptions: [ @@ -1630,7 +1614,7 @@ index 3d3e8f2d..5cbfc9ad 100644 enum: ['auto', 'on', 'off'], markdownEnumDescriptions: [ - localize('terminal.integrated.gpuAcceleration.auto', "Let VS Code detect which renderer will give the best experience."), -+ localize('terminal.integrated.gpuAcceleration.auto', "Let !!APP_NAME!! detect which renderer will give the best experience."), ++ localize('terminal.integrated.gpuAcceleration.auto', "Let BradfordCode detect which renderer will give the best experience."), localize('terminal.integrated.gpuAcceleration.on', "Enable GPU acceleration within the terminal."), localize('terminal.integrated.gpuAcceleration.off', "Disable GPU acceleration within the terminal. The terminal will render much slower when GPU acceleration is off but it should reliably work on all systems."), ], @@ -1639,7 +1623,7 @@ index 3d3e8f2d..5cbfc9ad 100644 markdownDescription: localize( 'terminal.integrated.commandsToSkipShell', - "A set of command IDs whose keybindings will not be sent to the shell but instead always be handled by VS Code. This allows keybindings that would normally be consumed by the shell to act instead the same as when the terminal is not focused, for example `Ctrl+P` to launch Quick Open.\n\n \n\nMany commands are skipped by default. To override a default and pass that command's keybinding to the shell instead, add the command prefixed with the `-` character. For example add `-workbench.action.quickOpen` to allow `Ctrl+P` to reach the shell.\n\n \n\nThe following list of default skipped commands is truncated when viewed in Settings Editor. To see the full list, {1} and search for the first command from the list below.\n\n \n\nDefault Skipped Commands:\n\n{0}", -+ "A set of command IDs whose keybindings will not be sent to the shell but instead always be handled by !!APP_NAME!!. This allows keybindings that would normally be consumed by the shell to act instead the same as when the terminal is not focused, for example `Ctrl+P` to launch Quick Open.\n\n \n\nMany commands are skipped by default. To override a default and pass that command's keybinding to the shell instead, add the command prefixed with the `-` character. For example add `-workbench.action.quickOpen` to allow `Ctrl+P` to reach the shell.\n\n \n\nThe following list of default skipped commands is truncated when viewed in Settings Editor. To see the full list, {1} and search for the first command from the list below.\n\n \n\nDefault Skipped Commands:\n\n{0}", ++ "A set of command IDs whose keybindings will not be sent to the shell but instead always be handled by BradfordCode. This allows keybindings that would normally be consumed by the shell to act instead the same as when the terminal is not focused, for example `Ctrl+P` to launch Quick Open.\n\n \n\nMany commands are skipped by default. To override a default and pass that command's keybinding to the shell instead, add the command prefixed with the `-` character. For example add `-workbench.action.quickOpen` to allow `Ctrl+P` to reach the shell.\n\n \n\nThe following list of default skipped commands is truncated when viewed in Settings Editor. To see the full list, {1} and search for the first command from the list below.\n\n \n\nDefault Skipped Commands:\n\n{0}", DEFAULT_COMMANDS_TO_SKIP_SHELL.sort().map(command => `- ${command}`).join('\n'), `[${localize('openDefaultSettingsJson', "open the default settings JSON")}](command:workbench.action.openRawDefaultSettings '${localize('openDefaultSettingsJson.capitalized', "Open Default Settings (JSON)")}')`, @@ -1648,7 +1632,7 @@ index 3d3e8f2d..5cbfc9ad 100644 }, [TerminalSettingId.AllowChords]: { - markdownDescription: localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass {0}, setting this to false is particularly useful when you want ctrl+k to go to your shell (not VS Code).", '`#terminal.integrated.commandsToSkipShell#`'), -+ markdownDescription: localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass {0}, setting this to false is particularly useful when you want ctrl+k to go to your shell (not !!APP_NAME!!).", '`#terminal.integrated.commandsToSkipShell#`'), ++ markdownDescription: localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass {0}, setting this to false is particularly useful when you want ctrl+k to go to your shell (not BradfordCode).", '`#terminal.integrated.commandsToSkipShell#`'), type: 'boolean', default: true }, @@ -1657,7 +1641,7 @@ index 3d3e8f2d..5cbfc9ad 100644 [TerminalSettingId.EnvMacOs]: { restricted: true, - markdownDescription: localize('terminal.integrated.env.osx', "Object with environment variables that will be added to the VS Code process to be used by the terminal on macOS. Set to `null` to delete the environment variable."), -+ markdownDescription: localize('terminal.integrated.env.osx', "Object with environment variables that will be added to the !!APP_NAME!! process to be used by the terminal on macOS. Set to `null` to delete the environment variable."), ++ markdownDescription: localize('terminal.integrated.env.osx', "Object with environment variables that will be added to the BradfordCode process to be used by the terminal on macOS. Set to `null` to delete the environment variable."), type: 'object', additionalProperties: { type: ['string', 'null'] @@ -1666,7 +1650,7 @@ index 3d3e8f2d..5cbfc9ad 100644 [TerminalSettingId.EnvLinux]: { restricted: true, - markdownDescription: localize('terminal.integrated.env.linux', "Object with environment variables that will be added to the VS Code process to be used by the terminal on Linux. Set to `null` to delete the environment variable."), -+ markdownDescription: localize('terminal.integrated.env.linux', "Object with environment variables that will be added to the !!APP_NAME!! process to be used by the terminal on Linux. Set to `null` to delete the environment variable."), ++ markdownDescription: localize('terminal.integrated.env.linux', "Object with environment variables that will be added to the BradfordCode process to be used by the terminal on Linux. Set to `null` to delete the environment variable."), type: 'object', additionalProperties: { type: ['string', 'null'] @@ -1675,7 +1659,7 @@ index 3d3e8f2d..5cbfc9ad 100644 [TerminalSettingId.EnvWindows]: { restricted: true, - markdownDescription: localize('terminal.integrated.env.windows', "Object with environment variables that will be added to the VS Code process to be used by the terminal on Windows. Set to `null` to delete the environment variable."), -+ markdownDescription: localize('terminal.integrated.env.windows', "Object with environment variables that will be added to the !!APP_NAME!! process to be used by the terminal on Windows. Set to `null` to delete the environment variable."), ++ markdownDescription: localize('terminal.integrated.env.windows', "Object with environment variables that will be added to the BradfordCode process to be used by the terminal on Windows. Set to `null` to delete the environment variable."), type: 'object', additionalProperties: { type: ['string', 'null'] @@ -1684,7 +1668,7 @@ index 3d3e8f2d..5cbfc9ad 100644 [TerminalSettingId.WindowsUseConptyDll]: { restricted: true, - markdownDescription: localize('terminal.integrated.windowsUseConptyDll', "Whether to use the conpty.dll (v1.25.260303002) shipped with VS Code, instead of the one bundled with Windows."), -+ markdownDescription: localize('terminal.integrated.windowsUseConptyDll', "Whether to use the conpty.dll (v1.25.260303002) shipped with !!APP_NAME!!, instead of the one bundled with Windows."), ++ markdownDescription: localize('terminal.integrated.windowsUseConptyDll', "Whether to use the conpty.dll (v1.25.260303002) shipped with BradfordCode, instead of the one bundled with Windows."), type: 'boolean', default: true, }, @@ -1693,7 +1677,7 @@ index 3d3e8f2d..5cbfc9ad 100644 [TerminalSettingId.ShellIntegrationEnabled]: { restricted: true, - markdownDescription: localize('terminal.integrated.shellIntegration.enabled', "Determines whether or not shell integration is auto-injected to support features like enhanced command tracking and current working directory detection. \n\nShell integration works by injecting the shell with a startup script. The script gives VS Code insight into what is happening within the terminal.\n\nSupported shells:\n\n- Linux/macOS: bash, fish, pwsh, zsh\n - Windows: pwsh, git bash\n\nThis setting applies only when terminals are created, so you will need to restart your terminals for it to take effect.\n\n Note that the script injection may not work if you have custom arguments defined in the terminal profile, have enabled {1}, have a [complex bash `PROMPT_COMMAND`](https://code.visualstudio.com/docs/editor/integrated-terminal#_complex-bash-promptcommand), or other unsupported setup. To disable decorations, see {0}", '`#terminal.integrated.shellIntegration.decorationsEnabled#`', '`#editor.accessibilitySupport#`'), -+ markdownDescription: localize('terminal.integrated.shellIntegration.enabled', "Determines whether or not shell integration is auto-injected to support features like enhanced command tracking and current working directory detection. \n\nShell integration works by injecting the shell with a startup script. The script gives !!APP_NAME!! insight into what is happening within the terminal.\n\nSupported shells:\n\n- Linux/macOS: bash, fish, pwsh, zsh\n - Windows: pwsh, git bash\n\nThis setting applies only when terminals are created, so you will need to restart your terminals for it to take effect.\n\n Note that the script injection may not work if you have custom arguments defined in the terminal profile, have enabled {1}, have a [complex bash `PROMPT_COMMAND`](https://code.visualstudio.com/docs/editor/integrated-terminal#_complex-bash-promptcommand), or other unsupported setup. To disable decorations, see {0}", '`#terminal.integrated.shellIntegration.decorationsEnabled#`', '`#editor.accessibilitySupport#`'), ++ markdownDescription: localize('terminal.integrated.shellIntegration.enabled', "Determines whether or not shell integration is auto-injected to support features like enhanced command tracking and current working directory detection. \n\nShell integration works by injecting the shell with a startup script. The script gives BradfordCode insight into what is happening within the terminal.\n\nSupported shells:\n\n- Linux/macOS: bash, fish, pwsh, zsh\n - Windows: pwsh, git bash\n\nThis setting applies only when terminals are created, so you will need to restart your terminals for it to take effect.\n\n Note that the script injection may not work if you have custom arguments defined in the terminal profile, have enabled {1}, have a [complex bash `PROMPT_COMMAND`](https://code.visualstudio.com/docs/editor/integrated-terminal#_complex-bash-promptcommand), or other unsupported setup. To disable decorations, see {0}", '`#terminal.integrated.shellIntegration.decorationsEnabled#`', '`#editor.accessibilitySupport#`'), type: 'boolean', default: true }, @@ -1706,7 +1690,7 @@ index dc20533b..b97acfc7 100644 export const terminalAutoRepliesConfiguration: IStringDictionary = { [TerminalAutoRepliesSettingId.AutoReplies]: { - markdownDescription: localize('terminal.integrated.autoReplies', "A set of messages that, when encountered in the terminal, will be automatically responded to. Provided the message is specific enough, this can help automate away common responses.\n\nRemarks:\n\n- Use {0} to automatically respond to the terminate batch job prompt on Windows.\n- The message includes escape sequences so the reply might not happen with styled text.\n- Each reply can only happen once every second.\n- Use {1} in the reply to mean the enter key.\n- To unset a default key, set the value to null.\n- Restart VS Code if new don't apply.", '`"Terminate batch job (Y/N)": "Y\\r"`', '`"\\r"`'), -+ markdownDescription: localize('terminal.integrated.autoReplies', "A set of messages that, when encountered in the terminal, will be automatically responded to. Provided the message is specific enough, this can help automate away common responses.\n\nRemarks:\n\n- Use {0} to automatically respond to the terminate batch job prompt on Windows.\n- The message includes escape sequences so the reply might not happen with styled text.\n- Each reply can only happen once every second.\n- Use {1} in the reply to mean the enter key.\n- To unset a default key, set the value to null.\n- Restart !!APP_NAME!! if new don't apply.", '`"Terminate batch job (Y/N)": "Y\\r"`', '`"\\r"`'), ++ markdownDescription: localize('terminal.integrated.autoReplies', "A set of messages that, when encountered in the terminal, will be automatically responded to. Provided the message is specific enough, this can help automate away common responses.\n\nRemarks:\n\n- Use {0} to automatically respond to the terminate batch job prompt on Windows.\n- The message includes escape sequences so the reply might not happen with styled text.\n- Each reply can only happen once every second.\n- Use {1} in the reply to mean the enter key.\n- To unset a default key, set the value to null.\n- Restart BradfordCode if new don't apply.", '`"Terminate batch job (Y/N)": "Y\\r"`', '`"\\r"`'), type: 'object', additionalProperties: { oneOf: [{ @@ -1719,7 +1703,7 @@ index d595ccbd..0fd28a30 100644 'problemMatcher': { 'type': 'array', - 'description': `The problem matcher to use to parse task output for errors and warnings. Can be a predefined matcher like '$tsc' (TypeScript), '$eslint - stylish', '$gcc', etc., or a custom pattern defined in tasks.json. This helps VS Code display errors in the Problems panel and enables quick navigation to error locations.`, -+ 'description': `The problem matcher to use to parse task output for errors and warnings. Can be a predefined matcher like '$tsc' (TypeScript), '$eslint - stylish', '$gcc', etc., or a custom pattern defined in tasks.json. This helps !!APP_NAME!! display errors in the Problems panel and enables quick navigation to error locations.`, ++ 'description': `The problem matcher to use to parse task output for errors and warnings. Can be a predefined matcher like '$tsc' (TypeScript), '$eslint - stylish', '$gcc', etc., or a custom pattern defined in tasks.json. This helps BradfordCode display errors in the Problems panel and enables quick navigation to error locations.`, 'items': { 'type': 'string' } @@ -1732,7 +1716,7 @@ index 5fc22b73..e02cab68 100644 legacyToolReferenceFullNames: ['runTasks/runTask'], displayName: localize('runInTerminalTool.displayName', 'Run Task'), - modelDescription: `Runs a VS Code task.\n\n- If you see that an appropriate task exists for building or running code, prefer to use this tool to run the task instead of using the ${TerminalToolId.RunInTerminal} tool.\n- Make sure that any appropriate build or watch task is running before trying to run tests or execute code.\n- If the user asks to run a task, use this tool to do so.`, -+ modelDescription: `Runs a !!APP_NAME!! task.\n\n- If you see that an appropriate task exists for building or running code, prefer to use this tool to run the task instead of using the ${TerminalToolId.RunInTerminal} tool.\n- Make sure that any appropriate build or watch task is running before trying to run tests or execute code.\n- If the user asks to run a task, use this tool to do so.`, ++ modelDescription: `Runs a BradfordCode task.\n\n- If you see that an appropriate task exists for building or running code, prefer to use this tool to run the task instead of using the ${TerminalToolId.RunInTerminal} tool.\n- Make sure that any appropriate build or watch task is running before trying to run tests or execute code.\n- If the user asks to run a task, use this tool to do so.`, userDescription: localize('runInTerminalTool.userDescription', 'Run tasks in the workspace'), icon: Codicon.tools, source: ToolDataSource.Internal, @@ -1745,7 +1729,7 @@ index 0f23e5a5..a3b75766 100644 run = () => workbenchExtensionEnablementService.setEnablement([extension], EnablementState.EnabledWorkspace); } else { - message = localize('terminal.voice.installSpeechExtension', "Would you like to install 'VS Code Speech' extension from 'Microsoft'?"); -+ message = localize('terminal.voice.installSpeechExtension', "Would you like to install '!!APP_NAME!! Speech' extension from 'Microsoft'?"); ++ message = localize('terminal.voice.installSpeechExtension', "Would you like to install 'BradfordCode Speech' extension from 'Microsoft'?"); run = () => commandService.executeCommand('workbench.extensions.installExtension', 'ms-vscode.vscode-speech'); primaryButton = localize('installExtension', "Install Extension"); } @@ -1759,8 +1743,8 @@ index c182b9c2..34bfb4d7 100644 detail: newQuality === 'insider' ? - nls.localize('relaunchDetailInsiders', "Press the reload button to switch to the Insiders version of VS Code.") : - nls.localize('relaunchDetailStable', "Press the reload button to switch to the Stable version of VS Code."), -+ nls.localize('relaunchDetailInsiders', "Press the reload button to switch to the Insiders version of !!APP_NAME!!.") : -+ nls.localize('relaunchDetailStable', "Press the reload button to switch to the Stable version of !!APP_NAME!!."), ++ nls.localize('relaunchDetailInsiders', "Press the reload button to switch to the Insiders version of BradfordCode.") : ++ nls.localize('relaunchDetailStable', "Press the reload button to switch to the Stable version of BradfordCode."), primaryButton: nls.localize({ key: 'reload', comment: ['&& denotes a mnemonic'] }, "&&Reload") }); @@ -1769,7 +1753,7 @@ index c182b9c2..34bfb4d7 100644 type: Severity.Info, message: nls.localize('selectSyncService.message', "Choose the settings sync service to use after changing the version"), - detail: nls.localize('selectSyncService.detail', "The Insiders version of VS Code will synchronize your settings, keybindings, extensions, snippets and UI State using separate insiders settings sync service by default."), -+ detail: nls.localize('selectSyncService.detail', "The Insiders version of !!APP_NAME!! will synchronize your settings, keybindings, extensions, snippets and UI State using separate insiders settings sync service by default."), ++ detail: nls.localize('selectSyncService.detail', "The Insiders version of BradfordCode will synchronize your settings, keybindings, extensions, snippets and UI State using separate insiders settings sync service by default."), buttons: [ { label: nls.localize({ key: 'use insiders', comment: ['&& denotes a mnemonic'] }, "&&Insiders"), @@ -1782,13 +1766,13 @@ index 393c8c36..a8e359db 100644 if (defaultTrustedDomains.length > 0) { - content += `// By default, VS Code trusts "localhost" as well as the following domains:\n`; -+ content += `// By default, !!APP_NAME!! trusts "localhost" as well as the following domains:\n`; ++ content += `// By default, BradfordCode trusts "localhost" as well as the following domains:\n`; defaultTrustedDomains.forEach(d => { content += `// - "${d}"\n`; }); } else { - content += `// By default, VS Code trusts "localhost".\n`; -+ content += `// By default, !!APP_NAME!! trusts "localhost".\n`; ++ content += `// By default, BradfordCode trusts "localhost".\n`; } content += CONFIG_HELP_TEXT_AFTER; @@ -1801,7 +1785,7 @@ index 067580a4..0982e9d6 100644 }, metadata: { - description: localize2('minWelcomeDescription', 'Opens a Walkthrough to help you get started in VS Code.') -+ description: localize2('minWelcomeDescription', 'Opens a Walkthrough to help you get started in !!APP_NAME!!.') ++ description: localize2('minWelcomeDescription', 'Opens a Walkthrough to help you get started in BradfordCode.') } }); } @@ -1810,7 +1794,7 @@ index 067580a4..0982e9d6 100644 'enumDescriptions': [ localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.none' }, "Start without an editor."), - localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePage' }, "Open the Welcome page, with content to aid in getting started with VS Code and extensions."), -+ localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePage' }, "Open the Welcome page, with content to aid in getting started with !!APP_NAME!! and extensions."), ++ localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePage' }, "Open the Welcome page, with content to aid in getting started with BradfordCode and extensions."), localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.readme' }, "Open the README when opening a folder that contains one, fallback to 'welcomePage' otherwise. Note: This is only observed as a global configuration, it will be ignored if set in a workspace or folder configuration."), localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.newUntitledFile' }, "Open a new untitled text file (only applies when opening an empty window)."), localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePageInEmptyWorkbench' }, "Open the Welcome page when opening an empty workbench."), @@ -1823,7 +1807,7 @@ index 297598ef..45282b79 100644 { label: 'onCommand', - description: localize('walkthroughs.steps.completionEvents.onCommand', 'Check off step when a given command is executed anywhere in VS Code.'), -+ description: localize('walkthroughs.steps.completionEvents.onCommand', 'Check off step when a given command is executed anywhere in !!APP_NAME!!.'), ++ description: localize('walkthroughs.steps.completionEvents.onCommand', 'Check off step when a given command is executed anywhere in BradfordCode.'), body: 'onCommand:${1:commandId}' }, { @@ -1889,13 +1873,13 @@ index 168a3d01..fd76dda2 100644 { id: 'Setup', - title: localize('gettingStarted.setup.title', "Get started with VS Code"), -+ title: localize('gettingStarted.setup.title', "Get started with !!APP_NAME!!"), ++ title: localize('gettingStarted.setup.title', "Get started with BradfordCode"), description: localize('gettingStarted.setup.description', "Customize your editor, learn the basics, and start coding"), isFeatured: true, icon: setupIcon, when: '!isWeb', - walkthroughPageTitle: localize('gettingStarted.setup.walkthroughPageTitle', 'Setup VS Code'), -+ walkthroughPageTitle: localize('gettingStarted.setup.walkthroughPageTitle', 'Setup !!APP_NAME!!'), ++ walkthroughPageTitle: localize('gettingStarted.setup.walkthroughPageTitle', 'Setup BradfordCode'), next: 'Beginner', content: { type: 'steps', @@ -1913,8 +1897,8 @@ index 168a3d01..fd76dda2 100644 title: localize('gettingStarted.videoTutorial.title', "Watch video tutorials"), - description: localize('gettingStarted.videoTutorial.description.interpolated', "Watch the first in a series of short & practical video tutorials for VS Code's key features.\n{0}", Button(localize('watch', "Watch Tutorial"), 'https://aka.ms/vscode-getting-started-video')), - media: { type: 'svg', altText: 'VS Code Settings', path: 'learn.svg' }, -+ description: localize('gettingStarted.videoTutorial.description.interpolated', "Watch the first in a series of short & practical video tutorials for !!APP_NAME!!'s key features.\n{0}", Button(localize('watch', "Watch Tutorial"), 'https://aka.ms/vscode-getting-started-video')), -+ media: { type: 'svg', altText: '!!APP_NAME!! Settings', path: 'learn.svg' }, ++ description: localize('gettingStarted.videoTutorial.description.interpolated', "Watch the first in a series of short & practical video tutorials for BradfordCode's key features.\n{0}", Button(localize('watch', "Watch Tutorial"), 'https://aka.ms/vscode-getting-started-video')), ++ media: { type: 'svg', altText: 'BradfordCode Settings', path: 'learn.svg' }, } ] } @@ -1923,14 +1907,14 @@ index 168a3d01..fd76dda2 100644 { id: 'SetupWeb', - title: localize('gettingStarted.setupWeb.title', "Get Started with VS Code for the Web"), -+ title: localize('gettingStarted.setupWeb.title', "Get Started with !!APP_NAME!! for the Web"), ++ title: localize('gettingStarted.setupWeb.title', "Get Started with BradfordCode for the Web"), description: localize('gettingStarted.setupWeb.description', "Customize your editor, learn the basics, and start coding"), isFeatured: true, icon: setupIcon, when: 'isWeb', next: 'Beginner', - walkthroughPageTitle: localize('gettingStarted.setupWeb.walkthroughPageTitle', 'Setup VS Code Web'), -+ walkthroughPageTitle: localize('gettingStarted.setupWeb.walkthroughPageTitle', 'Setup !!APP_NAME!! Web'), ++ walkthroughPageTitle: localize('gettingStarted.setupWeb.walkthroughPageTitle', 'Setup BradfordCode Web'), content: { type: 'steps', steps: [ @@ -1939,11 +1923,11 @@ index 168a3d01..fd76dda2 100644 id: 'extensionsWebWeb', title: localize('gettingStarted.extensions.title', "Code with extensions"), - description: localize('gettingStarted.extensionsWeb.description.interpolated', "Extensions are VS Code's power-ups. A growing number are becoming available in the web.\n{0}", Button(localize('browsePopularWeb', "Browse Popular Web Extensions"), 'command:workbench.extensions.action.showPopularExtensions')), -+ description: localize('gettingStarted.extensionsWeb.description.interpolated', "Extensions are !!APP_NAME!!'s power-ups. A growing number are becoming available in the web.\n{0}", Button(localize('browsePopularWeb', "Browse Popular Web Extensions"), 'command:workbench.extensions.action.showPopularExtensions')), ++ description: localize('gettingStarted.extensionsWeb.description.interpolated', "Extensions are BradfordCode's power-ups. A growing number are becoming available in the web.\n{0}", Button(localize('browsePopularWeb', "Browse Popular Web Extensions"), 'command:workbench.extensions.action.showPopularExtensions')), when: 'workspacePlatform == \'webworker\'', media: { - type: 'svg', altText: 'VS Code extension marketplace with featured language extensions', path: 'extensions-web.svg' -+ type: 'svg', altText: '!!APP_NAME!! extension marketplace with featured language extensions', path: 'extensions-web.svg' ++ type: 'svg', altText: 'BradfordCode extension marketplace with featured language extensions', path: 'extensions-web.svg' }, }, { @@ -1965,14 +1949,14 @@ index 168a3d01..fd76dda2 100644 id: 'commandPaletteTaskWeb', title: localize('gettingStarted.commandPalette.title', "Unlock productivity with the Command Palette "), - description: localize('gettingStarted.commandPalette.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in VS Code.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')), -+ description: localize('gettingStarted.commandPalette.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in !!APP_NAME!!.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')), ++ description: localize('gettingStarted.commandPalette.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in BradfordCode.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')), media: { type: 'svg', altText: 'Command Palette overlay for searching and executing commands.', path: 'commandPalette.svg' }, }, { id: 'pickAFolderTask-WebWeb', title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), - description: localize('gettingStarted.setup.OpenFolderWeb.description.interpolated', "You're all set to start coding. You can open a local project or a remote repository to get your files into VS Code.\n{0}\n{1}", Button(localize('openFolder', "Open Folder"), 'command:workbench.action.addRootFolder'), Button(localize('openRepository', "Open Repository"), 'command:remoteHub.openRepository')), -+ description: localize('gettingStarted.setup.OpenFolderWeb.description.interpolated', "You're all set to start coding. You can open a local project or a remote repository to get your files into !!APP_NAME!!.\n{0}\n{1}", Button(localize('openFolder', "Open Folder"), 'command:workbench.action.addRootFolder'), Button(localize('openRepository', "Open Repository"), 'command:remoteHub.openRepository')), ++ description: localize('gettingStarted.setup.OpenFolderWeb.description.interpolated', "You're all set to start coding. You can open a local project or a remote repository to get your files into BradfordCode.\n{0}\n{1}", Button(localize('openFolder', "Open Folder"), 'command:workbench.action.addRootFolder'), Button(localize('openRepository', "Open Repository"), 'command:remoteHub.openRepository')), when: 'workspaceFolderCount == 0', media: { type: 'svg', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: 'openFolder.svg' @@ -1981,13 +1965,13 @@ index 168a3d01..fd76dda2 100644 id: 'SetupAccessibility', title: localize('gettingStarted.setupAccessibility.title', "Get Started with Accessibility Features"), - description: localize('gettingStarted.setupAccessibility.description', "Learn the tools and shortcuts that make VS Code accessible. Note that some actions are not actionable from within the context of the walkthrough."), -+ description: localize('gettingStarted.setupAccessibility.description', "Learn the tools and shortcuts that make !!APP_NAME!! accessible. Note that some actions are not actionable from within the context of the walkthrough."), ++ description: localize('gettingStarted.setupAccessibility.description', "Learn the tools and shortcuts that make BradfordCode accessible. Note that some actions are not actionable from within the context of the walkthrough."), isFeatured: true, icon: setupIcon, when: CONTEXT_ACCESSIBILITY_MODE_ENABLED.key, next: 'Setup', - walkthroughPageTitle: localize('gettingStarted.setupAccessibility.walkthroughPageTitle', 'Setup VS Code Accessibility'), -+ walkthroughPageTitle: localize('gettingStarted.setupAccessibility.walkthroughPageTitle', 'Setup !!APP_NAME!! Accessibility'), ++ walkthroughPageTitle: localize('gettingStarted.setupAccessibility.walkthroughPageTitle', 'Setup BradfordCode Accessibility'), content: { type: 'steps', steps: [ @@ -1996,7 +1980,7 @@ index 168a3d01..fd76dda2 100644 id: 'commandPaletteTaskAccessibility', title: localize('gettingStarted.commandPaletteAccessibility.title', "Unlock productivity with the Command Palette "), - description: localize('gettingStarted.commandPaletteAccessibility.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in VS Code.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')), -+ description: localize('gettingStarted.commandPaletteAccessibility.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in !!APP_NAME!!.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')), ++ description: localize('gettingStarted.commandPaletteAccessibility.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in BradfordCode.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')), media: { type: 'markdown', path: 'empty' }, }, { @@ -2005,28 +1989,28 @@ index 168a3d01..fd76dda2 100644 id: 'settingsAndSync', title: localize('gettingStarted.settings.title', "Tune your settings"), - description: localize('gettingStarted.settingsAndSync.description.interpolated', "Customize every aspect of VS Code and [sync](command:workbench.userDataSync.actions.turnOn) customizations across devices.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')), -+ description: localize('gettingStarted.settingsAndSync.description.interpolated', "Customize every aspect of !!APP_NAME!! and [sync](command:workbench.userDataSync.actions.turnOn) customizations across devices.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')), ++ description: localize('gettingStarted.settingsAndSync.description.interpolated', "Customize every aspect of BradfordCode and [sync](command:workbench.userDataSync.actions.turnOn) customizations across devices.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')), when: 'workspacePlatform != \'webworker\' && syncStatus != uninitialized', completionEvents: ['onEvent:sync-enabled'], media: { - type: 'svg', altText: 'VS Code Settings', path: 'settings.svg' -+ type: 'svg', altText: '!!APP_NAME!! Settings', path: 'settings.svg' ++ type: 'svg', altText: 'BradfordCode Settings', path: 'settings.svg' }, }, { id: 'extensions', title: localize('gettingStarted.extensions.title', "Code with extensions"), - description: localize('gettingStarted.extensions.description.interpolated', "Extensions are VS Code's power-ups. They range from handy productivity hacks, expanding out-of-the-box features, to adding completely new capabilities.\n{0}", Button(localize('browsePopular', "Browse Popular Extensions"), 'command:workbench.extensions.action.showPopularExtensions')), -+ description: localize('gettingStarted.extensions.description.interpolated', "Extensions are !!APP_NAME!!'s power-ups. They range from handy productivity hacks, expanding out-of-the-box features, to adding completely new capabilities.\n{0}", Button(localize('browsePopular', "Browse Popular Extensions"), 'command:workbench.extensions.action.showPopularExtensions')), ++ description: localize('gettingStarted.extensions.description.interpolated', "Extensions are BradfordCode's power-ups. They range from handy productivity hacks, expanding out-of-the-box features, to adding completely new capabilities.\n{0}", Button(localize('browsePopular', "Browse Popular Extensions"), 'command:workbench.extensions.action.showPopularExtensions')), when: 'workspacePlatform != \'webworker\'', media: { - type: 'svg', altText: 'VS Code extension marketplace with featured language extensions', path: 'extensions.svg' -+ type: 'svg', altText: '!!APP_NAME!! extension marketplace with featured language extensions', path: 'extensions.svg' ++ type: 'svg', altText: 'BradfordCode extension marketplace with featured language extensions', path: 'extensions.svg' }, }, { diff --git a/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.ts b/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.ts -index ad276fc2..90e1e643 100644 +index c1fb3e12..4bbf4eb3 100644 --- a/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.ts +++ b/src/vs/workbench/contrib/welcomeOnboarding/browser/onboardingVariationA.ts @@ -180,7 +180,7 @@ export class OnboardingVariationA extends Disposable implements IOnboardingServi @@ -2034,7 +2018,7 @@ index ad276fc2..90e1e643 100644 this.overlay.setAttribute('role', 'dialog'); this.overlay.setAttribute('aria-modal', 'true'); - this.overlay.setAttribute('aria-label', localize('onboarding.a.aria', "Welcome to Visual Studio Code")); -+ this.overlay.setAttribute('aria-label', localize('onboarding.a.aria', "Welcome to !!APP_NAME!!")); ++ this.overlay.setAttribute('aria-label', localize('onboarding.a.aria', "Welcome to BradfordCode")); // Card this.card = append(this.overlay, $('.onboarding-a-card')); @@ -2047,7 +2031,7 @@ index bdd30bf2..cc397e9c 100644 return ` ## Interactive Editor Playground -The core editor in VS Code is packed with features. This page highlights a number of them and lets you interactively try them out through the use of a number of embedded editors. For full details on the editor features for VS Code and more head over to our [documentation](https://code.visualstudio.com/docs). -+The core editor in !!APP_NAME!! is packed with features. This page highlights a number of them and lets you interactively try them out through the use of a number of embedded editors. For full details on the editor features for !!APP_NAME!! and more head over to our [documentation](https://code.visualstudio.com/docs). ++The core editor in BradfordCode is packed with features. This page highlights a number of them and lets you interactively try them out through the use of a number of embedded editors. For full details on the editor features for BradfordCode and more head over to our [documentation](https://code.visualstudio.com/docs). * [Multi-cursor Editing](#multi-cursor-editing) - block selection, select all occurrences, add additional cursors and more. * [IntelliSense](#intellisense) - get code assistance and parameter suggestions for your code and external modules. @@ -2056,7 +2040,7 @@ index bdd30bf2..cc397e9c 100644 ### IntelliSense -Visual Studio Code comes with the powerful IntelliSense for JavaScript and TypeScript pre-installed. In the below example, position the text cursor right after the dot and press kb(editor.action.triggerSuggest) to invoke IntelliSense. Notice how the suggestions come from the Canvas API. -+!!APP_NAME!! comes with the powerful IntelliSense for JavaScript and TypeScript pre-installed. In the below example, position the text cursor right after the dot and press kb(editor.action.triggerSuggest) to invoke IntelliSense. Notice how the suggestions come from the Canvas API. ++BradfordCode comes with the powerful IntelliSense for JavaScript and TypeScript pre-installed. In the below example, position the text cursor right after the dot and press kb(editor.action.triggerSuggest) to invoke IntelliSense. Notice how the suggestions come from the Canvas API. |||js const canvas = document.querySelector('canvas'); @@ -2065,7 +2049,7 @@ index bdd30bf2..cc397e9c 100644 ||| -> **JSDoc Tip:** VS Code's IntelliSense uses JSDoc comments to provide richer suggestions. The types and documentation from JSDoc comments show up when you hover over a reference to |Book| or in IntelliSense when you create a new instance of |Book|. -+> **JSDoc Tip:** !!APP_NAME!!'s IntelliSense uses JSDoc comments to provide richer suggestions. The types and documentation from JSDoc comments show up when you hover over a reference to |Book| or in IntelliSense when you create a new instance of |Book|. ++> **JSDoc Tip:** BradfordCode's IntelliSense uses JSDoc comments to provide richer suggestions. The types and documentation from JSDoc comments show up when you hover over a reference to |Book| or in IntelliSense when you create a new instance of |Book|. ` : ''} ### Formatting @@ -2074,12 +2058,12 @@ index bdd30bf2..cc397e9c 100644 ## Thanks! -Well if you have got this far then you will have touched on some of the editing features in Visual Studio Code. But don't stop now :) We have lots of additional [documentation](https://code.visualstudio.com/docs), [introductory videos](https://code.visualstudio.com/docs/getstarted/introvideos) and [tips and tricks](https://go.microsoft.com/fwlink/?linkid=852118) for the product that will help you learn how to use it. And while you are here, here are a few additional things you can try: -+Well if you have got this far then you will have touched on some of the editing features in !!APP_NAME!!. But don't stop now :) We have lots of additional [documentation](https://code.visualstudio.com/docs), [introductory videos](https://code.visualstudio.com/docs/getstarted/introvideos) and [tips and tricks](https://go.microsoft.com/fwlink/?linkid=852118) for the product that will help you learn how to use it. And while you are here, here are a few additional things you can try: ++Well if you have got this far then you will have touched on some of the editing features in BradfordCode. But don't stop now :) We have lots of additional [documentation](https://code.visualstudio.com/docs), [introductory videos](https://code.visualstudio.com/docs/getstarted/introvideos) and [tips and tricks](https://go.microsoft.com/fwlink/?linkid=852118) for the product that will help you learn how to use it. And while you are here, here are a few additional things you can try: - Open the Integrated Terminal by pressing kb(workbench.action.terminal.toggleTerminal), then see what's possible by [reviewing the terminal documentation](https://code.visualstudio.com/docs/editor/integrated-terminal) - Work with version control by pressing kb(workbench.view.scm). Understand how to stage, commit, change branches, and view diffs and more by reviewing the [version control documentation](https://code.visualstudio.com/docs/editor/versioncontrol) - Browse thousands of extensions in our integrated gallery by pressing kb(workbench.view.extensions). The [documentation](https://code.visualstudio.com/docs/editor/extension-gallery) will show you how to see the most popular extensions, disable installed ones and more. diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts -index 64b8586a..20b82d7c 100644 +index 6cccbced..1e404b92 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -799,7 +799,7 @@ Registry.as(ConfigurationExtensions.Configuration) @@ -2087,7 +2071,7 @@ index 64b8586a..20b82d7c 100644 type: 'boolean', default: true, - description: localize('workspace.trust.description', "Controls whether or not Workspace Trust is enabled within VS Code."), -+ description: localize('workspace.trust.description', "Controls whether or not Workspace Trust is enabled within !!APP_NAME!!."), ++ description: localize('workspace.trust.description', "Controls whether or not Workspace Trust is enabled within BradfordCode."), tags: [WORKSPACE_TRUST_SETTING_TAG], scope: ConfigurationScope.APPLICATION, }, @@ -2096,25 +2080,25 @@ index 64b8586a..20b82d7c 100644 type: 'boolean', default: true, - markdownDescription: localize('workspace.trust.emptyWindow.description', "Controls whether or not the empty window is trusted by default within VS Code. When used with `#{0}#`, you can enable the full functionality of VS Code without prompting in an empty window.", WORKSPACE_TRUST_UNTRUSTED_FILES), -+ markdownDescription: localize('workspace.trust.emptyWindow.description', "Controls whether or not the empty window is trusted by default within !!APP_NAME!!. When used with `#{0}#`, you can enable the full functionality of !!APP_NAME!! without prompting in an empty window.", WORKSPACE_TRUST_UNTRUSTED_FILES), ++ markdownDescription: localize('workspace.trust.emptyWindow.description', "Controls whether or not the empty window is trusted by default within BradfordCode. When used with `#{0}#`, you can enable the full functionality of BradfordCode without prompting in an empty window.", WORKSPACE_TRUST_UNTRUSTED_FILES), tags: [WORKSPACE_TRUST_SETTING_TAG], scope: ConfigurationScope.APPLICATION } diff --git a/src/vs/workbench/electron-browser/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts -index 885cbf9b..4d008991 100644 +index 9db62d8f..26208123 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts -@@ -450,11 +450,11 @@ import product from '../../platform/product/common/product.js'; +@@ -451,11 +451,11 @@ import product from '../../platform/product/common/product.js'; }, 'disable-chromium-sandbox': { type: 'boolean', - description: localize('argv.disableChromiumSandbox', "Disables the Chromium sandbox. This is useful when running VS Code as elevated on Linux and running under Applocker on Windows.") -+ description: localize('argv.disableChromiumSandbox', "Disables the Chromium sandbox. This is useful when running !!APP_NAME!! as elevated on Linux and running under Applocker on Windows.") ++ description: localize('argv.disableChromiumSandbox', "Disables the Chromium sandbox. This is useful when running BradfordCode as elevated on Linux and running under Applocker on Windows.") }, 'use-inmemory-secretstorage': { type: 'boolean', - description: localize('argv.useInMemorySecretStorage', "Ensures that an in-memory store will be used for secret storage instead of using the OS's credential store. This is often used when running VS Code extension tests or when you're experiencing difficulties with the credential store.") -+ description: localize('argv.useInMemorySecretStorage', "Ensures that an in-memory store will be used for secret storage instead of using the OS's credential store. This is often used when running !!APP_NAME!! extension tests or when you're experiencing difficulties with the credential store.") ++ description: localize('argv.useInMemorySecretStorage', "Ensures that an in-memory store will be used for secret storage instead of using the OS's credential store. This is often used when running BradfordCode extension tests or when you're experiencing difficulties with the credential store.") }, 'remote-debugging-port': { type: 'string', @@ -2127,7 +2111,7 @@ index e60991aa..f7e95209 100644 } - const productName = localize('VS Code for Web', "{0} for the Web", this.productService.nameLong); -+ const productName = localize('!!APP_NAME!! for Web', "{0} for the Web", this.productService.nameLong); ++ const productName = localize('BradfordCode for Web', "{0} for the Web", this.productService.nameLong); const virtualWorkspaceSupport = this.extensionManifestPropertiesService.getExtensionVirtualWorkspaceSupportType(manifest); const virtualWorkspaceSupportReason = getWorkspaceSupportTypeMessage(manifest.capabilities?.virtualWorkspaces); const hasLimitedSupport = virtualWorkspaceSupport === 'limited' || !!virtualWorkspaceSupportReason; @@ -2140,24 +2124,24 @@ index 09dd590e..d3fc08d7 100644 'vscode': { type: 'string', - description: nls.localize('vscode.extension.engines.vscode', 'For VS Code extensions, specifies the VS Code version that the extension is compatible with. Cannot be *. For example: ^1.105.0 indicates compatibility with a minimum VS Code version of 1.105.0.'), -+ description: nls.localize('vscode.extension.engines.vscode', 'For !!APP_NAME!! extensions, specifies the !!APP_NAME!! version that the extension is compatible with. Cannot be *. For example: ^1.105.0 indicates compatibility with a minimum !!APP_NAME!! version of 1.105.0.'), ++ description: nls.localize('vscode.extension.engines.vscode', 'For BradfordCode extensions, specifies the BradfordCode version that the extension is compatible with. Cannot be *. For example: ^1.105.0 indicates compatibility with a minimum BradfordCode version of 1.105.0.'), default: '^1.105.0', } } }, publisher: { - description: nls.localize('vscode.extension.publisher', 'The publisher of the VS Code extension.'), -+ description: nls.localize('vscode.extension.publisher', 'The publisher of the !!APP_NAME!! extension.'), ++ description: nls.localize('vscode.extension.publisher', 'The publisher of the BradfordCode extension.'), type: 'string' }, displayName: { - description: nls.localize('vscode.extension.displayName', 'The display name for the extension used in the VS Code gallery.'), -+ description: nls.localize('vscode.extension.displayName', 'The display name for the extension used in the !!APP_NAME!! gallery.'), ++ description: nls.localize('vscode.extension.displayName', 'The display name for the extension used in the BradfordCode gallery.'), type: 'string' }, categories: { - description: nls.localize('vscode.extension.categories', 'The categories used by the VS Code gallery to categorize the extension.'), -+ description: nls.localize('vscode.extension.categories', 'The categories used by the !!APP_NAME!! gallery to categorize the extension.'), ++ description: nls.localize('vscode.extension.categories', 'The categories used by the BradfordCode gallery to categorize the extension.'), type: 'array', uniqueItems: true, items: { @@ -2166,11 +2150,11 @@ index 09dd590e..d3fc08d7 100644 galleryBanner: { type: 'object', - description: nls.localize('vscode.extension.galleryBanner', 'Banner used in the VS Code marketplace.'), -+ description: nls.localize('vscode.extension.galleryBanner', 'Banner used in the !!APP_NAME!! marketplace.'), ++ description: nls.localize('vscode.extension.galleryBanner', 'Banner used in the BradfordCode marketplace.'), properties: { color: { - description: nls.localize('vscode.extension.galleryBanner.color', 'The banner color on the VS Code marketplace page header.'), -+ description: nls.localize('vscode.extension.galleryBanner.color', 'The banner color on the !!APP_NAME!! marketplace page header.'), ++ description: nls.localize('vscode.extension.galleryBanner.color', 'The banner color on the BradfordCode marketplace page header.'), type: 'string' }, theme: { @@ -2179,7 +2163,7 @@ index 09dd590e..d3fc08d7 100644 }, contributes: { - description: nls.localize('vscode.extension.contributes', 'All contributions of the VS Code extension represented by this package.'), -+ description: nls.localize('vscode.extension.contributes', 'All contributions of the !!APP_NAME!! extension represented by this package.'), ++ description: nls.localize('vscode.extension.contributes', 'All contributions of the BradfordCode extension represented by this package.'), type: 'object', // eslint-disable-next-line local/code-no-any-casts properties: { @@ -2188,7 +2172,7 @@ index 09dd590e..d3fc08d7 100644 }, activationEvents: { - description: nls.localize('vscode.extension.activationEvents', 'Activation events for the VS Code extension.'), -+ description: nls.localize('vscode.extension.activationEvents', 'Activation events for the !!APP_NAME!! extension.'), ++ description: nls.localize('vscode.extension.activationEvents', 'Activation events for the BradfordCode extension.'), type: 'array', items: { type: 'string', @@ -2197,7 +2181,7 @@ index 09dd590e..d3fc08d7 100644 { label: '*', - description: nls.localize('vscode.extension.activationEvents.star', 'An activation event emitted on VS Code startup. To ensure a great end user experience, please use this activation event in your extension only when no other activation events combination works in your use-case.'), -+ description: nls.localize('vscode.extension.activationEvents.star', 'An activation event emitted on !!APP_NAME!! startup. To ensure a great end user experience, please use this activation event in your extension only when no other activation events combination works in your use-case.'), ++ description: nls.localize('vscode.extension.activationEvents.star', 'An activation event emitted on BradfordCode startup. To ensure a great end user experience, please use this activation event in your extension only when no other activation events combination works in your use-case.'), body: '*' } ], @@ -2206,17 +2190,17 @@ index 09dd590e..d3fc08d7 100644 properties: { 'vscode:prepublish': { - description: nls.localize('vscode.extension.scripts.prepublish', 'Script executed before the package is published as a VS Code extension.'), -+ description: nls.localize('vscode.extension.scripts.prepublish', 'Script executed before the package is published as a !!APP_NAME!! extension.'), ++ description: nls.localize('vscode.extension.scripts.prepublish', 'Script executed before the package is published as a BradfordCode extension.'), type: 'string' }, 'vscode:uninstall': { - description: nls.localize('vscode.extension.scripts.uninstall', 'Uninstall hook for VS Code extension. Script that gets executed when the extension is completely uninstalled from VS Code which is when VS Code is restarted (shutdown and start) after the extension is uninstalled. Only Node scripts are supported.'), -+ description: nls.localize('vscode.extension.scripts.uninstall', 'Uninstall hook for !!APP_NAME!! extension. Script that gets executed when the extension is completely uninstalled from !!APP_NAME!! which is when !!APP_NAME!! is restarted (shutdown and start) after the extension is uninstalled. Only Node scripts are supported.'), ++ description: nls.localize('vscode.extension.scripts.uninstall', 'Uninstall hook for BradfordCode extension. Script that gets executed when the extension is completely uninstalled from BradfordCode which is when BradfordCode is restarted (shutdown and start) after the extension is uninstalled. Only Node scripts are supported.'), type: 'string' } } diff --git a/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts b/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts -index 2b6104a7..1eb63831 100644 +index 4340604b..9ea3a495 100644 --- a/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts @@ -165,7 +165,7 @@ export class NativeExtensionService extends AbstractExtensionService implements @@ -2224,7 +2208,7 @@ index 2b6104a7..1eb63831 100644 nls.localize('extensionService.versionMismatchCrash', "Extension host cannot start: version mismatch."), [{ - label: nls.localize('relaunch', "Relaunch VS Code"), -+ label: nls.localize('relaunch', "Relaunch !!APP_NAME!!"), ++ label: nls.localize('relaunch', "Relaunch BradfordCode"), run: () => { this._instantiationService.invokeFunction((accessor) => { const hostService = accessor.get(IHostService); @@ -2237,7 +2221,7 @@ index 43da4619..3a58c158 100644 if (isRemoteWindow) { const { confirmed } = await this.dialogService.confirm({ - message: reloadMessage ?? localize('reload message', "Switching a profile requires reloading VS Code."), -+ message: reloadMessage ?? localize('reload message', "Switching a profile requires reloading !!APP_NAME!!."), ++ message: reloadMessage ?? localize('reload message', "Switching a profile requires reloading BradfordCode."), primaryButton: localize('reload button', "&&Reload"), }); if (confirmed) { diff --git a/upstream/stable.json b/upstream/stable.json index 184c1d4..fe07fae 100644 --- a/upstream/stable.json +++ b/upstream/stable.json @@ -1,4 +1,4 @@ { - "tag": "1.124.2", - "commit": "6928394f91b684055b873eecb8bc281365131f1c" + "tag": "1.126.0", + "commit": "7e7950df89d055b5a378379db9ee14290772148a" } From f37192bcb4c70117726ed2fd2c65277024d2492b Mon Sep 17 00:00:00 2001 From: sbs44 <83440025+sbs44@users.noreply.github.com> Date: Sat, 27 Jun 2026 05:53:48 -0400 Subject: [PATCH 2/5] fix(patches): rebase 4 more patches for VS Code 1.126.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rebase chain (depending on upstream changes between 1.124.2 → 1.126.0): - 00-brand-remove-branding: !!APP_NAME!! placeholders restored (the previous regen captured the substituted form); validator hunk dropped (upstream removed the target strings). Context shift in extensionLinter.ts only. - 00-build-disable-mangle: line-number shift in build/lib/compilation.ts. - 00-copilot-fix-action-condition: one hunk needed re-targeting in chatSetupContributions.ts (lines moved); all other hunks applied clean. - 20-keymap-use-custom-lib: single new line for @vscodium/native-keymap in package-lock.json main deps. - 21-policy-use-custom-lib: swap @vscode/policy-watcher → @vscodium/policy-watcher in package.json + surgical removal of @vscode/policy-watcher metadata block in package-lock.json. Next failure surfaces at patches/50-build-improve-gulp-tasks.patch and likely 51-ext-copilot-remove-it.patch — both will need significant rebasing against upstream's restructured Copilot integration. Refs #21 --- patches/00-brand-remove-branding.patch | 452 +++++++++--------- patches/00-build-disable-mangle.patch | 10 +- patches/00-copilot-fix-action-condition.patch | 34 +- patches/20-keymap-use-custom-lib.patch | 34 +- patches/21-policy-use-custom-lib.patch | 74 ++- 5 files changed, 296 insertions(+), 308 deletions(-) diff --git a/patches/00-brand-remove-branding.patch b/patches/00-brand-remove-branding.patch index 1c017cd..4ec7494 100644 --- a/patches/00-brand-remove-branding.patch +++ b/patches/00-brand-remove-branding.patch @@ -7,7 +7,7 @@ index 6255123d..6e6013e7 100644 SourceFile=%1 Source File OpenWithCodeContextMenu=Open w&ith %1 -UpdatingVisualStudioCode=Updating Visual Studio Code... -+UpdatingVisualStudioCode=Updating BradfordCode... ++UpdatingVisualStudioCode=Updating !!APP_NAME!!... diff --git a/build/win32/i18n/messages.es.isl b/build/win32/i18n/messages.es.isl index 7f6f1bd0..f508cf3a 100644 --- a/build/win32/i18n/messages.es.isl @@ -18,7 +18,7 @@ index 7f6f1bd0..f508cf3a 100644 OpenWithCodeContextMenu=Abrir &con %1 -UpdatingVisualStudioCode=Actualizando Visual Studio Code... \ No newline at end of file -+UpdatingVisualStudioCode=Actualizando BradfordCode... ++UpdatingVisualStudioCode=Actualizando !!APP_NAME!!... \ No newline at end of file diff --git a/build/win32/i18n/messages.it.isl b/build/win32/i18n/messages.it.isl index ac64aae7..7184230e 100644 @@ -30,7 +30,7 @@ index ac64aae7..7184230e 100644 OpenWithCodeContextMenu=Apri con %1 -UpdatingVisualStudioCode=Aggiornamento di Visual Studio Code... \ No newline at end of file -+UpdatingVisualStudioCode=Aggiornamento di BradfordCode... ++UpdatingVisualStudioCode=Aggiornamento di !!APP_NAME!!... \ No newline at end of file diff --git a/build/win32/i18n/messages.pt-br.isl b/build/win32/i18n/messages.pt-br.isl index 08c77290..550d805d 100644 @@ -42,7 +42,7 @@ index 08c77290..550d805d 100644 OpenWithCodeContextMenu=Abrir com %1 -UpdatingVisualStudioCode=Atualizando o Visual Studio Code... \ No newline at end of file -+UpdatingVisualStudioCode=Atualizando o BradfordCode... ++UpdatingVisualStudioCode=Atualizando o !!APP_NAME!!... \ No newline at end of file diff --git a/extensions/configuration-editing/src/configurationEditingMain.ts b/extensions/configuration-editing/src/configurationEditingMain.ts index 2578270c..9589db2a 100644 @@ -54,8 +54,8 @@ index 2578270c..9589db2a 100644 return [ - { label: 'workspaceFolder', detail: vscode.l10n.t("The path of the folder opened in VS Code") }, - { label: 'workspaceFolderBasename', detail: vscode.l10n.t("The name of the folder opened in VS Code without any slashes (/)") }, -+ { label: 'workspaceFolder', detail: vscode.l10n.t("The path of the folder opened in BradfordCode") }, -+ { label: 'workspaceFolderBasename', detail: vscode.l10n.t("The name of the folder opened in BradfordCode without any slashes (/)") }, ++ { label: 'workspaceFolder', detail: vscode.l10n.t("The path of the folder opened in !!APP_NAME!!") }, ++ { label: 'workspaceFolderBasename', detail: vscode.l10n.t("The name of the folder opened in !!APP_NAME!! without any slashes (/)") }, { label: 'fileWorkspaceFolderBasename', detail: vscode.l10n.t("The current opened file workspace folder name without any slashes (/)") }, { label: 'relativeFile', detail: vscode.l10n.t("The current opened file relative to ${workspaceFolder}") }, { label: 'relativeFileDirname', detail: vscode.l10n.t("The current opened file's dirname relative to ${workspaceFolder}") }, @@ -68,7 +68,7 @@ index 12b50f31..19e38322 100644 completions.push(this.newSimpleCompletionItem(getText('folderName'), range, vscode.l10n.t("name of the workspace folder the file is contained in (e.g. myFolder)"))); completions.push(this.newSimpleCompletionItem(getText('folderPath'), range, vscode.l10n.t("file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)"))); - completions.push(this.newSimpleCompletionItem(getText('appName'), range, vscode.l10n.t("e.g. VS Code"))); -+ completions.push(this.newSimpleCompletionItem(getText('appName'), range, vscode.l10n.t("e.g. BradfordCode"))); ++ completions.push(this.newSimpleCompletionItem(getText('appName'), range, vscode.l10n.t("e.g. !!APP_NAME!!"))); completions.push(this.newSimpleCompletionItem(getText('remoteName'), range, vscode.l10n.t("e.g. SSH"))); completions.push(this.newSimpleCompletionItem(getText('dirty'), range, vscode.l10n.t("an indicator for when the active editor has unsaved changes"))); completions.push(this.newSimpleCompletionItem(getText('separator'), range, vscode.l10n.t("a conditional separator (' - ') that only shows when surrounded by variables with values"))); @@ -82,8 +82,8 @@ index d3de2241..91f278e6 100644 "css.title": "CSS", - "css.customData.desc": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-css-languageservice/blob/master/docs/customData.md).\n\nVS Code loads custom data on startup to enhance its CSS support for CSS custom properties (variables), at-rules, pseudo-classes, and pseudo-elements you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", - "css.completion.triggerPropertyValueCompletion.desc": "By default, VS Code triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", -+ "css.customData.desc": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-css-languageservice/blob/master/docs/customData.md).\n\nBradfordCode loads custom data on startup to enhance its CSS support for CSS custom properties (variables), at-rules, pseudo-classes, and pseudo-elements you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", -+ "css.completion.triggerPropertyValueCompletion.desc": "By default, BradfordCode triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", ++ "css.customData.desc": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-css-languageservice/blob/master/docs/customData.md).\n\n!!APP_NAME!! loads custom data on startup to enhance its CSS support for CSS custom properties (variables), at-rules, pseudo-classes, and pseudo-elements you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", ++ "css.completion.triggerPropertyValueCompletion.desc": "By default, !!APP_NAME!! triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", "css.completion.completePropertyWithSemicolon.desc": "Insert semicolon at end of line when completing CSS properties.", "css.lint.argumentsInColorFunction.desc": "Invalid number of parameters.", "css.lint.boxModel.desc": "Do not use `width` or `height` when using `padding` or `border`.", @@ -92,7 +92,7 @@ index d3de2241..91f278e6 100644 "css.lint.vendorPrefix.desc": "When using a vendor-specific prefix, also include the standard property.", "css.lint.zeroUnits.desc": "No unit for zero needed.", - "css.trace.server.desc": "Traces the communication between VS Code and the CSS language server.", -+ "css.trace.server.desc": "Traces the communication between BradfordCode and the CSS language server.", ++ "css.trace.server.desc": "Traces the communication between !!APP_NAME!! and the CSS language server.", "css.validate.title": "Controls CSS validation and problem severities.", "css.validate.desc": "Enables or disables all validations.", "css.hover.documentation": "Show property and value documentation in CSS hovers.", @@ -101,7 +101,7 @@ index d3de2241..91f278e6 100644 "css.format.maxPreserveNewLines.desc": "Maximum number of line breaks to be preserved in one chunk, when `#css.format.preserveNewLines#` is enabled.", "less.title": "LESS", - "less.completion.triggerPropertyValueCompletion.desc": "By default, VS Code triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", -+ "less.completion.triggerPropertyValueCompletion.desc": "By default, BradfordCode triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", ++ "less.completion.triggerPropertyValueCompletion.desc": "By default, !!APP_NAME!! triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", "less.completion.completePropertyWithSemicolon.desc": "Insert semicolon at end of line when completing CSS properties.", "less.lint.argumentsInColorFunction.desc": "Invalid number of parameters.", "less.lint.boxModel.desc": "Do not use `width` or `height` when using `padding` or `border`.", @@ -110,7 +110,7 @@ index d3de2241..91f278e6 100644 "less.format.maxPreserveNewLines.desc": "Maximum number of line breaks to be preserved in one chunk, when `#less.format.preserveNewLines#` is enabled.", "scss.title": "SCSS (Sass)", - "scss.completion.triggerPropertyValueCompletion.desc": "By default, VS Code triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", -+ "scss.completion.triggerPropertyValueCompletion.desc": "By default, BradfordCode triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", ++ "scss.completion.triggerPropertyValueCompletion.desc": "By default, !!APP_NAME!! triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", "scss.completion.completePropertyWithSemicolon.desc": "Insert semicolon at end of line when completing CSS properties.", "scss.lint.argumentsInColorFunction.desc": "Invalid number of parameters.", "scss.lint.boxModel.desc": "Do not use `width` or `height` when using `padding` or `border`.", @@ -121,7 +121,7 @@ index 683bcc7f..fb6b0523 100644 @@ -1,5 +1,5 @@ { - "description": "Emmet support for VS Code", -+ "description": "Emmet support for BradfordCode", ++ "description": "Emmet support for !!APP_NAME!!", "command.wrapWithAbbreviation": "Wrap with Abbreviation", "command.removeTag": "Remove Tag", "command.updateTag": "Update Tag", @@ -134,7 +134,7 @@ index 1be4d0e1..d60d8357 100644 export const implicitActivationEvent = l10n.t("This activation event cannot be explicitly listed by your extension."); -export const redundantImplicitActivationEvent = l10n.t("This activation event can be removed as VS Code generates these automatically from your package.json contribution declarations."); -+export const redundantImplicitActivationEvent = l10n.t("This activation event can be removed as BradfordCode generates these automatically from your package.json contribution declarations."); ++export const redundantImplicitActivationEvent = l10n.t("This activation event can be removed as !!APP_NAME!! generates these automatically from your package.json contribution declarations."); diff --git a/extensions/extension-editing/src/extensionLinter.ts b/extensions/extension-editing/src/extensionLinter.ts index a6ab287a..dc928226 100644 --- a/extensions/extension-editing/src/extensionLinter.ts @@ -144,7 +144,7 @@ index a6ab287a..dc928226 100644 const relativeUrlRequiresHttpsRepository = l10n.t("Relative image URLs require a repository with HTTPS protocol to be specified in the package.json."); const relativeBadgeUrlRequiresHttpsRepository = l10n.t("Relative badge URLs require a repository with HTTPS protocol to be specified in this package.json."); -const apiProposalNotListed = l10n.t("This proposal cannot be used because for this extension the product defines a fixed set of API proposals. You can test your extension but before publishing you MUST reach out to the VS Code team."); -+const apiProposalNotListed = l10n.t("This proposal cannot be used because for this extension the product defines a fixed set of API proposals. You can test your extension but before publishing you MUST reach out to the BradfordCode team."); ++const apiProposalNotListed = l10n.t("This proposal cannot be used because for this extension the product defines a fixed set of API proposals. You can test your extension but before publishing you MUST reach out to the !!APP_NAME!! team."); const apiProposalVersionNotSupported = l10n.t("API proposal versions are no longer supported. Remove the '@' suffix."); const starActivation = l10n.t("Using '*' activation is usually a bad idea as it impacts performance."); @@ -157,7 +157,7 @@ index 147a75f9..33aeb0d4 100644 "comment": [ "{Locked='](command:git.showOutput'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -167,8 +167,8 @@ index 147a75f9..33aeb0d4 100644 "config.showCommitInput": "Controls whether to show the commit input in the Git source control panel.", - "config.terminalAuthentication": "Controls whether to enable VS Code to be the authentication handler for Git processes spawned in the Integrated Terminal. Note: Terminals need to be restarted to pick up a change in this setting.", - "config.terminalGitEditor": "Controls whether to enable VS Code to be the Git editor for Git processes spawned in the integrated terminal. Note: Terminals need to be restarted to pick up a change in this setting.", -+ "config.terminalAuthentication": "Controls whether to enable BradfordCode to be the authentication handler for Git processes spawned in the Integrated Terminal. Note: Terminals need to be restarted to pick up a change in this setting.", -+ "config.terminalGitEditor": "Controls whether to enable BradfordCode to be the Git editor for Git processes spawned in the integrated terminal. Note: Terminals need to be restarted to pick up a change in this setting.", ++ "config.terminalAuthentication": "Controls whether to enable !!APP_NAME!! to be the authentication handler for Git processes spawned in the Integrated Terminal. Note: Terminals need to be restarted to pick up a change in this setting.", ++ "config.terminalGitEditor": "Controls whether to enable !!APP_NAME!! to be the Git editor for Git processes spawned in the integrated terminal. Note: Terminals need to be restarted to pick up a change in this setting.", "config.timeline.showAuthor": "Controls whether to show the commit author in the Timeline view.", "config.timeline.showUncommitted": "Controls whether to show uncommitted changes in the Timeline view.", "config.timeline.date": "Controls which date to use for items in the Timeline view.", @@ -177,7 +177,7 @@ index 147a75f9..33aeb0d4 100644 "{Locked='](command:git.showOutput'}", "{Locked='](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -186,7 +186,7 @@ index 147a75f9..33aeb0d4 100644 "{Locked='](command:git.showOutput'}", "{Locked='](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -195,7 +195,7 @@ index 147a75f9..33aeb0d4 100644 "{Locked='](command:git.showOutput'}", "{Locked='](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -204,51 +204,51 @@ index 147a75f9..33aeb0d4 100644 }, "view.workbench.scm.disabled": { - "message": "If you would like to use Git features, please enable Git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "If you would like to use Git features, please enable Git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in BradfordCode [read our docs](https://aka.ms/vscode-scm).", ++ "message": "If you would like to use Git features, please enable Git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in !!APP_NAME!! [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:workbench.action.openSettings?%5B%22git.enabled%22%5D'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.empty": { - "message": "In order to use Git features, you can open a folder containing a Git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.cloneRecursive)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "In order to use Git features, you can open a folder containing a Git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.cloneRecursive)\nTo learn more about how to use Git and source control in BradfordCode [read our docs](https://aka.ms/vscode-scm).", ++ "message": "In order to use Git features, you can open a folder containing a Git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.cloneRecursive)\nTo learn more about how to use Git and source control in !!APP_NAME!! [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:vscode.openFolder'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.folder": { - "message": "The folder currently open doesn't have a Git repository. You can initialize a repository which will enable source control features powered by Git.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "The folder currently open doesn't have a Git repository. You can initialize a repository which will enable source control features powered by Git.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use Git and source control in BradfordCode [read our docs](https://aka.ms/vscode-scm).", ++ "message": "The folder currently open doesn't have a Git repository. You can initialize a repository which will enable source control features powered by Git.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use Git and source control in !!APP_NAME!! [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:git.init?%5Btrue%5D'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.workspace": { - "message": "The workspace currently open doesn't have any folders containing Git repositories. You can initialize a repository on a folder which will enable source control features powered by Git.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "The workspace currently open doesn't have any folders containing Git repositories. You can initialize a repository on a folder which will enable source control features powered by Git.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in BradfordCode [read our docs](https://aka.ms/vscode-scm).", ++ "message": "The workspace currently open doesn't have any folders containing Git repositories. You can initialize a repository on a folder which will enable source control features powered by Git.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in !!APP_NAME!! [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:git.init'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.emptyWorkspace": { - "message": "The workspace currently open doesn't have any folders containing Git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "The workspace currently open doesn't have any folders containing Git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use Git and source control in BradfordCode [read our docs](https://aka.ms/vscode-scm).", ++ "message": "The workspace currently open doesn't have any folders containing Git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use Git and source control in !!APP_NAME!! [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:workbench.action.addRootFolder'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -257,7 +257,7 @@ index 147a75f9..33aeb0d4 100644 "{Locked='](command:git.openRepositoriesInParentFolders'}", "{Locked='](command:workbench.action.openSettings?%5B%22git.openRepositoryInParentFolders%22%5D'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -266,7 +266,7 @@ index 147a75f9..33aeb0d4 100644 "{Locked='](command:git.openRepositoriesInParentFolders'}", "{Locked='](command:workbench.action.openSettings?%5B%22git.openRepositoryInParentFolders%22%5D'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -275,7 +275,7 @@ index 147a75f9..33aeb0d4 100644 "comment": [ "{Locked='](command:git.manageUnsafeRepositories'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -284,27 +284,27 @@ index 147a75f9..33aeb0d4 100644 "comment": [ "{Locked='](command:git.manageUnsafeRepositories'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.closedRepository": { - "message": "A Git repository was found that was previously closed.\n[Reopen Closed Repository](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "A Git repository was found that was previously closed.\n[Reopen Closed Repository](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in BradfordCode [read our docs](https://aka.ms/vscode-scm).", ++ "message": "A Git repository was found that was previously closed.\n[Reopen Closed Repository](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in !!APP_NAME!! [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:git.reopenClosedRepositories'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, "view.workbench.scm.closedRepositories": { - "message": "Git repositories were found that were previously closed.\n[Reopen Closed Repositories](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", -+ "message": "Git repositories were found that were previously closed.\n[Reopen Closed Repositories](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in BradfordCode [read our docs](https://aka.ms/vscode-scm).", ++ "message": "Git repositories were found that were previously closed.\n[Reopen Closed Repositories](command:git.reopenClosedRepositories)\nTo learn more about how to use Git and source control in !!APP_NAME!! [read our docs](https://aka.ms/vscode-scm).", "comment": [ "{Locked='](command:git.reopenClosedRepositories'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -313,12 +313,12 @@ index 147a75f9..33aeb0d4 100644 "comment": [ "{Locked='](command:git.clone'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, - "view.workbench.learnMore": "To learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm)." -+ "view.workbench.learnMore": "To learn more about how to use Git and source control in BradfordCode [read our docs](https://aka.ms/vscode-scm)." ++ "view.workbench.learnMore": "To learn more about how to use Git and source control in !!APP_NAME!! [read our docs](https://aka.ms/vscode-scm)." } diff --git a/extensions/github/package.nls.json b/extensions/github/package.nls.json index 04497595..aa29d00c 100644 @@ -328,7 +328,7 @@ index 04497595..aa29d00c 100644 { "displayName": "GitHub", - "description": "GitHub features for VS Code", -+ "description": "GitHub features for BradfordCode", ++ "description": "GitHub features for !!APP_NAME!!", "command.copyVscodeDevLink": "Copy vscode.dev Link", "command.publish": "Publish to GitHub", "command.openOnGitHub": "Open on GitHub", @@ -337,7 +337,7 @@ index 04497595..aa29d00c 100644 "command.openPullRequest": "Open PR", "config.branchProtection": "Controls whether to query repository rules for GitHub repositories", - "config.gitAuthentication": "Controls whether to enable automatic GitHub authentication for git commands within VS Code.", -+ "config.gitAuthentication": "Controls whether to enable automatic GitHub authentication for git commands within BradfordCode.", ++ "config.gitAuthentication": "Controls whether to enable automatic GitHub authentication for git commands within !!APP_NAME!!.", "config.gitProtocol": "Controls which protocol is used to clone a GitHub repository", "config.showAvatar": "Controls whether to show the GitHub avatar of the commit author in various hovers (ex: Git blame, Timeline, Source Control Graph, etc.)", "welcome.publishFolder": { @@ -346,7 +346,7 @@ index 04497595..aa29d00c 100644 "Do not translate '$(github)'. It will be rendered as an icon", "{Locked='](command:github.publish'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, @@ -355,7 +355,7 @@ index 04497595..aa29d00c 100644 "Do not translate '$(github)'. It will be rendered as an icon", "{Locked='](command:github.publish'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", -+ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for BradfordCode", ++ "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for !!APP_NAME!!", "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] } @@ -367,8 +367,8 @@ index 789a579c..7dae48d2 100644 { - "description": "Extension to add Grunt capabilities to VS Code.", - "displayName": "Grunt support for VS Code", -+ "description": "Extension to add Grunt capabilities to BradfordCode.", -+ "displayName": "Grunt support for BradfordCode", ++ "description": "Extension to add Grunt capabilities to !!APP_NAME!!.", ++ "displayName": "Grunt support for !!APP_NAME!!", "config.grunt.autoDetect": "Controls enablement of Grunt task detection. Grunt task detection can cause files in any open workspace to be executed.", "grunt.taskDefinition.type.description": "The Grunt task to customize.", "grunt.taskDefinition.args.description": "Command line arguments to pass to the grunt task", @@ -381,7 +381,7 @@ index cfb91c1f..ba6e0733 100644 activeEditorListener.dispose(); const configure = l10n.t('Configure'); - const res = await window.showInformationMessage(l10n.t('VS Code now has built-in support for auto-renaming tags. Do you want to enable it?'), configure); -+ const res = await window.showInformationMessage(l10n.t('BradfordCode now has built-in support for auto-renaming tags. Do you want to enable it?'), configure); ++ const res = await window.showInformationMessage(l10n.t('!!APP_NAME!! now has built-in support for auto-renaming tags. Do you want to enable it?'), configure); if (res === configure) { commands.executeCommand('workbench.action.openSettings', SettingIds.linkedEditing); } @@ -394,7 +394,7 @@ index d8390703..b4185c22 100644 "displayName": "HTML Language Features", "description": "Provides rich language support for HTML and Handlebar files", - "html.customData.desc": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-html-languageservice/blob/master/docs/customData.md).\n\nVS Code loads custom data on startup to enhance its HTML support for the custom HTML tags, attributes and attribute values you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", -+ "html.customData.desc": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-html-languageservice/blob/master/docs/customData.md).\n\nBradfordCode loads custom data on startup to enhance its HTML support for the custom HTML tags, attributes and attribute values you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", ++ "html.customData.desc": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-html-languageservice/blob/master/docs/customData.md).\n\n!!APP_NAME!! loads custom data on startup to enhance its HTML support for the custom HTML tags, attributes and attribute values you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", "html.format.enable.desc": "Enable/disable default HTML formatter.", "html.format.wrapLineLength.desc": "Maximum amount of characters per line (0 = disable).", "html.format.unformatted.desc": "List of tags, comma separated, that shouldn't be reformatted. `null` defaults to all tags listed at https://www.w3.org/TR/html5/dom.html#phrasing-content.", @@ -403,7 +403,7 @@ index d8390703..b4185c22 100644 "html.suggest.html5.desc": "Controls whether the built-in HTML language support suggests HTML5 tags, properties and values.", "html.suggest.hideEndTagSuggestions.desc": "Controls whether the built-in HTML language support suggests closing tags. When disabled, end tag completions like `` will not be shown.", - "html.trace.server.desc": "Traces the communication between VS Code and the HTML language server.", -+ "html.trace.server.desc": "Traces the communication between BradfordCode and the HTML language server.", ++ "html.trace.server.desc": "Traces the communication between !!APP_NAME!! and the HTML language server.", "html.validate.scripts": "Controls whether the built-in HTML language support validates embedded scripts.", "html.validate.styles": "Controls whether the built-in HTML language support validates embedded styles.", "html.autoCreateQuotes": "Enable/disable auto creation of quotes for HTML attribute assignment. The type of quotes can be configured by `#html.completion.attributeDefaultValue#`.", @@ -416,7 +416,7 @@ index 205143c3..cd246290 100644 "html.customData": { "type": "array", - "markdownDescription": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-html-languageservice/blob/master/docs/customData.md).\n\nVS Code loads custom data on startup to enhance its HTML support for the custom HTML tags, attributes and attribute values you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", -+ "markdownDescription": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-html-languageservice/blob/master/docs/customData.md).\n\nBradfordCode loads custom data on startup to enhance its HTML support for the custom HTML tags, attributes and attribute values you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", ++ "markdownDescription": "A list of relative file paths pointing to JSON files following the [custom data format](https://github.com/microsoft/vscode-html-languageservice/blob/master/docs/customData.md).\n\n!!APP_NAME!! loads custom data on startup to enhance its HTML support for the custom HTML tags, attributes and attribute values you specify in the JSON files.\n\nThe file paths are relative to workspace and only workspace folder settings are considered.", "items": { "type": "string", "description": "Relative path to a HTML custom data file" @@ -428,8 +428,8 @@ index e82030ef..d9706343 100644 { - "description": "Extension to add Jake capabilities to VS Code.", - "displayName": "Jake support for VS Code", -+ "description": "Extension to add Jake capabilities to BradfordCode.", -+ "displayName": "Jake support for BradfordCode", ++ "description": "Extension to add Jake capabilities to !!APP_NAME!!.", ++ "displayName": "Jake support for !!APP_NAME!!", "jake.taskDefinition.type.description": "The Jake task to customize.", "jake.taskDefinition.file.description": "The Jake file that provides the task. Can be omitted.", "config.jake.autoDetect": "Controls enablement of Jake task detection. Jake task detection can cause files in any open workspace to be executed." @@ -442,7 +442,7 @@ index 30199b2b..6e3820d5 100644 "json.format.keepLines.desc": "Keep all existing new lines when formatting.", "json.validate.enable.desc": "Enable/disable JSON validation.", - "json.tracing.desc": "Traces the communication between VS Code and the JSON language server.", -+ "json.tracing.desc": "Traces the communication between BradfordCode and the JSON language server.", ++ "json.tracing.desc": "Traces the communication between !!APP_NAME!! and the JSON language server.", "json.colorDecorators.enable.desc": "Enables or disables color decorators", "json.colorDecorators.enable.deprecationMessage": "The setting `json.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`.", "json.schemaResolutionErrorMessage": "Unable to resolve schema.", @@ -455,7 +455,7 @@ index cb292ba5..0855e6a0 100644 "markdown.showPreviewSecuritySelector.title": "Change Preview Security Settings", "markdown.trace.extension.desc": "Enable debug logging for the Markdown extension.", - "markdown.trace.server.desc": "Traces the communication between VS Code and the Markdown language server.", -+ "markdown.trace.server.desc": "Traces the communication between BradfordCode and the Markdown language server.", ++ "markdown.trace.server.desc": "Traces the communication between !!APP_NAME!! and the Markdown language server.", "markdown.server.log.desc": "Controls the logging level of the Markdown language server.", "markdown.preview.refresh.title": "Refresh Preview", "markdown.preview.toggleLock.title": "Toggle Preview Locking", @@ -464,7 +464,7 @@ index cb292ba5..0855e6a0 100644 "message": "Configures the path and file name of files created by copy/paste or drag and drop. This is a map of globs that match against a Markdown document path to the destination path where the new file should be created.\n\nThe destination path may use the following variables:\n\n- `${documentDirName}` — Absolute parent directory path of the Markdown document, e.g. `/Users/me/myProject/docs`.\n- `${documentRelativeDirName}` — Relative parent directory path of the Markdown document, e.g. `docs`. This is the same as `${documentDirName}` if the file is not part of a workspace.\n- `${documentFileName}` — The full filename of the Markdown document, e.g. `README.md`.\n- `${documentBaseName}` — The basename of the Markdown document, e.g. `README`.\n- `${documentExtName}` — The extension of the Markdown document, e.g. `md`.\n- `${documentFilePath}` — Absolute path of the Markdown document, e.g. `/Users/me/myProject/docs/README.md`.\n- `${documentRelativeFilePath}` — Relative path of the Markdown document, e.g. `docs/README.md`. This is the same as `${documentFilePath}` if the file is not part of a workspace.\n- `${documentWorkspaceFolder}` — The workspace folder for the Markdown document, e.g. `/Users/me/myProject`. This is the same as `${documentDirName}` if the file is not part of a workspace.\n- `${fileName}` — The file name of the dropped file, e.g. `image.png`.\n- `${fileExtName}` — The extension of the dropped file, e.g. `png`.\n- `${unixTime}` — The current Unix timestamp in milliseconds.\n- `${isoTime}` — The current time in ISO 8601 format, e.g. '2025-06-06T08:40:32.123Z'.", "comment": [ - "This setting is use the user drops or pastes image data into the editor. In this case, VS Code automatically creates a new image file in the workspace containing the dropped/pasted image.", -+ "This setting is use the user drops or pastes image data into the editor. In this case, BradfordCode automatically creates a new image file in the workspace containing the dropped/pasted image.", ++ "This setting is use the user drops or pastes image data into the editor. In this case, !!APP_NAME!! automatically creates a new image file in the workspace containing the dropped/pasted image.", "It's easier to explain this setting with an example. For example, let's say the setting value was:", "", "{ 'docs/*.md': '${documentDirName}/images/${fileName}' }", @@ -476,7 +476,7 @@ index 920ced76..f7ca6a48 100644 { "displayName": "Media Preview", - "description": "Provides VS Code's built-in previews for images, audio, and video", -+ "description": "Provides BradfordCode's built-in previews for images, audio, and video", ++ "description": "Provides !!APP_NAME!!'s built-in previews for images, audio, and video", "customEditor.audioPreview.displayName": "Audio Preview", "customEditor.imagePreview.displayName": "Image Preview", "customEditor.videoPreview.displayName": "Video Preview", @@ -489,12 +489,12 @@ index 01aaf33a..639d9ba9 100644 @@ -507,12 +507,12 @@ index 79cb70c9..cac55284 100644 @@ -525,12 +525,12 @@ index 2ee1169f..79c54036 100644 @@ -543,7 +543,7 @@ index 032dd35d..0b893063 100644 "id": "vscode.builtin-renderer", "entrypoint": "./renderer-out/index.js", - "displayName": "VS Code Builtin Notebook Output Renderer", -+ "displayName": "BradfordCode Builtin Notebook Output Renderer", ++ "displayName": "!!APP_NAME!! Builtin Notebook Output Renderer", "requiresMessaging": "never", "mimeTypes": [ "image/gif", @@ -555,7 +555,7 @@ index 5c77708d..8a988891 100644 { "description": "Extension to add task support for npm scripts.", - "displayName": "NPM support for VS Code", -+ "displayName": "NPM support for BradfordCode", ++ "displayName": "NPM support for !!APP_NAME!!", "workspaceTrust": "This extension executes tasks, which require trust to run.", "virtualWorkspaces": "Functionality that requires running the 'npm' command is not available in virtual workspaces.", "config.npm.autoDetect": "Controls whether npm scripts should be automatically detected.", @@ -568,7 +568,7 @@ index d52cabb8..f4f26d79 100644 "patterns": [ { - "comment": "The simpler (?<=\\bProcess\\.|\\bCommandLine\\.) breaks VS Code / Atom, see https://github.com/textmate/swift.tmbundle/issues/29", -+ "comment": "The simpler (?<=\\bProcess\\.|\\bCommandLine\\.) breaks BradfordCode / Atom, see https://github.com/textmate/swift.tmbundle/issues/29", ++ "comment": "The simpler (?<=\\bProcess\\.|\\bCommandLine\\.) breaks !!APP_NAME!! / Atom, see https://github.com/textmate/swift.tmbundle/issues/29", "name": "support.variable.swift", "match": "(?<=^Process\\.|\\WProcess\\.|^CommandLine\\.|\\WCommandLine\\.)(arguments|argc|unsafeArgv)" }, @@ -581,10 +581,10 @@ index a697f8c0..f55ecc02 100644 "configuration.tsserver.experimental.enableProjectDiagnostics": "Enables project wide error reporting.", "configuration.tsserver.experimental.enableProjectDiagnostics.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.tsserver.experimental.enableProjectDiagnostics#` instead.", - "typescript.locale": "Sets the locale used to report JavaScript and TypeScript errors. Defaults to use VS Code's locale.", -+ "typescript.locale": "Sets the locale used to report JavaScript and TypeScript errors. Defaults to use BradfordCode's locale.", ++ "typescript.locale": "Sets the locale used to report JavaScript and TypeScript errors. Defaults to use !!APP_NAME!!'s locale.", "typescript.locale.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.locale#` instead.", - "typescript.locale.auto": "Use VS Code's configured display language.", -+ "typescript.locale.auto": "Use BradfordCode's configured display language.", ++ "typescript.locale.auto": "Use !!APP_NAME!!'s configured display language.", "configuration.implicitProjectConfig.module": "Sets the module system for the program. See more: https://www.typescriptlang.org/tsconfig#module.", "configuration.implicitProjectConfig.target": "Set target JavaScript language version for emitted JavaScript and include library declarations. See more: https://www.typescriptlang.org/tsconfig#target.", "configuration.implicitProjectConfig.checkJs": "Enable/disable semantic checking of JavaScript files. Existing `jsconfig.json` or `tsconfig.json` files override this setting.", @@ -593,7 +593,7 @@ index a697f8c0..f55ecc02 100644 "typescript.workspaceSymbols.excludeLibrarySymbols": "Exclude symbols that come from library files in `Go to Symbol in Workspace` results. Requires using TypeScript 5.3+ in the workspace.", "configuration.workspaceSymbols.excludeLibrarySymbols.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.workspaceSymbols.excludeLibrarySymbols#` instead.", - "typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in VS Code.", -+ "typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in BradfordCode.", ++ "typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in !!APP_NAME!!.", "typescript.updateImportsOnFileMove.enabled.prompt": "Prompt on each rename.", "typescript.updateImportsOnFileMove.enabled.always": "Always update paths automatically.", "typescript.updateImportsOnFileMove.enabled.never": "Never rename paths and don't prompt.", @@ -602,10 +602,10 @@ index a697f8c0..f55ecc02 100644 "configuration.suggest.jsdoc.enabled": "Enable/disable suggestion to complete JSDoc comments.", "configuration.suggest.completeJSDocs.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.suggest.jsdoc.enabled#` instead.", - "configuration.tsserver.useVsCodeWatcher": "Use VS Code's file watchers instead of TypeScript's. Requires using TypeScript 5.4+ in the workspace.", -+ "configuration.tsserver.useVsCodeWatcher": "Use BradfordCode's file watchers instead of TypeScript's. Requires using TypeScript 5.4+ in the workspace.", ++ "configuration.tsserver.useVsCodeWatcher": "Use !!APP_NAME!!'s file watchers instead of TypeScript's. Requires using TypeScript 5.4+ in the workspace.", "configuration.tsserver.watchOptions": "Configure which watching strategies should be used to keep track of files and directories.", - "configuration.tsserver.watchOptions.vscode": "Use VS Code's file watchers instead of TypeScript's. Requires using TypeScript 5.4+ in the workspace.", -+ "configuration.tsserver.watchOptions.vscode": "Use BradfordCode's file watchers instead of TypeScript's. Requires using TypeScript 5.4+ in the workspace.", ++ "configuration.tsserver.watchOptions.vscode": "Use !!APP_NAME!!'s file watchers instead of TypeScript's. Requires using TypeScript 5.4+ in the workspace.", "configuration.tsserver.watchOptions.watchFile": "Strategy for how individual files are watched.", "configuration.tsserver.watchOptions.watchFile.fixedChunkSizePolling": "Polls files in chunks at regular interval.", "configuration.tsserver.watchOptions.watchFile.fixedPollingInterval": "Check every file for changes several times a second at a fixed interval.", @@ -614,14 +614,14 @@ index a697f8c0..f55ecc02 100644 "configuration.suggest.objectLiteralMethodSnippets.enabled": "Enable/disable snippet completions for methods in object literals.", "configuration.suggest.objectLiteralMethodSnippets.enabled.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.suggest.objectLiteralMethodSnippets.enabled#` instead.", - "configuration.tsserver.web.projectWideIntellisense.enabled": "Enable/disable project-wide IntelliSense on web. Requires that VS Code is running in a trusted context.", -+ "configuration.tsserver.web.projectWideIntellisense.enabled": "Enable/disable project-wide IntelliSense on web. Requires that BradfordCode is running in a trusted context.", ++ "configuration.tsserver.web.projectWideIntellisense.enabled": "Enable/disable project-wide IntelliSense on web. Requires that !!APP_NAME!! is running in a trusted context.", "configuration.tsserver.web.projectWideIntellisense.enabled.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.tsserver.web.projectWideIntellisense.enabled#` instead.", "configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors": "Suppresses semantic errors on web even when project wide IntelliSense is enabled. This is always on when project wide IntelliSense is not enabled or available. See `#js/ts.tsserver.web.projectWideIntellisense.enabled#`", "configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.tsserver.web.projectWideIntellisense.suppressSemanticErrors#` instead.", "configuration.tsserver.web.typeAcquisition.enabled": "Enable/disable package acquisition on the web. This enables IntelliSense for imported packages. Requires `#js/ts.tsserver.web.projectWideIntellisense.enabled#`. Currently not supported for Safari.", "configuration.tsserver.web.typeAcquisition.enabled.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.tsserver.web.typeAcquisition.enabled#` instead.", - "configuration.tsserver.nodePath": "Run TS Server on a custom Node installation. This can be a path to a Node executable, or `node` if you want VS Code to detect a Node installation.", -+ "configuration.tsserver.nodePath": "Run TS Server on a custom Node installation. This can be a path to a Node executable, or `node` if you want BradfordCode to detect a Node installation.", ++ "configuration.tsserver.nodePath": "Run TS Server on a custom Node installation. This can be a path to a Node executable, or `node` if you want !!APP_NAME!! to detect a Node installation.", "configuration.tsserver.nodePath.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.tsserver.node.path#` instead.", "configuration.tsserver.watchOptions.unifiedDeprecationMessage": "This setting is deprecated. Use `#js/ts.tsserver.watchOptions#` instead.", "configuration.updateImportsOnPaste": "Automatically update imports when pasting code. Requires TypeScript 5.6+.", @@ -631,13 +631,13 @@ index a697f8c0..f55ecc02 100644 "walkthroughs.nodejsWelcome.debugJsFile.title": "Run and Debug your JavaScript", - "walkthroughs.nodejsWelcome.debugJsFile.description": "Once you've installed Node.js, you can run JavaScript programs at a terminal by entering ``node your-file-name.js``\nAnother easy way to run Node.js programs is by using VS Code's debugger which lets you run your code, pause at different points, and help you understand what's going on step-by-step.\n[Start Debugging](command:javascript-walkthrough.commands.debugJsFile)", - "walkthroughs.nodejsWelcome.debugJsFile.altText": "Debug and run your JavaScript code in Node.js with Visual Studio Code.", -+ "walkthroughs.nodejsWelcome.debugJsFile.description": "Once you've installed Node.js, you can run JavaScript programs at a terminal by entering ``node your-file-name.js``\nAnother easy way to run Node.js programs is by using BradfordCode's debugger which lets you run your code, pause at different points, and help you understand what's going on step-by-step.\n[Start Debugging](command:javascript-walkthrough.commands.debugJsFile)", -+ "walkthroughs.nodejsWelcome.debugJsFile.altText": "Debug and run your JavaScript code in Node.js with BradfordCode.", ++ "walkthroughs.nodejsWelcome.debugJsFile.description": "Once you've installed Node.js, you can run JavaScript programs at a terminal by entering ``node your-file-name.js``\nAnother easy way to run Node.js programs is by using !!APP_NAME!!'s debugger which lets you run your code, pause at different points, and help you understand what's going on step-by-step.\n[Start Debugging](command:javascript-walkthrough.commands.debugJsFile)", ++ "walkthroughs.nodejsWelcome.debugJsFile.altText": "Debug and run your JavaScript code in Node.js with !!APP_NAME!!.", "walkthroughs.nodejsWelcome.learnMoreAboutJs.title": "Explore More", - "walkthroughs.nodejsWelcome.learnMoreAboutJs.description": "Want to get more comfortable with JavaScript, Node.js, and VS Code? Be sure to check out our docs!\nWe've got lots of resources for learning [JavaScript](https://code.visualstudio.com/docs/nodejs/working-with-javascript) and [Node.js](https://code.visualstudio.com/docs/nodejs/nodejs-tutorial).\n\n[Learn More](https://code.visualstudio.com/docs/nodejs/nodejs-tutorial)", - "walkthroughs.nodejsWelcome.learnMoreAboutJs.altText": "Learn more about JavaScript and Node.js in Visual Studio Code." -+ "walkthroughs.nodejsWelcome.learnMoreAboutJs.description": "Want to get more comfortable with JavaScript, Node.js, and BradfordCode? Be sure to check out our docs!\nWe've got lots of resources for learning [JavaScript](https://code.visualstudio.com/docs/nodejs/working-with-javascript) and [Node.js](https://code.visualstudio.com/docs/nodejs/nodejs-tutorial).\n\n[Learn More](https://code.visualstudio.com/docs/nodejs/nodejs-tutorial)", -+ "walkthroughs.nodejsWelcome.learnMoreAboutJs.altText": "Learn more about JavaScript and Node.js in BradfordCode." ++ "walkthroughs.nodejsWelcome.learnMoreAboutJs.description": "Want to get more comfortable with JavaScript, Node.js, and !!APP_NAME!!? Be sure to check out our docs!\nWe've got lots of resources for learning [JavaScript](https://code.visualstudio.com/docs/nodejs/working-with-javascript) and [Node.js](https://code.visualstudio.com/docs/nodejs/nodejs-tutorial).\n\n[Learn More](https://code.visualstudio.com/docs/nodejs/nodejs-tutorial)", ++ "walkthroughs.nodejsWelcome.learnMoreAboutJs.altText": "Learn more about JavaScript and Node.js in !!APP_NAME!!." } diff --git a/extensions/typescript-language-features/src/tsServer/versionManager.ts b/extensions/typescript-language-features/src/tsServer/versionManager.ts index 1154a29c..a42a1d25 100644 @@ -648,7 +648,7 @@ index 1154a29c..a42a1d25 100644 label: (!this.useWorkspaceTsdkSetting || !vscode.workspace.isTrusted ? '• ' - : '') + vscode.l10n.t("Use VS Code's Version"), -+ : '') + vscode.l10n.t("Use BradfordCode's Version"), ++ : '') + vscode.l10n.t("Use !!APP_NAME!!'s Version"), description: bundledVersion.displayName, detail: bundledVersion.pathLabel, run: async () => { @@ -661,7 +661,7 @@ index 12cb1cca..6b681ae1 100644 } - vscode.window.showErrorMessage(vscode.l10n.t("VS Code\'s tsserver was deleted by another application such as a misbehaving virus detection tool. Please reinstall VS Code.")); -+ vscode.window.showErrorMessage(vscode.l10n.t("BradfordCode\'s tsserver was deleted by another application such as a misbehaving virus detection tool. Please reinstall BradfordCode.")); ++ vscode.window.showErrorMessage(vscode.l10n.t("!!APP_NAME!!\'s tsserver was deleted by another application such as a misbehaving virus detection tool. Please reinstall !!APP_NAME!!.")); throw new Error('Could not find bundled tsserver.js'); } @@ -674,7 +674,7 @@ index 823985e9..a4a18aa8 100644 if (!rootPath) { vscode.window.showInformationMessage( - vscode.l10n.t("Please open a folder in VS Code to use a TypeScript or JavaScript project")); -+ vscode.l10n.t("Please open a folder in BradfordCode to use a TypeScript or JavaScript project")); ++ vscode.l10n.t("Please open a folder in !!APP_NAME!! to use a TypeScript or JavaScript project")); return; } @@ -687,7 +687,7 @@ index ae18dff7..fb9a8791 100644 if (this.pluginManager.plugins.length) { prompt = vscode.window.showErrorMessage( - vscode.l10n.t("The JS/TS language service immediately crashed 5 times. The service will not be restarted.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against VS Code.", pluginExtensionList)); -+ vscode.l10n.t("The JS/TS language service immediately crashed 5 times. The service will not be restarted.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against BradfordCode.", pluginExtensionList)); ++ vscode.l10n.t("The JS/TS language service immediately crashed 5 times. The service will not be restarted.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against !!APP_NAME!!.", pluginExtensionList)); } else { prompt = vscode.window.showErrorMessage( vscode.l10n.t("The JS/TS language service immediately crashed 5 times. The service will not be restarted."), @@ -696,7 +696,7 @@ index ae18dff7..fb9a8791 100644 if (this.pluginManager.plugins.length) { prompt = vscode.window.showWarningMessage( - vscode.l10n.t("The JS/TS language service crashed 5 times in the last 5 Minutes.\nThis may be caused by a plugin contributed by one of these extensions: {0}\nPlease try disabling these extensions before filing an issue against VS Code.", pluginExtensionList)); -+ vscode.l10n.t("The JS/TS language service crashed 5 times in the last 5 Minutes.\nThis may be caused by a plugin contributed by one of these extensions: {0}\nPlease try disabling these extensions before filing an issue against BradfordCode.", pluginExtensionList)); ++ vscode.l10n.t("The JS/TS language service crashed 5 times in the last 5 Minutes.\nThis may be caused by a plugin contributed by one of these extensions: {0}\nPlease try disabling these extensions before filing an issue against !!APP_NAME!!.", pluginExtensionList)); } else { prompt = vscode.window.showWarningMessage( vscode.l10n.t("The JS/TS language service crashed 5 times in the last 5 Minutes."), @@ -705,7 +705,7 @@ index ae18dff7..fb9a8791 100644 if (this.pluginManager.plugins.length) { prompt = vscode.window.showWarningMessage( - vscode.l10n.t("The JS/TS language service crashed.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against VS Code.", pluginExtensionList)); -+ vscode.l10n.t("The JS/TS language service crashed.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against BradfordCode.", pluginExtensionList)); ++ vscode.l10n.t("The JS/TS language service crashed.\nThis may be caused by a plugin contributed by one of these extensions: {0}.\nPlease try disabling these extensions before filing an issue against !!APP_NAME!!.", pluginExtensionList)); } else { prompt = vscode.window.showWarningMessage( vscode.l10n.t("The JS/TS language service crashed."), @@ -717,7 +717,7 @@ index d56cbf59..705fca8b 100644 { "name": "vscode-api-tests", - "description": "API tests for VS Code", -+ "description": "API tests for BradfordCode", ++ "description": "API tests for !!APP_NAME!!", "version": "0.0.1", "publisher": "vscode", "license": "MIT", @@ -729,7 +729,7 @@ index f3253b70..af413e79 100644 { "name": "vscode-colorize-tests", - "description": "Colorize tests for VS Code", -+ "description": "Colorize tests for BradfordCode", ++ "description": "Colorize tests for !!APP_NAME!!", "version": "0.0.1", "publisher": "vscode", "license": "MIT", @@ -739,7 +739,7 @@ index a0006d85..3947d588 100644 +++ b/extensions/vscode-colorize-tests/test/colorize-fixtures/14119.less @@ -1,3 +1,3 @@ -#f(@hm: "broken highlighting in VS Code") { -+#f(@hm: "broken highlighting in BradfordCode") { ++#f(@hm: "broken highlighting in !!APP_NAME!!") { content: ""; } \ No newline at end of file @@ -752,7 +752,7 @@ index 313fe87c..a9608805 100644 }, { - "c": "broken highlighting in VS Code", -+ "c": "broken highlighting in BradfordCode", ++ "c": "broken highlighting in !!APP_NAME!!", "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less string.quoted.double.less", "r": { "dark_plus": "string: #CE9178", @@ -764,7 +764,7 @@ index 9b0de50d..27b109e8 100644 { "name": "vscode-test-resolver", - "description": "Test resolver for VS Code", -+ "description": "Test resolver for BradfordCode", ++ "description": "Test resolver for !!APP_NAME!!", "version": "0.0.1", "publisher": "vscode", "license": "MIT", @@ -777,20 +777,20 @@ index a351aa77..8d4e933d 100644 return new Promise((resolve, reject) => { - log(`Downloading VS Code Server from: ${downloadUrl}`); -+ log(`Downloading BradfordCode Server from: ${downloadUrl}`); ++ log(`Downloading !!APP_NAME!! Server from: ${downloadUrl}`); const requestOptions: https.RequestOptions = parseUrl(downloadUrl); https.get(requestOptions, res => { if (res.statusCode !== 302) { - reject('Failed to get VS Code server archive location'); -+ reject('Failed to get BradfordCode server archive location'); ++ reject('Failed to get !!APP_NAME!! server archive location'); res.resume(); // read the rest of the response data and discard it return; } const archiveUrl = res.headers.location; if (!archiveUrl) { - reject('Failed to get VS Code server archive location'); -+ reject('Failed to get BradfordCode server archive location'); ++ reject('Failed to get !!APP_NAME!! server archive location'); res.resume(); // read the rest of the response data and discard it return; } @@ -799,7 +799,7 @@ index a351aa77..8d4e933d 100644 /** - * Unzip a .zip or .tar.gz VS Code archive -+ * Unzip a .zip or .tar.gz BradfordCode archive ++ * Unzip a .zip or .tar.gz !!APP_NAME!! archive */ function unzipVSCodeServer(vscodeArchivePath: string, extractDir: string, destDir: string, log: (messsage: string) => void) { log(`Extracting ${vscodeArchivePath}`); @@ -808,7 +808,7 @@ index a351aa77..8d4e933d 100644 log(`Found ${extractDir}. Skipping download.`); } else { - log(`Downloading VS Code Server ${quality} - ${commit} into ${extractDir}.`); -+ log(`Downloading BradfordCode Server ${quality} - ${commit} into ${extractDir}.`); ++ log(`Downloading !!APP_NAME!! Server ${quality} - ${commit} into ${extractDir}.`); try { const vscodeArchivePath = await downloadVSCodeServerArchive(updateUrl, commit, quality, destDir, log); if (fs.existsSync(vscodeArchivePath)) { @@ -817,7 +817,7 @@ index a351aa77..8d4e933d 100644 } } catch (err) { - throw Error(`Failed to download and unzip VS Code ${quality} - ${commit}`); -+ throw Error(`Failed to download and unzip BradfordCode ${quality} - ${commit}`); ++ throw Error(`Failed to download and unzip !!APP_NAME!! ${quality} - ${commit}`); } } return Promise.resolve(extractDir); @@ -830,7 +830,7 @@ index 5c9b4785..dfe21380 100644 if (!serverLocation) { const serverBin = path.join(remoteDataDir, 'bin'); - progress.report({ message: 'Installing VSCode Server' }); -+ progress.report({ message: 'Installing BradfordCode Server' }); ++ progress.report({ message: 'Installing !!APP_NAME!! Server' }); serverLocation = await downloadAndUnzipVSCodeServer(updateUrl, commit, quality, serverBin, m => outputChannel.appendLine(m)); } @@ -843,18 +843,18 @@ index f70cf87e..a3aa84bf 100644 // Default argv content const defaultArgvConfigContent = [ - '// This configuration file allows you to pass permanent command line arguments to VS Code.', -+ '// This configuration file allows you to pass permanent command line arguments to BradfordCode.', ++ '// This configuration file allows you to pass permanent command line arguments to !!APP_NAME!!.', '// Only a subset of arguments is currently supported to reduce the likelihood of breaking', '// the installation.', '//', '// PLEASE DO NOT CHANGE WITHOUT UNDERSTANDING THE IMPACT', '//', - '// NOTE: Changing this file requires a restart of VS Code.', -+ '// NOTE: Changing this file requires a restart of BradfordCode.', ++ '// NOTE: Changing this file requires a restart of !!APP_NAME!!.', '{', ' // Use software rendering instead of hardware accelerated rendering.', - ' // This can help in cases where you see rendering issues in VS Code.', -+ ' // This can help in cases where you see rendering issues in BradfordCode.', ++ ' // This can help in cases where you see rendering issues in !!APP_NAME!!.', ' // "disable-hardware-acceleration": true', '}' ]; @@ -867,7 +867,7 @@ index 811673d0..6860fa57 100644 async startup(): Promise { - this.logService.debug('Starting VS Code'); -+ this.logService.debug('Starting BradfordCode'); ++ this.logService.debug('Starting !!APP_NAME!!'); this.logService.debug(`from: ${this.environmentMainService.appRoot}`); this.logService.debug('args:', this.environmentMainService.args); @@ -880,7 +880,7 @@ index a200bf81..7572f34b 100644 super({ id: ToggleTabFocusModeAction.ID, - title: nls.localize2({ key: 'toggle.tabMovesFocus', comment: ['Turn on/off use of tab key for moving focus around VS Code'] }, 'Toggle Tab Key Moves Focus'), -+ title: nls.localize2({ key: 'toggle.tabMovesFocus', comment: ['Turn on/off use of tab key for moving focus around BradfordCode'] }, 'Toggle Tab Key Moves Focus'), ++ title: nls.localize2({ key: 'toggle.tabMovesFocus', comment: ['Turn on/off use of tab key for moving focus around !!APP_NAME!!'] }, 'Toggle Tab Key Moves Focus'), precondition: undefined, keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyM, @@ -893,7 +893,7 @@ index c256dba0..9ca1305c 100644 export const IsDevelopmentContext = new RawContextKey('isDevelopment', false, true); -export const ProductQualityContext = new RawContextKey('productQualityType', '', localize('productQualityType', "Quality type of VS Code")); -+export const ProductQualityContext = new RawContextKey('productQualityType', '', localize('productQualityType', "Quality type of BradfordCode")); ++export const ProductQualityContext = new RawContextKey('productQualityType', '', localize('productQualityType', "Quality type of !!APP_NAME!!")); export const InputFocusedContextKey = 'inputFocus'; export const InputFocusedContext = new RawContextKey(InputFocusedContextKey, false, localize('inputFocus', "Whether keyboard focus is inside an input box")); @@ -906,7 +906,7 @@ index 6eefd716..4e8cb6af 100644 const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, this.productService.version, this.productService.date)) { - throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extensionId, this.productService.version)); -+ throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with BradfordCode '{1}'.", extensionId, this.productService.version)); ++ throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with !!APP_NAME!! '{1}'.", extensionId, this.productService.version)); } const allowedToInstall = this.allowedExtensionsService.isAllowed({ id: extensionId, version: manifest.version, publisherDisplayName: undefined }); @@ -915,7 +915,7 @@ index 6eefd716..4e8cb6af 100644 await this.extensionsScanner.deleteExtension(existingExtension, 'existing'); } catch (e) { - throw new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", this.manifest.displayName || this.manifest.name)); -+ throw new Error(nls.localize('restartCode', "Please restart BradfordCode before reinstalling {0}.", this.manifest.displayName || this.manifest.name)); ++ throw new Error(nls.localize('restartCode', "Please restart !!APP_NAME!! before reinstalling {0}.", this.manifest.displayName || this.manifest.name)); } } } @@ -924,7 +924,7 @@ index 6eefd716..4e8cb6af 100644 await this.extensionsScanner.deleteExtension(existingWithSameVersion, 'existing'); } catch (e) { - throw new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", this.manifest.displayName || this.manifest.name)); -+ throw new Error(nls.localize('restartCode', "Please restart BradfordCode before reinstalling {0}.", this.manifest.displayName || this.manifest.name)); ++ throw new Error(nls.localize('restartCode', "Please restart !!APP_NAME!! before reinstalling {0}.", this.manifest.displayName || this.manifest.name)); } } @@ -937,7 +937,7 @@ index e7cf3f54..ac295f0a 100644 import { ITerminalEnvironment } from '../../terminal/common/terminal.js'; -const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console"); -+const TERMINAL_TITLE = nls.localize('console.title', "BradfordCode Console"); ++const TERMINAL_TITLE = nls.localize('console.title', "!!APP_NAME!! Console"); abstract class ExternalTerminalService { public _serviceBrand: undefined; @@ -950,7 +950,7 @@ index cf059eab..86b46ab6 100644 [TerminalSettingId.InheritEnv]: { scope: ConfigurationScope.APPLICATION, - description: localize('terminal.integrated.inheritEnv', "Whether new shells should inherit their environment from VS Code, which may source a login shell to ensure $PATH and other development variables are initialized. This has no effect on Windows."), -+ description: localize('terminal.integrated.inheritEnv', "Whether new shells should inherit their environment from BradfordCode, which may source a login shell to ensure $PATH and other development variables are initialized. This has no effect on Windows."), ++ description: localize('terminal.integrated.inheritEnv', "Whether new shells should inherit their environment from !!APP_NAME!!, which may source a login shell to ensure $PATH and other development variables are initialized. This has no effect on Windows."), type: 'boolean', default: true }, @@ -963,7 +963,7 @@ index 2c63e9e6..eba02c07 100644 scope: ConfigurationScope.APPLICATION, title: localize('enableWindowsBackgroundUpdatesTitle', "Enable Background Updates"), - description: localize('enableWindowsBackgroundUpdates', "Enable to download and install new VS Code versions in the background."), -+ description: localize('enableWindowsBackgroundUpdates', "Enable to download and install new BradfordCode versions in the background."), ++ description: localize('enableWindowsBackgroundUpdates', "Enable to download and install new !!APP_NAME!! versions in the background."), included: isWindows && !isWeb }, 'update.showReleaseNotes': { @@ -976,7 +976,7 @@ index 8cf5dceb..5860d520 100644 owner: 'joaomoreno'; messageHash: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The hash of the error message.' }; - comment: 'This is used to know how often VS Code updates have failed.'; -+ comment: 'This is used to know how often BradfordCode updates have failed.'; ++ comment: 'This is used to know how often !!APP_NAME!! updates have failed.'; }; export abstract class AbstractUpdateService implements IUpdateService { @@ -989,7 +989,7 @@ index 58ff362b..24124df3 100644 export async function main(desc: ProductDescription, args: string[]): Promise { if (!cliPipe && !cliCommand) { - console.log('Command is only available in WSL or inside a Visual Studio Code terminal.'); -+ console.log('Command is only available in WSL or inside a BradfordCode terminal.'); ++ console.log('Command is only available in WSL or inside a !!APP_NAME!! terminal.'); return; } @@ -998,7 +998,7 @@ index 58ff362b..24124df3 100644 function fatal(message: string, err: unknown): void { - console.error('Unable to connect to VS Code server: ' + message); -+ console.error('Unable to connect to BradfordCode server: ' + message); ++ console.error('Unable to connect to !!APP_NAME!! server: ' + message); console.error(err); process.exit(1); } @@ -1011,7 +1011,7 @@ index 710009f8..f76af8b6 100644 type ClientIdUsageClassification = { owner: 'TylerLeonhardt'; - comment: 'Used to see which extensions are using the VSCode client id override'; -+ comment: 'Used to see which extensions are using the BradfordCode client id override'; ++ comment: 'Used to see which extensions are using the !!APP_NAME!! client id override'; extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension id.' }; }; this.telemetryService.publicLog2<{ extensionId: string }, ClientIdUsageClassification>('authentication.clientIdUsage', { extensionId }); @@ -1024,7 +1024,7 @@ index 83378707..67f454a2 100644 properties: { id: { - description: localize({ key: 'vscode.extension.contributes.views.containers.id', comment: ['Contribution refers to those that an extension contributes to VS Code through an extension/contribution point. '] }, "Unique id used to identify the container in which views can be contributed using 'views' contribution point"), -+ description: localize({ key: 'vscode.extension.contributes.views.containers.id', comment: ['Contribution refers to those that an extension contributes to BradfordCode through an extension/contribution point. '] }, "Unique id used to identify the container in which views can be contributed using 'views' contribution point"), ++ description: localize({ key: 'vscode.extension.contributes.views.containers.id', comment: ['Contribution refers to those that an extension contributes to !!APP_NAME!! through an extension/contribution point. '] }, "Unique id used to identify the container in which views can be contributed using 'views' contribution point"), type: 'string', pattern: '^[a-zA-Z0-9_-]+$' }, @@ -1037,7 +1037,7 @@ index 2785bbd7..c6d79962 100644 [ ApiCommandArgument.Uri.with('resource', 'Resource to open'), - ApiCommandArgument.String.with('viewId', 'Custom editor view id. This should be the viewType string for custom editors or the notebookType string for notebooks. Use \'default\' to use VS Code\'s default text editor'), -+ ApiCommandArgument.String.with('viewId', 'Custom editor view id. This should be the viewType string for custom editors or the notebookType string for notebooks. Use \'default\' to use BradfordCode\'s default text editor'), ++ ApiCommandArgument.String.with('viewId', 'Custom editor view id. This should be the viewType string for custom editors or the notebookType string for notebooks. Use \'default\' to use !!APP_NAME!!\'s default text editor'), new ApiCommandArgument('columnOrOptions', 'Either the column in which to open or editor options, see vscode.TextDocumentShowOptions', v => v === undefined || typeof v === 'number' || typeof v === 'object', v => !v ? v : typeof v === 'number' ? [typeConverters.ViewColumn.from(v), undefined] : [typeConverters.ViewColumn.from(v.viewColumn), typeConverters.TextEditorOpenOptions.from(v)], @@ -1051,8 +1051,8 @@ index 92e874dc..b34e04b5 100644 static readonly TypeHierarchyItem = new ApiCommandArgument('item', 'A type hierarchy item', v => v instanceof extHostTypes.TypeHierarchyItem, extHostTypeConverter.TypeHierarchyItem.from); - static readonly TestItem = new ApiCommandArgument('testItem', 'A VS Code TestItem', v => v instanceof TestItemImpl, extHostTypeConverter.TestItem.from); - static readonly TestProfile = new ApiCommandArgument('testProfile', 'A VS Code test profile', v => v instanceof extHostTypes.TestRunProfileBase, extHostTypeConverter.TestRunProfile.from); -+ static readonly TestItem = new ApiCommandArgument('testItem', 'A BradfordCode TestItem', v => v instanceof TestItemImpl, extHostTypeConverter.TestItem.from); -+ static readonly TestProfile = new ApiCommandArgument('testProfile', 'A BradfordCode test profile', v => v instanceof extHostTypes.TestRunProfileBase, extHostTypeConverter.TestRunProfile.from); ++ static readonly TestItem = new ApiCommandArgument('testItem', 'A !!APP_NAME!! TestItem', v => v instanceof TestItemImpl, extHostTypeConverter.TestItem.from); ++ static readonly TestProfile = new ApiCommandArgument('testProfile', 'A !!APP_NAME!! test profile', v => v instanceof extHostTypes.TestRunProfileBase, extHostTypeConverter.TestRunProfile.from); constructor( readonly name: string, @@ -1081,7 +1081,7 @@ index 0d713847..33749645 100644 - test('Opening a notebook results in VS Code firing the event onDidChangeActiveNotebookEditor twice #118470', function () { -+ test('Opening a notebook results in BradfordCode firing the event onDidChangeActiveNotebookEditor twice #118470', function () { ++ test('Opening a notebook results in !!APP_NAME!! firing the event onDidChangeActiveNotebookEditor twice #118470', function () { let count = 0; disposables.add(extHostNotebooks.onDidChangeActiveNotebookEditor(() => count += 1)); @@ -1094,7 +1094,7 @@ index ee85640c..bddb01c5 100644 const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); - let content = '# VS Code Policy Diagnostics\n\n'; -+ let content = '# BradfordCode Policy Diagnostics\n\n'; ++ let content = '# !!APP_NAME!! Policy Diagnostics\n\n'; content += '*WARNING: This file may contain sensitive information.*\n\n'; content += '## System Information\n\n'; content += '| Property | Value |\n'; @@ -1107,7 +1107,7 @@ index 8251c4a4..19bee6b0 100644 super({ id: OpenNewsletterSignupUrlAction.ID, - title: localize2('newsletterSignup', 'Signup for the VS Code Newsletter'), -+ title: localize2('newsletterSignup', 'Signup for the BradfordCode Newsletter'), ++ title: localize2('newsletterSignup', 'Signup for the !!APP_NAME!! Newsletter'), category: Categories.Help, f1: true }); @@ -1120,7 +1120,7 @@ index e342f838..98e500bc 100644 // do not support this and require a full context switch to clean-up. if (created) { - throw new Error('Unable to create the VSCode workbench more than once.'); -+ throw new Error('Unable to create the BradfordCode workbench more than once.'); ++ throw new Error('Unable to create the !!APP_NAME!! workbench more than once.'); } else { created = true; } @@ -1133,7 +1133,7 @@ index f2a3b35e..1fecb536 100644 localize('rootPath', "`${rootPath}`: file path of the opened workspace or folder (e.g. /Users/Development/myWorkspace)."), localize('profileName', "`${profileName}`: name of the profile in which the workspace is opened (e.g. Data Science (Profile)). Ignored if default profile is used."), - localize('appName', "`${appName}`: e.g. VS Code."), -+ localize('appName', "`${appName}`: e.g. BradfordCode."), ++ localize('appName', "`${appName}`: e.g. !!APP_NAME!!."), localize('remoteName', "`${remoteName}`: e.g. SSH"), localize('dirty', "`${dirty}`: an indicator for when the active editor has unsaved changes."), localize('focusedView', "`${focusedView}`: the name of the view that is currently focused."), @@ -1146,7 +1146,7 @@ index 51a4a3f2..0e3b27dc 100644 export const EmbedderIdentifierContext = new RawContextKey('embedderIdentifier', undefined, localize('embedderIdentifier', 'The identifier of the embedder according to the product service, if one is defined')); -export const InAutomationContext = new RawContextKey('inAutomation', false, localize('inAutomation', "Whether VS Code is running under automation/smoke test")); -+export const InAutomationContext = new RawContextKey('inAutomation', false, localize('inAutomation', "Whether BradfordCode is running under automation/smoke test")); ++export const InAutomationContext = new RawContextKey('inAutomation', false, localize('inAutomation', "Whether !!APP_NAME!! is running under automation/smoke test")); //#endregion @@ -1159,7 +1159,7 @@ index 883dab9e..3fcf5140 100644 enumDescriptions: [ nls.localize('chat.mcp.access.none', "No access to MCP servers."), - nls.localize('chat.mcp.access.registry', "Allows access to MCP servers installed from the registry that VS Code is connected to."), -+ nls.localize('chat.mcp.access.registry', "Allows access to MCP servers installed from the registry that BradfordCode is connected to."), ++ nls.localize('chat.mcp.access.registry', "Allows access to MCP servers installed from the registry that !!APP_NAME!! is connected to."), nls.localize('chat.mcp.access.any', "Allow access to any installed MCP server.") ], default: McpAccessValue.All, @@ -1168,7 +1168,7 @@ index 883dab9e..3fcf5140 100644 }, { - key: 'chat.mcp.access.registry', value: nls.localize('chat.mcp.access.registry', "Allows access to MCP servers installed from the registry that VS Code is connected to."), -+ key: 'chat.mcp.access.registry', value: nls.localize('chat.mcp.access.registry', "Allows access to MCP servers installed from the registry that BradfordCode is connected to."), ++ key: 'chat.mcp.access.registry', value: nls.localize('chat.mcp.access.registry', "Allows access to MCP servers installed from the registry that !!APP_NAME!! is connected to."), }, { key: 'chat.mcp.access.any', value: nls.localize('chat.mcp.access.any', "Allow access to any installed MCP server.") @@ -1181,7 +1181,7 @@ index 1417e0dc..cc064c82 100644 // Register VSCode agent - const { disposable: vscodeDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.vscode', 'vscode', false, localize2('vscodeAgentDescription', "Ask questions about VS Code").value, ChatAgentLocation.Chat, ChatModeKind.Agent, context, controller); -+ const { disposable: vscodeDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.vscode', 'vscode', false, localize2('vscodeAgentDescription', "Ask questions about BradfordCode").value, ChatAgentLocation.Chat, ChatModeKind.Agent, context, controller); ++ const { disposable: vscodeDisposable } = SetupAgent.doRegisterAgent(instantiationService, chatAgentService, 'setup.vscode', 'vscode', false, localize2('vscodeAgentDescription', "Ask questions about !!APP_NAME!!").value, ChatAgentLocation.Chat, ChatModeKind.Agent, context, controller); disposables.add(vscodeDisposable); // Register workspace agent @@ -1191,8 +1191,8 @@ index 1417e0dc..cc064c82 100644 displayName: localize('setupToolDisplayName', "New Workspace"), - modelDescription: 'Scaffold a new workspace in VS Code', - userDescription: localize('setupToolsDescription', "Scaffold a new workspace in VS Code"), -+ modelDescription: 'Scaffold a new workspace in BradfordCode', -+ userDescription: localize('setupToolsDescription', "Scaffold a new workspace in BradfordCode"), ++ modelDescription: 'Scaffold a new workspace in !!APP_NAME!!', ++ userDescription: localize('setupToolsDescription', "Scaffold a new workspace in !!APP_NAME!!"), canBeReferencedInPrompt: true, toolReferenceName: 'new', when: ContextKeyExpr.true(), @@ -1205,24 +1205,24 @@ index d3be4e1e..60e06e85 100644 } else { if (validGithubCopilotAttributeNames.value.has(attribute.key)) { - report(toMarker(localize('promptValidator.ignoredAttribute.vscode-agent', "Attribute '{0}' is ignored when running locally in VS Code.", attribute.key), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); -+ report(toMarker(localize('promptValidator.ignoredAttribute.vscode-agent', "Attribute '{0}' is ignored when running locally in BradfordCode.", attribute.key), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); ++ report(toMarker(localize('promptValidator.ignoredAttribute.vscode-agent', "Attribute '{0}' is ignored when running locally in !!APP_NAME!!.", attribute.key), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); } else { - report(toMarker(localize('promptValidator.unknownAttribute.vscode-agent', "Attribute '{0}' is not supported in VS Code agent files. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); -+ report(toMarker(localize('promptValidator.unknownAttribute.vscode-agent', "Attribute '{0}' is not supported in BradfordCode agent files. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); ++ report(toMarker(localize('promptValidator.unknownAttribute.vscode-agent', "Attribute '{0}' is not supported in !!APP_NAME!! agent files. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); } } break; case PromptsType.instructions: if (target === Target.Claude) { - report(toMarker(localize('promptValidator.unknownAttribute.rules', "Attribute '{0}' is not supported in rules files by VS Code agents. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); -+ report(toMarker(localize('promptValidator.unknownAttribute.rules', "Attribute '{0}' is not supported in rules files by BradfordCode agents. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); ++ report(toMarker(localize('promptValidator.unknownAttribute.rules', "Attribute '{0}' is not supported in rules files by !!APP_NAME!! agents. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); } else { report(toMarker(localize('promptValidator.unknownAttribute.instructions', "Attribute '{0}' is not supported in instructions files. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); } break; case PromptsType.skill: - report(toMarker(localize('promptValidator.unknownAttribute.skill', "Attribute '{0}' is not supported by VS Code agents. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); -+ report(toMarker(localize('promptValidator.unknownAttribute.skill', "Attribute '{0}' is not supported by BradfordCode agents. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); ++ report(toMarker(localize('promptValidator.unknownAttribute.skill', "Attribute '{0}' is not supported by !!APP_NAME!! agents. Supported: {1}.", attribute.key, supportedNames.value), attribute.range, MarkerSeverity.Hint, [MarkerTag.Unnecessary])); break; } } @@ -1231,7 +1231,7 @@ index d3be4e1e..60e06e85 100644 /** - * Maps Claude tool names to their VS Code tool equivalents. -+ * Maps Claude tool names to their BradfordCode tool equivalents. ++ * Maps Claude tool names to their !!APP_NAME!! tool equivalents. */ export function mapClaudeTools(claudeToolNames: readonly string[]): string[] { const result: string[] = []; @@ -1244,7 +1244,7 @@ index 0b9ced32..b5ed7134 100644 'debugServer': { type: 'number', - description: nls.localize('debugServer', "For debug extension development only: if a port is specified VS Code tries to connect to a debug adapter running in server mode"), -+ description: nls.localize('debugServer', "For debug extension development only: if a port is specified BradfordCode tries to connect to a debug adapter running in server mode"), ++ description: nls.localize('debugServer', "For debug extension development only: if a port is specified !!APP_NAME!! tries to connect to a debug adapter running in server mode"), default: 4711 }, 'preLaunchTask': { @@ -1257,7 +1257,7 @@ index 0c0965d3..f4b0faef 100644 name: 'options', description: '(optional) Options for installing the extension. Object with the following properties: ' + - '`installOnlyNewlyAddedFromExtensionPackVSIX`: When enabled, VS Code installs only newly added extensions from the extension pack VSIX. This option is considered only when installing VSIX. ', -+ '`installOnlyNewlyAddedFromExtensionPackVSIX`: When enabled, BradfordCode installs only newly added extensions from the extension pack VSIX. This option is considered only when installing VSIX. ', ++ '`installOnlyNewlyAddedFromExtensionPackVSIX`: When enabled, !!APP_NAME!! installs only newly added extensions from the extension pack VSIX. This option is considered only when installing VSIX. ', isOptional: true, schema: { 'type': 'object', @@ -1265,19 +1265,19 @@ index 0c0965d3..f4b0faef 100644 'installOnlyNewlyAddedFromExtensionPackVSIX': { 'type': 'boolean', - 'description': localize('workbench.extensions.installExtension.option.installOnlyNewlyAddedFromExtensionPackVSIX', "When enabled, VS Code installs only newly added extensions from the extension pack VSIX. This option is considered only while installing a VSIX."), -+ 'description': localize('workbench.extensions.installExtension.option.installOnlyNewlyAddedFromExtensionPackVSIX', "When enabled, BradfordCode installs only newly added extensions from the extension pack VSIX. This option is considered only while installing a VSIX."), ++ 'description': localize('workbench.extensions.installExtension.option.installOnlyNewlyAddedFromExtensionPackVSIX', "When enabled, !!APP_NAME!! installs only newly added extensions from the extension pack VSIX. This option is considered only while installing a VSIX."), default: false }, 'installPreReleaseVersion': { 'type': 'boolean', - 'description': localize('workbench.extensions.installExtension.option.installPreReleaseVersion', "When enabled, VS Code installs the pre-release version of the extension if available."), -+ 'description': localize('workbench.extensions.installExtension.option.installPreReleaseVersion', "When enabled, BradfordCode installs the pre-release version of the extension if available."), ++ 'description': localize('workbench.extensions.installExtension.option.installPreReleaseVersion', "When enabled, !!APP_NAME!! installs the pre-release version of the extension if available."), default: false }, 'donotSync': { 'type': 'boolean', - 'description': localize('workbench.extensions.installExtension.option.donotSync', "When enabled, VS Code do not sync this extension when Settings Sync is on."), -+ 'description': localize('workbench.extensions.installExtension.option.donotSync', "When enabled, BradfordCode do not sync this extension when Settings Sync is on."), ++ 'description': localize('workbench.extensions.installExtension.option.donotSync', "When enabled, !!APP_NAME!! do not sync this extension when Settings Sync is on."), default: false }, 'justification': { @@ -1287,8 +1287,8 @@ index 0c0965d3..f4b0faef 100644 Severity.Info, - vsixs.length > 1 ? localize('InstallVSIXs.successReload', "Completed installing extensions. Please reload Visual Studio Code to enable them.") - : localize('InstallVSIXAction.successReload', "Completed installing extension. Please reload Visual Studio Code to enable it."), -+ vsixs.length > 1 ? localize('InstallVSIXs.successReload', "Completed installing extensions. Please reload BradfordCode to enable them.") -+ : localize('InstallVSIXAction.successReload', "Completed installing extension. Please reload BradfordCode to enable it."), ++ vsixs.length > 1 ? localize('InstallVSIXs.successReload', "Completed installing extensions. Please reload !!APP_NAME!! to enable them.") ++ : localize('InstallVSIXAction.successReload', "Completed installing extension. Please reload !!APP_NAME!! to enable it."), [{ label: localize('InstallVSIXAction.reloadNow', "Reload Now"), run: () => hostService.reload() @@ -1301,7 +1301,7 @@ index 452db88d..282c9b5a 100644 if (this.error.name === ExtensionManagementErrorCode.Unsupported) { - const productName = isWeb ? localize('VS Code for Web', "{0} for the Web", this.productService.nameLong) : this.productService.nameLong; -+ const productName = isWeb ? localize('BradfordCode for Web', "{0} for the Web", this.productService.nameLong) : this.productService.nameLong; ++ const productName = isWeb ? localize('!!APP_NAME!! for Web', "{0} for the Web", this.productService.nameLong) : this.productService.nameLong; const message = localize('cannot be installed', "The '{0}' extension is not available in {1}. Click 'More Information' to learn more.", this.extension.displayName || this.extension.identifier.id, productName); const { confirmed } = await this.dialogService.confirm({ type: Severity.Info, @@ -1310,7 +1310,7 @@ index 452db88d..282c9b5a 100644 }); } else if (this.extension.deprecationInfo.settings) { - detail = localize('deprecated with alternate settings message', "This extension is deprecated as this functionality is now built-in to VS Code."); -+ detail = localize('deprecated with alternate settings message', "This extension is deprecated as this functionality is now built-in to BradfordCode."); ++ detail = localize('deprecated with alternate settings message', "This extension is deprecated as this functionality is now built-in to !!APP_NAME!!."); const settings = this.extension.deprecationInfo.settings; buttons.push({ @@ -1319,7 +1319,7 @@ index 452db88d..282c9b5a 100644 try { await this.extensionsWorkbenchService.uninstall(this.extension); - alert(localize('uninstallExtensionComplete', "Please reload Visual Studio Code to complete the uninstallation of the extension {0}.", this.extension.displayName)); -+ alert(localize('uninstallExtensionComplete', "Please reload BradfordCode to complete the uninstallation of the extension {0}.", this.extension.displayName)); ++ alert(localize('uninstallExtensionComplete', "Please reload !!APP_NAME!! to complete the uninstallation of the extension {0}.", this.extension.displayName)); } catch (error) { if (!isCancellationError(error)) { this.dialogService.error(getErrorMessage(error)); @@ -1328,7 +1328,7 @@ index 452db88d..282c9b5a 100644 } else if (this.extension.deprecationInfo.settings) { const link = `[${localize('settings', "settings")}](${createCommandUri('workbench.action.openSettings', this.extension.deprecationInfo.settings.map(setting => `@id:${setting}`).join(' '))}})`; - this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated with alternate settings tooltip', "This extension is deprecated as this functionality is now built-in to VS Code. Configure these {0} to use this functionality.", link)) }, true); -+ this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated with alternate settings tooltip', "This extension is deprecated as this functionality is now built-in to BradfordCode. Configure these {0} to use this functionality.", link)) }, true); ++ this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated with alternate settings tooltip', "This extension is deprecated as this functionality is now built-in to !!APP_NAME!!. Configure these {0} to use this functionality.", link)) }, true); } else { const message = new MarkdownString(localize('deprecated tooltip', "This extension is deprecated as it is no longer being maintained.")); if (this.extension.deprecationInfo.additionalInfo) { @@ -1341,7 +1341,7 @@ index a58d77d1..cc5004c7 100644 if (this.type === ExtensionType.System) { return Promise.resolve(`# ${this.displayName || this.name} -**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled. -+**Notice:** This extension is bundled with BradfordCode. It can be disabled but not uninstalled. ++**Notice:** This extension is bundled with !!APP_NAME!!. It can be disabled but not uninstalled. ## Features ${this.description} `); @@ -1350,7 +1350,7 @@ index a58d77d1..cc5004c7 100644 if (this.type === ExtensionType.System) { - return Promise.resolve(`Please check the [VS Code Release Notes](command:${ShowCurrentReleaseNotesActionId}) for changes to the built-in extensions.`); -+ return Promise.resolve(`Please check the [BradfordCode Release Notes](command:${ShowCurrentReleaseNotesActionId}) for changes to the built-in extensions.`); ++ return Promise.resolve(`Please check the [!!APP_NAME!! Release Notes](command:${ShowCurrentReleaseNotesActionId}) for changes to the built-in extensions.`); } return Promise.reject(new Error('not available')); @@ -1363,7 +1363,7 @@ index 818e6628..19722f3f 100644 unwantedRecommendations: { type: 'array', - description: localize('app.extensions.json.unwantedRecommendations', "List of extensions recommended by VS Code that should not be recommended for users of this workspace. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."), -+ description: localize('app.extensions.json.unwantedRecommendations', "List of extensions recommended by BradfordCode that should not be recommended for users of this workspace. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."), ++ description: localize('app.extensions.json.unwantedRecommendations', "List of extensions recommended by !!APP_NAME!! that should not be recommended for users of this workspace. The identifier of an extension is always '${publisher}.${name}'. For example: 'vscode.csharp'."), items: { type: 'string', pattern: EXTENSION_IDENTIFIER_PATTERN, @@ -1372,7 +1372,7 @@ index 818e6628..19722f3f 100644 '\t\t', '\t],', - '\t// List of extensions recommended by VS Code that should not be recommended for users of this workspace.', -+ '\t// List of extensions recommended by BradfordCode that should not be recommended for users of this workspace.', ++ '\t// List of extensions recommended by !!APP_NAME!! that should not be recommended for users of this workspace.', '\t"unwantedRecommendations": [', '\t\t', '\t]', @@ -1385,7 +1385,7 @@ index d174c8c9..69d8d43e 100644 canBeReferencedInPrompt: true, displayName: localize('installExtensionsTool.displayName', 'Install Extensions'), - modelDescription: 'This is a tool for installing extensions in Visual Studio Code. You should provide the list of extension ids to install. The identifier of an extension is \'\${ publisher }.\${ name }\' for example: \'vscode.csharp\'.', -+ modelDescription: 'This is a tool for installing extensions in BradfordCode. You should provide the list of extension ids to install. The identifier of an extension is \'\${ publisher }.\${ name }\' for example: \'vscode.csharp\'.', ++ modelDescription: 'This is a tool for installing extensions in !!APP_NAME!!. You should provide the list of extension ids to install. The identifier of an extension is \'\${ publisher }.\${ name }\' for example: \'vscode.csharp\'.', userDescription: localize('installExtensionsTool.userDescription', 'Tool for installing extensions'), source: ToolDataSource.Internal, inputSchema: { @@ -1400,7 +1400,7 @@ index 91541b6a..3d4d5f05 100644 - modelDescription: 'This is a tool for browsing Visual Studio Code Extensions Marketplace. It allows the model to search for extensions and retrieve detailed information about them. The model should use this tool whenever it needs to discover extensions or resolve information about known ones. To use the tool, the model has to provide the category of the extensions, relevant search keywords, or known extension IDs. Note that search results may include false positives, so reviewing and filtering is recommended.', - userDescription: localize('searchExtensionsTool.userDescription', 'Search for VS Code extensions'), + modelDescription: 'This is a tool for browsing OpenVSX. It allows the model to search for extensions and retrieve detailed information about them. The model should use this tool whenever it needs to discover extensions or resolve information about known ones. To use the tool, the model has to provide the category of the extensions, relevant search keywords, or known extension IDs. Note that search results may include false positives, so reviewing and filtering is recommended.', -+ userDescription: localize('searchExtensionsTool.userDescription', 'Search for BradfordCode extensions'), ++ userDescription: localize('searchExtensionsTool.userDescription', 'Search for !!APP_NAME!! extensions'), source: ToolDataSource.Internal, inputSchema: { type: 'object', @@ -1413,7 +1413,7 @@ index f54ddfe2..2c524b07 100644 markdownDescription: nls.localize('externalUriOpeners.uri', "Map URI pattern to an opener id.\nExample patterns: \n{0}", exampleUriPatterns), enum: [defaultExternalUriOpenerId], - enumDescriptions: [nls.localize('externalUriOpeners.defaultId', "Open using VS Code's standard opener.")], -+ enumDescriptions: [nls.localize('externalUriOpeners.defaultId', "Open using BradfordCode's standard opener.")], ++ enumDescriptions: [nls.localize('externalUriOpeners.defaultId', "Open using !!APP_NAME!!'s standard opener.")], }, externalUriOpenerIdSchemaAddition ] @@ -1426,10 +1426,10 @@ index bd739953..bae23895 100644 id: { type: 'string', - description: localize('vscode.extension.contributes.localizations.translations.id', "Id of VS Code or Extension for which this translation is contributed to. Id of VS Code is always `vscode` and of extension should be in format `publisherId.extensionName`."), -+ description: localize('vscode.extension.contributes.localizations.translations.id', "Id of BradfordCode or Extension for which this translation is contributed to. Id of BradfordCode is always `vscode` and of extension should be in format `publisherId.extensionName`."), ++ description: localize('vscode.extension.contributes.localizations.translations.id', "Id of !!APP_NAME!! or Extension for which this translation is contributed to. Id of !!APP_NAME!! is always `vscode` and of extension should be in format `publisherId.extensionName`."), pattern: '^((vscode)|([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*))$', - patternErrorMessage: localize('vscode.extension.contributes.localizations.translations.id.pattern', "Id should be `vscode` or in format `publisherId.extensionName` for translating VS code or an extension respectively.") -+ patternErrorMessage: localize('vscode.extension.contributes.localizations.translations.id.pattern', "Id should be `vscode` or in format `publisherId.extensionName` for translating BradfordCode or an extension respectively.") ++ patternErrorMessage: localize('vscode.extension.contributes.localizations.translations.id.pattern', "Id should be `vscode` or in format `publisherId.extensionName` for translating !!APP_NAME!! or an extension respectively.") }, path: { type: 'string', @@ -1442,7 +1442,7 @@ index 050dde41..edf24a48 100644 }, metadata: { - description: localize2('configureLocaleDescription', "Changes the locale of VS Code based on installed language packs. Common languages include French, Chinese, Spanish, Japanese, German, Korean, and more.") -+ description: localize2('configureLocaleDescription', "Changes the locale of BradfordCode based on installed language packs. Common languages include French, Chinese, Spanish, Japanese, German, Korean, and more.") ++ description: localize2('configureLocaleDescription', "Changes the locale of !!APP_NAME!! based on installed language packs. Common languages include French, Chinese, Spanish, Japanese, German, Korean, and more.") } }); } @@ -1455,7 +1455,7 @@ index dc231e44..c983379d 100644 const markdownResult = this._register(this.markdownRendererService.render( new MarkdownString( - localize('mcp.welcome.descriptionWithLink', "Browse and install [Model Context Protocol (MCP) servers](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) directly from VS Code to extend agent mode with extra tools for connecting to databases, invoking APIs and performing specialized tasks."), -+ localize('mcp.welcome.descriptionWithLink', "Browse and install [Model Context Protocol (MCP) servers](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) directly from BradfordCode to extend agent mode with extra tools for connecting to databases, invoking APIs and performing specialized tasks."), ++ localize('mcp.welcome.descriptionWithLink', "Browse and install [Model Context Protocol (MCP) servers](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) directly from !!APP_NAME!! to extend agent mode with extra tools for connecting to databases, invoking APIs and performing specialized tasks."), { isTrusted: { enabledCommands: ['workbench.action.openSettings'] } }, ) .appendMarkdown('\n\n') @@ -1469,8 +1469,8 @@ index b533feb1..42539bde 100644 const DEFAULT_CONTENT: string = [ - `// ${nls.localize('displayLanguage', 'Defines the keyboard layout used in VS Code in the browser environment.')}`, - `// ${nls.localize('doc', 'Open VS Code and run "Developer: Inspect Key Mappings (JSON)" from Command Palette.')}`, -+ `// ${nls.localize('displayLanguage', 'Defines the keyboard layout used in BradfordCode in the browser environment.')}`, -+ `// ${nls.localize('doc', 'Open BradfordCode and run "Developer: Inspect Key Mappings (JSON)" from Command Palette.')}`, ++ `// ${nls.localize('displayLanguage', 'Defines the keyboard layout used in !!APP_NAME!! in the browser environment.')}`, ++ `// ${nls.localize('doc', 'Open !!APP_NAME!! and run "Developer: Inspect Key Mappings (JSON)" from Command Palette.')}`, ``, `// Once you have the keyboard layout info, please paste it below.`, '\n' @@ -1483,7 +1483,7 @@ index 36209197..8b4cc4a0 100644 } if (response.code && response.code === TerminateResponseCode.ProcessNotFound) { - this._notificationService.error(nls.localize('TerminateAction.noProcess', 'The launched process doesn\'t exist anymore. If the task spawned background tasks exiting VS Code might result in orphaned processes.')); -+ this._notificationService.error(nls.localize('TerminateAction.noProcess', 'The launched process doesn\'t exist anymore. If the task spawned background tasks exiting BradfordCode might result in orphaned processes.')); ++ this._notificationService.error(nls.localize('TerminateAction.noProcess', 'The launched process doesn\'t exist anymore. If the task spawned background tasks exiting !!APP_NAME!! might result in orphaned processes.')); } else { this._notificationService.error(nls.localize('TerminateAction.failed', 'Failed to terminate running task')); } @@ -1496,7 +1496,7 @@ index 9db6b8ad..1ec00756 100644 promptOnClose: { type: 'boolean', - description: nls.localize('JsonSchema.tasks.promptOnClose', 'Whether the user is prompted when VS Code closes with a running task.'), -+ description: nls.localize('JsonSchema.tasks.promptOnClose', 'Whether the user is prompted when BradfordCode closes with a running task.'), ++ description: nls.localize('JsonSchema.tasks.promptOnClose', 'Whether the user is prompted when !!APP_NAME!! closes with a running task.'), default: false }, isBuildCommand: { @@ -1505,7 +1505,7 @@ index 9db6b8ad..1ec00756 100644 promptOnClose: { type: 'boolean', - description: nls.localize('JsonSchema.promptOnClose', 'Whether the user is prompted when VS Code closes with a running background task.'), -+ description: nls.localize('JsonSchema.promptOnClose', 'Whether the user is prompted when BradfordCode closes with a running background task.'), ++ description: nls.localize('JsonSchema.promptOnClose', 'Whether the user is prompted when !!APP_NAME!! closes with a running background task.'), default: false }, echoCommand: { @@ -1518,7 +1518,7 @@ index f063bfc0..f17ee737 100644 promptOnClose: { type: 'boolean', - description: nls.localize('JsonSchema.tasks.promptOnClose', 'Whether the user is prompted when VS Code closes with a running task.'), -+ description: nls.localize('JsonSchema.tasks.promptOnClose', 'Whether the user is prompted when BradfordCode closes with a running task.'), ++ description: nls.localize('JsonSchema.tasks.promptOnClose', 'Whether the user is prompted when !!APP_NAME!! closes with a running task.'), default: false }, presentation: Objects.deepClone(presentation), @@ -1531,7 +1531,7 @@ index 9a745727..7c9fb358 100644 run: () => this.configurationService.updateValue(TerminalSettingId.FontFamily, 'monospace'), }]; - this._notificationService.prompt(Severity.Warning, nls.localize('terminal.monospaceOnly', "The terminal only supports monospace fonts. Be sure to restart VS Code if this is a newly installed font."), choices); -+ this._notificationService.prompt(Severity.Warning, nls.localize('terminal.monospaceOnly', "The terminal only supports monospace fonts. Be sure to restart BradfordCode if this is a newly installed font."), choices); ++ this._notificationService.prompt(Severity.Warning, nls.localize('terminal.monospaceOnly', "The terminal only supports monospace fonts. Be sure to restart !!APP_NAME!! if this is a newly installed font."), choices); } } })); @@ -1544,7 +1544,7 @@ index c5729c39..ccd535ba 100644 if ! builtin eval "$VSCODE_PYTHON_BASH_ACTIVATE"; then __vsc_activation_status=$? - builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m VS Code Python bash activation failed with exit code %d \x1b[0m' "$__vsc_activation_status" -+ builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m BradfordCode Python bash activation failed with exit code %d \x1b[0m' "$__vsc_activation_status" ++ builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m !!APP_NAME!! Python bash activation failed with exit code %d \x1b[0m' "$__vsc_activation_status" fi fi # Remove any leftover Python activation env vars. @@ -1557,7 +1557,7 @@ index c43718e3..ca17a601 100644 if ! builtin eval "$VSCODE_PYTHON_ZSH_ACTIVATE"; then __vsc_activation_status=$? - builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m VS Code Python zsh activation failed with exit code %d \x1b[0m' "$__vsc_activation_status" -+ builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m BradfordCode Python zsh activation failed with exit code %d \x1b[0m' "$__vsc_activation_status" ++ builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m !!APP_NAME!! Python zsh activation failed with exit code %d \x1b[0m' "$__vsc_activation_status" fi fi # Remove any leftover Python activation env vars. @@ -1570,7 +1570,7 @@ index f87fc762..15ffe678 100644 if test $__vsc_activation_status -ne 0 - builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m VS Code Python fish activation failed with exit code %d \x1b[0m \n' "$__vsc_activation_status" -+ builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m BradfordCode Python fish activation failed with exit code %d \x1b[0m \n' "$__vsc_activation_status" ++ builtin printf '\x1b[0m\x1b[7m * \x1b[0;103m !!APP_NAME!! Python fish activation failed with exit code %d \x1b[0m \n' "$__vsc_activation_status" end end # Remove any leftover Python activation env vars. @@ -1579,7 +1579,7 @@ index f87fc762..15ffe678 100644 # Helper function -function __vsc_esc -d "Emit escape sequences for VS Code shell integration" -+function __vsc_esc -d "Emit escape sequences for BradfordCode shell integration" ++function __vsc_esc -d "Emit escape sequences for !!APP_NAME!! shell integration" builtin printf "\e]633;%s\a" (string join ";" -- $argv) end @@ -1592,7 +1592,7 @@ index 72a329b8..2399d553 100644 catch { $activationError = $_ - Write-Host "`e[0m`e[7m * `e[0;103m VS Code Python powershell activation failed with exit code $($activationError.Exception.Message) `e[0m" -+ Write-Host "`e[0m`e[7m * `e[0;103m BradfordCode Python powershell activation failed with exit code $($activationError.Exception.Message) `e[0m" ++ Write-Host "`e[0m`e[7m * `e[0;103m !!APP_NAME!! Python powershell activation failed with exit code $($activationError.Exception.Message) `e[0m" } } # Remove any leftover Python activation env vars. @@ -1605,7 +1605,7 @@ index 3d3e8f2d..5cbfc9ad 100644 }, [TerminalSettingId.DetectLocale]: { - markdownDescription: localize('terminal.integrated.detectLocale', "Controls whether to detect and set the `$LANG` environment variable to a UTF-8 compliant option since VS Code's terminal only supports UTF-8 encoded data coming from the shell."), -+ markdownDescription: localize('terminal.integrated.detectLocale', "Controls whether to detect and set the `$LANG` environment variable to a UTF-8 compliant option since BradfordCode's terminal only supports UTF-8 encoded data coming from the shell."), ++ markdownDescription: localize('terminal.integrated.detectLocale', "Controls whether to detect and set the `$LANG` environment variable to a UTF-8 compliant option since !!APP_NAME!!'s terminal only supports UTF-8 encoded data coming from the shell."), type: 'string', enum: ['auto', 'off', 'on'], markdownEnumDescriptions: [ @@ -1614,7 +1614,7 @@ index 3d3e8f2d..5cbfc9ad 100644 enum: ['auto', 'on', 'off'], markdownEnumDescriptions: [ - localize('terminal.integrated.gpuAcceleration.auto', "Let VS Code detect which renderer will give the best experience."), -+ localize('terminal.integrated.gpuAcceleration.auto', "Let BradfordCode detect which renderer will give the best experience."), ++ localize('terminal.integrated.gpuAcceleration.auto', "Let !!APP_NAME!! detect which renderer will give the best experience."), localize('terminal.integrated.gpuAcceleration.on', "Enable GPU acceleration within the terminal."), localize('terminal.integrated.gpuAcceleration.off', "Disable GPU acceleration within the terminal. The terminal will render much slower when GPU acceleration is off but it should reliably work on all systems."), ], @@ -1623,7 +1623,7 @@ index 3d3e8f2d..5cbfc9ad 100644 markdownDescription: localize( 'terminal.integrated.commandsToSkipShell', - "A set of command IDs whose keybindings will not be sent to the shell but instead always be handled by VS Code. This allows keybindings that would normally be consumed by the shell to act instead the same as when the terminal is not focused, for example `Ctrl+P` to launch Quick Open.\n\n \n\nMany commands are skipped by default. To override a default and pass that command's keybinding to the shell instead, add the command prefixed with the `-` character. For example add `-workbench.action.quickOpen` to allow `Ctrl+P` to reach the shell.\n\n \n\nThe following list of default skipped commands is truncated when viewed in Settings Editor. To see the full list, {1} and search for the first command from the list below.\n\n \n\nDefault Skipped Commands:\n\n{0}", -+ "A set of command IDs whose keybindings will not be sent to the shell but instead always be handled by BradfordCode. This allows keybindings that would normally be consumed by the shell to act instead the same as when the terminal is not focused, for example `Ctrl+P` to launch Quick Open.\n\n \n\nMany commands are skipped by default. To override a default and pass that command's keybinding to the shell instead, add the command prefixed with the `-` character. For example add `-workbench.action.quickOpen` to allow `Ctrl+P` to reach the shell.\n\n \n\nThe following list of default skipped commands is truncated when viewed in Settings Editor. To see the full list, {1} and search for the first command from the list below.\n\n \n\nDefault Skipped Commands:\n\n{0}", ++ "A set of command IDs whose keybindings will not be sent to the shell but instead always be handled by !!APP_NAME!!. This allows keybindings that would normally be consumed by the shell to act instead the same as when the terminal is not focused, for example `Ctrl+P` to launch Quick Open.\n\n \n\nMany commands are skipped by default. To override a default and pass that command's keybinding to the shell instead, add the command prefixed with the `-` character. For example add `-workbench.action.quickOpen` to allow `Ctrl+P` to reach the shell.\n\n \n\nThe following list of default skipped commands is truncated when viewed in Settings Editor. To see the full list, {1} and search for the first command from the list below.\n\n \n\nDefault Skipped Commands:\n\n{0}", DEFAULT_COMMANDS_TO_SKIP_SHELL.sort().map(command => `- ${command}`).join('\n'), `[${localize('openDefaultSettingsJson', "open the default settings JSON")}](command:workbench.action.openRawDefaultSettings '${localize('openDefaultSettingsJson.capitalized', "Open Default Settings (JSON)")}')`, @@ -1632,7 +1632,7 @@ index 3d3e8f2d..5cbfc9ad 100644 }, [TerminalSettingId.AllowChords]: { - markdownDescription: localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass {0}, setting this to false is particularly useful when you want ctrl+k to go to your shell (not VS Code).", '`#terminal.integrated.commandsToSkipShell#`'), -+ markdownDescription: localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass {0}, setting this to false is particularly useful when you want ctrl+k to go to your shell (not BradfordCode).", '`#terminal.integrated.commandsToSkipShell#`'), ++ markdownDescription: localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass {0}, setting this to false is particularly useful when you want ctrl+k to go to your shell (not !!APP_NAME!!).", '`#terminal.integrated.commandsToSkipShell#`'), type: 'boolean', default: true }, @@ -1641,7 +1641,7 @@ index 3d3e8f2d..5cbfc9ad 100644 [TerminalSettingId.EnvMacOs]: { restricted: true, - markdownDescription: localize('terminal.integrated.env.osx', "Object with environment variables that will be added to the VS Code process to be used by the terminal on macOS. Set to `null` to delete the environment variable."), -+ markdownDescription: localize('terminal.integrated.env.osx', "Object with environment variables that will be added to the BradfordCode process to be used by the terminal on macOS. Set to `null` to delete the environment variable."), ++ markdownDescription: localize('terminal.integrated.env.osx', "Object with environment variables that will be added to the !!APP_NAME!! process to be used by the terminal on macOS. Set to `null` to delete the environment variable."), type: 'object', additionalProperties: { type: ['string', 'null'] @@ -1650,7 +1650,7 @@ index 3d3e8f2d..5cbfc9ad 100644 [TerminalSettingId.EnvLinux]: { restricted: true, - markdownDescription: localize('terminal.integrated.env.linux', "Object with environment variables that will be added to the VS Code process to be used by the terminal on Linux. Set to `null` to delete the environment variable."), -+ markdownDescription: localize('terminal.integrated.env.linux', "Object with environment variables that will be added to the BradfordCode process to be used by the terminal on Linux. Set to `null` to delete the environment variable."), ++ markdownDescription: localize('terminal.integrated.env.linux', "Object with environment variables that will be added to the !!APP_NAME!! process to be used by the terminal on Linux. Set to `null` to delete the environment variable."), type: 'object', additionalProperties: { type: ['string', 'null'] @@ -1659,7 +1659,7 @@ index 3d3e8f2d..5cbfc9ad 100644 [TerminalSettingId.EnvWindows]: { restricted: true, - markdownDescription: localize('terminal.integrated.env.windows', "Object with environment variables that will be added to the VS Code process to be used by the terminal on Windows. Set to `null` to delete the environment variable."), -+ markdownDescription: localize('terminal.integrated.env.windows', "Object with environment variables that will be added to the BradfordCode process to be used by the terminal on Windows. Set to `null` to delete the environment variable."), ++ markdownDescription: localize('terminal.integrated.env.windows', "Object with environment variables that will be added to the !!APP_NAME!! process to be used by the terminal on Windows. Set to `null` to delete the environment variable."), type: 'object', additionalProperties: { type: ['string', 'null'] @@ -1668,7 +1668,7 @@ index 3d3e8f2d..5cbfc9ad 100644 [TerminalSettingId.WindowsUseConptyDll]: { restricted: true, - markdownDescription: localize('terminal.integrated.windowsUseConptyDll', "Whether to use the conpty.dll (v1.25.260303002) shipped with VS Code, instead of the one bundled with Windows."), -+ markdownDescription: localize('terminal.integrated.windowsUseConptyDll', "Whether to use the conpty.dll (v1.25.260303002) shipped with BradfordCode, instead of the one bundled with Windows."), ++ markdownDescription: localize('terminal.integrated.windowsUseConptyDll', "Whether to use the conpty.dll (v1.25.260303002) shipped with !!APP_NAME!!, instead of the one bundled with Windows."), type: 'boolean', default: true, }, @@ -1677,7 +1677,7 @@ index 3d3e8f2d..5cbfc9ad 100644 [TerminalSettingId.ShellIntegrationEnabled]: { restricted: true, - markdownDescription: localize('terminal.integrated.shellIntegration.enabled', "Determines whether or not shell integration is auto-injected to support features like enhanced command tracking and current working directory detection. \n\nShell integration works by injecting the shell with a startup script. The script gives VS Code insight into what is happening within the terminal.\n\nSupported shells:\n\n- Linux/macOS: bash, fish, pwsh, zsh\n - Windows: pwsh, git bash\n\nThis setting applies only when terminals are created, so you will need to restart your terminals for it to take effect.\n\n Note that the script injection may not work if you have custom arguments defined in the terminal profile, have enabled {1}, have a [complex bash `PROMPT_COMMAND`](https://code.visualstudio.com/docs/editor/integrated-terminal#_complex-bash-promptcommand), or other unsupported setup. To disable decorations, see {0}", '`#terminal.integrated.shellIntegration.decorationsEnabled#`', '`#editor.accessibilitySupport#`'), -+ markdownDescription: localize('terminal.integrated.shellIntegration.enabled', "Determines whether or not shell integration is auto-injected to support features like enhanced command tracking and current working directory detection. \n\nShell integration works by injecting the shell with a startup script. The script gives BradfordCode insight into what is happening within the terminal.\n\nSupported shells:\n\n- Linux/macOS: bash, fish, pwsh, zsh\n - Windows: pwsh, git bash\n\nThis setting applies only when terminals are created, so you will need to restart your terminals for it to take effect.\n\n Note that the script injection may not work if you have custom arguments defined in the terminal profile, have enabled {1}, have a [complex bash `PROMPT_COMMAND`](https://code.visualstudio.com/docs/editor/integrated-terminal#_complex-bash-promptcommand), or other unsupported setup. To disable decorations, see {0}", '`#terminal.integrated.shellIntegration.decorationsEnabled#`', '`#editor.accessibilitySupport#`'), ++ markdownDescription: localize('terminal.integrated.shellIntegration.enabled', "Determines whether or not shell integration is auto-injected to support features like enhanced command tracking and current working directory detection. \n\nShell integration works by injecting the shell with a startup script. The script gives !!APP_NAME!! insight into what is happening within the terminal.\n\nSupported shells:\n\n- Linux/macOS: bash, fish, pwsh, zsh\n - Windows: pwsh, git bash\n\nThis setting applies only when terminals are created, so you will need to restart your terminals for it to take effect.\n\n Note that the script injection may not work if you have custom arguments defined in the terminal profile, have enabled {1}, have a [complex bash `PROMPT_COMMAND`](https://code.visualstudio.com/docs/editor/integrated-terminal#_complex-bash-promptcommand), or other unsupported setup. To disable decorations, see {0}", '`#terminal.integrated.shellIntegration.decorationsEnabled#`', '`#editor.accessibilitySupport#`'), type: 'boolean', default: true }, @@ -1690,7 +1690,7 @@ index dc20533b..b97acfc7 100644 export const terminalAutoRepliesConfiguration: IStringDictionary = { [TerminalAutoRepliesSettingId.AutoReplies]: { - markdownDescription: localize('terminal.integrated.autoReplies', "A set of messages that, when encountered in the terminal, will be automatically responded to. Provided the message is specific enough, this can help automate away common responses.\n\nRemarks:\n\n- Use {0} to automatically respond to the terminate batch job prompt on Windows.\n- The message includes escape sequences so the reply might not happen with styled text.\n- Each reply can only happen once every second.\n- Use {1} in the reply to mean the enter key.\n- To unset a default key, set the value to null.\n- Restart VS Code if new don't apply.", '`"Terminate batch job (Y/N)": "Y\\r"`', '`"\\r"`'), -+ markdownDescription: localize('terminal.integrated.autoReplies', "A set of messages that, when encountered in the terminal, will be automatically responded to. Provided the message is specific enough, this can help automate away common responses.\n\nRemarks:\n\n- Use {0} to automatically respond to the terminate batch job prompt on Windows.\n- The message includes escape sequences so the reply might not happen with styled text.\n- Each reply can only happen once every second.\n- Use {1} in the reply to mean the enter key.\n- To unset a default key, set the value to null.\n- Restart BradfordCode if new don't apply.", '`"Terminate batch job (Y/N)": "Y\\r"`', '`"\\r"`'), ++ markdownDescription: localize('terminal.integrated.autoReplies', "A set of messages that, when encountered in the terminal, will be automatically responded to. Provided the message is specific enough, this can help automate away common responses.\n\nRemarks:\n\n- Use {0} to automatically respond to the terminate batch job prompt on Windows.\n- The message includes escape sequences so the reply might not happen with styled text.\n- Each reply can only happen once every second.\n- Use {1} in the reply to mean the enter key.\n- To unset a default key, set the value to null.\n- Restart !!APP_NAME!! if new don't apply.", '`"Terminate batch job (Y/N)": "Y\\r"`', '`"\\r"`'), type: 'object', additionalProperties: { oneOf: [{ @@ -1703,7 +1703,7 @@ index d595ccbd..0fd28a30 100644 'problemMatcher': { 'type': 'array', - 'description': `The problem matcher to use to parse task output for errors and warnings. Can be a predefined matcher like '$tsc' (TypeScript), '$eslint - stylish', '$gcc', etc., or a custom pattern defined in tasks.json. This helps VS Code display errors in the Problems panel and enables quick navigation to error locations.`, -+ 'description': `The problem matcher to use to parse task output for errors and warnings. Can be a predefined matcher like '$tsc' (TypeScript), '$eslint - stylish', '$gcc', etc., or a custom pattern defined in tasks.json. This helps BradfordCode display errors in the Problems panel and enables quick navigation to error locations.`, ++ 'description': `The problem matcher to use to parse task output for errors and warnings. Can be a predefined matcher like '$tsc' (TypeScript), '$eslint - stylish', '$gcc', etc., or a custom pattern defined in tasks.json. This helps !!APP_NAME!! display errors in the Problems panel and enables quick navigation to error locations.`, 'items': { 'type': 'string' } @@ -1716,7 +1716,7 @@ index 5fc22b73..e02cab68 100644 legacyToolReferenceFullNames: ['runTasks/runTask'], displayName: localize('runInTerminalTool.displayName', 'Run Task'), - modelDescription: `Runs a VS Code task.\n\n- If you see that an appropriate task exists for building or running code, prefer to use this tool to run the task instead of using the ${TerminalToolId.RunInTerminal} tool.\n- Make sure that any appropriate build or watch task is running before trying to run tests or execute code.\n- If the user asks to run a task, use this tool to do so.`, -+ modelDescription: `Runs a BradfordCode task.\n\n- If you see that an appropriate task exists for building or running code, prefer to use this tool to run the task instead of using the ${TerminalToolId.RunInTerminal} tool.\n- Make sure that any appropriate build or watch task is running before trying to run tests or execute code.\n- If the user asks to run a task, use this tool to do so.`, ++ modelDescription: `Runs a !!APP_NAME!! task.\n\n- If you see that an appropriate task exists for building or running code, prefer to use this tool to run the task instead of using the ${TerminalToolId.RunInTerminal} tool.\n- Make sure that any appropriate build or watch task is running before trying to run tests or execute code.\n- If the user asks to run a task, use this tool to do so.`, userDescription: localize('runInTerminalTool.userDescription', 'Run tasks in the workspace'), icon: Codicon.tools, source: ToolDataSource.Internal, @@ -1729,7 +1729,7 @@ index 0f23e5a5..a3b75766 100644 run = () => workbenchExtensionEnablementService.setEnablement([extension], EnablementState.EnabledWorkspace); } else { - message = localize('terminal.voice.installSpeechExtension', "Would you like to install 'VS Code Speech' extension from 'Microsoft'?"); -+ message = localize('terminal.voice.installSpeechExtension', "Would you like to install 'BradfordCode Speech' extension from 'Microsoft'?"); ++ message = localize('terminal.voice.installSpeechExtension', "Would you like to install '!!APP_NAME!! Speech' extension from 'Microsoft'?"); run = () => commandService.executeCommand('workbench.extensions.installExtension', 'ms-vscode.vscode-speech'); primaryButton = localize('installExtension', "Install Extension"); } @@ -1743,8 +1743,8 @@ index c182b9c2..34bfb4d7 100644 detail: newQuality === 'insider' ? - nls.localize('relaunchDetailInsiders', "Press the reload button to switch to the Insiders version of VS Code.") : - nls.localize('relaunchDetailStable', "Press the reload button to switch to the Stable version of VS Code."), -+ nls.localize('relaunchDetailInsiders', "Press the reload button to switch to the Insiders version of BradfordCode.") : -+ nls.localize('relaunchDetailStable', "Press the reload button to switch to the Stable version of BradfordCode."), ++ nls.localize('relaunchDetailInsiders', "Press the reload button to switch to the Insiders version of !!APP_NAME!!.") : ++ nls.localize('relaunchDetailStable', "Press the reload button to switch to the Stable version of !!APP_NAME!!."), primaryButton: nls.localize({ key: 'reload', comment: ['&& denotes a mnemonic'] }, "&&Reload") }); @@ -1753,7 +1753,7 @@ index c182b9c2..34bfb4d7 100644 type: Severity.Info, message: nls.localize('selectSyncService.message', "Choose the settings sync service to use after changing the version"), - detail: nls.localize('selectSyncService.detail', "The Insiders version of VS Code will synchronize your settings, keybindings, extensions, snippets and UI State using separate insiders settings sync service by default."), -+ detail: nls.localize('selectSyncService.detail', "The Insiders version of BradfordCode will synchronize your settings, keybindings, extensions, snippets and UI State using separate insiders settings sync service by default."), ++ detail: nls.localize('selectSyncService.detail', "The Insiders version of !!APP_NAME!! will synchronize your settings, keybindings, extensions, snippets and UI State using separate insiders settings sync service by default."), buttons: [ { label: nls.localize({ key: 'use insiders', comment: ['&& denotes a mnemonic'] }, "&&Insiders"), @@ -1766,13 +1766,13 @@ index 393c8c36..a8e359db 100644 if (defaultTrustedDomains.length > 0) { - content += `// By default, VS Code trusts "localhost" as well as the following domains:\n`; -+ content += `// By default, BradfordCode trusts "localhost" as well as the following domains:\n`; ++ content += `// By default, !!APP_NAME!! trusts "localhost" as well as the following domains:\n`; defaultTrustedDomains.forEach(d => { content += `// - "${d}"\n`; }); } else { - content += `// By default, VS Code trusts "localhost".\n`; -+ content += `// By default, BradfordCode trusts "localhost".\n`; ++ content += `// By default, !!APP_NAME!! trusts "localhost".\n`; } content += CONFIG_HELP_TEXT_AFTER; @@ -1785,7 +1785,7 @@ index 067580a4..0982e9d6 100644 }, metadata: { - description: localize2('minWelcomeDescription', 'Opens a Walkthrough to help you get started in VS Code.') -+ description: localize2('minWelcomeDescription', 'Opens a Walkthrough to help you get started in BradfordCode.') ++ description: localize2('minWelcomeDescription', 'Opens a Walkthrough to help you get started in !!APP_NAME!!.') } }); } @@ -1794,7 +1794,7 @@ index 067580a4..0982e9d6 100644 'enumDescriptions': [ localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.none' }, "Start without an editor."), - localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePage' }, "Open the Welcome page, with content to aid in getting started with VS Code and extensions."), -+ localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePage' }, "Open the Welcome page, with content to aid in getting started with BradfordCode and extensions."), ++ localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePage' }, "Open the Welcome page, with content to aid in getting started with !!APP_NAME!! and extensions."), localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.readme' }, "Open the README when opening a folder that contains one, fallback to 'welcomePage' otherwise. Note: This is only observed as a global configuration, it will be ignored if set in a workspace or folder configuration."), localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.newUntitledFile' }, "Open a new untitled text file (only applies when opening an empty window)."), localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePageInEmptyWorkbench' }, "Open the Welcome page when opening an empty workbench."), @@ -1807,7 +1807,7 @@ index 297598ef..45282b79 100644 { label: 'onCommand', - description: localize('walkthroughs.steps.completionEvents.onCommand', 'Check off step when a given command is executed anywhere in VS Code.'), -+ description: localize('walkthroughs.steps.completionEvents.onCommand', 'Check off step when a given command is executed anywhere in BradfordCode.'), ++ description: localize('walkthroughs.steps.completionEvents.onCommand', 'Check off step when a given command is executed anywhere in !!APP_NAME!!.'), body: 'onCommand:${1:commandId}' }, { @@ -1873,13 +1873,13 @@ index 168a3d01..fd76dda2 100644 { id: 'Setup', - title: localize('gettingStarted.setup.title', "Get started with VS Code"), -+ title: localize('gettingStarted.setup.title', "Get started with BradfordCode"), ++ title: localize('gettingStarted.setup.title', "Get started with !!APP_NAME!!"), description: localize('gettingStarted.setup.description', "Customize your editor, learn the basics, and start coding"), isFeatured: true, icon: setupIcon, when: '!isWeb', - walkthroughPageTitle: localize('gettingStarted.setup.walkthroughPageTitle', 'Setup VS Code'), -+ walkthroughPageTitle: localize('gettingStarted.setup.walkthroughPageTitle', 'Setup BradfordCode'), ++ walkthroughPageTitle: localize('gettingStarted.setup.walkthroughPageTitle', 'Setup !!APP_NAME!!'), next: 'Beginner', content: { type: 'steps', @@ -1897,8 +1897,8 @@ index 168a3d01..fd76dda2 100644 title: localize('gettingStarted.videoTutorial.title', "Watch video tutorials"), - description: localize('gettingStarted.videoTutorial.description.interpolated', "Watch the first in a series of short & practical video tutorials for VS Code's key features.\n{0}", Button(localize('watch', "Watch Tutorial"), 'https://aka.ms/vscode-getting-started-video')), - media: { type: 'svg', altText: 'VS Code Settings', path: 'learn.svg' }, -+ description: localize('gettingStarted.videoTutorial.description.interpolated', "Watch the first in a series of short & practical video tutorials for BradfordCode's key features.\n{0}", Button(localize('watch', "Watch Tutorial"), 'https://aka.ms/vscode-getting-started-video')), -+ media: { type: 'svg', altText: 'BradfordCode Settings', path: 'learn.svg' }, ++ description: localize('gettingStarted.videoTutorial.description.interpolated', "Watch the first in a series of short & practical video tutorials for !!APP_NAME!!'s key features.\n{0}", Button(localize('watch', "Watch Tutorial"), 'https://aka.ms/vscode-getting-started-video')), ++ media: { type: 'svg', altText: '!!APP_NAME!! Settings', path: 'learn.svg' }, } ] } @@ -1907,14 +1907,14 @@ index 168a3d01..fd76dda2 100644 { id: 'SetupWeb', - title: localize('gettingStarted.setupWeb.title', "Get Started with VS Code for the Web"), -+ title: localize('gettingStarted.setupWeb.title', "Get Started with BradfordCode for the Web"), ++ title: localize('gettingStarted.setupWeb.title', "Get Started with !!APP_NAME!! for the Web"), description: localize('gettingStarted.setupWeb.description', "Customize your editor, learn the basics, and start coding"), isFeatured: true, icon: setupIcon, when: 'isWeb', next: 'Beginner', - walkthroughPageTitle: localize('gettingStarted.setupWeb.walkthroughPageTitle', 'Setup VS Code Web'), -+ walkthroughPageTitle: localize('gettingStarted.setupWeb.walkthroughPageTitle', 'Setup BradfordCode Web'), ++ walkthroughPageTitle: localize('gettingStarted.setupWeb.walkthroughPageTitle', 'Setup !!APP_NAME!! Web'), content: { type: 'steps', steps: [ @@ -1923,11 +1923,11 @@ index 168a3d01..fd76dda2 100644 id: 'extensionsWebWeb', title: localize('gettingStarted.extensions.title', "Code with extensions"), - description: localize('gettingStarted.extensionsWeb.description.interpolated', "Extensions are VS Code's power-ups. A growing number are becoming available in the web.\n{0}", Button(localize('browsePopularWeb', "Browse Popular Web Extensions"), 'command:workbench.extensions.action.showPopularExtensions')), -+ description: localize('gettingStarted.extensionsWeb.description.interpolated', "Extensions are BradfordCode's power-ups. A growing number are becoming available in the web.\n{0}", Button(localize('browsePopularWeb', "Browse Popular Web Extensions"), 'command:workbench.extensions.action.showPopularExtensions')), ++ description: localize('gettingStarted.extensionsWeb.description.interpolated', "Extensions are !!APP_NAME!!'s power-ups. A growing number are becoming available in the web.\n{0}", Button(localize('browsePopularWeb', "Browse Popular Web Extensions"), 'command:workbench.extensions.action.showPopularExtensions')), when: 'workspacePlatform == \'webworker\'', media: { - type: 'svg', altText: 'VS Code extension marketplace with featured language extensions', path: 'extensions-web.svg' -+ type: 'svg', altText: 'BradfordCode extension marketplace with featured language extensions', path: 'extensions-web.svg' ++ type: 'svg', altText: '!!APP_NAME!! extension marketplace with featured language extensions', path: 'extensions-web.svg' }, }, { @@ -1949,14 +1949,14 @@ index 168a3d01..fd76dda2 100644 id: 'commandPaletteTaskWeb', title: localize('gettingStarted.commandPalette.title', "Unlock productivity with the Command Palette "), - description: localize('gettingStarted.commandPalette.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in VS Code.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')), -+ description: localize('gettingStarted.commandPalette.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in BradfordCode.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')), ++ description: localize('gettingStarted.commandPalette.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in !!APP_NAME!!.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')), media: { type: 'svg', altText: 'Command Palette overlay for searching and executing commands.', path: 'commandPalette.svg' }, }, { id: 'pickAFolderTask-WebWeb', title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), - description: localize('gettingStarted.setup.OpenFolderWeb.description.interpolated', "You're all set to start coding. You can open a local project or a remote repository to get your files into VS Code.\n{0}\n{1}", Button(localize('openFolder', "Open Folder"), 'command:workbench.action.addRootFolder'), Button(localize('openRepository', "Open Repository"), 'command:remoteHub.openRepository')), -+ description: localize('gettingStarted.setup.OpenFolderWeb.description.interpolated', "You're all set to start coding. You can open a local project or a remote repository to get your files into BradfordCode.\n{0}\n{1}", Button(localize('openFolder', "Open Folder"), 'command:workbench.action.addRootFolder'), Button(localize('openRepository', "Open Repository"), 'command:remoteHub.openRepository')), ++ description: localize('gettingStarted.setup.OpenFolderWeb.description.interpolated', "You're all set to start coding. You can open a local project or a remote repository to get your files into !!APP_NAME!!.\n{0}\n{1}", Button(localize('openFolder', "Open Folder"), 'command:workbench.action.addRootFolder'), Button(localize('openRepository', "Open Repository"), 'command:remoteHub.openRepository')), when: 'workspaceFolderCount == 0', media: { type: 'svg', altText: 'Explorer view showing buttons for opening folder and cloning repository.', path: 'openFolder.svg' @@ -1965,13 +1965,13 @@ index 168a3d01..fd76dda2 100644 id: 'SetupAccessibility', title: localize('gettingStarted.setupAccessibility.title', "Get Started with Accessibility Features"), - description: localize('gettingStarted.setupAccessibility.description', "Learn the tools and shortcuts that make VS Code accessible. Note that some actions are not actionable from within the context of the walkthrough."), -+ description: localize('gettingStarted.setupAccessibility.description', "Learn the tools and shortcuts that make BradfordCode accessible. Note that some actions are not actionable from within the context of the walkthrough."), ++ description: localize('gettingStarted.setupAccessibility.description', "Learn the tools and shortcuts that make !!APP_NAME!! accessible. Note that some actions are not actionable from within the context of the walkthrough."), isFeatured: true, icon: setupIcon, when: CONTEXT_ACCESSIBILITY_MODE_ENABLED.key, next: 'Setup', - walkthroughPageTitle: localize('gettingStarted.setupAccessibility.walkthroughPageTitle', 'Setup VS Code Accessibility'), -+ walkthroughPageTitle: localize('gettingStarted.setupAccessibility.walkthroughPageTitle', 'Setup BradfordCode Accessibility'), ++ walkthroughPageTitle: localize('gettingStarted.setupAccessibility.walkthroughPageTitle', 'Setup !!APP_NAME!! Accessibility'), content: { type: 'steps', steps: [ @@ -1980,7 +1980,7 @@ index 168a3d01..fd76dda2 100644 id: 'commandPaletteTaskAccessibility', title: localize('gettingStarted.commandPaletteAccessibility.title', "Unlock productivity with the Command Palette "), - description: localize('gettingStarted.commandPaletteAccessibility.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in VS Code.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')), -+ description: localize('gettingStarted.commandPaletteAccessibility.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in BradfordCode.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')), ++ description: localize('gettingStarted.commandPaletteAccessibility.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in !!APP_NAME!!.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')), media: { type: 'markdown', path: 'empty' }, }, { @@ -1989,23 +1989,23 @@ index 168a3d01..fd76dda2 100644 id: 'settingsAndSync', title: localize('gettingStarted.settings.title', "Tune your settings"), - description: localize('gettingStarted.settingsAndSync.description.interpolated', "Customize every aspect of VS Code and [sync](command:workbench.userDataSync.actions.turnOn) customizations across devices.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')), -+ description: localize('gettingStarted.settingsAndSync.description.interpolated', "Customize every aspect of BradfordCode and [sync](command:workbench.userDataSync.actions.turnOn) customizations across devices.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')), ++ description: localize('gettingStarted.settingsAndSync.description.interpolated', "Customize every aspect of !!APP_NAME!! and [sync](command:workbench.userDataSync.actions.turnOn) customizations across devices.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')), when: 'workspacePlatform != \'webworker\' && syncStatus != uninitialized', completionEvents: ['onEvent:sync-enabled'], media: { - type: 'svg', altText: 'VS Code Settings', path: 'settings.svg' -+ type: 'svg', altText: 'BradfordCode Settings', path: 'settings.svg' ++ type: 'svg', altText: '!!APP_NAME!! Settings', path: 'settings.svg' }, }, { id: 'extensions', title: localize('gettingStarted.extensions.title', "Code with extensions"), - description: localize('gettingStarted.extensions.description.interpolated', "Extensions are VS Code's power-ups. They range from handy productivity hacks, expanding out-of-the-box features, to adding completely new capabilities.\n{0}", Button(localize('browsePopular', "Browse Popular Extensions"), 'command:workbench.extensions.action.showPopularExtensions')), -+ description: localize('gettingStarted.extensions.description.interpolated', "Extensions are BradfordCode's power-ups. They range from handy productivity hacks, expanding out-of-the-box features, to adding completely new capabilities.\n{0}", Button(localize('browsePopular', "Browse Popular Extensions"), 'command:workbench.extensions.action.showPopularExtensions')), ++ description: localize('gettingStarted.extensions.description.interpolated', "Extensions are !!APP_NAME!!'s power-ups. They range from handy productivity hacks, expanding out-of-the-box features, to adding completely new capabilities.\n{0}", Button(localize('browsePopular', "Browse Popular Extensions"), 'command:workbench.extensions.action.showPopularExtensions')), when: 'workspacePlatform != \'webworker\'', media: { - type: 'svg', altText: 'VS Code extension marketplace with featured language extensions', path: 'extensions.svg' -+ type: 'svg', altText: 'BradfordCode extension marketplace with featured language extensions', path: 'extensions.svg' ++ type: 'svg', altText: '!!APP_NAME!! extension marketplace with featured language extensions', path: 'extensions.svg' }, }, { @@ -2018,7 +2018,7 @@ index c1fb3e12..4bbf4eb3 100644 this.overlay.setAttribute('role', 'dialog'); this.overlay.setAttribute('aria-modal', 'true'); - this.overlay.setAttribute('aria-label', localize('onboarding.a.aria', "Welcome to Visual Studio Code")); -+ this.overlay.setAttribute('aria-label', localize('onboarding.a.aria', "Welcome to BradfordCode")); ++ this.overlay.setAttribute('aria-label', localize('onboarding.a.aria', "Welcome to !!APP_NAME!!")); // Card this.card = append(this.overlay, $('.onboarding-a-card')); @@ -2031,7 +2031,7 @@ index bdd30bf2..cc397e9c 100644 return ` ## Interactive Editor Playground -The core editor in VS Code is packed with features. This page highlights a number of them and lets you interactively try them out through the use of a number of embedded editors. For full details on the editor features for VS Code and more head over to our [documentation](https://code.visualstudio.com/docs). -+The core editor in BradfordCode is packed with features. This page highlights a number of them and lets you interactively try them out through the use of a number of embedded editors. For full details on the editor features for BradfordCode and more head over to our [documentation](https://code.visualstudio.com/docs). ++The core editor in !!APP_NAME!! is packed with features. This page highlights a number of them and lets you interactively try them out through the use of a number of embedded editors. For full details on the editor features for !!APP_NAME!! and more head over to our [documentation](https://code.visualstudio.com/docs). * [Multi-cursor Editing](#multi-cursor-editing) - block selection, select all occurrences, add additional cursors and more. * [IntelliSense](#intellisense) - get code assistance and parameter suggestions for your code and external modules. @@ -2040,7 +2040,7 @@ index bdd30bf2..cc397e9c 100644 ### IntelliSense -Visual Studio Code comes with the powerful IntelliSense for JavaScript and TypeScript pre-installed. In the below example, position the text cursor right after the dot and press kb(editor.action.triggerSuggest) to invoke IntelliSense. Notice how the suggestions come from the Canvas API. -+BradfordCode comes with the powerful IntelliSense for JavaScript and TypeScript pre-installed. In the below example, position the text cursor right after the dot and press kb(editor.action.triggerSuggest) to invoke IntelliSense. Notice how the suggestions come from the Canvas API. ++!!APP_NAME!! comes with the powerful IntelliSense for JavaScript and TypeScript pre-installed. In the below example, position the text cursor right after the dot and press kb(editor.action.triggerSuggest) to invoke IntelliSense. Notice how the suggestions come from the Canvas API. |||js const canvas = document.querySelector('canvas'); @@ -2049,7 +2049,7 @@ index bdd30bf2..cc397e9c 100644 ||| -> **JSDoc Tip:** VS Code's IntelliSense uses JSDoc comments to provide richer suggestions. The types and documentation from JSDoc comments show up when you hover over a reference to |Book| or in IntelliSense when you create a new instance of |Book|. -+> **JSDoc Tip:** BradfordCode's IntelliSense uses JSDoc comments to provide richer suggestions. The types and documentation from JSDoc comments show up when you hover over a reference to |Book| or in IntelliSense when you create a new instance of |Book|. ++> **JSDoc Tip:** !!APP_NAME!!'s IntelliSense uses JSDoc comments to provide richer suggestions. The types and documentation from JSDoc comments show up when you hover over a reference to |Book| or in IntelliSense when you create a new instance of |Book|. ` : ''} ### Formatting @@ -2058,7 +2058,7 @@ index bdd30bf2..cc397e9c 100644 ## Thanks! -Well if you have got this far then you will have touched on some of the editing features in Visual Studio Code. But don't stop now :) We have lots of additional [documentation](https://code.visualstudio.com/docs), [introductory videos](https://code.visualstudio.com/docs/getstarted/introvideos) and [tips and tricks](https://go.microsoft.com/fwlink/?linkid=852118) for the product that will help you learn how to use it. And while you are here, here are a few additional things you can try: -+Well if you have got this far then you will have touched on some of the editing features in BradfordCode. But don't stop now :) We have lots of additional [documentation](https://code.visualstudio.com/docs), [introductory videos](https://code.visualstudio.com/docs/getstarted/introvideos) and [tips and tricks](https://go.microsoft.com/fwlink/?linkid=852118) for the product that will help you learn how to use it. And while you are here, here are a few additional things you can try: ++Well if you have got this far then you will have touched on some of the editing features in !!APP_NAME!!. But don't stop now :) We have lots of additional [documentation](https://code.visualstudio.com/docs), [introductory videos](https://code.visualstudio.com/docs/getstarted/introvideos) and [tips and tricks](https://go.microsoft.com/fwlink/?linkid=852118) for the product that will help you learn how to use it. And while you are here, here are a few additional things you can try: - Open the Integrated Terminal by pressing kb(workbench.action.terminal.toggleTerminal), then see what's possible by [reviewing the terminal documentation](https://code.visualstudio.com/docs/editor/integrated-terminal) - Work with version control by pressing kb(workbench.view.scm). Understand how to stage, commit, change branches, and view diffs and more by reviewing the [version control documentation](https://code.visualstudio.com/docs/editor/versioncontrol) - Browse thousands of extensions in our integrated gallery by pressing kb(workbench.view.extensions). The [documentation](https://code.visualstudio.com/docs/editor/extension-gallery) will show you how to see the most popular extensions, disable installed ones and more. @@ -2071,7 +2071,7 @@ index 6cccbced..1e404b92 100644 type: 'boolean', default: true, - description: localize('workspace.trust.description', "Controls whether or not Workspace Trust is enabled within VS Code."), -+ description: localize('workspace.trust.description', "Controls whether or not Workspace Trust is enabled within BradfordCode."), ++ description: localize('workspace.trust.description', "Controls whether or not Workspace Trust is enabled within !!APP_NAME!!."), tags: [WORKSPACE_TRUST_SETTING_TAG], scope: ConfigurationScope.APPLICATION, }, @@ -2080,7 +2080,7 @@ index 6cccbced..1e404b92 100644 type: 'boolean', default: true, - markdownDescription: localize('workspace.trust.emptyWindow.description', "Controls whether or not the empty window is trusted by default within VS Code. When used with `#{0}#`, you can enable the full functionality of VS Code without prompting in an empty window.", WORKSPACE_TRUST_UNTRUSTED_FILES), -+ markdownDescription: localize('workspace.trust.emptyWindow.description', "Controls whether or not the empty window is trusted by default within BradfordCode. When used with `#{0}#`, you can enable the full functionality of BradfordCode without prompting in an empty window.", WORKSPACE_TRUST_UNTRUSTED_FILES), ++ markdownDescription: localize('workspace.trust.emptyWindow.description', "Controls whether or not the empty window is trusted by default within !!APP_NAME!!. When used with `#{0}#`, you can enable the full functionality of !!APP_NAME!! without prompting in an empty window.", WORKSPACE_TRUST_UNTRUSTED_FILES), tags: [WORKSPACE_TRUST_SETTING_TAG], scope: ConfigurationScope.APPLICATION } @@ -2093,12 +2093,12 @@ index 9db62d8f..26208123 100644 'disable-chromium-sandbox': { type: 'boolean', - description: localize('argv.disableChromiumSandbox', "Disables the Chromium sandbox. This is useful when running VS Code as elevated on Linux and running under Applocker on Windows.") -+ description: localize('argv.disableChromiumSandbox', "Disables the Chromium sandbox. This is useful when running BradfordCode as elevated on Linux and running under Applocker on Windows.") ++ description: localize('argv.disableChromiumSandbox', "Disables the Chromium sandbox. This is useful when running !!APP_NAME!! as elevated on Linux and running under Applocker on Windows.") }, 'use-inmemory-secretstorage': { type: 'boolean', - description: localize('argv.useInMemorySecretStorage', "Ensures that an in-memory store will be used for secret storage instead of using the OS's credential store. This is often used when running VS Code extension tests or when you're experiencing difficulties with the credential store.") -+ description: localize('argv.useInMemorySecretStorage', "Ensures that an in-memory store will be used for secret storage instead of using the OS's credential store. This is often used when running BradfordCode extension tests or when you're experiencing difficulties with the credential store.") ++ description: localize('argv.useInMemorySecretStorage', "Ensures that an in-memory store will be used for secret storage instead of using the OS's credential store. This is often used when running !!APP_NAME!! extension tests or when you're experiencing difficulties with the credential store.") }, 'remote-debugging-port': { type: 'string', @@ -2111,7 +2111,7 @@ index e60991aa..f7e95209 100644 } - const productName = localize('VS Code for Web', "{0} for the Web", this.productService.nameLong); -+ const productName = localize('BradfordCode for Web', "{0} for the Web", this.productService.nameLong); ++ const productName = localize('!!APP_NAME!! for Web', "{0} for the Web", this.productService.nameLong); const virtualWorkspaceSupport = this.extensionManifestPropertiesService.getExtensionVirtualWorkspaceSupportType(manifest); const virtualWorkspaceSupportReason = getWorkspaceSupportTypeMessage(manifest.capabilities?.virtualWorkspaces); const hasLimitedSupport = virtualWorkspaceSupport === 'limited' || !!virtualWorkspaceSupportReason; @@ -2124,24 +2124,24 @@ index 09dd590e..d3fc08d7 100644 'vscode': { type: 'string', - description: nls.localize('vscode.extension.engines.vscode', 'For VS Code extensions, specifies the VS Code version that the extension is compatible with. Cannot be *. For example: ^1.105.0 indicates compatibility with a minimum VS Code version of 1.105.0.'), -+ description: nls.localize('vscode.extension.engines.vscode', 'For BradfordCode extensions, specifies the BradfordCode version that the extension is compatible with. Cannot be *. For example: ^1.105.0 indicates compatibility with a minimum BradfordCode version of 1.105.0.'), ++ description: nls.localize('vscode.extension.engines.vscode', 'For !!APP_NAME!! extensions, specifies the !!APP_NAME!! version that the extension is compatible with. Cannot be *. For example: ^1.105.0 indicates compatibility with a minimum !!APP_NAME!! version of 1.105.0.'), default: '^1.105.0', } } }, publisher: { - description: nls.localize('vscode.extension.publisher', 'The publisher of the VS Code extension.'), -+ description: nls.localize('vscode.extension.publisher', 'The publisher of the BradfordCode extension.'), ++ description: nls.localize('vscode.extension.publisher', 'The publisher of the !!APP_NAME!! extension.'), type: 'string' }, displayName: { - description: nls.localize('vscode.extension.displayName', 'The display name for the extension used in the VS Code gallery.'), -+ description: nls.localize('vscode.extension.displayName', 'The display name for the extension used in the BradfordCode gallery.'), ++ description: nls.localize('vscode.extension.displayName', 'The display name for the extension used in the !!APP_NAME!! gallery.'), type: 'string' }, categories: { - description: nls.localize('vscode.extension.categories', 'The categories used by the VS Code gallery to categorize the extension.'), -+ description: nls.localize('vscode.extension.categories', 'The categories used by the BradfordCode gallery to categorize the extension.'), ++ description: nls.localize('vscode.extension.categories', 'The categories used by the !!APP_NAME!! gallery to categorize the extension.'), type: 'array', uniqueItems: true, items: { @@ -2150,11 +2150,11 @@ index 09dd590e..d3fc08d7 100644 galleryBanner: { type: 'object', - description: nls.localize('vscode.extension.galleryBanner', 'Banner used in the VS Code marketplace.'), -+ description: nls.localize('vscode.extension.galleryBanner', 'Banner used in the BradfordCode marketplace.'), ++ description: nls.localize('vscode.extension.galleryBanner', 'Banner used in the !!APP_NAME!! marketplace.'), properties: { color: { - description: nls.localize('vscode.extension.galleryBanner.color', 'The banner color on the VS Code marketplace page header.'), -+ description: nls.localize('vscode.extension.galleryBanner.color', 'The banner color on the BradfordCode marketplace page header.'), ++ description: nls.localize('vscode.extension.galleryBanner.color', 'The banner color on the !!APP_NAME!! marketplace page header.'), type: 'string' }, theme: { @@ -2163,7 +2163,7 @@ index 09dd590e..d3fc08d7 100644 }, contributes: { - description: nls.localize('vscode.extension.contributes', 'All contributions of the VS Code extension represented by this package.'), -+ description: nls.localize('vscode.extension.contributes', 'All contributions of the BradfordCode extension represented by this package.'), ++ description: nls.localize('vscode.extension.contributes', 'All contributions of the !!APP_NAME!! extension represented by this package.'), type: 'object', // eslint-disable-next-line local/code-no-any-casts properties: { @@ -2172,7 +2172,7 @@ index 09dd590e..d3fc08d7 100644 }, activationEvents: { - description: nls.localize('vscode.extension.activationEvents', 'Activation events for the VS Code extension.'), -+ description: nls.localize('vscode.extension.activationEvents', 'Activation events for the BradfordCode extension.'), ++ description: nls.localize('vscode.extension.activationEvents', 'Activation events for the !!APP_NAME!! extension.'), type: 'array', items: { type: 'string', @@ -2181,7 +2181,7 @@ index 09dd590e..d3fc08d7 100644 { label: '*', - description: nls.localize('vscode.extension.activationEvents.star', 'An activation event emitted on VS Code startup. To ensure a great end user experience, please use this activation event in your extension only when no other activation events combination works in your use-case.'), -+ description: nls.localize('vscode.extension.activationEvents.star', 'An activation event emitted on BradfordCode startup. To ensure a great end user experience, please use this activation event in your extension only when no other activation events combination works in your use-case.'), ++ description: nls.localize('vscode.extension.activationEvents.star', 'An activation event emitted on !!APP_NAME!! startup. To ensure a great end user experience, please use this activation event in your extension only when no other activation events combination works in your use-case.'), body: '*' } ], @@ -2190,12 +2190,12 @@ index 09dd590e..d3fc08d7 100644 properties: { 'vscode:prepublish': { - description: nls.localize('vscode.extension.scripts.prepublish', 'Script executed before the package is published as a VS Code extension.'), -+ description: nls.localize('vscode.extension.scripts.prepublish', 'Script executed before the package is published as a BradfordCode extension.'), ++ description: nls.localize('vscode.extension.scripts.prepublish', 'Script executed before the package is published as a !!APP_NAME!! extension.'), type: 'string' }, 'vscode:uninstall': { - description: nls.localize('vscode.extension.scripts.uninstall', 'Uninstall hook for VS Code extension. Script that gets executed when the extension is completely uninstalled from VS Code which is when VS Code is restarted (shutdown and start) after the extension is uninstalled. Only Node scripts are supported.'), -+ description: nls.localize('vscode.extension.scripts.uninstall', 'Uninstall hook for BradfordCode extension. Script that gets executed when the extension is completely uninstalled from BradfordCode which is when BradfordCode is restarted (shutdown and start) after the extension is uninstalled. Only Node scripts are supported.'), ++ description: nls.localize('vscode.extension.scripts.uninstall', 'Uninstall hook for !!APP_NAME!! extension. Script that gets executed when the extension is completely uninstalled from !!APP_NAME!! which is when !!APP_NAME!! is restarted (shutdown and start) after the extension is uninstalled. Only Node scripts are supported.'), type: 'string' } } @@ -2208,7 +2208,7 @@ index 4340604b..9ea3a495 100644 nls.localize('extensionService.versionMismatchCrash', "Extension host cannot start: version mismatch."), [{ - label: nls.localize('relaunch', "Relaunch VS Code"), -+ label: nls.localize('relaunch', "Relaunch BradfordCode"), ++ label: nls.localize('relaunch', "Relaunch !!APP_NAME!!"), run: () => { this._instantiationService.invokeFunction((accessor) => { const hostService = accessor.get(IHostService); @@ -2221,7 +2221,7 @@ index 43da4619..3a58c158 100644 if (isRemoteWindow) { const { confirmed } = await this.dialogService.confirm({ - message: reloadMessage ?? localize('reload message', "Switching a profile requires reloading VS Code."), -+ message: reloadMessage ?? localize('reload message', "Switching a profile requires reloading BradfordCode."), ++ message: reloadMessage ?? localize('reload message', "Switching a profile requires reloading !!APP_NAME!!."), primaryButton: localize('reload button', "&&Reload"), }); if (confirmed) { diff --git a/patches/00-build-disable-mangle.patch b/patches/00-build-disable-mangle.patch index 4190445..0296aaa 100644 --- a/patches/00-build-disable-mangle.patch +++ b/patches/00-build-disable-mangle.patch @@ -1,8 +1,10 @@ diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts -index 948c6b4..66ecdd3 100644 +index 32e3e25e..6e9396d2 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts -@@ -131,27 +131,3 @@ export function compileTask(src: string, out: string, build: boolean, options: { +@@ -137,31 +137,7 @@ export function compileTask(src: string, out: string, build: boolean, options: { + generator.execute(); + } - // mangle: TypeScript to TypeScript - let mangleStream = es.through(); @@ -27,6 +29,8 @@ index 948c6b4..66ecdd3 100644 - }); - } - - return srcPipe + const emit = util.streamToPromise(srcPipe - .pipe(mangleStream) .pipe(generator.stream) + .pipe(compile()) + .pipe(gulp.dest(out))); diff --git a/patches/00-copilot-fix-action-condition.patch b/patches/00-copilot-fix-action-condition.patch index bfa0b75..11a3d54 100644 --- a/patches/00-copilot-fix-action-condition.patch +++ b/patches/00-copilot-fix-action-condition.patch @@ -1,8 +1,8 @@ diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts -index dd222258..1c1e7201 100644 +index 7f7d1479..87b739be 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts -@@ -1166,7 +1166,7 @@ export function registerChatActions() { +@@ -1202,7 +1202,7 @@ export function registerChatActions() { id: 'workbench.action.chat.configureCodeCompletions', title: localize2('configureCompletions', "Configure Inline Suggestions..."), precondition: ContextKeyExpr.and( @@ -12,7 +12,7 @@ index dd222258..1c1e7201 100644 ChatContextKeys.Setup.untrusted.negate() ), diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts -index c7020b52..bb69de4f 100644 +index 00c445ab..f1e87408 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -317,7 +317,8 @@ class AttachSelectionToChatAction extends Action2 { @@ -26,9 +26,10 @@ index c7020b52..bb69de4f 100644 }, { id: MenuId.InlineChatEditorAffordance, diff --git a/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts +index 3fcf5140..82d4066f 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts -@@ -1672,7 +1672,7 @@ configurationRegistry.registerConfiguration({ +@@ -1757,7 +1757,7 @@ configurationRegistry.registerConfiguration({ [ChatConfiguration.AIDisabled]: { type: 'boolean', description: nls.localize('chat.disableAIFeatures', "Disable and hide built-in AI features provided by GitHub Copilot, including chat and inline suggestions."), @@ -50,10 +51,10 @@ index 8fb472b3..16cff2b1 100644 ChatContextKeys.Setup.disabledInWorkspace.negate(), ), diff --git a/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupContributions.ts b/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupContributions.ts -index 34449705..86277e78 100644 +index ac91175c..8fd319a7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupContributions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSetup/chatSetupContributions.ts -@@ -229,12 +229,13 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr +@@ -230,12 +230,13 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr title: ChatSetupTriggerAction.CHAT_SETUP_ACTION_LABEL, category: CHAT_CATEGORY, f1: true, @@ -73,7 +74,7 @@ index 34449705..86277e78 100644 ) }); } -@@ -367,6 +368,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr +@@ -368,6 +369,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr id: MenuId.AccountsContext, group: '2_copilot', when: ContextKeyExpr.and( @@ -81,14 +82,15 @@ index 34449705..86277e78 100644 ChatContextKeys.Setup.hidden.negate(), ChatContextKeys.Setup.disabledInWorkspace.negate(), ChatContextKeys.Setup.completed.negate(), -@@ -399,5 +401,6 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr +@@ -400,6 +402,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr id: MenuId.TitleBarAdjacentCenter, order: 0, // same position as the update button when: ContextKeyExpr.and( + ContextKeyExpr.has('config.chat.disableAIFeatures').negate(), IsWebContext.negate(), ChatContextKeys.Entitlement.signedOut, -@@ -430,6 +433,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr + ChatEntitlementContextKeys.hasByokModels.negate(), +@@ -449,6 +452,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr category: localize2('chat.category', 'Chat'), f1: true, precondition: ContextKeyExpr.and( @@ -96,7 +98,7 @@ index 34449705..86277e78 100644 ChatContextKeys.Setup.hidden.negate(), ChatContextKeys.Setup.disabledInWorkspace.negate(), ContextKeyExpr.or( -@@ -489,6 +493,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr +@@ -514,6 +518,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr category: localize2('chat.category', 'Chat'), f1: true, precondition: ContextKeyExpr.and( @@ -104,15 +106,15 @@ index 34449705..86277e78 100644 ChatContextKeys.Setup.hidden.negate(), ChatContextKeys.Setup.disabledInWorkspace.negate(), ContextKeyExpr.or( -@@ -585,6 +590,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr - registerGenerateCodeCommand('chat.internal.codeReview.run', 'github.copilot.chat.codeReview.run'); +@@ -608,6 +613,7 @@ export class ChatSetupContribution extends Disposable implements IWorkbenchContr + registerGenerateCodeCommand('chat.internal.review', 'github.copilot.chat.review'); const internalGenerateCodeContext = ContextKeyExpr.and( + ContextKeyExpr.has('config.chat.disableAIFeatures').negate(), ChatContextKeys.Setup.hidden.negate(), ChatContextKeys.Setup.disabledInWorkspace.negate(), ChatContextKeys.Setup.completed.negate(), -@@ -813,7 +819,11 @@ export class ChatTeardownContribution extends Disposable implements IWorkbenchCo +@@ -873,7 +879,11 @@ export class ChatTeardownContribution extends Disposable implements IWorkbenchCo title: ChatSetupHideAction.TITLE, f1: true, category: CHAT_CATEGORY, @@ -140,7 +142,7 @@ index 7e5d8f63..e949b389 100644 export class StartSessionAction extends Action2 { diff --git a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts -index c47eeb39..dcc00326 100644 +index c983379d..bc16fdc5 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts @@ -543,7 +543,7 @@ export class McpServersViewsContribution extends Disposable implements IWorkbenc @@ -199,10 +201,10 @@ index 6ee2a32c..c5cdc792 100644 ChatContextKeys.Setup.disabledInWorkspace.negate(), ChatContextKeys.Setup.completed.negate(), diff --git a/src/vs/workbench/contrib/scm/browser/scmInput.ts b/src/vs/workbench/contrib/scm/browser/scmInput.ts -index 6adf03c6..38a6d42c 100644 +index 63642985..5dee8dca 100644 --- a/src/vs/workbench/contrib/scm/browser/scmInput.ts +++ b/src/vs/workbench/contrib/scm/browser/scmInput.ts -@@ -846,6 +846,7 @@ registerAction2(class extends Action2 { +@@ -847,6 +847,7 @@ registerAction2(class extends Action2 { menu: { id: MenuId.SCMInputBox, when: ContextKeyExpr.and( diff --git a/patches/20-keymap-use-custom-lib.patch b/patches/20-keymap-use-custom-lib.patch index ecd1331..bd3adeb 100644 --- a/patches/20-keymap-use-custom-lib.patch +++ b/patches/20-keymap-use-custom-lib.patch @@ -1,5 +1,5 @@ diff --git a/.npmrc b/.npmrc -index 8c21e58ef14..25a93f408ad 100644 +index 8c21e58e..25a93f40 100644 --- a/.npmrc +++ b/.npmrc @@ -4,5 +4,6 @@ ms_build_id="14159160" @@ -10,7 +10,7 @@ index 8c21e58ef14..25a93f408ad 100644 legacy-peer-deps="true" timeout=180000 diff --git a/build/.moduleignore b/build/.moduleignore -index 90780acffa9..b226373f632 100644 +index 8eb8da15..da57545b 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -63,11 +63,11 @@ fsevents/test/** @@ -31,10 +31,10 @@ index 90780acffa9..b226373f632 100644 native-is-elevated/binding.gyp native-is-elevated/build/** diff --git a/eslint.config.js b/eslint.config.js -index fda37713fee..a2150a119a6 100644 +index 51b085ee..938419e2 100644 --- a/eslint.config.js +++ b/eslint.config.js -@@ -1530,7 +1530,7 @@ export default defineConfig( +@@ -1561,7 +1561,7 @@ export default defineConfig( 'inspector', 'minimist', 'node:module', @@ -44,7 +44,7 @@ index fda37713fee..a2150a119a6 100644 'node-pty', 'os', diff --git a/package-lock.json b/package-lock.json -index 38d50ffa4de..cad1ae14f2c 100644 +index 81d315f0..1d99e154 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ @@ -52,10 +52,10 @@ index 38d50ffa4de..cad1ae14f2c 100644 "@vscode/windows-process-tree": "^0.7.0", "@vscode/windows-registry": "^1.2.0", + "@vscodium/native-keymap": "3.3.7-258424", - "@xterm/addon-clipboard": "^0.3.0-beta.220", - "@xterm/addon-image": "^0.10.0-beta.220", - "@xterm/addon-ligatures": "^0.11.0-beta.220", -@@ -59,7 +60,6 @@ + "@xterm/addon-clipboard": "^0.3.0-beta.285", + "@xterm/addon-image": "^0.10.0-beta.285", + "@xterm/addon-ligatures": "^0.11.0-beta.285", +@@ -60,7 +61,6 @@ "kerberos": "2.1.1", "minimist": "^1.2.8", "native-is-elevated": "0.9.0", @@ -63,7 +63,7 @@ index 38d50ffa4de..cad1ae14f2c 100644 "node-addon-api": "^6.0.0", "node-pty": "^1.2.0-beta.13", "open": "^10.1.2", -@@ -4466,6 +4466,13 @@ +@@ -4496,6 +4496,13 @@ "hasInstallScript": true, "license": "MIT" }, @@ -77,7 +77,7 @@ index 38d50ffa4de..cad1ae14f2c 100644 "node_modules/@webgpu/types": { "version": "0.1.66", "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.66.tgz", -@@ -14114,9 +14121,10 @@ +@@ -14157,9 +14164,10 @@ } }, "node_modules/napi-build-utils": { @@ -91,7 +91,7 @@ index 38d50ffa4de..cad1ae14f2c 100644 }, "node_modules/native-is-elevated": { "version": "0.9.0", -@@ -14125,13 +14133,6 @@ +@@ -14168,13 +14176,6 @@ "hasInstallScript": true, "license": "MIT" }, @@ -105,7 +105,7 @@ index 38d50ffa4de..cad1ae14f2c 100644 "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", -@@ -15526,16 +15527,17 @@ +@@ -15580,16 +15581,17 @@ "license": "ISC" }, "node_modules/prebuild-install": { @@ -128,10 +128,10 @@ index 38d50ffa4de..cad1ae14f2c 100644 "pump": "^3.0.0", "rc": "^1.2.7", diff --git a/package.json b/package.json -index a43d079633a..508acdfb4dd 100644 +index 36485330..2a5830cd 100644 --- a/package.json +++ b/package.json -@@ -141,7 +141,7 @@ +@@ -144,7 +144,7 @@ "kerberos": "2.1.1", "minimist": "^1.2.8", "native-is-elevated": "0.9.0", @@ -141,7 +141,7 @@ index a43d079633a..508acdfb4dd 100644 "node-pty": "^1.2.0-beta.13", "open": "^10.1.2", diff --git a/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts b/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts -index 9b11a712c25..975c312747a 100644 +index 9b11a712..975c3127 100644 --- a/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts +++ b/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts @@ -42,12 +42,12 @@ flakySuite('Native Modules (all platforms)', () => { @@ -162,7 +162,7 @@ index 9b11a712c25..975c312747a 100644 test('@vscode/native-watchdog', async () => { diff --git a/src/vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService.ts b/src/vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService.ts -index 8950ce2184a..f31cea623bc 100644 +index 8950ce21..f31cea62 100644 --- a/src/vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService.ts +++ b/src/vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService.ts @@ -3,7 +3,7 @@ diff --git a/patches/21-policy-use-custom-lib.patch b/patches/21-policy-use-custom-lib.patch index 232590b..71381db 100644 --- a/patches/21-policy-use-custom-lib.patch +++ b/patches/21-policy-use-custom-lib.patch @@ -1,5 +1,5 @@ diff --git a/build/.moduleignore b/build/.moduleignore -index b226373f..7750bbf0 100644 +index da57545b..5056155d 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -151,13 +151,15 @@ vsda/** @@ -39,10 +39,10 @@ index 7f650ba7..3e3521fd 100644 ` `, ` `, diff --git a/build/lib/policies/render.ts b/build/lib/policies/render.ts -index 47b485d1..c314d031 100644 +index bd5958cb..b45f99b9 100644 --- a/build/lib/policies/render.ts +++ b/build/lib/policies/render.ts -@@ -47,7 +47,7 @@ export function renderADMX(regKey: string, versions: string[], categories: Categ +@@ -54,7 +54,7 @@ export function renderADMX(regKey: string, versions: string[], categories: Categ return ` @@ -51,7 +51,7 @@ index 47b485d1..c314d031 100644 -@@ -165,7 +165,7 @@ export function renderProfileManifest(appName: string, bundleIdentifier: string, +@@ -172,7 +172,7 @@ export function renderProfileManifest(appName: string, bundleIdentifier: string, pfm_default @@ -60,7 +60,7 @@ index 47b485d1..c314d031 100644 pfm_name PayloadOrganization pfm_title -@@ -183,11 +183,11 @@ export function renderProfileManifest(appName: string, bundleIdentifier: string, +@@ -190,11 +190,11 @@ export function renderProfileManifest(appName: string, bundleIdentifier: string, pfm_app_url @@ -74,7 +74,7 @@ index 47b485d1..c314d031 100644 pfm_domain ${bundleIdentifier} pfm_format_version -@@ -253,13 +253,13 @@ ${policyEntries} +@@ -260,13 +260,13 @@ ${policyEntries} PayloadDescription @@ -91,10 +91,10 @@ index 47b485d1..c314d031 100644 Configuration PayloadUUID diff --git a/eslint.config.js b/eslint.config.js -index a6dd3274..96b0db3b 100644 +index 938419e2..f3a103ca 100644 --- a/eslint.config.js +++ b/eslint.config.js -@@ -1512,7 +1512,7 @@ export default defineConfig( +@@ -1542,7 +1542,7 @@ export default defineConfig( '@vscode/ripgrep-universal', '@vscode/iconv-lite-umd', '@vscode/native-watchdog', @@ -104,33 +104,35 @@ index a6dd3274..96b0db3b 100644 '@vscode/spdlog', '@vscode/windows-process-tree', diff --git a/package-lock.json b/package-lock.json -index ab7a6f15..3a191a0f 100644 +index 1d99e154..4acb64a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,6 @@ "@vscode/diff": "0.0.2-7", "@vscode/iconv-lite-umd": "0.7.1", "@vscode/native-watchdog": "^1.4.6", -- "@vscode/policy-watcher": "^1.3.2", +- "@vscode/policy-watcher": "^1.4.0", "@vscode/proxy-agent": "^0.42.0", "@vscode/ripgrep-universal": "^1.18.0", "@vscode/sandbox-runtime": "0.0.1", -@@ -42,6 +41,7 @@ - "@vscode/windows-process-tree": "^0.7.0", - "@vscode/windows-registry": "^1.2.0", - "@vscodium/native-keymap": "3.3.7-258424", -+ "@vscodium/policy-watcher": "^1.3.2-252465", - "@xterm/addon-clipboard": "^0.3.0-beta.220", - "@xterm/addon-image": "^0.10.0-beta.220", - "@xterm/addon-ligatures": "^0.11.0-beta.220", -@@ -4103,26 +4103,6 @@ +@@ -74,7 +73,8 @@ + "ws": "^8.19.0", + "yauzl": "^3.3.1", + "yazl": "^2.4.3", +- "zod": "^3.25.76" ++ "zod": "^3.25.76", ++ "@vscodium/policy-watcher": "^1.3.2-252465" + }, + "devDependencies": { + "@anthropic-ai/claude-agent-sdk": "0.3.169", +@@ -4120,26 +4120,6 @@ "hasInstallScript": true, "license": "MIT" }, - "node_modules/@vscode/policy-watcher": { -- "version": "1.3.7", -- "resolved": "https://registry.npmjs.org/@vscode/policy-watcher/-/policy-watcher-1.3.7.tgz", -- "integrity": "sha512-OvIczTbtGLZs7YU0ResbjM0KEB2ORBnlJ4ICxaB9fKHNVBwNVp4i2qIkDQGp3UBGtu7P8/+eg4/ZKk2oJGFcug==", +- "version": "1.4.0", +- "resolved": "https://registry.npmjs.org/@vscode/policy-watcher/-/policy-watcher-1.4.0.tgz", +- "integrity": "sha512-QKTLV/UtV0HH5AJELfN5D3Jcxj2hB9CYT9GtG334I8bU5TdusaSGYSpDjumZBGWO2YxwVYpBNytA7mq/I3ZtzA==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { @@ -150,7 +152,7 @@ index ab7a6f15..3a191a0f 100644 "node_modules/@vscode/proxy-agent": { "version": "0.42.0", "resolved": "https://registry.npmjs.org/@vscode/proxy-agent/-/proxy-agent-0.42.0.tgz", -@@ -4459,6 +4439,46 @@ +@@ -4503,6 +4483,26 @@ "hasInstallScript": true, "license": "MIT" }, @@ -168,26 +170,6 @@ index ab7a6f15..3a191a0f 100644 + "node_modules/@vscodium/policy-watcher/node_modules/node-addon-api": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.6.0.tgz", -+ "integrity": "sha512-uH63yYpyBQOzHTSDcN+yYRsetH8U4hd6Bb+kZTZRRzAGiWcUOMdsWlqkOICUqWy/zUWxlDmpywXmH4M0fNu5SQ==", -+ "license": "MIT", -+ "engines": { -+ "node": "^18 || ^20 || >= 21" -+ } -+ }, -+ "node_modules/@vscodium/policy-watcher": { -+ "version": "1.3.2-255408", -+ "resolved": "https://registry.npmjs.org/@vscodium/policy-watcher/-/policy-watcher-1.3.2-255408.tgz", -+ "integrity": "sha512-0KERmB+VkSz9hvFWEDGalCpxQ9+qjLaUazXMBkzWQ9SjKPaD6zU9u6wA4/OUu816JnvCFEeJYEe9WcDZPnKQ1w==", -+ "hasInstallScript": true, -+ "license": "MIT", -+ "dependencies": { -+ "bindings": "^1.5.0", -+ "node-addon-api": "^8.2.0" -+ } -+ }, -+ "node_modules/@vscodium/policy-watcher/node_modules/node-addon-api": { -+ "version": "8.6.0", -+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.6.0.tgz", + "integrity": "sha512-gBVjCaqDlRUk0EwoPNKzIr9KkS9041G/q31IBShPs1Xz6UTA+EXdZADbzqAJQrpDRq71CIMnOP5VMut3SL0z5Q==", + "license": "MIT", + "engines": { @@ -198,14 +180,14 @@ index ab7a6f15..3a191a0f 100644 "version": "0.1.66", "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.66.tgz", diff --git a/package.json b/package.json -index 433f446d..942259cc 100644 +index 2a5830cd..9a425766 100644 --- a/package.json +++ b/package.json -@@ -108,7 +108,7 @@ +@@ -113,7 +113,7 @@ "@vscode/diff": "0.0.2-7", "@vscode/iconv-lite-umd": "0.7.1", "@vscode/native-watchdog": "^1.4.6", -- "@vscode/policy-watcher": "^1.3.2", +- "@vscode/policy-watcher": "^1.4.0", + "@vscodium/policy-watcher": "^1.3.2-252465", "@vscode/proxy-agent": "^0.42.0", "@vscode/ripgrep-universal": "^1.18.0", From 2f68075f14e27ff82d214398b51c93b9516cbbe3 Mon Sep 17 00:00:00 2001 From: sbs44 <83440025+sbs44@users.noreply.github.com> Date: Sat, 27 Jun 2026 16:16:42 -0400 Subject: [PATCH 3/5] fix(patches): re-engineer 51-ext-copilot-remove for VS Code 1.126.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch 50 (gulp tasks): single context-line drift — upstream inserted `getCopilotTgrepExcludeFilter` into the copilot import; corrected the trailing context line so the hunk applies. Patch 51 (copilot removal): fully re-authored against 1.126's substantially expanded Copilot/agent-host subsystem. The old patch (written for ~1.124) was hopelessly stale — 7 of its 8 target deletions no longer matched, and 19+ new SDK-importing files existed that it never touched. Approach (Copilot-only removal; Claude + agentHost + sessions kept, all MIT): - Removed @github/copilot + @github/copilot-sdk from package.json and all 11 node_modules/@github/copilot* blocks from package-lock.json (proprietary Copilot binaries can't be redistributed in a signed .dmg). - Deleted 15 Copilot-CLI-provider source files + 9 provider-specific tests. - Kept shared agent infra (copilotToolDisplay, mapSessionEvents, sessionCustomizationDiscovery, both otel services, copilotTestEvents fixture) and stripped their @github/copilot-sdk *type* imports, replacing them with local type defs / `any` aliases so the kept Claude provider + agentService tests keep compiling. - Unregistered the Copilot provider in agentHostMain.ts + agentHostServerMain.ts. - Reworked build wiring: gulpfile.vscode.ts + gulpfile.reh.ts (drop copilot import, compileCopilotExtensionBuildTask, copilotRuntimePrebuilds + copilot/ ripgrep filters, prepareCopilotRipgrepShim task + call), build/npm/dirs.ts (drop extensions/copilot), build/npm/postinstall.ts (drop copilot-sdk patch). Verified: all 36 patches apply cleanly in CI order; zero remaining @github/copilot import statements and zero imports of deleted files across src/ + build/. Refs #21 --- patches/50-build-improve-gulp-tasks.patch | 2 +- patches/51-ext-copilot-remove-it.patch | 8398 ++++++++++++++++----- 2 files changed, 6472 insertions(+), 1928 deletions(-) diff --git a/patches/50-build-improve-gulp-tasks.patch b/patches/50-build-improve-gulp-tasks.patch index af7dc8d..4806970 100644 --- a/patches/50-build-improve-gulp-tasks.patch +++ b/patches/50-build-improve-gulp-tasks.patch @@ -10,7 +10,7 @@ index 88c66de6..386bbb0b 100644 +import { compileBuildWithoutManglingTask } from './gulpfile.compile.ts'; import { compileNonNativeExtensionsBuildTask, compileNativeExtensionsBuildTask, compileAllExtensionsBuildTask, compileExtensionMediaBuildTask, cleanExtensionsBuildTask, compileCopilotExtensionBuildTask } from './gulpfile.extensions.ts'; import { copyCodiconsTask } from './lib/compilation.ts'; - import { getCopilotExcludeFilter, getCopilotRuntimePrebuildFiles, getRipgrepExcludeFilter, prepareBuiltInCopilotRipgrepShim } from './lib/copilot.ts'; + import { getCopilotExcludeFilter, getCopilotRuntimePrebuildFiles, getCopilotTgrepExcludeFilter, getRipgrepExcludeFilter, prepareBuiltInCopilotRipgrepShim } from './lib/copilot.ts'; @@ -655,26 +655,46 @@ BUILD_TARGETS.forEach(buildTarget => { minified && useCdnSourceMapsForPackagingTasks ? `${sourceMappingURLBase}/core` : undefined ) diff --git a/patches/51-ext-copilot-remove-it.patch b/patches/51-ext-copilot-remove-it.patch index 45ca345..5bb92f3 100644 --- a/patches/51-ext-copilot-remove-it.patch +++ b/patches/51-ext-copilot-remove-it.patch @@ -1,5 +1,5 @@ diff --git a/build/gulpfile.reh.ts b/build/gulpfile.reh.ts -index beb7ba848fd..3b0be1b7692 100644 +index 62c30da5..cac6a2e4 100644 --- a/build/gulpfile.reh.ts +++ b/build/gulpfile.reh.ts @@ -23,13 +23,12 @@ import glob from 'glob'; @@ -13,23 +13,24 @@ index beb7ba848fd..3b0be1b7692 100644 import log from 'fancy-log'; import buildfile from './buildfile.ts'; import { fetchUrls, fetchGithub } from './lib/fetch.ts'; --import { getCopilotExcludeFilter, getCopilotRuntimePrebuildFiles, getRipgrepExcludeFilter, prepareBuiltInCopilotRipgrepShim } from './lib/copilot.ts'; +-import { getCopilotExcludeFilter, getCopilotRuntimePrebuildFiles, getCopilotTgrepExcludeFilter, getRipgrepExcludeFilter, prepareBuiltInCopilotRipgrepShim } from './lib/copilot.ts'; + import { readAgentSdkResults } from './agent-sdk/common.ts'; - const rcedit = promisify(rceditCallback); -@@ -379,10 +378,7 @@ function packageTask(type: string, platform: string, arch: string, sourceFolderN +@@ -395,11 +394,7 @@ function packageTask(type: string, platform: string, arch: string, sourceFolderN .pipe(filter(['**', '!**/package-lock.json', '!**/*.{js,css}.map'])) .pipe(util.cleanNodeModules(path.join(import.meta.dirname, '.moduleignore'))) .pipe(util.cleanNodeModules(path.join(import.meta.dirname, `.moduleignore.${process.platform}`))); - const copilotRuntimePrebuilds = gulp.src(getCopilotRuntimePrebuildFiles(platform, arch, 'remote/node_modules'), { base: 'remote', dot: true, allowEmpty: true }); - const deps = es.merge(cleanedDeps, copilotRuntimePrebuilds) - .pipe(filter(getCopilotExcludeFilter(platform, arch))) +- .pipe(filter(getCopilotTgrepExcludeFilter(platform, arch))) - .pipe(filter(getRipgrepExcludeFilter(platform, arch))) + const deps = cleanedDeps .pipe(jsFilter) .pipe(util.stripSourceMappingURL()) .pipe(jsFilter.restore); -@@ -535,16 +531,6 @@ function patchWin32DependenciesTask(destinationFolderName: string) { +@@ -553,16 +548,6 @@ function patchWin32DependenciesTask(destinationFolderName: string) { }; } @@ -46,7 +47,7 @@ index beb7ba848fd..3b0be1b7692 100644 /** * @param product The parsed product.json file contents */ -@@ -594,7 +580,6 @@ function tweakProductForServerWeb(product: typeof import('../product.json')) { +@@ -612,7 +597,6 @@ function tweakProductForServerWeb(product: typeof import('../product.json')) { task.task(`node-${platform}-${arch}`) as task.Task, util.rimraf(path.join(BUILD_ROOT, destinationFolderName)), packageTask(type, platform, arch, sourceFolderName, destinationFolderName), @@ -54,7 +55,7 @@ index beb7ba848fd..3b0be1b7692 100644 ]; if (platform === 'win32') { -@@ -608,7 +593,6 @@ function tweakProductForServerWeb(product: typeof import('../product.json')) { +@@ -626,7 +610,6 @@ function tweakProductForServerWeb(product: typeof import('../product.json')) { compileBuildWithManglingTask, cleanExtensionsBuildTask, compileNonNativeExtensionsBuildTask, @@ -63,7 +64,7 @@ index beb7ba848fd..3b0be1b7692 100644 minified ? minifyTask : bundleTask, serverTaskCI diff --git a/build/gulpfile.vscode.ts b/build/gulpfile.vscode.ts -index 190ba87eb51..8476c4568fd 100644 +index db372863..c5af9fda 100644 --- a/build/gulpfile.vscode.ts +++ b/build/gulpfile.vscode.ts @@ -26,9 +26,8 @@ import { config } from './lib/electron.ts'; @@ -73,23 +74,24 @@ index 190ba87eb51..8476c4568fd 100644 -import { compileNonNativeExtensionsBuildTask, compileNativeExtensionsBuildTask, compileAllExtensionsBuildTask, compileExtensionMediaBuildTask, cleanExtensionsBuildTask, compileCopilotExtensionBuildTask } from './gulpfile.extensions.ts'; +import { compileNonNativeExtensionsBuildTask, compileNativeExtensionsBuildTask, compileAllExtensionsBuildTask, compileExtensionMediaBuildTask, cleanExtensionsBuildTask } from './gulpfile.extensions.ts'; import { copyCodiconsTask } from './lib/compilation.ts'; --import { getCopilotExcludeFilter, getCopilotRuntimePrebuildFiles, getRipgrepExcludeFilter, prepareBuiltInCopilotRipgrepShim } from './lib/copilot.ts'; +-import { getCopilotExcludeFilter, getCopilotRuntimePrebuildFiles, getCopilotTgrepExcludeFilter, getRipgrepExcludeFilter, prepareBuiltInCopilotRipgrepShim } from './lib/copilot.ts'; + import { readAgentSdkResults } from './agent-sdk/common.ts'; import { useEsbuildTranspile } from './buildConfig.ts'; import { promisify } from 'util'; - import globCallback from 'glob'; -@@ -332,10 +331,7 @@ function packageTask(platform: string, arch: string, sourceFolderName: string, d +@@ -340,11 +339,7 @@ function packageTask(platform: string, arch: string, sourceFolderName: string, d .pipe(filter(depFilterPattern)) .pipe(util.cleanNodeModules(path.join(import.meta.dirname, '.moduleignore'))) .pipe(util.cleanNodeModules(path.join(import.meta.dirname, `.moduleignore.${process.platform}`))); - const copilotRuntimePrebuilds = gulp.src(getCopilotRuntimePrebuildFiles(platform, arch), { base: '.', dot: true, allowEmpty: true }); - const deps = es.merge(cleanedDeps, copilotRuntimePrebuilds) - .pipe(filter(getCopilotExcludeFilter(platform, arch))) +- .pipe(filter(getCopilotTgrepExcludeFilter(platform, arch))) - .pipe(filter(getRipgrepExcludeFilter(platform, arch))) + const deps = cleanedDeps .pipe(jsFilter) .pipe(util.rewriteSourceMappingURL(sourceMappingURLBase)) .pipe(jsFilter.restore) -@@ -591,23 +587,6 @@ function patchWin32DependenciesTask(destinationFolderName: string) { +@@ -601,23 +596,6 @@ function patchWin32DependenciesTask(destinationFolderName: string) { }; } @@ -113,7 +115,7 @@ index 190ba87eb51..8476c4568fd 100644 const buildRoot = path.dirname(root); const BUILD_TARGETS = [ -@@ -633,7 +612,6 @@ BUILD_TARGETS.forEach(buildTarget => { +@@ -643,7 +621,6 @@ BUILD_TARGETS.forEach(buildTarget => { compileNativeExtensionsBuildTask, util.rimraf(path.join(buildRoot, destinationFolderName)), packageTask(platform, arch, sourceFolderName, destinationFolderName, opts), @@ -121,7 +123,7 @@ index 190ba87eb51..8476c4568fd 100644 ]; if (platform === 'win32') { -@@ -659,7 +637,6 @@ BUILD_TARGETS.forEach(buildTarget => { +@@ -669,7 +646,6 @@ BUILD_TARGETS.forEach(buildTarget => { copyCodiconsTask, cleanExtensionsBuildTask, compileNonNativeExtensionsBuildTask, @@ -129,7 +131,7 @@ index 190ba87eb51..8476c4568fd 100644 compileExtensionMediaBuildTask, )); task.task(prepackTask); -@@ -680,7 +657,6 @@ BUILD_TARGETS.forEach(buildTarget => { +@@ -690,7 +666,6 @@ BUILD_TARGETS.forEach(buildTarget => { compileBuildWithoutManglingTask, cleanExtensionsBuildTask, compileNonNativeExtensionsBuildTask, @@ -138,7 +140,7 @@ index 190ba87eb51..8476c4568fd 100644 minified ? minifyVSCodeTask : bundleVSCodeTask, )); diff --git a/build/npm/dirs.ts b/build/npm/dirs.ts -index 289a469754a..8c6df6f7ede 100644 +index 289a4697..8c6df6f7 100644 --- a/build/npm/dirs.ts +++ b/build/npm/dirs.ts @@ -15,7 +15,6 @@ export const dirs = [ @@ -150,29 +152,13 @@ index 289a469754a..8c6df6f7ede 100644 'extensions/css-language-features/server', 'extensions/debug-auto-launch', diff --git a/build/npm/postinstall.ts b/build/npm/postinstall.ts -index 0d00ac39261..23e23c051a7 100644 +index 0d00ac39..7e2ef16c 100644 --- a/build/npm/postinstall.ts +++ b/build/npm/postinstall.ts -@@ -318,37 +318,6 @@ async function main() { - - fs.writeFileSync(stateFile, JSON.stringify(_state)); - fs.writeFileSync(stateContentsFile, JSON.stringify(computeContents())); -- -- // Symlink .claude/ files to their canonical locations to test Claude agent harness -- const claudeDir = path.join(root, '.claude'); -- fs.mkdirSync(claudeDir, { recursive: true }); -- -- const claudeMdLink = path.join(claudeDir, 'CLAUDE.md'); -- const claudeMdLinkType = ensureAgentHarnessLink(path.join('..', '.github', 'copilot-instructions.md'), claudeMdLink); -- if (claudeMdLinkType !== 'existing') { -- log('.', `Created ${claudeMdLinkType} .claude/CLAUDE.md -> .github/copilot-instructions.md`); -- } -- -- const claudeSkillsLink = path.join(claudeDir, 'skills'); -- const claudeSkillsLinkType = ensureAgentHarnessLink(path.join('..', '.agents', 'skills'), claudeSkillsLink); -- if (claudeSkillsLinkType !== 'existing') { -- log('.', `Created ${claudeSkillsLinkType} .claude/skills -> .agents/skills`); -- } +@@ -334,21 +334,6 @@ async function main() { + if (claudeSkillsLinkType !== 'existing') { + log('.', `Created ${claudeSkillsLinkType} .claude/skills -> .agents/skills`); + } - - // Temporary: patch @github/copilot-sdk session.js to fix ESM import - // (missing .js extension on vscode-jsonrpc/node). Fixed upstream in v0.1.32. @@ -192,48 +178,49 @@ index 0d00ac39261..23e23c051a7 100644 main().catch(err => { diff --git a/package-lock.json b/package-lock.json -index 8ffdf12aa94..a4ae22d6b31 100644 +index 4acb64a7..2ec27af6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,6 @@ "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "^0.82.0", -- "@github/copilot": "^1.0.57", -- "@github/copilot-sdk": "^1.0.0", +- "@github/copilot": "^1.0.64-0", +- "@github/copilot-sdk": "^1.0.2", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", "@microsoft/dev-tunnels-connections": "^1.3.41", -@@ -1106,179 +1104,6 @@ +@@ -1084,192 +1082,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@github/copilot": { -- "version": "1.0.57", -- "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.57.tgz", -- "integrity": "sha512-7dpOu9/qiodmFohZVpTxYmTcjbcXfstWeHof0Ka5RkhguKMkbS3c+sW23a7TTjtlViTV73z+IZFfFW1ru621kw==", +- "version": "1.0.64-0", +- "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.64-0.tgz", +- "integrity": "sha512-PlH7ByBHjmPLqLXS4CE2q8hN6CFEfkCMV6ScBEzW/u73+KYQB4fGNouo8Lr8okL6D5CW5rzPJbsXyISyJqVOZg==", - "license": "SEE LICENSE IN LICENSE.md", - "dependencies": { -- "detect-libc": "^2.1.2" +- "detect-libc": "^2.1.2", +- "os-theme": "^0.0.8" - }, - "bin": { - "copilot": "npm-loader.js" - }, - "optionalDependencies": { -- "@github/copilot-darwin-arm64": "1.0.57", -- "@github/copilot-darwin-x64": "1.0.57", -- "@github/copilot-linux-arm64": "1.0.57", -- "@github/copilot-linux-x64": "1.0.57", -- "@github/copilot-linuxmusl-arm64": "1.0.57", -- "@github/copilot-linuxmusl-x64": "1.0.57", -- "@github/copilot-win32-arm64": "1.0.57", -- "@github/copilot-win32-x64": "1.0.57" +- "@github/copilot-darwin-arm64": "1.0.64-0", +- "@github/copilot-darwin-x64": "1.0.64-0", +- "@github/copilot-linux-arm64": "1.0.64-0", +- "@github/copilot-linux-x64": "1.0.64-0", +- "@github/copilot-linuxmusl-arm64": "1.0.64-0", +- "@github/copilot-linuxmusl-x64": "1.0.64-0", +- "@github/copilot-win32-arm64": "1.0.64-0", +- "@github/copilot-win32-x64": "1.0.64-0" - } - }, - "node_modules/@github/copilot-darwin-arm64": { -- "version": "1.0.57", -- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.57.tgz", -- "integrity": "sha512-ZmsojZbitPSRfgw3W9wBrHGLRDsBvMCjGsGnJ7xXOU6qxeF/IyWHADxEv1WKfDw8BdCM+LE5yITPXB8bcvCdqQ==", +- "version": "1.0.64-0", +- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.64-0.tgz", +- "integrity": "sha512-97DUGiuYrkCYOlSSLWMmr+K0uGzAxz1JOL/GyO/7mNl6V/1xgs6Van1Jj+Dpj4ly96iHE8lUIW8cQNCG66644g==", - "cpu": [ - "arm64" - ], @@ -247,9 +234,9 @@ index 8ffdf12aa94..a4ae22d6b31 100644 - } - }, - "node_modules/@github/copilot-darwin-x64": { -- "version": "1.0.57", -- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.57.tgz", -- "integrity": "sha512-F4TFDOdORy4oSHJS4DE+3sTk09uk1lohOloe0jfvoEVxJSU6jdQcJLNGoo+BQljcG7a1HEBrmB04iAWG1UXVfA==", +- "version": "1.0.64-0", +- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.64-0.tgz", +- "integrity": "sha512-2PXY4mSFtIjFdRaAt8PakegRgGtf6Sz9z6U/dIgVygNfctVNzaL5FH65PNPm8Y80jaDvEcz1/XY5MiQtxnlzZQ==", - "cpu": [ - "x64" - ], @@ -263,12 +250,15 @@ index 8ffdf12aa94..a4ae22d6b31 100644 - } - }, - "node_modules/@github/copilot-linux-arm64": { -- "version": "1.0.57", -- "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.57.tgz", -- "integrity": "sha512-6apNY/v7CMxKk45CctUZLzQnddBpIG9keSendFKYN+kBIEBSdy//s/Cz/4YQX1iERnklpgZRP7FvcwaKs0/7YA==", +- "version": "1.0.64-0", +- "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.64-0.tgz", +- "integrity": "sha512-PLP+vR508fOTlCr9CSZiXi9geicHKXuX9jLGdwNqK2TMZO5TqCLz8wP7dBEmkdkeXcFKovMb8nQVB1Toc6xutw==", - "cpu": [ - "arm64" - ], +- "libc": [ +- "glibc" +- ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ @@ -279,12 +269,15 @@ index 8ffdf12aa94..a4ae22d6b31 100644 - } - }, - "node_modules/@github/copilot-linux-x64": { -- "version": "1.0.57", -- "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.57.tgz", -- "integrity": "sha512-EOOnU4Y+vZHfxVl8eBAP7JtSTmu5d4ZDUC9wCGpAA5k703lEnpu8UOv04mTHRn8KTzb8gj+ijNhxDWe3Xljbaw==", +- "version": "1.0.64-0", +- "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.64-0.tgz", +- "integrity": "sha512-NvVjQ69zr390ijzo2f75+v0DHm6xnvPbi67ugnKDk7ZPbx8P3vSxVdAnrzrrL4T3T8ng3pJANcC4p+eGbx+UDw==", - "cpu": [ - "x64" - ], +- "libc": [ +- "glibc" +- ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ @@ -295,12 +288,15 @@ index 8ffdf12aa94..a4ae22d6b31 100644 - } - }, - "node_modules/@github/copilot-linuxmusl-arm64": { -- "version": "1.0.57", -- "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.57.tgz", -- "integrity": "sha512-FCAaaJLX5T2ZpMeS1TCNnhQuGqyH9WVZndFdN1VOEnN/iWeSSaVF3lM4TPyRHHnWDVxzZtB+VLqOSjINZntD6g==", +- "version": "1.0.64-0", +- "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.64-0.tgz", +- "integrity": "sha512-qCnVF5vIcTO74CukAENZo8e5nqXm4QUshuKN69aiZb5GOhVvyyIKsf5Jo7ikZt54jJBHycAMUKlTA8L3/nK+KA==", - "cpu": [ - "arm64" - ], +- "libc": [ +- "musl" +- ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ @@ -311,12 +307,15 @@ index 8ffdf12aa94..a4ae22d6b31 100644 - } - }, - "node_modules/@github/copilot-linuxmusl-x64": { -- "version": "1.0.57", -- "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.57.tgz", -- "integrity": "sha512-AMIBN830yOvNcrj2Q0tGMImqat/V24wZS/4m5BaUssELM7r7KrT9ZBnBs+nWDZYeQaRoblFWL3f4AfxE3t94lQ==", +- "version": "1.0.64-0", +- "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.64-0.tgz", +- "integrity": "sha512-WDBEmkBk1RulTfdLK5IuttNBadjLOBpvQyonGQ/aLeaetRNNdapoygrSjFU7q1QBSenmCyanXH6D+TS7tP3Qsw==", - "cpu": [ - "x64" - ], +- "libc": [ +- "musl" +- ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ @@ -327,17 +326,17 @@ index 8ffdf12aa94..a4ae22d6b31 100644 - } - }, - "node_modules/@github/copilot-sdk": { -- "version": "1.0.0", -- "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-1.0.0.tgz", -- "integrity": "sha512-OKjmJMDM+GB2uHr8UA6O0FNs1Gfw/tkoE5vUNlYmKbydc9Yjf6pvuBdseGjAVvzc6f9HIbB5eZKLUrxbOTw+yA==", +- "version": "1.0.2", +- "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-1.0.2.tgz", +- "integrity": "sha512-JJDsGM/bA1LGy1Ro/8iC8RLpKsLmuiFdQ67oFAVfi0Hfxyx289teHwmM70ehK76DXBkrPqqcxcliJi56k1ggFA==", - "license": "MIT", - "dependencies": { -- "@github/copilot": "^1.0.57", +- "@github/copilot": "^1.0.64-0", - "vscode-jsonrpc": "^8.2.1", - "zod": "^4.3.6" - }, - "engines": { -- "node": ">=20.0.0" +- "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@github/copilot-sdk/node_modules/zod": { @@ -350,9 +349,9 @@ index 8ffdf12aa94..a4ae22d6b31 100644 - } - }, - "node_modules/@github/copilot-win32-arm64": { -- "version": "1.0.57", -- "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.57.tgz", -- "integrity": "sha512-3TL2bd1/p/sYbNgDIqbnjES//zlXP5b0sPEXKQRrpVF9ZLN3vjQ1tmBWx8Qx7zn2J3oywH2dG7qKjuxWTJRXKA==", +- "version": "1.0.64-0", +- "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.64-0.tgz", +- "integrity": "sha512-PC7yuUKcVbhli4bpzWFVT3juxj+v/iONazetNe3tMpHWza3W7MeFRifzAseSErKQCt2fHJth3m8bQAwFN2jfrA==", - "cpu": [ - "arm64" - ], @@ -366,9 +365,9 @@ index 8ffdf12aa94..a4ae22d6b31 100644 - } - }, - "node_modules/@github/copilot-win32-x64": { -- "version": "1.0.57", -- "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.57.tgz", -- "integrity": "sha512-zuKqRn0pIF+ZvuiMXbZkYK1AMlrV21kFTpyf5l7gdI1dzJuwHNI0Qfe0gzaZYaU1B4htbzMk9MhEbjR1PQcoJg==", +- "version": "1.0.64-0", +- "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.64-0.tgz", +- "integrity": "sha512-d2fnUTIlqNxCqS2PuV+FD99ZOYBaX72OLtAmphbKyz36KyZ6D4ssiu8M4vHVTKWWdyc3TWiLsnIB+ryWdv1gGw==", - "cpu": [ - "x64" - ], @@ -385,57 +384,43 @@ index 8ffdf12aa94..a4ae22d6b31 100644 "version": "2.0.1", "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-2.0.1.tgz", diff --git a/package.json b/package.json -index a7bc41dcbfc..5d2243257a9 100644 +index 9a425766..594fa86b 100644 --- a/package.json +++ b/package.json -@@ -43,9 +43,6 @@ - "watch-extensions": "npm run gulp watch-extensions watch-extension-media", - "watch-extensionsd": "deemon npm run watch-extensions", - "kill-watch-extensionsd": "deemon --kill npm run watch-extensions", -- "watch-copilot": "npm --prefix extensions/copilot run watch", -- "watch-copilotd": "deemon npm run watch-copilot", -- "kill-watch-copilotd": "deemon --kill npm run watch-copilot", - "precommit": "node --experimental-strip-types build/hygiene.ts", - "gulp": "node --experimental-strip-types --max-old-space-size=8192 ./node_modules/gulp/bin/gulp.js", - "electron": "node build/lib/electron.ts", -@@ -84,8 +81,6 @@ - "perf": "node scripts/code-perf.js", - "perf:chat": "node scripts/chat-simulation/test-chat-perf-regression.js", - "perf:chat-leak": "node scripts/chat-simulation/test-chat-mem-leaks.js", -- "copilot:setup": "npm --prefix extensions/copilot run setup", -- "copilot:get_token": "npm --prefix extensions/copilot run get_token", - "update-build-ts-version": "npm install -D typescript@next && npm install -D @typescript/native-preview && (cd build && npm run typecheck)", - "install-local-component-explorer": "npm install ../vscode-packages/js-component-explorer/dist/vscode-component-explorer-0.1.0.tgz ../vscode-packages/js-component-explorer/dist/vscode-component-explorer-cli-0.1.0.tgz --no-save && cd build/rspack && npm install ../../../vscode-packages/js-component-explorer/dist/vscode-component-explorer-webpack-plugin-0.1.0.tgz --no-save && cd ../vite && npm install ../../../vscode-packages/js-component-explorer/dist/vscode-component-explorer-vite-plugin-0.1.0.tgz --no-save", - "symlink-local-component-explorer": "npm install ../vscode-packages/js-component-explorer/packages/explorer ../vscode-packages/js-component-explorer/packages/cli --no-save && cd build/rspack && npm install ../../../vscode-packages/js-component-explorer/packages/webpack-plugin ../../../vscode-packages/js-component-explorer/packages/explorer --no-save && cd ../vite && npm install ../../../vscode-packages/js-component-explorer/packages/vite-plugin ../../../vscode-packages/js-component-explorer/packages/explorer --no-save", -@@ -93,8 +88,6 @@ +@@ -95,8 +95,6 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.82.0", -- "@github/copilot": "^1.0.57", -- "@github/copilot-sdk": "^1.0.0", +- "@github/copilot": "^1.0.64-0", +- "@github/copilot-sdk": "^1.0.2", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", "@microsoft/dev-tunnels-connections": "^1.3.41", diff --git a/src/vs/platform/agentHost/common/otel/agentHostOTelService.ts b/src/vs/platform/agentHost/common/otel/agentHostOTelService.ts -index 667e3bc8c6d..51af8b2ce19 100644 +index 667e3bc8..634bca98 100644 --- a/src/vs/platform/agentHost/common/otel/agentHostOTelService.ts +++ b/src/vs/platform/agentHost/common/otel/agentHostOTelService.ts -@@ -3,7 +3,12 @@ +@@ -3,7 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { TelemetryConfig } from '@github/copilot-sdk'; +// `TelemetryConfig` was originally imported from '@github/copilot-sdk'; the SDK -+// is removed in BradfordCode. This service still exposes a config shape that -+// mirrors the SDK's `TelemetryConfig`; keep the type symbol available at -+// compile time without depending on the removed package. -+// eslint-disable-next-line @typescript-eslint/no-explicit-any -+type TelemetryConfig = any; ++// is removed in !!APP_NAME!! (proprietary Copilot binaries can't be ++// redistributed). The OTel service no longer feeds a Copilot SDK, so define the ++// config shape locally from the fields the service actually builds/reads. ++export type TelemetryConfig = { ++ exporterType?: string; ++ otlpEndpoint?: string; ++ filePath?: string; ++ sourceName?: string; ++ captureContent?: boolean; ++}; import type { URI } from '../../../../base/common/uri.js'; import { createDecorator } from '../../../instantiation/common/instantiation.js'; diff --git a/src/vs/platform/agentHost/node/agentHostMain.ts b/src/vs/platform/agentHost/node/agentHostMain.ts -index 59c5257d573..5b854613420 100644 +index 0dd36914..6440c830 100644 --- a/src/vs/platform/agentHost/node/agentHostMain.ts +++ b/src/vs/platform/agentHost/node/agentHostMain.ts @@ -20,7 +20,6 @@ import { AgentService } from './agentService.js'; @@ -443,45 +428,187 @@ index 59c5257d573..5b854613420 100644 import { IAgentHostCompletions } from './agentHostCompletions.js'; import { IAgentHostTerminalManager } from './agentHostTerminalManager.js'; -import { CopilotAgent } from './copilot/copilotAgent.js'; + import { CopilotBranchNameGenerator, ICopilotBranchNameGenerator } from './copilot/copilotBranchNameGenerator.js'; import { CopilotApiService, ICopilotApiService } from './shared/copilotApiService.js'; import { ClaudeAgent } from './claude/claudeAgent.js'; - import { ClaudeAgentSdkService, IClaudeAgentSdkService } from './claude/claudeAgentSdkService.js'; -@@ -169,7 +168,6 @@ async function startAgentHost(): Promise { +@@ -184,7 +183,6 @@ async function startAgentHost(): Promise { diServices.set(IAgentHostTerminalManager, agentService.terminalManager); diServices.set(IAgentConfigurationService, agentService.configurationService); diServices.set(IAgentHostCompletions, agentService.completionsService); - agentService.registerProvider(instantiationService.createInstance(CopilotAgent)); - // The Claude agent provider is opt-in. Gated on the - // `chat.agentHost.claudeAgent.path` workbench setting being non-empty, - // forwarded by the agent host starters as `VSCODE_AGENT_HOST_CLAUDE_SDK_PATH`. + // Claude and Codex providers are gated on two things: + // 1. The user-facing enable toggle (`chat.agentHost.Agent.enabled`, + // forwarded as an env var by the starters). Claude defaults to on, diff --git a/src/vs/platform/agentHost/node/agentHostServerMain.ts b/src/vs/platform/agentHost/node/agentHostServerMain.ts -index 52a291518b6..6197da16587 100644 +index 5c428d75..a23b5047 100644 --- a/src/vs/platform/agentHost/node/agentHostServerMain.ts +++ b/src/vs/platform/agentHost/node/agentHostServerMain.ts -@@ -32,7 +32,6 @@ import product from '../../product/common/product.js'; - import { IProductService } from '../../product/common/productService.js'; +@@ -33,7 +33,6 @@ import { IProductService } from '../../product/common/productService.js'; import { InstantiationService } from '../../instantiation/common/instantiationService.js'; import { ServiceCollection } from '../../instantiation/common/serviceCollection.js'; + import { registerAgentHostNetworkServices } from './agentHostBootstrap.js'; -import { CopilotAgent } from './copilot/copilotAgent.js'; + import { CopilotBranchNameGenerator, ICopilotBranchNameGenerator } from './copilot/copilotBranchNameGenerator.js'; import { CopilotApiService, ICopilotApiService } from './shared/copilotApiService.js'; import { ClaudeAgent } from './claude/claudeAgent.js'; - import { ClaudeAgentSdkService, IClaudeAgentSdkService } from './claude/claudeAgentSdkService.js'; -@@ -251,9 +250,6 @@ async function main(): Promise { +@@ -282,9 +281,6 @@ async function main(): Promise { diServices.set(ICodexProxyService, codexProxyService); const agentHostOTelService = disposables.add(instantiationService.createInstance(AgentHostOTelService)); diServices.set(IAgentHostOTelService, agentHostOTelService); - const copilotAgent = disposables.add(instantiationService.createInstance(CopilotAgent)); - agentService.registerProvider(copilotAgent); - log('CopilotAgent registered'); - if (options.claudeSdkPath) { - // `ClaudeAgentSdkService` reads `AgentHostClaudeSdkPathEnvVar` directly, - // so make sure it is set even if the path was provided via CLI flag. + // Claude and Codex providers are gated on two things: + // 1. The user-facing enable toggle (`chat.agentHost.Agent.enabled`, + // forwarded as an env var by the renderer-side starters; the remote +diff --git a/src/vs/platform/agentHost/node/copilot/agentHostSandboxEngine.ts b/src/vs/platform/agentHost/node/copilot/agentHostSandboxEngine.ts +deleted file mode 100644 +index ec3155d1..00000000 +--- a/src/vs/platform/agentHost/node/copilot/agentHostSandboxEngine.ts ++++ /dev/null +@@ -1,136 +0,0 @@ +-/*--------------------------------------------------------------------------------------------- +- * Copyright (c) Microsoft Corporation. All rights reserved. +- * Licensed under the MIT License. See License.txt in the project root for license information. +- *--------------------------------------------------------------------------------------------*/ +- +-import { Event } from '../../../../base/common/event.js'; +-import { FileAccess } from '../../../../base/common/network.js'; +-import { dirname } from '../../../../base/common/path.js'; +-import { OS, OperatingSystem } from '../../../../base/common/platform.js'; +-import { URI } from '../../../../base/common/uri.js'; +-import { createHash } from 'crypto'; +-import { IEnvironmentService, INativeEnvironmentService } from '../../../environment/common/environment.js'; +-import { IInstantiationService } from '../../../instantiation/common/instantiation.js'; +-import { IProductService } from '../../../product/common/productService.js'; +-import { ISandboxHelperService, type ISandboxDependencyStatus, type IWindowsMxcPolicyContainment, type IWindowsMxcSandboxPolicy } from '../../../sandbox/common/sandboxHelperService.js'; +-import { ITerminalSandboxEngineHost, ITerminalSandboxRuntimeInfo, TerminalSandboxEngine } from '../../../sandbox/common/terminalSandboxEngine.js'; +-import { IAgentConfigurationService } from '../agentConfigurationService.js'; +-import { AgentHostSandboxConfigKey, sandboxConfigSchema, sandboxSettingIdToAgentHostKey } from '../../common/sandboxConfigSchema.js'; +- +-/** Subdirectory under the user home + product data folder where the engine creates its temp dir. */ +-const SANDBOX_TEMP_DIR_NAME = 'tmp'; +- +-/** +- * Host adapter that bridges agent-host environment data into the shared +- * {@link TerminalSandboxEngine}. One instance per session, wired up via +- * {@link createAgentHostSandboxEngine}. +- */ +-class AgentHostTerminalSandboxHost implements ITerminalSandboxEngineHost { +- readonly onDidChangeRoots = Event.None; +- readonly onDidChangeSandboxSettings: Event; +- private readonly _sandboxHelper: ISandboxHelperService; +- +- constructor( +- private readonly _sessionId: string, +- private readonly _workingDirectory: URI | undefined, +- private readonly _environmentService: INativeEnvironmentService, +- private readonly _productService: IProductService, +- private readonly _agentConfigurationService: IAgentConfigurationService, +- sandboxHelper: ISandboxHelperService, +- ) { +- this._sandboxHelper = sandboxHelper; +- this.onDidChangeSandboxSettings = this._agentConfigurationService.onDidRootConfigChange; +- } +- +- async getOS(): Promise { +- return OS; +- } +- +- async getRuntimeInfo(): Promise { +- const appRoot = dirname(FileAccess.asFileUri('').path); +- const runAsNode = !!process.versions['electron']; +- return { appRoot, execPath: process.execPath, runAsNode }; +- } +- +- async getUserHome(): Promise { +- return this._environmentService.userHome; +- } +- +- async getSandboxTempDir(): Promise { +- const userHome = this._environmentService.userHome; +- if (!userHome) { +- return undefined; +- } +- const sandboxRoot = URI.joinPath(userHome, this._productService.dataFolderName, SANDBOX_TEMP_DIR_NAME); +- // Keep the per-session leaf short and bounded: the sandbox runtime +- // creates its network-bridge UNIX sockets (e.g. `claude-socks-.sock`, +- // ~35 bytes) directly under this directory, and the full socket path must +- // stay within the AF_UNIX 108-byte limit. The raw session id is a URI +- // segment (often a UUID), so hash it to a short hex string instead. A +- // 64-bit SHA-256 prefix (16 hex chars) keeps the leaf short and +- // collisions infeasible. +- const digest = createHash('sha256').update(this._sessionId).digest('hex'); +- const sessionLeaf = `agenthost_${digest.substring(0, 16)}`; +- return URI.joinPath(sandboxRoot, sessionLeaf); +- } +- +- async getWorkspaceStorageReadRoot(): Promise { +- // The agent host has no workspace-storage equivalent today. +- return undefined; +- } +- +- getWriteRoots(): readonly URI[] { +- return this._workingDirectory ? [this._workingDirectory] : []; +- } +- +- async checkSandboxDependencies(): Promise { +- return this._sandboxHelper.checkSandboxDependencies(); +- } +- +- async getWindowsMxcFilesystemPolicy() { +- return this._sandboxHelper.getWindowsMxcFilesystemPolicy(); +- } +- +- async getWindowsMxcEnvironment() { +- return this._sandboxHelper.getWindowsMxcEnvironment(); +- } +- +- async buildWindowsMxcSandboxPayload(commandLine: string, policy: IWindowsMxcSandboxPolicy, workingDirectory?: string, containerName?: string, containment?: IWindowsMxcPolicyContainment) { +- return this._sandboxHelper.buildWindowsMxcSandboxPayload(commandLine, policy, workingDirectory, containerName, containment); +- } +- +- getSandboxSetting(settingId: string): T | undefined { +- // The agent host stores sandbox settings nested under a single +- // top-level `sandbox` object with prefix-free sub-keys (e.g. +- // `sandbox.enabled` rather than `chat.agent.sandbox.enabled`). Map +- // from the engine's modern setting ID into that sub-key namespace; +- // unknown IDs (which include all deprecated keys — handled host-side +- // by the workbench client) resolve to undefined. +- const innerKey = sandboxSettingIdToAgentHostKey[settingId]; +- if (innerKey === undefined) { +- return undefined; +- } +- const sandbox = this._agentConfigurationService.getRootValue(sandboxConfigSchema, AgentHostSandboxConfigKey.Sandbox); +- return sandbox?.[innerKey] as T | undefined; +- } +-} +- +-/** +- * Construct a per-session {@link TerminalSandboxEngine} for the agent host. +- * The returned engine is registered with the caller's instantiation service +- * but the caller is responsible for disposing it (typically by registering it +- * alongside the per-session {@link ShellManager}). +- */ +-export function createAgentHostSandboxEngine( +- instantiationService: IInstantiationService, +- environmentService: IEnvironmentService, +- productService: IProductService, +- agentConfigurationService: IAgentConfigurationService, +- sandboxHelper: ISandboxHelperService, +- sessionId: string, +- workingDirectory: URI | undefined, +-): TerminalSandboxEngine { +- const host = new AgentHostTerminalSandboxHost(sessionId, workingDirectory, environmentService as INativeEnvironmentService, productService, agentConfigurationService, sandboxHelper); +- return instantiationService.createInstance(TerminalSandboxEngine, host); +-} +- diff --git a/src/vs/platform/agentHost/node/copilot/copilotAgent.ts b/src/vs/platform/agentHost/node/copilot/copilotAgent.ts deleted file mode 100644 -index 23d008d0f91..00000000000 +index 3f37722b..00000000 --- a/src/vs/platform/agentHost/node/copilot/copilotAgent.ts +++ /dev/null -@@ -1,2554 +0,0 @@ +@@ -1,3187 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. @@ -489,51 +616,77 @@ index 23d008d0f91..00000000000 - -import { CopilotClient, RuntimeConnection, type CopilotClientOptions } from '@github/copilot-sdk'; -import * as fs from 'fs/promises'; --import { Limiter, SequencerByKey, Throttler } from '../../../../base/common/async.js'; --import { CancellationTokenSource, type CancellationToken } from '../../../../base/common/cancellation.js'; --import { rgDiskPath } from '../../../../base/node/ripgrep.js'; +-import * as os from 'os'; +-import { CancelablePromise, createCancelablePromise, Delayer, Limiter, SequencerByKey } from '../../../../base/common/async.js'; +-import { type CancellationToken } from '../../../../base/common/cancellation.js'; -import { CancellationError } from '../../../../base/common/errors.js'; -import { Emitter, Event } from '../../../../base/common/event.js'; -import { appendEscapedMarkdownInlineCode } from '../../../../base/common/htmlContent.js'; -import { Disposable, DisposableMap, MutableDisposable } from '../../../../base/common/lifecycle.js'; -import { ResourceMap } from '../../../../base/common/map.js'; -import { FileAccess } from '../../../../base/common/network.js'; +-import { formatTokenCount } from '../../../../base/common/numbers.js'; -import { equals } from '../../../../base/common/objects.js'; -import { observableValue } from '../../../../base/common/observable.js'; --import { basename, delimiter, dirname } from '../../../../base/common/path.js'; --import { basename as resourceBasename, dirname as resourceDirname } from '../../../../base/common/resources.js'; +-import { basename, delimiter, dirname, join } from '../../../../base/common/path.js'; +-import { basename as resourceBasename } from '../../../../base/common/resources.js'; -import { URI } from '../../../../base/common/uri.js'; -import { generateUuid } from '../../../../base/common/uuid.js'; +-import { rgDiskPath } from '../../../../base/node/ripgrep.js'; -import { localize } from '../../../../nls.js'; --import { IParsedPlugin, parseAgentFile, parsePlugin, parseSkillFile } from '../../../agentPlugins/common/pluginParsers.js'; +-import { IParsedAgent, IParsedPlugin, IParsedRule, IParsedSkill, parseAgentFile, parsePlugin, parseRuleFile, parseSkillFile } from '../../../agentPlugins/common/pluginParsers.js'; -import { IFileService } from '../../../files/common/files.js'; -import { IInstantiationService } from '../../../instantiation/common/instantiation.js'; --import { ILogService } from '../../../log/common/log.js'; +-import { ILogService, LogLevel } from '../../../log/common/log.js'; +-import { IAgentHostCheckpointService } from '../../common/agentHostCheckpointService.js'; +-import { createAgentModelPricingMeta } from '../../common/agentModelPricing.js'; -import { AgentHostConfigKey, agentHostCustomizationConfigSchema, toContainerCustomization } from '../../common/agentHostCustomizationConfig.js'; --import { AgentHostSessionSyncEnabledConfigKey, AutoApproveLevel, ISchemaProperty, SessionMode, createSchema, platformRootSchema, platformSessionSchema, schemaProperty } from '../../common/agentHostSchema.js'; +-import { AgentHostMcpServersConfigKey, AgentHostSessionSyncEnabledConfigKey, AutoApproveLevel, ISchemaProperty, SessionMode, createSchema, migrateLegacyAutopilotConfig, platformRootSchema, platformSessionSchema, schemaProperty, type AgentHostMcpServers } from '../../common/agentHostSchema.js'; -import { IAgentPluginManager, ISyncedCustomization } from '../../common/agentPluginManager.js'; --import { AgentSession, AgentSignal, GITHUB_COPILOT_PROTECTED_RESOURCE, IAgent, IAgentCreateSessionConfig, IAgentCreateSessionResult, IAgentDescriptor, IAgentMaterializeSessionEvent, IAgentModelInfo, IAgentResolveSessionConfigParams, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, IAgentSessionProjectInfo } from '../../common/agentService.js'; +-import { AgentSession, AgentSignal, GITHUB_COPILOT_PROTECTED_RESOURCE, GITHUB_REPO_PROTECTED_RESOURCE, IAgent, IAgentCreateChatOptions, IAgentCreateSessionConfig, IAgentCreateSessionResult, IAgentDescriptor, IAgentMaterializeSessionEvent, IAgentModelInfo, IAgentResolveSessionConfigParams, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, IAgentSessionProjectInfo, IMcpNotification } from '../../common/agentService.js'; +-import { getEffectiveAgents } from '../../common/customAgents.js'; +-import { getReasoningEffortDescription, getReasoningEffortLabel } from '../../common/reasoningEffort.js'; +-import type { IAgentServerToolHost } from '../../common/agentServerTools.js'; +-import { IAgentHostOTelService } from '../../common/otel/agentHostOTelService.js'; -import { SessionConfigKey } from '../../common/sessionConfigKeys.js'; -import { ISessionDataService, SESSION_DB_FILENAME } from '../../common/sessionDataService.js'; -import type { ResolveSessionConfigResult, SessionConfigCompletionsResult } from '../../common/state/protocol/commands.js'; --import { ProtectedResourceMetadata, type ChildCustomizationType, type ConfigSchema, type ModelSelection, type AgentSelection, type ToolDefinition } from '../../common/state/protocol/state.js'; +-import { ProtectedResourceMetadata, type AgentSelection, type ChildCustomizationType, type ConfigPropertySchema, type ConfigSchema, type ModelSelection, type ToolDefinition } from '../../common/state/protocol/state.js'; -import { ActionType, type SessionAction } from '../../common/state/sessionActions.js'; --import { AHP_AUTH_REQUIRED, ProtocolError } from '../../common/state/sessionProtocol.js'; --import { CustomizationLoadStatus, CustomizationType, ResponsePartKind, SessionInputResponseKind, customizationId, parseSubagentSessionUri, type ChildCustomization, type ClientPluginCustomization, type Customization, type DirectoryCustomization, type MessageAttachment, type PendingMessage, type PluginCustomization, type PolicyState, type ResponsePart, type SessionInputAnswer, type ToolCallResult, type Turn } from '../../common/state/sessionState.js'; +-import { AgentCustomization, CustomizationLoadStatus, CustomizationType, ResponsePartKind, RuleCustomization, ChatInputResponseKind, SkillCustomization, customizationId, buildChatUri, isDefaultChatUri, parseChatUri, parseSubagentSessionUri, type ChildCustomization, type ClientPluginCustomization, type Customization, type DirectoryCustomization, type HookCustomization, type MessageAttachment, type PendingMessage, type PluginCustomization, type PolicyState, type ResponsePart, type ChatInputAnswer, type ToolCallResult, type Turn } from '../../common/state/sessionState.js'; +-import { ActiveClientState } from '../activeClientState.js'; -import { IAgentConfigurationService } from '../agentConfigurationService.js'; --import { IAgentHostOTelService } from '../../common/otel/agentHostOTelService.js'; -import { IAgentHostCompletions } from '../agentHostCompletions.js'; -import { IAgentHostGitService, META_DIFF_BASE_BRANCH } from '../agentHostGitService.js'; --import { IAgentHostCheckpointService } from '../../common/agentHostCheckpointService.js'; +-import { findMcpChildId } from '../shared/mcpCustomizationController.js'; +-import { ICopilotBranchNameGenerator } from './copilotBranchNameGenerator.js'; -import { CopilotAgentSession, type CopilotSdkMode } from './copilotAgentSession.js'; -import { ICopilotSessionContext, projectFromCopilotContext } from './copilotGitProject.js'; -import { parsedPluginsEqual, toChildCustomizations } from './copilotPluginConverters.js'; +-import { CopilotSessionLauncher, ContextTierConfigKey, ThinkingLevelConfigKey, getCopilotContextTier, getCopilotReasoningEffort, type CopilotSessionLaunchPlan, type IActiveClientSnapshot } from './copilotSessionLauncher.js'; -import { ShellManager } from './copilotShellTools.js'; --import { CopilotSessionLauncher, ThinkingLevelConfigKey, getCopilotReasoningEffort, type CopilotSessionLaunchPlan, type IActiveClientSnapshot } from './copilotSessionLauncher.js'; --import { DiscoveredType, SessionCustomizationDiscovery, type IDiscoveredDirectory } from './sessionCustomizationDiscovery.js'; --import { SessionPluginBundler } from '../shared/sessionPluginBundler.js'; +-import { isRestrictedTelemetryEnabled } from './copilotTokenFields.js'; -import { CopilotSlashCommandCompletionProvider } from './copilotSlashCommandCompletionProvider.js'; --import { getEffectiveAgents } from '../../common/customAgents.js'; +-import { DiscoveredType, SessionCustomizationDiscovery, areDiscoveredDirectoriesEqual, type IDiscoveredDirectory } from './sessionCustomizationDiscovery.js'; +-import { COPILOT_INTEGRATION_ID } from '../../../endpoint/common/licenseAgreement.js'; +- +-/** +- * Maps a VS Code {@link LogLevel} to the Copilot CLI runtime's `logLevel` +- * option so the spawned CLI logs (written to `~/.copilot/logs/process-*.log`) +- * match the agent host's configured verbosity. `Trace` maps to the CLI's most +- * verbose `'all'` level so renderer-side trace logging surfaces the CLI's +- * internal diagnostics. +- */ +-function copilotCliLogLevelFor(level: LogLevel): NonNullable { +- switch (level) { +- case LogLevel.Off: return 'none'; +- case LogLevel.Trace: return 'all'; +- case LogLevel.Debug: return 'debug'; +- case LogLevel.Info: return 'info'; +- case LogLevel.Warning: return 'warning'; +- case LogLevel.Error: return 'error'; +- } +-} - -interface ICreatedWorktree { - readonly repositoryRoot: URI; @@ -578,7 +731,9 @@ index 23d008d0f91..00000000000 - readonly project: IAgentSessionProjectInfo | undefined; -} - --export { COPILOT_AGENT_HOST_SYSTEM_MESSAGE } from './copilotSessionLauncher.js'; +-export { COPILOT_AGENT_HOST_SYSTEM_MESSAGE } from './prompts/systemMessage.js'; +- +-type ModelInfo = Awaited>['models'][number]; - -interface ISerializedModelSelection { - id?: unknown; @@ -586,8 +741,43 @@ index 23d008d0f91..00000000000 -} - -/** -- * Subset of the JSON-RPC `MessageConnection` we reach into via the SDK's -- * private `connection` field to wire plan mode. See {@link CopilotAgent._enablePlanModeOnClient}. +- * A persisted additional (non-default) peer chat. Records the SDK conversation +- * id that backs the chat so it can be resumed after a process restart, along +- * with any model override chosen at creation time. +- */ +-interface IPersistedChat { +- readonly sdkSessionId: string; +- readonly model?: ModelSelection; +-} +- +-/** +- * Augments the published `@vscode/copilot-api` `ModelBilling` with the `tokenPrices` field the runtime CAPI `/models` +- * payload already carries but the SDK type doesn't yet declare. Mirror of `IClaudeModelSupports` in `claudeAgent.ts`. +- */ +-interface ICopilotModelBilling { +- readonly multiplier?: number; +- /** Coarse price bucket surfaced as a tag in the model picker hover. */ +- readonly priceCategory?: string; +- readonly tokenPrices?: { +- /** Default-tier prices, expressed as credits per 1M tokens. */ +- readonly contextMax?: number; +- readonly inputPrice?: number; +- readonly cachePrice?: number; +- readonly cacheWritePrice?: number; +- readonly outputPrice?: number; +- readonly longContext?: { +- readonly contextMax?: number; +- readonly inputPrice?: number; +- readonly cachePrice?: number; +- readonly cacheWritePrice?: number; +- readonly outputPrice?: number; +- }; +- }; +-} +- +-/** +- * Subset of the JSON-RPC `MessageConnection` we reach into via the SDK's private `connection` field to wire plan mode. +- * See {@link CopilotAgent._enablePlanModeOnClient}. - */ -interface IExitPlanModeConnection { - sendRequest(method: string, params: unknown): Promise; @@ -627,32 +817,6 @@ index 23d008d0f91..00000000000 - return branchName.replace(/\//g, '-'); -} - --export function getCopilotWorktreeBranchName(sessionId: string, branchNameHint: string | undefined): string { -- return `agents/${branchNameHint ? `${branchNameHint}-${sessionId.substring(0, 8)}` : sessionId}`; --} -- --/** -- * Derive a slug-style branch-name hint from the user's first message. Used -- * by the worktree isolation flow so the generated branch name reflects the -- * intent of the session instead of being just a session id. -- * -- * Returns `undefined` if the message has no slug-able content (e.g. only -- * punctuation), in which case the caller falls back to a session-id-only -- * branch name. -- */ --export function getCopilotBranchNameHintFromMessage(message: string): string | undefined { -- const words = message -- .toLowerCase() -- .normalize('NFKD') -- .replace(/[^a-z0-9]+/g, '-') -- .replace(/^-+|-+$/g, '') -- .split('-') -- .filter(word => word.length > 0) -- .slice(0, 8); -- const hint = words.join('-').slice(0, 48).replace(/-+$/g, ''); -- return hint.length > 0 ? hint : undefined; --} -- -/** - * Builds the localized "Created isolated worktree for branch X" markdown - * shown at the top of the first response in worktree-isolated sessions. @@ -711,14 +875,49 @@ index 23d008d0f91..00000000000 - readonly onDidSessionProgress = this._onDidSessionProgress.event; - private readonly _onDidMaterializeSession = this._register(new Emitter()); - readonly onDidMaterializeSession = this._onDidMaterializeSession.event; +- /** +- * Per-session MCP notifications, fanned in from every active +- * {@link CopilotAgentSession}. Each session contributes a single +- * subscription, disposed alongside the session. +- */ +- private readonly _onMcpNotification = this._register(new Emitter()); +- readonly onMcpNotification = this._onMcpNotification.event; - private readonly _models = observableValue(this, []); - readonly models = this._models; - - private _client: CopilotClient | undefined; - private _clientStarting: Promise | undefined; - private _githubToken: string | undefined; +- private _serverToolHost: IAgentServerToolHost | undefined; +- +- setServerToolHost(host: IAgentServerToolHost): void { +- this._serverToolHost = host; +- } +- +- /** Reflects the `rt=1` field on the GitHub Copilot bearer token; gates enhanced GH telemetry. */ +- private _restrictedTelemetryEnabled = false; +- private readonly _onDidChangeRestrictedTelemetry = this._register(new Emitter()); +- readonly onDidChangeRestrictedTelemetry = this._onDidChangeRestrictedTelemetry.event; +- +- get restrictedTelemetryEnabled(): boolean { +- return this._restrictedTelemetryEnabled; +- } +- - private readonly _sessions = this._register(new DisposableMap()); - /** +- * Additional (non-default) chats within a session, keyed by chat channel +- * URI string. Each entry is its own Copilot SDK conversation sharing the +- * owning session's working directory/model scope. The default chat is not +- * tracked here — it maps to the primary {@link _sessions} entry. +- */ +- private readonly _chatSessions = this._register(new DisposableMap()); +- /** +- * Per-session MCP-notification subscriptions, keyed by `sessionId`. +- * Disposed in lockstep with the matching {@link _sessions} entry so +- * the fan-in does not leak listeners as sessions come and go. +- */ +- private readonly _mcpNotificationSubs = this._register(new DisposableMap()); +- /** - * In-flight {@link _resumeSession} promises, keyed by sessionId. Used to - * deduplicate concurrent resume requests for the same session so that - * we never construct two {@link CopilotAgentSession} entries for the @@ -760,6 +959,7 @@ index 23d008d0f91..00000000000 - @IAgentHostGitService private readonly _gitService: IAgentHostGitService, - @IAgentConfigurationService private readonly _configurationService: IAgentConfigurationService, - @IAgentHostOTelService private readonly _otelService: IAgentHostOTelService, +- @ICopilotBranchNameGenerator private readonly _branchNameGenerator: ICopilotBranchNameGenerator, - @IAgentHostCompletions completions: IAgentHostCompletions, - @IAgentHostCheckpointService private readonly _checkpointService: IAgentHostCheckpointService, - ) { @@ -770,6 +970,7 @@ index 23d008d0f91..00000000000 - this._register(completions.registerProvider(new CopilotSlashCommandCompletionProvider(this.id, { - hasHistory: (sessionId) => !this._provisionalSessions.has(sessionId) && this._sessions.has(sessionId), - isRubberDuckEnabled: () => this._isRubberDuckEnabled(), +- hasRuntimeSlashCommand: async (sessionId, command) => this._sessions.get(sessionId)?.hasRuntimeSlashCommand(command) ?? false, - }))); - - // Restart the CLI client when a setting baked into the client/subprocess at @@ -818,6 +1019,7 @@ index 23d008d0f91..00000000000 - if (this._client) { - this._logService.info(`[Copilot] Startup config changed (${changed}), restarting CopilotClient`); - this._sessions.clearAndDisposeAll(); +- this._mcpNotificationSubs.clearAndDisposeAll(); - await this._stopClient(); - } - } @@ -831,13 +1033,16 @@ index 23d008d0f91..00000000000 - getDescriptor(): IAgentDescriptor { - return { - provider: 'copilotcli', -- displayName: 'Copilot CLI', +- displayName: 'Copilot', - description: 'Copilot SDK agent running in a dedicated process', - }; - } - - getProtectedResources(): ProtectedResourceMetadata[] { -- return [GITHUB_COPILOT_PROTECTED_RESOURCE]; +- return [ +- GITHUB_COPILOT_PROTECTED_RESOURCE, +- GITHUB_REPO_PROTECTED_RESOURCE +- ]; - } - - getCustomizations(): readonly Customization[] { @@ -847,7 +1052,23 @@ index 23d008d0f91..00000000000 - async getSessionCustomizations(session: URI): Promise { - const directory = await this._getSessionCustomizationDirectory(session); - const activeClient = this._getOrCreateActiveClient(session, directory); -- return activeClient.pluginController.getCustomizationsSettled(); +- const fromPlugins = await activeClient.pluginController.getCustomizationsSettled(); +- const sessionId = AgentSession.id(session); +- const entry = this._sessions.get(sessionId); +- const topLevelMcp = entry?.topLevelMcpCustomizations() ?? []; +- if (topLevelMcp.length === 0) { +- return fromPlugins; +- } +- return [...fromPlugins, ...topLevelMcp]; +- } +- +- async handleMcpRequest(session: URI, serverName: string, method: string, params: Record | undefined): Promise { +- const sessionId = AgentSession.id(session); +- const entry = this._sessions.get(sessionId); +- if (!entry) { +- throw new Error(`Method not found: no active session ${sessionId}`); +- } +- return entry.handleMcpRequest(serverName, method, params); - } - - private async _getSessionCustomizationDirectory(session: URI): Promise { @@ -862,22 +1083,31 @@ index 23d008d0f91..00000000000 - } - - async authenticate(resource: string, token: string): Promise { +- if (resource === GITHUB_REPO_PROTECTED_RESOURCE.resource) { +- return true; +- } - if (resource !== GITHUB_COPILOT_PROTECTED_RESOURCE.resource) { - return false; - } - const tokenChanged = this._githubToken !== token; - this._githubToken = token; +- this._updateRestrictedTelemetry(token); - this._logService.info(`[Copilot] Auth token ${tokenChanged ? 'updated' : 'unchanged'}`); -- if (tokenChanged && this._client && this._sessions.size === 0) { -- this._logService.info('[Copilot] Restarting CopilotClient with new token'); -- await this._stopClient(); -- } - if (tokenChanged) { - void this._refreshModels(); - } - return true; - } - +- private _updateRestrictedTelemetry(token: string | undefined): void { +- const rtEnabled = isRestrictedTelemetryEnabled(token); +- if (rtEnabled !== this._restrictedTelemetryEnabled) { +- this._restrictedTelemetryEnabled = rtEnabled; +- this._logService.info(`[Copilot] Restricted telemetry ${rtEnabled ? 'enabled' : 'disabled'}`); +- this._onDidChangeRestrictedTelemetry.fire(); +- } +- } +- - private async _refreshModels(): Promise { - const tokenAtRefreshStart = this._githubToken; - if (!tokenAtRefreshStart) { @@ -885,7 +1115,7 @@ index 23d008d0f91..00000000000 - return; - } - try { -- const models = await this._listModels(); +- const models = await this._listModels(tokenAtRefreshStart); - if (this._githubToken === tokenAtRefreshStart) { - this._models.set(models, undefined); - } @@ -945,10 +1175,6 @@ index 23d008d0f91..00000000000 - // ---- client lifecycle --------------------------------------------------- - - private async _ensureClient(): Promise { -- const tokenAtStartup = this._githubToken; -- if (!tokenAtStartup) { -- throw new ProtocolError(AHP_AUTH_REQUIRED, 'Authentication is required to use Copilot', this.getProtectedResources()); -- } - if (this._client) { - return this._client; - } @@ -961,7 +1187,7 @@ index 23d008d0f91..00000000000 - const sessionSyncAtStartup = this._isSessionSyncEnabled(); - const rubberDuckAtStartup = this._isRubberDuckEnabled(); - const clientStarting = (async () => { -- this._logService.info('[Copilot] Starting CopilotClient... (with token)'); +- this._logService.info('[Copilot] Starting CopilotClient...'); - - // Build a clean env for the CLI subprocess, stripping Electron/VS Code vars - // that can interfere with the Node.js process the SDK spawns. @@ -980,6 +1206,28 @@ index 23d008d0f91..00000000000 - } - env['COPILOT_CLI_RUN_AS_NODE'] = '1'; - env['USE_BUILTIN_RIPGREP'] = 'false'; +- env['COPILOT_MCP_APPS'] = 'true'; +- +- // On Linux the MXC bubblewrap sandbox backend does not forward a PTY into +- // the container, so the CLI's default PTY-backed interactive shell can +- // never start bash under the sandbox: the inner shell sees a non-tty +- // stdin, runs non-interactively, reads EOF and exits immediately, which +- // surfaces as "Failed to start bash process". Force the CLI's pipe-based +- // spawn shell backend (`SHELL_SPAWN_BACKEND`), which runs each command as +- // a one-shot child process and works correctly under bubblewrap. The CLI +- // already force-enables this on Alpine/musl; glibc Linux needs it too for +- // sandboxed shells. This becomes a no-op once the bundled CLI defaults the +- // spawn backend on for all of Linux. +- if (process.platform === 'linux') { +- const enabledFlags = env['COPILOT_CLI_ENABLED_FEATURE_FLAGS']; +- const flags = new Set((enabledFlags ?? '').split(',').map(f => f.trim()).filter(Boolean)); +- flags.add('SHELL_SPAWN_BACKEND'); +- env['COPILOT_CLI_ENABLED_FEATURE_FLAGS'] = [...flags].join(','); +- } +- +- // Identify VS Code's agent host traffic in CAPI +- env['GITHUB_COPILOT_INTEGRATION_ID'] = COPILOT_INTEGRATION_ID; +- this._logService.info(`[Copilot] Set CLI env: GITHUB_COPILOT_INTEGRATION_ID=${COPILOT_INTEGRATION_ID}`); - - // Enable the rubber duck critic subagent in the CLI when the agent host - // config opts in. `RUBBER_DUCK_AGENT` is the SDK's required interface for @@ -993,7 +1241,15 @@ index 23d008d0f91..00000000000 - // Resolve the CLI entry point from node_modules. We can't use require.resolve() - // because @github/copilot's exports map blocks direct subpath access. - // FileAccess.asFileUri('') points to the `out/` directory; node_modules is one level up. -- const cliPath = URI.joinPath(FileAccess.asFileUri(''), '..', 'node_modules', '@github', 'copilot', 'index.js').fsPath; +- const nodeModulesUri = URI.joinPath(FileAccess.asFileUri(''), '..', 'node_modules'); +- const cliPath = URI.joinPath(nodeModulesUri, '@github', 'copilot', 'index.js').fsPath; +- +- // The SDK's sandbox auto-detection looks for `//wxc-exec.exe` +- // (and the Linux/macOS equivalents). VS Code core ships the MXC sandbox binaries +- // at `node_modules/@microsoft/mxc-sdk/bin//`, so point `MXC_BIN_DIR` there. +- // The @github/copilot package's own `mxc-bin/` is excluded from the product build +- // (see build/.moduleignore), mirroring `CopilotCLISDK.getPackage` in the extension. +- env['MXC_BIN_DIR'] = URI.joinPath(nodeModulesUri, '@microsoft', 'mxc-sdk', 'bin').fsPath; - - // Add VS Code's built-in ripgrep to PATH so the CLI subprocess can find it. - const resolvedRgDiskPath = await rgDiskPath(); @@ -1008,19 +1264,15 @@ index 23d008d0f91..00000000000 - const telemetry = await this._otelService.getSdkTelemetryConfig(); - - const clientOptions: CopilotClientOptions = { -- gitHubToken: tokenAtStartup, - useLoggedInUser: false, - connection: RuntimeConnection.forStdio({ path: cliPath }), - env, - telemetry, +- logLevel: copilotCliLogLevelFor(this._logService.getLevel()), - enableRemoteSessions: this._isSessionSyncEnabled(), - }; - const client = this._createCopilotClient(clientOptions); - await client.start(); -- if (this._githubToken !== tokenAtStartup) { -- await client.stop(); -- throw new Error('Copilot authentication changed while the client was starting'); -- } - if (this._isSessionSyncEnabled() !== sessionSyncAtStartup || this._isRubberDuckEnabled() !== rubberDuckAtStartup) { - await client.stop(); - throw new Error('Copilot startup config changed while the client was starting'); @@ -1040,36 +1292,105 @@ index 23d008d0f91..00000000000 - - // ---- session management ------------------------------------------------- - -- private _createThinkingLevelConfigSchema(supportedReasoningEfforts: readonly string[] | undefined, defaultReasoningEffort: string | undefined): ConfigSchema | undefined { +- private _createThinkingLevelConfigSchemaProperty(supportedReasoningEfforts: readonly string[] | undefined, defaultReasoningEffort: string | undefined): ConfigPropertySchema | undefined { - if (!supportedReasoningEfforts?.length) { - return undefined; - } - -- const enumLabels = supportedReasoningEfforts.map(value => { -- switch (value) { -- case 'low': return localize('copilot.modelThinkingLevel.low', "Low"); -- case 'medium': return localize('copilot.modelThinkingLevel.medium', "Medium"); -- case 'high': return localize('copilot.modelThinkingLevel.high', "High"); -- case 'xhigh': return localize('copilot.modelThinkingLevel.xhigh', "Extra High"); -- default: return value; -- } -- }); +- return { +- type: 'string', +- title: localize('copilot.modelThinkingLevel.title', "Thinking Level"), +- description: localize('copilot.modelThinkingLevel.description', "Controls how much reasoning effort the model uses."), +- default: defaultReasoningEffort, +- enum: [...supportedReasoningEfforts], +- enumLabels: supportedReasoningEfforts.map(getReasoningEffortLabel), +- enumDescriptions: supportedReasoningEfforts.map(value => getReasoningEffortDescription(value) ?? ''), +- }; +- } +- +- /** +- * Synthesize a `contextTier` config property when the model exposes a `long_context` pricing tier with a distinct +- * context-max. Picker surfaces this as the "Context Size" button. Mirrors `getContextSizeOptions` in +- * `extensions/copilot/src/extension/conversation/vscode-node/languageModelAccess.ts`. +- * +- * `billing.tokenPrices` is present on the runtime CAPI `/models` payload but not yet declared on the published SDK +- * `ModelBilling` type — narrow through {@link ICopilotModelBilling} until the SDK catches up. +- */ +- private _createContextTierConfigSchemaProperty(billing: ModelInfo['billing'] | undefined): ConfigPropertySchema | undefined { +- const tokenPrices = billing?.tokenPrices; +- const defaultMax = tokenPrices?.contextMax; +- const longContextMax = tokenPrices?.longContext?.contextMax; +- if (!defaultMax || !longContextMax || defaultMax >= longContextMax) { +- return undefined; +- } +- +- const hasLongContextSurcharge = typeof tokenPrices?.longContext?.inputPrice === 'number' +- || typeof tokenPrices?.longContext?.outputPrice === 'number'; - - return { -- type: 'object', -- properties: { -- [ThinkingLevelConfigKey]: { -- type: 'string', -- title: localize('copilot.modelThinkingLevel.title', "Thinking Level"), -- description: localize('copilot.modelThinkingLevel.description', "Controls how much reasoning effort the model uses."), -- default: defaultReasoningEffort, -- enum: [...supportedReasoningEfforts], -- enumLabels, -- }, -- }, +- type: 'string', +- title: localize('copilot.modelContextTier.title', "Context Size"), +- description: localize('copilot.modelContextTier.description', "Selects the context window size for this model."), +- default: 'default', +- enum: ['default', 'long_context'], +- enumLabels: [formatTokenCount(defaultMax), formatTokenCount(longContextMax)], +- enumDescriptions: [ +- localize('copilot.modelContextTier.default', "Default"), +- hasLongContextSurcharge +- ? localize('copilot.modelContextTier.longerSessions', "Longer sessions") +- : localize('copilot.modelContextTier.longerSessionsNoCompaction', "Longer sessions without compaction"), +- ], - }; - } - +- /** +- * Builds the open `_meta` pricing bag for a model from its billing info so the chat model picker can render its +- * cost hover. Cost values are credits per 1M tokens. +- * +- * Long-context costs are only emitted when they differ from the default tier, mirroring `normalizeTokenPrices` in +- * `extensions/copilot/src/extension/conversation/common/languageModelAccess.ts`. +- * +- * `billing.tokenPrices` / `billing.priceCategory` are present on the runtime CAPI `/models` payload but not yet +- * declared on the published SDK `ModelBilling` type — narrow through {@link ICopilotModelBilling}. +- */ +- private _createModelPricingMeta(modelInfo: ModelInfo | undefined): Record | undefined { +- const billing = modelInfo?.billing; +- const tokenPrices = billing?.tokenPrices; +- const longContext = tokenPrices?.longContext; +- +- const differsFromDefault = (longValue: number | undefined, defaultValue: number | undefined): number | undefined => +- longValue !== undefined && longValue !== defaultValue ? longValue : undefined; +- +- return createAgentModelPricingMeta({ +- multiplierNumeric: typeof billing?.multiplier === 'number' ? billing.multiplier : undefined, +- inputCost: tokenPrices?.inputPrice, +- cacheCost: tokenPrices?.cachePrice, +- cacheWriteCost: tokenPrices?.cachePrice, +- outputCost: tokenPrices?.outputPrice, +- longContextInputCost: differsFromDefault(longContext?.inputPrice, tokenPrices?.inputPrice), +- longContextCacheCost: differsFromDefault(longContext?.cachePrice, tokenPrices?.cachePrice), +- longContextCacheWriteCost: differsFromDefault(longContext?.cachePrice, tokenPrices?.cachePrice), +- longContextOutputCost: differsFromDefault(longContext?.outputPrice, tokenPrices?.outputPrice), +- priceCategory: typeof modelInfo?.modelPickerPriceCategory === 'string' ? modelInfo.modelPickerPriceCategory : undefined, +- }); +- } +- +- private _createModelConfigSchema(m: ModelInfo): ConfigSchema | undefined { +- const properties: ConfigSchema['properties'] = {}; +- const thinkingLevel = this._createThinkingLevelConfigSchemaProperty(m.supportedReasoningEfforts, m.defaultReasoningEffort); +- if (thinkingLevel) { +- properties[ThinkingLevelConfigKey] = thinkingLevel; +- } +- const contextTier = this._createContextTierConfigSchemaProperty(m.billing); +- if (contextTier) { +- properties[ContextTierConfigKey] = contextTier; +- } +- if (Object.keys(properties).length === 0) { +- return undefined; +- } +- return { type: 'object', properties }; +- } +- - private _serializeModelSelection(model: ModelSelection): string { - return JSON.stringify(model); - } @@ -1213,10 +1534,10 @@ index 23d008d0f91..00000000000 - }; - } - -- private async _listModels(): Promise { +- private async _listModels(gitHubToken: string): Promise { - this._logService.info('[Copilot] Listing models...'); - const client = await this._ensureClient(); -- const models = await client.listModels(); +- const { models } = await client.rpc.models.list({ gitHubToken }); - const result = models.map((m): IAgentModelInfo => ({ - provider: this.id, - id: m.id, @@ -1225,42 +1546,55 @@ index 23d008d0f91..00000000000 - // no fixed context window — surface them with maxContextWindow undefined. - maxContextWindow: m.capabilities?.limits?.max_context_window_tokens, - supportsVision: !!m.capabilities?.supports?.vision, -- configSchema: this._createThinkingLevelConfigSchema(m.supportedReasoningEfforts, m.defaultReasoningEffort), +- configSchema: this._createModelConfigSchema(m), - policyState: m.policy?.state as PolicyState | undefined, -- _meta: typeof m.billing?.multiplier === 'number' ? { -- multiplierNumeric: m.billing.multiplier, -- } : undefined, +- _meta: this._createModelPricingMeta(m), - })); -- this._logService.info(`[Copilot] Found ${result.length} models`); +- this._logService.info(`[Copilot] Found ${result.length} models: ${result.map(m => m.name).join(', ')}`); - return result; - } - -- async createSession(config?: IAgentCreateSessionConfig): Promise { -- if (!config?.workingDirectory) { -- throw new Error('workingDirectory is required to create a Copilot session'); +- /** +- * Resolves the working directory for a {@link createSession} call: the caller-supplied folder, else a +- * still-provisional session's folder for an idempotent re-create, else a freshly created empty directory under the +- * OS temp dir (used when the editor has no workspace open). +- */ +- private async _resolveCreateWorkingDirectory(sessionConfig: IAgentCreateSessionConfig, sessionId: string): Promise { +- const existing = sessionConfig.workingDirectory ?? this._provisionalSessions.get(sessionId)?.workingDirectory; +- if (existing) { +- return existing; - } +- const tmpPath = await fs.mkdtemp(join(os.tmpdir(), 'agent-host-session-')); +- const workingDirectory = URI.file(tmpPath); +- this._logService.trace(`[Copilot] No workingDirectory provided, defaulting to temp directory: ${workingDirectory.fsPath}`); +- return workingDirectory; +- } - -- this._logService.info(`[Copilot] Creating session... ${config?.model ? `model=${config.model.id}` : ''}`); -- const client = await this._ensureClient(); +- async createSession(config?: IAgentCreateSessionConfig): Promise { +- const sessionConfig = config ?? {}; - +- this._logService.info(`[Copilot] Creating session... ${sessionConfig.model ? `model=${sessionConfig.model.id}` : ''}`); +- const sessionId = sessionConfig.session ? AgentSession.id(sessionConfig.session) : generateUuid(); +- const workingDirectory = await this._resolveCreateWorkingDirectory(sessionConfig, sessionId); +- const client = await this._ensureClient(); - // When forking, use the SDK's sessions.fork RPC. Forking from a source - // session that has no turns is equivalent to creating a fresh session; - // in that case the agent service drops `config.fork` before calling us, - // so we never enter this branch with a provisional source. -- if (config?.fork) { -- const sourceSessionId = AgentSession.id(config.fork.session); +- if (sessionConfig.fork) { +- const sourceSessionId = AgentSession.id(sessionConfig.fork.session); - - // Serialize against the source session to prevent concurrent - // modifications while we read its state. - return this._sessionSequencer.queue(sourceSessionId, async () => { -- this._logService.info(`[Copilot] Forking session ${sourceSessionId} at turnId=${config.fork!.turnId}`); +- this._logService.info(`[Copilot] Forking session ${sourceSessionId} at turnId=${sessionConfig.fork!.turnId}`); - - const sourceEntry = this._sessions.get(sourceSessionId) ?? await this._resumeSession(sourceSessionId); - - // Look up the SDK event ID for the turn *after* the fork point. - // toEventId is exclusive — events before it are included. - // If there's no next turn, omit toEventId to include all events. -- const toEventId = await sourceEntry.getNextTurnEventId(config.fork!.turnId); +- const toEventId = await sourceEntry.getNextTurnEventId(sessionConfig.fork!.turnId); - - const forkResult = await client.rpc.sessions.fork({ - sessionId: sourceSessionId, @@ -1274,7 +1608,7 @@ index 23d008d0f91..00000000000 - const targetDbDir = this._sessionDataService.getSessionDataDirById(newSessionId); - const targetDbPath = URI.joinPath(targetDbDir, SESSION_DB_FILENAME); - try { -- const sourceDbRef = await this._sessionDataService.tryOpenDatabase(config.fork!.session); +- const sourceDbRef = await this._sessionDataService.tryOpenDatabase(sessionConfig.fork!.session); - if (sourceDbRef) { - try { - await fs.mkdir(targetDbDir.fsPath, { recursive: true }); @@ -1291,18 +1625,18 @@ index 23d008d0f91..00000000000 - const agentSession = await this._resumeSession(newSessionId); - - // Remap turn IDs to match the new protocol turn IDs -- if (config.fork!.turnIdMapping) { -- await agentSession.remapTurnIds(config.fork!.turnIdMapping); +- if (sessionConfig.fork!.turnIdMapping) { +- await agentSession.remapTurnIds(sessionConfig.fork!.turnIdMapping); - } - - const session = agentSession.sessionUri; - this._logService.info(`[Copilot] Forked session created: ${session.toString()}`); -- const project = await projectFromCopilotContext({ cwd: config.workingDirectory?.fsPath }, this._gitService); -- await this._storeSessionMetadata(session, config.model, config.workingDirectory, config.workingDirectory, project, true); -- if (config.agent !== undefined) { -- await this._storeSessionAgentMetadata(session, config.agent); +- const project = await projectFromCopilotContext({ cwd: workingDirectory.fsPath }, this._gitService); +- await this._storeSessionMetadata(session, sessionConfig.model, workingDirectory, workingDirectory, project, true); +- if (sessionConfig.agent !== undefined) { +- await this._storeSessionAgentMetadata(session, sessionConfig.agent); - } -- return { session, workingDirectory: config.workingDirectory, ...(project ? { project } : {}) }; +- return { session, workingDirectory, ...(project ? { project } : {}) }; - }); - } - @@ -1312,7 +1646,6 @@ index 23d008d0f91..00000000000 - // {@link _materializeProvisional}. Until then this session occupies - // only an in-memory slot plus a state-manager entry, so a workspace - // switch (or quick close) costs nothing on disk. -- const sessionId = config.session ? AgentSession.id(config.session) : generateUuid(); - const sessionUri = AgentSession.uri(this.id, sessionId); - - // Idempotency for already-materialized sessions: a duplicate @@ -1323,8 +1656,8 @@ index 23d008d0f91..00000000000 - // message. - if (this._sessions.has(sessionId)) { - this._logService.info(`[Copilot] createSession is a no-op: session already materialized: ${sessionUri.toString()}`); -- const project = await projectFromCopilotContext({ cwd: config.workingDirectory.fsPath }, this._gitService); -- return { session: sessionUri, workingDirectory: config.workingDirectory, ...(project ? { project } : {}) }; +- const project = await projectFromCopilotContext({ cwd: workingDirectory.fsPath }, this._gitService); +- return { session: sessionUri, workingDirectory, ...(project ? { project } : {}) }; - } - - // Idempotent: a duplicate `createSession` for a still-provisional URI @@ -1340,36 +1673,36 @@ index 23d008d0f91..00000000000 - // Seed active-client snapshot if the client claimed it eagerly. This - // runs identically for provisional and real sessions; the SDK side - // of activeClient state isn't engaged until materialization. -- if (config.activeClient) { -- const ac = this._getOrCreateActiveClient(sessionUri, config.workingDirectory); -- ac.updateTools(config.activeClient.clientId, config.activeClient.tools); -- if (config.activeClient.customizations !== undefined) { +- if (sessionConfig.activeClient) { +- const ac = this._getOrCreateActiveClient(sessionUri, workingDirectory); +- ac.updateTools(sessionConfig.activeClient.clientId, sessionConfig.activeClient.tools); +- if (sessionConfig.activeClient.customizations !== undefined) { - // Provisional eager-create: no session-state listener is - // hooked up yet, so suppress action events. The session - // reads the final view via its initial snapshot once it - // materializes. -- await ac.pluginController.sync(config.activeClient.clientId, config.activeClient.customizations, { quiet: true }); +- await ac.pluginController.sync(sessionConfig.activeClient.clientId, sessionConfig.activeClient.customizations, { quiet: true }); - } - } - - // Compute project metadata cheaply from the original working dir. - // Worktrees aren't created until materialization, so the project is - // reported relative to the user's chosen folder. -- const project = await projectFromCopilotContext({ cwd: config.workingDirectory.fsPath }, this._gitService); +- const project = await projectFromCopilotContext({ cwd: workingDirectory.fsPath }, this._gitService); - - if (!alreadyProvisional) { - this._provisionalSessions.set(sessionId, { - sessionId, - sessionUri, -- workingDirectory: config.workingDirectory, -- model: config.model, -- agent: config.agent, +- workingDirectory, +- model: sessionConfig.model, +- agent: sessionConfig.agent, - project, - }); - } - - this._logService.info(`[Copilot] Session created (provisional): ${sessionUri.toString()}`); -- return { session: sessionUri, workingDirectory: config.workingDirectory, provisional: true, ...(project ? { project } : {}) }; +- return { session: sessionUri, workingDirectory, provisional: true, ...(project ? { project } : {}) }; - } - - /** @@ -1428,11 +1761,12 @@ index 23d008d0f91..00000000000 - workingDirectory, - resolvedAgentName, - snapshot, +- activeClientState: activeClient.state, - shellManager, - githubToken: this._githubToken, - model: provisional.model, - }; -- agentSession = this._createAgentSession(launchPlan, customizationDirectory); +- agentSession = this._createAgentSession(launchPlan, customizationDirectory, activeClient); - await agentSession.initializeSession(); - this._registerInitializedSession(sessionId, agentSession); - } catch (error) { @@ -1450,7 +1784,7 @@ index 23d008d0f91..00000000000 - } - - // Capture the per-session baseline (turn/0) git checkpoint so -- // per-turn diffs computed on `SessionTurnComplete` can reflect the +- // per-turn diffs computed on `ChatTurnComplete` can reflect the - // full working-tree delta — including terminal-tool edits that are - // invisible to the FileEditTracker pipeline. Best-effort: a - // non-git folder or capture failure leaves the session running @@ -1510,7 +1844,7 @@ index 23d008d0f91..00000000000 - ...(branchProperty ? { [SessionConfigKey.Branch]: branchProperty } : {}), - }); - -- const values = sessionSchema.validateOrDefault(params.config, { +- const values = sessionSchema.validateOrDefault(migrateLegacyAutopilotConfig(params.config), { - [SessionConfigKey.Isolation]: isolationValue, - [SessionConfigKey.AutoApprove]: 'default' satisfies AutoApproveLevel, - [SessionConfigKey.Mode]: 'interactive' satisfies SessionMode, @@ -1542,11 +1876,11 @@ index 23d008d0f91..00000000000 - return activeClient.pluginController.sync(clientId, customizations); - } - -- setClientTools(session: URI, clientId: string, tools: ToolDefinition[]): void { +- setClientTools(session: URI, clientId: string | undefined, tools: ToolDefinition[]): void { - const sessionId = AgentSession.id(session); - const activeClient = this._getOrCreateActiveClient(session, undefined); - const hasCachedEntry = this._sessions.has(sessionId); -- this._logService.info(`[Copilot:${sessionId}] setClientTools: clientId=${clientId}, tools=[${tools.map(t => t.name).join(', ') || '(none)'}], hasCachedSdkSession=${hasCachedEntry}`); +- this._logService.info(`[Copilot:${sessionId}] setClientTools: clientId=${clientId ?? '(none)'}, tools=[${tools.map(t => t.name).join(', ') || '(none)'}], hasCachedSdkSession=${hasCachedEntry}`); - activeClient.updateTools(clientId, tools); - } - @@ -1558,8 +1892,10 @@ index 23d008d0f91..00000000000 - while ((parsed = parseSubagentSessionUri(target))) { - target = parsed.parentSession; - } -- const sessionId = AgentSession.id(target); -- const entry = this._sessions.get(sessionId); +- // The completion may belong to a peer chat (tracked in `_chatSessions` +- // keyed by chat URI) rather than the default/parent session. +- const entry = this._sessions.get(AgentSession.id(target)) +- ?? this._chatSessions.get(target.toString()); - entry?.handleClientToolCallComplete(toolCallId, result); - } - @@ -1572,7 +1908,20 @@ index 23d008d0f91..00000000000 - } - } - -- async sendMessage(session: URI, prompt: string, attachments?: readonly MessageAttachment[], turnId?: string): Promise { +- async sendMessage(session: URI, prompt: string, attachments?: readonly MessageAttachment[], turnId?: string, chat?: URI): Promise { +- // Additional (non-default) chats are backed by their own SDK +- // conversation tracked in `_chatSessions`, keyed by the chat URI. +- if (chat && !isDefaultChatUri(chat)) { +- const entry = await this._ensureChatSession(session, chat); +- if (!entry) { +- throw new Error(`[Copilot] sendMessage for unknown chat: ${chat.toString()}`); +- } +- if (turnId) { +- entry.resetTurnState(turnId); +- } +- await entry.send(prompt, attachments, turnId, this._resolveSdkMode(session)); +- return; +- } - const sessionId = AgentSession.id(session); - await this._sessionSequencer.queue(sessionId, async () => { - await this._activeClients.get(session)?.pluginController.retryFailedClientSyncIfNeeded(); @@ -1593,14 +1942,14 @@ index 23d008d0f91..00000000000 - const activeClient = this._activeClients.get(session); - const hadCachedEntry = !!entry; - this._logService.info(`[Copilot:${sessionId}] sendMessage: cachedEntry=${hadCachedEntry}, hasActiveClient=${!!activeClient}, activeClientId=${activeClient ? '(set)' : '(none)'}`); -- if (entry && activeClient && await activeClient.isOutdated(entry.appliedSnapshot)) { -- this._logService.info(`[Copilot:${sessionId}] Session config changed (isOutdated=true), refreshing session. snapshotClientId=${entry.appliedSnapshot.clientId}`); +- if (entry && activeClient && await activeClient.requiresRestart(entry.appliedSnapshot)) { +- this._logService.info(`[Copilot:${sessionId}] Session config changed (requiresRestart=true), refreshing session. clientId=${activeClient.state.clientId ?? '(none)'}`); - this._sessions.deleteAndDispose(sessionId); - entry = undefined; - } - - if (!entry) { -- this._logService.info(`[Copilot:${sessionId}] No cached entry${hadCachedEntry ? ' (was evicted by isOutdated)' : ''}, calling _resumeSession`); +- this._logService.info(`[Copilot:${sessionId}] No cached entry${hadCachedEntry ? ' (was evicted by requiresRestart)' : ''}, calling _resumeSession`); - } - entry ??= await this._resumeSession(sessionId); - @@ -1635,17 +1984,18 @@ index 23d008d0f91..00000000000 - } - - /** -- * Translates the AHP-side `(mode, autoApprove)` pair to the Copilot -- * SDK's three-mode space (`interactive` / `plan` / `autopilot`): +- * Translates the AHP-side `mode` to the Copilot SDK's three-mode space +- * (`interactive` / `plan` / `autopilot`). With Autopilot living on the +- * `mode` axis the mapping is now direct: +- * +- * - `mode='plan'` → SDK `plan`. +- * - `mode='autopilot'` → SDK `autopilot` (autonomous, continue-until-done). +- * - `mode='interactive'` → SDK `interactive`. - * -- * - `mode='plan'` → SDK `plan` (auto-approval is irrelevant; the -- * agent host's existing session-state auto-approval logic handles -- * `plan.md` writes). -- * - `mode='interactive'` + `autoApprove='autopilot'` → SDK `autopilot` -- * (the SDK auto-approves all tool calls). -- * - `mode='interactive'` + any other autoApprove → SDK `interactive` -- * (the agent host's own auto-approval logic continues to gate tool -- * calls based on `autoApprove`). +- * Tool auto-approval is governed independently by the orthogonal +- * `autoApprove` axis (Default / Bypass), enforced by the agent +- * host's own permission handler — which the SDK still invokes even under +- * autopilot mode. - * - * Returns `undefined` when no mode is configured for the session, so - * the SDK's current mode is left untouched. @@ -1653,14 +2003,16 @@ index 23d008d0f91..00000000000 - private _resolveSdkMode(session: URI): CopilotSdkMode | undefined { - const sessionKey = session.toString(); - const mode = this._configurationService.getEffectiveValue(sessionKey, platformSessionSchema, SessionConfigKey.Mode); -- if (mode === 'plan') { -- return 'plan'; -- } -- if (mode === 'interactive') { -- const autoApprove = this._configurationService.getEffectiveValue(sessionKey, platformSessionSchema, SessionConfigKey.AutoApprove); -- return autoApprove === 'autopilot' ? 'autopilot' : 'interactive'; +- switch (mode) { +- case 'plan': +- return 'plan'; +- case 'autopilot': +- return 'autopilot'; +- case 'interactive': +- return 'interactive'; +- default: +- return undefined; - } -- return undefined; - } - - setPendingMessages(session: URI, steeringMessage: PendingMessage | undefined, _queuedMessages: readonly PendingMessage[]): void { @@ -1677,11 +2029,20 @@ index 23d008d0f91..00000000000 - } - - // Queued messages are consumed by the server (AgentSideEffects) -- // which dispatches SessionTurnStarted and calls sendMessage directly. +- // which dispatches ChatTurnStarted and calls sendMessage directly. - // No SDK-level enqueue is needed. - } - - async getSessionMessages(session: URI): Promise { +- // An additional (non-default) peer chat is addressed by its `ahp-chat` +- // channel URI. Resume its backing SDK conversation and return its turns. +- const chatInfo = parseChatUri(session); +- if (chatInfo && !isDefaultChatUri(session)) { +- const parentSession = URI.parse(chatInfo.session); +- const entry = await this._ensureChatSession(parentSession, session); +- return entry ? entry.getMessages() : []; +- } +- - // If the URI describes a subagent child session (`/subagent/`), - // load the parent's events once and extract the child's filtered turns. - const subagentInfo = parseSubagentSessionUri(session); @@ -1738,6 +2099,13 @@ index 23d008d0f91..00000000000 - async disposeSession(session: URI): Promise { - const sessionId = AgentSession.id(session); - await this._sessionSequencer.queue(sessionId, async () => { +- // Remove the session from the SDK's on-disk store first so it doesn't reappear in `listSessions()` after a +- // restart, and so that any final persist triggered by in-memory teardown can't recreate it. Provisional +- // sessions were never persisted, so there is nothing to delete on the SDK side. +- if (!this._provisionalSessions.has(sessionId)) { +- const client = await this._ensureClient(); +- await client.deleteSession(sessionId); +- } - await this._destroyAndDisposeSession(sessionId); - }); - } @@ -1826,7 +2194,11 @@ index 23d008d0f91..00000000000 - } - } - -- async abortSession(session: URI): Promise { +- async abortSession(session: URI, chat?: URI): Promise { +- if (chat && !isDefaultChatUri(chat)) { +- await this._chatSessions.get(chat.toString())?.abort(); +- return; +- } - const sessionId = AgentSession.id(session); - await this._sessionSequencer.queue(sessionId, async () => { - const entry = this._sessions.get(sessionId); @@ -1836,66 +2208,263 @@ index 23d008d0f91..00000000000 - }); - } - -- async truncateSession(session: URI, turnId?: string): Promise { -- const sessionId = AgentSession.id(session); -- if (this._provisionalSessions.has(sessionId)) { +- async createChat(session: URI, chat: URI, options?: IAgentCreateChatOptions): Promise { +- if (isDefaultChatUri(chat)) { +- return; +- } +- const chatKey = chat.toString(); +- if (this._chatSessions.has(chatKey)) { - return; - } +- const sessionId = AgentSession.id(session); - await this._sessionSequencer.queue(sessionId, async () => { -- this._logService.info(`[Copilot:${sessionId}] Truncating session${turnId !== undefined ? ` at turnId=${turnId}` : ' (all turns)'}`); -- -- // Ensure the session is loaded so we can use the SDK RPC -- const entry = this._sessions.get(sessionId) ?? await this._resumeSession(sessionId); -- -- // Look up the SDK event ID for the truncation boundary. -- // The protocol semantics: turnId is the last turn to KEEP. -- // The SDK semantics: eventId and all events after it are removed. -- // So we need the event ID of the *next* turn after turnId. -- // For "remove all", we need the first turn's event ID. -- let eventId: string | undefined; -- if (turnId) { -- eventId = await entry.getNextTurnEventId(turnId); -- } else { -- eventId = await entry.getFirstTurnEventId(); +- // Re-check inside the per-session sequencer: the outer `has` check +- // above is only a fast early-out. If two `createChat` calls for the +- // same chat URI race, both can pass that outer check; the sequencer +- // serializes them, so the second task must re-check here to avoid +- // overwriting (and disposing) the conversation the first one set. +- if (this._chatSessions.has(chatKey)) { +- return; - } -- -- if (eventId) { -- await entry.truncateAtEventId(eventId, turnId); -- } else { -- this._logService.info(`[Copilot:${sessionId}] No event ID found for truncation, nothing to truncate`); +- // Resolve the owning session so the new chat inherits its working +- // directory scope. The parent may be provisional (no SDK session +- // yet); in that case use its provisional working directory. +- const parentEntry = this._sessions.get(sessionId); +- const workingDirectory = parentEntry?.workingDirectory +- ?? this._provisionalSessions.get(sessionId)?.workingDirectory; +- const client = await this._ensureClient(); +- const chatSdkId = generateUuid(); +- // Peer chats share the owning session's ActiveClient so that +- // client tool / customization updates (which are keyed by the +- // session URI via `setClientTools` / `setClientCustomizations`) +- // reach the additional chat's SDK conversation. Keying it by the +- // chat URI instead would snapshot empty/stale tools and never see +- // subsequent updates, and would also leak (nothing disposes a +- // chat-keyed ActiveClient). +- const activeClient = this._getOrCreateActiveClient(session, workingDirectory); +- const snapshot = await activeClient.snapshot(); +- const shellManager = this._instantiationService.createInstance(ShellManager, chat, workingDirectory); +- const launchPlan: CopilotSessionLaunchPlan = { +- kind: 'create', +- client, +- sessionId: chatSdkId, +- workingDirectory, +- resolvedAgentName: undefined, +- snapshot, +- activeClientState: activeClient.state, +- shellManager, +- githubToken: this._githubToken, +- model: options?.model, +- }; +- let agentSession: CopilotAgentSession | undefined; +- try { +- agentSession = this._createAgentSession(launchPlan, workingDirectory, activeClient, chat); +- await agentSession.initializeSession(); +- this._chatSessions.set(chatKey, agentSession); +- const parsed = parseChatUri(chat); +- if (parsed) { +- const persisted = await this._readPersistedChats(session); +- persisted.set(parsed.chatId, { sdkSessionId: chatSdkId, ...(options?.model ? { model: options.model } : {}) }); +- await this._writePersistedChats(session, persisted); +- } +- this._logService.info(`[Copilot] Created additional chat ${chatKey} in session ${session.toString()}`); +- } catch (error) { +- agentSession?.dispose(); +- throw error; - } -- -- this._logService.info(`[Copilot:${sessionId}] Session truncated`); - }); - } - -- async changeModel(session: URI, model: ModelSelection): Promise { -- const sessionId = AgentSession.id(session); -- const provisional = this._provisionalSessions.get(sessionId); -- if (provisional) { -- provisional.model = model; +- async disposeChat(session: URI, chat: URI): Promise { +- if (isDefaultChatUri(chat)) { - return; - } -- const entry = this._sessions.get(sessionId); -- if (entry) { -- await entry.setModel(model.id, getCopilotReasoningEffort(model)); +- const chatKey = chat.toString(); +- // Resolve the chat's backing SDK conversation id — from the in-memory +- // session if present, otherwise from the persisted catalog — so we can +- // delete it from the SDK's on-disk store. Without this a fresh process +- // could re-resume an orphaned conversation that no longer has a catalog +- // entry. Best-effort: a missing id still drops the catalog entry below. +- const parsed = parseChatUri(chat); +- let sdkSessionId = this._chatSessions.get(chatKey)?.sessionId; +- if (parsed) { +- const persisted = await this._readPersistedChats(session); +- sdkSessionId ??= persisted.get(parsed.chatId)?.sdkSessionId; +- if (persisted.delete(parsed.chatId)) { +- await this._writePersistedChats(session, persisted); +- } +- } +- this._chatSessions.deleteAndDispose(chatKey); +- if (sdkSessionId) { +- try { +- const client = await this._ensureClient(); +- await client.deleteSession(sdkSessionId); +- } catch (err) { +- this._logService.warn(`[Copilot] Failed to delete SDK session for chat ${chatKey}: ${err instanceof Error ? err.message : String(err)}`); +- } - } -- await this._storeSessionMetadata(session, model, undefined, undefined, undefined); - } - -- async changeAgent(session: URI, agent: AgentSelection | undefined): Promise { -- const sessionId = AgentSession.id(session); -- const provisional = this._provisionalSessions.get(sessionId); -- if (provisional) { -- provisional.agent = agent; -- return; +- /** +- * Returns the catalog of additional (non-default) peer chats persisted for a +- * session, as `ahp-chat` channel URIs. Used by the agent service to +- * re-register peer chats (and seed their history) when a session is restored +- * after a process restart. +- */ +- async getChats(session: URI): Promise { +- const persisted = await this._readPersistedChats(session); +- const result: URI[] = []; +- for (const chatId of persisted.keys()) { +- result.push(URI.parse(buildChatUri(session.toString(), chatId))); - } -- const entry = this._sessions.get(sessionId); -- if (entry) { -- // Resolve the URI → SDK name from the session's currently-applied -- // plugin snapshot. If the agent is no longer present (plugin -- // removed, never loaded), pass `undefined` so the SDK clears its -- // selection rather than silently keeping the previous one. +- return result; +- } +- +- /** +- * Returns the SDK-backed {@link CopilotAgentSession} for an additional peer +- * chat, resuming its persisted SDK conversation if it is not already in +- * memory (e.g. after a process restart). Returns `undefined` when the chat +- * has no persisted backing conversation. +- */ +- private async _ensureChatSession(session: URI, chat: URI): Promise { +- const chatKey = chat.toString(); +- const existing = this._chatSessions.get(chatKey); +- if (existing) { +- return existing; +- } +- const parsed = parseChatUri(chat); +- if (!parsed) { +- return undefined; +- } +- const sessionId = AgentSession.id(session); +- return this._sessionSequencer.queue(sessionId, async () => { +- const again = this._chatSessions.get(chatKey); +- if (again) { +- return again; +- } +- const persisted = await this._readPersistedChats(session); +- const info = persisted.get(parsed.chatId); +- if (!info) { +- return undefined; +- } +- const parentEntry = this._sessions.get(sessionId) ?? await this._resumeSession(sessionId).catch(() => undefined); +- const workingDirectory = parentEntry?.workingDirectory +- ?? this._provisionalSessions.get(sessionId)?.workingDirectory; +- if (!workingDirectory) { +- this._logService.warn(`[Copilot] Cannot resume chat ${chatKey}: missing working directory`); +- return undefined; +- } +- const client = await this._ensureClient(); +- const activeClient = this._getOrCreateActiveClient(session, workingDirectory); +- const snapshot = await activeClient.snapshot(); +- const shellManager = this._instantiationService.createInstance(ShellManager, chat, workingDirectory); +- const launchPlan: CopilotSessionLaunchPlan = { +- kind: 'resume', +- client, +- sessionId: info.sdkSessionId, +- workingDirectory, +- resolvedAgentName: undefined, +- snapshot, +- activeClientState: activeClient.state, +- shellManager, +- githubToken: this._githubToken, +- fallback: { model: info.model }, +- }; +- let agentSession: CopilotAgentSession | undefined; +- try { +- agentSession = this._createAgentSession(launchPlan, workingDirectory, activeClient, chat); +- await agentSession.initializeSession(); +- this._chatSessions.set(chatKey, agentSession); +- this._logService.info(`[Copilot] Resumed additional chat ${chatKey} in session ${session.toString()}`); +- return agentSession; +- } catch (error) { +- agentSession?.dispose(); +- this._logService.warn(`[Copilot] Failed to resume additional chat ${chatKey}: ${error instanceof Error ? error.message : String(error)}`); +- return undefined; +- } +- }); +- } +- +- async truncateSession(session: URI, turnId?: string): Promise { +- const sessionId = AgentSession.id(session); +- if (this._provisionalSessions.has(sessionId)) { +- return; +- } +- await this._sessionSequencer.queue(sessionId, async () => { +- this._logService.info(`[Copilot:${sessionId}] Truncating session${turnId !== undefined ? ` at turnId=${turnId}` : ' (all turns)'}`); +- +- // Ensure the session is loaded so we can use the SDK RPC +- const entry = this._sessions.get(sessionId) ?? await this._resumeSession(sessionId); +- +- // Look up the SDK event ID for the truncation boundary. +- // The protocol semantics: turnId is the last turn to KEEP. +- // The SDK semantics: eventId and all events after it are removed. +- // So we need the event ID of the *next* turn after turnId. +- // For "remove all", we need the first turn's event ID. +- let eventId: string | undefined; +- if (turnId) { +- eventId = await entry.getNextTurnEventId(turnId); +- } else { +- eventId = await entry.getFirstTurnEventId(); +- } +- +- if (eventId) { +- await entry.truncateAtEventId(eventId, turnId); +- } else { +- this._logService.info(`[Copilot:${sessionId}] No event ID found for truncation, nothing to truncate`); +- } +- +- this._logService.info(`[Copilot:${sessionId}] Session truncated`); +- }); +- } +- +- async changeModel(session: URI, model: ModelSelection, chat?: URI): Promise { +- // Additional (non-default) chats are backed by their own SDK +- // conversation tracked in `_chatSessions`; apply the change there and +- // skip the session-level metadata store (peer chats are not persisted +- // per-chat). +- if (chat && !isDefaultChatUri(chat)) { +- await this._chatSessions.get(chat.toString())?.setModel(model.id, getCopilotReasoningEffort(model), getCopilotContextTier(model)); +- return; +- } +- const sessionId = AgentSession.id(session); +- const provisional = this._provisionalSessions.get(sessionId); +- if (provisional) { +- provisional.model = model; +- return; +- } +- const entry = this._sessions.get(sessionId); +- if (entry) { +- await entry.setModel(model.id, getCopilotReasoningEffort(model), getCopilotContextTier(model)); +- } +- await this._storeSessionMetadata(session, model, undefined, undefined, undefined); +- } +- +- async changeAgent(session: URI, agent: AgentSelection | undefined, chat?: URI): Promise { +- // Additional (non-default) chats own their SDK conversation in +- // `_chatSessions`. Apply the agent to that conversation (resolving the +- // URI → SDK name against its own applied snapshot) and skip the +- // session-level metadata store. +- if (chat && !isDefaultChatUri(chat)) { +- const chatEntry = this._chatSessions.get(chat.toString()); +- if (chatEntry) { +- const resolvedAgentName = agent ? await this._resolveAgentName(session, chatEntry.appliedSnapshot, agent) : undefined; +- await chatEntry.setAgent(resolvedAgentName); +- } +- return; +- } +- const sessionId = AgentSession.id(session); +- const provisional = this._provisionalSessions.get(sessionId); +- if (provisional) { +- provisional.agent = agent; +- return; +- } +- const entry = this._sessions.get(sessionId); +- if (entry) { +- // Resolve the URI → SDK name from the session's currently-applied +- // plugin snapshot. If the agent is no longer present (plugin +- // removed, never loaded), pass `undefined` so the SDK clears its +- // selection rather than silently keeping the previous one. - const resolvedAgentName = agent ? await this._resolveAgentName(session, entry.appliedSnapshot, agent) : undefined; - await entry.setAgent(resolvedAgentName); - } @@ -1921,14 +2490,24 @@ index 23d008d0f91..00000000000 - return; - } - } +- for (const [, chat] of this._chatSessions) { +- if (chat.respondToPermissionRequest(requestId, approved)) { +- return; +- } +- } - } - -- respondToUserInputRequest(requestId: string, response: SessionInputResponseKind, answers?: Record): void { +- respondToUserInputRequest(requestId: string, response: ChatInputResponseKind, answers?: Record): void { - for (const [, session] of this._sessions) { - if (session.respondToUserInputRequest(requestId, response, answers)) { - return; - } - } +- for (const [, chat] of this._chatSessions) { +- if (chat.respondToUserInputRequest(requestId, response, answers)) { +- return; +- } +- } - } - - /** @@ -1942,11 +2521,25 @@ index 23d008d0f91..00000000000 - - // ---- helpers ------------------------------------------------------------ - +- /** +- * Disposes every peer chat (tracked in {@link _chatSessions}) whose +- * owning session matches `sessionId`. The chat URI encodes its parent +- * session, so we recover it via {@link parseChatUri}. +- */ +- private _disposeChildChats(sessionId: string): void { +- for (const chatKey of [...this._chatSessions.keys()]) { +- const parsed = parseChatUri(URI.parse(chatKey)); +- if (parsed && AgentSession.id(parsed.session) === sessionId) { +- this._chatSessions.deleteAndDispose(chatKey); +- } +- } +- } +- - private _getOrCreateActiveClient(session: URI, directory: URI | undefined): ActiveClient { - let client = this._activeClients.get(session); - if (!client) { - const pluginController = this._plugins.createSessionController(directory); -- client = new ActiveClient(session, pluginController, this._onDidSessionProgress); +- client = this._instantiationService.createInstance(ActiveClient, session, pluginController, this._onDidSessionProgress); - this._activeClients.set(session, client); - } else if (directory) { - client.pluginController.setDirectory(directory); @@ -1962,8 +2555,8 @@ index 23d008d0f91..00000000000 - * {@link _resumeSession} for the same id cannot dispose this entry mid-init - * via {@link DisposableMap.set}. - */ -- private _createAgentSession(launchPlan: CopilotSessionLaunchPlan, customizationDirectory: URI | undefined): CopilotAgentSession { -- const sessionUri = AgentSession.uri(this.id, launchPlan.sessionId); +- private _createAgentSession(launchPlan: CopilotSessionLaunchPlan, customizationDirectory: URI | undefined, activeClient: ActiveClient, channelUri?: URI): CopilotAgentSession { +- const sessionUri = channelUri ?? AgentSession.uri(this.id, launchPlan.sessionId); - - const agentSession = this._instantiationService.createInstance( - CopilotAgentSession, @@ -1977,9 +2570,14 @@ index 23d008d0f91..00000000000 - workingDirectory: launchPlan.workingDirectory, - customizationDirectory, - clientSnapshot: launchPlan.snapshot, +- activeClientState: launchPlan.activeClientState, +- resolveMcpChildId: name => findMcpChildId(activeClient.pluginController.getCustomizations(), name), +- serverToolHost: this._serverToolHost, - }, - ); - +- this._mcpNotificationSubs.set(launchPlan.sessionId, agentSession.onMcpNotification(n => this._onMcpNotification.fire(n))); +- - return agentSession; - } - @@ -2003,6 +2601,10 @@ index 23d008d0f91..00000000000 - } - - private async _destroyAndDisposeSession(sessionId: string): Promise { +- // Tear down any peer chats owned by this session first so their SDK +- // conversations don't leak when the parent is deleted/disposed +- // without each chat being individually disposed via `disposeChat`. +- this._disposeChildChats(sessionId); - // Provisional sessions have no SDK session, no worktree, and no - // on-disk metadata — drop the in-memory record and clean up the - // active-client snapshot. The state-manager entry is removed by the @@ -2024,6 +2626,7 @@ index 23d008d0f91..00000000000 - } - } - this._sessions.deleteAndDispose(sessionId); +- this._mcpNotificationSubs.deleteAndDispose(sessionId); - this._activeClients.get(sessionUri)?.dispose(); - this._activeClients.delete(sessionUri); - await this._removeCreatedWorktree(sessionId); @@ -2075,6 +2678,7 @@ index 23d008d0f91..00000000000 - workingDirectory, - resolvedAgentName, - snapshot, +- activeClientState: activeClient.state, - shellManager, - githubToken: this._githubToken, - fallback: { @@ -2082,7 +2686,7 @@ index 23d008d0f91..00000000000 - }, - }; - -- const agentSession = this._createAgentSession(launchPlan, customizationDirectory); +- const agentSession = this._createAgentSession(launchPlan, customizationDirectory, activeClient); - try { - await agentSession.initializeSession(); - } catch (err) { @@ -2119,8 +2723,7 @@ index 23d008d0f91..00000000000 - } - - const worktreesRoot = getCopilotWorktreesRoot(repositoryRoot); -- const branchNameHint = prompt ? getCopilotBranchNameHintFromMessage(prompt) : undefined; -- const branchName = getCopilotWorktreeBranchName(sessionId, branchNameHint); +- const branchName = await this._branchNameGenerator.generateBranchName({ sessionId, message: prompt, githubToken: this._githubToken }); - const worktree = URI.joinPath(worktreesRoot, getCopilotWorktreeName(branchName)); - await fs.mkdir(worktreesRoot.fsPath, { recursive: true }); - const baseBranch = typeof config.config[SessionConfigKey.Branch] === 'string' ? config.config[SessionConfigKey.Branch] as string : undefined; @@ -2167,6 +2770,66 @@ index 23d008d0f91..00000000000 - private static readonly _META_WORKTREE_BRANCH = 'copilot.worktree.branchName'; - private static readonly _META_WORKTREE_PATH = 'copilot.worktree.path'; - private static readonly _META_WORKTREE_REPOSITORY_ROOT = 'copilot.worktree.repositoryRoot'; +- /** Persisted catalog of additional (non-default) peer chats, keyed by chatId. */ +- private static readonly _META_CHATS = 'copilot.chats'; +- +- /** +- * Reads the persisted peer-chat catalog for a session. Each entry maps a +- * chatId (the `ahp-chat` authority) to the SDK conversation that backs it +- * (and its optional model override), so the chat can be resumed after a +- * restart even though {@link _chatSessions} is empty in a fresh process. +- */ +- private async _readPersistedChats(session: URI): Promise> { +- const ref = await this._sessionDataService.tryOpenDatabase(session); +- if (!ref) { +- return new Map(); +- } +- try { +- const raw = await ref.object.getMetadata(CopilotAgent._META_CHATS); +- if (!raw) { +- return new Map(); +- } +- const parsed = JSON.parse(raw) as Record; +- const result = new Map(); +- for (const [chatId, value] of Object.entries(parsed)) { +- // The metadata blob is client-influenced and may be corrupted or +- // tampered: drop entries that don't carry a usable SDK session id +- // rather than letting an invalid id reach `client.deleteSession`. +- if (!value || typeof value !== 'object') { +- continue; +- } +- const { sdkSessionId, model } = value as { sdkSessionId?: unknown; model?: unknown }; +- if (typeof sdkSessionId !== 'string' || !sdkSessionId) { +- continue; +- } +- result.set(chatId, { sdkSessionId, ...(model ? { model: model as ModelSelection } : {}) }); +- } +- return result; +- } catch (err) { +- this._logService.warn(`[Copilot] Failed to read persisted chats for ${session.toString()}: ${err instanceof Error ? err.message : String(err)}`); +- return new Map(); +- } finally { +- ref.dispose(); +- } +- } +- +- /** Writes the persisted peer-chat catalog for a session. */ +- private async _writePersistedChats(session: URI, chats: Map): Promise { +- const dbRef = this._sessionDataService.openDatabase(session); +- try { +- // Use a null-prototype object: chatIds derive from a client-chosen +- // chat URI authority, so a value like `__proto__` would otherwise +- // pollute the prototype / corrupt the serialized payload. +- const obj: Record = Object.create(null); +- for (const [chatId, info] of chats) { +- obj[chatId] = info; +- } +- await dbRef.object.setMetadata(CopilotAgent._META_CHATS, JSON.stringify(obj)); +- } finally { +- dbRef.dispose(); +- } +- } +- - - private async _writeWorktreeMetadata(session: URI, metadata: { branchName: string; baseBranch: string | undefined; worktreePath: URI; repositoryRoot: URI }): Promise { - const dbRef = this._sessionDataService.openDatabase(session); @@ -2360,13 +3023,15 @@ index 23d008d0f91..00000000000 - readonly input?: ClientPluginCustomization; -} - +-const REFRESH_DEBOUNCE_MS = 100; +- -/** - * A per-working-directory bundle of customizations the agent host - * discovered itself from disk (workspace + user-home conventions). - * - * Owns a {@link SessionCustomizationDiscovery} (filesystem scan + -- * watchers) and a {@link SessionPluginBundler} (in-memory synthetic -- * Open Plugin under the `vscode-synced-customization:` scheme). +- * watchers) and maps discovered files into an in-memory +- * {@link IParsedPlugin} while preserving original file URIs. - * - * Refreshes itself when the discovery fires `onDidChange`. The owning - * {@link PluginController} is notified via the supplied `onDidRefresh` @@ -2376,27 +3041,26 @@ index 23d008d0f91..00000000000 - */ -class SessionDiscoveredEntry extends Disposable { - +- - private readonly _discovery: SessionCustomizationDiscovery; -- private readonly _bundler: SessionPluginBundler; -- private readonly _refreshThrottler = this._register(new Throttler()); -- private _refreshCancellationSource: CancellationTokenSource | undefined; +- private readonly _refreshDelayer = this._register(new Delayer(REFRESH_DEBOUNCE_MS)); +- private _refreshPromise: CancelablePromise | null = null; +- private _pendingRefreshNotify = false; - - private _customizations: readonly DirectoryCustomization[] = []; -- private _plugin: IParsedPlugin | undefined; +- private _directories: readonly IDiscoveredDirectory[] | undefined; - private _settled: Promise; - private readonly _fileService: IFileService; - - constructor( - workingDirectory: URI, - userHome: URI, -- private readonly _resolvePlugin: (uri: URI) => Promise, - private readonly _onDidRefresh: () => void, - private readonly _logService: ILogService, - instantiationService: IInstantiationService, - ) { - super(); - this._discovery = this._register(instantiationService.createInstance(SessionCustomizationDiscovery, workingDirectory, userHome)); -- this._bundler = this._register(instantiationService.createInstance(SessionPluginBundler, workingDirectory)); - this._fileService = instantiationService.invokeFunction(accessor => accessor.get(IFileService)); - this._settled = this._queueRefresh(false); - this._register(this._discovery.onDidChange(() => { @@ -2405,8 +3069,8 @@ index 23d008d0f91..00000000000 - } - - override dispose(): void { -- this._refreshCancellationSource?.dispose(true); -- this._refreshCancellationSource = undefined; +- this._refreshPromise?.cancel(); +- this._refreshPromise = null; - super.dispose(); - } - @@ -2418,73 +3082,75 @@ index 23d008d0f91..00000000000 - return this._customizations; - } - -- currentPlugin(): IParsedPlugin | undefined { -- return this._plugin; -- } -- - private _queueRefresh(notify: boolean): Promise { -- this._refreshCancellationSource?.cancel(); -- return this._refreshThrottler.queue(async throttlerToken => { -- const refreshCancellationSource = new CancellationTokenSource(throttlerToken); -- this._refreshCancellationSource = refreshCancellationSource; -- try { -- const didRefresh = await this._refresh(refreshCancellationSource.token); -- if (didRefresh && notify && !refreshCancellationSource.token.isCancellationRequested) { +- this._refreshPromise?.cancel(); +- this._refreshPromise = null; +- this._pendingRefreshNotify = this._pendingRefreshNotify || notify; +- +- return this._refreshDelayer.trigger(() => { +- const shouldNotify = this._pendingRefreshNotify; +- this._pendingRefreshNotify = false; +- +- const refreshPromise = this._refreshPromise = createCancelablePromise(async token => { +- const didRefresh = await this._refresh(token); +- if (didRefresh && shouldNotify) { - this._onDidRefresh(); - } -- } finally { -- if (this._refreshCancellationSource === refreshCancellationSource) { -- this._refreshCancellationSource = undefined; +- }); +- +- return refreshPromise.then(() => { +- if (this._refreshPromise === refreshPromise) { +- this._refreshPromise = null; - } -- refreshCancellationSource.dispose(); -- } +- }, err => { +- if (this._refreshPromise === refreshPromise) { +- this._refreshPromise = null; +- } +- if (err instanceof CancellationError) { +- return; +- } +- throw err; +- }); - }); - } - - private async _refresh(token: CancellationToken): Promise { - try { -- const directories = await this._discovery.directories(); +- const directories = await this._discovery.scan(token); - if (token.isCancellationRequested) { - return false; - } - -- const customizations = await toDiscoveredDirectoryCustomizations(directories, this._fileService); -- if (token.isCancellationRequested) { +- if (this._directories && areDiscoveredDirectoriesEqual(this._directories, directories)) { - return false; - } - -- const bundleResult = await this._bundler.bundle(directories, token); +- const customizations = await toDiscoveredDirectoryCustomizations(directories, this._fileService); - if (token.isCancellationRequested) { - return false; - } - -- // Don't update `_customizations` / `_plugin` when cancelled. +- // Don't update `_customizations` / `_directories` when cancelled. - // Otherwise a cancelled refresh could temporarily clear them and cause callers to see empty customizations. -- if (!bundleResult) { -- this._customizations = customizations; -- this._plugin = undefined; -- } else { -- const pluginDir = URI.parse(bundleResult.ref.uri); -- const plugin = await this._resolvePlugin(pluginDir); -- this._customizations = customizations; -- this._plugin = plugin; -- } +- this._customizations = customizations; +- this._directories = directories; - return true; - } catch (err) { -- // Don't update `_customizations` / `_plugin` when cancelled. +- // Don't update `_customizations` / `_directories` when cancelled. - // Otherwise a cancelled refresh could temporarily clear them and cause callers to see empty customizations. - if (token.isCancellationRequested) { - return false; - } - this._logService.warn(`[Copilot:SessionDiscoveredEntry] Discovery/bundle failed: ${err instanceof Error ? err.message : String(err)}`); +- const hadState = this._customizations.length > 0 || this._directories !== undefined; - this._customizations = []; -- this._plugin = undefined; -- return true; +- this._directories = undefined; +- return hadState; - } - } -} - --function toDiscoveredDirectoryCustomizations(directories: readonly IDiscoveredDirectory[], fileService: IFileService): Promise { +-export function toDiscoveredDirectoryCustomizations(directories: readonly IDiscoveredDirectory[], fileService: IFileService): Promise { - return Promise.all(directories.map(async directory => { - const protocolUri = directory.uri.toString(); - return { @@ -2496,7 +3162,7 @@ index 23d008d0f91..00000000000 - contents: toDirectoryContentsType(directory.type), - writable: false, - load: { kind: CustomizationLoadStatus.Loaded }, -- children: await Promise.all(directory.files.map(file => toDiscoveredChildCustomization(file, directory.type, fileService))), +- children: await Promise.all(directory.files.map(file => toDiscoveredChildCustomization(file.uri, directory.type, fileService))), - }; - })); -} @@ -2510,6 +3176,8 @@ index 23d008d0f91..00000000000 - case DiscoveredType.Instruction: - case DiscoveredType.AgentInstruction: - return CustomizationType.Rule; +- case DiscoveredType.Hook: +- return CustomizationType.Hook; - } -} - @@ -2518,31 +3186,50 @@ index 23d008d0f91..00000000000 - const id = customizationId(uri); - if (type === DiscoveredType.Agent) { - const agentInfo = await parseAgentFile(file, fileService); -- return { +- const agentCustomization: AgentCustomization = { - type: CustomizationType.Agent, - id, - uri, - name: agentInfo.name, -- ...(agentInfo.description ? { description: agentInfo.description } : {}), -- }; +- description: agentInfo.description, +- } satisfies AgentCustomization; +- if (agentInfo.userInvocable !== undefined) { +- agentCustomization._meta = { userInvocable: agentInfo.userInvocable }; +- } +- return agentCustomization; - } - if (type === DiscoveredType.Skill) { - const skillInfo = await parseSkillFile(file, fileService); -- return { +- const skillCustomization: SkillCustomization = { - type: CustomizationType.Skill, - id, - uri, -- name: resourceBasename(resourceDirname(file)), -- ...(skillInfo.description ? { description: skillInfo.description } : {}), +- name: skillInfo.name, +- description: skillInfo.description, - }; +- return skillCustomization; - } - if (type === DiscoveredType.Instruction) { -- return { +- const ruleInfo = await parseRuleFile(file, fileService); +- const ruleCustomization: RuleCustomization = { - type: CustomizationType.Rule, - id, - uri, +- name: ruleInfo.name, +- description: ruleInfo.description, +- globs: ruleInfo.globs, +- alwaysApply: ruleInfo.alwaysApply, +- }; +- return ruleCustomization; +- } +- if (type === DiscoveredType.Hook) { +- const hookCustomization: HookCustomization = { +- type: CustomizationType.Hook, +- id, +- uri, - name: resourceBasename(file), - }; +- return hookCustomization; - } - // agent instruction - return { @@ -2554,6 +3241,69 @@ index 23d008d0f91..00000000000 - }; -} - +- +-/** +- * Projects already-parsed discovered customizations into an in-memory +- * {@link IParsedPlugin} while preserving original source URIs. +- */ +-export function mapToParsedPlugin(customizations: readonly DirectoryCustomization[]): IParsedPlugin | undefined { +- if (customizations.length === 0) { +- return undefined; +- } +- +- const agents: IParsedAgent[] = []; +- const skills: IParsedSkill[] = []; +- const instructions: IParsedRule[] = []; +- +- for (const directory of customizations) { +- for (const child of directory.children ?? []) { +- if (child.type === CustomizationType.Agent) { +- agents.push({ +- uri: URI.parse(child.uri), +- name: child.name, +- description: child.description, +- customization: child, +- }); +- continue; +- } +- +- if (child.type === CustomizationType.Skill) { +- skills.push({ +- uri: URI.parse(child.uri), +- name: child.name, +- description: child.description, +- customization: child, +- }); +- continue; +- } +- +- if (child.type === CustomizationType.Rule) { +- if (child.alwaysApply && child.name.match(/\.md$/i)) { +- continue; // agent instruction +- } +- instructions.push({ +- uri: URI.parse(child.uri), +- name: child.name, +- description: child.description, +- customization: child, +- }); +- } +- } +- } +- +- if (agents.length === 0 && skills.length === 0 && instructions.length === 0) { +- return undefined; +- } +- +- return { +- hooks: [], +- mcpServers: [], +- skills: skills, +- agents: agents, +- instructions: instructions, +- }; +-} +- -/** - * Process-wide plugin state shared across all sessions. - * @@ -2823,7 +3573,7 @@ index 23d008d0f91..00000000000 - ]); - - const discovered = entry?.currentCustomizations() ?? []; -- const sessionPlugin = discovered.some(customization => this._isEnabled(customization)) ? entry?.currentPlugin() : undefined; +- const sessionPlugin = discovered.some(customization => this._isEnabled(customization)) ? mapToParsedPlugin(discovered) : undefined; - const sessionPlugins: IParsedPlugin[] = sessionPlugin ? [sessionPlugin] : []; - - return [ @@ -2956,7 +3706,6 @@ index 23d008d0f91..00000000000 - this._sessionDiscovered.value = new SessionDiscoveredEntry( - this._directory, - URI.file(this._parent.getUserHome()), -- uri => this._parent.tryParsePlugin(uri), - () => this._onDidPublish.fire({ - type: ActionType.SessionCustomizationsChanged, - customizations: [...this.getCustomizations()], @@ -2986,8 +3735,13 @@ index 23d008d0f91..00000000000 - * tears down the controller and any disk watchers it created. - */ -class ActiveClient extends Disposable { -- private _tools: readonly ToolDefinition[] = []; -- private _clientId = ''; +- /** +- * Live holder of the owning `clientId` and contributed tools. Shared by +- * reference with the session's {@link CopilotAgentSession} so a window +- * reload (new `clientId`, identical tools) is reflected at tool-call +- * stamp time without restarting the SDK session. +- */ +- readonly state = new ActiveClientState(); - - public readonly pluginController: SessionPluginController; - @@ -2995,6 +3749,7 @@ index 23d008d0f91..00000000000 - private readonly _sessionUri: URI, - pluginController: SessionPluginController, - onDidSessionProgress: Emitter, +- @IAgentConfigurationService private readonly _configurationService: IAgentConfigurationService, - ) { - super(); - this.pluginController = this._register(pluginController); @@ -3005,59 +3760,65 @@ index 23d008d0f91..00000000000 - })); - } - -- updateTools(clientId: string, tools: readonly ToolDefinition[]): void { -- this._clientId = clientId; -- this._tools = tools; +- updateTools(clientId: string | undefined, tools: readonly ToolDefinition[]): void { +- this.state.update(clientId, tools); - } - - async snapshot(): Promise { -- return { clientId: this._clientId, tools: this._tools, plugins: await this.pluginController.getAppliedPlugins() }; +- return { +- tools: this.state.tools, +- plugins: await this.pluginController.getAppliedPlugins(), +- mcpServers: this._getMcpServers(), +- }; +- } +- +- private _getMcpServers(): AgentHostMcpServers { +- const servers = this._configurationService.getRootValue(platformRootSchema, AgentHostMcpServersConfigKey) ?? {}; +- +- return structuredClone(servers); - } - -- async isOutdated(snap: IActiveClientSnapshot): Promise { +- /** +- * Returns `true` when the SDK session must be disposed and resumed to +- * pick up a changed config. Compares ONLY plugins and the structural +- * tool set (name + description + inputSchema). The `clientId` is +- * deliberately excluded — a clientId-only change is reflected live via +- * {@link state} and never requires a restart. +- */ +- async requiresRestart(snap: IActiveClientSnapshot): Promise { - const plugins = await this.pluginController.getAppliedPlugins(); - if (!parsedPluginsEqual(snap.plugins, plugins)) { - return true; - } -- if (snap.tools.length !== this._tools.length) { +- if (!equals(snap.mcpServers, this._getMcpServers())) { - return true; - } -- // Compare tool definitions by name, description, and schema — -- // not just names — so schema/description changes trigger a refresh. -- const snapByName = new Map(snap.tools.map(t => [t.name, t])); -- for (const tool of this._tools) { -- const prev = snapByName.get(tool.name); -- if (!prev -- || prev.description !== tool.description -- || !equals(prev.inputSchema, tool.inputSchema)) { -- return true; -- } -- } -- return false; +- return !this.state.structuralEquals({ tools: snap.tools }); - } -} diff --git a/src/vs/platform/agentHost/node/copilot/copilotAgentSession.ts b/src/vs/platform/agentHost/node/copilot/copilotAgentSession.ts deleted file mode 100644 -index 770817327b0..00000000000 +index a0c0a765..00000000 --- a/src/vs/platform/agentHost/node/copilot/copilotAgentSession.ts +++ /dev/null -@@ -1,2389 +0,0 @@ +@@ -1,3141 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - --import type { ExitPlanModeRequest, MessageOptions, PermissionRequestResult, SessionConfig, Tool, ToolResultObject } from '@github/copilot-sdk'; +-import type { CopilotSession, ExitPlanModeRequest, MessageOptions, PermissionRequestResult, SessionConfig, Tool, ToolResultObject, McpServerStatus as SdkMcpServerStatus } from '@github/copilot-sdk'; -import { DeferredPromise } from '../../../../base/common/async.js'; -import { encodeBase64, VSBuffer } from '../../../../base/common/buffer.js'; -import { Emitter } from '../../../../base/common/event.js'; --import { CancellationError } from '../../../../base/common/errors.js'; +-import { CancellationError, getErrorMessage } from '../../../../base/common/errors.js'; +-import { escapeMarkdownSyntaxTokens } from '../../../../base/common/htmlContent.js'; -import { Disposable, IReference, toDisposable } from '../../../../base/common/lifecycle.js'; -import { Schemas } from '../../../../base/common/network.js'; -import { isAbsolute, join } from '../../../../base/common/path.js'; -import { extUriBiasedIgnorePathCase, normalizePath } from '../../../../base/common/resources.js'; -import { splitLinesIncludeSeparators } from '../../../../base/common/strings.js'; --import { hasKey, isDefined, isObject, isString } from '../../../../base/common/types.js'; +-import { hasKey, isDefined, isObject, isString, type Mutable } from '../../../../base/common/types.js'; -import { URI } from '../../../../base/common/uri.js'; -import { generateUuid } from '../../../../base/common/uuid.js'; -import { localize } from '../../../../nls.js'; @@ -3067,40 +3828,69 @@ index 770817327b0..00000000000 -import { ILogService } from '../../../log/common/log.js'; -import { ITelemetryService } from '../../../telemetry/common/telemetry.js'; -import { AgentHostConfigKey, agentHostCustomizationConfigSchema } from '../../common/agentHostCustomizationConfig.js'; +-import { AgentHostSandboxConfigKey, sandboxConfigSchema } from '../../common/sandboxConfigSchema.js'; -import { platformSessionSchema } from '../../common/agentHostSchema.js'; --import { AgentSignal } from '../../common/agentService.js'; +-import { AgentSignal, IMcpNotification } from '../../common/agentService.js'; -import { stripRedundantCdPrefix } from '../../common/commandLineHelpers.js'; +-import { toToolCallMeta, type IToolCallMeta, type IToolCallUiMeta } from '../../common/meta/agentToolCallMeta.js'; +-import { OtelData, type OtelAttributeValue } from '../../common/otlp/otlpLogEmitter.js'; -import type { LanguageModelToolInvokedClassification, LanguageModelToolInvokedEvent } from '../../../telemetry/common/languageModelToolTelemetry.js'; -import { SessionConfigKey } from '../../common/sessionConfigKeys.js'; +-import { isAgentFeedbackAnnotationsAttachment, renderAgentFeedbackAnnotationsAttachment } from '../../common/meta/agentFeedbackAttachments.js'; -import { ISessionDatabase, ISessionDataService, SESSION_ATTACHMENTS_DIRNAME } from '../../common/sessionDataService.js'; -import { MessageAttachmentKind, ToolCallContributorKind, type FileEdit, type MessageAttachment } from '../../common/state/protocol/state.js'; --import { ActionType, type SessionAction } from '../../common/state/sessionActions.js'; --import { MessageKind, ResponsePartKind, SessionInputAnswerState, SessionInputAnswerValueKind, SessionInputQuestionKind, SessionInputResponseKind, ToolCallConfirmationReason, ToolCallStatus, ToolResultContentType, type PendingMessage, type SessionInputAnswer, type SessionInputOption, type SessionInputQuestion, type SessionInputRequest, type ToolCallResult, type ToolResultContent, type Turn, type UsageInfo } from '../../common/state/sessionState.js'; +-import { ActionType, type ChatAction, type SessionAction } from '../../common/state/sessionActions.js'; +-import { MessageKind, ResponsePartKind, ChatInputAnswerState, ChatInputAnswerValueKind, ChatInputQuestionKind, ChatInputResponseKind, ToolCallConfirmationReason, ToolCallStatus, ToolResultContentType, type PendingMessage, type ChatInputAnswer, type ChatInputOption, type ChatInputQuestion, type ChatInputRequest, type ToolCallResult, type ToolResultContent, type Turn, type UsageInfo, type UsageInfoMeta } from '../../common/state/sessionState.js'; -import { IAgentConfigurationService } from '../agentConfigurationService.js'; -import type { IExitPlanModeResponse } from './copilotAgent.js'; -import { CopilotSessionWrapper } from './copilotSessionWrapper.js'; -import type { CopilotSessionLaunchPlan, IActiveClientSnapshot, ICopilotSessionLauncher, ICopilotSessionRuntime } from './copilotSessionLauncher.js'; +-import { ActiveClientState } from '../activeClientState.js'; +-import { PendingRequestRegistry } from '../../common/pendingRequestRegistry.js'; -import { buildCopilotSystemNotification } from './copilotSystemNotification.js'; --import { parseLeadingSlashCommand } from './copilotSlashCommandCompletionProvider.js'; +-import { isPromptInvokedCopilotSlashCommand, isRuntimeCopilotSlashCommand, parseLeadingSlashCommand } from './copilotSlashCommandCompletionProvider.js'; -import type { IUnsandboxedCommandConfirmationRequest, ShellManager } from './copilotShellTools.js'; --import { getEditFilePaths, getInvocationMessage, getPastTenseMessage, getPermissionDisplay, getShellLanguage, getSubagentMetadata, getToolDisplayName, getToolInputString, getToolKind, isEditTool, isHiddenTool, isShellTool, synthesizeSkillToolCall, tryStringify, type ITypedPermissionRequest } from './copilotToolDisplay.js'; +-import { buildSandboxConfigForSdk } from './sandboxConfigForSdk.js'; +-import type { IAgentServerToolHost } from '../../common/agentServerTools.js'; +-import { getEditFilePaths, getInvocationMessage, getPastTenseMessage, getPermissionDisplay, getShellLanguage, getSubagentMetadata, getTaskCompleteMarkdown, getToolDisplayName, getToolInputString, getToolKind, isEditTool, isHiddenTool, isShellTool, isTaskCompleteTool, synthesizeSkillToolCall, tryStringify, type ITypedPermissionRequest } from './copilotToolDisplay.js'; -import { FileEditTracker } from '../shared/fileEditTracker.js'; +-import { stripProxyErrorMarker, tryBuildChatErrorMeta, tryBuildChatErrorMetaFromFields } from '../shared/forwardedChatError.js'; +-import { McpCustomizationController, type ISdkMcpServer } from '../shared/mcpCustomizationController.js'; -import { mapSessionEvents } from './mapSessionEvents.js'; -import { buildPendingEditContentUri } from './pendingEditContentStore.js'; +-import { McpServerStatus, type McpServerState } from '../../common/state/protocol/channels-session/state.js'; - -/** -- * The full set of agent modes the Copilot SDK accepts. Wider than the -- * {@link SessionMode} the AHP exposes — the SDK has a first-class -- * `'autopilot'` mode while AHP models that as -- * `mode='interactive', autoApprove='autopilot'`. The Copilot agent -- * translates between the two views in {@link CopilotAgentSession.send} -- * and the `session.mode_changed` listener. +- * The full set of agent modes the Copilot SDK accepts. AHP now exposes the +- * same three modes (`interactive` / `plan` / `autopilot`) on its `mode` axis, +- * so the Copilot agent maps between the two views directly in +- * {@link CopilotAgentSession.send} and the `session.mode_changed` listener. - */ -export type CopilotSdkMode = 'interactive' | 'plan' | 'autopilot'; -type CopilotSdkAttachment = Required['attachments'][number]; +-type CopilotCommandInvocationResult = Awaited>; +-type RuntimeSlashCommandCache = { readonly expiresAt: number; readonly promise: Promise> }; - -const COPILOT_HOME_DIRECTORY = '.copilot'; -const SESSION_STATE_DIRECTORY = join(COPILOT_HOME_DIRECTORY, 'session-state'); +-const EMPTY_TOOL_RESULT_TEXT = ''; +-const RUNTIME_SLASH_COMMAND_CACHE_TTL_MS = 30_000; +- +-function getEmptyToolResultText(binaryResults: readonly { readonly type: 'image' | 'resource' }[] | undefined): string { +- if (!binaryResults?.length) { +- return EMPTY_TOOL_RESULT_TEXT; +- } +- +- const hasImage = binaryResults.some(result => result.type === 'image'); +- const hasFile = binaryResults.some(result => result.type === 'resource'); +- if (hasImage && hasFile) { +- return 'Tool produced the attached image and file'; +- } +- if (hasImage) { +- return 'Tool produced the attached image'; +- } +- return 'Tool produced the attached file'; +-} - -/** - * Display labels and descriptions for the SDK's `exit_plan_mode` action ids. @@ -3152,12 +3942,23 @@ index 770817327b0..00000000000 - return isString(command) ? command : undefined; -} - +-function toCopilotSdkMode(mode: string | undefined): CopilotSdkMode | undefined { +- switch (mode) { +- case 'interactive': +- case 'plan': +- case 'autopilot': +- return mode; +- default: +- return undefined; +- } +-} +- -/** - * Projects an {@link ElicitationSchema} field into a -- * {@link SessionInputQuestion}. The schema's property key becomes the +- * {@link ChatInputQuestion}. The schema's property key becomes the - * question id so we can route the answer back by field name. - */ --function elicitationFieldToQuestion(fieldName: string, field: ElicitationSchemaField, required: boolean): SessionInputQuestion { +-function elicitationFieldToQuestion(fieldName: string, field: ElicitationSchemaField, required: boolean): ChatInputQuestion { - const base = { - id: fieldName, - title: field.title ?? fieldName, @@ -3167,23 +3968,23 @@ index 770817327b0..00000000000 - - switch (field.type) { - case 'boolean': -- return { ...base, kind: SessionInputQuestionKind.Boolean, defaultValue: field.default }; +- return { ...base, kind: ChatInputQuestionKind.Boolean, defaultValue: field.default }; - case 'integer': - case 'number': - return { - ...base, -- kind: field.type === 'integer' ? SessionInputQuestionKind.Integer : SessionInputQuestionKind.Number, +- kind: field.type === 'integer' ? ChatInputQuestionKind.Integer : ChatInputQuestionKind.Number, - min: field.minimum, - max: field.maximum, - defaultValue: field.default, - }; - case 'array': { -- const options: SessionInputOption[] = hasKey(field.items, { enum: true }) +- const options: ChatInputOption[] = hasKey(field.items, { enum: true }) - ? field.items.enum.map(value => ({ id: value, label: value })) - : field.items.anyOf.map(option => ({ id: option.const, label: option.title })); - return { - ...base, -- kind: SessionInputQuestionKind.MultiSelect, +- kind: ChatInputQuestionKind.MultiSelect, - options, - min: field.minItems, - max: field.maxItems, @@ -3192,16 +3993,16 @@ index 770817327b0..00000000000 - case 'string': { - if (hasKey(field, { enum: true })) { - const enumNames = field.enumNames; -- const options: SessionInputOption[] = field.enum.map((value, idx) => ({ id: value, label: enumNames?.[idx] ?? value })); -- return { ...base, kind: SessionInputQuestionKind.SingleSelect, options }; +- const options: ChatInputOption[] = field.enum.map((value, idx) => ({ id: value, label: enumNames?.[idx] ?? value })); +- return { ...base, kind: ChatInputQuestionKind.SingleSelect, options }; - } - if (hasKey(field, { oneOf: true })) { -- const options: SessionInputOption[] = field.oneOf.map(option => ({ id: option.const, label: option.title })); -- return { ...base, kind: SessionInputQuestionKind.SingleSelect, options }; +- const options: ChatInputOption[] = field.oneOf.map(option => ({ id: option.const, label: option.title })); +- return { ...base, kind: ChatInputQuestionKind.SingleSelect, options }; - } - return { - ...base, -- kind: SessionInputQuestionKind.Text, +- kind: ChatInputQuestionKind.Text, - format: field.format, - min: field.minLength, - max: field.maxLength, @@ -3212,19 +4013,19 @@ index 770817327b0..00000000000 -} - -/** -- * Projects a {@link SessionInputAnswer} back into the +- * Projects a {@link ChatInputAnswer} back into the - * {@link ElicitationFieldValue} shape expected by the SDK for the given - * schema field. Returns `undefined` when the answer is missing/skipped or - * cannot be coerced to the field's declared type. - */ --function elicitationAnswerToFieldValue(field: ElicitationSchemaField, answer: SessionInputAnswer | undefined): ElicitationFieldValue | undefined { -- if (!answer || answer.state === SessionInputAnswerState.Skipped) { +-function elicitationAnswerToFieldValue(field: ElicitationSchemaField, answer: ChatInputAnswer | undefined): ElicitationFieldValue | undefined { +- if (!answer || answer.state === ChatInputAnswerState.Skipped) { - return undefined; - } - const value = answer.value; - if (field.type === 'boolean') { -- if (value.kind === SessionInputAnswerValueKind.Boolean) { return value.value; } -- if (value.kind === SessionInputAnswerValueKind.Text) { +- if (value.kind === ChatInputAnswerValueKind.Boolean) { return value.value; } +- if (value.kind === ChatInputAnswerValueKind.Text) { - if (value.value === 'true') { return true; } - if (value.value === 'false') { return false; } - return undefined; @@ -3232,10 +4033,10 @@ index 770817327b0..00000000000 - return undefined; - } - if (field.type === 'number' || field.type === 'integer') { -- if (value.kind === SessionInputAnswerValueKind.Number) { +- if (value.kind === ChatInputAnswerValueKind.Number) { - return field.type === 'integer' ? Math.trunc(value.value) : value.value; - } -- if (value.kind === SessionInputAnswerValueKind.Text) { +- if (value.kind === ChatInputAnswerValueKind.Text) { - if (value.value.trim() === '') { return undefined; } - const n = Number(value.value); - return Number.isFinite(n) ? (field.type === 'integer' ? Math.trunc(n) : n) : undefined; @@ -3243,20 +4044,20 @@ index 770817327b0..00000000000 - return undefined; - } - if (field.type === 'array') { -- if (value.kind === SessionInputAnswerValueKind.SelectedMany) { +- if (value.kind === ChatInputAnswerValueKind.SelectedMany) { - return [...value.value, ...(value.freeformValues ?? [])]; - } -- if (value.kind === SessionInputAnswerValueKind.Selected) { +- if (value.kind === ChatInputAnswerValueKind.Selected) { - return value.value ? [value.value, ...(value.freeformValues ?? [])] : [...(value.freeformValues ?? [])]; - } -- if (value.kind === SessionInputAnswerValueKind.Text) { +- if (value.kind === ChatInputAnswerValueKind.Text) { - return value.value ? [value.value] : []; - } - return undefined; - } - // field.type === 'string' -- if (value.kind === SessionInputAnswerValueKind.Text) { return value.value; } -- if (value.kind === SessionInputAnswerValueKind.Selected) { return value.value; } +- if (value.kind === ChatInputAnswerValueKind.Text) { return value.value; } +- if (value.kind === ChatInputAnswerValueKind.Selected) { return value.value; } - return undefined; -} - @@ -3307,6 +4108,89 @@ index 770817327b0..00000000000 - readonly customizationDirectory?: URI; - /** Snapshot of the active client's tools and plugins at session creation time. */ - readonly clientSnapshot?: IActiveClientSnapshot; +- /** +- * Looks up the AHP id of an existing child MCP customization by +- * server name, so SDK MCP state events can target plugin-derived +- * entries narrowly. Returns `undefined` for SDK servers that have +- * no corresponding plugin entry — the session surfaces those as +- * bare top-level customizations via {@link CopilotAgentSession.topLevelMcpCustomizations}. +- */ +- readonly resolveMcpChildId: (serverName: string) => string | undefined; +- /** +- * Live holder of the owning client's identity, shared by reference with +- * the agent's per-session {@link ActiveClient}. Read at tool-call stamp +- * time so a window reload (new `clientId`, identical tools) stamps with +- * the current id. When omitted, a fresh state seeded from +- * {@link clientSnapshot} is used (test / standalone path). +- */ +- readonly activeClientState?: ActiveClientState; +- /** +- * Server-side host for the agent host's server tools. When provided, the +- * session advertises the server tools (feedback "comments" today, more in +- * the future) and exposes SDK tool handlers that execute them in-process. +- */ +- readonly serverToolHost?: IAgentServerToolHost; +-} +- +-/** +- * Lifecycle state of a {@link CopilotTurn}. +- * +- * - `pending` — the host has dispatched the message (`send()`), but the SDK +- * has not yet emitted any event for this turn's agentic loop. +- * - `running` — the SDK has emitted at least one event for this turn. +- * - `completed` — the turn finished normally (the loop went idle). +- * - `aborted` — the turn's loop was cancelled via an abort. +- */ +-type CopilotTurnState = 'pending' | 'running' | 'completed' | 'aborted'; +- +-/** +- * Encapsulates all per-turn bookkeeping for a single protocol turn, plus an +- * explicit lifecycle {@link CopilotTurn.state}. Holding this state on one +- * object (created fresh per turn) rather than as a handful of mutable session +- * fields means there is a single, atomic notion of "the current turn": there +- * is no set of counters/maps that must be reset in lockstep, and turn +- * transitions (running/completed/aborted) are explicit and checkable. +- * +- * The `pending → running` distinction guards turn completion against a stray +- * idle: an abort's terminal `session.idle` finds a queued message's turn still +- * `pending` (the SDK has not begun it) and leaves it open, rather than +- * completing it and orphaning its real response. A non-abort idle still +- * completes a `pending` turn defensively, so a degenerate no-op send cannot +- * hang the session. +- */ +-class CopilotTurn { +- +- private _state: CopilotTurnState = 'pending'; +- +- /** Accumulated Copilot usage for this turn, in nano-AIU. */ +- copilotUsageTotalNanoAiu = 0; +- +- /** +- * Current markdown response part IDs for this turn, keyed by +- * `parentToolCallId ?? ''`. Parent and subagent text stream through the +- * same SDK session but land in different AHP sessions, so their markdown +- * part state must not mask or append to each other. +- */ +- readonly markdownPartIds = new Map(); +- +- /** Current reasoning response part IDs for this turn, keyed by `parentToolCallId ?? ''`. */ +- readonly reasoningPartIds = new Map(); +- +- constructor(readonly id: string) { } +- +- get state(): CopilotTurnState { return this._state; } +- get isPending(): boolean { return this._state === 'pending'; } +- get isRunning(): boolean { return this._state === 'running'; } +- +- /** Transition `pending → running` on the first SDK event. No-op once running/finished. */ +- markRunning(): void { +- if (this._state === 'pending') { +- this._state = 'running'; +- } +- } +- +- markCompleted(): void { this._state = 'completed'; } +- markAborted(): void { this._state = 'aborted'; } -} - -/** @@ -3320,21 +4204,32 @@ index 770817327b0..00000000000 - readonly sessionId: string; - readonly sessionUri: URI; - +- /** Working directory this session operates in, if any. */ +- get workingDirectory(): URI | undefined { return this._workingDirectory; } +- +- /** Tracks active tool invocations so we can produce past-tense messages on completion. */ - /** Tracks active tool invocations so we can produce past-tense messages on completion. */ -- private readonly _activeToolCalls = new Map | undefined; content: ToolResultContent[]; parentToolCallId: string | undefined; startTimeMs: number; mcpServerName: string | undefined }>(); +- private readonly _activeToolCalls = new Map | undefined; content: ToolResultContent[]; parentToolCallId: string | undefined; startTimeMs: number; mcpServerName: string | undefined; meta: IToolCallMeta | undefined }>(); +- /** +- * Maps a running subagent's `agentId` to its parent tool call id. Session- +- * scoped rather than per-turn: a subagent's lifetime is bounded by its +- * `subagent.started` / `subagent.completed` events (and background +- * subagents can outlive the parent tool call), so this routing must not be +- * cleared on turn boundaries. +- */ - private readonly _parentToolCallIdsByAgentId = new Map(); - /** Pending permission requests awaiting a renderer-side decision. */ - private readonly _pendingPermissions = new Map>(); - /** Pending user input requests awaiting a renderer-side answer. */ -- private readonly _pendingUserInputs = new Map }>; questionId: string }>(); +- private readonly _pendingUserInputs = new Map }>; questionId: string }>(); - /** - * Pending elicitation requests awaiting a renderer-side answer. Keyed - * by request id; the schema is retained so the completion handler can -- * project the submitted {@link SessionInputAnswer}s back into the +- * project the submitted {@link ChatInputAnswer}s back into the - * SDK's {@link ElicitationResult.content} shape. - */ - private readonly _pendingElicitations = new Map }>; +- readonly deferred: DeferredPromise<{ response: ChatInputResponseKind; answers?: Record }>; - readonly schema: ElicitationSchema | undefined; - }>(); - /** @@ -3357,10 +4252,31 @@ index 770817327b0..00000000000 - private readonly _databaseRef: IReference; - /** On-disk root for per-session data (database, attachments, …). */ - private readonly _sessionDataDir: URI; -- /** Protocol turn ID set by {@link send}, used for file edit tracking. */ -- private _turnId = ''; +- /** +- * The current protocol turn and its per-turn bookkeeping, or `undefined` +- * when the session is idle (no active turn). Replaces the former set of +- * loosely-coupled per-turn fields (`_turnId`, usage counter, streaming +- * part-id maps) with a single object carrying an explicit +- * {@link CopilotTurn.state} lifecycle. Created (`pending`) by +- * {@link resetTurnState}, finalized by {@link _completeActiveTurn}. +- */ +- private _currentTurn: CopilotTurn | undefined; +- /** +- * Protocol turn ID of the active turn, or `''` when idle. Used by file +- * edit tracking and emitted on per-turn actions. +- */ +- private get _turnId(): string { return this._currentTurn?.id ?? ''; } +- /** +- * Last model id seen on the SDK's per-LLM-call `Usage` event (or a +- * direct {@link setModel} call). We rely on the +- * `Usage` event rather than the tool-call event itself because +- * tool-call events don't carry the model id; the `Usage` event for +- * an LLM turn precedes that turn's `tool_use` events. +- */ +- private _lastSeenModelId: string | undefined; - /** SDK session wrapper, set by {@link initializeSession}. */ - private _wrapper!: CopilotSessionWrapper; +- private _runtimeSlashCommandCache: RuntimeSlashCommandCache | undefined; - /** Last agent mode pushed to the SDK via {@link applyMode}, to elide redundant `rpc.mode.set` calls. */ - private _lastAppliedMode: CopilotSdkMode | undefined; - private readonly _steeringMessagesInFlight = new Set(); @@ -3369,7 +4285,7 @@ index 770817327b0..00000000000 - * surfaced to the chat UI as a separate user message. When the SDK - * echoes a steering through a `user.message` event whose `content` - * matches one of these entries, we finalize the in-flight turn and -- * dispatch a new {@link ActionType.SessionTurnStarted} whose +- * dispatch a new {@link ActionType.ChatTurnStarted} whose - * `userMessage` is the steering content. The reducer also removes - * the pending steering via the action's `queuedMessageId`. - * @@ -3381,10 +4297,17 @@ index 770817327b0..00000000000 - - /** Snapshot captured at session creation for refresh detection. */ - private readonly _appliedSnapshot: IActiveClientSnapshot; +- /** +- * Live owning-client identity, read at tool-call stamp time so a window +- * reload that re-pushes identical tools with a new `clientId` stamps +- * subsequent client tool calls with the current id rather than the one +- * frozen into {@link _appliedSnapshot}. +- */ +- private readonly _activeClientState: ActiveClientState; - /** Tool names that are client-provided, derived from snapshot. */ - private readonly _clientToolNames: ReadonlySet; - /** Deferred promises for pending client tool calls, keyed by toolCallId. */ -- private readonly _pendingClientToolCalls = new Map>(); +- private readonly _pendingClientToolCalls = new PendingRequestRegistry(); - /** `pending-edit-content:` URIs written during permission requests, keyed - * by toolCallId. Cleaned up when the permission resolves or the session - * is disposed. */ @@ -3396,16 +4319,28 @@ index 770817327b0..00000000000 - private readonly _shellManager: ShellManager | undefined; - private readonly _workingDirectory: URI | undefined; - private readonly _customizationDirectory: URI | undefined; +- private readonly _serverToolHost: IAgentServerToolHost | undefined; +- /** Bridges SDK-reported MCP server state into AHP customization actions. */ +- private readonly _mcpCustomizations: McpCustomizationController; - - /** -- * Current markdown response part IDs for the active turn, keyed by -- * `parentToolCallId ?? ''`. Parent and subagent text stream through the -- * same SDK session but land in different AHP sessions, so their markdown -- * part state must not mask or append to each other. +- * Fans MCP server notifications (today: `notifications/tools/list_changed`) +- * up to the agent and on to the protocol server. Fired by the +- * `onToolsUpdated` listener once per ready MCP channel. +- */ +- private readonly _onMcpNotification = this._register(new Emitter()); +- readonly onMcpNotification = this._onMcpNotification.event; +- +- /** +- * Pending MCP `sampling/createMessage` requests received over the +- * AHP `mcp://` channel, keyed by the cancellation handle we passed +- * into {@link rpc.mcp.executeSampling}. Tracked so that session +- * teardown can issue a best-effort +- * {@link rpc.mcp.cancelSamplingExecution} for each one instead of +- * leaving the SDK-side promise (and the upstream App) hanging. - */ -- private readonly _currentMarkdownPartIds = new Map(); -- /** Current reasoning response part IDs for the active turn, keyed by `parentToolCallId ?? ''`. */ -- private readonly _currentReasoningPartIds = new Map(); +- private readonly _pendingMcpSamplings = new Set(); +- - /** Tracks whether a non-empty activity has been published, so we only emit a clear when needed. */ - private _hasReportedActivity = false; - @@ -3428,9 +4363,19 @@ index 770817327b0..00000000000 - this._shellManager = options.shellManager; - this._workingDirectory = options.workingDirectory; - this._customizationDirectory = options.customizationDirectory; +- this._serverToolHost = options.serverToolHost; - -- this._appliedSnapshot = options.clientSnapshot ?? { clientId: '', tools: [], plugins: [] }; +- this._appliedSnapshot = options.clientSnapshot ?? { tools: [], plugins: [], mcpServers: {} }; - this._clientToolNames = new Set(this._appliedSnapshot.tools.map(t => t.name)); +- // Share the agent's live ActiveClientState when provided so clientId +- // changes are observed at stamp time. Standalone / test construction +- // seeds a private instance with the applied tools and no owning client. +- if (options.activeClientState) { +- this._activeClientState = options.activeClientState; +- } else { +- this._activeClientState = new ActiveClientState(); +- this._activeClientState.update(undefined, this._appliedSnapshot.tools); +- } - - this._databaseRef = sessionDataService.openDatabase(options.sessionUri); - this._register(toDisposable(() => this._databaseRef.dispose())); @@ -3438,12 +4383,20 @@ index 770817327b0..00000000000 - - this._editTracker = this._instantiationService.createInstance(FileEditTracker, options.sessionUri.toString(), this._databaseRef.object); - +- this._mcpCustomizations = this._register(new McpCustomizationController({ +- providerId: this.sessionUri.scheme, +- sessionId: this.sessionId, +- resolveChildId: options.resolveMcpChildId, +- emit: action => this._emitAction(action), +- })); +- - this._register(toDisposable(() => this._denyPendingPermissions())); - this._register(toDisposable(() => this._shellManager?.dispose())); - this._register(toDisposable(() => this._cancelPendingUserInputs())); - this._register(toDisposable(() => this._cancelPendingElicitations())); - this._register(toDisposable(() => this._cancelPendingPlanReviews())); - this._register(toDisposable(() => this._drainPendingSteeringFlips())); +- this._register(toDisposable(() => this._cancelPendingMcpSamplings())); - - // When a shell tool associates a terminal with a tool call, fire a - // tool_content_changed event so the UI can connect to the terminal @@ -3462,7 +4415,7 @@ index 770817327b0..00000000000 - }); - - this._emitAction({ -- type: ActionType.SessionToolCallContentChanged, +- type: ActionType.ChatToolCallContentChanged, - turnId: this._turnId, - toolCallId, - content: tracked.content, @@ -3475,7 +4428,7 @@ index 770817327b0..00000000000 - // ---- AgentSignal helpers ------------------------------------------------ - - /** Wraps a {@link SessionAction} in an {@link AgentSignal} envelope and emits it. */ -- private _emitAction(action: SessionAction, parentToolCallId?: string): void { +- private _emitAction(action: SessionAction | ChatAction, parentToolCallId?: string): void { - this._onDidSessionProgress.fire({ - kind: 'action', - session: this.sessionUri, @@ -3487,7 +4440,7 @@ index 770817327b0..00000000000 - /** - * Promotes a pending steering message into its own protocol turn: - * closes the in-flight turn (so its responseParts settle into history) -- * and dispatches {@link ActionType.SessionTurnStarted} for a fresh +- * and dispatches {@link ActionType.ChatTurnStarted} for a fresh - * turn whose user message is the steering content. The action's - * `queuedMessageId` atomically clears the corresponding pending - * steering message from the session state. @@ -3505,21 +4458,26 @@ index 770817327b0..00000000000 - const previousTurnId = this._turnId; - if (previousTurnId) { - this._emitAction({ -- type: ActionType.SessionTurnComplete, +- type: ActionType.ChatTurnComplete, - turnId: previousTurnId, - }); - } - const newTurnId = generateUuid(); - this._emitAction({ -- type: ActionType.SessionTurnStarted, +- type: ActionType.ChatTurnStarted, - turnId: newTurnId, - message: steering.message, - queuedMessageId: steering.id, - }); -- // Mirror `resetTurnState` so per-turn counters/mappings (usage -- // total, streaming part ids, subagent agentId map) don't bleed -- // from the preempted turn into the new steering turn. +- // Mirror `resetTurnState` so per-turn counters/mappings (usage total, +- // streaming part ids) don't bleed from the preempted turn into the new +- // steering turn. The steering turn is created mid-loop in response to an +- // SDK `user.message` event, so the SDK is already actively producing its +- // response: mark it `running` immediately rather than leaving it +- // `pending`, otherwise an abort during the steering turn would treat it +- // as a not-yet-started queued turn and leave it open. - this.resetTurnState(newTurnId); +- this._currentTurn?.markRunning(); - return newTurnId; - } - @@ -3580,29 +4538,25 @@ index 770817327b0..00000000000 - } - - /** -- * Resets per-turn streaming state so the next text/reasoning chunk -- * allocates a fresh response part for the new turn. +- * Starts a fresh `pending` turn, discarding any per-turn streaming state +- * from a previous turn so the next text/reasoning chunk allocates a new +- * response part. The turn becomes `running` on the first SDK event. - */ - resetTurnState(turnId: string): void { -- this._turnId = turnId; -- this._currentMarkdownPartIds.clear(); -- this._currentReasoningPartIds.clear(); -- this._parentToolCallIdsByAgentId.clear(); +- this._currentTurn = new CopilotTurn(turnId); - } - - private _completeActiveTurn(): void { -- if (!this._turnId) { +- const turn = this._currentTurn; +- if (!turn) { - return; - } -- const turnId = this._turnId; +- turn.markCompleted(); - this._emitAction({ -- type: ActionType.SessionTurnComplete, -- turnId, +- type: ActionType.ChatTurnComplete, +- turnId: turn.id, - }); -- this._turnId = ''; -- this._currentMarkdownPartIds.clear(); -- this._currentReasoningPartIds.clear(); -- this._parentToolCallIdsByAgentId.clear(); +- this._currentTurn = undefined; - } - - private _getEditFilePaths(parameters: unknown): string[] { @@ -3656,21 +4610,30 @@ index 770817327b0..00000000000 - * markdown response part; subsequent deltas append to it. - */ - private _emitMarkdownDelta(content: string, parentToolCallId?: string): void { +- const turn = this._currentTurn; +- if (!turn) { +- // A markdown delta should only ever arrive while a turn is active. +- // Without a turn we can't persist the part id (so every delta would +- // allocate a fresh part) and the action would carry an empty turnId. +- // Drop it and surface the unexpected state. +- this._logService.error(`[Copilot:${this.sessionId}] Markdown delta emitted with no active turn; dropping`); +- return; +- } - const markdownScope = parentToolCallId ?? ''; -- let partId = this._currentMarkdownPartIds.get(markdownScope); +- let partId = turn.markdownPartIds.get(markdownScope); - if (!partId) { - partId = generateUuid(); -- this._currentMarkdownPartIds.set(markdownScope, partId); +- turn.markdownPartIds.set(markdownScope, partId); - this._emitAction({ -- type: ActionType.SessionResponsePart, -- turnId: this._turnId, +- type: ActionType.ChatResponsePart, +- turnId: turn.id, - part: { kind: ResponsePartKind.Markdown, id: partId, content }, - }, parentToolCallId); - return; - } - this._emitAction({ -- type: ActionType.SessionDelta, -- turnId: this._turnId, +- type: ActionType.ChatDelta, +- turnId: turn.id, - partId, - content, - }, parentToolCallId); @@ -3678,21 +4641,26 @@ index 770817327b0..00000000000 - - /** Emits a reasoning delta, similar to {@link _emitMarkdownDelta} but for reasoning parts. */ - private _emitReasoningDelta(content: string, parentToolCallId?: string): void { +- const turn = this._currentTurn; +- if (!turn) { +- this._logService.error(`[Copilot:${this.sessionId}] Reasoning delta emitted with no active turn; dropping`); +- return; +- } - const reasoningScope = parentToolCallId ?? ''; -- let partId = this._currentReasoningPartIds.get(reasoningScope); +- let partId = turn.reasoningPartIds.get(reasoningScope); - if (!partId) { - partId = generateUuid(); -- this._currentReasoningPartIds.set(reasoningScope, partId); +- turn.reasoningPartIds.set(reasoningScope, partId); - this._emitAction({ -- type: ActionType.SessionResponsePart, -- turnId: this._turnId, +- type: ActionType.ChatResponsePart, +- turnId: turn.id, - part: { kind: ResponsePartKind.Reasoning, id: partId, content }, - }, parentToolCallId); - return; - } - this._emitAction({ -- type: ActionType.SessionReasoning, -- turnId: this._turnId, +- type: ActionType.ChatReasoning, +- turnId: turn.id, - partId, - content, - }, parentToolCallId); @@ -3712,8 +4680,9 @@ index 770817327b0..00000000000 - - /** - * Creates SDK {@link Tool} objects for the client-provided tools in the -- * applied snapshot. The handler creates a {@link DeferredPromise} and waits -- * for the client to dispatch `session/toolCallComplete`. +- * applied snapshot. The handler parks a request in +- * {@link _pendingClientToolCalls} and waits for the client to dispatch +- * `session/toolCallComplete`. - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private _createClientSdkTools(): Tool[] { @@ -3727,14 +4696,10 @@ index 770817327b0..00000000000 - parameters: def.inputSchema ?? { type: 'object' as const, properties: {} }, - handler: async (_args: Record, { toolCallId }) => { - try { -- let deferred = this._pendingClientToolCalls.get(toolCallId); -- if (!deferred) { -- deferred = new DeferredPromise(); -- this._pendingClientToolCalls.set(toolCallId, deferred); -- } -- const result = await deferred.p; -- this._pendingClientToolCalls.delete(toolCallId); -- return result; +- // The completion may legitimately arrive before this handler +- // registers; the registry buffers early results so register() +- // resolves immediately in that case. +- return await this._pendingClientToolCalls.register(toolCallId); - } catch (error) { - this._logService.error(error, `[Copilot:${this.sessionId}] Failed in client tool handler: tool=${def.name}, toolCallId=${toolCallId}`); - throw error; @@ -3744,16 +4709,42 @@ index 770817327b0..00000000000 - } - - /** -- * Resolves a pending client tool call. Returns `true` if the -- * toolCallId was found and handled. +- * Builds SDK tool handlers for the agent host's server tools. Each handler +- * executes the tool against this session's state via the +- * {@link IAgentServerToolHost} and returns its textual result. Returns an +- * empty list when no server-tool host is wired (e.g. test / standalone +- * construction). - */ -- handleClientToolCallComplete(toolCallId: string, result: ToolCallResult) { -- let deferred = this._pendingClientToolCalls.get(toolCallId); -- if (!deferred) { -- deferred = new DeferredPromise(); -- this._pendingClientToolCalls.set(toolCallId, deferred); +- // eslint-disable-next-line @typescript-eslint/no-explicit-any +- private _createServerSdkTools(): Tool[] { +- const host = this._serverToolHost; +- if (!host) { +- return []; - } +- const sessionUri = this.sessionUri.toString(); +- return host.definitions.map(def => ({ +- name: def.name, +- description: def.description ?? '', +- parameters: def.inputSchema ?? { type: 'object' as const, properties: {} }, +- handler: async (args: Record): Promise => { +- try { +- const text = host.executeTool(sessionUri, def.name, args); +- return { textResultForLlm: text, resultType: 'success' }; +- } catch (error) { +- const message = error instanceof Error ? error.message : String(error); +- this._logService.error(error, `[Copilot:${this.sessionId}] Failed in server tool handler: tool=${def.name}`); +- return { textResultForLlm: message, resultType: 'failure', error: message }; +- } +- }, +- })); +- } - +- /** +- * Resolves a pending client tool call. If the SDK handler has not yet +- * registered for `toolCallId`, the result is buffered so the handler +- * resolves immediately once it does. +- */ +- handleClientToolCallComplete(toolCallId: string, result: ToolCallResult) { - const textContent = result.content - ?.filter(c => c.type === ToolResultContentType.Text) - .map(c => c.text) @@ -3762,16 +4753,17 @@ index 770817327b0..00000000000 - const binaryResults = result.content - ?.filter(c => c.type === ToolResultContentType.EmbeddedResource) - .map(c => ({ data: c.data, mimeType: c.contentType, type: (/^image(\/|$)/.test(c.contentType) ? 'image' : 'resource') as 'image' | 'resource' })); +- const textResultForLlm = textContent.trim() ? textContent : getEmptyToolResultText(binaryResults); - - if (result.success) { -- deferred.complete({ -- textResultForLlm: textContent, +- this._pendingClientToolCalls.respondOrBuffer(toolCallId, { +- textResultForLlm, - resultType: 'success', - binaryResultsForLlm: binaryResults?.length ? binaryResults : undefined, - }); - } else { -- deferred.complete({ -- textResultForLlm: textContent || result.error?.message || 'Tool call failed', +- this._pendingClientToolCalls.respondOrBuffer(toolCallId, { +- textResultForLlm: textContent.trim() ? textContent : result.error?.message || 'Tool call failed', - resultType: 'failure', - error: result.error?.message, - binaryResultsForLlm: binaryResults?.length ? binaryResults : undefined, @@ -3796,6 +4788,11 @@ index 770817327b0..00000000000 - this._wrapper = this._register(wrapper); - this._subscribeToEvents(); - this._subscribeForLogging(); +- +- // Advertise the agent host's server tools for this session so clients +- // see them as server-provided. Execution happens in-process via the SDK +- // tool handlers built in `_createServerSdkTools`. +- this._serverToolHost?.advertise(this.sessionUri.toString()); - } - - private _createRuntimeAdapter(): ICopilotSessionRuntime { @@ -3806,6 +4803,7 @@ index 770817327b0..00000000000 - handleElicitationRequest: context => this._handleElicitationRequest(context), - requestUnsandboxedCommandConfirmation: request => this._requestUnsandboxedCommandConfirmation(request), - createClientSdkTools: () => this._createClientSdkTools(), +- createServerSdkTools: () => this._createServerSdkTools(), - handlePreToolUse: input => this._handlePreToolUse(input), - handlePostToolUse: input => this._handlePostToolUse(input), - }; @@ -3814,8 +4812,11 @@ index 770817327b0..00000000000 - // ---- session operations ------------------------------------------------- - - async send(prompt: string, attachments?: readonly MessageAttachment[], turnId?: string, mode?: CopilotSdkMode): Promise { -- if (turnId) { -- this._turnId = turnId; +- if (turnId && this._currentTurn?.id !== turnId) { +- // Establish the `pending` turn for this message. Callers normally +- // call `resetTurnState` just before `send()`; this covers the +- // direct-send path and is a no-op when the turn already exists. +- this.resetTurnState(turnId); - } - this._logService.info(`[Copilot:${this.sessionId}] sendMessage called: "${prompt.substring(0, 100)}${prompt.length > 100 ? '...' : ''}" (${attachments?.length ?? 0} attachments)`); - @@ -3823,14 +4824,62 @@ index 770817327b0..00000000000 - if (slashCommand?.command === 'compact') { - try { - await this._wrapper.session.rpc.history.compact(); +- this.emitInitialMarkdown(localize('copilotAgent.compactionCompleted', "Compaction completed")); - } catch (err) { +- if (getErrorMessage(err).toLowerCase().includes('nothing to compact')) { +- this.emitInitialMarkdown(localize('copilotAgent.compactionCompleted', "Compaction completed")); +- this._completeActiveTurn(); +- return; +- } - this._logService.error(err, `[Copilot:${this.sessionId}] rpc.history.compact failed`); - throw err; - } +- // `/compact` is handled inline via the history RPC rather than by +- // driving an SDK turn, so the SDK never fires `onIdle` to close the +- // turn. Complete the turn here so the session returns to idle +- // instead of spinning forever. +- this._completeActiveTurn(); - return; - } -- if (slashCommand?.command === 'research') { -- prompt = slashCommand.rest ? `/research ${slashCommand.rest}` : '/research'; +- if (slashCommand && isRuntimeCopilotSlashCommand(slashCommand.command) && await this.hasRuntimeSlashCommand(slashCommand.command)) { +- let result: CopilotCommandInvocationResult; +- try { +- result = await this._wrapper.session.rpc.commands.invoke({ +- name: slashCommand.command, +- }); +- } catch (err) { +- this._logService.error(err, `[Copilot:${this.sessionId}] rpc.commands.invoke(${slashCommand.command}) failed`); +- throw err; +- } +- switch (result.kind) { +- case 'text': +- this._emitMarkdownDelta(result.markdown === true ? result.text : escapeMarkdownSyntaxTokens(result.text)); +- break; +- case 'completed': +- if (result.message) { +- this._emitMarkdownDelta(result.message); +- } +- break; +- case 'agent-prompt': { +- const runtimeMode = toCopilotSdkMode(result.mode); +- if (runtimeMode) { +- mode = runtimeMode; +- } +- prompt = result.prompt; +- break; +- } +- default: +- this._logService.warn(`[Copilot:${this.sessionId}] Unexpected /${slashCommand.command} command result kind: ${result.kind}`); +- this._emitMarkdownDelta(localize('copilotSlashCommand.unsupportedRuntimeResult', "The /{0} command returned an unsupported result.", slashCommand.command)); +- break; +- } +- if (result.kind !== 'agent-prompt') { +- this._completeActiveTurn(); +- return; +- } +- } +- if (slashCommand && isPromptInvokedCopilotSlashCommand(slashCommand.command)) { +- prompt = slashCommand.rest ? `/${slashCommand.command} ${slashCommand.rest}` : `/${slashCommand.command}`; - } - if (slashCommand?.command === 'plan') { - mode = 'plan'; @@ -3861,6 +4910,31 @@ index 770817327b0..00000000000 - this._logService.info(`[Copilot:${this.sessionId}] session.send() returned`); - } - +- async hasRuntimeSlashCommand(command: string): Promise { +- try { +- return (await this._getRuntimeSlashCommands()).has(command); +- } catch (err) { +- this._logService.warn(`[Copilot:${this.sessionId}] rpc.commands.list failed`, err); +- return false; +- } +- } +- +- private _getRuntimeSlashCommands(): Promise> { +- const now = Date.now(); +- if (this._runtimeSlashCommandCache && this._runtimeSlashCommandCache.expiresAt > now) { +- return this._runtimeSlashCommandCache.promise; +- } +- const promise = this._wrapper.session.rpc.commands.list({ includeBuiltins: true, includeSkills: false, includeClientCommands: false }) +- .then(result => new Set(result.commands.filter(command => command.kind === 'builtin').map(command => command.name))); +- this._runtimeSlashCommandCache = { expiresAt: now + RUNTIME_SLASH_COMMAND_CACHE_TTL_MS, promise }; +- promise.catch(() => { +- if (this._runtimeSlashCommandCache?.promise === promise) { +- this._runtimeSlashCommandCache = undefined; +- } +- }); +- return promise; +- } +- - /** - * Translate a protocol {@link MessageAttachment} into the Copilot CLI - * SDK's `attachments` payload shape. Resource attachments map to the @@ -3871,12 +4945,28 @@ index 770817327b0..00000000000 - * Simple attachments with a model representation map to `text/plain` - * blob attachments. - * +- * Any Resource attachment carrying a {@link TextSelection} (e.g. `displayKind === 'selection'` or `'symbol'`) is +- * mapped to the SDK's `selection` variant so the range survives the round-trip — keying off the `selection` field +- * rather than just `displayKind` avoids symbol attachments degrading to a plain file reference (#315193). +- * - * For selections we read the resource content from disk and slice it - * by the carried range (the protocol's {@link TextSelection} only - * carries the range, not the inline text). On read failure the - * selection downgrades to a plain file reference. - */ - private async _toSdkAttachment(attachment: MessageAttachment): Promise { +- if (isAgentFeedbackAnnotationsAttachment(attachment)) { +- const rendered = renderAgentFeedbackAnnotationsAttachment(attachment); +- if (!rendered) { +- return undefined; +- } +- return { +- type: 'blob' as const, +- data: encodeBase64(VSBuffer.fromString(rendered)), +- mimeType: 'text/plain', +- displayName: attachment.label, +- }; +- } - if (attachment.type === MessageAttachmentKind.Simple) { - if (attachment.modelRepresentation) { - return { @@ -3897,7 +4987,7 @@ index 770817327b0..00000000000 - const uri = URI.parse(attachment.uri); - const path = uri.scheme === 'file' ? uri.fsPath : uri.toString(); - const displayName = attachment.label ?? path; -- if (attachment.displayKind === 'selection' && attachment.selection) { +- if (attachment.selection) { - try { - const text = await this._readSelectedText(uri, attachment.selection.range); - return { type: 'selection' as const, filePath: path, displayName, text, selection: attachment.selection.range }; @@ -3952,6 +5042,15 @@ index 770817327b0..00000000000 - } - } - +- /** +- * `true` when the session's effective `mode` is `autopilot` — the +- * autonomous, continue-until-done mode in which no user is available to +- * answer questions or fill in elicitation forms. +- */ +- private _isAutopilotMode(): boolean { +- return this._configurationService.getEffectiveValue(this.sessionUri.toString(), platformSessionSchema, SessionConfigKey.Mode) === 'autopilot'; +- } +- - async sendSteering(steeringMessage: PendingMessage): Promise { - if (this._steeringMessagesInFlight.has(steeringMessage.id) || this._pendingSteeringFlips.has(steeringMessage.id)) { - return; @@ -4012,9 +5111,96 @@ index 770817327b0..00000000000 - await this._wrapper.session.disconnect(); - } - -- async setModel(model: string, reasoningEffort?: SessionConfig['reasoningEffort']): Promise { +- async setModel(model: string, reasoningEffort?: SessionConfig['reasoningEffort'], contextTier?: SessionConfig['contextTier']): Promise { - this._logService.info(`[Copilot:${this.sessionId}] Changing model to: ${model}`); -- await this._wrapper.session.setModel(model, { reasoningEffort }); +- this._lastSeenModelId = model; +- await this._wrapper.session.setModel(model, { reasoningEffort, contextTier }); +- } +- +- /** +- * Dispatches an MCP JSON-RPC method received on the `mcp://` side +- * channel to the Copilot SDK's `session.rpc.mcp.*` surface. +- * +- * Mapping: +- * - `tools/list` → `rpc.mcp.apps.listTools` +- * - `tools/call` → `rpc.mcp.apps.callTool` +- * - `resources/read` → `rpc.mcp.apps.readResource` +- * - `resources/list` → `rpc.mcp.apps.listResources` (empty list fallback) +- * - `resources/templates/list` → `rpc.mcp.apps.listResourceTemplates` (empty list fallback) +- * - `sampling/createMessage` → `rpc.mcp.executeSampling` +- * +- * Other MCP methods are rejected with `Method not found` (the caller +- * translates that into a JSON-RPC `-32601`). +- */ +- async handleMcpRequest(serverName: string, method: string, params: Record | undefined): Promise { +- const apps = this._wrapper.session.rpc.mcp.apps; +- switch (method) { +- case 'tools/list': +- return apps.listTools({ serverName, originServerName: serverName }); +- case 'tools/call': { +- const name = params && typeof params['name'] === 'string' ? params['name'] : undefined; +- if (!name) { +- throw new Error(`tools/call missing 'name' parameter`); +- } +- const rawArgs = params ? params['arguments'] : undefined; +- const args = isObject(rawArgs) ? rawArgs as Record : undefined; +- return apps.callTool({ serverName, toolName: name, arguments: args, originServerName: serverName }); +- } +- case 'resources/read': { +- const uri = params && typeof params['uri'] === 'string' ? params['uri'] : undefined; +- if (!uri) { +- throw new Error(`resources/read missing 'uri' parameter`); +- } +- return apps.readResource({ serverName, uri }); +- } +- case 'resources/list': { +- // Not implemented in the SDK yet +- return { resources: [] }; +- } +- case 'resources/templates/list': { +- // Not implemented in the SDK yet +- return { resourceTemplates: [] }; +- } +- case 'sampling/createMessage': +- return this._handleSamplingCreateMessage(serverName, params); +- default: +- throw new Error(`Method not found: ${method}`); +- } +- } +- +- /** +- * Forwards an App→host `sampling/createMessage` request received +- * over the AHP `mcp://` channel to `rpc.mcp.executeSampling`. The +- * Copilot runtime owns the MCP→chat-completion conversion and the +- * sampling response shape, so we pass the raw MCP params through +- * untouched and return the SDK's result directly. +- * +- * Resolves the JSON-RPC request with the `CreateMessageResult` on +- * success and rejects on failure/cancellation, mirroring the +- * `sampling/createMessage` MCP contract. +- */ +- private async _handleSamplingCreateMessage(serverName: string, params: Record | undefined): Promise { +- if (!params) { +- throw new Error(`sampling/createMessage missing params`); +- } +- +- const requestId = generateUuid(); +- const mcpRequestId = generateUuid(); +- this._pendingMcpSamplings.add(requestId); +- try { +- const result = await this._wrapper.session.rpc.mcp.executeSampling({ +- requestId, +- serverName, +- mcpRequestId, +- request: params, +- }); +- if (result.action === 'success') { +- return result.result ?? null; +- } +- throw new Error(`sampling/createMessage ${result.action}${result.error ? `: ${result.error}` : ''}`); +- } finally { +- this._pendingMcpSamplings.delete(requestId); +- } - } - - /** @@ -4093,6 +5279,19 @@ index 770817327b0..00000000000 - } - } - +- // Auto-approve the agent host's server tools. They only read or +- // mutate the session's own server-held state and never touch the +- // workspace, shell, or network, so prompting for them is redundant +- // noise. Tools that explicitly require confirmation (e.g. revealing +- // unreviewed review comments) are excluded so the user is prompted. +- if (request.kind === 'custom-tool' && typeof request.toolName === 'string' +- && this._serverToolHost?.toolNames.includes(request.toolName) +- && !this._serverToolHost.requiresConfirmation(request.toolName) +- ) { +- this._logService.info(`[Copilot:${this.sessionId}] Auto-approving server tool ${request.toolName}`); +- return { kind: 'approve-once' }; +- } +- - const isShellRequest = request.kind === 'shell' - || (request.kind === 'custom-tool' && typeof request.toolName === 'string' && isShellTool(request.toolName)); - @@ -4134,9 +5333,9 @@ index 770817327b0..00000000000 - // Fire a pending_confirmation signal to transition the tool to PendingConfirmation - const toolName = request.toolName ?? request.kind; - // Forward the tool's parentToolCallId (if any) so the host can -- // route the resulting SessionToolCallReady to the correct +- // route the resulting ChatToolCallReady to the correct - // subagent session — without it the action would land on the -- // parent session, which has no matching SessionToolCallStart. +- // parent session, which has no matching ChatToolCallStart. - const parentToolCallId = this._activeToolCalls.get(toolCallId)?.parentToolCallId; - this._onDidSessionProgress.fire({ - kind: 'pending_confirmation', @@ -4201,25 +5400,31 @@ index 770817327b0..00000000000 - } - - /** -- * Returns true when our custom shell tool is registered and the -- * {@link TerminalSandboxEngine} reports sandboxing is enabled — i.e. -- * shell commands run inside the sandbox by default. The shell tool -- * prompts on its own when escalating to unsandboxed execution, so the -- * SDK's pre-call permission prompt is redundant in that case. +- * Returns true when shell commands run inside a sandbox by default — either +- * through the AgentHost's own {@link TerminalSandboxEngine} (when the custom +- * terminal tool is enabled) or through the SDK's built-in shell tool wrapped +- * by the `sandboxConfig` we pushed via `session.options.update`. +- * +- * In both cases the shell tool prompts on its own when escalating to +- * unsandboxed execution, so the SDK's pre-call `shell` permission prompt +- * is redundant and we auto-approve it. - * -- * Returns false when shell tools are not registered (the SDK's built-in -- * terminal runs unsandboxed unless `AgentHostConfigKey.EnableCustomTerminalTool` -- * is set) -- * so the standard confirmation flow is preserved. +- * Returns false when neither sandbox path is configured, so the standard +- * confirmation flow is preserved. - */ - private async _isShellSandboxedByDefault(): Promise { -- if (!this._shellManager) { -- return false; -- } -- if (this._configurationService.getRootValue(agentHostCustomizationConfigSchema, AgentHostConfigKey.EnableCustomTerminalTool) !== true) { -- return false; +- const customTerminalToolEnabled = this._configurationService.getRootValue(agentHostCustomizationConfigSchema, AgentHostConfigKey.EnableCustomTerminalTool) === true; +- if (customTerminalToolEnabled) { +- if (!this._shellManager) { +- return false; +- } +- return this._shellManager.getOrCreateSandboxEngine().isEnabled(); - } -- return this._shellManager.getOrCreateSandboxEngine().isEnabled(); +- // SDK-managed shell path: gate on the same host config that +- // `CopilotSessionLauncher` reads when forwarding `sandboxConfig` to +- // the SDK, so the two stay in lock-step. +- const sandbox = this._configurationService.getRootValue(sandboxConfigSchema, AgentHostSandboxConfigKey.Sandbox); +- return buildSandboxConfigForSdk(process.platform, sandbox) !== undefined; - } - - /** @@ -4345,7 +5550,7 @@ index 770817327b0..00000000000 - request: UserInputRequest, - _invocation: { sessionId: string }, - ): Promise { -- const isAutopilot = this._configurationService.getEffectiveValue(this.sessionUri.toString(), platformSessionSchema, SessionConfigKey.AutoApprove) === 'autopilot'; +- const isAutopilot = this._isAutopilotMode(); - if (isAutopilot) { - return { - answer: 'The user is not available to answer your question. Choose a pragmatic option best aligned with the context of the request.', @@ -4359,15 +5564,15 @@ index 770817327b0..00000000000 - const questionId = generateUuid(); - this._logService.info(`[Copilot:${this.sessionId}] User input request: requestId=${requestId}, question="${questionPreview}"`); - -- const deferred = new DeferredPromise<{ response: SessionInputResponseKind; answers?: Record }>(); +- const deferred = new DeferredPromise<{ response: ChatInputResponseKind; answers?: Record }>(); - this._pendingUserInputs.set(requestId, { deferred, questionId }); - -- // Build the protocol SessionInputRequest from the SDK's simple format -- const inputRequest: SessionInputRequest = { +- // Build the protocol ChatInputRequest from the SDK's simple format +- const inputRequest: ChatInputRequest = { - id: requestId, - questions: [request.choices && request.choices.length > 0 - ? { -- kind: SessionInputQuestionKind.SingleSelect, +- kind: ChatInputQuestionKind.SingleSelect, - id: questionId, - message: request.question, - required: true, @@ -4375,7 +5580,7 @@ index 770817327b0..00000000000 - allowFreeformInput: request.allowFreeform ?? true, - } - : { -- kind: SessionInputQuestionKind.Text, +- kind: ChatInputQuestionKind.Text, - id: questionId, - message: request.question, - required: true, @@ -4384,27 +5589,27 @@ index 770817327b0..00000000000 - }; - - this._emitAction({ -- type: ActionType.SessionInputRequested, +- type: ActionType.ChatInputRequested, - request: inputRequest, - }); - - const result = await deferred.p; - this._logService.info(`[Copilot:${this.sessionId}] User input response: requestId=${requestId}, response=${result.response}`); - -- if (result.response !== SessionInputResponseKind.Accept || !result.answers) { +- if (result.response !== ChatInputResponseKind.Accept || !result.answers) { - return { answer: '', wasFreeform: true }; - } - - // Extract the answer for our single question - const answer = result.answers[questionId]; -- if (!answer || answer.state === SessionInputAnswerState.Skipped) { +- if (!answer || answer.state === ChatInputAnswerState.Skipped) { - return { answer: '', wasFreeform: true }; - } - - const { value: val } = answer; -- if (val.kind === SessionInputAnswerValueKind.Text) { +- if (val.kind === ChatInputAnswerValueKind.Text) { - return { answer: val.value, wasFreeform: true }; -- } else if (val.kind === SessionInputAnswerValueKind.Selected) { +- } else if (val.kind === ChatInputAnswerValueKind.Selected) { - const wasFreeform = !request.choices?.includes(val.value); - return { answer: val.value, wasFreeform }; - } @@ -4423,9 +5628,9 @@ index 770817327b0..00000000000 - * - * - `form` mode requests are projected from the SDK's - * {@link ElicitationSchema} into a list of -- * {@link SessionInputQuestion}s. +- * {@link ChatInputQuestion}s. - * - `url` mode requests surface as a question-less input request whose -- * {@link SessionInputRequest.url} drives the renderer's "open URL" +- * {@link ChatInputRequest.url} drives the renderer's "open URL" - * affordance. - * - * Under autopilot the request is auto-cancelled — there is no user @@ -4433,7 +5638,7 @@ index 770817327b0..00000000000 - * be misleading to the MCP server. - */ - private async _handleElicitationRequest(context: ElicitationContext): Promise { -- const isAutopilot = this._configurationService.getEffectiveValue(this.sessionUri.toString(), platformSessionSchema, SessionConfigKey.AutoApprove) === 'autopilot'; +- const isAutopilot = this._isAutopilotMode(); - if (isAutopilot) { - return { action: 'cancel' }; - } @@ -4445,14 +5650,14 @@ index 770817327b0..00000000000 - - const schema = context.mode === 'url' ? undefined : context.requestedSchema; - const requiredSet = new Set(schema?.required ?? []); -- const questions: SessionInputQuestion[] | undefined = schema +- const questions: ChatInputQuestion[] | undefined = schema - ? Object.entries(schema.properties).map(([fieldName, field]) => elicitationFieldToQuestion(fieldName, field, requiredSet.has(fieldName))) - : undefined; - -- const deferred = new DeferredPromise<{ response: SessionInputResponseKind; answers?: Record }>(); +- const deferred = new DeferredPromise<{ response: ChatInputResponseKind; answers?: Record }>(); - this._pendingElicitations.set(requestId, { deferred, schema }); - -- const inputRequest: SessionInputRequest = { +- const inputRequest: ChatInputRequest = { - id: requestId, - message: context.message, - ...(context.mode === 'url' && context.url ? { url: context.url } : {}), @@ -4460,23 +5665,23 @@ index 770817327b0..00000000000 - }; - - this._emitAction({ -- type: ActionType.SessionInputRequested, +- type: ActionType.ChatInputRequested, - request: inputRequest, - }); - - const result = await deferred.p; - this._logService.info(`[Copilot:${this.sessionId}] Elicitation response: requestId=${requestId}, response=${result.response}`); - -- if (result.response === SessionInputResponseKind.Decline) { +- if (result.response === ChatInputResponseKind.Decline) { - return { action: 'decline' }; - } -- if (result.response !== SessionInputResponseKind.Accept) { +- if (result.response !== ChatInputResponseKind.Accept) { - return { action: 'cancel' }; - } - const answers = result.answers ?? {}; - if (!schema) { - const freeform = answers.answer; -- if (freeform && freeform.state !== SessionInputAnswerState.Skipped && freeform.value.kind === SessionInputAnswerValueKind.Text) { +- if (freeform && freeform.state !== ChatInputAnswerState.Skipped && freeform.value.kind === ChatInputAnswerValueKind.Text) { - return { action: 'accept', content: { answer: freeform.value.value } }; - } - return { action: 'accept' }; @@ -4495,7 +5700,7 @@ index 770817327b0..00000000000 - } - } - -- respondToUserInputRequest(requestId: string, response: SessionInputResponseKind, answers?: Record): boolean { +- respondToUserInputRequest(requestId: string, response: ChatInputResponseKind, answers?: Record): boolean { - const pendingPlanReview = this._pendingPlanReviews.get(requestId); - if (pendingPlanReview) { - this._pendingPlanReviews.delete(requestId); @@ -4538,14 +5743,14 @@ index 770817327b0..00000000000 - */ - private _resolveExitPlanMode( - pending: { actions: readonly string[]; recommendedAction: string; questionId: string }, -- response: SessionInputResponseKind, -- answers?: Record, +- response: ChatInputResponseKind, +- answers?: Record, - ): IExitPlanModeResponse { -- if (response !== SessionInputResponseKind.Accept) { +- if (response !== ChatInputResponseKind.Accept) { - return { approved: false }; - } - const answer = answers?.[pending.questionId]; -- if (!answer || answer.state === SessionInputAnswerState.Skipped) { +- if (!answer || answer.state === ChatInputAnswerState.Skipped) { - return { approved: false }; - } - const value = answer.value; @@ -4556,11 +5761,11 @@ index 770817327b0..00000000000 - // user types instead of picking). Normalize to one shape. - let candidateAction: string | undefined; - let feedback: string | undefined; -- if (value.kind === SessionInputAnswerValueKind.Selected) { +- if (value.kind === ChatInputAnswerValueKind.Selected) { - candidateAction = value.value; - const freeform = value.freeformValues?.find(s => s.trim().length > 0)?.trim(); - feedback = freeform; -- } else if (value.kind === SessionInputAnswerValueKind.Text) { +- } else if (value.kind === ChatInputAnswerValueKind.Text) { - feedback = value.value.trim() || undefined; - } else { - return { approved: false }; @@ -4592,6 +5797,14 @@ index 770817327b0..00000000000 - return { approved: false }; - } - +- // Reflect the chosen implementation path on the AHP `mode` axis right +- // away so the mode picker updates as soon as the user approves the +- // plan (e.g. Plan → Autopilot when they pick "Implement with +- // Autopilot"). The SDK also fires `session.mode_changed`, but that is +- // async; writing here makes the UI update deterministic. The patch is +- // idempotent, so the later event is a no-op. +- this._syncAhpModeFromExitPlanAction(selectedAction); +- - const isAutopilot = selectedAction === 'autopilot' || selectedAction === 'autopilot_fleet'; - return { - approved: true, @@ -4600,6 +5813,26 @@ index 770817327b0..00000000000 - }; - } - +- /** +- * Translates an approved `exit_plan_mode` action into the AHP `mode` axis +- * and writes it so the mode picker reflects the choice immediately: +- * +- * - `autopilot` / `autopilot_fleet` → `mode='autopilot'`. +- * - `interactive` → `mode='interactive'`. +- * - `exit_only` (approve plan without executing) leaves the mode untouched. +- */ +- private _syncAhpModeFromExitPlanAction(selectedAction: string): void { +- switch (selectedAction) { +- case 'autopilot': +- case 'autopilot_fleet': +- this._syncAhpConfigFromSdkMode('autopilot'); +- break; +- case 'interactive': +- this._syncAhpConfigFromSdkMode('interactive'); +- break; +- } +- } +- - private async _handlePreToolUse(input: PreToolUseHookInput): Promise { - try { - if (isEditTool(input.toolName, getToolCommand(input))) { @@ -4640,7 +5873,7 @@ index 770817327b0..00000000000 - this._logService.info(`[Copilot:${sessionId}] System notification received: kind=${e.data.kind.type}`); - if (this._turnId) { - this._emitAction({ -- type: ActionType.SessionResponsePart, +- type: ActionType.ChatResponsePart, - turnId: this._turnId, - part: { - kind: ResponsePartKind.SystemNotification, @@ -4653,7 +5886,7 @@ index 770817327b0..00000000000 - const turnId = generateUuid(); - this.resetTurnState(turnId); - this._emitAction({ -- type: ActionType.SessionTurnStarted, +- type: ActionType.ChatTurnStarted, - turnId, - message: { - text: notification.messageText, @@ -4685,6 +5918,8 @@ index 770817327b0..00000000000 - if (e.data.source && e.data.source.toLowerCase() !== 'user') { - return; - } +- // First SDK event for the loop: promote the turn out of `pending`. +- this._currentTurn?.markRunning(); - const steering = this._takeMatchingPendingSteering(e.data.content); - if (steering) { - this._beginSteeringTurn(steering); @@ -4721,13 +5956,13 @@ index 770817327b0..00000000000 - } - const parentToolCallId = this._parentToolCallIdForSubagentEvent(e); - const markdownScope = parentToolCallId ?? ''; -- if (this._currentMarkdownPartIds.has(markdownScope)) { +- if (this._currentTurn?.markdownPartIds.has(markdownScope)) { - return; - } - const partId = generateUuid(); -- this._currentMarkdownPartIds.set(markdownScope, partId); +- this._currentTurn?.markdownPartIds.set(markdownScope, partId); - this._emitAction({ -- type: ActionType.SessionResponsePart, +- type: ActionType.ChatResponsePart, - turnId: this._turnId, - part: { kind: ResponsePartKind.Markdown, id: partId, content: e.data.content }, - }, parentToolCallId); @@ -4769,21 +6004,36 @@ index 770817327b0..00000000000 - return; - } - const parentToolCallId = this._parentToolCallIdForSubagentEvent(e); -- this._activeToolCalls.set(e.data.toolCallId, { toolName: e.data.toolName, displayName, parameters, content: [], parentToolCallId, startTimeMs: Date.now(), mcpServerName: e.data.mcpServerName }); +- this._activeToolCalls.set(e.data.toolCallId, { toolName: e.data.toolName, displayName, parameters, content: [], parentToolCallId, startTimeMs: Date.now(), mcpServerName: e.data.mcpServerName, meta: undefined }); +- if (isTaskCompleteTool(e.data.toolName)) { +- const scope = parentToolCallId ?? ''; +- this._currentTurn?.markdownPartIds.delete(scope); +- this._currentTurn?.reasoningPartIds.delete(scope); +- return; +- } - const toolKind = getToolKind(e.data.toolName); - const subagentMeta = toolKind === 'subagent' ? getSubagentMetadata(parameters) : undefined; -- const toolClientId = this._clientToolNames.has(e.data.toolName) ? this._appliedSnapshot.clientId : undefined; -- const contributor = toolClientId ? { kind: ToolCallContributorKind.Client, clientId: toolClientId } as const : undefined; +- +- let contributor: { readonly kind: ToolCallContributorKind.Client; readonly clientId: string } | { readonly kind: ToolCallContributorKind.MCP; readonly customizationId: string } | undefined; +- const isClientTool = this._clientToolNames.has(e.data.toolName); +- if (isClientTool && this._activeClientState.clientId) { +- contributor = { kind: ToolCallContributorKind.Client, clientId: this._activeClientState.clientId }; +- } else if (e.data.mcpServerName) { +- const customizationId = this._mcpCustomizations.customizationIdForServer(e.data.mcpServerName); +- if (customizationId !== undefined) { +- contributor = { kind: ToolCallContributorKind.MCP, customizationId }; +- } +- } - - // A new tool call invalidates the current markdown and reasoning - // parts so the next text/reasoning delta after the tool call - // starts a fresh part. Without invalidating reasoning here, a - // later round of reasoning (after tool_start/tool_complete) - // would silently append to the pre-tool-call reasoning block. -- this._currentMarkdownPartIds.delete(parentToolCallId ?? ''); -- this._currentReasoningPartIds.delete(parentToolCallId ?? ''); +- this._currentTurn?.markdownPartIds.delete(parentToolCallId ?? ''); +- this._currentTurn?.reasoningPartIds.delete(parentToolCallId ?? ''); - -- const meta: Record = { toolKind, language: toolKind === 'terminal' ? getShellLanguage(e.data.toolName) : undefined }; +- const meta: Mutable = { toolKind, language: toolKind === 'terminal' ? getShellLanguage(e.data.toolName) : undefined }; - if (subagentMeta?.description) { - meta.subagentDescription = subagentMeta.description; - } @@ -4799,26 +6049,76 @@ index 770817327b0..00000000000 - if (e.data.mcpToolName) { - meta.mcpToolName = e.data.mcpToolName; - } +- // TODO(sdk-gap): the Copilot SDK doesn't yet surface MCP App +- // `_meta.ui.resourceUri` on `tool.execution_start`; we attach +- // it on `tool.execution_complete` below so the App webview +- // mounts on completion. Drop-in once the SDK exposes it here: +- // const resourceUri = e.data.toolDescription?._meta?.ui?.resourceUri; +- // if (resourceUri) { meta.ui = { resourceUri }; } +- +- // Stash the start-time meta on the tracked tool call so the +- // `tool.execution_complete` emission below can merge any +- // additional namespaces (e.g. `ui`) on top without dropping +- // what we already published at start time. +- const tracked = this._activeToolCalls.get(e.data.toolCallId); +- if (tracked) { +- tracked.meta = meta; +- } - - this._emitAction({ -- type: ActionType.SessionToolCallStart, +- type: ActionType.ChatToolCallStart, - turnId: this._turnId, - toolCallId: e.data.toolCallId, - toolName: e.data.toolName, - displayName, - contributor, -- _meta: meta, +- _meta: toToolCallMeta(meta), - }, parentToolCallId); - +- // No client is connected to run this client tool. Fail it +- // immediately instead of leaving it pending until the +- // server-side disconnect timeout fires. We emit the completion +- // ourselves and drop the active-tool entry so the SDK's own +- // tool.execution_complete for this id is suppressed. +- if (isClientTool && !contributor) { +- this._logService.warn(`[Copilot:${sessionId}] Client tool '${e.data.toolName}' started with no connected client; failing it immediately.`); +- this._activeToolCalls.delete(e.data.toolCallId); +- this._emitAction({ +- type: ActionType.ChatToolCallReady, +- turnId: this._turnId, +- toolCallId: e.data.toolCallId, +- invocationMessage: getInvocationMessage(e.data.toolName, displayName, parameters), +- toolInput: getToolInputString(e.data.toolName, parameters, toolArgs), +- confirmed: ToolCallConfirmationReason.NotNeeded, +- }, parentToolCallId); +- this._emitAction({ +- type: ActionType.ChatToolCallComplete, +- turnId: this._turnId, +- toolCallId: e.data.toolCallId, +- result: { +- success: false, +- pastTenseMessage: `${displayName} failed`, +- error: { message: `No client was connected to run ${displayName}` }, +- }, +- }, parentToolCallId); +- this._pendingClientToolCalls.respondOrBuffer(e.data.toolCallId, { +- textResultForLlm: `No client was connected to run ${displayName}.`, +- resultType: 'failure', +- error: 'No client connected', +- }); +- return; +- } +- - // For client tools, do NOT auto-ready — the tool handler will fire - // a separate tool_ready signal once the deferred is in place (or -- // the permission flow fires it first). -- if (toolClientId) { +- // the permission flow fires it first). MCP tools have no such +- // handler and are auto-readied below alongside built-in tools. +- if (contributor?.kind === ToolCallContributorKind.Client) { - return; - } - - this._emitAction({ -- type: ActionType.SessionToolCallReady, +- type: ActionType.ChatToolCallReady, - turnId: this._turnId, - toolCallId: e.data.toolCallId, - invocationMessage: getInvocationMessage(e.data.toolName, displayName, parameters), @@ -4842,6 +6142,19 @@ index 770817327b0..00000000000 - const displayName = tracked.displayName; - const toolOutput = e.data.error?.message ?? e.data.result?.content; - +- if (isTaskCompleteTool(tracked.toolName)) { +- this._sendToolInvokedTelemetry(e.data.success, e.data.error?.code, tracked); +- const summary = getTaskCompleteMarkdown(tracked.parameters, toolOutput); +- if (summary) { +- this._emitAction({ +- type: ActionType.ChatResponsePart, +- turnId: this._turnId, +- part: { kind: ResponsePartKind.Markdown, id: generateUuid(), content: summary }, +- }); +- } +- return; +- } +- - const content: ToolResultContent[] = [...tracked.content]; - if (toolOutput !== undefined) { - content.push({ type: ToolResultContentType.Text, text: toolOutput }); @@ -4851,7 +6164,7 @@ index 770817327b0..00000000000 - const filePaths = isEditTool(tracked.toolName, command) ? this._getEditFilePaths(tracked.parameters) : []; - for (const filePath of filePaths) { - try { -- const fileEdit = await this._editTracker.takeCompletedEdit(this._turnId, e.data.toolCallId, filePath); +- const fileEdit = await this._editTracker.takeCompletedEdit(this._turnId, e.data.toolCallId, filePath, tracked.toolName, tracked.parameters, this._lastSeenModelId); - if (fileEdit) { - content.push(fileEdit); - } @@ -4874,8 +6187,25 @@ index 770817327b0..00000000000 - } - - this._sendToolInvokedTelemetry(e.data.success, e.data.error?.code, tracked); +- // eslint-disable-next-line local/code-no-untyped-meta-access -- Copilot SDK's own typed `_meta`, not the AHP protocol bag. +- const resourceUri = e.data.toolDescription?._meta?.ui?.resourceUri; +- let completeMeta: IToolCallMeta | undefined = tracked.meta; +- if (resourceUri) { +- const ui: Mutable = { resourceUri }; +- if (tracked.mcpServerName) { +- const channel = this._mcpCustomizations.channelForServer(tracked.mcpServerName); +- if (channel !== undefined) { +- ui.channel = channel; +- } +- } +- // Merge the `ui` namespace on top of whatever meta we +- // emitted at start time (`toolKind`, `subagentDescription`, +- // `toolArguments`, …). Reducers replace the whole `_meta` +- // blob, so we must do the merge here. +- completeMeta = { ...(tracked.meta ?? {}), ui }; +- } - this._emitAction({ -- type: ActionType.SessionToolCallComplete, +- type: ActionType.ChatToolCallComplete, - turnId: this._turnId, - toolCallId: e.data.toolCallId, - result: { @@ -4884,14 +6214,15 @@ index 770817327b0..00000000000 - content: content.length > 0 ? content : undefined, - error: e.data.error, - }, +- _meta: completeMeta ? toToolCallMeta(completeMeta) : undefined, - }, parentToolCallId); - })); - -- this._register(wrapper.onIdle(() => { +- this._register(wrapper.onIdle(e => { - this._logService.info(`[Copilot:${sessionId}] Session idle`); - // Clear any in-progress activity description set during the - // turn (e.g. via the `report_intent` tool) — the agent is no -- // longer doing anything once the turn completes. +- // longer doing anything once the loop settles. - if (this._hasReportedActivity) { - this._hasReportedActivity = false; - this._emitAction({ @@ -4899,6 +6230,36 @@ index 770817327b0..00000000000 - activity: undefined, - }); - } +- const turn = this._currentTurn; +- if (!turn) { +- return; +- } +- // An abort drives the loop to idle. That terminal idle must never +- // complete a turn: +- // - if `turn` is the aborted (running) turn, the client-dispatched +- // `ChatTurnCancelled` finalizes the protocol turn; drop our handle +- // so a later idle can't complete it. +- // - if `turn` is still `pending`, a queued message started it after +- // the abort and the SDK has not run it yet; completing it would +- // emit an empty `ChatTurnComplete` and orphan its real response. +- // Leave it open for its own (non-abort) idle. +- // The structural `pending` guard below already protects the +- // queued-message case; reading `e.data.aborted` is the authoritative +- // SDK signal that lets us also tear down the aborted running turn. +- if (e.data.aborted) { +- if (turn.isRunning) { +- this._logService.trace(`[Copilot:${sessionId}] Idle from abort; tearing down running turn ${turn.id}`); +- turn.markAborted(); +- this._currentTurn = undefined; +- } else { +- this._logService.trace(`[Copilot:${sessionId}] Idle from abort; leaving ${turn.state} turn ${turn.id} open`); +- } +- return; +- } +- // Only a `running` turn is completed by a normal idle. A `pending` +- // turn here means the SDK went idle before emitting any event for it +- // (a degenerate no-op send); complete it defensively so the session +- // does not hang. - this._completeActiveTurn(); - })); - @@ -4910,21 +6271,21 @@ index 770817327b0..00000000000 - this._logService.info(`[Copilot:${sessionId}] Skill invoked: ${e.data.name} (${e.data.path})`); - const synth = synthesizeSkillToolCall(e.data, e.id); - this._emitAction({ -- type: ActionType.SessionToolCallStart, +- type: ActionType.ChatToolCallStart, - turnId: this._turnId, - toolCallId: synth.toolCallId, - toolName: synth.toolName, - displayName: synth.displayName, - }); - this._emitAction({ -- type: ActionType.SessionToolCallReady, +- type: ActionType.ChatToolCallReady, - turnId: this._turnId, - toolCallId: synth.toolCallId, - invocationMessage: synth.invocationMessage, - confirmed: ToolCallConfirmationReason.NotNeeded, - }); - this._emitAction({ -- type: ActionType.SessionToolCallComplete, +- type: ActionType.ChatToolCallComplete, - turnId: this._turnId, - toolCallId: synth.toolCallId, - result: { @@ -4951,23 +6312,47 @@ index 770817327b0..00000000000 - - this._register(wrapper.onSessionError(e => { - this._logService.error(`[Copilot:${sessionId}] Session error: ${e.data.errorType} - ${e.data.message}`); +- // Prefer the structured SDK fields (the Copilot CLI classifies its own +- // CAPI errors); fall back to decoding a forwarded marker from the message. +- const meta = tryBuildChatErrorMetaFromFields(e.data) ?? tryBuildChatErrorMeta(e.data.message); - this._emitAction({ -- type: ActionType.SessionError, +- type: ActionType.ChatError, - turnId: this._turnId, - error: { - errorType: e.data.errorType, -- message: e.data.message, +- message: stripProxyErrorMarker(e.data.message), - stack: e.data.stack, +- ...(meta ? { _meta: meta } : {}), - }, - }); - })); - - this._register(wrapper.onUsage(e => { -- const metadata: Record = {}; +- const metadata: UsageInfoMeta = {}; - if (typeof e.data.cost === 'number') { - metadata.cost = e.data.cost; - } -- this._logService.trace(`[Copilot:${sessionId}] Usage: model=${e.data.model}, in=${e.data.inputTokens ?? '?'}, out=${e.data.outputTokens ?? '?'}, cacheRead=${e.data.cacheReadTokens ?? '?'}, cost=${e.data.cost ?? '?'}`); +- // TODO: `copilotUsage` is marked `asInternal` in the SDK schema so it is not exposed on the generated +- // `AssistantUsageData` type, but it is present at runtime. Read it dynamically. +- const copilotUsage = (e.data as unknown as Record).copilotUsage as { totalNanoAiu?: number } | undefined; +- const turn = this._currentTurn; +- if (turn && typeof copilotUsage?.totalNanoAiu === 'number') { +- turn.copilotUsageTotalNanoAiu += copilotUsage.totalNanoAiu; +- metadata.copilotUsage = { +- ...copilotUsage, +- totalNanoAiu: turn.copilotUsageTotalNanoAiu, +- }; +- } +- // `quotaSnapshots` is likewise `asInternal` in the SDK schema (not on the generated type) but is +- // present at runtime. Forward the per-category snapshots on `_meta` so the client can keep the +- // account quota UI current. Mirrors the extension-host CLI path, which feeds these into its quota service. +- const quotaSnapshots = normalizeQuotaSnapshots((e.data as unknown as Record).quotaSnapshots); +- if (quotaSnapshots) { +- metadata.quotaSnapshots = quotaSnapshots; +- } +- if (typeof e.data.model === 'string' && e.data.model) { +- this._lastSeenModelId = e.data.model; +- } - const usage: UsageInfo = { - inputTokens: e.data.inputTokens, - outputTokens: e.data.outputTokens, @@ -4976,7 +6361,7 @@ index 770817327b0..00000000000 - ...(Object.keys(metadata).length > 0 ? { _meta: metadata } : {}), - }; - this._emitAction({ -- type: ActionType.SessionUsage, +- type: ActionType.ChatUsage, - turnId: this._turnId, - usage, - }); @@ -4992,10 +6377,8 @@ index 770817327b0..00000000000 - - // Sync the AHP session config when the SDK's `currentMode` changes - // (e.g. after the model approves a plan, or after we set the mode -- // before sending). The SDK has three modes (`interactive` / `plan` / -- // `autopilot`); AHP only models `interactive` / `plan` and treats -- // autopilot as `mode='interactive', autoApprove='autopilot'`, so we -- // translate before writing. +- // before sending). The SDK and AHP share the same three modes +- // (`interactive` / `plan` / `autopilot`), so we map directly. - this._register(wrapper.onSessionModeChanged(e => { - this._logService.info(`[Copilot:${sessionId}] session.mode_changed: ${e.data.previousMode} -> ${e.data.newMode}`); - const newMode = e.data.newMode; @@ -5005,17 +6388,140 @@ index 770817327b0..00000000000 - this._lastAppliedMode = newMode; - this._syncAhpConfigFromSdkMode(newMode); - })); +- +- // Translate SDK-reported MCP server lifecycle into AHP customization +- // actions. The controller decides whether each server is a +- // plugin-derived child (narrow `SessionMcpServerStateChanged`) or a +- // bare top-level entry (`SessionCustomizationUpdated`). +- this._register(wrapper.onMcpServersLoaded(e => { +- this._applyMcpServerList(e.data.servers); +- })); +- this._register(wrapper.onMcpServerStatusChanged(e => { +- const server = this._toSdkMcpServer(e.data.serverName, e.data.status, e.data.error); +- if (!server) { +- this._mcpCustomizations.remove(e.data.serverName); +- return; +- } +- this._mcpCustomizations.applyOne(server); +- })); +- +- this._register(wrapper.onToolsUpdated(() => { +- this._fireMcpToolsListChanged(); +- })); +- +- // Seed the inventory with any servers the SDK has already loaded by +- // the time we attach. The `session.mcp_servers_loaded` event may +- // have fired before our subscription (e.g. for restored sessions or +- // when servers are configured at session-creation time), and there +- // is no replay. Subsequent `applyAll` calls from the event are +- // idempotent, so this safely converges either way. +- this._seedMcpServersFromRpc(); +- } +- +- /** +- * One-shot fetch of `rpc.mcp.list` at subscription time. Best-effort: +- * any failure is logged and the inventory simply stays empty until the +- * next live event arrives. +- */ +- private _seedMcpServersFromRpc(): void { +- const mcpRpc = this._wrapper.session.rpc?.mcp; +- if (!mcpRpc) { +- // Older SDKs (and test mocks) may not expose the MCP RPC surface. +- return; +- } +- mcpRpc.list().then(result => { +- if (this._store.isDisposed) { +- return; +- } +- this._applyMcpServerList(result.servers); +- }, err => { +- this._logService.warn(`[Copilot:${this.sessionId}] Failed to seed MCP server inventory`, err); +- }); +- } +- +- private _applyMcpServerList(servers: readonly { readonly name: string; readonly status: SdkMcpServerStatus; readonly error?: string }[]): void { +- const sdkServers = servers +- .map(s => this._toSdkMcpServer(s.name, s.status, s.error)) +- .filter(isDefined); +- this._mcpCustomizations.applyAll(sdkServers); +- } +- +- /** +- * Broadcasts `notifications/tools/list_changed` for every MCP server +- * currently in the `Ready` state. The SDK's `session.tools_updated` +- * event is a coarse "tools refreshed" hint that doesn't identify +- * which server changed, so we fan out to all ready channels. Clients +- * are expected to refetch `tools/list` on each notification. +- */ +- private _fireMcpToolsListChanged(): void { +- for (const { channel } of this._mcpCustomizations.readyChannels()) { +- this._onMcpNotification.fire({ +- channel, +- method: 'notifications/tools/list_changed', +- }); +- } +- } +- +- /** Snapshot of MCP servers that have no plugin-derived child entry. */ +- topLevelMcpCustomizations() { +- return this._mcpCustomizations.topLevelCustomizations(); +- } +- +- /** +- * Translates the SDK's flat MCP status string into AHP's discriminated +- * {@link McpServerState} union. Returns `undefined` for +- * `not_configured`, which has no AHP equivalent — the server is +- * dropped from the inventory. +- * +- * V1 maps `needs-auth` to {@link McpServerStatus.Starting}: OAuth +- * handling is intentionally out of scope, so authRequired transitions +- * are masked as "still connecting" until the auth pipeline lands. +- */ +- private _toSdkMcpServer(name: string, status: SdkMcpServerStatus, error?: string): ISdkMcpServer | undefined { +- const state = this._translateSdkMcpStatus(status, error); +- if (!state) { +- return undefined; +- } +- return { name, state }; +- } +- +- private _translateSdkMcpStatus(status: SdkMcpServerStatus, error?: string): McpServerState | undefined { +- switch (status) { +- case 'connected': +- return { kind: McpServerStatus.Ready }; +- case 'failed': +- return { +- kind: McpServerStatus.Error, +- error: { +- errorType: 'mcp-server-failed', +- message: error ?? 'MCP server failed to start', +- }, +- }; +- case 'pending': +- case 'needs-auth': +- // TODO: surface `needs-auth` as McpServerStatus.AuthRequired +- // once OAuth wiring is in place. +- return { kind: McpServerStatus.Starting }; +- case 'disabled': +- return { kind: McpServerStatus.Stopped }; +- case 'not_configured': +- return undefined; +- default: +- return undefined; +- } - } - - /** - * Translates the SDK's three-mode space (`interactive` / `plan` / -- * `autopilot`) to AHP's two-axis model: +- * `autopilot`) to AHP's `mode` axis directly: - * - * - SDK `plan` → AHP `mode='plan'`. - * - SDK `interactive` → AHP `mode='interactive'`. -- * - SDK `autopilot` → AHP `mode='interactive', autoApprove='autopilot'`. -- * Autopilot is exposed in AHP as the highest auto-approval level on -- * the orthogonal `autoApprove` axis, not as a mode value. +- * - SDK `autopilot` → AHP `mode='autopilot'`. +- * +- * Autopilot lives on the `mode` axis; the orthogonal `autoApprove` axis +- * (Default / Bypass) is left untouched so the user's chosen +- * approval level is preserved across SDK mode transitions. - * - * Patches that already match the current AHP values are still - * dispatched (the reducer is a no-op in that case) but written values @@ -5029,8 +6535,7 @@ index 770817327b0..00000000000 - patch[SessionConfigKey.Mode] = 'plan'; - break; - case 'autopilot': -- patch[SessionConfigKey.Mode] = 'interactive'; -- patch[SessionConfigKey.AutoApprove] = 'autopilot'; +- patch[SessionConfigKey.Mode] = 'autopilot'; - break; - case 'interactive': - patch[SessionConfigKey.Mode] = 'interactive'; @@ -5041,7 +6546,7 @@ index 770817327b0..00000000000 - - /** - * Handles the CLI's `exitPlanMode.request` RPC by surfacing it as a -- * {@link SessionInputRequest} and awaiting the client's response. The +- * {@link ChatInputRequest} and awaiting the client's response. The - * resolved {@link IExitPlanModeResponse} flows back to the CLI, which - * calls `session.respondToExitPlanMode` internally — that resumes the - * paused `exit_plan_mode` tool call and (on accept) updates the SDK's @@ -5052,16 +6557,6 @@ index 770817327b0..00000000000 - const questionId = generateUuid(); - this._logService.info(`[Copilot:${this.sessionId}] exitPlanMode.request: rpcId=${requestId}, actions=[${data.actions.join(',')}], recommended=${data.recommendedAction}`); - -- // When the session's effective auto-approval level is `autopilot`, -- // approve the plan automatically without surfacing a question to -- // the user. Mirrors the "autopilot fast-path" in the Copilot CLI's -- // own plan-mode handler. -- const autoApprove = this._configurationService.getEffectiveValue(this.sessionUri.toString(), platformSessionSchema, SessionConfigKey.AutoApprove); -- if (autoApprove === 'autopilot') { -- const response = autoApproveExitPlanMode(data); -- this._logService.info(`[Copilot:${this.sessionId}] exitPlanMode.request auto-accepted (autoApprove=autopilot): selectedAction=${response.selectedAction ?? '(none)'}`); -- return response; -- } - - // Resolve the plan file path so we can embed a markdown link. - let planPath: string | null = null; @@ -5091,10 +6586,10 @@ index 770817327b0..00000000000 - }; - }); - -- const inputRequest: SessionInputRequest = { +- const inputRequest: ChatInputRequest = { - id: requestId, - questions: [{ -- kind: SessionInputQuestionKind.SingleSelect, +- kind: ChatInputQuestionKind.SingleSelect, - id: questionId, - title: localize('agentHost.planReview.title', "Review Plan"), - message: localize('agentHost.planReview.questionMessage', "How would you like to proceed?"), @@ -5116,7 +6611,7 @@ index 770817327b0..00000000000 - kind: 'action', - session: this.sessionUri, - action: { -- type: ActionType.SessionInputRequested, +- type: ActionType.ChatInputRequested, - request: inputRequest, - } - }); @@ -5142,7 +6637,21 @@ index 770817327b0..00000000000 - })); - - this._register(wrapper.onSessionInfo(e => { -- this._logService.trace(`[Copilot:${sessionId}] Session info [${e.data.infoType}]: ${e.data.message}`); +- const attributes: Record = { infoType: e.data.infoType }; +- if (e.data.tip) { +- attributes.tip = e.data.tip; +- } +- const message = `[Copilot:${sessionId}] [${e.data.infoType}]: ${e.data.message}`; +- const otelData = new OtelData(attributes); +- if (e.data.infoType === 'mcp') { +- this._logService.info(message, otelData); +- } else { +- this._logService.trace(message, otelData); +- } +- })); +- +- this._register(wrapper.onSessionWarning(e => { +- this._logService.warn(`[Copilot:${sessionId}] ${e.data.message}`, new OtelData({ warningType: e.data.warningType })); - })); - - this._register(wrapper.onSessionModelChange(e => { @@ -5186,6 +6695,7 @@ index 770817327b0..00000000000 - })); - - this._register(wrapper.onTurnStart(e => { +- this._currentTurn?.markRunning(); - this._logService.trace(`[Copilot:${sessionId}] Turn started: ${e.data.turnId}`); - })); - @@ -5341,14 +6851,14 @@ index 770817327b0..00000000000 - - private _cancelPendingUserInputs(): void { - for (const [, pending] of this._pendingUserInputs) { -- pending.deferred.complete({ response: SessionInputResponseKind.Cancel }); +- pending.deferred.complete({ response: ChatInputResponseKind.Cancel }); - } - this._pendingUserInputs.clear(); - } - - private _cancelPendingElicitations(): void { - for (const [, pending] of this._pendingElicitations) { -- pending.deferred.complete({ response: SessionInputResponseKind.Cancel }); +- pending.deferred.complete({ response: ChatInputResponseKind.Cancel }); - } - this._pendingElicitations.clear(); - } @@ -5360,53 +6870,19 @@ index 770817327b0..00000000000 - this._pendingPlanReviews.clear(); - } - -- private _cancelPendingClientToolCalls(): void { -- for (const [, deferred] of this._pendingClientToolCalls) { -- deferred.complete({ textResultForLlm: 'Tool call cancelled: session ended', resultType: 'failure', error: 'Session ended' }); +- private _cancelPendingMcpSamplings(): void { +- const pending = Array.from(this._pendingMcpSamplings); +- this._pendingMcpSamplings.clear(); +- for (const requestId of pending) { +- this._wrapper.session.rpc.mcp.cancelSamplingExecution({ requestId }).catch(() => { +- // Best-effort: SDK may have already torn down. +- }); - } -- this._pendingClientToolCalls.clear(); -- } --} -- --/** -- * Builds the {@link IExitPlanModeResponse} used when the session is in -- * autopilot and we approve the plan without user interaction. -- * -- * Selection priority mirrors the Copilot CLI's own autopilot handler. -- * -- * 1. If the SDK's `recommendedAction` is offered, take it. -- * 2. Otherwise fall back to `autopilot` → `autopilot_fleet` → `interactive` -- * → `exit_only`. -- * 3. As a last resort, approve without picking a `selectedAction` (the SDK -- * keeps `currentMode='interactive'` in that case). -- * -- * `autoApproveEdits: true` is set whenever the chosen action is one of the -- * autopilot variants, mirroring the CLI behavior. -- */ --function autoApproveExitPlanMode(data: ExitPlanModeRequest): IExitPlanModeResponse { -- const choices = data.actions ?? []; -- const isAutopilotAction = (action: string) => action === 'autopilot' || action === 'autopilot_fleet'; -- -- if (data.recommendedAction && choices.includes(data.recommendedAction)) { -- const selectedAction = data.recommendedAction; -- return { -- approved: true, -- selectedAction, -- ...(isAutopilotAction(selectedAction) ? { autoApproveEdits: true } : {}), -- }; - } - -- for (const action of ['autopilot', 'autopilot_fleet', 'interactive', 'exit_only']) { -- if (choices.includes(action)) { -- return { -- approved: true, -- selectedAction: action, -- ...(isAutopilotAction(action) ? { autoApproveEdits: true } : {}), -- }; -- } +- private _cancelPendingClientToolCalls(): void { +- this._pendingClientToolCalls.denyAll({ textResultForLlm: 'Tool call cancelled: session ended', resultType: 'failure', error: 'Session ended' }); - } -- -- return { approved: true, autoApproveEdits: true }; -} - -/** @@ -5431,12 +6907,48 @@ index 770817327b0..00000000000 - } - return { added, removed }; -} +- +-/** +- * Normalizes the SDK's internal `quotaSnapshots` field — present on the `assistant.usage` event at +- * runtime but absent from the generated `AssistantUsageData` type — into the serializable shape +- * carried on {@link UsageInfoMeta.quotaSnapshots}. Returns `undefined` when no usable snapshot is present. +- */ +-function normalizeQuotaSnapshots(raw: unknown): UsageInfoMeta['quotaSnapshots'] | undefined { +- if (!raw || typeof raw !== 'object') { +- return undefined; +- } +- const result: NonNullable = {}; +- let hasAny = false; +- for (const [quotaType, value] of Object.entries(raw as Record)) { +- if (!value || typeof value !== 'object') { +- continue; +- } +- const v = value as Record; +- const resetDateRaw = v.resetDate; +- const resetDate = typeof resetDateRaw === 'string' +- ? resetDateRaw +- : resetDateRaw instanceof Date +- ? resetDateRaw.toISOString() +- : undefined; +- result[quotaType] = { +- isUnlimitedEntitlement: typeof v.isUnlimitedEntitlement === 'boolean' ? v.isUnlimitedEntitlement : undefined, +- entitlementRequests: typeof v.entitlementRequests === 'number' ? v.entitlementRequests : undefined, +- usedRequests: typeof v.usedRequests === 'number' ? v.usedRequests : undefined, +- remainingPercentage: typeof v.remainingPercentage === 'number' ? v.remainingPercentage : undefined, +- overage: typeof v.overage === 'number' ? v.overage : undefined, +- overageAllowedWithExhaustedQuota: typeof v.overageAllowedWithExhaustedQuota === 'boolean' ? v.overageAllowedWithExhaustedQuota : undefined, +- resetDate, +- }; +- hasAny = true; +- } +- return hasAny ? result : undefined; +-} diff --git a/src/vs/platform/agentHost/node/copilot/copilotPluginConverters.ts b/src/vs/platform/agentHost/node/copilot/copilotPluginConverters.ts deleted file mode 100644 -index 6ac611adf64..00000000000 +index 2c055e48..00000000 --- a/src/vs/platform/agentHost/node/copilot/copilotPluginConverters.ts +++ /dev/null -@@ -1,418 +0,0 @@ +@@ -1,502 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. @@ -5444,10 +6956,12 @@ index 6ac611adf64..00000000000 - -import { spawn } from 'child_process'; -import type { CustomAgentConfig, MCPServerConfig, SessionConfig } from '@github/copilot-sdk'; +-import { Schemas } from '../../../../base/common/network.js'; -import { OperatingSystem, OS } from '../../../../base/common/platform.js'; +-import { URI } from '../../../../base/common/uri.js'; -import { parseFrontMatter } from '../../../../base/common/yaml.js'; -import { IFileService } from '../../../files/common/files.js'; --import { McpServerType } from '../../../mcp/common/mcpPlatformTypes.js'; +-import { McpServerType, type IMcpServerConfiguration } from '../../../mcp/common/mcpPlatformTypes.js'; -import type { IMcpServerDefinition, INamedPluginResource, IParsedAgent, IParsedHookCommand, IParsedHookGroup, IParsedPlugin } from '../../../agentPlugins/common/pluginParsers.js'; -import { type AgentCustomization, type ChildCustomization } from '../../common/state/protocol/state.js'; -import { dirname } from '../../../../base/common/path.js'; @@ -5470,32 +6984,73 @@ index 6ac611adf64..00000000000 -export function toSdkMcpServers(defs: readonly IMcpServerDefinition[]): Record { - const result: Record = {}; - for (const def of defs) { -- const config = def.configuration; -- if (config.type === McpServerType.LOCAL) { -- result[def.name] = { -- type: 'local', -- command: config.command, -- args: config.args ? [...config.args] : [], -- tools: ['*'], -- ...(config.env && { env: toStringEnv(config.env) }), -- ...(config.cwd && { cwd: config.cwd }), -- }; -- } else { -- result[def.name] = { -- type: 'http', -- url: config.url, -- tools: ['*'], -- ...(config.headers && { headers: { ...config.headers } }), -- }; -- } +- result[def.name] = toSdkMcpServer(def.name, def.configuration); - } - return result; -} - -/** -- * Ensures all env values are strings (the SDK requires `Record`). +- * Converts root MCP server config maps into the SDK's `mcpServers` config. +- * +- * The map originates from user-controlled root config, where the schema cannot +- * express per-entry validation (no `additionalProperties`). Entries are +- * therefore treated as `unknown` and silently skipped unless they match one of +- * the two supported shapes (`stdio` with a `command`, or `http` with a `url`), +- * so a malformed entry can't surface as `command`/`url: undefined` in the SDK +- * config. - */ --function toStringEnv(env: Record): Record { +-export function toSdkMcpServersFromConfigMap(servers: Record): Record { +- const result: Record = {}; +- for (const [name, config] of Object.entries(servers)) { +- if (isSupportedMcpServerConfiguration(config)) { +- result[name] = toSdkMcpServer(name, config); +- } +- } +- return result; +-} +- +-/** +- * Narrows an untrusted value to a supported {@link IMcpServerConfiguration}: +- * a `stdio` server with a string `command`, or an `http` server with a string +- * `url`. +- */ +-function isSupportedMcpServerConfiguration(value: unknown): value is IMcpServerConfiguration { +- if (!value || typeof value !== 'object') { +- return false; +- } +- const candidate = value as { type?: unknown; command?: unknown; url?: unknown }; +- if (candidate.type === McpServerType.LOCAL) { +- return typeof candidate.command === 'string'; +- } +- if (candidate.type === McpServerType.REMOTE) { +- return typeof candidate.url === 'string'; +- } +- return false; +-} +- +-function toSdkMcpServer(_name: string, config: IMcpServerConfiguration): MCPServerConfig { +- if (config.type === McpServerType.LOCAL) { +- return { +- type: 'local', +- command: config.command, +- args: config.args ? [...config.args] : [], +- tools: ['*'], +- ...(config.env && { env: toStringEnv(config.env) }), +- ...(config.cwd && { cwd: config.cwd }), +- }; +- } +- return { +- type: 'http', +- url: config.url, +- tools: ['*'], +- ...(config.headers && { headers: { ...config.headers } }), +- }; +-} +- +-/** +- * Ensures all env values are strings (the SDK requires `Record`). +- */ +-function toStringEnv(env: Record): Record { - const result: Record = {}; - for (const [key, value] of Object.entries(env)) { - if (value !== null) { @@ -5527,7 +7082,11 @@ index 6ac611adf64..00000000000 - const content = await fileService.readFile(agent.uri); - const raw = content.value.toString(); - const md = parseFrontMatter(raw); -- const name = md?.getStringValue('name') ?? agent.name; +- // Match `parseAgentFile`'s name derivation (trim + falsy fallback) so +- // the SDK config name equals the `resolvedAgentName` resolved from the +- // parsed plugin agent; otherwise a whitespace-padded frontmatter `name` +- // would make the SDK reject the session-start `agent:` as not found. +- const name = md?.getStringValue('name')?.trim() || agent.name; - const description = md?.getStringValue('description'); - const tools = md?.getStringArrayValue('tools'); - const prompt = md?.body ?? raw; @@ -5551,6 +7110,43 @@ index 6ac611adf64..00000000000 - return configs; -} - +-/** A plugin's agents together with its on-disk location (if any). */ +-export interface IPluginAgentsForSdk { +- readonly pluginDir?: URI; +- readonly agents: readonly INamedPluginResource[]; +-} +- +-/** +- * Builds the SDK's `customAgents` config for a session. +- * +- * Agents contributed by plugins materialized into an on-disk (file-scheme) +- * directory are normally left out of `customAgents` and discovered by the SDK +- * through `pluginDirectories` instead, to avoid duplicates. However, the SDK +- * validates the session-start `agent:` option against `customAgents` *by name +- * only* — it does NOT consult `pluginDirectories`. So a selected plugin or +- * extension agent (e.g. one chosen in the agent picker) would otherwise fail +- * with "Custom agent '' not found". This forces the resolved selection +- * into `customAgents` so it can be activated, while every other file-dir agent +- * continues to load via `pluginDirectories`. +- */ +-export async function toSdkSessionCustomAgents( +- plugins: readonly IPluginAgentsForSdk[], +- resolvedAgentName: string | undefined, +- fileService: IFileService, +-): Promise { +- const pluginsWithoutDirs = plugins.filter(p => !p.pluginDir || p.pluginDir.scheme !== Schemas.file); +- const customAgents = await toSdkCustomAgents(pluginsWithoutDirs.flatMap(p => p.agents), fileService); +- if (resolvedAgentName && !customAgents.some(agent => agent.name === resolvedAgentName)) { +- const selectedAgents = plugins.flatMap(p => p.agents).filter(agent => agent.name === resolvedAgentName); +- for (const config of await toSdkCustomAgents(selectedAgents, fileService)) { +- if (!customAgents.some(agent => agent.name === config.name)) { +- customAgents.push(config); +- } +- } +- } +- return customAgents; +-} +- -/** - * Projects parsed plugin agents into their protocol-level - * {@link AgentCustomization} shape. @@ -5857,10 +7453,10 @@ index 6ac611adf64..00000000000 -} diff --git a/src/vs/platform/agentHost/node/copilot/copilotSessionLauncher.ts b/src/vs/platform/agentHost/node/copilot/copilotSessionLauncher.ts deleted file mode 100644 -index 043c025c8f6..00000000000 +index 97470ca6..00000000 --- a/src/vs/platform/agentHost/node/copilot/copilotSessionLauncher.ts +++ /dev/null -@@ -1,276 +0,0 @@ +@@ -1,353 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. @@ -5873,21 +7469,30 @@ index 043c025c8f6..00000000000 -import { IFileService } from '../../../files/common/files.js'; -import { ILogService } from '../../../log/common/log.js'; -import { AgentHostConfigKey, agentHostCustomizationConfigSchema } from '../../common/agentHostCustomizationConfig.js'; --import { AgentHostSessionSyncEnabledConfigKey, platformRootSchema } from '../../common/agentHostSchema.js'; +-import { AgentHostSessionSyncEnabledConfigKey, platformRootSchema, type AgentHostMcpServers } from '../../common/agentHostSchema.js'; +-import { AgentHostSandboxConfigKey, sandboxConfigSchema } from '../../common/sandboxConfigSchema.js'; -import { IAgentConfigurationService } from '../agentConfigurationService.js'; -import { IAgentHostTerminalManager } from '../agentHostTerminalManager.js'; -import type { ModelSelection, ToolDefinition } from '../../common/state/protocol/state.js'; +-import type { ActiveClientState } from '../activeClientState.js'; -import { CopilotSessionWrapper } from './copilotSessionWrapper.js'; -import { ShellManager, createShellTools, type IUnsandboxedCommandConfirmationRequest } from './copilotShellTools.js'; --import { toSdkCustomAgents, toSdkHooks, toSdkInstructionDirectories, toSdkMcpServers, toSdkSkillDirectories } from './copilotPluginConverters.js'; +-import { toSdkHooks, toSdkInstructionDirectories, toSdkMcpServers, toSdkMcpServersFromConfigMap, toSdkSessionCustomAgents, toSdkSkillDirectories } from './copilotPluginConverters.js'; +-import { buildSandboxConfigForSdk, type ISdkSandboxConfig } from './sandboxConfigForSdk.js'; -import type { ITypedPermissionRequest } from './copilotToolDisplay.js'; -import type { ICopilotPluginInfo } from './copilotAgent.js'; +-import { agentHostPromptRegistry, type IAgentHostPromptContext } from './prompts/promptRegistry.js'; +-import './prompts/allPrompts.js'; - -export const ThinkingLevelConfigKey = 'thinkingLevel'; +-export const ContextTierConfigKey = 'contextTier'; - -const ReasoningEfforts = ['low', 'medium', 'high', 'xhigh'] as const; -type ReasoningEffort = NonNullable; - +-const ContextTiers = ['default', 'long_context'] as const; +-type ContextTier = NonNullable; +- -type UserInputHandler = NonNullable; -type UserInputRequest = Parameters[0]; -type UserInputInvocation = Parameters[1]; @@ -5903,24 +7508,19 @@ index 043c025c8f6..00000000000 - readonly remoteSession?: 'export'; -}; - --export const COPILOT_AGENT_HOST_SYSTEM_MESSAGE = { -- mode: 'customize', -- sections: { -- identity: { -- action: 'replace', -- content: 'You are an AI assistant using Copilot CLI runtime in VS Code. You help users with software engineering tasks. When asked about your identity, you must state that you are an AI assistant using Copilot CLI runtime in VS Code.', -- }, -- }, --} satisfies NonNullable; -- -/** -- * Immutable snapshot of the active client's contributions at session creation -- * time. Used to detect when the session needs to be refreshed. +- * Immutable snapshot of the active client's structural contributions at +- * session creation time. Used to detect when the session needs to be +- * refreshed. Root MCP servers participate in restart detection because they +- * are merged into the SDK session config. The owning `clientId` is +- * deliberately NOT part of this snapshot: client identity is tracked live via +- * {@link ActiveClientState} so a window +- * reload (new `clientId`, identical tools/plugins) does not force a restart. - */ -export interface IActiveClientSnapshot { -- readonly clientId: string; - readonly tools: readonly ToolDefinition[]; - readonly plugins: readonly ICopilotPluginInfo[]; +- readonly mcpServers: AgentHostMcpServers; -} - -export interface ICopilotSessionRuntime { @@ -5933,6 +7533,8 @@ index 043c025c8f6..00000000000 - handlePostToolUse(input: PostToolUseHookInput): Promise; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - createClientSdkTools(): Tool[]; +- // eslint-disable-next-line @typescript-eslint/no-explicit-any +- createServerSdkTools(): Tool[]; -} - -export interface ICopilotSessionLauncher { @@ -5951,6 +7553,13 @@ index 043c025c8f6..00000000000 - readonly workingDirectory: URI | undefined; - readonly resolvedAgentName: string | undefined; - readonly snapshot: IActiveClientSnapshot; +- /** +- * Live, long-lived holder of the owning client's identity. Read at +- * tool-call stamp time so a window reload (new `clientId`, identical +- * tools) stamps subsequent client tool calls with the current id +- * rather than the one frozen into {@link snapshot} at creation. +- */ +- readonly activeClientState: ActiveClientState; - readonly shellManager: ShellManager | undefined; - readonly githubToken: string | undefined; -} @@ -5974,6 +7583,10 @@ index 043c025c8f6..00000000000 - return ReasoningEfforts.some(reasoningEffort => reasoningEffort === value); -} - +-function isContextTier(value: string | undefined): value is ContextTier { +- return ContextTiers.some(contextTier => contextTier === value); +-} +- -function getCopilotSdkErrorCode(err: unknown): number | undefined { - if (typeof err !== 'object' || err === null) { - return undefined; @@ -6023,6 +7636,11 @@ index 043c025c8f6..00000000000 - return isReasoningEffort(thinkingLevel) ? thinkingLevel : undefined; -} - +-export function getCopilotContextTier(model: ModelSelection | undefined): SessionConfig['contextTier'] { +- const contextTier = model?.config?.[ContextTierConfigKey]; +- return isContextTier(contextTier) ? contextTier : undefined; +-} +- -export class CopilotSessionLauncher implements ICopilotSessionLauncher { - - constructor( @@ -6034,8 +7652,9 @@ index 043c025c8f6..00000000000 - - async launch(plan: CopilotSessionLaunchPlan, runtime: ICopilotSessionRuntime): Promise { - const config = await this._buildSessionConfig(plan, runtime); +- const sandboxConfig = this._computeSandboxConfig(); - if (plan.kind === 'create') { -- return this._createSession(plan, config); +- return this._createSession(plan, config, sandboxConfig); - } - - try { @@ -6046,6 +7665,7 @@ index 043c025c8f6..00000000000 - ...(plan.resolvedAgentName ? { agent: plan.resolvedAgentName } : {}), - }); - this._logService.info(`[Copilot:${plan.sessionId}] SDK resumeSession succeeded`); +- await this._applySandboxConfig(raw, sandboxConfig, plan.sessionId); - return new CopilotSessionWrapper(raw); - } catch (err) { - const errCode = getCopilotSdkErrorCode(err); @@ -6063,25 +7683,69 @@ index 043c025c8f6..00000000000 - ...plan, - kind: 'create', - model: plan.fallback.model, -- }, config); +- }, config, sandboxConfig); - this._logService.info(`[Copilot:${plan.sessionId}] Fallback createSession succeeded`); - return wrapper; - } - } - -- private async _createSession(plan: ICopilotCreateSessionLaunchPlan, config: CopilotSessionLaunchConfig): Promise { +- private async _createSession(plan: ICopilotCreateSessionLaunchPlan, config: CopilotSessionLaunchConfig, sandboxConfig: ISdkSandboxConfig | undefined): Promise { - const raw = await plan.client.createSession({ - ...config, - sessionId: plan.sessionId, - streaming: true, - model: plan.model?.id, - reasoningEffort: getCopilotReasoningEffort(plan.model), +- contextTier: getCopilotContextTier(plan.model), - ...(plan.resolvedAgentName ? { agent: plan.resolvedAgentName } : {}), - workingDirectory: plan.workingDirectory?.fsPath, - }); +- await this._applySandboxConfig(raw, sandboxConfig, plan.sessionId); - return new CopilotSessionWrapper(raw); - } - +- /** +- * Compute the SDK-shaped sandbox policy to push to the runtime for the +- * SDK's built-in shell tool. +- * +- * Returns `undefined` when {@link AgentHostConfigKey.EnableCustomTerminalTool} +- * is ON — in that case the AgentHost provides its own shell tools, which +- * wrap commands via the host terminal sandbox engine, so no SDK-side +- * sandbox policy is needed. Otherwise the policy is derived from the +- * host's `sandbox` config bag (forwarded from the workbench's +- * `chat.agent.sandbox.*` settings), mirroring what +- * `buildSandboxConfigForCLI` does for the Copilot extension's CLI path. +- */ +- private _computeSandboxConfig(): ISdkSandboxConfig | undefined { +- const enableCustomTerminalTool = this._configurationService.getRootValue(agentHostCustomizationConfigSchema, AgentHostConfigKey.EnableCustomTerminalTool) === true; +- if (enableCustomTerminalTool) { +- return undefined; +- } +- return buildSandboxConfigForSdk(process.platform, this._configurationService.getRootValue(sandboxConfigSchema, AgentHostSandboxConfigKey.Sandbox)); +- } +- +- /** +- * Forward the SDK-shaped sandbox policy to the runtime via +- * `session.options.update`, immediately after the session is created or +- * resumed. `SessionUpdateOptionsParams.sandboxConfig` is now typed by the +- * SDK (as `SandboxConfig`), and our {@link ISdkSandboxConfig} shape is +- * structurally assignable to it, so we forward it directly. +- * +- * No-op when {@link _computeSandboxConfig} returned `undefined` (custom +- * terminal tool enabled, or the host sandbox config evaluates to disabled). +- */ +- private async _applySandboxConfig(session: CopilotSessionWrapper['session'], sandboxConfig: ISdkSandboxConfig | undefined, sessionId: string): Promise { +- if (!sandboxConfig) { +- return; +- } +- try { +- await session.rpc.options.update({ sandboxConfig }); +- this._logService.info(`[Copilot:${sessionId}] Applied SDK sandboxConfig via session.options.update`); +- } catch (err) { +- this._logService.warn(`[Copilot:${sessionId}] Failed to apply SDK sandboxConfig`, err); +- } +- } +- - private async _buildSessionConfig(plan: CopilotSessionLaunchPlan, runtime: ICopilotSessionRuntime): Promise { - const plugins = plan.snapshot.plugins; - const enableCustomTerminalTool = this._configurationService.getRootValue(agentHostCustomizationConfigSchema, AgentHostConfigKey.EnableCustomTerminalTool) === true; @@ -6092,12 +7756,21 @@ index 043c025c8f6..00000000000 - } - shellTools = await createShellTools(plan.shellManager, this._terminalManager, this._logService, request => runtime.requestUnsandboxedCommandConfirmation(request)); - } -- // Rely on SDK to find all agents/skills & the like from the plugins instead of us feeding them. -- // Else we could end up with duplicates or the like. +- // Rely on the SDK to discover most agents/skills/etc. from `pluginDirectories` +- // instead of feeding them explicitly, to avoid duplicates. Custom agents are the +- // exception: the SDK validates the session-start `agent:` against `customAgents` +- // by name, so the selected agent is force-included (see `toSdkSessionCustomAgents`). - const pluginsWithoutDirs = plugins.filter(p => !p.pluginDir || p.pluginDir.scheme !== Schemas.file); -- const customAgents = await toSdkCustomAgents(pluginsWithoutDirs.flatMap(p => p.agents), this._fileService); +- const customAgents = await toSdkSessionCustomAgents(plugins, plan.resolvedAgentName, this._fileService); +- const skillDirectories = toSdkSkillDirectories(pluginsWithoutDirs.flatMap(p => p.skills)); +- const instructionDirectories = toSdkInstructionDirectories(plugins.flatMap(p => p.instructions)); +- const model = plan.kind === 'create' ? plan.model : plan.fallback.model; +- const promptContext: IAgentHostPromptContext = { +- getSetting: key => this._configurationService.getRootValue(agentHostCustomizationConfigSchema, key), +- }; - return { - clientName: 'vscode', +- enableMcpApps: true, - onPermissionRequest: request => runtime.handlePermissionRequest(request), - onUserInputRequest: (request, invocation) => runtime.handleUserInputRequest(request, invocation), - onElicitationRequest: context => runtime.handleElicitationRequest(context), @@ -6105,16 +7778,16 @@ index 043c025c8f6..00000000000 - onPreToolUse: input => runtime.handlePreToolUse(input), - onPostToolUse: input => runtime.handlePostToolUse(input), - }), -- mcpServers: toSdkMcpServers(pluginsWithoutDirs.flatMap(p => p.mcpServers)), +- mcpServers: { ...toSdkMcpServersFromConfigMap(plan.snapshot.mcpServers), ...toSdkMcpServers(pluginsWithoutDirs.flatMap(p => p.mcpServers)) }, - onExitPlanModeRequest: (request, invocation) => runtime.handleExitPlanModeRequest(request, invocation), - workingDirectory: plan.workingDirectory?.fsPath, - customAgents, -- skillDirectories: toSdkSkillDirectories(pluginsWithoutDirs.flatMap(p => p.skills)), -- instructionDirectories: toSdkInstructionDirectories(plugins.flatMap(p => p.instructions)), -- systemMessage: COPILOT_AGENT_HOST_SYSTEM_MESSAGE, +- skillDirectories, +- instructionDirectories, +- systemMessage: agentHostPromptRegistry.resolveSystemMessageConfig(model, promptContext), - pluginDirectories: coalesce(plugins.map(p => p.pluginDir)) - .filter(d => d.scheme === Schemas.file).map(d => d.fsPath), -- tools: [...shellTools, ...runtime.createClientSdkTools()], +- tools: [...shellTools, ...runtime.createClientSdkTools(), ...runtime.createServerSdkTools()], - // Pass the GitHub token at the session level. The SDK's - // client-level `gitHubToken` authenticates the CLI process, - // but each session also needs its own token resolved into a @@ -6139,10 +7812,10 @@ index 043c025c8f6..00000000000 -} diff --git a/src/vs/platform/agentHost/node/copilot/copilotSessionWrapper.ts b/src/vs/platform/agentHost/node/copilot/copilotSessionWrapper.ts deleted file mode 100644 -index 0890c64e4b1..00000000000 +index 04eba456..00000000 --- a/src/vs/platform/agentHost/node/copilot/copilotSessionWrapper.ts +++ /dev/null -@@ -1,227 +0,0 @@ +@@ -1,247 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. @@ -6213,6 +7886,11 @@ index 0890c64e4b1..00000000000 - return this._onSessionInfo ??= this._sdkEvent('session.info'); - } - +- private _onSessionWarning: Event> | undefined; +- get onSessionWarning(): Event> { +- return this._onSessionWarning ??= this._sdkEvent('session.warning'); +- } +- - private _onSessionModelChange: Event> | undefined; - get onSessionModelChange(): Event> { - return this._onSessionModelChange ??= this._sdkEvent('session.model_change'); @@ -6363,6 +8041,21 @@ index 0890c64e4b1..00000000000 - return this._onSessionModeChanged ??= this._sdkEvent('session.mode_changed'); - } - +- private _onMcpServersLoaded: Event> | undefined; +- get onMcpServersLoaded(): Event> { +- return this._onMcpServersLoaded ??= this._sdkEvent('session.mcp_servers_loaded'); +- } +- +- private _onMcpServerStatusChanged: Event> | undefined; +- get onMcpServerStatusChanged(): Event> { +- return this._onMcpServerStatusChanged ??= this._sdkEvent('session.mcp_server_status_changed'); +- } +- +- private _onToolsUpdated: Event> | undefined; +- get onToolsUpdated(): Event> { +- return this._onToolsUpdated ??= this._sdkEvent('session.tools_updated'); +- } +- - private _sdkEvent(eventType: K): Event> { - const emitter = this._register(new Emitter>()); - const unsubscribe = this.session.on(eventType, (data: SessionEventPayload) => emitter.fire(data)); @@ -6372,10 +8065,10 @@ index 0890c64e4b1..00000000000 -} diff --git a/src/vs/platform/agentHost/node/copilot/copilotShellTools.ts b/src/vs/platform/agentHost/node/copilot/copilotShellTools.ts deleted file mode 100644 -index 14004b8afb1..00000000000 +index d0db4dea..00000000 --- a/src/vs/platform/agentHost/node/copilot/copilotShellTools.ts +++ /dev/null -@@ -1,1093 +0,0 @@ +@@ -1,1089 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. @@ -7094,11 +8787,7 @@ index 14004b8afb1..00000000000 - ); - } - -- const autoApproveUnsandboxed = engine.isAutoApproveUnsandboxedCommands(); - const requestUnsandboxedConfirmation = async (blockedDomains?: readonly string[]): Promise => { -- if (autoApproveUnsandboxed) { -- return true; -- } - if (!confirmUnsandboxedExecution) { - const blocked = blockedDomains?.join(', ') ?? '(unknown)'; - return makeFailureResult( @@ -7471,10 +9160,10 @@ index 14004b8afb1..00000000000 -} diff --git a/src/vs/platform/agentHost/node/copilot/copilotSlashCommandCompletionProvider.ts b/src/vs/platform/agentHost/node/copilot/copilotSlashCommandCompletionProvider.ts deleted file mode 100644 -index 4943986a0e1..00000000000 +index 25849598..00000000 --- a/src/vs/platform/agentHost/node/copilot/copilotSlashCommandCompletionProvider.ts +++ /dev/null -@@ -1,132 +0,0 @@ +@@ -1,155 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. @@ -7485,6 +9174,7 @@ index 4943986a0e1..00000000000 -import { AgentSession } from '../../common/agentService.js'; -import { CompletionItem, CompletionItemKind, CompletionsParams } from '../../common/state/protocol/commands.js'; -import { MessageAttachmentKind } from '../../common/state/protocol/state.js'; +-import { toCommandCompletionAttachmentMeta } from '../../common/meta/agentCompletionAttachmentMeta.js'; -import { CompletionTriggerCharacter, IAgentHostCompletionItemProvider } from '../agentHostCompletions.js'; -import { extractLeadingSlashToken } from '../agentHostSlashCompletion.js'; - @@ -7492,17 +9182,34 @@ index 4943986a0e1..00000000000 - * Slash-command name and the token we surface to the user / round-trip on - * the {@link MessageAttachmentKind.Simple} attachment's `_meta`. - */ --export type CopilotSlashCommandName = 'plan' | 'compact' | 'research' | 'rubber-duck'; +-export type CopilotSlashCommandName = 'plan' | 'compact' | 'research' | 'rubber-duck' | 'env' | 'review' | 'security-review'; - --const COMMANDS: readonly CopilotSlashCommandName[] = ['plan', 'compact', 'research', 'rubber-duck']; +-const COMMANDS: readonly CopilotSlashCommandName[] = ['plan', 'compact', 'research', 'rubber-duck', 'env', 'review', 'security-review']; +-const RUNTIME_RPC_INVOKED_COMMANDS = new Set(['env']); +-const PROMPT_INVOKED_COMMANDS = new Set(['research', 'review', 'security-review']); -function getCommandDescription(command: CopilotSlashCommandName): string { - switch (command) { - case 'plan': return localize('copilotSlashCommand.plan.description', "Create an implementation plan before coding"); - case 'compact': return localize('copilotSlashCommand.compact.description', "Free up context by compacting the conversation history"); - case 'research': return localize('copilotSlashCommand.research.description', "Run deep research on a topic using search and web sources"); - case 'rubber-duck': return localize('copilotSlashCommand.rubberDuck.description', "Get an independent critique of the current approach"); +- case 'env': return localize('copilotSlashCommand.env.description', "Show loaded environment details"); +- case 'review': return localize('copilotSlashCommand.review.description', "Run code review agent to analyze changes"); +- case 'security-review': return localize('copilotSlashCommand.securityReview.description', "Analyze staged and unstaged changes for security vulnerabilities"); - } -} +- +-export function isRuntimeCopilotSlashCommand(command: CopilotSlashCommandName): boolean { +- return RUNTIME_RPC_INVOKED_COMMANDS.has(command); +-} +- +-export function isPromptInvokedCopilotSlashCommand(command: CopilotSlashCommandName): boolean { +- return PROMPT_INVOKED_COMMANDS.has(command); +-} +- +-function commandExpectsInput(command: CopilotSlashCommandName): boolean { +- return command !== 'compact' && command !== 'env'; +-} -/** - * Lookup hook used by {@link CopilotSlashCommandCompletionProvider} to - * decide whether history-dependent commands (e.g. `/compact`) make sense @@ -7517,6 +9224,8 @@ index 4943986a0e1..00000000000 - * the agent host config. When absent or `false`, `/rubber-duck` is hidden. - */ - isRubberDuckEnabled?(): boolean; +- /** Whether the runtime reports the given slash command as available. */ +- hasRuntimeSlashCommand?(sessionId: string, command: string): Promise; -} - -/** @@ -7531,13 +9240,13 @@ index 4943986a0e1..00000000000 -/** - * Parses a Copilot CLI slash command at the very start of `prompt`. - * -- * The command must be `/plan`, `/compact`, `/research`, or `/rubber-duck`, +- * The command must be `/plan`, `/compact`, `/research`, `/rubber-duck`, `/env`, `/review`, or `/security-review`, - * followed either by end-of-input or by at least one whitespace character. - * `/compact-hello`, `/plans`, or a leading-space `/compact` all return - * `undefined`. Match is case-sensitive. - */ -export function parseLeadingSlashCommand(prompt: string): IParsedLeadingSlashCommand | undefined { -- const match = /^\/(plan|compact|research|rubber-duck)(?:$|\s+([\s\S]*))/.exec(prompt); +- const match = /^\/(plan|compact|research|rubber-duck|env|review|security-review)(?:$|\s+([\s\S]*))/.exec(prompt); - if (!match) { - return undefined; - } @@ -7593,14 +9302,17 @@ index 4943986a0e1..00000000000 - if (command === 'rubber-duck' && !rubberDuckEnabled) { - continue; - } +- if (isRuntimeCopilotSlashCommand(command) && !(await this._sessionInfo?.hasRuntimeSlashCommand?.(sessionId, command))) { +- continue; +- } - items.push({ -- insertText: command === 'compact' ? '/' + command : '/' + command + ' ', +- insertText: commandExpectsInput(command) ? '/' + command + ' ' : '/' + command, - rangeStart: 0, - rangeEnd: leading.rangeEnd, - attachment: { - type: MessageAttachmentKind.Simple, - label: '/' + command, -- _meta: { command, description: getCommandDescription(command) }, +- _meta: toCommandCompletionAttachmentMeta({ command, description: getCommandDescription(command) }), - }, - }); - } @@ -7609,7 +9321,7 @@ index 4943986a0e1..00000000000 -} diff --git a/src/vs/platform/agentHost/node/copilot/copilotSystemNotification.ts b/src/vs/platform/agentHost/node/copilot/copilotSystemNotification.ts deleted file mode 100644 -index a5d5c7836ef..00000000000 +index a5d5c783..00000000 --- a/src/vs/platform/agentHost/node/copilot/copilotSystemNotification.ts +++ /dev/null @@ -1,52 +0,0 @@ @@ -7665,323 +9377,817 @@ index a5d5c7836ef..00000000000 - const match = /^\s*([\s\S]*?)\s*<\/system_notification>$/.exec(trimmed); - return (match?.[1] ?? trimmed).trim(); -} +diff --git a/src/vs/platform/agentHost/node/copilot/copilotTokenFields.ts b/src/vs/platform/agentHost/node/copilot/copilotTokenFields.ts +deleted file mode 100644 +index 998236f0..00000000 +--- a/src/vs/platform/agentHost/node/copilot/copilotTokenFields.ts ++++ /dev/null +@@ -1,26 +0,0 @@ +-/*--------------------------------------------------------------------------------------------- +- * Copyright (c) Microsoft Corporation. All rights reserved. +- * Licensed under the MIT License. See License.txt in the project root for license information. +- *--------------------------------------------------------------------------------------------*/ +- +-/** Parses the `key=value;...` field map from the leading colon-delimited segment of a Copilot token (e.g. `tid=abc;exp=123;rt=1:HMAC...`). */ +-export function parseCopilotTokenFields(token: string | undefined): ReadonlyMap { +- const result = new Map(); +- if (!token) { +- return result; +- } +- const colonIdx = token.indexOf(':'); +- const header = colonIdx === -1 ? token : token.substring(0, colonIdx); +- for (const field of header.split(';')) { +- const eqIdx = field.indexOf('='); +- if (eqIdx <= 0) { +- continue; +- } +- result.set(field.substring(0, eqIdx), field.substring(eqIdx + 1)); +- } +- return result; +-} +- +-export function isRestrictedTelemetryEnabled(token: string | undefined): boolean { +- return parseCopilotTokenFields(token).get('rt') === '1'; +-} diff --git a/src/vs/platform/agentHost/node/copilot/copilotToolDisplay.ts b/src/vs/platform/agentHost/node/copilot/copilotToolDisplay.ts -index bf2d438e32b..967739e3723 100644 +index 3a4c2d71..f596430b 100644 --- a/src/vs/platform/agentHost/node/copilot/copilotToolDisplay.ts +++ b/src/vs/platform/agentHost/node/copilot/copilotToolDisplay.ts -@@ -3,14 +3,11 @@ +@@ -3,7 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { PermissionRequest } from '@github/copilot-sdk'; ++// `PermissionRequest` originally came from '@github/copilot-sdk' (removed in ++// !!APP_NAME!!). Only its `kind` discriminator is referenced here, so model it ++// as a minimal local shape. ++type PermissionRequest = { kind: string }; import { hasKey } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { appendEscapedMarkdownInlineCode, escapeMarkdownLinkLabel, MarkdownString } from '../../../../base/common/htmlContent.js'; - import { hash } from '../../../../base/common/hash.js'; - import { localize } from '../../../../nls.js'; --import type { IAgentToolPendingConfirmationSignal } from '../../common/agentService.js'; --import { stripRedundantCdPrefix } from '../../common/commandLineHelpers.js'; - import { StringOrMarkdown } from '../../common/state/protocol/state.js'; - import { basename } from '../../../../base/common/resources.js'; - -@@ -895,152 +892,3 @@ export function tryStringify(value: unknown): string | undefined { - } - } - --/** -- * Loose, optional-field projection of the SDK's {@link PermissionRequest} -- * discriminated union. Lets the rest of the agent host read the well-known -- * fields without `switch (request.kind)` narrowing at every access site. -- * -- * The SDK's `PermissionRequest` (a union with required per-variant fields) is -- * structurally assignable to this interface — every variant carries `kind` -- * and `toolCallId?`, and the variant-specific fields are listed here as -- * optional. Use this type at the agent-host boundary so call sites and tests -- * can rely on a single shape. -- */ --export interface ITypedPermissionRequest { -- /** Permission kind discriminator from the SDK. */ -- kind: PermissionRequest['kind']; -- /** Tool call ID that triggered this permission request, when available. */ -- toolCallId?: string; -- /** File path — set for `read` permission requests. */ -- path?: string; -- /** File path — set for `write` permission requests. */ -- fileName?: string; -- /** Full shell command text — set for `shell` permission requests. */ -- fullCommandText?: string; -- /** Human-readable intention describing the operation. */ -- intention?: string; -- /** MCP server name — set for `mcp` permission requests. */ -- serverName?: string; -- /** Tool name — set for `mcp` and `custom-tool` permission requests. */ -- toolName?: string; -- /** Tool arguments — set for `custom-tool` permission requests. */ -- args?: Record; -- /** URL — set for `url` permission requests. */ -- url?: string; -- /** Unified diff of the proposed change — set for `write` permission requests. */ -- diff?: string; -- /** New file contents that will be written — set for `write` permission requests. */ -- newFileContents?: string; --} -- --/** Safely extract a string value from an SDK field that may be `unknown` at runtime. */ --function str(value: unknown): string | undefined { -- return typeof value === 'string' ? value : undefined; --} -- --/** -- * Derives display fields from a permission request for the tool confirmation UI. -- */ --export function getPermissionDisplay(request: ITypedPermissionRequest, workingDirectory?: URI): { -- confirmationTitle: string; -- invocationMessage: StringOrMarkdown; -- toolInput?: string; -- /** Normalized permission kind for auto-approval routing. */ -- permissionKind: IAgentToolPendingConfirmationSignal['permissionKind']; -- /** File path extracted from the request. */ -- permissionPath?: string; --} { -- const path = str(request.path) ?? str(request.fileName); -- const fullCommandText = str(request.fullCommandText); -- const intention = str(request.intention); -- const serverName = str(request.serverName); -- const toolName = str(request.toolName); -- -- switch (request.kind) { -- case 'shell': { -- // Strip a redundant `cd && …` prefix so the -- // confirmation dialog shows the simplified command. -- const shellParams: Record | undefined = fullCommandText ? { command: fullCommandText } : undefined; -- stripRedundantCdPrefix(CopilotToolName.Bash, shellParams, workingDirectory); -- const cleanedCommand = typeof shellParams?.command === 'string' ? shellParams.command : fullCommandText; -- return { -- confirmationTitle: localize('copilot.permission.shell.title', "Run in terminal?"), -- invocationMessage: intention ?? getInvocationMessage(CopilotToolName.Bash, getToolDisplayName(CopilotToolName.Bash), cleanedCommand ? { command: cleanedCommand } : undefined), -- toolInput: cleanedCommand, -- permissionKind: 'shell', -- permissionPath: path, -- }; -- } -- case 'custom-tool': { -- // Custom tool overrides (e.g. our shell tool). Extract the actual -- // tool args from the SDK's wrapper envelope. -- const args = typeof request.args === 'object' && request.args !== null ? request.args as Record : undefined; -- const sdkToolName = str(request.toolName); -- if (args && sdkToolName && isShellTool(sdkToolName) && typeof args.command === 'string') { -- stripRedundantCdPrefix(sdkToolName, args, workingDirectory); -- const command = args.command as string; -- return { -- confirmationTitle: localize('copilot.permission.shell.title', "Run in terminal?"), -- invocationMessage: getInvocationMessage(sdkToolName, getToolDisplayName(sdkToolName), { command }), -- toolInput: command, -- permissionKind: 'shell', -- permissionPath: path, -- }; -- } -- return { -- confirmationTitle: localize('copilot.permission.default.title', "Allow tool call?"), -- invocationMessage: md(localize('copilot.permission.default.message', "Allow the model to call {0}?", appendEscapedMarkdownInlineCode(toolName ?? request.kind))), -- toolInput: args ? tryStringify(args) : tryStringify(request), -- permissionKind: request.kind, -- permissionPath: path, -- }; -- } -- case 'write': -- return { -- confirmationTitle: localize('copilot.permission.write.title', "Write file?"), -- invocationMessage: getInvocationMessage(CopilotToolName.Edit, getToolDisplayName(CopilotToolName.Edit), path ? { path } : undefined), -- toolInput: tryStringify(path ? { path } : request) ?? undefined, -- permissionKind: 'write', -- permissionPath: path, -- }; -- case 'mcp': { -- const title = toolName ?? localize('copilot.permission.mcp.defaultTool', "MCP Tool"); -- return { -- confirmationTitle: serverName -- ? localize('copilot.permission.mcp.title', "Allow tool from {0}?", serverName) -- : localize('copilot.permission.default.title', "Allow tool call?"), -- invocationMessage: serverName ? `${serverName}: ${title}` : title, -- toolInput: tryStringify({ serverName, toolName }) ?? undefined, -- permissionKind: 'mcp', -- permissionPath: path, -- }; -- } -- case 'read': -- return { -- confirmationTitle: localize('copilot.permission.read.title', "Read file?"), -- invocationMessage: intention ?? getInvocationMessage(CopilotToolName.View, getToolDisplayName(CopilotToolName.View), path ? { path } : undefined), -- toolInput: tryStringify(path ? { path, intention } : request) ?? undefined, -- permissionKind: 'read', -- permissionPath: path, -- }; -- case 'url': { -- const url = str(request.url); -- // Parse through URL for punycode escaping, but preserve the raw value if parsing fails. -- const normalizedUrl = url ? (URL.canParse(url) ? new URL(url).href : url) : undefined; -- return { -- confirmationTitle: localize('copilot.permission.url.title', "Fetch URL?"), -- invocationMessage: md(localize('copilot.permission.url.message', "Allow fetching web content?")), -- toolInput: normalizedUrl ? JSON.stringify({ url: normalizedUrl }) : undefined, -- permissionKind: 'url', -- }; -- } -- default: -- return { -- confirmationTitle: localize('copilot.permission.default.title', "Allow tool call?"), -- invocationMessage: md(localize('copilot.permission.default.message', "Allow the model to call {0}?", appendEscapedMarkdownInlineCode(toolName ?? request.kind))), -- toolInput: tryStringify(request) ?? undefined, -- permissionKind: request.kind, -- permissionPath: path, -- }; -- } --} diff --git a/src/vs/platform/agentHost/node/copilot/mapSessionEvents.ts b/src/vs/platform/agentHost/node/copilot/mapSessionEvents.ts -index 632c2994430..e25e78b5ca2 100644 +index 93eccbf8..fd4755bc 100644 --- a/src/vs/platform/agentHost/node/copilot/mapSessionEvents.ts +++ b/src/vs/platform/agentHost/node/copilot/mapSessionEvents.ts -@@ -3,7 +3,21 @@ +@@ -3,7 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ --import { MessageOptions } from '@github/copilot-sdk'; -+// `MessageOptions` was originally imported from '@github/copilot-sdk'; the SDK -+// is removed in BradfordCode but `mapSessionEvents` is still used by test -+// fixtures + the agent service tests, so define the attachment shape locally -+// from the SDK's public union. Use `any` for the runtime-shaped fields -+// (TextRange, blob payload) so we don't have to re-derive their types. -+type MessageOptions = { -+ attachments?: ReadonlyArray< -+ | { type: 'file'; path: string; displayName?: string } -+ | { type: 'directory'; path: string; displayName?: string } -+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -+ | { type: 'selection'; filePath: string; displayName: string; selection?: any } -+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -+ | { type: 'blob'; mimeType: string; data: any; displayName?: string } -+ >; -+}; +-import type { AssistantMessageToolRequest, Attachment, SessionEvent, ToolExecutionCompleteData } from '@github/copilot-sdk'; ++// These four types originally came from '@github/copilot-sdk' (removed in ++// !!APP_NAME!! — the proprietary Copilot binaries can't be redistributed). They ++// are deep SDK discriminated unions consumed only by the now test-only ++// event-to-Turn mapping below; alias them to `any` so the mapping keeps ++// compiling without re-deriving the SDK's full shape. ++/* eslint-disable @typescript-eslint/no-explicit-any */ ++type AssistantMessageToolRequest = any; ++type Attachment = any; ++type SessionEvent = any; ++type ToolExecutionCompleteData = any; ++/* eslint-enable @typescript-eslint/no-explicit-any */ import { decodeBase64 } from '../../../../base/common/buffer.js'; import { basename } from '../../../../base/common/path.js'; import { isString } from '../../../../base/common/types.js'; -diff --git a/src/vs/platform/agentHost/node/otel/agentHostOTelService.ts b/src/vs/platform/agentHost/node/otel/agentHostOTelService.ts -index 61cf2cf76c4..e7b0e069468 100644 ---- a/src/vs/platform/agentHost/node/otel/agentHostOTelService.ts -+++ b/src/vs/platform/agentHost/node/otel/agentHostOTelService.ts -@@ -5,7 +5,12 @@ - - import { mkdir } from 'fs/promises'; - import { dirname, join } from '../../../../base/common/path.js'; --import type { TelemetryConfig } from '@github/copilot-sdk'; -+// `TelemetryConfig` was originally imported from '@github/copilot-sdk'; the SDK -+// is removed in BradfordCode, but agentHostOTelService still exposes a config -+// shape that mirrors it. The common/ side already maintains a structural -+// mirror; this file just needs the type symbol available at compile time. -+// eslint-disable-next-line @typescript-eslint/no-explicit-any -+type TelemetryConfig = any; - import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js'; - import { URI } from '../../../../base/common/uri.js'; - import { INativeEnvironmentService } from '../../../environment/common/environment.js'; -diff --git a/src/vs/platform/agentHost/test/node/copilotAgent.test.ts b/src/vs/platform/agentHost/test/node/copilotAgent.test.ts +diff --git a/src/vs/platform/agentHost/node/copilot/prompts/allPrompts.ts b/src/vs/platform/agentHost/node/copilot/prompts/allPrompts.ts deleted file mode 100644 -index 6bb478dbb36..00000000000 ---- a/src/vs/platform/agentHost/test/node/copilotAgent.test.ts +index 6c69ad78..00000000 +--- a/src/vs/platform/agentHost/node/copilot/prompts/allPrompts.ts +++ /dev/null -@@ -1,1746 +0,0 @@ +@@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - --import type { CopilotClient, CopilotSession, ModelInfo, SessionEventPayload, SessionEventType, TypedSessionEventHandler } from '@github/copilot-sdk'; --import assert from 'assert'; --import * as fs from 'fs/promises'; --import * as os from 'os'; --import { VSBuffer } from '../../../../base/common/buffer.js'; --import { DeferredPromise } from '../../../../base/common/async.js'; --import { isCancellationError } from '../../../../base/common/errors.js'; --import { Disposable, type DisposableStore, type IDisposable, type IReference } from '../../../../base/common/lifecycle.js'; --import { Event } from '../../../../base/common/event.js'; --import { Schemas } from '../../../../base/common/network.js'; --import { waitForState } from '../../../../base/common/observable.js'; --import { URI } from '../../../../base/common/uri.js'; --import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; --import { INativeEnvironmentService } from '../../../environment/common/environment.js'; --import { FileService } from '../../../files/common/fileService.js'; --import { IFileService } from '../../../files/common/files.js'; --import { InMemoryFileSystemProvider } from '../../../files/common/inMemoryFilesystemProvider.js'; --import { IInstantiationService } from '../../../instantiation/common/instantiation.js'; --import { InstantiationService } from '../../../instantiation/common/instantiationService.js'; --import { ServiceCollection } from '../../../instantiation/common/serviceCollection.js'; --import { ILogService, NullLogService } from '../../../log/common/log.js'; --import { ITelemetryService } from '../../../telemetry/common/telemetry.js'; --import { NullTelemetryService } from '../../../telemetry/common/telemetryUtils.js'; --import { AgentHostConfigKey } from '../../common/agentHostCustomizationConfig.js'; --import { IAgentPluginManager, ISyncedCustomization } from '../../common/agentPluginManager.js'; --import { AgentSession, GITHUB_COPILOT_PROTECTED_RESOURCE, type AgentSignal, type IAgentActionSignal, type IAgentSessionMetadata } from '../../common/agentService.js'; --import { ISessionDataService } from '../../common/sessionDataService.js'; --import { AHP_AUTH_REQUIRED, ProtocolError } from '../../common/state/sessionProtocol.js'; --import { buildSubagentSessionUri, CustomizationLoadStatus, MessageKind, ResponsePartKind, ToolCallConfirmationReason, ToolCallStatus, TurnState, customizationId, type ClientPluginCustomization, type MarkdownResponsePart, type PluginCustomization, type ToolCallResult, type Turn, RuleCustomization } from '../../common/state/sessionState.js'; --import { CustomizationType } from '../../common/state/protocol/state.js'; --import { ActionType, type IDeltaAction, type SessionAction } from '../../common/state/sessionActions.js'; -- --import { AgentConfigurationService, IAgentConfigurationService } from '../../node/agentConfigurationService.js'; --import { AgentHostStateManager } from '../../node/agentHostStateManager.js'; --import { IAgentHostGitService } from '../../node/agentHostGitService.js'; --import { IAgentHostTerminalManager } from '../../node/agentHostTerminalManager.js'; --import { IAgentHostOTelService } from '../../common/otel/agentHostOTelService.js'; --import { AgentHostCompletions, IAgentHostCompletions } from '../../node/agentHostCompletions.js'; --import { COPILOT_AGENT_HOST_SYSTEM_MESSAGE, CopilotAgent, getCopilotBranchNameHintFromMessage, getCopilotWorktreeBranchName, getCopilotWorktreeName, getCopilotWorktreesRoot } from '../../node/copilot/copilotAgent.js'; --import { NULL_CHECKPOINT_SERVICE } from '../../common/agentHostCheckpointService.js'; --import { CopilotAgentSession } from '../../node/copilot/copilotAgentSession.js'; --import type { CopilotSessionLaunchPlan } from '../../node/copilot/copilotSessionLauncher.js'; --import { ShellManager } from '../../node/copilot/copilotShellTools.js'; --import { SessionDatabase } from '../../node/sessionDatabase.js'; --import { createNullSessionDataService } from '../common/sessionTestHelpers.js'; +-// Side-effect import hub for per-model Copilot CLI agent-host prompt +-// contributors. Importing this module registers every contributor into the +-// shared `agentHostPromptRegistry`. Mirrors the Copilot extension's +-// `allAgentPrompts.ts`. +-// +-// Add per-model modules here as they are ported over, e.g.: +-// +-// import './geminiPrompt.js'; +-// import './openaiPrompt.js'; - --class TestAgentPluginManager implements IAgentPluginManager { -- declare readonly _serviceBrand: undefined; +-import './anthropicPrompt.js'; +diff --git a/src/vs/platform/agentHost/node/copilot/prompts/anthropicPrompt.ts b/src/vs/platform/agentHost/node/copilot/prompts/anthropicPrompt.ts +deleted file mode 100644 +index befa9348..00000000 +--- a/src/vs/platform/agentHost/node/copilot/prompts/anthropicPrompt.ts ++++ /dev/null +@@ -1,80 +0,0 @@ +-/*--------------------------------------------------------------------------------------------- +- * Copyright (c) Microsoft Corporation. All rights reserved. +- * Licensed under the MIT License. See License.txt in the project root for license information. +- *--------------------------------------------------------------------------------------------*/ - -- readonly basePath = URI.from({ scheme: 'inmemory', path: '/agentPlugins' }); +-import type { SectionOverride, SystemMessageSection } from '@github/copilot-sdk'; +-import { AgentHostConfigKey } from '../../../common/agentHostCustomizationConfig.js'; +-import type { ModelSelection } from '../../../common/state/protocol/state.js'; +-import { agentHostPromptRegistry, type IAgentHostPrompt, type IAgentHostPromptContext } from './promptRegistry.js'; +-import { COPILOT_AGENT_HOST_IDENTITY } from './systemMessage.js'; - -- async syncCustomizations(_clientId: string, _customizations: ClientPluginCustomization[], _progress?: (status: PluginCustomization) => void): Promise { -- return []; -- } +-/** +- * `customize`-mode section overrides for Claude Opus 4.8, tuned per Anthropic's +- * "Prompting Claude Opus 4.8" guide: +- * https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/prompting-claude-opus-4-8 +- * +- * Opus 4.8 performs well out of the box, so this stays intentionally minimal: +- * it keeps the SDK foundation prompt (and its tool/safety sections) intact and +- * only nudges the two behaviors the guide calls out for tuning — +- * - verbosity/tone: the model calibrates length to task complexity, so steer +- * it toward concision when a consistent style is wanted; and +- * - subagents: the model spawns fewer by default, so give explicit fan-out +- * guidance. +- * The guide warns against forcing interim-progress scaffolding ("summarize +- * after every N tool calls"), so none is added here. The identity is re-stated +- * to keep the agent-host self-description that the default message applies. +- */ +-function opus48SectionOverrides(): Partial> { +- return { +- identity: { +- action: 'replace', +- content: COPILOT_AGENT_HOST_IDENTITY, +- }, +- tone: { +- action: 'append', +- // Leading newline so the appended text starts on its own line rather +- // than running on from the SDK foundation tone section's last sentence. +- content: '\nProvide concise, focused responses. Skip non-essential context, and keep examples minimal. Use a direct style and use emojis sparingly.', +- }, +- guidelines: { +- action: 'append', +- content: [ +- 'Do not spawn a subagent for work you can complete directly in a single response (e.g. refactoring a function you can already see).', +- 'Spawn multiple subagents in the same turn when fanning out across items or reading multiple files.', +- ].join('\n'), +- }, +- }; -} - --class TestAgentHostGitService implements IAgentHostGitService { -- declare readonly _serviceBrand: undefined; +-/** +- * Returns whether `model` is a Claude Opus 4.8 model. Mirrors the Copilot +- * extension's version-specific `isOpus47`, matching both the SDK dashed id +- * (`claude-opus-4-8`) and the CAPI dotted id (`claude-opus-4.8`). +- */ +-function isOpus48(model: ModelSelection): boolean { +- return model.id.startsWith('claude-opus-4-8') || model.id.startsWith('claude-opus-4.8'); +-} - -- repositoryRoot: URI | undefined = undefined; -- addedWorktrees: { repositoryRoot: URI; worktree: URI; branchName: string; startPoint: string }[] = []; -- addedExistingWorktrees: { repositoryRoot: URI; worktree: URI; branchName: string }[] = []; -- removedWorktrees: { repositoryRoot: URI; worktree: URI }[] = []; -- existingBranches = new Set(); -- dirtyWorkingDirectories = new Set(); +-/** +- * Resolves the Opus 4.8 agent prompt for Claude Opus 4.8 Copilot SDK sessions. +- * +- * Mirrors the Copilot extension's version-specific opus resolver +- * (`Claude47OpusPrompt`, gated by `isOpus47` + a setting): it matches only Opus +- * 4.8 via {@link isOpus48}, and is opt-in via +- * {@link AgentHostConfigKey.Opus48Prompt}. When the setting is off it returns +- * `undefined` and the registry falls back to the default system message. +- */ +-class Claude48OpusPromptResolver implements IAgentHostPrompt { +- static readonly familyPrefixes: readonly string[] = []; - -- async isInsideWorkTree(): Promise { return false; } -- async getCurrentBranch(): Promise { return undefined; } -- async getDefaultBranch(): Promise { return undefined; } -- async getBranches(): Promise { return []; } -- async getRepositoryRoot(): Promise { return this.repositoryRoot; } -- async getWorktreeRoots(): Promise { return []; } -- async addWorktree(repositoryRoot: URI, worktree: URI, branchName: string, startPoint: string): Promise { -- this.addedWorktrees.push({ repositoryRoot, worktree, branchName, startPoint }); -- this.existingBranches.add(branchName); -- } -- async addExistingWorktree(repositoryRoot: URI, worktree: URI, branchName: string): Promise { -- this.addedExistingWorktrees.push({ repositoryRoot, worktree, branchName }); -- } -- async removeWorktree(repositoryRoot: URI, worktree: URI): Promise { -- this.removedWorktrees.push({ repositoryRoot, worktree }); -- } -- async branchExists(_repositoryRoot: URI, branchName: string): Promise { -- return this.existingBranches.has(branchName); +- static matchesModel(model: ModelSelection): boolean { +- return isOpus48(model); - } -- async hasUncommittedChanges(workingDirectory: URI): Promise { -- return this.dirtyWorkingDirectories.has(workingDirectory.fsPath); +- +- resolveSectionOverrides(_model: ModelSelection, context: IAgentHostPromptContext): Partial> | undefined { +- return context.getSetting(AgentHostConfigKey.Opus48Prompt) === true ? opus48SectionOverrides() : undefined; - } -- async commitAll(): Promise { } +-} +- +-agentHostPromptRegistry.registerPrompt(Claude48OpusPromptResolver); +diff --git a/src/vs/platform/agentHost/node/copilot/prompts/promptRegistry.ts b/src/vs/platform/agentHost/node/copilot/prompts/promptRegistry.ts +deleted file mode 100644 +index 793cf5e1..00000000 +--- a/src/vs/platform/agentHost/node/copilot/prompts/promptRegistry.ts ++++ /dev/null +@@ -1,148 +0,0 @@ +-/*--------------------------------------------------------------------------------------------- +- * Copyright (c) Microsoft Corporation. All rights reserved. +- * Licensed under the MIT License. See License.txt in the project root for license information. +- *--------------------------------------------------------------------------------------------*/ +- +-import type { SectionOverride, SystemMessageConfig, SystemMessageSection } from '@github/copilot-sdk'; +-import { agentHostCustomizationConfigSchema } from '../../../common/agentHostCustomizationConfig.js'; +-import type { SchemaValue } from '../../../common/agentHostSchema.js'; +-import type { ModelSelection } from '../../../common/state/protocol/state.js'; +-import { COPILOT_AGENT_HOST_SYSTEM_MESSAGE, fullSystemPrompt, sectionOverrides } from './systemMessage.js'; +- +-type CustomizationConfigDefinition = typeof agentHostCustomizationConfigSchema.definition; +- +-/** +- * Read-time context handed to prompt contributors so they can gate behavior on +- * host configuration — the agent-host equivalent of the Copilot extension +- * injecting `IConfigurationService` into a resolver. +- * +- * Scoped to the host customization schema so contributors (and tests) read +- * settings in a fully-typed way without depending on the whole configuration +- * service. +- */ +-export interface IAgentHostPromptContext { +- /** +- * Returns the host-level value for a customization setting, or `undefined` +- * when unset. Mirrors `IAgentConfigurationService.getRootValue` bound to +- * {@link agentHostCustomizationConfigSchema}. +- */ +- getSetting(key: K): SchemaValue | undefined; +-} +- +-/** +- * Per-model system-prompt contributor for Copilot CLI agent-host sessions. +- * +- * Mirrors the Copilot extension's `IAgentPrompt`, but — because the agent host +- * runs in its own process and cannot use prompt-tsx — a contributor returns +- * plain data the SDK accepts directly rather than prompt-tsx elements. +- * +- * A contributor may provide EITHER a full system-prompt override OR a set of +- * section overrides. When it provides a full prompt that wins (`replace` mode); +- * otherwise the section overrides are applied (`customize` mode). +- */ +-export interface IAgentHostPrompt { +- /** +- * Full system-prompt override. Resolved into `{ mode: 'replace' }`, which +- * drops the SDK foundation prompt and its guardrails. +- */ +- resolveFullSystemPrompt?(model: ModelSelection, context: IAgentHostPromptContext): string | undefined; +- +- /** +- * Section-level overrides. Resolved into `{ mode: 'customize' }`, keeping the +- * SDK foundation prompt and guardrails intact. +- */ +- resolveSectionOverrides?(model: ModelSelection, context: IAgentHostPromptContext): Partial> | undefined; +-} +- +-/** +- * Constructor/static shape for a registered prompt contributor. Mirrors the +- * Copilot extension's `IAgentPromptCtor`: a contributor matches a model either +- * by a custom {@link matchesModel} predicate or by a model-id family prefix. +- */ +-export interface IAgentHostPromptCtor { +- /** Model-id prefixes this contributor handles (e.g. `'claude'`, `'gpt-5'`). */ +- readonly familyPrefixes: readonly string[]; +- +- /** Optional custom matcher; takes precedence over {@link familyPrefixes}. */ +- matchesModel?(model: ModelSelection): boolean; +- +- new(): IAgentHostPrompt; +-} +- +-type PromptWithMatcher = IAgentHostPromptCtor & { matchesModel: (model: ModelSelection) => boolean }; +- +-/** +- * Registry of per-model system-prompt contributors for Copilot CLI agent-host +- * sessions. Mirrors the Copilot extension's `PromptRegistry`: contributors +- * register a model match (custom predicate or family prefix) and the session +- * launcher calls {@link resolveSystemMessageConfig} when building a session. +- * +- * Exported as a class for isolated unit testing; a shared singleton +- * ({@link agentHostPromptRegistry}) is what contributors register into and the +- * launcher consumes. +- */ +-export class AgentHostPromptRegistry { +- private readonly _promptsWithMatcher: PromptWithMatcher[] = []; +- private readonly _familyPrefixList: { readonly prefix: string; readonly ctor: IAgentHostPromptCtor }[] = []; +- +- registerPrompt(ctor: IAgentHostPromptCtor): void { +- if (ctor.matchesModel) { +- this._promptsWithMatcher.push(ctor as PromptWithMatcher); +- } +- for (const prefix of ctor.familyPrefixes) { +- this._familyPrefixList.push({ prefix, ctor }); +- } +- } +- +- private _getContributor(model: ModelSelection): IAgentHostPromptCtor | undefined { +- for (const ctor of this._promptsWithMatcher) { +- if (ctor.matchesModel(model)) { +- return ctor; +- } +- } +- for (const { prefix, ctor } of this._familyPrefixList) { +- if (model.id.startsWith(prefix)) { +- return ctor; +- } +- } +- return undefined; +- } +- +- /** +- * Resolves the {@link SystemMessageConfig} for a session's model. +- * +- * Falls back to {@link COPILOT_AGENT_HOST_SYSTEM_MESSAGE} when the model is +- * unknown (e.g. server-side "Auto" selection where no model is chosen at +- * create time), when no contributor matches, or when the matching +- * contributor opts out for the current {@link context} (e.g. a setting that +- * gates it is disabled). +- */ +- resolveSystemMessageConfig(model: ModelSelection | undefined, context: IAgentHostPromptContext): SystemMessageConfig { +- if (!model) { +- return COPILOT_AGENT_HOST_SYSTEM_MESSAGE; +- } +- const ctor = this._getContributor(model); +- if (!ctor) { +- return COPILOT_AGENT_HOST_SYSTEM_MESSAGE; +- } +- const contributor = new ctor(); +- const fullPrompt = contributor.resolveFullSystemPrompt?.(model, context); +- if (fullPrompt !== undefined) { +- return fullSystemPrompt(fullPrompt); +- } +- const sections = contributor.resolveSectionOverrides?.(model, context); +- // An empty overrides object is treated as "no override" so we keep the +- // default identity customization rather than emitting a +- // `{ mode: 'customize', sections: {} }` that drops it. +- if (sections && Object.keys(sections).length > 0) { +- return sectionOverrides(sections); +- } +- return COPILOT_AGENT_HOST_SYSTEM_MESSAGE; +- } +-} +- +-/** +- * Shared registry instance. Per-model contributors register here (see +- * `allPrompts.ts`) and the session launcher reads from it. +- */ +-export const agentHostPromptRegistry = new AgentHostPromptRegistry(); +diff --git a/src/vs/platform/agentHost/node/copilot/prompts/systemMessage.ts b/src/vs/platform/agentHost/node/copilot/prompts/systemMessage.ts +deleted file mode 100644 +index 1b52c8cc..00000000 +--- a/src/vs/platform/agentHost/node/copilot/prompts/systemMessage.ts ++++ /dev/null +@@ -1,51 +0,0 @@ +-/*--------------------------------------------------------------------------------------------- +- * Copyright (c) Microsoft Corporation. All rights reserved. +- * Licensed under the MIT License. See License.txt in the project root for license information. +- *--------------------------------------------------------------------------------------------*/ +- +-import type { SectionOverride, SystemMessageConfig, SystemMessageSection } from '@github/copilot-sdk'; +- +-/** +- * Identity section content shared by the default agent-host system message and +- * any per-model override that wants to keep the same self-description. Kept as +- * a single constant so the identity text is defined in exactly one place. +- */ +-export const COPILOT_AGENT_HOST_IDENTITY = 'You are an AI assistant using Copilot CLI runtime in VS Code. You help users with software engineering tasks. When asked about your identity, you must state that you are an AI assistant using Copilot CLI runtime in VS Code.'; +- +-/** +- * Default system-message customization applied to every Copilot CLI agent-host +- * session that has no per-model override registered in the +- * {@link AgentHostPromptRegistry}. +- * +- * Uses `customize` mode so the CLI/SDK foundation prompt (and its built-in +- * guardrails) stay intact — only the `identity` section is replaced. +- */ +-export const COPILOT_AGENT_HOST_SYSTEM_MESSAGE = { +- mode: 'customize', +- sections: { +- identity: { +- action: 'replace', +- content: COPILOT_AGENT_HOST_IDENTITY, +- }, +- }, +-} satisfies SystemMessageConfig; +- +-/** +- * Builds a {@link SystemMessageConfig} that fully replaces the CLI/SDK system +- * prompt with `content`. +- * +- * ⚠️ `replace` mode drops ALL SDK guardrails (including security restrictions); +- * prefer {@link sectionOverrides} unless the caller intends to own the entire +- * prompt. +- */ +-export function fullSystemPrompt(content: string): SystemMessageConfig { +- return { mode: 'replace', content }; +-} +- +-/** +- * Builds a `customize`-mode {@link SystemMessageConfig} that overrides only the +- * given sections, leaving the rest of the CLI/SDK foundation prompt intact. +- */ +-export function sectionOverrides(sections: Partial>): SystemMessageConfig { +- return { mode: 'customize', sections }; +-} +diff --git a/src/vs/platform/agentHost/node/copilot/sandboxConfigForSdk.ts b/src/vs/platform/agentHost/node/copilot/sandboxConfigForSdk.ts +deleted file mode 100644 +index 5d3d6ce3..00000000 +--- a/src/vs/platform/agentHost/node/copilot/sandboxConfigForSdk.ts ++++ /dev/null +@@ -1,133 +0,0 @@ +-/*--------------------------------------------------------------------------------------------- +- * Copyright (c) Microsoft Corporation. All rights reserved. +- * Licensed under the MIT License. See License.txt in the project root for license information. +- *--------------------------------------------------------------------------------------------*/ +- +-import { AgentSandboxEnabledValue } from '../../../sandbox/common/settings.js'; +-import { AgentHostSandboxKey, type ISandboxConfigValue } from '../../common/sandboxConfigSchema.js'; +- +-/** +- * Per-platform filesystem rule bundle accepted under each `fileSystem.` +- * sub-key (`AgentHostSandboxKey.LinuxFileSystem` etc.) in the AgentHost root +- * sandbox config bag. Mirrors the workbench's `chat.agent.sandbox.fileSystem.*` +- * shape so the workbench-side forwarder can copy values verbatim. +- */ +-export interface IAgentSandboxFileSystemSetting { +- allowRead?: string[]; +- allowWrite?: string[]; +- denyRead?: string[]; +- denyWrite?: string[]; +-} +- +-/** +- * SDK-side sandbox configuration produced by {@link buildSandboxConfigForSdk}. +- * +- * Structurally a narrowed form of the SDK's `SandboxConfig` type (from +- * `@github/copilot-sdk`'s `SessionUpdateOptionsParams.sandboxConfig`) — the +- * same shape the Copilot extension produces via its own `buildSandboxConfigForCLI`. +- * Defined locally because `SandboxConfig` is not re-exported from the SDK's +- * public entry point; this shape stays assignable to it. +- */ +-export interface ISdkSandboxConfig { +- enabled: true; +- userPolicy: { +- filesystem: { +- readwritePaths?: string[]; +- readonlyPaths?: string[]; +- deniedPaths?: string[]; +- }; +- network: { +- allowOutbound: boolean; +- allowedHosts?: string[]; +- blockedHosts?: string[]; +- }; +- }; +-} +- +-/** +- * Translate the AgentHost's host-side sandbox configuration into the +- * opaque `sandboxConfig` shape the Copilot SDK forwards to the runtime +- * via `session.options.update`. +- * +- * Used when {@link AgentHostConfigKey.EnableCustomTerminalTool} is OFF — the +- * SDK's built-in shell tool runs the user's commands, so we have to push the +- * sandbox policy down into the SDK itself. When the custom terminal tool is +- * ON, the AgentHost's own {@link TerminalSandboxEngine} wraps commands and +- * this function is not consulted. +- * +- * Mirrors `buildSandboxConfigForCLI` in +- * `extensions/copilot/src/extension/chatSessions/copilotcli/node/copilotcliSessionService.ts` +- * so the two surfaces behave the same: +- * - Path precedence: `denyRead` > `denyWrite` > `allowWrite` > `allowRead`. +- * Each path appears in exactly one of `deniedPaths` / `readonlyPaths` / +- * `readwritePaths`. +- * - Network: `allowNetwork` opens outbound to everything and drops the +- * allow/deny lists. Otherwise the allow/deny lists open outbound when +- * set so they're actually enforced; macOS fails closed because the +- * runtime has no per-host filter (Seatbelt would silently degrade to +- * "allow all outbound"). +- */ +-export function buildSandboxConfigForSdk( +- platform: NodeJS.Platform, +- sandbox: ISandboxConfigValue | undefined, +-): ISdkSandboxConfig | undefined { +- if (!sandbox) { +- return undefined; +- } +- +- const enabledRaw = platform === 'win32' && sandbox[AgentHostSandboxKey.WindowsEnabled] !== undefined +- ? sandbox[AgentHostSandboxKey.WindowsEnabled] +- : sandbox[AgentHostSandboxKey.Enabled]; +- if (enabledRaw !== AgentSandboxEnabledValue.On && enabledRaw !== AgentSandboxEnabledValue.AllowNetwork) { +- return undefined; +- } +- +- const fsRaw = platform === 'win32' +- ? sandbox[AgentHostSandboxKey.WindowsFileSystem] +- : platform === 'darwin' +- ? sandbox[AgentHostSandboxKey.MacFileSystem] +- : sandbox[AgentHostSandboxKey.LinuxFileSystem]; +- const fs = (fsRaw && typeof fsRaw === 'object') ? fsRaw as IAgentSandboxFileSystemSetting : {}; +- +- const denied = new Set(fs.denyRead ?? []); +- const readonly = new Set(); +- const readwrite = new Set(); +- for (const p of fs.denyWrite ?? []) { +- if (!denied.has(p)) { +- readonly.add(p); +- } +- } +- for (const p of fs.allowWrite ?? []) { +- if (!denied.has(p) && !readonly.has(p)) { +- readwrite.add(p); +- } +- } +- for (const p of fs.allowRead ?? []) { +- if (!denied.has(p) && !readonly.has(p) && !readwrite.has(p)) { +- readonly.add(p); +- } +- } +- +- const allowAllNetwork = enabledRaw === AgentSandboxEnabledValue.AllowNetwork; +- const hostListsEnforceable = platform !== 'darwin'; +- const rawAllow = sandbox[AgentHostSandboxKey.AllowedNetworkDomains]; +- const rawBlock = sandbox[AgentHostSandboxKey.DeniedNetworkDomains]; +- const allowedHosts = !allowAllNetwork && hostListsEnforceable && rawAllow?.length ? [...rawAllow] : undefined; +- const blockedHosts = !allowAllNetwork && hostListsEnforceable && rawBlock?.length ? [...rawBlock] : undefined; +- const allowOutbound = allowAllNetwork || !!allowedHosts || !!blockedHosts; +- return { +- enabled: true, +- userPolicy: { +- filesystem: { +- ...(readwrite.size ? { readwritePaths: [...readwrite] } : {}), +- ...(readonly.size ? { readonlyPaths: [...readonly] } : {}), +- ...(denied.size ? { deniedPaths: [...denied] } : {}), +- }, +- network: { +- allowOutbound, +- ...(allowOutbound && allowedHosts ? { allowedHosts } : {}), +- ...(allowOutbound && blockedHosts ? { blockedHosts } : {}), +- }, +- }, +- }; +-} +diff --git a/src/vs/platform/agentHost/node/copilot/sessionCustomizationDiscovery.ts b/src/vs/platform/agentHost/node/copilot/sessionCustomizationDiscovery.ts +index 9331c71b..e48523e3 100644 +--- a/src/vs/platform/agentHost/node/copilot/sessionCustomizationDiscovery.ts ++++ b/src/vs/platform/agentHost/node/copilot/sessionCustomizationDiscovery.ts +@@ -3,7 +3,19 @@ + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +-import type { CopilotClient } from '@github/copilot-sdk'; ++// `CopilotClient` came from '@github/copilot-sdk' (removed in !!APP_NAME!!). ++// `discover()` only touches the RPC discovery endpoints and is no longer reached ++// by a live Copilot client; model the minimal surface it uses locally. ++type CopilotClient = { ++ rpc: { ++ // eslint-disable-next-line @typescript-eslint/no-explicit-any ++ agents: { discover(params: unknown): Promise }; ++ // eslint-disable-next-line @typescript-eslint/no-explicit-any ++ instructions: { discover(params: unknown): Promise }; ++ // eslint-disable-next-line @typescript-eslint/no-explicit-any ++ skills: { discover(params: unknown): Promise }; ++ }; ++}; + import { CancellationToken } from '../../../../base/common/cancellation.js'; + import { CancellationError } from '../../../../base/common/errors.js'; + import { Emitter, Event } from '../../../../base/common/event.js'; +diff --git a/src/vs/platform/agentHost/node/otel/agentHostOTelService.ts b/src/vs/platform/agentHost/node/otel/agentHostOTelService.ts +index 61cf2cf7..655f5b97 100644 +--- a/src/vs/platform/agentHost/node/otel/agentHostOTelService.ts ++++ b/src/vs/platform/agentHost/node/otel/agentHostOTelService.ts +@@ -5,7 +5,6 @@ + + import { mkdir } from 'fs/promises'; + import { dirname, join } from '../../../../base/common/path.js'; +-import type { TelemetryConfig } from '@github/copilot-sdk'; + import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js'; + import { URI } from '../../../../base/common/uri.js'; + import { INativeEnvironmentService } from '../../../environment/common/environment.js'; +@@ -20,7 +19,7 @@ import { + } from '../../../otel/node/otlp/outboundForwarder.js'; + import { OTelSqliteStore } from '../../../otel/node/sqlite/otelSqliteStore.js'; + import { AgentHostOTelSpansDbSubPath } from '../../common/agentService.js'; +-import { IAgentHostOTelService } from '../../common/otel/agentHostOTelService.js'; ++import { IAgentHostOTelService, type TelemetryConfig } from '../../common/otel/agentHostOTelService.js'; + + /** Sub-path under the user data directory where the span DB lives. */ + const SPANS_DB_SUBPATH = AgentHostOTelSpansDbSubPath; +diff --git a/src/vs/platform/agentHost/test/node/agentHostPromptRegistry.test.ts b/src/vs/platform/agentHost/test/node/agentHostPromptRegistry.test.ts +deleted file mode 100644 +index c2421fb6..00000000 +--- a/src/vs/platform/agentHost/test/node/agentHostPromptRegistry.test.ts ++++ /dev/null +@@ -1,125 +0,0 @@ +-/*--------------------------------------------------------------------------------------------- +- * Copyright (c) Microsoft Corporation. All rights reserved. +- * Licensed under the MIT License. See License.txt in the project root for license information. +- *--------------------------------------------------------------------------------------------*/ +- +-import assert from 'assert'; +-import type { SectionOverride, SystemMessageSection } from '@github/copilot-sdk'; +-import { AgentHostConfigKey, agentHostCustomizationConfigSchema } from '../../common/agentHostCustomizationConfig.js'; +-import type { SchemaValues } from '../../common/agentHostSchema.js'; +-import type { ModelSelection } from '../../common/state/protocol/state.js'; +-import { AgentHostPromptRegistry, agentHostPromptRegistry, type IAgentHostPromptContext } from '../../node/copilot/prompts/promptRegistry.js'; +-import { COPILOT_AGENT_HOST_SYSTEM_MESSAGE } from '../../node/copilot/prompts/systemMessage.js'; +-import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +-import '../../node/copilot/prompts/allPrompts.js'; +- +-/** Builds a prompt context backed by an in-memory bag of customization settings. */ +-function context(settings: SchemaValues = {}): IAgentHostPromptContext { +- return { getSetting: key => settings[key] }; +-} +- +-suite('AgentHostPromptRegistry', () => { +- +- ensureNoDisposablesAreLeakedInTestSuite(); +- +- test('falls back to the default system message when no model is provided', () => { +- const registry = new AgentHostPromptRegistry(); +- assert.deepStrictEqual(registry.resolveSystemMessageConfig(undefined, context()), COPILOT_AGENT_HOST_SYSTEM_MESSAGE); +- }); +- +- test('falls back to the default when no contributor matches the model', () => { +- const registry = new AgentHostPromptRegistry(); +- assert.deepStrictEqual(registry.resolveSystemMessageConfig({ id: 'unknown-model' }, context()), COPILOT_AGENT_HOST_SYSTEM_MESSAGE); +- }); +- +- test('a contributor can fully replace the system prompt (replace mode)', () => { +- const registry = new AgentHostPromptRegistry(); +- registry.registerPrompt(class { +- static readonly familyPrefixes = ['gpt-5']; +- resolveFullSystemPrompt(): string { +- return 'FULL PROMPT'; +- } +- }); +- assert.deepStrictEqual( +- registry.resolveSystemMessageConfig({ id: 'gpt-5-mini' }, context()), +- { mode: 'replace', content: 'FULL PROMPT' } +- ); +- }); +- +- test('a contributor can override individual sections (customize mode)', () => { +- const registry = new AgentHostPromptRegistry(); +- registry.registerPrompt(class { +- static readonly familyPrefixes = ['claude']; +- resolveSectionOverrides(): Partial> { +- return { guidelines: { action: 'append', content: 'Be concise.' } }; +- } +- }); +- assert.deepStrictEqual( +- registry.resolveSystemMessageConfig({ id: 'claude-sonnet' }, context()), +- { mode: 'customize', sections: { guidelines: { action: 'append', content: 'Be concise.' } } } +- ); +- }); +- +- test('treats empty section overrides as no override (falls back to default)', () => { +- const registry = new AgentHostPromptRegistry(); +- registry.registerPrompt(class { +- static readonly familyPrefixes = ['claude']; +- resolveSectionOverrides(): Partial> { +- return {}; +- } +- }); +- assert.deepStrictEqual( +- registry.resolveSystemMessageConfig({ id: 'claude-sonnet' }, context()), +- COPILOT_AGENT_HOST_SYSTEM_MESSAGE +- ); +- }); +- +- test('matchesModel takes precedence over family prefixes', () => { +- const registry = new AgentHostPromptRegistry(); +- registry.registerPrompt(class { +- static readonly familyPrefixes: readonly string[] = []; +- static matchesModel(model: ModelSelection): boolean { +- return model.id.includes('codex'); +- } +- resolveFullSystemPrompt(): string { +- return 'CODEX'; +- } +- }); +- assert.deepStrictEqual( +- registry.resolveSystemMessageConfig({ id: 'gpt-5-codex' }, context()), +- { mode: 'replace', content: 'CODEX' } +- ); +- }); +- +- test('contributors gate on the prompt context', () => { +- const registry = new AgentHostPromptRegistry(); +- registry.registerPrompt(class { +- static readonly familyPrefixes = ['claude']; +- resolveSectionOverrides(_model: ModelSelection, ctx: IAgentHostPromptContext): Partial> | undefined { +- return ctx.getSetting(AgentHostConfigKey.Opus48Prompt) === true ? { tone: { action: 'append', content: 'GATED' } } : undefined; +- } +- }); +- assert.deepStrictEqual( +- registry.resolveSystemMessageConfig({ id: 'claude-x' }, context({ [AgentHostConfigKey.Opus48Prompt]: true })), +- { mode: 'customize', sections: { tone: { action: 'append', content: 'GATED' } } } +- ); +- assert.deepStrictEqual( +- registry.resolveSystemMessageConfig({ id: 'claude-x' }, context()), +- COPILOT_AGENT_HOST_SYSTEM_MESSAGE +- ); +- }); +- +- suite('Opus contributor (registered via allPrompts)', () => { +- const opusModel: ModelSelection = { id: 'claude-opus-4-8' }; +- +- function resolveOpus(enabled: boolean | undefined) { +- return agentHostPromptRegistry.resolveSystemMessageConfig(opusModel, context(enabled === undefined ? {} : { [AgentHostConfigKey.Opus48Prompt]: enabled })); +- } +- +- test('applies customize overrides only when enabled', () => { +- assert.strictEqual(resolveOpus(undefined), COPILOT_AGENT_HOST_SYSTEM_MESSAGE); +- assert.strictEqual(resolveOpus(false), COPILOT_AGENT_HOST_SYSTEM_MESSAGE); +- assert.strictEqual(resolveOpus(true).mode, 'customize'); +- }); +- }); +-}); +diff --git a/src/vs/platform/agentHost/test/node/copilotAgent.test.ts b/src/vs/platform/agentHost/test/node/copilotAgent.test.ts +deleted file mode 100644 +index bd411161..00000000 +--- a/src/vs/platform/agentHost/test/node/copilotAgent.test.ts ++++ /dev/null +@@ -1,2591 +0,0 @@ +-/*--------------------------------------------------------------------------------------------- +- * Copyright (c) Microsoft Corporation. All rights reserved. +- * Licensed under the MIT License. See License.txt in the project root for license information. +- *--------------------------------------------------------------------------------------------*/ +- +-import type { CopilotClient, CopilotSession, ModelInfo, SessionEventPayload, SessionEventType, TypedSessionEventHandler } from '@github/copilot-sdk'; +-import type Anthropic from '@anthropic-ai/sdk'; +-import type { CCAModel } from '@vscode/copilot-api'; +-import assert from 'assert'; +-import * as fs from 'fs/promises'; +-import * as os from 'os'; +-import { VSBuffer } from '../../../../base/common/buffer.js'; +-import { DeferredPromise } from '../../../../base/common/async.js'; +-import { isCancellationError } from '../../../../base/common/errors.js'; +-import { Disposable, type DisposableStore, type IDisposable, type IReference } from '../../../../base/common/lifecycle.js'; +-import { Event } from '../../../../base/common/event.js'; +-import { Schemas } from '../../../../base/common/network.js'; +-import { waitForState } from '../../../../base/common/observable.js'; +-import { URI } from '../../../../base/common/uri.js'; +-import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +-import { INativeEnvironmentService } from '../../../environment/common/environment.js'; +-import { FileService } from '../../../files/common/fileService.js'; +-import { IFileService } from '../../../files/common/files.js'; +-import { InMemoryFileSystemProvider } from '../../../files/common/inMemoryFilesystemProvider.js'; +-import { IInstantiationService } from '../../../instantiation/common/instantiation.js'; +-import { InstantiationService } from '../../../instantiation/common/instantiationService.js'; +-import { ServiceCollection } from '../../../instantiation/common/serviceCollection.js'; +-import { ILogService, NullLogService } from '../../../log/common/log.js'; +-import { ITelemetryService } from '../../../telemetry/common/telemetry.js'; +-import { NullTelemetryService } from '../../../telemetry/common/telemetryUtils.js'; +-import { AgentHostConfigKey } from '../../common/agentHostCustomizationConfig.js'; +-import { IAgentPluginManager, ISyncedCustomization } from '../../common/agentPluginManager.js'; +-import { AgentSession, GITHUB_COPILOT_PROTECTED_RESOURCE, type AgentSignal, type IAgentActionSignal, type IAgentSessionMetadata } from '../../common/agentService.js'; +-import { ISessionDataService } from '../../common/sessionDataService.js'; +-import { buildSubagentSessionUri, buildChatUri, CustomizationLoadStatus, MessageKind, ResponsePartKind, ToolCallConfirmationReason, ToolCallStatus, TurnState, customizationId, type ClientPluginCustomization, type MarkdownResponsePart, type PluginCustomization, type ToolCallResult, type Turn, RuleCustomization } from '../../common/state/sessionState.js'; +-import { CustomizationType, type ToolDefinition } from '../../common/state/protocol/state.js'; +-import { ActionType, type ChatAction, type IDeltaAction, type SessionAction } from '../../common/state/sessionActions.js'; +- +-import { AgentConfigurationService, IAgentConfigurationService } from '../../node/agentConfigurationService.js'; +-import { AgentHostStateManager } from '../../node/agentHostStateManager.js'; +-import { IAgentHostGitService } from '../../node/agentHostGitService.js'; +-import { IAgentHostTerminalManager } from '../../node/agentHostTerminalManager.js'; +-import { IAgentHostOTelService } from '../../common/otel/agentHostOTelService.js'; +-import { AgentHostCompletions, IAgentHostCompletions } from '../../node/agentHostCompletions.js'; +-import { COPILOT_AGENT_HOST_SYSTEM_MESSAGE, CopilotAgent, getCopilotWorktreeName, getCopilotWorktreesRoot } from '../../node/copilot/copilotAgent.js'; +-import { NULL_CHECKPOINT_SERVICE } from '../../common/agentHostCheckpointService.js'; +-import { CopilotAgentSession } from '../../node/copilot/copilotAgentSession.js'; +-import { CopilotBranchNameGenerator, ICopilotBranchNameGenerator, getCopilotBranchNameHintFromMessage, normalizeCopilotBranchName } from '../../node/copilot/copilotBranchNameGenerator.js'; +-import type { CopilotSessionLaunchPlan, IActiveClientSnapshot } from '../../node/copilot/copilotSessionLauncher.js'; +-import { ShellManager } from '../../node/copilot/copilotShellTools.js'; +-import { SessionDatabase } from '../../node/sessionDatabase.js'; +-import { createNullSessionDataService } from '../common/sessionTestHelpers.js'; +-import { ActiveClientState } from '../../node/activeClientState.js'; +-import { ICopilotApiService, type ICopilotApiServiceRequestOptions, type ICopilotUtilityChatCompletionRequest } from '../../node/shared/copilotApiService.js'; +- +-class TestAgentPluginManager implements IAgentPluginManager { +- declare readonly _serviceBrand: undefined; +- +- readonly basePath = URI.from({ scheme: 'inmemory', path: '/agentPlugins' }); +- +- async syncCustomizations(_clientId: string, _customizations: ClientPluginCustomization[], _progress?: (status: PluginCustomization) => void): Promise { +- return []; +- } +-} +- +-class TestAgentHostGitService implements IAgentHostGitService { +- declare readonly _serviceBrand: undefined; +- +- repositoryRoot: URI | undefined = undefined; +- addedWorktrees: { repositoryRoot: URI; worktree: URI; branchName: string; startPoint: string }[] = []; +- addedExistingWorktrees: { repositoryRoot: URI; worktree: URI; branchName: string }[] = []; +- removedWorktrees: { repositoryRoot: URI; worktree: URI }[] = []; +- existingBranches = new Set(); +- dirtyWorkingDirectories = new Set(); +- +- async isInsideWorkTree(): Promise { return false; } +- async getCurrentBranch(): Promise { return undefined; } +- async getDefaultBranch(): Promise { return undefined; } +- async getBranches(): Promise { return []; } +- async getRepositoryRoot(): Promise { return this.repositoryRoot; } +- async getWorktreeRoots(): Promise { return []; } +- async addWorktree(repositoryRoot: URI, worktree: URI, branchName: string, startPoint: string): Promise { +- this.addedWorktrees.push({ repositoryRoot, worktree, branchName, startPoint }); +- this.existingBranches.add(branchName); +- } +- async addExistingWorktree(repositoryRoot: URI, worktree: URI, branchName: string): Promise { +- this.addedExistingWorktrees.push({ repositoryRoot, worktree, branchName }); +- } +- async removeWorktree(repositoryRoot: URI, worktree: URI): Promise { +- this.removedWorktrees.push({ repositoryRoot, worktree }); +- } +- async branchExists(_repositoryRoot: URI, branchName: string): Promise { +- return this.existingBranches.has(branchName); +- } +- async hasUncommittedChanges(workingDirectory: URI): Promise { +- return this.dirtyWorkingDirectories.has(workingDirectory.fsPath); +- } +- async commitAll(): Promise { } +- async restore(): Promise { } - async hasUpstream(): Promise { return false; } - async pushBranch(): Promise { } - async getSessionGitState(): Promise { return undefined; } @@ -8017,6 +10223,30 @@ index 6bb478dbb36..00000000000 - async getDefaultShell(): Promise { return '/bin/bash'; } -} - +-class TestCopilotApiService implements ICopilotApiService { +- declare readonly _serviceBrand: undefined; +- +- readonly utilityCalls: { token: string; request: ICopilotUtilityChatCompletionRequest; options?: ICopilotApiServiceRequestOptions }[] = []; +- response = 'generated-branch-name'; +- error: Error | undefined; +- +- messages(_githubToken: string, _request: Anthropic.MessageCreateParamsStreaming, _options?: ICopilotApiServiceRequestOptions): AsyncGenerator; +- messages(_githubToken: string, _request: Anthropic.MessageCreateParamsNonStreaming, _options?: ICopilotApiServiceRequestOptions): Promise; +- messages(): AsyncGenerator | Promise { +- throw new Error('not used'); +- } +- async countTokens(): Promise { throw new Error('not used'); } +- async models(): Promise { return []; } +- async responses(): Promise { throw new Error('not used'); } +- async utilityChatCompletion(githubToken: string, request: ICopilotUtilityChatCompletionRequest, options?: ICopilotApiServiceRequestOptions): Promise { +- this.utilityCalls.push({ token: githubToken, request, options }); +- if (this.error) { +- throw this.error; +- } +- return this.response; +- } +-} +- -class TestSessionDataService extends Disposable implements ISessionDataService { - declare readonly _serviceBrand: undefined; - @@ -8047,6 +10277,7 @@ index 6bb478dbb36..00000000000 - cleanupOrphanedData(): Promise { return Promise.resolve(); } - whenIdle(): Promise { return Promise.resolve(); } -} +-type CopilotModelsList = CopilotClient['rpc']['models']['list']; - -interface ITestCopilotModelInfo { - readonly id: string; @@ -8056,13 +10287,27 @@ index 6bb478dbb36..00000000000 - readonly limits?: { readonly max_context_window_tokens?: number }; - }; - readonly policy?: { readonly state?: NonNullable['state'] }; -- readonly billing?: ModelInfo['billing']; +- readonly billing?: ModelInfo['billing'] & { +- readonly priceCategory?: string; +- readonly tokenPrices?: { +- readonly contextMax?: number; +- readonly inputPrice?: number; +- readonly cachePrice?: number; +- readonly cacheWritePrice?: number; +- readonly outputPrice?: number; +- readonly longContext?: { readonly contextMax?: number; readonly inputPrice?: number; readonly cachePrice?: number; readonly cacheWritePrice?: number; readonly outputPrice?: number }; +- }; +- }; +- readonly modelPickerPriceCategory?: string; - readonly supportedReasoningEfforts?: ModelInfo['supportedReasoningEfforts']; - readonly defaultReasoningEffort?: ModelInfo['defaultReasoningEffort']; -} - --interface ITestCopilotClient extends Pick { -- readonly rpc: { readonly sessions: { readonly fork: CopilotClient['rpc']['sessions']['fork'] } }; +-interface ITestCopilotClient extends Pick { +- readonly rpc: { +- readonly sessions: { readonly fork: CopilotClient['rpc']['sessions']['fork'] }; +- readonly models: { readonly list: CopilotModelsList }; +- }; -} - -function toSdkModelInfo(model: ITestCopilotModelInfo): ModelInfo { @@ -8080,23 +10325,46 @@ index 6bb478dbb36..00000000000 - }, - ...(model.policy ? { policy: { state: model.policy.state ?? 'enabled', terms: '' } } : {}), - ...(model.billing ? { billing: model.billing } : {}), +- ...(model.modelPickerPriceCategory ? { modelPickerPriceCategory: model.modelPickerPriceCategory } : {}), - ...(model.supportedReasoningEfforts ? { supportedReasoningEfforts: model.supportedReasoningEfforts } : {}), - ...(model.defaultReasoningEffort ? { defaultReasoningEffort: model.defaultReasoningEffort } : {}), - }; -} - -class TestCopilotClient implements ITestCopilotClient { -- readonly rpc: ITestCopilotClient['rpc'] = { sessions: { fork: async () => ({ sessionId: 'forked-session' }) } }; +- readonly rpc: ITestCopilotClient['rpc'] = { +- sessions: { fork: async () => ({ sessionId: 'forked-session' }) }, +- models: { +- list: async params => { +- this.modelListRequests.push(params); +- const error = this.modelListErrors.shift(); +- if (error) { +- throw error; +- } +- return { models: this._models.map(toSdkModelInfo) }; +- } +- }, +- }; +- startCallCount = 0; +- stopCallCount = 0; - listSessionCallCount = 0; +- readonly modelListRequests: Parameters[0][] = []; +- readonly modelListErrors: Error[] = []; - readonly getSessionMetadataCalls: string[] = []; +- readonly deletedSessionIds: string[] = []; - - constructor( - private readonly _sessions: Awaited>, - private readonly _models: readonly ITestCopilotModelInfo[] = [], - ) { } - -- async start(): Promise { } -- async stop(): ReturnType { return []; } +- async start(): Promise { +- this.startCallCount++; +- } +- async stop(): ReturnType { +- this.stopCallCount++; +- return []; +- } - async listSessions(): ReturnType { - this.listSessionCallCount++; - return this._sessions; @@ -8106,6 +10374,9 @@ index 6bb478dbb36..00000000000 - this.getSessionMetadataCalls.push(sessionId); - return this._sessions.find(s => s.sessionId === sessionId); - } +- async deleteSession(sessionId: string): Promise { +- this.deletedSessionIds.push(sessionId); +- } - createSession: ITestCopilotClient['createSession'] = async () => { throw new Error('not implemented'); }; - resumeSession: ITestCopilotClient['resumeSession'] = async () => { throw new Error('not implemented'); }; -} @@ -8158,9 +10429,10 @@ index 6bb478dbb36..00000000000 - @ISessionDataService sessionDataService: ISessionDataService, - @IAgentHostGitService gitService: IAgentHostGitService, - @IAgentConfigurationService configurationService: IAgentConfigurationService, +- @ICopilotBranchNameGenerator branchNameGenerator: ICopilotBranchNameGenerator, - @IAgentHostCompletions completions: IAgentHostCompletions, - ) { -- super(logService, instantiationService, sessionDataService, gitService, configurationService, new MockAgentHostOTelService(), completions, NULL_CHECKPOINT_SERVICE); +- super(logService, instantiationService, sessionDataService, gitService, configurationService, new MockAgentHostOTelService(), branchNameGenerator, completions, NULL_CHECKPOINT_SERVICE); - this._enablePlanModeOnClient(this._copilotClient as CopilotClient); - } - @@ -8180,9 +10452,10 @@ index 6bb478dbb36..00000000000 - @ISessionDataService sessionDataService: ISessionDataService, - @IAgentHostGitService gitService: IAgentHostGitService, - @IAgentConfigurationService configurationService: IAgentConfigurationService, +- @ICopilotBranchNameGenerator branchNameGenerator: ICopilotBranchNameGenerator, - @IAgentHostCompletions completions: IAgentHostCompletions, - ) { -- super(logService, instantiationService, sessionDataService, gitService, configurationService, new MockAgentHostOTelService(), completions, NULL_CHECKPOINT_SERVICE); +- super(logService, instantiationService, sessionDataService, gitService, configurationService, new MockAgentHostOTelService(), branchNameGenerator, completions, NULL_CHECKPOINT_SERVICE); - this._enablePlanModeOnClient(this._copilotClient as CopilotClient); - } - @@ -8217,7 +10490,7 @@ index 6bb478dbb36..00000000000 - kind: 'action', - session: sessionUri, - action: { -- type: ActionType.SessionResponsePart, +- type: ActionType.ChatResponsePart, - turnId, - part: { kind: ResponsePartKind.Markdown, id: `synth-${Date.now()}`, content }, - }, @@ -8232,7 +10505,7 @@ index 6bb478dbb36..00000000000 - } -} - --function createTestAgentContext(disposables: Pick, options?: { sessionDataService?: ISessionDataService; copilotClient?: ITestCopilotClient; useRealResumePath?: boolean; gitService?: TestAgentHostGitService; environmentServiceRegistration?: 'native' | 'none'; pluginManager?: IAgentPluginManager; fileService?: FileService }): { agent: CopilotAgent; instantiationService: IInstantiationService; configurationService: IAgentConfigurationService; fileService: FileService } { +-function createTestAgentContext(disposables: Pick, options?: { sessionDataService?: ISessionDataService; copilotClient?: ITestCopilotClient; useRealResumePath?: boolean; gitService?: TestAgentHostGitService; environmentServiceRegistration?: 'native' | 'none'; pluginManager?: IAgentPluginManager; fileService?: FileService; copilotApiService?: ICopilotApiService }): { agent: CopilotAgent; instantiationService: IInstantiationService; configurationService: IAgentConfigurationService; fileService: FileService } { - const services = new ServiceCollection(); - const logService = new NullLogService(); - const fileService = options?.fileService ?? disposables.add(new FileService(logService)); @@ -8252,6 +10525,9 @@ index 6bb478dbb36..00000000000 - flush: async () => undefined, - }); - services.set(IAgentHostCompletions, disposables.add(new AgentHostCompletions(logService))); +- const copilotApiService = options?.copilotApiService ?? new TestCopilotApiService(); +- services.set(ICopilotApiService, copilotApiService); +- services.set(ICopilotBranchNameGenerator, new CopilotBranchNameGenerator(copilotApiService, logService)); - services.set(ITelemetryService, NullTelemetryService); - if (options?.environmentServiceRegistration !== 'none') { - const environmentService = { @@ -8268,7 +10544,7 @@ index 6bb478dbb36..00000000000 - return { agent, instantiationService, configurationService: configService, fileService }; -} - --function createTestAgent(disposables: Pick, options?: { sessionDataService?: ISessionDataService; copilotClient?: ITestCopilotClient; useRealResumePath?: boolean; gitService?: TestAgentHostGitService; environmentServiceRegistration?: 'native' | 'none'; pluginManager?: IAgentPluginManager }): CopilotAgent { +-function createTestAgent(disposables: Pick, options?: { sessionDataService?: ISessionDataService; copilotClient?: ITestCopilotClient; useRealResumePath?: boolean; gitService?: TestAgentHostGitService; environmentServiceRegistration?: 'native' | 'none'; pluginManager?: IAgentPluginManager; copilotApiService?: ICopilotApiService }): CopilotAgent { - return createTestAgentContext(disposables, options).agent; -} - @@ -8287,10 +10563,11 @@ index 6bb478dbb36..00000000000 - }, - resumeSession: async () => new MockCopilotSession() as unknown as CopilotSession, - }, +- activeClientState: new ActiveClientState(), - sessionId: 'test-session-1', - workingDirectory: undefined, - resolvedAgentName: undefined, -- snapshot: { clientId: '', tools: [], plugins: [] }, +- snapshot: { tools: [], plugins: [], mcpServers: {} }, - shellManager, - githubToken: 'token', - model: undefined, @@ -8334,6 +10611,19 @@ index 6bb478dbb36..00000000000 -suite('CopilotAgent', () => { - const disposables = ensureNoDisposablesAreLeakedInTestSuite(); - +- test('advertises Copilot as its display name', async () => { +- const agent = createTestAgent(disposables); +- try { +- assert.deepStrictEqual(agent.getDescriptor(), { +- provider: 'copilotcli', +- displayName: 'Copilot', +- description: 'Copilot SDK agent running in a dedicated process', +- }); +- } finally { +- await disposeAgent(agent); +- } +- }); +- - test('uses the Copilot CLI sibling worktrees root convention', () => { - assert.strictEqual( - getCopilotWorktreesRoot(URI.file('/Users/me/src/vscode')).fsPath, @@ -8341,54 +10631,228 @@ index 6bb478dbb36..00000000000 - ); - }); - -- test('uses Agents-window Copilot CLI branch prefix', () => { -- assert.strictEqual(getCopilotWorktreeBranchName('12345678-aaaa-bbbb-cccc-123456789abc', 'add-agent-host-config'), 'agents/add-agent-host-config-12345678'); -- assert.strictEqual(getCopilotWorktreeBranchName('12345678-aaaa-bbbb-cccc-123456789abc', undefined), 'agents/12345678-aaaa-bbbb-cccc-123456789abc'); +- test('uses generated Agents-window Copilot CLI branch names', async () => { +- const copilotApiService = new TestCopilotApiService(); +- copilotApiService.response = 'add-agent-host-config'; +- const generator = new CopilotBranchNameGenerator(copilotApiService, new NullLogService()); +- +- assert.deepStrictEqual({ +- generated: await generator.generateBranchName({ sessionId: '12345678-aaaa-bbbb-cccc-123456789abc', message: 'Add agent host config', githubToken: 'token' }), +- fallback: await generator.generateBranchName({ sessionId: '12345678-aaaa-bbbb-cccc-123456789abc', message: 'Add agent host config' }), +- token: copilotApiService.utilityCalls[0]?.token, +- promptIncludesUserText: copilotApiService.utilityCalls[0]?.request.messages.some(message => message.content.includes('Add agent host config')), +- }, { +- generated: 'agents/add-agent-host-config-12345678', +- fallback: 'agents/add-agent-host-config-12345678', +- token: 'token', +- promptIncludesUserText: true, +- }); - }); - - test('uses Git extension branch-derived worktree folder names', () => { - assert.strictEqual(getCopilotWorktreeName('agents/add-agent-host-config-12345678'), 'agents-add-agent-host-config-12345678'); - }); - -- test('keeps hinted branch names short', () => { -- assert.strictEqual(getCopilotWorktreeBranchName('12345678-aaaa-bbbb-cccc-123456789abc', 'a'.repeat(48)).length, 'agents/'.length + 48 + '-12345678'.length); +- test('keeps generated branch names short', async () => { +- const copilotApiService = new TestCopilotApiService(); +- copilotApiService.response = 'a'.repeat(100); +- const generator = new CopilotBranchNameGenerator(copilotApiService, new NullLogService()); +- +- assert.strictEqual( +- (await generator.generateBranchName({ sessionId: '12345678-aaaa-bbbb-cccc-123456789abc', message: 'Add agent host config', githubToken: 'token' })).length, +- 'agents/'.length + 48 + '-12345678'.length, +- ); +- }); +- +- test('normalizes generated branch names', () => { +- assert.deepStrictEqual({ +- simple: normalizeCopilotBranchName('feature-branch'), +- uppercase: normalizeCopilotBranchName('Feature-Branch'), +- special: normalizeCopilotBranchName('Fix: Add new feature! (#42)'), +- unicode: normalizeCopilotBranchName('café-feature'), +- empty: normalizeCopilotBranchName('🚀🎉'), +- }, { +- simple: 'feature-branch', +- uppercase: 'feature-branch', +- special: 'fixaddnewfeature42', +- unicode: 'caf-feature', +- empty: '', +- }); - }); - -- test('derives slug branch hint from first message', () => { -- assert.strictEqual(getCopilotBranchNameHintFromMessage('Add agent host config'), 'add-agent-host-config'); -- assert.strictEqual(getCopilotBranchNameHintFromMessage(' Fix: the bug!! '), 'fix-the-bug'); -- assert.strictEqual(getCopilotBranchNameHintFromMessage('Refactor café ☕ rendering'), 'refactor-cafe-rendering'); -- assert.strictEqual(getCopilotBranchNameHintFromMessage('one two three four five six seven eight nine ten'), 'one-two-three-four-five-six-seven-eight'); -- assert.strictEqual(getCopilotBranchNameHintFromMessage('a'.repeat(100))?.length, 48); -- assert.strictEqual(getCopilotBranchNameHintFromMessage('!!! ??? ...'), undefined); -- assert.strictEqual(getCopilotBranchNameHintFromMessage(''), undefined); +- test('derives slug branch hint from first message for fallback', () => { +- assert.deepStrictEqual({ +- simple: getCopilotBranchNameHintFromMessage('Add agent host config'), +- punctuation: getCopilotBranchNameHintFromMessage(' Fix: the bug!! '), +- unicode: getCopilotBranchNameHintFromMessage('Refactor café ☕ rendering'), +- words: getCopilotBranchNameHintFromMessage('one two three four five six seven eight nine ten'), +- long: getCopilotBranchNameHintFromMessage('a'.repeat(100))?.length, +- empty: getCopilotBranchNameHintFromMessage('!!! ??? ...'), +- }, { +- simple: 'add-agent-host-config', +- punctuation: 'fix-the-bug', +- unicode: 'refactor-cafe-rendering', +- words: 'one-two-three-four-five-six-seven-eight', +- long: 48, +- empty: undefined, +- }); - }); - -- test('returns empty models and throws AuthRequired for sessions before authentication', async () => { -- const agent = createTestAgent(disposables); +- test('falls back to first-message slug when generated branch name cannot be used', async () => { +- const copilotApiService = new TestCopilotApiService(); +- copilotApiService.response = '!!! ??? ...'; +- const generator = new CopilotBranchNameGenerator(copilotApiService, new NullLogService()); +- +- assert.strictEqual( +- await generator.generateBranchName({ sessionId: '12345678-aaaa-bbbb-cccc-123456789abc', message: 'Add agent host config', githubToken: 'token' }), +- 'agents/add-agent-host-config-12345678', +- ); +- }); +- +- test('falls back to first-message slug when branch name generation fails', async () => { +- const copilotApiService = new TestCopilotApiService(); +- copilotApiService.error = new Error('failed'); +- const generator = new CopilotBranchNameGenerator(copilotApiService, new NullLogService()); +- +- assert.strictEqual( +- await generator.generateBranchName({ sessionId: '12345678-aaaa-bbbb-cccc-123456789abc', message: 'Add agent host config', githubToken: 'token' }), +- 'agents/add-agent-host-config-12345678', +- ); +- }); +- +- test('falls back to session id when no branch name can be derived', async () => { +- const copilotApiService = new TestCopilotApiService(); +- copilotApiService.response = '!!! ??? ...'; +- const generator = new CopilotBranchNameGenerator(copilotApiService, new NullLogService()); +- +- assert.strictEqual( +- await generator.generateBranchName({ sessionId: '12345678-aaaa-bbbb-cccc-123456789abc', message: '!!! ??? ...', githubToken: 'token' }), +- 'agents/12345678-aaaa-bbbb-cccc-123456789abc', +- ); +- }); +- +- test('returns empty models and lists sessions before authentication', async () => { +- const sessionDataService = disposables.add(new TestSessionDataService()); +- const ownedSession = AgentSession.uri('copilotcli', 'owned-before-auth'); +- const ownedDb = sessionDataService.openDatabase(ownedSession); +- ownedDb.dispose(); +- const client = new TestCopilotClient([sdkSession('owned-before-auth')]); +- const agent = createTestAgent(disposables, { sessionDataService, copilotClient: client }); - try { -- assert.deepStrictEqual(agent.models.get(), []); -- await assert.rejects( -- () => agent.listSessions(), -- (error: Error) => error instanceof ProtocolError && error.code === AHP_AUTH_REQUIRED, -- ); +- const sessions = await agent.listSessions(); +- assert.deepStrictEqual({ +- models: agent.models.get(), +- sessions: sessions.map(session => AgentSession.id(session.session)), +- starts: client.startCallCount, +- listCalls: client.listSessionCallCount, +- }, { +- models: [], +- sessions: ['owned-before-auth'], +- starts: 1, +- listCalls: 1, +- }); - } finally { - await disposeAgent(agent); - } - }); - -- test('requires authentication before creating a session', async () => { -- const agent = createTestAgent(disposables); +- test('starts the client and creates a provisional session before authentication', async () => { +- const client = new TestCopilotClient([]); +- const agent = createTestAgent(disposables, { copilotClient: client }); +- const session = AgentSession.uri('copilotcli', 'unauth-create'); +- const workingDirectory = URI.file('/workspace'); - try { -- await assert.rejects( -- () => agent.createSession({ workingDirectory: URI.file('/workspace') }), -- (error: Error) => error instanceof ProtocolError && error.code === AHP_AUTH_REQUIRED, -- ); +- const result = await agent.createSession({ session, workingDirectory }); +- assert.ok(result.workingDirectory); +- assert.deepStrictEqual({ +- session: result.session.toString(), +- workingDirectory: result.workingDirectory.toString(), +- provisional: result.provisional, +- starts: client.startCallCount, +- stops: client.stopCallCount, +- }, { +- session: session.toString(), +- workingDirectory: workingDirectory.toString(), +- provisional: true, +- starts: 1, +- stops: 0, +- }); - } finally { - await disposeAgent(agent); - } - }); - +- test('passes the GitHub token when refreshing models', async () => { +- const client = new TestCopilotClient([], [{ +- id: 'gpt-4o', +- name: 'GPT-4o', +- }]); +- const agent = createTestAgent(disposables, { copilotClient: client }); +- try { +- await agent.authenticate('https://api.github.com', 'model-token'); +- await waitForState(agent.models, models => models.length > 0); +- +- assert.deepStrictEqual(client.modelListRequests, [{ gitHubToken: 'model-token' }]); +- } finally { +- await disposeAgent(agent); +- } +- }); +- +- test('does not stop the client when the auth token changes', async () => { +- const client = new TestCopilotClient([], [{ +- id: 'gpt-4o', +- name: 'GPT-4o', +- }]); +- const agent = createTestAgent(disposables, { copilotClient: client }); +- try { +- await agent.listSessions(); +- await agent.authenticate('https://api.github.com', 'model-token-a'); +- for (let i = 0; i < 200 && client.modelListRequests.length < 1; i++) { +- await new Promise(resolve => setTimeout(resolve, 0)); +- } +- await agent.authenticate('https://api.github.com', 'model-token-b'); +- for (let i = 0; i < 200 && client.modelListRequests.length < 2; i++) { +- await new Promise(resolve => setTimeout(resolve, 0)); +- } +- +- assert.deepStrictEqual({ +- starts: client.startCallCount, +- stops: client.stopCallCount, +- requests: client.modelListRequests, +- }, { +- starts: 1, +- stops: 0, +- requests: [{ gitHubToken: 'model-token-a' }, { gitHubToken: 'model-token-b' }], +- }); +- } finally { +- await disposeAgent(agent); +- } +- }); +- +- test('createSession falls back to an empty temp directory when workingDirectory is omitted', async () => { +- const agent = createTestAgent(disposables); +- let createdWorkingDirectory: URI | undefined; +- try { +- await agent.authenticate('https://api.github.com', 'token'); +- +- const result = await agent.createSession({ +- session: AgentSession.uri('copilotcli', 'temp-fallback'), +- }); +- +- assert.strictEqual(result.provisional, true); +- assert.ok(result.workingDirectory); +- createdWorkingDirectory = result.workingDirectory; +- assert.strictEqual(createdWorkingDirectory.scheme, Schemas.file); +- assert.strictEqual(createdWorkingDirectory.fsPath.toLowerCase().startsWith(os.tmpdir().toLowerCase()), true); +- assert.deepStrictEqual(await fs.readdir(createdWorkingDirectory.fsPath), []); +- } finally { +- if (createdWorkingDirectory) { +- await fs.rm(createdWorkingDirectory.fsPath, { recursive: true, force: true }); +- } +- await disposeAgent(agent); +- } +- }).timeout(30_000); +- - suite('restart on startup config change', () => { - - class StopCountingClient extends TestCopilotClient { @@ -8482,6 +10946,124 @@ index 6bb478dbb36..00000000000 - } - }); - +- test('models include token-price and price-category metadata when billing provides it', async () => { +- const agent = createTestAgent(disposables, { +- copilotClient: new TestCopilotClient([], [{ +- id: 'claude-sonnet', +- name: 'Claude Sonnet', +- capabilities: { limits: { max_context_window_tokens: 200_000 } }, +- billing: { +- multiplier: 1, +- tokenPrices: { +- contextMax: 200_000, +- inputPrice: 3, +- cachePrice: 1, +- outputPrice: 15, +- longContext: { contextMax: 1_000_000, inputPrice: 6, cachePrice: 1, outputPrice: 22.5 }, +- }, +- }, +- modelPickerPriceCategory: 'medium', +- }]), +- }); +- try { +- await agent.authenticate('https://api.github.com', 'token'); +- const models = await waitForState(agent.models, models => models.length > 0); +- +- assert.deepStrictEqual(models[0]._meta, { +- multiplierNumeric: 1, +- inputCost: 3, +- cacheCost: 1, +- cacheWriteCost: 1, +- outputCost: 15, +- longContextInputCost: 6, +- longContextOutputCost: 22.5, +- priceCategory: 'medium', +- }); +- } finally { +- await disposeAgent(agent); +- } +- }); +- +- test('configSchema emits a thinkingLevel property when the model advertises reasoning efforts', async () => { +- const agent = createTestAgent(disposables, { +- copilotClient: new TestCopilotClient([], [{ +- id: 'o3', +- name: 'o3', +- capabilities: { limits: { max_context_window_tokens: 128000 } }, +- supportedReasoningEfforts: ['low', 'medium', 'high'], +- defaultReasoningEffort: 'medium', +- }]), +- }); +- try { +- await agent.authenticate('https://api.github.com', 'token'); +- const models = await waitForState(agent.models, models => models.length > 0); +- +- const schema = models[0].configSchema; +- assert.deepStrictEqual(schema?.properties.thinkingLevel?.enum, ['low', 'medium', 'high']); +- assert.strictEqual(schema?.properties.thinkingLevel?.default, 'medium'); +- assert.strictEqual(schema?.properties.contextTier, undefined); +- } finally { +- await disposeAgent(agent); +- } +- }); +- +- test('configSchema emits a contextTier property when long_context tier exceeds default', async () => { +- const agent = createTestAgent(disposables, { +- copilotClient: new TestCopilotClient([], [{ +- id: 'claude-sonnet', +- name: 'Claude Sonnet', +- capabilities: { limits: { max_context_window_tokens: 200_000 } }, +- billing: { +- multiplier: 1, +- tokenPrices: { +- contextMax: 200_000, +- longContext: { contextMax: 1_000_000, inputPrice: 2 }, +- }, +- }, +- }]), +- }); +- try { +- await agent.authenticate('https://api.github.com', 'token'); +- const models = await waitForState(agent.models, models => models.length > 0); +- +- const contextTier = models[0].configSchema?.properties.contextTier; +- assert.deepStrictEqual(contextTier?.enum, ['default', 'long_context']); +- assert.strictEqual(contextTier?.default, 'default'); +- assert.deepStrictEqual(contextTier?.enumLabels, ['200K', '1M']); +- } finally { +- await disposeAgent(agent); +- } +- }); +- +- test('configSchema omits contextTier when long_context tier is missing or not larger', async () => { +- const agent = createTestAgent(disposables, { +- copilotClient: new TestCopilotClient([], [ +- { +- id: 'no-long-context', +- name: 'No Long Context', +- billing: { multiplier: 1, tokenPrices: { contextMax: 200_000 } }, +- }, +- { +- id: 'equal-long-context', +- name: 'Equal Long Context', +- billing: { +- multiplier: 1, +- tokenPrices: { contextMax: 200_000, longContext: { contextMax: 200_000 } }, +- }, +- }, +- ]), +- }); +- try { +- await agent.authenticate('https://api.github.com', 'token'); +- const models = await waitForState(agent.models, models => models.length > 0); +- +- assert.strictEqual(models[0].configSchema, undefined); +- assert.strictEqual(models[1].configSchema, undefined); +- } finally { +- await disposeAgent(agent); +- } +- }); +- - test('agent-created sessions can resolve session-state paths via INativeEnvironmentService', async () => { - const sessionDataService = disposables.add(new TestSessionDataService()); - const { agent, instantiationService } = createTestAgentContext(disposables, { @@ -8702,7 +11284,7 @@ index 6bb478dbb36..00000000000 - const pluginManager = new PluginDirSpyManager(); - const { agent } = createTestAgentContext(disposables, { sessionDataService, copilotClient: client, pluginManager, fileService }); - -- const actions: SessionAction[] = []; +- const actions: (SessionAction | ChatAction)[] = []; - disposables.add(agent.onDidSessionProgress(s => { - if (s.kind === 'action') { - actions.push(s.action); @@ -8741,13 +11323,30 @@ index 6bb478dbb36..00000000000 - const fileService = disposables.add(new FileService(new NullLogService())); - disposables.add(fileService.registerProvider(Schemas.inMemory, disposables.add(new InMemoryFileSystemProvider()))); - +- const agentContent = [ +- '---', +- 'name: helper', +- 'description: helps out', +- '---', +- 'agent body', +- ]; +- const instructionContent = [ +- '---', +- 'name: nested', +- 'description: nested instructions', +- 'applyTo: *.ts, *.js', +- '---', +- 'instruction body', +- ]; +- +- - const workspace = URI.from({ scheme: Schemas.inMemory, path: '/workspace' }); - await fileService.createFolder(URI.joinPath(workspace, '.github', 'agents')); - await fileService.createFolder(URI.joinPath(workspace, '.github', 'instructions', 'team')); - const agentFile = URI.joinPath(workspace, '.github', 'agents', 'helper.agent.md'); - const instructionFile = URI.joinPath(workspace, '.github', 'instructions', 'team', 'nested.instructions.md'); -- await fileService.writeFile(agentFile, VSBuffer.fromString('agent body')); -- await fileService.writeFile(instructionFile, VSBuffer.fromString('instruction body')); +- await fileService.writeFile(agentFile, VSBuffer.fromString(agentContent.join('\n'))); +- await fileService.writeFile(instructionFile, VSBuffer.fromString(instructionContent.join('\n'))); - const agentsMdFile = URI.joinPath(workspace, 'AGENTS.md'); - await fileService.writeFile(agentsMdFile, VSBuffer.fromString('agents md body')); - @@ -8782,6 +11381,7 @@ index 6bb478dbb36..00000000000 - id: customizationId(agentFile.toString()), - uri: agentFile.toString(), - name: 'helper', +- description: 'helps out', - }]); - - const instructionDirectory = discoveredDirectories.find(customization => customization.uri === URI.joinPath(workspace, '.github', 'instructions').toString()); @@ -8791,7 +11391,10 @@ index 6bb478dbb36..00000000000 - type: CustomizationType.Rule, - id: customizationId(instructionFile.toString()), - uri: instructionFile.toString(), -- name: 'nested.instructions.md', +- name: 'nested', +- description: 'nested instructions', +- globs: ['*.ts', '*.js'], +- alwaysApply: undefined, - }]); - - const agentInstructionsDirectory = discoveredDirectories.find(customization => customization.uri === workspace.toString()); @@ -8846,6 +11449,142 @@ index 6bb478dbb36..00000000000 - await disposeAgent(agent); - } - }); +- +- test('getSessionCustomizations does not republish discovered directories when watcher changes are discovery-neutral', async () => { +- const fileService = disposables.add(new FileService(new NullLogService())); +- disposables.add(fileService.registerProvider(Schemas.inMemory, disposables.add(new InMemoryFileSystemProvider()))); +- +- const workspace = URI.from({ scheme: Schemas.inMemory, path: '/workspace' }); +- const agentsRoot = URI.joinPath(workspace, '.github', 'agents'); +- await fileService.createFolder(agentsRoot); +- await fileService.writeFile(URI.joinPath(agentsRoot, 'helper.agent.md'), VSBuffer.fromString('agent body')); +- +- const sessionDataService = disposables.add(new TestSessionDataService()); +- const client = new TestCopilotClient([]); +- const { agent } = createTestAgentContext(disposables, { sessionDataService, copilotClient: client, fileService }); +- +- const actions: (SessionAction | ChatAction)[] = []; +- disposables.add(agent.onDidSessionProgress(progress => { +- if (progress.kind === 'action') { +- actions.push(progress.action); +- } +- })); +- +- const countDirectoryPublishesForAgentsRoot = (): number => actions.filter(action => { +- if (action.type === ActionType.SessionCustomizationUpdated) { +- const customization = (action as Extract).customization; +- return customization.type === CustomizationType.Directory && customization.uri === agentsRoot.toString(); +- } +- if (action.type === ActionType.SessionCustomizationsChanged) { +- const customizations = (action as Extract).customizations; +- return customizations.some(customization => customization.type === CustomizationType.Directory && customization.uri === agentsRoot.toString()); +- } +- return false; +- }).length; +- +- try { +- await agent.authenticate('https://api.github.com', 'token'); +- +- const session = AgentSession.uri('copilotcli', 'session-discovery-neutral-watcher-change'); +- await agent.createSession({ +- session, +- workingDirectory: workspace, +- }); +- +- await agent.getSessionCustomizations(session); +- await new Promise(resolve => setTimeout(resolve, 50)); +- const publishCountBefore = countDirectoryPublishesForAgentsRoot(); +- +- // README.md is intentionally excluded from discovered agents. +- await fileService.writeFile(URI.joinPath(agentsRoot, 'README.md'), VSBuffer.fromString('ignored')); +- +- for (let i = 0; i < 20; i++) { +- await new Promise(resolve => setTimeout(resolve, 50)); +- assert.strictEqual(countDirectoryPublishesForAgentsRoot(), publishCountBefore, 'expected no republish when discovery output is unchanged'); +- } +- +- const after = await agent.getSessionCustomizations(session); +- assert.deepStrictEqual(after.filter(customization => customization.type === CustomizationType.Directory).map(customization => customization.uri), [agentsRoot.toString()]); +- } finally { +- await disposeAgent(agent); +- } +- }); +- +- test('getSessionCustomizations coalesces burst watcher changes into one discovered refresh publish', async () => { +- const fileService = disposables.add(new FileService(new NullLogService())); +- disposables.add(fileService.registerProvider(Schemas.inMemory, disposables.add(new InMemoryFileSystemProvider()))); +- +- const workspace = URI.from({ scheme: Schemas.inMemory, path: '/workspace' }); +- const agentsRoot = URI.joinPath(workspace, '.github', 'agents'); +- const instructionsRoot = URI.joinPath(workspace, '.github', 'instructions'); +- await fileService.createFolder(agentsRoot); +- await fileService.createFolder(instructionsRoot); +- await fileService.writeFile(URI.joinPath(agentsRoot, 'helper-0.agent.md'), VSBuffer.fromString('agent 0')); +- await fileService.writeFile(URI.joinPath(instructionsRoot, 'base.instructions.md'), VSBuffer.fromString('---\napplyTo:\n - src/**\n---\nbase instruction')); +- +- const sessionDataService = disposables.add(new TestSessionDataService()); +- const client = new TestCopilotClient([]); +- const { agent } = createTestAgentContext(disposables, { sessionDataService, copilotClient: client, fileService }); +- +- const actions: (SessionAction | ChatAction)[] = []; +- disposables.add(agent.onDidSessionProgress(progress => { +- if (progress.kind === 'action') { +- actions.push(progress.action); +- } +- })); +- +- type DiscoveredDirectoryCustomization = PluginCustomization & { children: NonNullable }; +- +- const countDiscoveredRefreshPublishes = (): number => actions.filter(action => { +- if (action.type !== ActionType.SessionCustomizationsChanged) { +- return false; +- } +- const customizations = (action as Extract).customizations; +- return customizations.some(customization => customization.type === CustomizationType.Directory && customization.uri === agentsRoot.toString()) +- && customizations.some(customization => customization.type === CustomizationType.Directory && customization.uri === instructionsRoot.toString()); +- }).length; +- +- try { +- await agent.authenticate('https://api.github.com', 'token'); +- +- const session = AgentSession.uri('copilotcli', 'session-discovery-burst-watcher-change'); +- await agent.createSession({ +- session, +- workingDirectory: workspace, +- }); +- +- await agent.getSessionCustomizations(session); +- await new Promise(resolve => setTimeout(resolve, 50)); +- const publishCountBeforeBurst = countDiscoveredRefreshPublishes(); +- +- await Promise.all([ +- fileService.writeFile(URI.joinPath(agentsRoot, 'helper-1.agent.md'), VSBuffer.fromString('agent 1')), +- fileService.writeFile(URI.joinPath(agentsRoot, 'helper-2.agent.md'), VSBuffer.fromString('agent 2')), +- fileService.writeFile(URI.joinPath(instructionsRoot, 'extra.instructions.md'), VSBuffer.fromString('---\napplyTo:\n - test/**\n---\nextra instruction')), +- ]); +- +- let discoveredAgentCount = 0; +- let discoveredInstructionCount = 0; +- for (let i = 0; i < 20 && (discoveredAgentCount < 3 || discoveredInstructionCount < 2); i++) { +- await new Promise(resolve => setTimeout(resolve, 50)); +- const customizations = await agent.getSessionCustomizations(session); +- const discoveredAgentDirectory = customizations.find((customization): customization is DiscoveredDirectoryCustomization => customization.type === CustomizationType.Directory && customization.uri === agentsRoot.toString()); +- const discoveredInstructionDirectory = customizations.find((customization): customization is DiscoveredDirectoryCustomization => customization.type === CustomizationType.Directory && customization.uri === instructionsRoot.toString()); +- discoveredAgentCount = discoveredAgentDirectory?.children.filter(child => child.type === CustomizationType.Agent).length ?? 0; +- discoveredInstructionCount = discoveredInstructionDirectory?.children.filter(child => child.type === CustomizationType.Rule).length ?? 0; +- } +- +- assert.strictEqual(discoveredAgentCount, 3, 'expected agent burst changes to be discovered'); +- assert.strictEqual(discoveredInstructionCount, 2, 'expected instruction burst changes to be discovered'); +- assert.strictEqual( +- countDiscoveredRefreshPublishes(), +- publishCountBeforeBurst + 1, +- 'expected burst watcher changes across folders to result in exactly one discovered refresh publish (_onDidRefresh)' +- ); +- } finally { +- await disposeAgent(agent); +- } +- }); - }); - - suite('provisional sessions', () => { @@ -8904,6 +11643,58 @@ index 6bb478dbb36..00000000000 - } - }); - +- test('disposeSession removes the session from the SDK on-disk store', async () => { +- const sessionDataService = disposables.add(new TestSessionDataService()); +- const client = new TestCopilotClient([]); +- const agent = createTestAgent(disposables, { sessionDataService, copilotClient: client }); +- try { +- await agent.authenticate('https://api.github.com', 'token'); +- +- const session = AgentSession.uri('copilotcli', 'persisted-session-1'); +- await agent.disposeSession(session); +- +- assert.deepStrictEqual(client.deletedSessionIds, ['persisted-session-1']); +- } finally { +- await disposeAgent(agent); +- } +- }); +- +- test('disposeSession on provisional session does not call client.deleteSession', async () => { +- const sessionDataService = disposables.add(new TestSessionDataService()); +- const client = new TestCopilotClient([]); +- const agent = createTestAgent(disposables, { sessionDataService, copilotClient: client }); +- try { +- await agent.authenticate('https://api.github.com', 'token'); +- +- const result = await agent.createSession({ +- session: AgentSession.uri('copilotcli', 'prov-3'), +- workingDirectory: URI.file('/workspace'), +- }); +- +- await agent.disposeSession(result.session); +- +- assert.deepStrictEqual(client.deletedSessionIds, []); +- assert.strictEqual(agent.hasSession(result.session), false); +- } finally { +- await disposeAgent(agent); +- } +- }); +- +- test('disposeSession propagates SDK delete errors and preserves in-memory state', async () => { +- const sessionDataService = disposables.add(new TestSessionDataService()); +- const client = new TestCopilotClient([]); +- client.deleteSession = async () => { throw new Error('boom'); }; +- const agent = createTestAgent(disposables, { sessionDataService, copilotClient: client }); +- try { +- await agent.authenticate('https://api.github.com', 'token'); +- +- const session = AgentSession.uri('copilotcli', 'persisted-session-2'); +- await assert.rejects(() => agent.disposeSession(session), /boom/); +- } finally { +- await disposeAgent(agent); +- } +- }); +- - // Forking a provisional session is no longer a special case: the agent - // service drops `config.fork` for sources with no turns, so the call - // reduces to a plain new-session create. @@ -9048,43 +11839,296 @@ index 6bb478dbb36..00000000000 - const parentUri = AgentSession.uri('copilotcli', 'session-parent'); - const { calls } = installStubSession(agent, AgentSession.id(parentUri)); - -- const subagentUri = URI.parse(buildSubagentSessionUri(parentUri.toString(), 'tc-parent')); -- const result: ToolCallResult = { success: true, pastTenseMessage: 'subagent tool done' }; -- agent.onClientToolCallComplete(subagentUri, 'tc-inner', result); +- const subagentUri = URI.parse(buildSubagentSessionUri(parentUri.toString(), 'tc-parent')); +- const result: ToolCallResult = { success: true, pastTenseMessage: 'subagent tool done' }; +- agent.onClientToolCallComplete(subagentUri, 'tc-inner', result); +- +- assert.deepStrictEqual(calls, [{ toolCallId: 'tc-inner', result }]); +- } finally { +- await disposeAgent(agent); +- } +- }); +- +- test('routes a nested subagent session URI (depth > 1) to the root session entry', async () => { +- // Regression for depth > 1: a nested subagent URI like +- // `copilot:/root/subagent/tc1/subagent/tc2` must walk all the way +- // to the root session entry in `_sessions`, not stop at the +- // intermediate parent `copilot:/root/subagent/tc1`. +- const agent = createTestAgent(disposables); +- try { +- const rootUri = AgentSession.uri('copilotcli', 'session-root'); +- const { calls } = installStubSession(agent, AgentSession.id(rootUri)); +- +- const subagentUri = URI.parse(buildSubagentSessionUri(rootUri.toString(), 'tc-parent')); +- const nestedUri = URI.parse(buildSubagentSessionUri(subagentUri.toString(), 'tc-nested')); +- const result: ToolCallResult = { success: true, pastTenseMessage: 'nested done' }; +- agent.onClientToolCallComplete(nestedUri, 'tc-inner', result); +- +- assert.deepStrictEqual(calls, [{ toolCallId: 'tc-inner', result }]); +- } finally { +- await disposeAgent(agent); +- } +- }); +- +- test('is a no-op when no session entry exists for the resolved id', async () => { +- const agent = createTestAgent(disposables); +- try { +- const sessionUri = AgentSession.uri('copilotcli', 'session-missing'); +- // No stub installed — the call should be silently ignored. +- agent.onClientToolCallComplete(sessionUri, 'tc-x', { success: true, pastTenseMessage: 'noop' }); +- } finally { +- await disposeAgent(agent); +- } +- }); +- +- test('routes a peer chat URI to its chat-session entry', async () => { +- // Client-tool completions for tools running inside an additional +- // (non-default) chat are dispatched against the chat channel URI. +- // The agent must resolve that to the `_chatSessions` entry, which +- // is keyed by the chat URI string rather than a session id. +- const agent = createTestAgent(disposables); +- try { +- const sessionUri = AgentSession.uri('copilotcli', 'session-with-peer'); +- const chatUri = URI.parse(buildChatUri(sessionUri, 'peer-1')); +- const calls: { toolCallId: string; result: ToolCallResult }[] = []; +- const stub = { +- handleClientToolCallComplete(toolCallId: string, result: ToolCallResult) { calls.push({ toolCallId, result }); }, +- dispose() { }, +- }; +- (agent as unknown as { _chatSessions: Map })._chatSessions.set(chatUri.toString(), stub); +- +- const result: ToolCallResult = { success: true, pastTenseMessage: 'peer done' }; +- agent.onClientToolCallComplete(chatUri, 'tc-peer', result); +- +- assert.deepStrictEqual(calls, [{ toolCallId: 'tc-peer', result }]); +- } finally { +- await disposeAgent(agent); +- } +- }); +- }); +- +- suite('peer chat routing and lifecycle', () => { +- +- /** Installs a stub peer chat into `_chatSessions` keyed by the chat URI. */ +- function installStubChat(agent: CopilotAgent, chatUri: URI, options?: { permissionOwner?: string; inputOwner?: string }) { +- const events: string[] = []; +- let disposed = false; +- const stub = { +- respondToPermissionRequest(requestId: string, approved: boolean): boolean { +- if (options?.permissionOwner === requestId) { +- events.push(`perm:${requestId}:${approved}`); +- return true; +- } +- return false; +- }, +- respondToUserInputRequest(requestId: string, response: unknown): boolean { +- if (options?.inputOwner === requestId) { +- events.push(`input:${requestId}`); +- return true; +- } +- return false; +- }, +- handleClientToolCallComplete() { }, +- dispose() { disposed = true; }, +- }; +- (agent as unknown as { _chatSessions: Map })._chatSessions.set(chatUri.toString(), stub); +- return { events, isDisposed: () => disposed }; +- } +- +- test('respondToPermissionRequest routes to a peer chat session', async () => { +- const agent = createTestAgent(disposables); +- try { +- const sessionUri = AgentSession.uri('copilotcli', 'session-perm'); +- const chatUri = URI.parse(buildChatUri(sessionUri, 'peer-perm')); +- const chat = installStubChat(agent, chatUri, { permissionOwner: 'req-1' }); +- +- agent.respondToPermissionRequest('req-1', true); +- +- assert.deepStrictEqual(chat.events, ['perm:req-1:true']); +- } finally { +- await disposeAgent(agent); +- } +- }); +- +- test('respondToUserInputRequest routes to a peer chat session', async () => { +- const agent = createTestAgent(disposables); +- try { +- const sessionUri = AgentSession.uri('copilotcli', 'session-input'); +- const chatUri = URI.parse(buildChatUri(sessionUri, 'peer-input')); +- const chat = installStubChat(agent, chatUri, { inputOwner: 'req-2' }); +- +- agent.respondToUserInputRequest('req-2', 'submit' as never); +- +- assert.deepStrictEqual(chat.events, ['input:req-2']); +- } finally { +- await disposeAgent(agent); +- } +- }); +- +- test('disposeSession disposes the session\'s peer chats', async () => { +- const sessionDataService = disposables.add(new TestSessionDataService()); +- const client = new TestCopilotClient([]); +- const agent = createTestAgent(disposables, { sessionDataService, copilotClient: client }); +- try { +- await agent.authenticate('https://api.github.com', 'token'); +- +- const result = await agent.createSession({ +- session: AgentSession.uri('copilotcli', 'parent-with-peers'), +- workingDirectory: URI.file('/workspace'), +- }); +- const chatUri = URI.parse(buildChatUri(result.session, 'peer-x')); +- const chat = installStubChat(agent, chatUri); +- +- await agent.disposeSession(result.session); +- +- assert.strictEqual(chat.isDisposed(), true, 'peer chat should be disposed with its parent session'); +- const chatSessions = (agent as unknown as { _chatSessions: Map })._chatSessions; +- assert.strictEqual(chatSessions.has(chatUri.toString()), false, 'peer chat entry should be removed'); +- } finally { +- await disposeAgent(agent); +- } +- }); +- +- test('getChats returns the persisted peer chat catalog', async () => { +- const sessionDataService = disposables.add(new TestSessionDataService()); +- const agent = createTestAgent(disposables, { sessionDataService, copilotClient: new TestCopilotClient([]) }); +- try { +- const session = AgentSession.uri('copilotcli', 'session-getchats'); +- const db = sessionDataService.openDatabase(session); +- await db.object.setMetadata('copilot.chats', JSON.stringify({ +- 'peer-a': { sdkSessionId: 'sdk-a' }, +- 'peer-b': { sdkSessionId: 'sdk-b' }, +- })); +- +- const chats = await agent.getChats(session); +- +- assert.deepStrictEqual( +- chats.map(c => c.toString()).sort(), +- [buildChatUri(session, 'peer-a'), buildChatUri(session, 'peer-b')].sort(), +- ); +- } finally { +- await disposeAgent(agent); +- } +- }); +- +- test('getChats drops corrupted or invalid persisted entries', async () => { +- const sessionDataService = disposables.add(new TestSessionDataService()); +- const agent = createTestAgent(disposables, { sessionDataService, copilotClient: new TestCopilotClient([]) }); +- try { +- const session = AgentSession.uri('copilotcli', 'session-getchats-invalid'); +- const db = sessionDataService.openDatabase(session); +- await db.object.setMetadata('copilot.chats', JSON.stringify({ +- 'peer-ok': { sdkSessionId: 'sdk-ok' }, +- 'peer-null': null, +- 'peer-missing-id': { model: { id: 'm' } }, +- 'peer-nonstring-id': { sdkSessionId: 42 }, +- 'peer-empty-id': { sdkSessionId: '' }, +- })); +- +- const chats = await agent.getChats(session); +- +- assert.deepStrictEqual( +- chats.map(c => c.toString()), +- [buildChatUri(session, 'peer-ok')], +- ); +- } finally { +- await disposeAgent(agent); +- } +- }); +- +- test('disposeChat removes the persisted entry and deletes its SDK conversation', async () => { +- const sessionDataService = disposables.add(new TestSessionDataService()); +- const client = new TestCopilotClient([]); +- const agent = createTestAgent(disposables, { sessionDataService, copilotClient: client }); +- try { +- await agent.authenticate('https://api.github.com', 'token'); +- const session = AgentSession.uri('copilotcli', 'session-dispose-chat'); +- const db = sessionDataService.openDatabase(session); +- await db.object.setMetadata('copilot.chats', JSON.stringify({ +- 'peer-a': { sdkSessionId: 'sdk-a' }, +- })); +- const chatUri = URI.parse(buildChatUri(session, 'peer-a')); +- +- await agent.disposeChat(session, chatUri); - -- assert.deepStrictEqual(calls, [{ toolCallId: 'tc-inner', result }]); +- const remaining = await db.object.getMetadata('copilot.chats'); +- assert.deepStrictEqual({ +- remaining: remaining ? JSON.parse(remaining) : {}, +- deleted: client.deletedSessionIds, +- }, { +- remaining: {}, +- deleted: ['sdk-a'], +- }); - } finally { - await disposeAgent(agent); - } - }); +- }); - -- test('routes a nested subagent session URI (depth > 1) to the root session entry', async () => { -- // Regression for depth > 1: a nested subagent URI like -- // `copilot:/root/subagent/tc1/subagent/tc2` must walk all the way -- // to the root session entry in `_sessions`, not stop at the -- // intermediate parent `copilot:/root/subagent/tc1`. -- const agent = createTestAgent(disposables); -- try { -- const rootUri = AgentSession.uri('copilotcli', 'session-root'); -- const { calls } = installStubSession(agent, AgentSession.id(rootUri)); +- // Regression for the #319516 incident: a window reload reconnects with a +- // NEW clientId but an identical tool list. The cached SDK session's +- // staleness check (`ActiveClient.requiresRestart`) must NOT treat a +- // clientId-only change as a config change — otherwise either the session +- // is needlessly restarted, or (the actual bug) the cached session is +- // reused while the live clientId is never updated, so subsequent client +- // tool calls are stamped with the dead window's id and hang forever. +- suite('client tool refresh on reload (#319516)', () => { +- /** Minimal structural view of the agent's private per-session ActiveClient. */ +- type TestActiveClient = { +- readonly state: { readonly clientId: string | undefined }; +- snapshot(): Promise; +- requiresRestart(snap: IActiveClientSnapshot): Promise; +- }; - -- const subagentUri = URI.parse(buildSubagentSessionUri(rootUri.toString(), 'tc-parent')); -- const nestedUri = URI.parse(buildSubagentSessionUri(subagentUri.toString(), 'tc-nested')); -- const result: ToolCallResult = { success: true, pastTenseMessage: 'nested done' }; -- agent.onClientToolCallComplete(nestedUri, 'tc-inner', result); +- function getActiveClient(agent: CopilotAgent, session: URI): TestActiveClient { +- const activeClients = (agent as unknown as { _activeClients: { get(s: URI): TestActiveClient | undefined } })._activeClients; +- const activeClient = activeClients.get(session); +- assert.ok(activeClient, 'expected an ActiveClient to exist after setClientTools'); +- return activeClient; +- } - -- assert.deepStrictEqual(calls, [{ toolCallId: 'tc-inner', result }]); +- const tools: ToolDefinition[] = [{ name: 'my_tool', description: 'A test tool', inputSchema: { type: 'object', properties: {} } }]; +- +- test('clientId-only change (reload) does NOT require a restart and updates the live clientId', async () => { +- const agent = createTestAgent(disposables); +- try { +- const session = AgentSession.uri('copilotcli', 'reload-session'); +- +- // Window A registers its tools; this is the snapshot the SDK +- // session would be created with. +- agent.setClientTools(session, 'client-A', tools); +- const activeClient = getActiveClient(agent, session); +- const appliedSnapshot = await activeClient.snapshot(); +- assert.strictEqual(activeClient.state.clientId, 'client-A'); +- +- // Window A reloads: window B reconnects with a new clientId but +- // the identical tool list. +- agent.setClientTools(session, 'client-B', [...tools]); +- +- // Root-cause assertions: the cached SDK session must be reused +- // (no restart) AND the live clientId must now be window B's, so +- // the next client tool call is stamped with a live owner. +- assert.strictEqual(await activeClient.requiresRestart(appliedSnapshot), false); +- assert.strictEqual(activeClient.state.clientId, 'client-B'); - } finally { - await disposeAgent(agent); - } - }); - -- test('is a no-op when no session entry exists for the resolved id', async () => { +- test('a structural tool change still requires a restart', async () => { - const agent = createTestAgent(disposables); - try { -- const sessionUri = AgentSession.uri('copilotcli', 'session-missing'); -- // No stub installed — the call should be silently ignored. -- agent.onClientToolCallComplete(sessionUri, 'tc-x', { success: true, pastTenseMessage: 'noop' }); +- const session = AgentSession.uri('copilotcli', 'tools-change-session'); +- +- agent.setClientTools(session, 'client-A', tools); +- const activeClient = getActiveClient(agent, session); +- const appliedSnapshot = await activeClient.snapshot(); +- +- // A genuinely different tool set (added tool) must restart so the +- // SDK session is rebuilt with the new tools. +- agent.setClientTools(session, 'client-A', [...tools, { name: 'second_tool', description: 'another', inputSchema: { type: 'object', properties: {} } }]); +- +- assert.strictEqual(await activeClient.requiresRestart(appliedSnapshot), true); - } finally { - await disposeAgent(agent); - } @@ -9312,11 +12356,14 @@ index 6bb478dbb36..00000000000 - const gitService = new TestAgentHostGitService(); - gitService.repositoryRoot = repositoryRoot; - +- const copilotApiService = new TestCopilotApiService(); +- copilotApiService.response = 'add-feature'; - const sessionDataService = disposables.add(new TestSessionDataService()); - const agent = createTestAgent(disposables, { - sessionDataService, - copilotClient: new TestCopilotClient([]), - gitService, +- copilotApiService, - }) as TestableCopilotAgent; - - const fakeMessages: Turn[] = [ @@ -9357,8 +12404,7 @@ index 6bb478dbb36..00000000000 - // before constructing the SDK session. Verifies that the - // real production code path persists branch metadata and - // queues the live announcement. -- const branchHint = 'add-feature'; -- const expectedBranchName = getCopilotWorktreeBranchName(sessionId, branchHint); +- const expectedBranchName = `agents/add-feature-${sessionId.substring(0, 8)}`; - const workingDir = await agent.resolveWorktreeForTest({ - workingDirectory: repositoryRoot, - config: { isolation: 'worktree', branch: 'main' }, @@ -9381,13 +12427,13 @@ index 6bb478dbb36..00000000000 - - const markdownSignals = signals.filter((s): s is IAgentActionSignal => - s.kind === 'action' && ( -- (s.action.type === ActionType.SessionResponsePart && s.action.part.kind === ResponsePartKind.Markdown) || -- s.action.type === ActionType.SessionDelta +- (s.action.type === ActionType.ChatResponsePart && s.action.part.kind === ResponsePartKind.Markdown) || +- s.action.type === ActionType.ChatDelta - ) - ); - assert.strictEqual(markdownSignals.length, 1, 'exactly one markdown announcement signal should be emitted for the worktree announcement'); - const announcement = markdownSignals[0]; -- const announcementContent = announcement.action.type === ActionType.SessionResponsePart +- const announcementContent = announcement.action.type === ActionType.ChatResponsePart - ? (announcement.action.part as MarkdownResponsePart).content - : (announcement.action as IDeltaAction).content; - assert.ok(announcementContent.includes(expectedBranchName), `announcement should contain branch name '${expectedBranchName}', got '${announcementContent}'`); @@ -9397,8 +12443,8 @@ index 6bb478dbb36..00000000000 - await agent.sendMessage(session, 'follow-up'); - const reemittedMarkdown = signals.filter(s => - s.kind === 'action' && ( -- (s.action.type === ActionType.SessionResponsePart && s.action.part.kind === ResponsePartKind.Markdown) || -- s.action.type === ActionType.SessionDelta +- (s.action.type === ActionType.ChatResponsePart && s.action.part.kind === ResponsePartKind.Markdown) || +- s.action.type === ActionType.ChatDelta - ) - ); - assert.strictEqual(reemittedMarkdown.length, 0, 'announcement must not be re-emitted on subsequent sends'); @@ -9455,8 +12501,8 @@ index 6bb478dbb36..00000000000 - await agent.sendMessage(session, 'hello'); - const markdownSignals = signals.filter(s => - s.kind === 'action' && ( -- (s.action.type === ActionType.SessionResponsePart && s.action.part.kind === ResponsePartKind.Markdown) || -- s.action.type === ActionType.SessionDelta +- (s.action.type === ActionType.ChatResponsePart && s.action.part.kind === ResponsePartKind.Markdown) || +- s.action.type === ActionType.ChatDelta - ) - ); - assert.deepStrictEqual(markdownSignals, [], 'no announcement should be emitted live'); @@ -9636,10 +12682,10 @@ index 6bb478dbb36..00000000000 -}); diff --git a/src/vs/platform/agentHost/test/node/copilotAgentSession.test.ts b/src/vs/platform/agentHost/test/node/copilotAgentSession.test.ts deleted file mode 100644 -index 3ba908cae5d..00000000000 +index 7cff7183..00000000 --- a/src/vs/platform/agentHost/test/node/copilotAgentSession.test.ts +++ /dev/null -@@ -1,3076 +0,0 @@ +@@ -1,3982 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. @@ -9647,7 +12693,7 @@ index 3ba908cae5d..00000000000 - -import type { CopilotSession, SessionEvent, SessionEventPayload, SessionEventType, Tool, ToolResultObject, TypedSessionEventHandler } from '@github/copilot-sdk'; -import assert from 'assert'; --import { DeferredPromise } from '../../../../base/common/async.js'; +-import { DeferredPromise, timeout } from '../../../../base/common/async.js'; -import { encodeBase64, VSBuffer } from '../../../../base/common/buffer.js'; -import { Emitter } from '../../../../base/common/event.js'; -import { DisposableStore } from '../../../../base/common/lifecycle.js'; @@ -9663,17 +12709,20 @@ index 3ba908cae5d..00000000000 -import { ITelemetryService, TelemetryLevel } from '../../../telemetry/common/telemetry.js'; -import { NullTelemetryServiceShape } from '../../../telemetry/common/telemetryUtils.js'; -import { AgentSession, type AgentSignal, type IAgentActionSignal, type IAgentToolPendingConfirmationSignal } from '../../common/agentService.js'; +-import { AgentFeedbackAttachmentDisplayKind } from '../../common/meta/agentFeedbackAttachments.js'; -import { IDiffComputeService } from '../../common/diffComputeService.js'; -import { ISessionDataService } from '../../common/sessionDataService.js'; --import { ActionType, type SessionDeltaAction, type SessionErrorAction, type SessionInputRequestedAction, type SessionResponsePartAction, type SessionToolCallCompleteAction, type SessionToolCallReadyAction, type SessionToolCallStartAction } from '../../common/state/sessionActions.js'; --import { MessageAttachmentKind, MessageKind, ResponsePartKind, SessionInputAnswerState, SessionInputAnswerValueKind, SessionInputQuestionKind, SessionInputResponseKind, ToolCallContributorKind, ToolCallStatus, ToolResultContentType, type ToolResultFileEditContent } from '../../common/state/sessionState.js'; +-import { ActionType, type ChatDeltaAction, type ChatErrorAction, type ChatInputRequestedAction, type ChatResponsePartAction, type ChatToolCallCompleteAction, type ChatToolCallReadyAction, type ChatToolCallStartAction, type ChatTurnCompleteAction } from '../../common/state/sessionActions.js'; +-import { MessageAttachmentKind, MessageKind, ResponsePartKind, ChatInputAnswerState, ChatInputAnswerValueKind, ChatInputQuestionKind, ChatInputResponseKind, ToolCallContributorKind, ToolCallStatus, ToolResultContentType, type ToolDefinition, type ToolResultContent, type ToolResultFileEditContent } from '../../common/state/sessionState.js'; -import { CopilotAgentSession } from '../../node/copilot/copilotAgentSession.js'; +-import { ActiveClientState } from '../../node/activeClientState.js'; -import { type CopilotSessionLaunchPlan, type IActiveClientSnapshot, type ICopilotSessionLauncher, type ICopilotSessionRuntime } from '../../node/copilot/copilotSessionLauncher.js'; -import { CopilotSessionWrapper } from '../../node/copilot/copilotSessionWrapper.js'; -import { buildCopilotSystemNotification } from '../../node/copilot/copilotSystemNotification.js'; -import { IAgentConfigurationService } from '../../node/agentConfigurationService.js'; -import { SessionConfigKey } from '../../common/sessionConfigKeys.js'; -import { createSessionDataService, createZeroDiffComputeService } from '../common/sessionTestHelpers.js'; +-import { IAgentServerToolHost } from '../../common/agentServerTools.js'; - -// ---- Mock CopilotSession (SDK level) ---------------------------------------- - @@ -9686,6 +12735,13 @@ index 3ba908cae5d..00000000000 - readonly sessionId = 'test-session-1'; - readonly sendRequests: unknown[] = []; - readonly modeSetCalls: Array<{ mode: 'interactive' | 'plan' | 'autopilot' }> = []; +- readonly compactCalls: unknown[] = []; +- readonly commandListCalls: unknown[] = []; +- readonly commandInvokeCalls: Array<{ name: string; input?: string }> = []; +- compactResult: { success: boolean; tokensRemoved: number; messagesRemoved: number } = { success: true, tokensRemoved: 0, messagesRemoved: 0 }; +- compactError: unknown = undefined; +- commandListResult: { commands: Array<{ name: string; kind: 'builtin' | 'skill' | 'client'; description: string; allowDuringAgentExecution: boolean }> } = { commands: [] }; +- commandInvokeResult: { kind: 'text'; text: string; markdown?: boolean } | { kind: 'completed'; message?: string } | { kind: 'agent-prompt'; prompt: string; displayPrompt: string; mode?: 'interactive' | 'plan' | 'autopilot' } = { kind: 'text', text: '' }; - messages: SessionEvent[] = []; - - private readonly _handlers = new Map void>>(); @@ -9734,7 +12790,39 @@ index 3ba908cae5d..00000000000 - update: async (_params: { content: string }) => { /* no-op */ }, - delete: async () => { /* no-op */ }, - }, +- history: { +- compact: async (params?: unknown) => { +- this.compactCalls.push(params ?? null); +- if (this.compactError !== undefined) { +- throw this.compactError; +- } +- return this.compactResult; +- }, +- }, +- commands: { +- list: async (params?: unknown) => { +- this.commandListCalls.push(params ?? null); +- return this.commandListResult; +- }, +- invoke: async (params: { name: string; input?: string }) => { +- this.commandInvokeCalls.push(params); +- return this.commandInvokeResult; +- }, +- }, +- mcp: { +- list: async () => { +- if (this.mcpListError !== undefined) { +- throw this.mcpListError; +- } +- return this.mcpListResult; +- }, +- executeSampling: async () => ({ status: 'completed' as const, result: undefined }), +- cancelSamplingExecution: async () => { /* no-op */ }, +- }, - }; +- +- mcpListResult: { servers: ReadonlyArray<{ name: string; status: 'connected' | 'failed' | 'pending'; error?: string }> } = { servers: [] }; +- mcpListError: unknown = undefined; -} - -class CapturingLogService extends NullLogService { @@ -9800,12 +12888,11 @@ index 3ba908cae5d..00000000000 - _editTracker: { - trackEditStart(path: string): Promise; - completeEdit(path: string): Promise; -- takeCompletedEdit(turnId: string, toolCallId: string, path: string): Promise; +- takeCompletedEdit(turnId: string, toolCallId: string, path: string, toolName: string, toolInput: unknown, modelId: string | undefined): Promise; - }; - _pendingClientToolCalls: { -- get(toolCallId: string): DeferredPromise | undefined; -- set(toolCallId: string, value: DeferredPromise): Map>; -- delete(toolCallId: string): boolean; +- register(toolCallId: string): Promise; +- respondOrBuffer(toolCallId: string, value: ToolResultObject): void; - }; -}; - @@ -9819,15 +12906,16 @@ index 3ba908cae5d..00000000000 - .map(s => s.action); -} - --function getInputRequest(signal: AgentSignal): SessionInputRequestedAction['request'] { +-function getInputRequest(signal: AgentSignal): ChatInputRequestedAction['request'] { - assert.strictEqual(signal.kind, 'action'); - if (signal.kind !== 'action') { throw new Error('unreachable'); } -- assert.strictEqual(signal.action.type, ActionType.SessionInputRequested); -- return (signal.action as SessionInputRequestedAction).request; +- assert.strictEqual(signal.action.type, ActionType.ChatInputRequested); +- return (signal.action as ChatInputRequestedAction).request; -} - -async function createAgentSession(disposables: DisposableStore, options?: { - clientSnapshot?: IActiveClientSnapshot; +- activeClientState?: ActiveClientState; - environmentServiceRegistration?: 'native' | 'none'; - logService?: ILogService; - telemetryService?: ITelemetryService; @@ -9837,6 +12925,10 @@ index 3ba908cae5d..00000000000 - configValues?: Record; - fileContents?: Record; - fileReadErrors?: readonly string[]; +- /** Configure the mock session before {@link CopilotAgentSession.initializeSession} runs. */ +- configureMockSession?: (session: MockCopilotSession) => void; +- /** Optional server-tool host wired into the session. */ +- serverToolHost?: IAgentServerToolHost; -}): Promise<{ - session: CopilotAgentSession; - runtime: ICopilotSessionRuntime; @@ -9872,6 +12964,7 @@ index 3ba908cae5d..00000000000 - - const sessionUri = AgentSession.uri('copilot', 'test-session-1'); - const mockSession = new MockCopilotSession(); +- options?.configureMockSession?.(mockSession); - - const launchPlan: CopilotSessionLaunchPlan = { - kind: 'create', @@ -9879,10 +12972,11 @@ index 3ba908cae5d..00000000000 - createSession: async () => mockSession as unknown as CopilotSession, - resumeSession: async () => mockSession as unknown as CopilotSession, - }, +- activeClientState: new ActiveClientState(), - sessionId: 'test-session-1', - workingDirectory: options?.workingDirectory, - resolvedAgentName: undefined, -- snapshot: options?.clientSnapshot ?? { clientId: '', tools: [], plugins: [] }, +- snapshot: options?.clientSnapshot ?? { tools: [], plugins: [], mcpServers: {} }, - shellManager: undefined, - githubToken: undefined, - model: undefined, @@ -9928,6 +13022,7 @@ index 3ba908cae5d..00000000000 - getRootValue: () => undefined, - updateRootConfig: () => { /* no-op */ }, - persistRootConfig: () => { /* no-op */ }, +- whenIdle: async () => { /* no-op */ }, - }; - services.set(IAgentConfigurationService, fakeConfigurationService); - const environmentService = { @@ -9950,7 +13045,10 @@ index 3ba908cae5d..00000000000 - launchPlan, - shellManager: undefined, - clientSnapshot: options?.clientSnapshot, +- activeClientState: options?.activeClientState, +- resolveMcpChildId: () => undefined, - workingDirectory: options?.workingDirectory, +- serverToolHost: options?.serverToolHost, - }, - )); - @@ -10012,6 +13110,104 @@ index 3ba908cae5d..00000000000 - }]); - }); - +- test('maps symbol Resource attachments to SDK selection so the range survives (#315193)', async () => { +- // Symbols arrive as a Resource with displayKind 'symbol' AND a populated selection.range. Keying the selection +- // branch off the `selection` field (not displayKind === 'selection') keeps the range instead of degrading the +- // symbol to a plain file reference. +- const symbolUri = URI.file('/workspace/sym.ts'); +- const { session, mockSession } = await createAgentSession(disposables, { +- fileContents: { +- [symbolUri.toString()]: 'line0\nline1\nfunction foo() {}\nline3', +- }, +- }); +- +- await session.send('explain this', [ +- { +- type: MessageAttachmentKind.Resource, +- uri: symbolUri.toString(), +- label: 'foo', +- displayKind: 'symbol', +- selection: { +- range: { +- start: { line: 2, character: 9 }, +- end: { line: 2, character: 12 }, +- }, +- }, +- }, +- ]); +- +- assert.deepStrictEqual(mockSession.sendRequests, [{ +- prompt: 'explain this', +- attachments: [ +- { +- type: 'selection', +- filePath: symbolUri.fsPath, +- displayName: 'foo', +- text: 'foo', +- selection: { +- start: { line: 2, character: 9 }, +- end: { line: 2, character: 12 }, +- }, +- }, +- ], +- }]); +- }); +- +- test('falls back to file reference when reading a symbol Resource attachment fails', async () => { +- const symbolUri = URI.file('/workspace/missing.ts'); +- const { session, mockSession } = await createAgentSession(disposables, { +- fileReadErrors: [symbolUri.toString()], +- }); +- +- await session.send('explain this', [ +- { +- type: MessageAttachmentKind.Resource, +- uri: symbolUri.toString(), +- label: 'foo', +- displayKind: 'symbol', +- selection: { +- range: { +- start: { line: 2, character: 9 }, +- end: { line: 2, character: 12 }, +- }, +- }, +- }, +- ]); +- +- assert.deepStrictEqual(mockSession.sendRequests, [{ +- prompt: 'explain this', +- attachments: [ +- { type: 'file', path: symbolUri.fsPath, displayName: 'foo' }, +- ], +- }]); +- }); +- +- test('sends agent feedback annotations attachments as text blobs', async () => { +- const { session, mockSession } = await createAgentSession(disposables); +- +- await session.send('/act-on-feedback', [{ +- type: MessageAttachmentKind.Annotations, +- label: '1 comment', +- displayKind: AgentFeedbackAttachmentDisplayKind, +- resource: 'ahp-session:/s/annotations', +- annotationIds: ['feedback-1'], +- }]); +- +- const expectedText = +- 'The user attached specific feedback comments to act on (comment ids):\n' + +- '- feedback-1\n\n' + +- 'Use the `listComments` tool to read their content and focus on these comments.'; +- assert.deepStrictEqual(mockSession.sendRequests, [{ +- prompt: '/act-on-feedback', +- attachments: [{ +- type: 'blob', +- data: encodeBase64(VSBuffer.fromString(expectedText)), +- mimeType: 'text/plain', +- displayName: '1 comment', +- }], +- }]); +- }); +- - test('sends simple attachments as text blobs and restores them from SDK blobs', async () => { - const { session, mockSession } = await createAgentSession(disposables); - @@ -10066,6 +13262,283 @@ index 3ba908cae5d..00000000000 - }]); - }); - +- test('sends paste simple attachments as text blobs', async () => { +- const { session, mockSession } = await createAgentSession(disposables); +- +- await session.send('continue', [{ +- type: MessageAttachmentKind.Simple, +- label: 'Previous conversation', +- displayKind: 'paste', +- modelRepresentation: 'Transcript text', +- }]); +- +- assert.deepStrictEqual(mockSession.sendRequests, [{ +- prompt: 'continue', +- attachments: [{ +- type: 'blob', +- data: encodeBase64(VSBuffer.fromString('Transcript text')), +- mimeType: 'text/plain', +- displayName: 'Previous conversation', +- }], +- }]); +- +- mockSession.messages = [{ +- type: 'user.message', +- id: 'event-1', +- parentId: null, +- timestamp: new Date().toISOString(), +- data: { +- interactionId: 'message-1', +- content: 'continue', +- attachments: [{ +- type: 'blob', +- data: encodeBase64(VSBuffer.fromString('Transcript text')), +- mimeType: 'text/plain', +- displayName: 'Previous conversation', +- }], +- }, +- }]; +- +- assert.deepStrictEqual((await session.getMessages())[0].message.attachments, [{ +- type: MessageAttachmentKind.Simple, +- label: 'Previous conversation', +- modelRepresentation: 'Transcript text', +- }]); +- }); +- +- test('`/compact` runs the history compact RPC and completes the turn with output', async () => { +- const { session, mockSession, signals } = await createAgentSession(disposables); +- +- await session.send('/compact', undefined, 'turn-compact'); +- +- // The compact command is handled inline via the history RPC and must +- // not fall through to a normal SDK `send()` turn. +- assert.strictEqual(mockSession.compactCalls.length, 1); +- assert.deepStrictEqual(mockSession.sendRequests, []); +- +- // The turn opened by the server is closed inline (the SDK never fires +- // `onIdle` for the compact path) after emitting the completion message. +- const actions = getActions(signals); +- const responseParts = actions.filter((a): a is ChatResponsePartAction => a.type === ActionType.ChatResponsePart); +- assert.strictEqual(responseParts.length, 1); +- const responsePart = responseParts[0]; +- assert.strictEqual(responsePart.part.kind, ResponsePartKind.Markdown); +- if (responsePart.part.kind !== ResponsePartKind.Markdown) { +- throw new Error('unreachable'); +- } +- assert.deepStrictEqual({ +- turnId: responsePart.turnId, +- kind: responsePart.part.kind, +- content: responsePart.part.content, +- }, { +- turnId: 'turn-compact', +- kind: ResponsePartKind.Markdown, +- content: 'Compaction completed', +- }); +- const turnComplete = actions.find(a => a.type === ActionType.ChatTurnComplete); +- assert.ok(turnComplete, 'expected the turn to complete'); +- assert.strictEqual((turnComplete as ChatTurnCompleteAction).turnId, 'turn-compact'); +- }); +- +- test('`/compact` completes the turn even when compaction reports failure', async () => { +- const { session, mockSession, signals } = await createAgentSession(disposables); +- mockSession.compactResult = { success: false, tokensRemoved: 0, messagesRemoved: 0 }; +- +- await session.send('/compact', undefined, 'turn-compact'); +- +- assert.strictEqual(mockSession.compactCalls.length, 1); +- assert.deepStrictEqual(mockSession.sendRequests, []); +- const turnComplete = getActions(signals).find(a => a.type === ActionType.ChatTurnComplete); +- assert.ok(turnComplete, 'expected the turn to complete on a failed compaction'); +- }); +- +- test('`/compact` treats nothing-to-compact errors as completed', async () => { +- const logService = new CapturingLogService(); +- const { session, mockSession, signals } = await createAgentSession(disposables, { logService }); +- mockSession.compactError = new Error('NOTHING TO COMPACT for this conversation'); +- +- await session.send('/compact', undefined, 'turn-compact'); +- +- const actions = getActions(signals); +- assert.deepStrictEqual({ +- compactCalls: mockSession.compactCalls.length, +- sendRequests: mockSession.sendRequests, +- errors: logService.errors, +- responseParts: actions +- .filter(a => a.type === ActionType.ChatResponsePart) +- .map(a => { +- const part = (a as ChatResponsePartAction).part; +- return part.kind === ResponsePartKind.Markdown ? { turnId: a.turnId, kind: part.kind, content: part.content } : { turnId: a.turnId, kind: part.kind }; +- }), +- turnComplete: actions +- .filter(a => a.type === ActionType.ChatTurnComplete) +- .map(a => (a as ChatTurnCompleteAction).turnId), +- }, { +- compactCalls: 1, +- sendRequests: [], +- errors: [], +- responseParts: [{ turnId: 'turn-compact', kind: ResponsePartKind.Markdown, content: 'Compaction completed' }], +- turnComplete: ['turn-compact'], +- }); +- }); +- +- test('`/env` runs the runtime command when listed and emits markdown output', async () => { +- const { session, mockSession, signals } = await createAgentSession(disposables); +- mockSession.commandListResult = { +- commands: [{ +- name: 'env', +- kind: 'builtin', +- description: 'Show loaded environment details', +- allowDuringAgentExecution: true, +- }], +- }; +- mockSession.commandInvokeResult = { kind: 'text', text: '## Environment\n\nLoaded.', markdown: true }; +- +- await session.send('/env', undefined, 'turn-env'); +- +- const actions = getActions(signals); +- assert.deepStrictEqual({ +- commandListCalls: mockSession.commandListCalls, +- commandInvokeCalls: mockSession.commandInvokeCalls, +- sendRequests: mockSession.sendRequests, +- responseParts: actions +- .filter(a => a.type === ActionType.ChatResponsePart) +- .map(a => { +- const part = (a as ChatResponsePartAction).part; +- return part.kind === ResponsePartKind.Markdown ? { kind: part.kind, content: part.content } : { kind: part.kind }; +- }), +- turnComplete: actions +- .filter(a => a.type === ActionType.ChatTurnComplete) +- .map(a => (a as ChatTurnCompleteAction).turnId), +- }, { +- commandListCalls: [{ includeBuiltins: true, includeSkills: false, includeClientCommands: false }], +- commandInvokeCalls: [{ name: 'env' }], +- sendRequests: [], +- responseParts: [{ kind: ResponsePartKind.Markdown, content: '## Environment\n\nLoaded.' }], +- turnComplete: ['turn-env'], +- }); +- }); +- +- test('`/env` escapes plain text runtime command output before emitting markdown', async () => { +- const { session, mockSession, signals } = await createAgentSession(disposables); +- mockSession.commandListResult = { +- commands: [{ +- name: 'env', +- kind: 'builtin', +- description: 'Show loaded environment details', +- allowDuringAgentExecution: true, +- }], +- }; +- mockSession.commandInvokeResult = { kind: 'text', text: '*plain*\n- item', markdown: false }; +- +- await session.send('/env', undefined, 'turn-env'); +- +- const responsePart = getActions(signals).find(a => a.type === ActionType.ChatResponsePart) as ChatResponsePartAction | undefined; +- assert.strictEqual(responsePart?.part.kind, ResponsePartKind.Markdown); +- if (responsePart?.part.kind === ResponsePartKind.Markdown) { +- assert.strictEqual(responsePart.part.content, '\\*plain\\*\n\\- item'); +- } +- }); +- +- test('`/env` falls through to a normal SDK send when not listed', async () => { +- const { session, mockSession, signals } = await createAgentSession(disposables); +- mockSession.commandListResult = { commands: [] }; +- +- await session.send('/env', undefined, 'turn-env'); +- +- assert.deepStrictEqual({ +- commandListCalls: mockSession.commandListCalls, +- commandInvokeCalls: mockSession.commandInvokeCalls, +- sendRequests: mockSession.sendRequests, +- responseParts: getActions(signals).filter(a => a.type === ActionType.ChatResponsePart), +- turnComplete: getActions(signals).filter(a => a.type === ActionType.ChatTurnComplete), +- }, { +- commandListCalls: [{ includeBuiltins: true, includeSkills: false, includeClientCommands: false }], +- commandInvokeCalls: [], +- sendRequests: [{ prompt: '/env', attachments: undefined }], +- responseParts: [], +- turnComplete: [], +- }); +- }); +- +- test('caches runtime slash command availability across checks', async () => { +- const { session, mockSession } = await createAgentSession(disposables); +- mockSession.commandListResult = { +- commands: [ +- { +- name: 'env', +- kind: 'builtin', +- description: 'Show loaded environment details', +- allowDuringAgentExecution: true, +- }, +- { +- name: 'review', +- kind: 'builtin', +- description: 'Run code review agent to analyze changes', +- allowDuringAgentExecution: false, +- }, +- { +- name: 'not-a-builtin', +- kind: 'skill', +- description: 'Skill command', +- allowDuringAgentExecution: false, +- }, +- ], +- }; +- +- assert.deepStrictEqual({ +- env: await session.hasRuntimeSlashCommand('env'), +- review: await session.hasRuntimeSlashCommand('review'), +- skill: await session.hasRuntimeSlashCommand('not-a-builtin'), +- commandListCalls: mockSession.commandListCalls, +- }, { +- env: true, +- review: true, +- skill: false, +- commandListCalls: [{ includeBuiltins: true, includeSkills: false, includeClientCommands: false }], +- }); +- }); +- +- test('`/review` forwards as a prompt-invoked command', async () => { +- const { session, mockSession, signals } = await createAgentSession(disposables); +- +- await session.send('/review focus on tests', undefined, 'turn-review'); +- +- assert.deepStrictEqual({ +- commandListCalls: mockSession.commandListCalls, +- commandInvokeCalls: mockSession.commandInvokeCalls, +- sendRequests: mockSession.sendRequests, +- responseParts: getActions(signals).filter(a => a.type === ActionType.ChatResponsePart), +- turnComplete: getActions(signals).filter(a => a.type === ActionType.ChatTurnComplete), +- }, { +- commandListCalls: [], +- commandInvokeCalls: [], +- sendRequests: [{ prompt: '/review focus on tests', attachments: undefined }], +- responseParts: [], +- turnComplete: [], +- }); +- }); +- +- test('`/security-review` forwards as a prompt-invoked command', async () => { +- const { session, mockSession, signals } = await createAgentSession(disposables); +- +- await session.send('/security-review', undefined, 'turn-security-review'); +- +- assert.deepStrictEqual({ +- commandListCalls: mockSession.commandListCalls, +- commandInvokeCalls: mockSession.commandInvokeCalls, +- sendRequests: mockSession.sendRequests, +- responseParts: getActions(signals).filter(a => a.type === ActionType.ChatResponsePart), +- turnComplete: getActions(signals).filter(a => a.type === ActionType.ChatTurnComplete), +- }, { +- commandListCalls: [], +- commandInvokeCalls: [], +- sendRequests: [{ prompt: '/security-review', attachments: undefined }], +- responseParts: [], +- turnComplete: [], +- }); +- }); +- - test('emits accumulated Copilot usage metadata', async () => { - const { session, mockSession, signals } = await createAgentSession(disposables); - @@ -10076,18 +13549,21 @@ index 3ba908cae5d..00000000000 - outputTokens: 20, - cacheReadTokens: 5, - cost: 2, -- }); +- // `copilotUsage` is marked `asInternal` in the SDK schema so it is not on the public type, but is present at runtime. +- copilotUsage: { totalNanoAiu: 500_000_000, tokenDetails: [] }, +- } as unknown as SessionEventPayload<'assistant.usage'>['data']); - mockSession.fire('assistant.usage', { - model: 'claude-sonnet-4.6', - inputTokens: 30, - outputTokens: 40, - cost: 2, -- }); +- copilotUsage: { totalNanoAiu: 750_000_000, tokenDetails: [] }, +- } as unknown as SessionEventPayload<'assistant.usage'>['data']); - - const usageActions = signals - .filter((s): s is IAgentActionSignal => s.kind === 'action') - .map(s => s.action) -- .filter(a => a.type === ActionType.SessionUsage); +- .filter(a => a.type === ActionType.ChatUsage); - - assert.deepStrictEqual(usageActions.map(a => a.usage), [ - { @@ -10097,6 +13573,7 @@ index 3ba908cae5d..00000000000 - cacheReadTokens: 5, - _meta: { - cost: 2, +- copilotUsage: { totalNanoAiu: 500_000_000, tokenDetails: [] }, - }, - }, - { @@ -10106,6 +13583,50 @@ index 3ba908cae5d..00000000000 - cacheReadTokens: undefined, - _meta: { - cost: 2, +- copilotUsage: { totalNanoAiu: 1_250_000_000, tokenDetails: [] }, +- }, +- }, +- ]); +- }); +- +- test('forwards account quota snapshots on usage metadata', async () => { +- const { session, mockSession, signals } = await createAgentSession(disposables); +- +- session.resetTurnState('turn-quota'); +- mockSession.fire('assistant.usage', { +- model: 'claude-sonnet-4.6', +- inputTokens: 10, +- outputTokens: 20, +- // `quotaSnapshots` is marked `asInternal` in the SDK schema so it is not on the public type, but is present at runtime. +- quotaSnapshots: { +- premium_interactions: { +- isUnlimitedEntitlement: false, +- entitlementRequests: 300, +- usedRequests: 75, +- usageAllowedWithExhaustedQuota: true, +- remainingPercentage: 75, +- overage: 1.5, +- overageAllowedWithExhaustedQuota: true, +- resetDate: '2026-07-01T00:00:00.000Z', +- }, +- }, +- } as unknown as SessionEventPayload<'assistant.usage'>['data']); +- +- const usageActions = signals +- .filter((s): s is IAgentActionSignal => s.kind === 'action') +- .map(s => s.action) +- .filter(a => a.type === ActionType.ChatUsage); +- +- assert.deepStrictEqual(usageActions.map(a => a.usage._meta?.quotaSnapshots), [ +- { +- premium_interactions: { +- isUnlimitedEntitlement: false, +- entitlementRequests: 300, +- usedRequests: 75, +- remainingPercentage: 75, +- overage: 1.5, +- overageAllowedWithExhaustedQuota: true, +- resetDate: '2026-07-01T00:00:00.000Z', - }, - }, - ]); @@ -10563,7 +14084,7 @@ index 3ba908cae5d..00000000000 - - // Sending the steering must not flip turns until the SDK has - // echoed the user message back through the event stream. -- assert.strictEqual(signals.find(s => s.kind === 'action' && (s as IAgentActionSignal).action.type === ActionType.SessionTurnStarted), undefined); +- assert.strictEqual(signals.find(s => s.kind === 'action' && (s as IAgentActionSignal).action.type === ActionType.ChatTurnStarted), undefined); - - mockSession.fire('user.message', { - content: 'focus on tests', @@ -10571,8 +14092,8 @@ index 3ba908cae5d..00000000000 - } as SessionEventPayload<'user.message'>['data']); - - const actions = signals.filter(s => s.kind === 'action').map(s => (s as IAgentActionSignal).action); -- const turnComplete = actions.find(a => a.type === ActionType.SessionTurnComplete); -- const turnStarted = actions.find(a => a.type === ActionType.SessionTurnStarted); +- const turnComplete = actions.find(a => a.type === ActionType.ChatTurnComplete); +- const turnStarted = actions.find(a => a.type === ActionType.ChatTurnStarted); - assert.ok(turnComplete, 'should complete the in-flight turn before promoting steering'); - assert.strictEqual(turnComplete.turnId, 'turn-original'); - assert.ok(turnStarted, 'should start a new turn for the steering message'); @@ -10594,7 +14115,7 @@ index 3ba908cae5d..00000000000 - const turnStarted = signals - .filter(s => s.kind === 'action') - .map(s => (s as IAgentActionSignal).action) -- .find(a => a.type === ActionType.SessionTurnStarted)!; +- .find(a => a.type === ActionType.ChatTurnStarted)!; - - mockSession.fire('assistant.message_delta', { - deltaContent: 'No problem', @@ -10603,7 +14124,7 @@ index 3ba908cae5d..00000000000 - const responseParts = signals - .filter(s => s.kind === 'action') - .map(s => (s as IAgentActionSignal).action) -- .filter(a => a.type === ActionType.SessionResponsePart); +- .filter(a => a.type === ActionType.ChatResponsePart); - assert.ok(responseParts.length > 0, 'expected delta to allocate a response part'); - assert.strictEqual(responseParts[0].turnId, turnStarted.turnId, 'response part should land in the steering turn, not the original'); - }); @@ -10623,7 +14144,7 @@ index 3ba908cae5d..00000000000 - source: 'skill-pdf', - } as SessionEventPayload<'user.message'>['data']); - -- const turnStarted = signals.find(s => s.kind === 'action' && (s as IAgentActionSignal).action.type === ActionType.SessionTurnStarted); +- const turnStarted = signals.find(s => s.kind === 'action' && (s as IAgentActionSignal).action.type === ActionType.ChatTurnStarted); - assert.strictEqual(turnStarted, undefined, 'synthetic user messages should not promote steering to a turn'); - }); - @@ -10636,7 +14157,7 @@ index 3ba908cae5d..00000000000 - content: 'something completely different', - } as SessionEventPayload<'user.message'>['data']); - -- const turnStarted = signals.find(s => s.kind === 'action' && (s as IAgentActionSignal).action.type === ActionType.SessionTurnStarted); +- const turnStarted = signals.find(s => s.kind === 'action' && (s as IAgentActionSignal).action.type === ActionType.ChatTurnStarted); - assert.strictEqual(turnStarted, undefined, 'unrelated user messages should not consume the pending steering'); - }); - @@ -10649,15 +14170,44 @@ index 3ba908cae5d..00000000000 - assert.strictEqual(mockSession.sendRequests.length, 1); - }); - -- test('fires steering_consumed on abort when the steering never reached its turn', async () => { -- const { session, signals } = await createAgentSession(disposables); +- test('fires steering_consumed on abort when the steering never reached its turn', async () => { +- const { session, signals } = await createAgentSession(disposables); +- +- await session.sendSteering({ id: 'steer-1', message: { text: 'focus on tests', origin: { kind: MessageKind.User } } }); +- await session.abort(); +- +- const consumed = signals.find(s => s.kind === 'steering_consumed'); +- assert.ok(consumed, 'abort should clean up pending steering UI state'); +- assert.strictEqual((consumed as { id: string }).id, 'steer-1'); +- }); +- +- test('an abort during a steering turn tears it down without completing it', async () => { +- // A steering turn is promoted mid-loop while the SDK is actively +- // producing its response, so it must be `running` (not `pending`). +- // Otherwise an abort's terminal idle would treat it as a not-yet- +- // started queued turn and leave it open, and a later idle would +- // orphan-complete it. +- const { session, mockSession, signals } = await createAgentSession(disposables); +- session.resetTurnState('turn-original'); +- mockSession.fire('assistant.turn_start', { turnId: 'sdk-0' } as SessionEventPayload<'assistant.turn_start'>['data']); +- +- await session.sendSteering({ id: 'steer-1', message: { text: 'focus on tests', origin: { kind: MessageKind.User } } }); +- mockSession.fire('user.message', { +- content: 'focus on tests', +- interactionId: 'interaction-steer', +- } as SessionEventPayload<'user.message'>['data']); +- +- const steeringTurnId = getActions(signals).find(a => a.type === ActionType.ChatTurnStarted)?.turnId; +- assert.ok(steeringTurnId && steeringTurnId !== 'turn-original', 'steering should start its own turn'); - -- await session.sendSteering({ id: 'steer-1', message: { text: 'focus on tests', origin: { kind: MessageKind.User } } }); -- await session.abort(); +- // Abort: the running steering turn is finalized by the client's +- // ChatTurnCancelled, so its terminal idle must tear it down rather +- // than complete it — and a subsequent stray idle must find no turn. +- mockSession.fire('session.idle', { aborted: true } as SessionEventPayload<'session.idle'>['data']); +- mockSession.fire('session.idle', {} as SessionEventPayload<'session.idle'>['data']); - -- const consumed = signals.find(s => s.kind === 'steering_consumed'); -- assert.ok(consumed, 'abort should clean up pending steering UI state'); -- assert.strictEqual((consumed as { id: string }).id, 'steer-1'); +- const steeringCompletions = getActions(signals).filter(a => a.type === ActionType.ChatTurnComplete && a.turnId === steeringTurnId); +- assert.strictEqual(steeringCompletions.length, 0, 'an aborted steering turn must not be completed'); - }); - - test('does not signal cleanup when send fails', async () => { @@ -10668,7 +14218,7 @@ index 3ba908cae5d..00000000000 - await session.sendSteering({ id: 'steer-fail', message: { text: 'will fail', origin: { kind: MessageKind.User } } }); - - const consumed = signals.find(s => s.kind === 'steering_consumed'); -- const turnStarted = signals.find(s => s.kind === 'action' && (s as IAgentActionSignal).action.type === ActionType.SessionTurnStarted); +- const turnStarted = signals.find(s => s.kind === 'action' && (s as IAgentActionSignal).action.type === ActionType.ChatTurnStarted); - assert.strictEqual(consumed, undefined, 'should not fire steering_consumed on failure'); - assert.strictEqual(turnStarted, undefined, 'should not start a new turn on failure'); - }); @@ -10738,7 +14288,7 @@ index 3ba908cae5d..00000000000 - - assert.strictEqual(mockSession.sendRequests.length, 0, 'system notification should not call session.send'); - const actions = getActions(signals); -- const turnStarted = actions.find(a => a.type === ActionType.SessionTurnStarted); +- const turnStarted = actions.find(a => a.type === ActionType.ChatTurnStarted); - assert.ok(turnStarted, 'should synthesize a fresh turn'); - assert.deepStrictEqual(turnStarted.message, { text: '`sleep 6` completed', origin: { kind: MessageKind.SystemNotification } }); - }); @@ -10750,15 +14300,15 @@ index 3ba908cae5d..00000000000 - content: 'Shell command completed', - kind: { type: 'shell_completed', shellId: 'shell-a', exitCode: 0, description: 'sleep 6' }, - } as SessionEventPayload<'system.notification'>['data']); -- const turnStarted = getActions(signals).find(a => a.type === ActionType.SessionTurnStarted)!; +- const turnStarted = getActions(signals).find(a => a.type === ActionType.ChatTurnStarted)!; - - mockSession.fire('assistant.message_delta', { - deltaContent: 'Reading the shell output now.', - } as SessionEventPayload<'assistant.message_delta'>['data']); - -- const responsePart = getActions(signals).find(a => a.type === ActionType.SessionResponsePart && a.part.kind === ResponsePartKind.Markdown); +- const responsePart = getActions(signals).find(a => a.type === ActionType.ChatResponsePart && a.part.kind === ResponsePartKind.Markdown); - assert.ok(responsePart, 'expected response part for follow-up assistant delta'); -- assert.strictEqual((responsePart as SessionResponsePartAction).turnId, (turnStarted as { turnId: string }).turnId); +- assert.strictEqual((responsePart as ChatResponsePartAction).turnId, (turnStarted as { turnId: string }).turnId); - }); - - test('notification during an active turn appends a SystemNotification response part', async () => { @@ -10771,8 +14321,8 @@ index 3ba908cae5d..00000000000 - } as SessionEventPayload<'system.notification'>['data']); - - const actions = getActions(signals); -- assert.strictEqual(actions.find(a => a.type === ActionType.SessionTurnStarted), undefined, 'should not create a duplicate turn'); -- const systemPart = actions.find(a => a.type === ActionType.SessionResponsePart && a.part.kind === ResponsePartKind.SystemNotification) as SessionResponsePartAction | undefined; +- assert.strictEqual(actions.find(a => a.type === ActionType.ChatTurnStarted), undefined, 'should not create a duplicate turn'); +- const systemPart = actions.find(a => a.type === ActionType.ChatResponsePart && a.part.kind === ResponsePartKind.SystemNotification) as ChatResponsePartAction | undefined; - assert.ok(systemPart, 'expected system notification response part'); - assert.strictEqual(systemPart.turnId, 'turn-active'); - assert.strictEqual(systemPart.part.kind, ResponsePartKind.SystemNotification); @@ -10786,17 +14336,18 @@ index 3ba908cae5d..00000000000 - content: 'Shell command completed', - kind: { type: 'shell_completed', shellId: 'shell-a', exitCode: 0, description: 'sleep 6' }, - } as SessionEventPayload<'system.notification'>['data']); -- const turnStarted = getActions(signals).find(a => a.type === ActionType.SessionTurnStarted)!; +- const turnStarted = getActions(signals).find(a => a.type === ActionType.ChatTurnStarted)!; - - mockSession.fire('session.idle', {} as SessionEventPayload<'session.idle'>['data']); - -- const turnComplete = getActions(signals).find(a => a.type === ActionType.SessionTurnComplete); +- const turnComplete = getActions(signals).find(a => a.type === ActionType.ChatTurnComplete); - assert.ok(turnComplete, 'expected idle to complete the generated turn'); - assert.strictEqual((turnComplete as { turnId: string }).turnId, turnStarted.turnId); - }); - -- test('events after a completed turn do not target the stale previous turn id', async () => { -- const { session, mockSession, signals } = await createAgentSession(disposables); +- test('a late event after a completed turn is dropped and logged (never targets the stale turn id)', async () => { +- const logService = new CapturingLogService(); +- const { session, mockSession, signals } = await createAgentSession(disposables, { logService }); - session.resetTurnState('turn-old'); - - mockSession.fire('session.idle', {} as SessionEventPayload<'session.idle'>['data']); @@ -10804,12 +14355,13 @@ index 3ba908cae5d..00000000000 - deltaContent: 'late text', - } as SessionEventPayload<'assistant.message_delta'>['data']); - -- const lateMarkdownActions = getActions(signals) -- .filter(a => a.type === ActionType.SessionResponsePart && a.part.kind === ResponsePartKind.Markdown) -- .map(a => a as SessionResponsePartAction); -- const lateMarkdown = lateMarkdownActions[lateMarkdownActions.length - 1]; -- assert.ok(lateMarkdown, 'late event still emits a no-op action for the reducer'); -- assert.notStrictEqual(lateMarkdown.turnId, 'turn-old'); +- // With no active turn, the late delta is dropped (not emitted even as a +- // no-op), so it can never be attributed to the stale 'turn-old', and the +- // unexpected state is surfaced via an error log. +- const markdownActions = getActions(signals) +- .filter(a => a.type === ActionType.ChatResponsePart && a.part.kind === ResponsePartKind.Markdown); +- assert.strictEqual(markdownActions.length, 0, 'the late delta must be dropped, not emitted'); +- assert.ok(logService.errors.some(e => /no active turn/i.test(String(e.first))), 'the dropped delta should be logged'); - }); - }); - @@ -10827,9 +14379,9 @@ index 3ba908cae5d..00000000000 - - assert.strictEqual(signals.length, 2); - const toolStart = signals[0]; -- assert.ok(isAction(toolStart, ActionType.SessionToolCallStart)); -- if (isAction(toolStart, ActionType.SessionToolCallStart)) { -- const action = toolStart.action as SessionToolCallStartAction; +- assert.ok(isAction(toolStart, ActionType.ChatToolCallStart)); +- if (isAction(toolStart, ActionType.ChatToolCallStart)) { +- const action = toolStart.action as ChatToolCallStartAction; - assert.strictEqual(action.toolCallId, 'tc-10'); - assert.strictEqual(action.toolName, 'bash'); - } @@ -10847,16 +14399,16 @@ index 3ba908cae5d..00000000000 - assert.strictEqual(signals.length, 2); - // toolInput on the auto-ready signal (signals[1]) - const readySignal = signals[1]; -- assert.ok(isAction(readySignal, ActionType.SessionToolCallReady)); -- if (isAction(readySignal, ActionType.SessionToolCallReady)) { -- const action = readySignal.action as SessionToolCallReadyAction; +- assert.ok(isAction(readySignal, ActionType.ChatToolCallReady)); +- if (isAction(readySignal, ActionType.ChatToolCallReady)) { +- const action = readySignal.action as ChatToolCallReadyAction; - assert.strictEqual(action.toolInput, 'npm test'); - } - // toolArguments in _meta on the tool_start signal (signals[0]) - const startSignal = signals[0]; -- assert.ok(isAction(startSignal, ActionType.SessionToolCallStart)); -- if (isAction(startSignal, ActionType.SessionToolCallStart)) { -- const meta = (startSignal.action as SessionToolCallStartAction)._meta; +- assert.ok(isAction(startSignal, ActionType.ChatToolCallStart)); +- if (isAction(startSignal, ActionType.ChatToolCallStart)) { +- const meta = (startSignal.action as ChatToolCallStartAction)._meta; - const toolArgs = meta?.['toolArguments'] as string | undefined; - assert.ok(toolArgs && toolArgs.includes('"npm test"'), `toolArguments should contain rewritten command, was: ${toolArgs}`); - assert.ok(!toolArgs?.includes('cd /repo/project'), 'toolArguments should not contain stripped prefix'); @@ -10881,9 +14433,9 @@ index 3ba908cae5d..00000000000 - - assert.strictEqual(signals.length, 3); - const completeSignal = signals[2]; -- assert.ok(isAction(completeSignal, ActionType.SessionToolCallComplete)); -- if (isAction(completeSignal, ActionType.SessionToolCallComplete)) { -- const action = completeSignal.action as SessionToolCallCompleteAction; +- assert.ok(isAction(completeSignal, ActionType.ChatToolCallComplete)); +- if (isAction(completeSignal, ActionType.ChatToolCallComplete)) { +- const action = completeSignal.action as ChatToolCallCompleteAction; - const past = action.result.pastTenseMessage; - const pastStr = typeof past === 'string' ? past : (past?.markdown ?? ''); - assert.ok(!pastStr.includes('cd /repo/project'), `past-tense message should not contain stripped prefix, got: ${pastStr}`); @@ -10965,13 +14517,17 @@ index 3ba908cae5d..00000000000 - - test('client tool telemetry does not use clientId as toolExtensionId', async () => { - const telemetryService = new RecordingTelemetryService(); +- const tools = [{ name: 'my_tool', description: 'A test tool', inputSchema: { type: 'object', properties: {} } }] as const; +- const activeClientState = new ActiveClientState(); +- activeClientState.update('test-client', tools); - const { mockSession } = await createAgentSession(disposables, { - telemetryService, - clientSnapshot: { -- clientId: 'test-client', -- tools: [{ name: 'my_tool', description: 'A test tool', inputSchema: { type: 'object', properties: {} } }], +- tools, - plugins: [], +- mcpServers: {}, - }, +- activeClientState, - }); - - mockSession.fire('tool.execution_start', { @@ -11003,6 +14559,35 @@ index 3ba908cae5d..00000000000 - invocationTimeMs: true, - }, - }); +- +- }); +- +- test('live task_complete emits root markdown instead of a tool call', async () => { +- const { mockSession, signals } = await createAgentSession(disposables); +- +- mockSession.fire('tool.execution_start', { +- toolCallId: 'tc-task-complete', +- toolName: 'task_complete', +- arguments: { summary: 'Completed the requested work.' }, +- } as SessionEventPayload<'tool.execution_start'>['data']); +- mockSession.fire('tool.execution_complete', { +- toolCallId: 'tc-task-complete', +- success: true, +- result: { content: 'Completed the requested work.' }, +- } as SessionEventPayload<'tool.execution_complete'>['data']); +- +- const actions = getActions(signals); +- assert.deepStrictEqual(actions.map(a => a.type), [ActionType.ChatResponsePart]); +- const responsePart = actions[0] as ChatResponsePartAction; +- assert.strictEqual(responsePart.part.kind, ResponsePartKind.Markdown); +- if (responsePart.part.kind !== ResponsePartKind.Markdown) { +- return; +- } +- assert.deepStrictEqual(responsePart.part, { +- kind: ResponsePartKind.Markdown, +- id: responsePart.part.id, +- content: '\n\n**Task completed:** Completed the requested work.', +- }); - }); - - test('live tool_start does not rewrite when cd target differs from workingDirectory', async () => { @@ -11016,9 +14601,9 @@ index 3ba908cae5d..00000000000 - - assert.strictEqual(signals.length, 2); - const readySignal = signals[1]; -- assert.ok(isAction(readySignal, ActionType.SessionToolCallReady)); -- if (isAction(readySignal, ActionType.SessionToolCallReady)) { -- assert.strictEqual((readySignal.action as SessionToolCallReadyAction).toolInput, 'cd /tmp && ls'); +- assert.ok(isAction(readySignal, ActionType.ChatToolCallReady)); +- if (isAction(readySignal, ActionType.ChatToolCallReady)) { +- assert.strictEqual((readySignal.action as ChatToolCallReadyAction).toolInput, 'cd /tmp && ls'); - } - }); - @@ -11032,9 +14617,9 @@ index 3ba908cae5d..00000000000 - - assert.strictEqual(signals.length, 2); - const readySignal = signals[1]; -- assert.ok(isAction(readySignal, ActionType.SessionToolCallReady)); -- if (isAction(readySignal, ActionType.SessionToolCallReady)) { -- assert.strictEqual((readySignal.action as SessionToolCallReadyAction).toolInput, 'cd /repo/project && npm test'); +- assert.ok(isAction(readySignal, ActionType.ChatToolCallReady)); +- if (isAction(readySignal, ActionType.ChatToolCallReady)) { +- assert.strictEqual((readySignal.action as ChatToolCallReadyAction).toolInput, 'cd /repo/project && npm test'); - } - }); - @@ -11089,7 +14674,7 @@ index 3ba908cae5d..00000000000 - const { session, mockSession, waitForSignal } = await createAgentSession(disposables, { workingDirectory }); - const sessionInternals = session as unknown as ISessionInternalsForTest; - const taken: string[] = []; -- sessionInternals._editTracker.takeCompletedEdit = async (_turnId, _toolCallId, path) => { +- sessionInternals._editTracker.takeCompletedEdit = async (_turnId, _toolCallId, path, _toolName, _toolInput, _modelId) => { - taken.push(path); - return undefined; - }; @@ -11116,7 +14701,7 @@ index 3ba908cae5d..00000000000 - success: true, - } as SessionEventPayload<'tool.execution_complete'>['data']); - -- await waitForSignal(s => isAction(s, ActionType.SessionToolCallComplete)); +- await waitForSignal(s => isAction(s, ActionType.ChatToolCallComplete)); - - assert.deepStrictEqual(taken, [join(workingDirectory.fsPath, 'foo.ts'), join(workingDirectory.fsPath, 'src/bar.ts')]); - }); @@ -11174,9 +14759,9 @@ index 3ba908cae5d..00000000000 - - assert.strictEqual(signals.length, 3); - const completeSignal = signals[2]; -- assert.ok(isAction(completeSignal, ActionType.SessionToolCallComplete)); -- if (isAction(completeSignal, ActionType.SessionToolCallComplete)) { -- const action = completeSignal.action as SessionToolCallCompleteAction; +- assert.ok(isAction(completeSignal, ActionType.ChatToolCallComplete)); +- if (isAction(completeSignal, ActionType.ChatToolCallComplete)) { +- const action = completeSignal.action as ChatToolCallCompleteAction; - assert.strictEqual(action.toolCallId, 'tc-12'); - assert.ok(action.result.success); - assert.ok(action.result.pastTenseMessage); @@ -11199,7 +14784,7 @@ index 3ba908cae5d..00000000000 - mockSession.fire('session.idle', {} as SessionEventPayload<'session.idle'>['data']); - - assert.strictEqual(signals.length, 1); -- assert.ok(isAction(signals[0], ActionType.SessionTurnComplete)); +- assert.ok(isAction(signals[0], ActionType.ChatTurnComplete)); - }); - - test('idle event without an active turn is ignored', async () => { @@ -11209,6 +14794,94 @@ index 3ba908cae5d..00000000000 - assert.strictEqual(signals.length, 0); - }); - +- test('drops and logs a markdown delta emitted with no active turn', async () => { +- // A delta should only arrive while a turn is active. With none, we +- // can't persist the part id (so every delta would allocate a fresh +- // part) and the action would carry an empty turnId — drop it and log. +- const logService = new CapturingLogService(); +- const { mockSession, signals } = await createAgentSession(disposables, { logService }); +- +- // No resetTurnState → no active turn. +- mockSession.fire('assistant.message_delta', { +- deltaContent: 'orphan text', +- } as SessionEventPayload<'assistant.message_delta'>['data']); +- +- const parts = getActions(signals).filter(a => a.type === ActionType.ChatResponsePart || a.type === ActionType.ChatDelta); +- assert.strictEqual(parts.length, 0, 'no response part/delta should be emitted without an active turn'); +- assert.strictEqual(logService.errors.length, 1, 'should log an error'); +- assert.match(String(logService.errors[0].first), /no active turn/i); +- }); +- +- test('abort-induced idle does not complete a pending queued turn', async () => { +- // Repro for the blank-response-after-abort race: a running turn is +- // aborted while a queued message exists. The queued message's +- // `send()` creates a fresh (pending) turn before the abort's +- // terminal `session.idle` is delivered. That idle must not complete +- // the queued turn — structurally, because it has not started running +- // yet, and because the idle carries `aborted: true`. +- const { session, mockSession, signals } = await createAgentSession(disposables); +- +- // The queued message has started its (pending) turn by the time the +- // abort's terminal idle arrives — no SDK event has run it yet. +- session.resetTurnState('turn-queued'); +- mockSession.fire('session.idle', { aborted: true } as SessionEventPayload<'session.idle'>['data']); +- +- assert.strictEqual( +- getActions(signals).filter(a => a.type === ActionType.ChatTurnComplete).length, +- 0, +- 'abort-induced idle must not complete the pending queued turn', +- ); +- +- // The queued turn now actually runs (first SDK event) and then +- // completes on its own (non-abort) idle. +- mockSession.fire('assistant.turn_start', { turnId: 'sdk-0' } as SessionEventPayload<'assistant.turn_start'>['data']); +- mockSession.fire('session.idle', {} as SessionEventPayload<'session.idle'>['data']); +- +- const completions = getActions(signals).filter(a => a.type === ActionType.ChatTurnComplete); +- assert.strictEqual(completions.length, 1, 'the queued turn should complete on its real idle'); +- assert.strictEqual((completions[0] as ChatTurnCompleteAction).turnId, 'turn-queued'); +- }); +- +- test('abort-induced idle tears down a running turn without completing it', async () => { +- // Plain abort (no queued message): the running turn is finalized by +- // the client-dispatched ChatTurnCancelled, so the abort's idle must +- // not also emit a ChatTurnComplete. The turn handle is dropped so a +- // later stray idle cannot complete it. +- const { session, mockSession, signals } = await createAgentSession(disposables); +- +- session.resetTurnState('turn-aborted'); +- mockSession.fire('assistant.turn_start', { turnId: 'sdk-0' } as SessionEventPayload<'assistant.turn_start'>['data']); +- mockSession.fire('session.idle', { aborted: true } as SessionEventPayload<'session.idle'>['data']); +- +- assert.strictEqual( +- getActions(signals).filter(a => a.type === ActionType.ChatTurnComplete).length, +- 0, +- 'abort-induced idle must not complete the running aborted turn', +- ); +- +- // A subsequent stray idle has no turn to act on. +- mockSession.fire('session.idle', {} as SessionEventPayload<'session.idle'>['data']); +- assert.strictEqual(getActions(signals).filter(a => a.type === ActionType.ChatTurnComplete).length, 0); +- }); +- +- test('a running turn after a prior abort still completes on its idle', async () => { +- // No lingering state across turns: the next turn completes normally. +- const { session, mockSession, signals } = await createAgentSession(disposables); +- +- session.resetTurnState('turn-aborted'); +- mockSession.fire('assistant.turn_start', { turnId: 'sdk-0' } as SessionEventPayload<'assistant.turn_start'>['data']); +- mockSession.fire('session.idle', { aborted: true } as SessionEventPayload<'session.idle'>['data']); +- assert.strictEqual(getActions(signals).filter(a => a.type === ActionType.ChatTurnComplete).length, 0); +- +- session.resetTurnState('turn-next'); +- mockSession.fire('assistant.turn_start', { turnId: 'sdk-1' } as SessionEventPayload<'assistant.turn_start'>['data']); +- mockSession.fire('session.idle', {} as SessionEventPayload<'session.idle'>['data']); +- +- const completions = getActions(signals).filter(a => a.type === ActionType.ChatTurnComplete); +- assert.strictEqual(completions.length, 1); +- assert.strictEqual((completions[0] as ChatTurnCompleteAction).turnId, 'turn-next'); +- }); +- - test('error event is forwarded', async () => { - const { mockSession, signals } = await createAgentSession(disposables); - mockSession.fire('session.error', { @@ -11218,16 +14891,17 @@ index 3ba908cae5d..00000000000 - } as SessionEventPayload<'session.error'>['data']); - - assert.strictEqual(signals.length, 1); -- assert.ok(isAction(signals[0], ActionType.SessionError)); -- if (isAction(signals[0], ActionType.SessionError)) { -- const action = signals[0].action as SessionErrorAction; +- assert.ok(isAction(signals[0], ActionType.ChatError)); +- if (isAction(signals[0], ActionType.ChatError)) { +- const action = signals[0].action as ChatErrorAction; - assert.strictEqual(action.error.errorType, 'TestError'); - assert.strictEqual(action.error.message, 'something went wrong'); - } - }); - - test('message delta is forwarded', async () => { -- const { mockSession, signals } = await createAgentSession(disposables); +- const { session, mockSession, signals } = await createAgentSession(disposables); +- session.resetTurnState('turn-1'); - mockSession.fire('assistant.message_delta', { - messageId: 'msg-1', - deltaContent: 'Hello ', @@ -11236,12 +14910,12 @@ index 3ba908cae5d..00000000000 - assert.ok(signals.length >= 1); - const hasDelta = signals.some(s => { - if (s.kind !== 'action') { return false; } -- if (s.action.type === ActionType.SessionResponsePart) { -- const part = (s.action as SessionResponsePartAction).part; +- if (s.action.type === ActionType.ChatResponsePart) { +- const part = (s.action as ChatResponsePartAction).part; - return part.kind === ResponsePartKind.Markdown && part.content === 'Hello '; - } -- if (s.action.type === ActionType.SessionDelta) { -- return (s.action as SessionDeltaAction).content === 'Hello '; +- if (s.action.type === ActionType.ChatDelta) { +- return (s.action as ChatDeltaAction).content === 'Hello '; - } - return false; - }); @@ -11268,12 +14942,12 @@ index 3ba908cae5d..00000000000 - assert.ok(signals.length >= 1); - const hasPart = signals.some(s => { - if (s.kind !== 'action') { return false; } -- if (s.action.type === ActionType.SessionResponsePart) { -- const part = (s.action as SessionResponsePartAction).part; +- if (s.action.type === ActionType.ChatResponsePart) { +- const part = (s.action as ChatResponsePartAction).part; - return part.kind === ResponsePartKind.Markdown && part.content === 'Let me help you.'; - } -- if (s.action.type === ActionType.SessionDelta) { -- return (s.action as SessionDeltaAction).content === 'Let me help you.'; +- if (s.action.type === ActionType.ChatDelta) { +- return (s.action as ChatDeltaAction).content === 'Let me help you.'; - } - return false; - }); @@ -11455,10 +15129,10 @@ index 3ba908cae5d..00000000000 - } as SessionEventPayload<'assistant.message'>['data']); - - const markdownParts = signals.flatMap(signal => { -- if (signal.kind !== 'action' || signal.action.type !== ActionType.SessionResponsePart) { +- if (signal.kind !== 'action' || signal.action.type !== ActionType.ChatResponsePart) { - return []; - } -- const part = (signal.action as SessionResponsePartAction).part; +- const part = (signal.action as ChatResponsePartAction).part; - if (part.kind !== ResponsePartKind.Markdown) { - return []; - } @@ -11472,7 +15146,8 @@ index 3ba908cae5d..00000000000 - }); - - test('reasoning delta after tool_start starts a new reasoning response part', async () => { -- const { mockSession, signals } = await createAgentSession(disposables); +- const { session, mockSession, signals } = await createAgentSession(disposables); +- session.resetTurnState('turn-1'); - - // First reasoning delta — allocates a fresh reasoning response part. - mockSession.fire('assistant.reasoning_delta', { @@ -11500,12 +15175,12 @@ index 3ba908cae5d..00000000000 - } as SessionEventPayload<'assistant.reasoning_delta'>['data']); - - // Pull the protocol-level reasoning response parts. Both -- // `SessionResponsePart{Reasoning}` (allocates a new part) and -- // `SessionReasoning` (appends to an existing part) translate to +- // `ChatResponsePart{Reasoning}` (allocates a new part) and +- // `ChatReasoning` (appends to an existing part) translate to - // the legacy `'reasoning'` view, so we have to inspect raw - // signals to tell them apart. - const reasoningResponseParts = signals.flatMap(s => { -- if (s.kind !== 'action' || s.action.type !== ActionType.SessionResponsePart) { +- if (s.kind !== 'action' || s.action.type !== ActionType.ChatResponsePart) { - return []; - } - return s.action.part.kind === ResponsePartKind.Reasoning ? [s.action.part] : []; @@ -11540,10 +15215,10 @@ index 3ba908cae5d..00000000000 - } as SessionEventPayload<'assistant.reasoning_delta'>['data']); - - const reasoningParts = signals.flatMap(signal => { -- if (signal.kind !== 'action' || signal.action.type !== ActionType.SessionResponsePart) { +- if (signal.kind !== 'action' || signal.action.type !== ActionType.ChatResponsePart) { - return []; - } -- const part = (signal.action as SessionResponsePartAction).part; +- const part = (signal.action as ChatResponsePartAction).part; - if (part.kind !== ResponsePartKind.Reasoning) { - return []; - } @@ -11580,10 +15255,10 @@ index 3ba908cae5d..00000000000 - } as SessionEventPayload<'tool.execution_complete'>['data'], { agentId: 'agent-1' }); - - const toolCompletions = signals.flatMap(signal => { -- if (!isAction(signal, ActionType.SessionToolCallComplete)) { +- if (!isAction(signal, ActionType.ChatToolCallComplete)) { - return []; - } -- const action = signal.action as SessionToolCallCompleteAction; +- const action = signal.action as ChatToolCallCompleteAction; - return [{ parentToolCallId: signal.parentToolCallId, toolCallId: action.toolCallId }]; - }); - @@ -11656,10 +15331,10 @@ index 3ba908cae5d..00000000000 - const questionId = request.questions[0].id; - - // Respond to unblock the promise -- session.respondToUserInputRequest(requestId, SessionInputResponseKind.Accept, { +- session.respondToUserInputRequest(requestId, ChatInputResponseKind.Accept, { - [questionId]: { -- state: SessionInputAnswerState.Submitted, -- value: { kind: SessionInputAnswerValueKind.Text, value: 'Alice' } +- state: ChatInputAnswerState.Submitted, +- value: { kind: ChatInputAnswerValueKind.Text, value: 'Alice' } - } - }); - @@ -11680,18 +15355,18 @@ index 3ba908cae5d..00000000000 - const request = getInputRequest(signals[0]); - assert.ok(request.questions); - assert.strictEqual(request.questions.length, 1); -- assert.strictEqual(request.questions[0].kind, SessionInputQuestionKind.SingleSelect); -- if (request.questions[0].kind === SessionInputQuestionKind.SingleSelect) { +- assert.strictEqual(request.questions[0].kind, ChatInputQuestionKind.SingleSelect); +- if (request.questions[0].kind === ChatInputQuestionKind.SingleSelect) { - assert.strictEqual(request.questions[0].options.length, 3); - assert.strictEqual(request.questions[0].options[0].label, 'red'); - } - - // Respond with a selected choice - const questions = request.questions; -- session.respondToUserInputRequest(request.id, SessionInputResponseKind.Accept, { +- session.respondToUserInputRequest(request.id, ChatInputResponseKind.Accept, { - [questions[0].id]: { -- state: SessionInputAnswerState.Submitted, -- value: { kind: SessionInputAnswerValueKind.Selected, value: 'blue' } +- state: ChatInputAnswerState.Submitted, +- value: { kind: ChatInputAnswerValueKind.Selected, value: 'blue' } - } - }); - @@ -11709,7 +15384,7 @@ index 3ba908cae5d..00000000000 - ); - - const request = getInputRequest(signals[0]); -- session.respondToUserInputRequest(request.id, SessionInputResponseKind.Cancel); +- session.respondToUserInputRequest(request.id, ChatInputResponseKind.Cancel); - - const result = await resultPromise; - assert.strictEqual(result.answer, ''); @@ -11718,7 +15393,7 @@ index 3ba908cae5d..00000000000 - - test('respondToUserInputRequest returns false for unknown id', async () => { - const { session } = await createAgentSession(disposables); -- assert.strictEqual(session.respondToUserInputRequest('unknown-id', SessionInputResponseKind.Accept), false); +- assert.strictEqual(session.respondToUserInputRequest('unknown-id', ChatInputResponseKind.Accept), false); - }); - - test('handleUserInputRequest returns empty answer on skipped question', async () => { @@ -11731,9 +15406,9 @@ index 3ba908cae5d..00000000000 - - const request = getInputRequest(signals[0]); - const questionId = request.questions![0].id; -- session.respondToUserInputRequest(request.id, SessionInputResponseKind.Accept, { +- session.respondToUserInputRequest(request.id, ChatInputResponseKind.Accept, { - [questionId]: { -- state: SessionInputAnswerState.Skipped, +- state: ChatInputAnswerState.Skipped, - } - }); - @@ -11758,7 +15433,7 @@ index 3ba908cae5d..00000000000 - - test('autopilot auto-answers a free-form question without firing a progress event', async () => { - const { runtime, signals } = await createAgentSession(disposables, { -- configValues: { [SessionConfigKey.AutoApprove]: 'autopilot' }, +- configValues: { [SessionConfigKey.Mode]: 'autopilot' }, - }); - - const result = await runtime.handleUserInputRequest( @@ -11774,11 +15449,11 @@ index 3ba908cae5d..00000000000 - assert.strictEqual(signals.length, 0); - }); - -- test('autopilot does not auto-answer when autoApprove is not "autopilot"', async () => { -- // Sanity check: with autoApprove=default the question must +- test('autopilot does not auto-answer when mode is not "autopilot"', async () => { +- // Sanity check: with mode=interactive the question must - // still be surfaced as a progress event (the existing behavior). - const { runtime, signals } = await createAgentSession(disposables, { -- configValues: { [SessionConfigKey.AutoApprove]: 'default' }, +- configValues: { [SessionConfigKey.Mode]: 'interactive' }, - }); - - runtime.handleUserInputRequest( @@ -11790,7 +15465,7 @@ index 3ba908cae5d..00000000000 - // short-circuit or emit a progress event. - await Promise.resolve(); - assert.strictEqual(signals.length, 1); -- assert.ok(isAction(signals[0], ActionType.SessionInputRequested)); +- assert.ok(isAction(signals[0], ActionType.ChatInputRequested)); - }); - }); - @@ -11823,27 +15498,27 @@ index 3ba908cae5d..00000000000 - assert.strictEqual(request.message, 'Configure deployment'); - assert.ok(request.questions); - assert.deepStrictEqual(request.questions.map(q => ({ id: q.id, kind: q.kind, required: q.required })), [ -- { id: 'environment', kind: SessionInputQuestionKind.SingleSelect, required: true }, -- { id: 'replicas', kind: SessionInputQuestionKind.Integer, required: false }, -- { id: 'confirm', kind: SessionInputQuestionKind.Boolean, required: true }, -- { id: 'region', kind: SessionInputQuestionKind.Text, required: false }, -- { id: 'tags', kind: SessionInputQuestionKind.MultiSelect, required: false }, +- { id: 'environment', kind: ChatInputQuestionKind.SingleSelect, required: true }, +- { id: 'replicas', kind: ChatInputQuestionKind.Integer, required: false }, +- { id: 'confirm', kind: ChatInputQuestionKind.Boolean, required: true }, +- { id: 'region', kind: ChatInputQuestionKind.Text, required: false }, +- { id: 'tags', kind: ChatInputQuestionKind.MultiSelect, required: false }, - ]); - const envQuestion = request.questions[0]; -- assert.strictEqual(envQuestion.kind, SessionInputQuestionKind.SingleSelect); -- if (envQuestion.kind === SessionInputQuestionKind.SingleSelect) { +- assert.strictEqual(envQuestion.kind, ChatInputQuestionKind.SingleSelect); +- if (envQuestion.kind === ChatInputQuestionKind.SingleSelect) { - assert.deepStrictEqual(envQuestion.options, [ - { id: 'dev', label: 'Development' }, - { id: 'prod', label: 'Production' }, - ]); - } - -- session.respondToUserInputRequest(request.id, SessionInputResponseKind.Accept, { -- environment: { state: SessionInputAnswerState.Submitted, value: { kind: SessionInputAnswerValueKind.Selected, value: 'prod' } }, -- replicas: { state: SessionInputAnswerState.Submitted, value: { kind: SessionInputAnswerValueKind.Number, value: 5 } }, -- confirm: { state: SessionInputAnswerState.Submitted, value: { kind: SessionInputAnswerValueKind.Boolean, value: true } }, -- region: { state: SessionInputAnswerState.Submitted, value: { kind: SessionInputAnswerValueKind.Text, value: 'eu-west-1' } }, -- tags: { state: SessionInputAnswerState.Submitted, value: { kind: SessionInputAnswerValueKind.SelectedMany, value: ['a', 'c'] } }, +- session.respondToUserInputRequest(request.id, ChatInputResponseKind.Accept, { +- environment: { state: ChatInputAnswerState.Submitted, value: { kind: ChatInputAnswerValueKind.Selected, value: 'prod' } }, +- replicas: { state: ChatInputAnswerState.Submitted, value: { kind: ChatInputAnswerValueKind.Number, value: 5 } }, +- confirm: { state: ChatInputAnswerState.Submitted, value: { kind: ChatInputAnswerValueKind.Boolean, value: true } }, +- region: { state: ChatInputAnswerState.Submitted, value: { kind: ChatInputAnswerValueKind.Text, value: 'eu-west-1' } }, +- tags: { state: ChatInputAnswerState.Submitted, value: { kind: ChatInputAnswerValueKind.SelectedMany, value: ['a', 'c'] } }, - }); - - assert.deepStrictEqual(await resultPromise, { @@ -11875,8 +15550,8 @@ index 3ba908cae5d..00000000000 - }); - - const request = getInputRequest(signals[0]); -- session.respondToUserInputRequest(request.id, SessionInputResponseKind.Accept, { -- name: { state: SessionInputAnswerState.Skipped }, +- session.respondToUserInputRequest(request.id, ChatInputResponseKind.Accept, { +- name: { state: ChatInputAnswerState.Skipped }, - // `count` is missing entirely - }); - @@ -11897,7 +15572,7 @@ index 3ba908cae5d..00000000000 - assert.strictEqual(request.url, 'https://example.com/auth'); - assert.strictEqual(request.questions, undefined); - -- session.respondToUserInputRequest(request.id, SessionInputResponseKind.Accept); +- session.respondToUserInputRequest(request.id, ChatInputResponseKind.Accept); - assert.deepStrictEqual(await resultPromise, { action: 'accept' }); - }); - @@ -11914,8 +15589,8 @@ index 3ba908cae5d..00000000000 - const request = getInputRequest(signals[0]); - assert.strictEqual(request.questions, undefined); - -- session.respondToUserInputRequest(request.id, SessionInputResponseKind.Accept, { -- answer: { state: SessionInputAnswerState.Submitted, value: { kind: SessionInputAnswerValueKind.Text, value: 'teal' } }, +- session.respondToUserInputRequest(request.id, ChatInputResponseKind.Accept, { +- answer: { state: ChatInputAnswerState.Submitted, value: { kind: ChatInputAnswerValueKind.Text, value: 'teal' } }, - }); - - assert.deepStrictEqual(await resultPromise, { action: 'accept', content: { answer: 'teal' } }); @@ -11932,7 +15607,7 @@ index 3ba908cae5d..00000000000 - }); - - const request = getInputRequest(signals[0]); -- session.respondToUserInputRequest(request.id, SessionInputResponseKind.Decline); +- session.respondToUserInputRequest(request.id, ChatInputResponseKind.Decline); - assert.deepStrictEqual(await resultPromise, { action: 'decline' }); - }); - @@ -11947,13 +15622,13 @@ index 3ba908cae5d..00000000000 - }); - - const request = getInputRequest(signals[0]); -- session.respondToUserInputRequest(request.id, SessionInputResponseKind.Cancel); +- session.respondToUserInputRequest(request.id, ChatInputResponseKind.Cancel); - assert.deepStrictEqual(await resultPromise, { action: 'cancel' }); - }); - - test('autopilot auto-cancels without firing a progress event', async () => { - const { runtime, signals } = await createAgentSession(disposables, { -- configValues: { [SessionConfigKey.AutoApprove]: 'autopilot' }, +- configValues: { [SessionConfigKey.Mode]: 'autopilot' }, - }); - - const result = await runtime.handleElicitationRequest({ @@ -12068,17 +15743,55 @@ index 3ba908cae5d..00000000000 - suite('client tool calls', () => { - - const snapshot: IActiveClientSnapshot = { -- clientId: 'test-client', - tools: [{ - name: 'my_tool', - description: 'A test tool', - inputSchema: { type: 'object', properties: {} }, - }], - plugins: [], +- mcpServers: {}, +- }; +- +- /** Builds a live ActiveClientState seeded with the given owning clientId and the snapshot's tools. */ +- const activeClientStateWith = (clientId: string): ActiveClientState => { +- const state = new ActiveClientState(); +- state.update(clientId, snapshot.tools); +- return state; - }; - +- test('client tool started with no connected client fails immediately', async () => { +- // No activeClientState is provided, so the session seeds one with +- // an undefined clientId — i.e. no client is connected to run the tool. +- const { runtime, mockSession, signals } = await createAgentSession(disposables, { clientSnapshot: snapshot }); +- +- mockSession.fire('tool.execution_start', { +- toolCallId: 'tc-no-client', +- toolName: 'my_tool', +- arguments: {}, +- } as SessionEventPayload<'tool.execution_start'>['data']); +- +- // tool_start is stamped as a client contributor with no owner... +- const startSignal = signals.find(s => isAction(s, ActionType.ChatToolCallStart)); +- assert.ok(startSignal && isAction(startSignal, ActionType.ChatToolCallStart)); +- assert.deepStrictEqual((startSignal.action as ChatToolCallStartAction).contributor, undefined); +- +- // ...and is failed immediately (ready + complete) rather than left +- // pending for the server-side disconnect timeout. +- assert.strictEqual(signals.filter(s => isAction(s, ActionType.ChatToolCallReady)).length, 1); +- const completeSignal = signals.find(s => isAction(s, ActionType.ChatToolCallComplete)); +- assert.ok(completeSignal && isAction(completeSignal, ActionType.ChatToolCallComplete)); +- assert.strictEqual((completeSignal.action as ChatToolCallCompleteAction).result.success, false); +- +- // When the SDK invokes the handler it resolves immediately with the +- // buffered failure result. +- const tools = runtime.createClientSdkTools(); +- const result = await invokeClientToolHandler(tools[0], 'tc-no-client'); +- assert.strictEqual(result.resultType, 'failure'); +- }); +- - test('client tool handler waits for completion without emitting tool_ready', async () => { -- const { session, runtime, mockSession, signals } = await createAgentSession(disposables, { clientSnapshot: snapshot }); +- +- const { session, runtime, mockSession, signals } = await createAgentSession(disposables, { clientSnapshot: snapshot, activeClientState: activeClientStateWith('test-client') }); - - // SDK emits tool.execution_start — tool_start fires immediately - mockSession.fire('tool.execution_start', { @@ -12088,11 +15801,11 @@ index 3ba908cae5d..00000000000 - } as SessionEventPayload<'tool.execution_start'>['data']); - - // tool_start fires immediately (client tools don't auto-ready) -- assert.strictEqual(signals.filter(s => isAction(s, ActionType.SessionToolCallStart)).length, 1); -- const startSignal = signals.find(s => isAction(s, ActionType.SessionToolCallStart)); -- assert.ok(startSignal && isAction(startSignal, ActionType.SessionToolCallStart)); -- if (isAction(startSignal!, ActionType.SessionToolCallStart)) { -- assert.deepStrictEqual((startSignal.action as SessionToolCallStartAction).contributor, { kind: ToolCallContributorKind.Client, clientId: 'test-client' }); +- assert.strictEqual(signals.filter(s => isAction(s, ActionType.ChatToolCallStart)).length, 1); +- const startSignal = signals.find(s => isAction(s, ActionType.ChatToolCallStart)); +- assert.ok(startSignal && isAction(startSignal, ActionType.ChatToolCallStart)); +- if (isAction(startSignal!, ActionType.ChatToolCallStart)) { +- assert.deepStrictEqual((startSignal.action as ChatToolCallStartAction).contributor, { kind: ToolCallContributorKind.Client, clientId: 'test-client' }); - } - - // SDK invokes the handler — it creates a deferred and waits, @@ -12101,7 +15814,7 @@ index 3ba908cae5d..00000000000 - const handlerPromise = invokeClientToolHandler(tools[0], 'tc-client-1', { file: 'test.ts' }); - - // No pending_confirmation or tool_ready should have been emitted by the handler -- assert.strictEqual(signals.filter(s => s.kind === 'pending_confirmation' || isAction(s, ActionType.SessionToolCallReady)).length, 0); +- assert.strictEqual(signals.filter(s => s.kind === 'pending_confirmation' || isAction(s, ActionType.ChatToolCallReady)).length, 0); - - // Complete the tool call - session.handleClientToolCallComplete('tc-client-1', { @@ -12116,7 +15829,9 @@ index 3ba908cae5d..00000000000 - }); - - test('client tool handler does not emit tool_ready (permission flow owns it)', async () => { -- const { session, runtime, mockSession, signals, waitForSignal } = await createAgentSession(disposables, { clientSnapshot: snapshot }); +- const activeClientState = new ActiveClientState(); +- activeClientState.update('client-perm', snapshot.tools); +- const { session, runtime, mockSession, signals, waitForSignal } = await createAgentSession(disposables, { clientSnapshot: snapshot, activeClientState }); - - // SDK emits tool.execution_start — tool_start fires immediately - mockSession.fire('tool.execution_start', { @@ -12126,7 +15841,7 @@ index 3ba908cae5d..00000000000 - } as SessionEventPayload<'tool.execution_start'>['data']); - - // tool_start fired, no pending_confirmation yet -- assert.strictEqual(signals.filter(s => isAction(s, ActionType.SessionToolCallStart)).length, 1); +- assert.strictEqual(signals.filter(s => isAction(s, ActionType.ChatToolCallStart)).length, 1); - assert.strictEqual(signals.filter(s => s.kind === 'pending_confirmation').length, 0); - - // Permission request fires — pending_confirmation from permission flow. @@ -12158,7 +15873,11 @@ index 3ba908cae5d..00000000000 - success: true, - pastTenseMessage: 'did it', - }); -- await handlerPromise; +- assert.deepStrictEqual(await handlerPromise, { +- textResultForLlm: '', +- resultType: 'success', +- binaryResultsForLlm: undefined, +- }); - }); - - test('pending_confirmation forwards parentToolCallId for tools inside subagents', async () => { @@ -12166,10 +15885,10 @@ index 3ba908cae5d..00000000000 - // permission-flow `pending_confirmation` must carry the - // parentToolCallId from the originating tool_start. Without it - // the host has no way to route the resulting -- // SessionToolCallReady to the subagent session and emits a +- // ChatToolCallReady to the subagent session and emits a - // stray ready against the parent session (no preceding -- // SessionToolCallStart). -- const { session, runtime, mockSession, signals, waitForSignal } = await createAgentSession(disposables, { clientSnapshot: snapshot }); +- // ChatToolCallStart). +- const { session, runtime, mockSession, signals, waitForSignal } = await createAgentSession(disposables, { clientSnapshot: snapshot, activeClientState: activeClientStateWith('test-client') }); - - mockSession.fire('subagent.started', { - toolCallId: 'tc-parent-subagent', @@ -12294,7 +16013,7 @@ index 3ba908cae5d..00000000000 - const { session, runtime } = await createAgentSession(disposables, { clientSnapshot: snapshot, logService }); - const tools = runtime.createClientSdkTools(); - const sessionInternals = session as unknown as ISessionInternalsForTest; -- sessionInternals._pendingClientToolCalls.get = () => { +- sessionInternals._pendingClientToolCalls.register = () => { - throw new Error('client tool boom'); - }; - @@ -12320,7 +16039,7 @@ index 3ba908cae5d..00000000000 - } as SessionEventPayload<'tool.execution_start'>['data']); - - // tool_start should have fired -- assert.strictEqual(signals.filter(s => isAction(s, ActionType.SessionToolCallStart)).length, 1); +- assert.strictEqual(signals.filter(s => isAction(s, ActionType.ChatToolCallStart)).length, 1); - - // Permission before the handler should produce only the confirmation - // pending_confirmation, not a synthetic auto-ready. @@ -12359,6 +16078,199 @@ index 3ba908cae5d..00000000000 - // Text content should be extracted - assert.strictEqual(result.textResultForLlm, 'text part'); - }); +- +- test('handleClientToolCallComplete describes embedded-resource-only content', async () => { +- const testCases = [ +- { +- toolCallId: 'tc-image-only', +- contentType: 'image/png', +- expectedText: 'Tool produced the attached image', +- expectedType: 'image', +- }, +- { +- toolCallId: 'tc-file-only', +- contentType: 'application/pdf', +- expectedText: 'Tool produced the attached file', +- expectedType: 'resource', +- }, +- { +- toolCallId: 'tc-image-and-file', +- contentType: 'image/png', +- additionalContentType: 'application/pdf', +- expectedText: 'Tool produced the attached image and file', +- expectedType: 'image', +- }, +- ] satisfies ReadonlyArray<{ +- readonly toolCallId: string; +- readonly contentType: string; +- readonly additionalContentType?: string; +- readonly expectedText: string; +- readonly expectedType: 'image' | 'resource'; +- }>; +- const embeddedResource = (data: string, contentType: string): ToolResultContent => ({ type: ToolResultContentType.EmbeddedResource, data, contentType }); +- +- for (const testCase of testCases) { +- const { session, runtime } = await createAgentSession(disposables, { clientSnapshot: snapshot }); +- const tools = runtime.createClientSdkTools(); +- const handlerPromise = invokeClientToolHandler(tools[0], testCase.toolCallId); +- const content: ToolResultContent[] = [ +- embeddedResource('base64data', testCase.contentType), +- ...(testCase.additionalContentType ? [embeddedResource('base64data2', testCase.additionalContentType)] : []), +- ]; +- +- session.handleClientToolCallComplete(testCase.toolCallId, { +- success: true, +- pastTenseMessage: 'done', +- content, +- }); +- +- assert.deepStrictEqual(await handlerPromise, { +- textResultForLlm: testCase.expectedText, +- resultType: 'success', +- binaryResultsForLlm: [ +- { data: 'base64data', mimeType: testCase.contentType, type: testCase.expectedType }, +- ...(testCase.additionalContentType ? [{ data: 'base64data2', mimeType: testCase.additionalContentType, type: 'resource' }] : []), +- ], +- }); +- disposables.clear(); +- } +- }); +- +- test('client tool start stamps the LIVE clientId from the shared ActiveClientState', async () => { +- const activeClientState = new ActiveClientState(); +- activeClientState.update('client-A', snapshot.tools); +- const { mockSession, signals } = await createAgentSession(disposables, { clientSnapshot: snapshot, activeClientState }); +- +- mockSession.fire('tool.execution_start', { +- toolCallId: 'tc-live-1', +- toolName: 'my_tool', +- arguments: {}, +- } as SessionEventPayload<'tool.execution_start'>['data']); +- +- // A window reload re-pushes the same tools under a new clientId. +- activeClientState.update('client-B', snapshot.tools); +- mockSession.fire('tool.execution_start', { +- toolCallId: 'tc-live-2', +- toolName: 'my_tool', +- arguments: {}, +- } as SessionEventPayload<'tool.execution_start'>['data']); +- +- const starts = signals.filter((s): s is IAgentActionSignal => isAction(s, ActionType.ChatToolCallStart)); +- assert.deepStrictEqual(starts.map(s => (s.action as ChatToolCallStartAction).contributor), [ +- { kind: ToolCallContributorKind.Client, clientId: 'client-A' }, +- { kind: ToolCallContributorKind.Client, clientId: 'client-B' }, +- ]); +- }); +- +- test('completion arriving before the SDK handler registers still resolves', async () => { +- const { session, runtime } = await createAgentSession(disposables, { clientSnapshot: snapshot }); +- const tools = runtime.createClientSdkTools(); +- +- // Completion races ahead of the handler. +- session.handleClientToolCallComplete('tc-early', { +- success: true, +- pastTenseMessage: 'done', +- content: [{ type: ToolResultContentType.Text, text: 'buffered result' }], +- }); +- +- const result = await invokeClientToolHandler(tools[0], 'tc-early'); +- assert.strictEqual(result.resultType, 'success'); +- assert.strictEqual(result.textResultForLlm, 'buffered result'); +- }); +- }); +- +- // ---- Server tools ------------------------------------------------------- +- +- suite('server tools', () => { +- +- const fakeToolDefinitions: readonly ToolDefinition[] = [ +- { name: 'serverToolA', description: 'A', inputSchema: { type: 'object', properties: {} } }, +- { name: 'serverToolB', description: 'B', inputSchema: { type: 'object', properties: {} } }, +- ]; +- +- class FakeServerToolHost implements IAgentServerToolHost { +- readonly definitions: readonly ToolDefinition[] = fakeToolDefinitions; +- readonly toolNames: readonly string[] = fakeToolDefinitions.map(def => def.name); +- readonly advertised: string[] = []; +- readonly executions: Array<{ sessionUri: string; toolName: string; rawArgs: unknown }> = []; +- result = 'ok'; +- error: Error | undefined; +- +- advertise(sessionUri: string): void { +- this.advertised.push(sessionUri); +- } +- +- requiresConfirmation(_toolName: string): boolean { return false; } +- +- executeTool(sessionUri: string, toolName: string, rawArgs: unknown): string { +- this.executions.push({ sessionUri, toolName, rawArgs }); +- if (this.error) { +- throw this.error; +- } +- return this.result; +- } +- } +- +- test('advertises the server tools on initialize and exposes them as server SDK tools', async () => { +- const serverToolHost = new FakeServerToolHost(); +- const { runtime } = await createAgentSession(disposables, { serverToolHost }); +- +- const sessionUri = AgentSession.uri('copilot', 'test-session-1').toString(); +- assert.deepStrictEqual(serverToolHost.advertised, [sessionUri]); +- +- const tools = runtime.createServerSdkTools(); +- assert.deepStrictEqual(tools.map(t => t.name).sort(), [...serverToolHost.toolNames].sort()); +- }); +- +- test('server tool handler routes to the host and returns a success result', async () => { +- const serverToolHost = new FakeServerToolHost(); +- serverToolHost.result = 'listed 2 comments'; +- const { runtime } = await createAgentSession(disposables, { serverToolHost }); +- +- const tools = runtime.createServerSdkTools(); +- const result = await invokeClientToolHandler(tools[0], 'tc-server-tool', { foo: 'bar' }); +- +- const sessionUri = AgentSession.uri('copilot', 'test-session-1').toString(); +- assert.deepStrictEqual(serverToolHost.executions, [{ sessionUri, toolName: tools[0].name, rawArgs: { foo: 'bar' } }]); +- assert.strictEqual(result.resultType, 'success'); +- assert.strictEqual(result.textResultForLlm, 'listed 2 comments'); +- }); +- +- test('server tool handler surfaces host failures as a failure result', async () => { +- const serverToolHost = new FakeServerToolHost(); +- serverToolHost.error = new Error('boom'); +- const { runtime } = await createAgentSession(disposables, { serverToolHost }); +- +- const tools = runtime.createServerSdkTools(); +- const result = await invokeClientToolHandler(tools[0], 'tc-server-tool'); +- +- assert.strictEqual(result.resultType, 'failure'); +- assert.strictEqual(result.textResultForLlm, 'boom'); +- assert.strictEqual(result.error, 'boom'); +- }); +- +- test('exposes no server SDK tools and advertises nothing when no host is wired', async () => { +- const { runtime } = await createAgentSession(disposables); +- assert.deepStrictEqual(runtime.createServerSdkTools(), []); +- }); +- +- test('auto-approves every server tool without prompting for confirmation', async () => { +- const serverToolHost = new FakeServerToolHost(); +- const { runtime, signals } = await createAgentSession(disposables, { serverToolHost }); +- +- const results = []; +- for (const toolName of serverToolHost.toolNames) { +- results.push(await runtime.handlePermissionRequest({ kind: 'custom-tool', toolCallId: `tc-${toolName}`, toolName })); +- } +- +- assert.deepStrictEqual({ +- results, +- pendingConfirmations: signals.filter(s => s.kind === 'pending_confirmation').length, +- }, { +- results: serverToolHost.toolNames.map(() => ({ kind: 'approve-once' })), +- pendingConfirmations: 0, +- }); +- }); - }); - - // ---- Plan mode ---------------------------------------------------------- @@ -12399,12 +16311,13 @@ index 3ba908cae5d..00000000000 - - test('handleExitPlanModeRequest produces a single-select input request with options and recommended', async () => { - const { session, runtime, mockSession, signals, waitForSignal } = await createAgentSession(disposables); +- session.resetTurnState('turn-plan'); - - mockSession.planReadResult = { exists: true, content: '## Plan', path: '/sessions/abc/plan.md' }; - - const responsePromise = runtime.handleExitPlanModeRequest(planRequestParams(), { sessionId: 'test-session-1' }); - -- const signal = await waitForSignal(s => isAction(s, ActionType.SessionInputRequested)); +- const signal = await waitForSignal(s => isAction(s, ActionType.ChatInputRequested)); - const request = getInputRequest(signal); - - // The plan summary and "View full plan" link are emitted as a @@ -12412,12 +16325,12 @@ index 3ba908cae5d..00000000000 - // client renders them inline above the question. - const deltaContent = signals.flatMap(s => { - if (s.kind !== 'action') { return []; } -- if (s.action.type === ActionType.SessionResponsePart) { -- const part = (s.action as SessionResponsePartAction).part; +- if (s.action.type === ActionType.ChatResponsePart) { +- const part = (s.action as ChatResponsePartAction).part; - return part.kind === ResponsePartKind.Markdown ? [part.content] : []; - } -- if (s.action.type === ActionType.SessionDelta) { -- return [(s.action as SessionDeltaAction).content]; +- if (s.action.type === ActionType.ChatDelta) { +- return [(s.action as ChatDeltaAction).content]; - } - return []; - }).join(''); @@ -12425,8 +16338,8 @@ index 3ba908cae5d..00000000000 - assert.ok(deltaContent.includes('plan.md'), 'delta should include a link to the plan file'); - - const question = request.questions?.[0]; -- assert.strictEqual(question?.kind, SessionInputQuestionKind.SingleSelect); -- if (question?.kind === SessionInputQuestionKind.SingleSelect) { +- assert.strictEqual(question?.kind, ChatInputQuestionKind.SingleSelect); +- if (question?.kind === ChatInputQuestionKind.SingleSelect) { - assert.deepStrictEqual(question.options.map(o => o.id), ['autopilot', 'interactive', 'exit_only']); - const recommended = question.options.find(o => o.recommended); - assert.strictEqual(recommended?.id, 'autopilot'); @@ -12434,55 +16347,62 @@ index 3ba908cae5d..00000000000 - } - - // Resolve the request so the deferred completes and the test can clean up. -- session.respondToUserInputRequest(request.id, SessionInputResponseKind.Decline); +- session.respondToUserInputRequest(request.id, ChatInputResponseKind.Decline); - await responsePromise; - }); - -- test('completing the input request with autopilot resolves with approved + autopilot + autoApproveEdits', async () => { -- const { session, runtime, waitForSignal } = await createAgentSession(disposables); +- test('completing the input request with autopilot resolves with approved + autopilot + autoApproveEdits and syncs mode=autopilot', async () => { +- const { session, runtime, waitForSignal, sessionConfigUpdates } = await createAgentSession(disposables); - - const responsePromise = runtime.handleExitPlanModeRequest(planRequestParams({ actions: ['autopilot', 'interactive'], recommendedAction: 'autopilot' }), { sessionId: 'test-session-1' }); -- const signal = await waitForSignal(s => isAction(s, ActionType.SessionInputRequested)); +- const signal = await waitForSignal(s => isAction(s, ActionType.ChatInputRequested)); - const request = getInputRequest(signal); - const requestId = request.id; - const questionId = request.questions![0].id; - -- session.respondToUserInputRequest(requestId, SessionInputResponseKind.Accept, { +- session.respondToUserInputRequest(requestId, ChatInputResponseKind.Accept, { - [questionId]: { -- state: SessionInputAnswerState.Submitted, -- value: { kind: SessionInputAnswerValueKind.Selected, value: 'autopilot' }, +- state: ChatInputAnswerState.Submitted, +- value: { kind: ChatInputAnswerValueKind.Selected, value: 'autopilot' }, - }, - }); - - assert.deepStrictEqual(await responsePromise, { approved: true, selectedAction: 'autopilot', autoApproveEdits: true }); +- // Picking "Implement with Autopilot" flips the AHP mode immediately. +- assert.deepStrictEqual(sessionConfigUpdates, [ +- { session: 'copilot:/test-session-1', patch: { mode: 'autopilot' } }, +- ]); - }); - -- test('completing the input request with interactive resolves with approved + interactive (no autoApprove)', async () => { -- const { session, runtime, waitForSignal } = await createAgentSession(disposables); +- test('completing the input request with interactive resolves with approved + interactive (no autoApprove) and syncs mode=interactive', async () => { +- const { session, runtime, waitForSignal, sessionConfigUpdates } = await createAgentSession(disposables); - - const responsePromise = runtime.handleExitPlanModeRequest(planRequestParams({ actions: ['autopilot', 'interactive'], recommendedAction: 'interactive' }), { sessionId: 'test-session-1' }); -- const signal = await waitForSignal(s => isAction(s, ActionType.SessionInputRequested)); +- const signal = await waitForSignal(s => isAction(s, ActionType.ChatInputRequested)); - const request = getInputRequest(signal); - const requestId = request.id; - const questionId = request.questions![0].id; - -- session.respondToUserInputRequest(requestId, SessionInputResponseKind.Accept, { +- session.respondToUserInputRequest(requestId, ChatInputResponseKind.Accept, { - [questionId]: { -- state: SessionInputAnswerState.Submitted, -- value: { kind: SessionInputAnswerValueKind.Selected, value: 'interactive' }, +- state: ChatInputAnswerState.Submitted, +- value: { kind: ChatInputAnswerValueKind.Selected, value: 'interactive' }, - }, - }); - - assert.deepStrictEqual(await responsePromise, { approved: true, selectedAction: 'interactive' }); +- assert.deepStrictEqual(sessionConfigUpdates, [ +- { session: 'copilot:/test-session-1', patch: { mode: 'interactive' } }, +- ]); - }); - - test('declining the input request resolves with approved=false', async () => { - const { session, runtime, waitForSignal } = await createAgentSession(disposables); - - const responsePromise = runtime.handleExitPlanModeRequest(planRequestParams(), { sessionId: 'test-session-1' }); -- const signal = await waitForSignal(s => isAction(s, ActionType.SessionInputRequested)); +- const signal = await waitForSignal(s => isAction(s, ActionType.ChatInputRequested)); - -- session.respondToUserInputRequest(getInputRequest(signal).id, SessionInputResponseKind.Decline); +- session.respondToUserInputRequest(getInputRequest(signal).id, ChatInputResponseKind.Decline); - - assert.deepStrictEqual(await responsePromise, { approved: false }); - }); @@ -12491,15 +16411,15 @@ index 3ba908cae5d..00000000000 - const { session, runtime, waitForSignal } = await createAgentSession(disposables); - - const responsePromise = runtime.handleExitPlanModeRequest(planRequestParams({ actions: ['autopilot', 'interactive', 'exit_only'], recommendedAction: 'exit_only' }), { sessionId: 'test-session-1' }); -- const signal = await waitForSignal(s => isAction(s, ActionType.SessionInputRequested)); +- const signal = await waitForSignal(s => isAction(s, ActionType.ChatInputRequested)); - const request = getInputRequest(signal); - const requestId = request.id; - const questionId = request.questions![0].id; - -- session.respondToUserInputRequest(requestId, SessionInputResponseKind.Accept, { +- session.respondToUserInputRequest(requestId, ChatInputResponseKind.Accept, { - [questionId]: { -- state: SessionInputAnswerState.Submitted, -- value: { kind: SessionInputAnswerValueKind.Selected, value: 'exit_only' }, +- state: ChatInputAnswerState.Submitted, +- value: { kind: ChatInputAnswerValueKind.Selected, value: 'exit_only' }, - }, - }); - @@ -12510,16 +16430,16 @@ index 3ba908cae5d..00000000000 - const { session, runtime, waitForSignal } = await createAgentSession(disposables); - - const responsePromise = runtime.handleExitPlanModeRequest(planRequestParams({ actions: ['autopilot', 'interactive'], recommendedAction: 'interactive' }), { sessionId: 'test-session-1' }); -- const signal = await waitForSignal(s => isAction(s, ActionType.SessionInputRequested)); +- const signal = await waitForSignal(s => isAction(s, ActionType.ChatInputRequested)); - const request = getInputRequest(signal); - const requestId = request.id; - const questionId = request.questions![0].id; - -- session.respondToUserInputRequest(requestId, SessionInputResponseKind.Accept, { +- session.respondToUserInputRequest(requestId, ChatInputResponseKind.Accept, { - [questionId]: { -- state: SessionInputAnswerState.Submitted, +- state: ChatInputAnswerState.Submitted, - value: { -- kind: SessionInputAnswerValueKind.Selected, +- kind: ChatInputAnswerValueKind.Selected, - value: 'interactive', - freeformValues: ['Please use Python instead of Node.js'], - }, @@ -12537,7 +16457,7 @@ index 3ba908cae5d..00000000000 - const { session, runtime, waitForSignal } = await createAgentSession(disposables); - - const responsePromise = runtime.handleExitPlanModeRequest(planRequestParams({ actions: ['interactive', 'exit_only'], recommendedAction: 'interactive' }), { sessionId: 'test-session-1' }); -- const signal = await waitForSignal(s => isAction(s, ActionType.SessionInputRequested)); +- const signal = await waitForSignal(s => isAction(s, ActionType.ChatInputRequested)); - const request = getInputRequest(signal); - const requestId = request.id; - const questionId = request.questions![0].id; @@ -12546,10 +16466,10 @@ index 3ba908cae5d..00000000000 - // somehow sent `autopilot` (e.g. stale UI state). The agent - // host clamps to `recommendedAction` so the SDK never sees a - // value it didn't offer. -- session.respondToUserInputRequest(requestId, SessionInputResponseKind.Accept, { +- session.respondToUserInputRequest(requestId, ChatInputResponseKind.Accept, { - [questionId]: { -- state: SessionInputAnswerState.Submitted, -- value: { kind: SessionInputAnswerValueKind.Selected, value: 'autopilot' }, +- state: ChatInputAnswerState.Submitted, +- value: { kind: ChatInputAnswerValueKind.Selected, value: 'autopilot' }, - }, - }); - @@ -12563,15 +16483,15 @@ index 3ba908cae5d..00000000000 - // the offered set. The client picked something invalid. With - // no usable selectedAction and no feedback, decline. - const responsePromise = runtime.handleExitPlanModeRequest(planRequestParams({ actions: ['exit_only'], recommendedAction: 'autopilot' }), { sessionId: 'test-session-1' }); -- const signal = await waitForSignal(s => isAction(s, ActionType.SessionInputRequested)); +- const signal = await waitForSignal(s => isAction(s, ActionType.ChatInputRequested)); - const request = getInputRequest(signal); - const requestId = request.id; - const questionId = request.questions![0].id; - -- session.respondToUserInputRequest(requestId, SessionInputResponseKind.Accept, { +- session.respondToUserInputRequest(requestId, ChatInputResponseKind.Accept, { - [questionId]: { -- state: SessionInputAnswerState.Submitted, -- value: { kind: SessionInputAnswerValueKind.Selected, value: 'interactive' }, +- state: ChatInputAnswerState.Submitted, +- value: { kind: ChatInputAnswerValueKind.Selected, value: 'interactive' }, - }, - }); - @@ -12582,7 +16502,7 @@ index 3ba908cae5d..00000000000 - const { session, runtime, waitForSignal } = await createAgentSession(disposables); - - const responsePromise = runtime.handleExitPlanModeRequest(planRequestParams({ actions: ['autopilot', 'interactive'], recommendedAction: 'interactive' }), { sessionId: 'test-session-1' }); -- const signal = await waitForSignal(s => isAction(s, ActionType.SessionInputRequested)); +- const signal = await waitForSignal(s => isAction(s, ActionType.ChatInputRequested)); - const request = getInputRequest(signal); - const requestId = request.id; - const questionId = request.questions![0].id; @@ -12591,10 +16511,10 @@ index 3ba908cae5d..00000000000 - // value, but a defensive Text response should still be - // translated to a revision request when the answer is - // non-empty (selectedAction falls back to recommendedAction). -- session.respondToUserInputRequest(requestId, SessionInputResponseKind.Accept, { +- session.respondToUserInputRequest(requestId, ChatInputResponseKind.Accept, { - [questionId]: { -- state: SessionInputAnswerState.Submitted, -- value: { kind: SessionInputAnswerValueKind.Text, value: 'Add tests for edge cases' }, +- state: ChatInputAnswerState.Submitted, +- value: { kind: ChatInputAnswerValueKind.Text, value: 'Add tests for edge cases' }, - }, - }); - @@ -12609,16 +16529,16 @@ index 3ba908cae5d..00000000000 - const { session, runtime, waitForSignal } = await createAgentSession(disposables); - - const responsePromise = runtime.handleExitPlanModeRequest(planRequestParams({ actions: ['autopilot', 'interactive'], recommendedAction: 'interactive' }), { sessionId: 'test-session-1' }); -- const signal = await waitForSignal(s => isAction(s, ActionType.SessionInputRequested)); +- const signal = await waitForSignal(s => isAction(s, ActionType.ChatInputRequested)); - const request = getInputRequest(signal); - const requestId = request.id; - const questionId = request.questions![0].id; - -- session.respondToUserInputRequest(requestId, SessionInputResponseKind.Accept, { +- session.respondToUserInputRequest(requestId, ChatInputResponseKind.Accept, { - [questionId]: { -- state: SessionInputAnswerState.Submitted, +- state: ChatInputAnswerState.Submitted, - value: { -- kind: SessionInputAnswerValueKind.Selected, +- kind: ChatInputAnswerValueKind.Selected, - value: 'interactive', - freeformValues: [' ', ''], - }, @@ -12648,16 +16568,16 @@ index 3ba908cae5d..00000000000 - ]); - }); - -- test('session.mode_changed → autopilot translates to mode=interactive + autoApprove=autopilot', async () => { -- // The SDK has a first-class `autopilot` mode but AHP exposes it -- // as the `autopilot` value on the orthogonal `autoApprove` axis. -- // The translation is contained in the Copilot agent. +- test('session.mode_changed → autopilot maps directly to mode=autopilot', async () => { +- // The SDK and AHP share the same three-mode space; autopilot now +- // lives on the `mode` axis and the `autoApprove` axis is left +- // untouched. The translation is contained in the Copilot agent. - const { mockSession, sessionConfigUpdates } = await createAgentSession(disposables); - - mockSession.fire('session.mode_changed', { previousMode: 'plan', newMode: 'autopilot' } as SessionEventPayload<'session.mode_changed'>['data']); - - assert.deepStrictEqual(sessionConfigUpdates, [ -- { session: 'copilot:/test-session-1', patch: { mode: 'interactive', autoApprove: 'autopilot' } }, +- { session: 'copilot:/test-session-1', patch: { mode: 'autopilot' } }, - ]); - }); - @@ -12669,37 +16589,23 @@ index 3ba908cae5d..00000000000 - assert.strictEqual(sessionConfigUpdates.length, 0); - }); - -- // ---- autopilot fast-path ------------------------------------------- +- // ---- no automatic plan → implementation handoff ------------------- - -- test('handleExitPlanModeRequest auto-accepts when autoApprove=autopilot (recommended action)', async () => { -- const { runtime, signals } = await createAgentSession(disposables, { -- configValues: { [SessionConfigKey.AutoApprove]: 'autopilot' }, +- test('handleExitPlanModeRequest always surfaces the plan-review UI, even in autopilot mode', async () => { +- const { session, runtime, waitForSignal } = await createAgentSession(disposables, { +- configValues: { [SessionConfigKey.Mode]: 'autopilot' }, - }); - -- const response = await runtime.handleExitPlanModeRequest(planRequestParams({ +- const responsePromise = runtime.handleExitPlanModeRequest(planRequestParams({ - actions: ['autopilot', 'interactive', 'exit_only'], - recommendedAction: 'autopilot', - }), { sessionId: 'test-session-1' }); - -- assert.deepStrictEqual(response, { approved: true, selectedAction: 'autopilot', autoApproveEdits: true }); -- // User-input request should NOT be surfaced to the client. -- assert.strictEqual(signals.filter(s => isAction(s, ActionType.SessionInputRequested)).length, 0); -- }); -- -- test('handleExitPlanModeRequest auto-accepts with priority order when no recommended action available', async () => { -- const { runtime } = await createAgentSession(disposables, { -- configValues: { [SessionConfigKey.AutoApprove]: 'autopilot' }, -- }); -- -- // SDK proposes a recommended action that's NOT in the offered set — -- // fall back to the priority order (autopilot > autopilot_fleet > -- // interactive > exit_only). -- const response = await runtime.handleExitPlanModeRequest(planRequestParams({ -- actions: ['interactive', 'exit_only'], -- recommendedAction: 'autopilot_fleet', -- }), { sessionId: 'test-session-1' }); -- -- assert.deepStrictEqual(response, { approved: true, selectedAction: 'interactive' }); +- // There is no automatic handoff from plan into implementation: the +- // user must explicitly choose an action regardless of mode. +- const signal = await waitForSignal(s => isAction(s, ActionType.ChatInputRequested)); +- session.respondToUserInputRequest(getInputRequest(signal).id, ChatInputResponseKind.Decline); +- await responsePromise; - }); - - test('handleExitPlanModeRequest does NOT auto-accept when autoApprove=default', async () => { @@ -12710,18 +16616,64 @@ index 3ba908cae5d..00000000000 - const responsePromise = runtime.handleExitPlanModeRequest(planRequestParams(), { sessionId: 'test-session-1' }); - - // The user-input request fires — the user must respond. -- const signal = await waitForSignal(s => isAction(s, ActionType.SessionInputRequested)); -- session.respondToUserInputRequest(getInputRequest(signal).id, SessionInputResponseKind.Decline); +- const signal = await waitForSignal(s => isAction(s, ActionType.ChatInputRequested)); +- session.respondToUserInputRequest(getInputRequest(signal).id, ChatInputResponseKind.Decline); - await responsePromise; - }); - }); +- +- suite('MCP server inventory', () => { +- +- test('seeds inventory from rpc.mcp.list at subscription time', async () => { +- const { signals, waitForSignal } = await createAgentSession(disposables, { +- configureMockSession: m => { +- m.mcpListResult = { +- servers: [ +- { name: 'alpha', status: 'connected' }, +- { name: 'beta', status: 'pending' }, +- ], +- }; +- }, +- }); +- +- await waitForSignal(s => isAction(s, ActionType.SessionCustomizationUpdated)); +- // Give the seed's microtask chain time to apply both servers. +- await timeout(0); +- +- const updates = getActions(signals).filter(a => a.type === ActionType.SessionCustomizationUpdated); +- const names = updates.map(a => (a as { customization: { name: string } }).customization.name).sort(); +- assert.deepStrictEqual(names, ['alpha', 'beta']); +- }); +- +- test('logs a warning and continues when rpc.mcp.list rejects', async () => { +- const logService = new CapturingLogService(); +- const { mockSession, waitForSignal } = await createAgentSession(disposables, { +- logService, +- configureMockSession: m => { m.mcpListError = new Error('boom'); }, +- }); +- // Allow the rejected promise to surface. +- await timeout(0); +- await timeout(0); +- +- assert.ok( +- logService.warnings.some(w => w.message.includes('Failed to seed MCP server inventory')), +- `expected seed-failure warning, got: ${JSON.stringify(logService.warnings)}`, +- ); +- +- // Subsequent live events still flow through the normal pipeline. +- mockSession.fire('session.mcp_servers_loaded', { +- servers: [{ name: 'late', status: 'connected' }], +- } as SessionEventPayload<'session.mcp_servers_loaded'>['data']); +- await waitForSignal(s => isAction(s, ActionType.SessionCustomizationUpdated)); +- }); +- }); -}); diff --git a/src/vs/platform/agentHost/test/node/copilotPluginConverters.test.ts b/src/vs/platform/agentHost/test/node/copilotPluginConverters.test.ts deleted file mode 100644 -index 6c51573d1ff..00000000000 +index 21baa7f1..00000000 --- a/src/vs/platform/agentHost/test/node/copilotPluginConverters.test.ts +++ /dev/null -@@ -1,448 +0,0 @@ +@@ -1,511 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. @@ -12739,7 +16691,7 @@ index 6c51573d1ff..00000000000 -import { InMemoryFileSystemProvider } from '../../../files/common/inMemoryFilesystemProvider.js'; -import { NullLogService } from '../../../log/common/log.js'; -import { McpServerType } from '../../../mcp/common/mcpPlatformTypes.js'; --import { toSdkInstructionDirectories, toSdkMcpServers, toSdkCustomAgents, toSdkSkillDirectories, parsedPluginsEqual, toSdkHooks } from '../../node/copilot/copilotPluginConverters.js'; +-import { toSdkInstructionDirectories, toSdkMcpServers, toSdkCustomAgents, toSdkSessionCustomAgents, toSdkSkillDirectories, parsedPluginsEqual, toSdkHooks, type IPluginAgentsForSdk } from '../../node/copilot/copilotPluginConverters.js'; -import type { IMcpServerDefinition, INamedPluginResource, IParsedHookGroup, IParsedPlugin, IParsedSkill } from '../../../agentPlugins/common/pluginParsers.js'; -import { CustomizationType, McpServerStatus, type HookCustomization, type McpServerCustomization, type SkillCustomization } from '../../common/state/protocol/state.js'; - @@ -12949,6 +16901,69 @@ index 6c51573d1ff..00000000000 - prompt: 'Body only.', - }]); - }); +- +- test('trims whitespace from frontmatter name to match parsed agent name', async () => { +- const agentUri = URI.from({ scheme: Schemas.inMemory, path: '/agents/padded.md' }); +- await fileService.writeFile(agentUri, VSBuffer.fromString( +- `---\nname: " Inbox "\n---\nBody.` +- )); +- +- const agents: INamedPluginResource[] = [{ uri: agentUri, name: 'padded' }]; +- const result = await toSdkCustomAgents(agents, fileService); +- +- assert.strictEqual(result[0].name, 'Inbox'); +- }); +- }); +- +- // ---- toSdkSessionCustomAgents --------------------------------------- +- +- suite('toSdkSessionCustomAgents', () => { +- +- test('includes agents from plugins without a file directory', async () => { +- const agentUri = URI.from({ scheme: Schemas.inMemory, path: '/loose/helper.md' }); +- await fileService.writeFile(agentUri, VSBuffer.fromString('Loose agent')); +- +- const plugins: IPluginAgentsForSdk[] = [{ agents: [{ uri: agentUri, name: 'helper' }] }]; +- const result = await toSdkSessionCustomAgents(plugins, undefined, fileService); +- +- assert.deepStrictEqual(result, [{ name: 'helper', tools: null, prompt: 'Loose agent' }]); +- }); +- +- test('excludes file-dir plugin agents when none is selected', async () => { +- const agentUri = URI.from({ scheme: Schemas.inMemory, path: '/plugin/inbox.md' }); +- await fileService.writeFile(agentUri, VSBuffer.fromString('Inbox agent')); +- +- const plugins: IPluginAgentsForSdk[] = [{ +- pluginDir: URI.file('/plugins/inbox'), +- agents: [{ uri: agentUri, name: 'Inbox' }], +- }]; +- const result = await toSdkSessionCustomAgents(plugins, undefined, fileService); +- +- assert.deepStrictEqual(result, []); +- }); +- +- test('forces the selected file-dir plugin agent into customAgents', async () => { +- const agentUri = URI.from({ scheme: Schemas.inMemory, path: '/plugin/inbox.md' }); +- await fileService.writeFile(agentUri, VSBuffer.fromString('Inbox agent')); +- +- const plugins: IPluginAgentsForSdk[] = [{ +- pluginDir: URI.file('/plugins/inbox'), +- agents: [{ uri: agentUri, name: 'Inbox' }], +- }]; +- const result = await toSdkSessionCustomAgents(plugins, 'Inbox', fileService); +- +- assert.deepStrictEqual(result, [{ name: 'Inbox', tools: null, prompt: 'Inbox agent' }]); +- }); +- +- test('does not duplicate the selected agent when already present', async () => { +- const agentUri = URI.from({ scheme: Schemas.inMemory, path: '/loose/helper.md' }); +- await fileService.writeFile(agentUri, VSBuffer.fromString('Loose agent')); +- +- const plugins: IPluginAgentsForSdk[] = [{ agents: [{ uri: agentUri, name: 'helper' }] }]; +- const result = await toSdkSessionCustomAgents(plugins, 'helper', fileService); +- +- assert.deepStrictEqual(result, [{ name: 'helper', tools: null, prompt: 'Loose agent' }]); +- }); - }); - - // ---- toSdkSkillDirectories ------------------------------------------ @@ -13172,10 +17187,10 @@ index 6c51573d1ff..00000000000 -}); diff --git a/src/vs/platform/agentHost/test/node/copilotShellTools.test.ts b/src/vs/platform/agentHost/test/node/copilotShellTools.test.ts deleted file mode 100644 -index 93f0cfbe1e4..00000000000 +index 5f6c455d..00000000 --- a/src/vs/platform/agentHost/test/node/copilotShellTools.test.ts +++ /dev/null -@@ -1,978 +0,0 @@ +@@ -1,944 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. @@ -13300,6 +17315,7 @@ index 93f0cfbe1e4..00000000000 - getRootValue: ((_schema: unknown, key: string) => configValues[key]) as IAgentConfigurationService['getRootValue'], - updateRootConfig: () => { /* no-op */ }, - persistRootConfig: () => { /* no-op */ }, +- whenIdle: async () => { /* no-op */ }, - }; - return { - service, @@ -14114,52 +18130,17 @@ index 93f0cfbe1e4..00000000000 - assert.strictEqual(result.resultType, 'failure'); - assert.strictEqual(result.error, 'unsandboxed_disabled'); - assert.match(result.textResultForLlm ?? '', /allowUnsandboxedCommands/); -- assert.strictEqual(confirmationRequests.length, 0, 'No confirmation should have been requested'); -- assert.strictEqual(terminalManager.sentTexts.length, 0, 'Disallowed command should not be sent to the terminal'); -- }); -- -- test('primary shell tool skips confirmation when autoApproveUnsandboxedCommands is enabled', async function () { -- const { instantiationService, terminalManager, agentConfigurationService } = createServices({ sandboxEnabled: true }); -- agentConfigurationService.setSandboxValue(AgentHostSandboxKey.AllowUnsandboxedCommands, true); -- agentConfigurationService.setSandboxValue(AgentHostSandboxKey.AutoApproveUnsandboxedCommands, true); -- terminalManager.commandDetectionSupported = true; -- const shellManager = disposables.add(instantiationService.createInstance(ShellManager, URI.parse('copilot:/session-1'), undefined)); -- const confirmationRequests: IUnsandboxedCommandConfirmationRequest[] = []; -- const tools = await createShellTools(shellManager, terminalManager, new NullLogService(), async request => { -- confirmationRequests.push(request); -- return true; -- }); -- const bashTool = tools.find(tool => tool.name === 'bash'); -- assert.ok(bashTool); -- -- const invocation: ToolInvocation = { -- sessionId: 'session-1', -- toolCallId: 'tool-1', -- toolName: 'bash', -- arguments: { command: 'curl https://example.com' }, -- }; -- const resultPromise = bashTool.handler!({ command: 'curl https://example.com' }, invocation); -- await terminalManager.commandFinishedListenerRegistered.p; -- terminalManager.fireCommandFinished({ -- commandId: 'cmd-1', -- exitCode: 0, -- command: 'curl https://example.com', -- output: '', -- }); -- const result = await resultPromise as ToolResultObject; -- -- assert.strictEqual(confirmationRequests.length, 0, 'No confirmation should have been requested when auto-approve is enabled'); -- assert.ok(terminalManager.sentTexts.length >= 1, 'Auto-approved command should be sent to the terminal unsandboxed'); -- assert.ok(terminalManager.sentTexts.every(entry => !entry.data.includes('sandbox-runtime')), 'Auto-approved command should run unsandboxed'); -- assert.strictEqual(result.resultType, 'success'); +- assert.strictEqual(confirmationRequests.length, 0, 'No confirmation should have been requested'); +- assert.strictEqual(terminalManager.sentTexts.length, 0, 'Disallowed command should not be sent to the terminal'); - }); +- -}); diff --git a/src/vs/platform/agentHost/test/node/copilotSlashCommandCompletionProvider.test.ts b/src/vs/platform/agentHost/test/node/copilotSlashCommandCompletionProvider.test.ts deleted file mode 100644 -index 7578ef4d294..00000000000 +index 70d60420..00000000 --- a/src/vs/platform/agentHost/test/node/copilotSlashCommandCompletionProvider.test.ts +++ /dev/null -@@ -1,198 +0,0 @@ +@@ -1,295 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. @@ -14197,14 +18178,50 @@ index 7578ef4d294..00000000000 - assert.deepStrictEqual(parseLeadingSlashCommand('/rubber-duck'), { command: 'rubber-duck', rest: '' }); - }); - +- test('matches lone /env', () => { +- assert.deepStrictEqual(parseLeadingSlashCommand('/env'), { command: 'env', rest: '' }); +- }); +- +- test('matches lone /review', () => { +- assert.deepStrictEqual(parseLeadingSlashCommand('/review'), { command: 'review', rest: '' }); +- }); +- +- test('matches lone /security-review', () => { +- assert.deepStrictEqual(parseLeadingSlashCommand('/security-review'), { command: 'security-review', rest: '' }); +- }); +- - test('captures trailing text after a space for /rubber-duck', () => { - assert.deepStrictEqual(parseLeadingSlashCommand('/rubber-duck review my approach'), { command: 'rubber-duck', rest: 'review my approach' }); - }); - +- test('captures trailing text after a space for /env', () => { +- assert.deepStrictEqual(parseLeadingSlashCommand('/env ignored input'), { command: 'env', rest: 'ignored input' }); +- }); +- +- test('captures trailing text after a space for /review', () => { +- assert.deepStrictEqual(parseLeadingSlashCommand('/review focus on tests'), { command: 'review', rest: 'focus on tests' }); +- }); +- +- test('captures trailing text after a space for /security-review', () => { +- assert.deepStrictEqual(parseLeadingSlashCommand('/security-review focus on auth'), { command: 'security-review', rest: 'focus on auth' }); +- }); +- - test('rejects /rubber-duck-extra (no separator)', () => { - assert.strictEqual(parseLeadingSlashCommand('/rubber-duck-extra'), undefined); - }); - +- test('rejects /env-extra (no separator)', () => { +- assert.strictEqual(parseLeadingSlashCommand('/env-extra'), undefined); +- }); +- +- test('rejects /review-extra (no separator)', () => { +- assert.strictEqual(parseLeadingSlashCommand('/review-extra'), undefined); +- }); +- +- test('rejects /security-review-extra (no separator)', () => { +- assert.strictEqual(parseLeadingSlashCommand('/security-review-extra'), undefined); +- }); +- - test('rejects /rubber alone (incomplete command)', () => { - assert.strictEqual(parseLeadingSlashCommand('/rubber'), undefined); - }); @@ -14235,7 +18252,7 @@ index 7578ef4d294..00000000000 - }); - - suite('provideCompletionItems', () => { -- const provider = new CopilotSlashCommandCompletionProvider('copilotcli', { hasHistory: () => true, isRubberDuckEnabled: () => true }); +- const provider = new CopilotSlashCommandCompletionProvider('copilotcli', { hasHistory: () => true, isRubberDuckEnabled: () => true, hasRuntimeSlashCommand: async () => true }); - const session = 'copilotcli:/abc'; - - async function run(text: string, offset = text.length) { @@ -14252,727 +18269,1254 @@ index 7578ef4d294..00000000000 - assert.deepStrictEqual(items, []); - }); - -- test('returns all items for lone "/"', async () => { -- const items = await run('/'); -- assert.deepStrictEqual(items.map(i => i.insertText), ['/plan ', '/compact', '/research ', '/rubber-duck ']); +- test('returns all items for lone "/"', async () => { +- const items = await run('/'); +- assert.deepStrictEqual(items.map(i => i.insertText), ['/plan ', '/compact', '/research ', '/rubber-duck ', '/env', '/review ', '/security-review ']); +- }); +- +- test('filters to /plan when "/p" typed', async () => { +- const items = await run('/p'); +- assert.deepStrictEqual(items.map(i => i.insertText), ['/plan ']); +- }); +- +- test('filters to /compact when "/c" typed', async () => { +- const items = await run('/c'); +- assert.deepStrictEqual(items.map(i => i.insertText), ['/compact']); +- }); +- +- test('filters to /env when "/e" typed and runtime command exists', async () => { +- const items = await run('/e'); +- assert.deepStrictEqual(items.map(i => i.insertText), ['/env']); +- }); +- +- test('filters to /research and /rubber-duck when "/r" typed', async () => { +- const items = await run('/r'); +- assert.deepStrictEqual(items.map(i => i.insertText), ['/research ', '/rubber-duck ', '/review ']); +- }); +- +- test('filters to /security-review when "/s" typed', async () => { +- const items = await run('/s'); +- assert.deepStrictEqual(items.map(i => i.insertText), ['/security-review ']); +- }); +- +- test('returns nothing when /word does not match any command prefix', async () => { +- const items = await run('/zz'); +- assert.deepStrictEqual(items, []); +- }); +- +- test('returns nothing when input does not start with /', async () => { +- const items = await run('hello /pl', 9); +- assert.deepStrictEqual(items, []); +- }); +- +- test('returns nothing when cursor is past the leading word', async () => { +- // Cursor sits after the trailing space, no longer in the slash token. +- const items = await run('/plan ', 6); +- assert.deepStrictEqual(items, []); +- }); +- +- test('range covers only the leading slash word', async () => { +- const items = await run('/p extra text', 2); +- assert.strictEqual(items.length, 1); +- assert.strictEqual(items[0].rangeStart, 0); +- assert.strictEqual(items[0].rangeEnd, 2); +- }); +- +- test('attachment is Simple with command + description meta', async () => { +- const items = await run('/'); +- assert.deepStrictEqual(items.map(item => ({ type: item.attachment?.type, meta: item.attachment?._meta })), [ +- { +- type: MessageAttachmentKind.Simple, +- meta: { +- command: 'plan', +- description: 'Create an implementation plan before coding', +- }, +- }, +- { +- type: MessageAttachmentKind.Simple, +- meta: { +- command: 'compact', +- description: 'Free up context by compacting the conversation history', +- }, +- }, +- { +- type: MessageAttachmentKind.Simple, +- meta: { +- command: 'research', +- description: 'Run deep research on a topic using search and web sources', +- }, +- }, +- { +- type: MessageAttachmentKind.Simple, +- meta: { +- command: 'rubber-duck', +- description: 'Get an independent critique of the current approach', +- }, +- }, +- { +- type: MessageAttachmentKind.Simple, +- meta: { +- command: 'env', +- description: 'Show loaded environment details', +- }, +- }, +- { +- type: MessageAttachmentKind.Simple, +- meta: { +- command: 'review', +- description: 'Run code review agent to analyze changes', +- }, +- }, +- { +- type: MessageAttachmentKind.Simple, +- meta: { +- command: 'security-review', +- description: 'Analyze staged and unstaged changes for security vulnerabilities', +- }, +- }, +- ]); +- }); +- +- test('omits /compact when session has no history', async () => { +- const gated = new CopilotSlashCommandCompletionProvider('copilotcli', { hasHistory: () => false, isRubberDuckEnabled: () => true, hasRuntimeSlashCommand: async () => true }); +- const items = await gated.provideCompletionItems({ +- kind: CompletionItemKind.UserMessage, channel: session, text: '/', offset: 1, +- }, CancellationToken.None); +- assert.deepStrictEqual(items.map(i => i.insertText), ['/plan ', '/research ', '/rubber-duck ', '/env', '/review ', '/security-review ']); +- }); +- +- test('omits /rubber-duck when not enabled', async () => { +- const gated = new CopilotSlashCommandCompletionProvider('copilotcli', { hasHistory: () => true, isRubberDuckEnabled: () => false, hasRuntimeSlashCommand: async () => true }); +- const items = await gated.provideCompletionItems({ +- kind: CompletionItemKind.UserMessage, channel: session, text: '/', offset: 1, +- }, CancellationToken.None); +- assert.deepStrictEqual(items.map(i => i.insertText), ['/plan ', '/compact', '/research ', '/env', '/review ', '/security-review ']); +- }); +- +- test('omits /env when runtime command is unavailable', async () => { +- const gated = new CopilotSlashCommandCompletionProvider('copilotcli', { hasHistory: () => true, isRubberDuckEnabled: () => true, hasRuntimeSlashCommand: async (_id, command) => command !== 'env' }); +- const items = await gated.provideCompletionItems({ +- kind: CompletionItemKind.UserMessage, channel: session, text: '/', offset: 1, +- }, CancellationToken.None); +- assert.deepStrictEqual(items.map(i => i.insertText), ['/plan ', '/compact', '/research ', '/rubber-duck ', '/review ', '/security-review ']); +- }); +- +- test('keeps prompt-invoked commands when runtime commands are unavailable', async () => { +- const gated = new CopilotSlashCommandCompletionProvider('copilotcli', { hasHistory: () => true, isRubberDuckEnabled: () => true, hasRuntimeSlashCommand: async (_id, command) => command === 'env' }); +- const items = await gated.provideCompletionItems({ +- kind: CompletionItemKind.UserMessage, channel: session, text: '/', offset: 1, +- }, CancellationToken.None); +- assert.deepStrictEqual(items.map(i => i.insertText), ['/plan ', '/compact', '/research ', '/rubber-duck ', '/env', '/review ', '/security-review ']); +- }); +- +- test('passes raw session id (no scheme/slash) to hasHistory', async () => { +- let seen: string | undefined; +- const gated = new CopilotSlashCommandCompletionProvider('copilotcli', { +- hasHistory: (id: string) => { seen = id; return true; }, +- isRubberDuckEnabled: () => true, +- hasRuntimeSlashCommand: async () => true, +- }); +- await gated.provideCompletionItems({ +- kind: CompletionItemKind.UserMessage, channel: 'copilotcli:/abc', text: '/', offset: 1, +- }, CancellationToken.None); +- assert.strictEqual(seen, 'abc'); +- }); +- +- test('passes raw session id to runtime command availability', async () => { +- let seen: string | undefined; +- const gated = new CopilotSlashCommandCompletionProvider('copilotcli', { +- hasHistory: () => true, +- isRubberDuckEnabled: () => true, +- hasRuntimeSlashCommand: async (id: string) => { seen = id; return true; }, +- }); +- await gated.provideCompletionItems({ +- kind: CompletionItemKind.UserMessage, channel: 'copilotcli:/abc', text: '/e', offset: 2, +- }, CancellationToken.None); +- assert.strictEqual(seen, 'abc'); +- }); +- }); +-}); +diff --git a/src/vs/platform/agentHost/test/node/copilotTestEvents.ts b/src/vs/platform/agentHost/test/node/copilotTestEvents.ts +index 4e64f874..f3065c70 100644 +--- a/src/vs/platform/agentHost/test/node/copilotTestEvents.ts ++++ b/src/vs/platform/agentHost/test/node/copilotTestEvents.ts +@@ -3,7 +3,13 @@ + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +-import type { Attachment, SessionEvent } from '@github/copilot-sdk'; ++// `Attachment`/`SessionEvent` originally came from '@github/copilot-sdk' ++// (removed in !!APP_NAME!!). This fixture only uses them as opaque types it ++// casts into, so alias them to `any` locally. ++/* eslint-disable @typescript-eslint/no-explicit-any */ ++type Attachment = any; ++type SessionEvent = any; ++/* eslint-enable @typescript-eslint/no-explicit-any */ + + // ============================================================================= + // Minimal session-event shapes for tests +diff --git a/src/vs/platform/agentHost/test/node/copilotTokenFields.test.ts b/src/vs/platform/agentHost/test/node/copilotTokenFields.test.ts +deleted file mode 100644 +index a9d124e4..00000000 +--- a/src/vs/platform/agentHost/test/node/copilotTokenFields.test.ts ++++ /dev/null +@@ -1,77 +0,0 @@ +-/*--------------------------------------------------------------------------------------------- +- * Copyright (c) Microsoft Corporation. All rights reserved. +- * Licensed under the MIT License. See License.txt in the project root for license information. +- *--------------------------------------------------------------------------------------------*/ +- +-import assert from 'assert'; +-import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +-import { isRestrictedTelemetryEnabled, parseCopilotTokenFields } from '../../node/copilot/copilotTokenFields.js'; +- +-suite('copilotTokenFields', () => { +- +- ensureNoDisposablesAreLeakedInTestSuite(); +- +- suite('parseCopilotTokenFields', () => { +- test('returns empty map for undefined token', () => { +- assert.strictEqual(parseCopilotTokenFields(undefined).size, 0); +- }); +- +- test('returns empty map for empty token', () => { +- assert.strictEqual(parseCopilotTokenFields('').size, 0); +- }); +- +- test('parses fields from the leading colon-delimited segment', () => { +- const fields = parseCopilotTokenFields('tid=abc;exp=123;rt=1:HMACSIGNATURE'); +- assert.strictEqual(fields.get('tid'), 'abc'); +- assert.strictEqual(fields.get('exp'), '123'); +- assert.strictEqual(fields.get('rt'), '1'); +- }); +- +- test('parses fields when no colon separator is present', () => { +- const fields = parseCopilotTokenFields('tid=abc;rt=1'); +- assert.strictEqual(fields.get('tid'), 'abc'); +- assert.strictEqual(fields.get('rt'), '1'); +- }); +- +- test('skips segments without a value separator', () => { +- const fields = parseCopilotTokenFields('tid=abc;rt;exp=123:HMAC'); +- assert.strictEqual(fields.has('rt'), false); +- assert.strictEqual(fields.get('tid'), 'abc'); +- assert.strictEqual(fields.get('exp'), '123'); +- }); +- }); +- +- suite('isRestrictedTelemetryEnabled', () => { +- test('false for undefined token', () => { +- assert.strictEqual(isRestrictedTelemetryEnabled(undefined), false); +- }); +- +- test('false for empty token', () => { +- assert.strictEqual(isRestrictedTelemetryEnabled(''), false); +- }); +- +- test('false when rt field is missing', () => { +- assert.strictEqual(isRestrictedTelemetryEnabled('tid=abc;exp=123:HMAC'), false); +- }); +- +- test('false when rt=0', () => { +- assert.strictEqual(isRestrictedTelemetryEnabled('tid=abc;rt=0;exp=123:HMAC'), false); +- }); +- +- test('true when rt=1 with other fields', () => { +- assert.strictEqual(isRestrictedTelemetryEnabled('tid=abc;rt=1;exp=123:HMAC'), true); +- }); +- +- test('true when rt=1 is the first field', () => { +- assert.strictEqual(isRestrictedTelemetryEnabled('rt=1;tid=abc:HMAC'), true); +- }); +- +- test('true when rt=1 is the last field', () => { +- assert.strictEqual(isRestrictedTelemetryEnabled('tid=abc;exp=123;rt=1:HMAC'), true); +- }); +- +- test('true when token has no colon-delimited signature segment', () => { +- assert.strictEqual(isRestrictedTelemetryEnabled('tid=abc;rt=1'), true); +- }); +- }); +-}); +diff --git a/src/vs/platform/agentHost/test/node/sandboxConfigForSdk.test.ts b/src/vs/platform/agentHost/test/node/sandboxConfigForSdk.test.ts +deleted file mode 100644 +index e81afe38..00000000 +--- a/src/vs/platform/agentHost/test/node/sandboxConfigForSdk.test.ts ++++ /dev/null +@@ -1,242 +0,0 @@ +-/*--------------------------------------------------------------------------------------------- +- * Copyright (c) Microsoft Corporation. All rights reserved. +- * Licensed under the MIT License. See License.txt in the project root for license information. +- *--------------------------------------------------------------------------------------------*/ +- +-import assert from 'assert'; +-import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; +-import { AgentHostSandboxKey, type ISandboxConfigValue } from '../../common/sandboxConfigSchema.js'; +-import { AgentSandboxEnabledValue } from '../../../sandbox/common/settings.js'; +-import { buildSandboxConfigForSdk, type IAgentSandboxFileSystemSetting } from '../../node/copilot/sandboxConfigForSdk.js'; +- +-/** +- * Build the host-side `sandbox` root-config bag (the shape the workbench +- * forwarder dispatches in a `RootConfigChanged` action) for the given +- * `enabled` enum + optional per-OS filesystem rules and network host lists. +- * +- * Mirrors the per-OS dispatch in the Copilot extension's +- * `buildSandboxConfigForCLI` tests — the SDK helper consumes the same fields +- * but receives them via the host root bag instead of the per-OS keyed +- * object. +- */ +-function sandbox( +- platform: NodeJS.Platform, +- enabled: AgentSandboxEnabledValue | undefined, +- fs?: IAgentSandboxFileSystemSetting, +- hosts?: { allowedHosts?: readonly string[]; blockedHosts?: readonly string[] }, +-): ISandboxConfigValue | undefined { +- if (!enabled && !fs && !hosts) { +- return undefined; +- } +- const cfg: ISandboxConfigValue = {}; +- if (enabled !== undefined) { +- if (platform === 'win32') { +- cfg[AgentHostSandboxKey.WindowsEnabled] = enabled; +- } else { +- cfg[AgentHostSandboxKey.Enabled] = enabled; +- } +- } +- if (fs) { +- const fsKey = platform === 'win32' +- ? AgentHostSandboxKey.WindowsFileSystem +- : platform === 'darwin' +- ? AgentHostSandboxKey.MacFileSystem +- : AgentHostSandboxKey.LinuxFileSystem; +- cfg[fsKey] = fs as Record; +- } +- if (hosts?.allowedHosts?.length) { +- cfg[AgentHostSandboxKey.AllowedNetworkDomains] = [...hosts.allowedHosts]; +- } +- if (hosts?.blockedHosts?.length) { +- cfg[AgentHostSandboxKey.DeniedNetworkDomains] = [...hosts.blockedHosts]; +- } +- return cfg; +-} +- +-suite('buildSandboxConfigForSdk', () => { +- ensureNoDisposablesAreLeakedInTestSuite(); +- +- suite('enablement', () => { +- test('returns undefined when no setting is set', () => { +- assert.strictEqual(buildSandboxConfigForSdk('darwin', undefined), undefined); +- assert.strictEqual(buildSandboxConfigForSdk('win32', undefined), undefined); +- }); +- +- test('returns undefined when the bag is empty', () => { +- assert.strictEqual(buildSandboxConfigForSdk('darwin', {}), undefined); +- assert.strictEqual(buildSandboxConfigForSdk('win32', {}), undefined); +- }); +- +- test('returns undefined for `off`', () => { +- assert.strictEqual(buildSandboxConfigForSdk('darwin', sandbox('darwin', AgentSandboxEnabledValue.Off)), undefined); +- assert.strictEqual(buildSandboxConfigForSdk('win32', sandbox('win32', AgentSandboxEnabledValue.Off)), undefined); +- }); +- +- test('enables sandbox for `on` on every platform', () => { +- for (const platform of ['darwin', 'linux', 'win32'] as const) { +- assert.deepStrictEqual(buildSandboxConfigForSdk(platform, sandbox(platform, AgentSandboxEnabledValue.On)), { +- enabled: true, +- userPolicy: { filesystem: {}, network: { allowOutbound: false } }, +- }); +- } +- }); +- +- test('enables sandbox and outbound network for `allowNetwork` on every platform', () => { +- for (const platform of ['darwin', 'linux', 'win32'] as const) { +- assert.deepStrictEqual(buildSandboxConfigForSdk(platform, sandbox(platform, AgentSandboxEnabledValue.AllowNetwork)), { +- enabled: true, +- userPolicy: { filesystem: {}, network: { allowOutbound: true } }, +- }); +- } +- }); +- +- test('on Windows, WindowsEnabled overrides the cross-platform Enabled key', () => { +- // Cross-platform value says off, but the Windows override says on +- // → Windows session honors the override. +- const cfg = { +- [AgentHostSandboxKey.Enabled]: AgentSandboxEnabledValue.Off, +- [AgentHostSandboxKey.WindowsEnabled]: AgentSandboxEnabledValue.On, +- }; +- assert.deepStrictEqual(buildSandboxConfigForSdk('win32', cfg), { +- enabled: true, +- userPolicy: { filesystem: {}, network: { allowOutbound: false } }, +- }); +- // Non-Windows ignores WindowsEnabled. +- assert.strictEqual(buildSandboxConfigForSdk('darwin', cfg), undefined); +- }); +- }); +- +- suite('filesystem policy', () => { +- test('selects the OS-specific slice from the per-OS filesystem keys', () => { +- const cfg: ISandboxConfigValue = { +- [AgentHostSandboxKey.Enabled]: AgentSandboxEnabledValue.On, +- [AgentHostSandboxKey.WindowsEnabled]: AgentSandboxEnabledValue.On, +- [AgentHostSandboxKey.LinuxFileSystem]: { allowWrite: ['/linux'] }, +- [AgentHostSandboxKey.MacFileSystem]: { allowWrite: ['/mac'] }, +- [AgentHostSandboxKey.WindowsFileSystem]: { allowWrite: ['C:\\win'] }, +- }; +- assert.deepStrictEqual(buildSandboxConfigForSdk('linux', cfg)?.userPolicy.filesystem, { readwritePaths: ['/linux'] }); +- assert.deepStrictEqual(buildSandboxConfigForSdk('darwin', cfg)?.userPolicy.filesystem, { readwritePaths: ['/mac'] }); +- assert.deepStrictEqual(buildSandboxConfigForSdk('win32', cfg)?.userPolicy.filesystem, { readwritePaths: ['C:\\win'] }); +- }); +- +- test('maps each setting to the corresponding SDK list', () => { +- const fs: IAgentSandboxFileSystemSetting = { +- allowWrite: ['/work'], +- allowRead: ['/read'], +- denyWrite: ['/readonly'], +- denyRead: ['/secret'], +- }; +- assert.deepStrictEqual(buildSandboxConfigForSdk('darwin', sandbox('darwin', AgentSandboxEnabledValue.On, fs)), { +- enabled: true, +- userPolicy: { +- filesystem: { +- readwritePaths: ['/work'], +- readonlyPaths: ['/readonly', '/read'], +- deniedPaths: ['/secret'], +- }, +- network: { allowOutbound: false }, +- }, +- }); +- }); +- +- test('omits filesystem lists that are empty', () => { +- assert.deepStrictEqual(buildSandboxConfigForSdk('darwin', sandbox('darwin', AgentSandboxEnabledValue.On, {})), { +- enabled: true, +- userPolicy: { filesystem: {}, network: { allowOutbound: false } }, +- }); - }); - -- test('filters to /plan when "/p" typed', async () => { -- const items = await run('/p'); -- assert.deepStrictEqual(items.map(i => i.insertText), ['/plan ']); +- test('denyRead wins over every other setting for the same path', () => { +- const fs: IAgentSandboxFileSystemSetting = { +- allowRead: ['/p'], +- allowWrite: ['/p'], +- denyWrite: ['/p'], +- denyRead: ['/p'], +- }; +- assert.deepStrictEqual(buildSandboxConfigForSdk('darwin', sandbox('darwin', AgentSandboxEnabledValue.On, fs))?.userPolicy.filesystem, { +- deniedPaths: ['/p'], +- }); - }); - -- test('filters to /compact when "/c" typed', async () => { -- const items = await run('/c'); -- assert.deepStrictEqual(items.map(i => i.insertText), ['/compact']); +- test('denyWrite wins over allowWrite / allowRead for the same path', () => { +- const fs: IAgentSandboxFileSystemSetting = { +- allowRead: ['/p'], +- allowWrite: ['/p'], +- denyWrite: ['/p'], +- }; +- assert.deepStrictEqual(buildSandboxConfigForSdk('darwin', sandbox('darwin', AgentSandboxEnabledValue.On, fs))?.userPolicy.filesystem, { +- readonlyPaths: ['/p'], +- }); - }); - -- test('filters to /research and /rubber-duck when "/r" typed', async () => { -- const items = await run('/r'); -- assert.deepStrictEqual(items.map(i => i.insertText), ['/research ', '/rubber-duck ']); +- test('allowWrite wins over allowRead for the same path', () => { +- const fs: IAgentSandboxFileSystemSetting = { +- allowRead: ['/p'], +- allowWrite: ['/p'], +- }; +- assert.deepStrictEqual(buildSandboxConfigForSdk('darwin', sandbox('darwin', AgentSandboxEnabledValue.On, fs))?.userPolicy.filesystem, { +- readwritePaths: ['/p'], +- }); - }); - -- test('returns nothing when /word does not match any command prefix', async () => { -- const items = await run('/zz'); -- assert.deepStrictEqual(items, []); +- test('keeps distinct paths in their own lists when settings overlap on some paths', () => { +- const fs: IAgentSandboxFileSystemSetting = { +- allowWrite: ['/work', '/shared'], +- denyWrite: ['/shared'], +- }; +- assert.deepStrictEqual(buildSandboxConfigForSdk('darwin', sandbox('darwin', AgentSandboxEnabledValue.On, fs))?.userPolicy.filesystem, { +- readwritePaths: ['/work'], +- readonlyPaths: ['/shared'], +- }); - }); +- }); - -- test('returns nothing when input does not start with /', async () => { -- const items = await run('hello /pl', 9); -- assert.deepStrictEqual(items, []); +- suite('network hosts', () => { +- test('forwards allowedHosts and opens outbound even when sandbox is `on`', () => { +- assert.deepStrictEqual(buildSandboxConfigForSdk('linux', sandbox('linux', AgentSandboxEnabledValue.On, undefined, { allowedHosts: ['github.com'] }))?.userPolicy.network, { +- allowOutbound: true, +- allowedHosts: ['github.com'], +- }); - }); - -- test('returns nothing when cursor is past the leading word', async () => { -- // Cursor sits after the trailing space, no longer in the slash token. -- const items = await run('/plan ', 6); -- assert.deepStrictEqual(items, []); +- test('ignores host lists when sandbox is `allowNetwork` (allow all)', () => { +- assert.deepStrictEqual(buildSandboxConfigForSdk('linux', sandbox('linux', AgentSandboxEnabledValue.AllowNetwork, undefined, { allowedHosts: ['a.example'], blockedHosts: ['b.example'] }))?.userPolicy.network, { +- allowOutbound: true, +- }); - }); - -- test('range covers only the leading slash word', async () => { -- const items = await run('/p extra text', 2); -- assert.strictEqual(items.length, 1); -- assert.strictEqual(items[0].rangeStart, 0); -- assert.strictEqual(items[0].rangeEnd, 2); +- test('forwards blockedHosts and opens outbound when only blockedHosts is set (deny-list-only allows non-denied domains)', () => { +- assert.deepStrictEqual(buildSandboxConfigForSdk('linux', sandbox('linux', AgentSandboxEnabledValue.On, undefined, { blockedHosts: ['evil.example'] }))?.userPolicy.network, { +- allowOutbound: true, +- blockedHosts: ['evil.example'], +- }); - }); - -- test('attachment is Simple with command + description meta', async () => { -- const items = await run('/'); -- assert.deepStrictEqual(items.map(item => ({ type: item.attachment?.type, meta: item.attachment?._meta })), [ -- { -- type: MessageAttachmentKind.Simple, -- meta: { -- command: 'plan', -- description: 'Create an implementation plan before coding', -- }, -- }, -- { -- type: MessageAttachmentKind.Simple, -- meta: { -- command: 'compact', -- description: 'Free up context by compacting the conversation history', -- }, -- }, -- { -- type: MessageAttachmentKind.Simple, -- meta: { -- command: 'research', -- description: 'Run deep research on a topic using search and web sources', -- }, -- }, -- { -- type: MessageAttachmentKind.Simple, -- meta: { -- command: 'rubber-duck', -- description: 'Get an independent critique of the current approach', -- }, -- }, -- ]); +- test('forwards both allowedHosts and blockedHosts together when sandbox is `on`', () => { +- assert.deepStrictEqual(buildSandboxConfigForSdk('linux', sandbox('linux', AgentSandboxEnabledValue.On, undefined, { allowedHosts: ['a.example'], blockedHosts: ['b.example'] }))?.userPolicy.network, { +- allowOutbound: true, +- allowedHosts: ['a.example'], +- blockedHosts: ['b.example'], +- }); - }); - -- test('omits /compact when session has no history', async () => { -- const gated = new CopilotSlashCommandCompletionProvider('copilotcli', { hasHistory: () => false, isRubberDuckEnabled: () => true }); -- const items = await gated.provideCompletionItems({ -- kind: CompletionItemKind.UserMessage, channel: session, text: '/', offset: 1, -- }, CancellationToken.None); -- assert.deepStrictEqual(items.map(i => i.insertText), ['/plan ', '/research ', '/rubber-duck ']); +- test('ignores empty host lists', () => { +- assert.deepStrictEqual(buildSandboxConfigForSdk('linux', sandbox('linux', AgentSandboxEnabledValue.On, undefined, { allowedHosts: [], blockedHosts: [] }))?.userPolicy.network, { +- allowOutbound: false, +- }); - }); - -- test('omits /rubber-duck when not enabled', async () => { -- const gated = new CopilotSlashCommandCompletionProvider('copilotcli', { hasHistory: () => true, isRubberDuckEnabled: () => false }); -- const items = await gated.provideCompletionItems({ -- kind: CompletionItemKind.UserMessage, channel: session, text: '/', offset: 1, -- }, CancellationToken.None); -- assert.deepStrictEqual(items.map(i => i.insertText), ['/plan ', '/compact', '/research ']); +- test('drops host lists and keeps outbound closed on darwin (Seatbelt has no per-host filter)', () => { +- assert.deepStrictEqual(buildSandboxConfigForSdk('darwin', sandbox('darwin', AgentSandboxEnabledValue.On, undefined, { allowedHosts: ['github.com'], blockedHosts: ['evil.example'] }))?.userPolicy.network, { +- allowOutbound: false, +- }); - }); - -- test('passes raw session id (no scheme/slash) to hasHistory', async () => { -- let seen: string | undefined; -- const gated = new CopilotSlashCommandCompletionProvider('copilotcli', { -- hasHistory: (id: string) => { seen = id; return true; }, -- isRubberDuckEnabled: () => true, +- test('darwin `allowNetwork` still opens outbound (host lists were already ignored)', () => { +- assert.deepStrictEqual(buildSandboxConfigForSdk('darwin', sandbox('darwin', AgentSandboxEnabledValue.AllowNetwork, undefined, { allowedHosts: ['github.com'] }))?.userPolicy.network, { +- allowOutbound: true, - }); -- await gated.provideCompletionItems({ -- kind: CompletionItemKind.UserMessage, channel: 'copilotcli:/abc', text: '/', offset: 1, -- }, CancellationToken.None); -- assert.strictEqual(seen, 'abc'); - }); - }); -}); -diff --git a/src/vs/platform/agentHost/test/node/copilotToolDisplay.test.ts b/src/vs/platform/agentHost/test/node/copilotToolDisplay.test.ts +diff --git a/src/vs/platform/agentHost/test/node/sessionCustomizationDiscovery.test.ts b/src/vs/platform/agentHost/test/node/sessionCustomizationDiscovery.test.ts deleted file mode 100644 -index d7a36fb8625..00000000000 ---- a/src/vs/platform/agentHost/test/node/copilotToolDisplay.test.ts +index 8697c546..00000000 +--- a/src/vs/platform/agentHost/test/node/sessionCustomizationDiscovery.test.ts +++ /dev/null -@@ -1,612 +0,0 @@ +@@ -1,728 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import assert from 'assert'; +-import type { CopilotClient } from '@github/copilot-sdk'; +-import { DeferredPromise, raceTimeout, timeout } from '../../../../base/common/async.js'; +-import { VSBuffer } from '../../../../base/common/buffer.js'; +-import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; +-import { DisposableStore } from '../../../../base/common/lifecycle.js'; +-import { Schemas } from '../../../../base/common/network.js'; -import { URI } from '../../../../base/common/uri.js'; -import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; --import { getEditFilePath, getEditFilePaths, getInvocationMessage, getPastTenseMessage, getPermissionDisplay, getShellLanguage, getToolDisplayName, getToolInputString, getToolKind, isEditTool, isHiddenTool, synthesizeSkillToolCall, type ITypedPermissionRequest } from '../../node/copilot/copilotToolDisplay.js'; +-import { FileService } from '../../../files/common/fileService.js'; +-import { IFileService } from '../../../files/common/files.js'; +-import { InMemoryFileSystemProvider } from '../../../files/common/inMemoryFilesystemProvider.js'; +-import { TestInstantiationService } from '../../../instantiation/test/common/instantiationServiceMock.js'; +-import { ILogService, NullLogService } from '../../../log/common/log.js'; +-import { IAgentPluginManager } from '../../common/agentPluginManager.js'; +-import { DiscoveredType, SessionCustomizationDiscovery } from '../../node/copilot/sessionCustomizationDiscovery.js'; +-import { SessionPluginBundler } from '../../node/shared/sessionPluginBundler.js'; +-import { mapToParsedPlugin, toDiscoveredDirectoryCustomizations } from '../../node/copilot/copilotAgent.js'; - --suite('copilotToolDisplay — friendly tool names', () => { +-suite('SessionCustomizationDiscovery', () => { - -- ensureNoDisposablesAreLeakedInTestSuite(); +- const disposables = new DisposableStore(); +- let fileService: FileService; +- let instantiationService: TestInstantiationService; +- let workspace: URI; +- let userHome: URI; +- let pluginBasePath: URI; - -- test('mirrors internal Copilot CLI friendly labels for representative tools', () => { -- const cases: Array<[toolName: string, displayName: string]> = [ -- ['bash', 'Run Shell Command'], -- ['powershell', 'Run Shell Command'], -- ['read_bash', 'Read Terminal'], -- ['read_powershell', 'Read Terminal'], -- ['write_bash', 'Write to Bash'], -- ['write_powershell', 'Write to PowerShell'], -- ['stop_bash', 'Stop Terminal Session'], -- ['stop_powershell', 'Stop Terminal Session'], -- ['bash_shutdown', 'Stop Terminal Session'], -- ['powershell_shutdown', 'Stop Terminal Session'], -- ['list_bash', 'List Shell Sessions'], -- ['list_powershell', 'List Shell Sessions'], -- ['view', 'Read'], -- ['edit', 'Edit File'], -- ['str_replace_editor', 'Edit File'], -- ['str_replace', 'Edit File'], -- ['insert', 'Edit File'], -- ['create', 'Create File'], -- ['grep', 'Search'], -- ['rg', 'Search'], -- ['glob', 'Search'], -- ['search_code_subagent', 'Search Code'], -- ['reply_to_comment', 'Reply to Comment'], -- ['code_review', 'Code Review'], -- ['think', 'Thinking'], -- ['report_intent', 'Report Intent'], -- ['report_progress', 'Progress update'], -- ['web_fetch', 'Fetch Web Content'], -- ['web_search', 'Web Search'], -- ['update_todo', 'Update Todo'], -- ['show_file', 'Show File'], -- ['fetch_copilot_cli_documentation', 'Fetch Documentation'], -- ['propose_work', 'Propose Work'], -- ['task_complete', 'Task Complete'], -- ['ask_user', 'Ask User'], -- ['skill', 'Invoke Skill'], -- ['task', 'Delegate Task'], -- ['list_agents', 'List Agents'], -- ['read_agent', 'Read Agent'], -- ['exit_plan_mode', 'Exit Plan Mode'], -- ['sql', 'Execute SQL'], -- ['lsp', 'Language Server'], -- ['create_pull_request', 'Create Pull Request'], -- ['gh-advisory-database', 'Check Dependencies'], -- ['store_memory', 'Store Memory'], -- ['apply_patch', 'Apply Patch'], -- ['write_agent', 'Write to Agent'], -- ['mcp_reload', 'Reload MCP Config'], -- ['mcp_validate', 'Validate MCP Config'], -- ['tool_search_tool_regex', 'Search Tools'], -- ['parallel_validation', 'Validate Changes'], -- ['codeql_checker', 'CodeQL Security Scan'], -- ]; +- setup(() => { +- fileService = disposables.add(new FileService(new NullLogService())); +- const memFs = disposables.add(new InMemoryFileSystemProvider()); +- disposables.add(fileService.registerProvider(Schemas.inMemory, memFs)); - -- for (const [toolName, displayName] of cases) { -- assert.strictEqual(getToolDisplayName(toolName), displayName, toolName); -- } +- instantiationService = disposables.add(new TestInstantiationService()); +- instantiationService.stub(IFileService, fileService); +- instantiationService.stub(ILogService, new NullLogService()); +- +- workspace = URI.from({ scheme: Schemas.inMemory, path: '/workspace' }); +- userHome = URI.from({ scheme: Schemas.inMemory, path: '/home' }); +- pluginBasePath = URI.from({ scheme: Schemas.inMemory, path: '/agentPlugins' }); +- instantiationService.stub(IAgentPluginManager, { basePath: pluginBasePath } as Partial); - }); - -- test('falls back to the raw tool name for unknown tools', () => { -- assert.strictEqual(getToolDisplayName('some_new_tool'), 'some_new_tool'); +- teardown(() => { +- disposables.clear(); - }); --}); +- ensureNoDisposablesAreLeakedInTestSuite(); - --suite('copilotToolDisplay — edit tool classification', () => { +- async function seed(path: string, content = ''): Promise { +- const uri = URI.from({ scheme: Schemas.inMemory, path }); +- await fileService.writeFile(uri, VSBuffer.fromString(content)); +- return uri; +- } - -- ensureNoDisposablesAreLeakedInTestSuite(); +- test('discovers supported agent instruction files in workspace roots', async () => { +- const wsCopilotInstructions = await seed('/workspace/.github/copilot-instructions.md', 'workspace copilot instructions'); +- const wsGeminiInstructions = await seed('/workspace/GEMINI.md', 'workspace gemini instructions'); - -- test('classifies direct file edit tools', () => { -- for (const toolName of ['edit', 'str_replace', 'insert', 'create', 'apply_patch', 'git_apply_patch']) { -- assert.strictEqual(isEditTool(toolName), true, toolName); -- } -- }); +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const files = (await discovery.scan(CancellationToken.None)) +- .flatMap(directory => directory.files.map(file => ({ uri: file.uri, type: directory.type }))) +- .filter(entry => entry.type === DiscoveredType.AgentInstruction) +- .map(entry => entry.uri.toString()) +- .sort((a, b) => a.localeCompare(b)); - -- test('classifies str_replace_editor by command', () => { -- for (const command of ['edit', 'str_replace', 'insert', 'create']) { -- assert.strictEqual(isEditTool('str_replace_editor', command), true, command); -- } -- assert.strictEqual(isEditTool('str_replace_editor', 'view'), false); -- assert.strictEqual(isEditTool('str_replace_editor', 'unknown'), false); -- assert.strictEqual(isEditTool('str_replace_editor'), false); +- assert.deepStrictEqual(files, [ +- wsCopilotInstructions.toString(), +- wsGeminiInstructions.toString(), +- ].sort((a, b) => a.localeCompare(b))); - }); --}); - --suite('getPermissionDisplay — cd-prefix stripping', () => { +- test('groups discovered customizations by parent folder', async () => { +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const client = { +- rpc: { +- agents: { +- discover: async () => ({ +- agents: [ +- { id: 'one', name: 'One', description: '', path: '/workspace/.github/agents/one.agent.md', userInvocable: false }, +- { id: 'two', name: 'Two', description: '', path: '/workspace/.github/agents/two.agent.md', userInvocable: true }, +- { id: 'three', name: 'Three', description: '', path: '/workspace/.github/other/three.agent.md', userInvocable: false }, +- ], +- }), +- }, +- instructions: { discover: async () => ({ sources: [] }) }, +- skills: { discover: async () => ({ skills: [] }) }, +- }, +- } as unknown as CopilotClient; - -- ensureNoDisposablesAreLeakedInTestSuite(); +- const customizations = await discovery.discover(client, CancellationToken.None); +- const agentDirectories = customizations.filter(customization => customization.contents === 'agent'); - -- const wd = URI.file('/repo/project'); +- const getPath = (uri: string) => URI.parse(uri).path; - -- test('strips redundant cd from shell permission request fullCommandText', () => { -- const request: ITypedPermissionRequest = { -- kind: 'shell', -- fullCommandText: 'cd /repo/project && npm test', -- } as ITypedPermissionRequest; -- const display = getPermissionDisplay(request, wd); -- assert.strictEqual(display.toolInput, 'npm test'); -- assert.strictEqual(display.permissionKind, 'shell'); +- assert.strictEqual(agentDirectories.length, 2); +- assert.deepStrictEqual(agentDirectories.map(customization => getPath(customization.uri)).sort(), [ +- '/workspace/.github/agents', +- '/workspace/.github/other', +- ]); +- const agentsInAgentsDir = agentDirectories.find(customization => getPath(customization.uri) === '/workspace/.github/agents'); +- assert.ok(agentsInAgentsDir); +- assert.deepStrictEqual(agentsInAgentsDir.children?.map(child => getPath(child.uri)).sort(), [ +- '/workspace/.github/agents/one.agent.md', +- '/workspace/.github/agents/two.agent.md', +- ]); - }); - -- test('leaves shell command alone when cd target differs from working directory', () => { -- const request: ITypedPermissionRequest = { -- kind: 'shell', -- fullCommandText: 'cd /tmp && ls', -- } as ITypedPermissionRequest; -- const display = getPermissionDisplay(request, wd); -- assert.strictEqual(display.toolInput, 'cd /tmp && ls'); +- test('returns directories sorted by type and URI', async () => { +- await seed('/workspace/.github/agents/aaa.agent.md', 'workspace agent a'); +- await seed('/workspace/.github/agents/foo.agent.md', 'workspace agent'); +- await seed('/workspace/.github/skills/alpha/SKILL.md', 'workspace skill alpha'); +- await seed('/workspace/.github/skills/bar/SKILL.md', 'workspace skill'); +- await seed('/workspace/.github/instructions/alpha.instructions.md', 'workspace instruction alpha'); +- await seed('/workspace/.github/instructions/baz.instructions.md', 'workspace instruction'); +- await seed('/workspace/.github/copilot-instructions.md', 'workspace copilot instructions'); +- await seed('/home/.copilot/agents/abc.agent.md', 'user agent abc'); +- await seed('/home/.copilot/agents/qux.agent.md', 'user agent'); +- await seed('/home/.agents/skills/aaa/SKILL.md', 'user skill aaa'); +- await seed('/home/.agents/skills/zap/SKILL.md', 'user skill'); +- +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const directories = await discovery.scan(CancellationToken.None); +- const actual = directories.map(directory => `${directory.type}:${directory.uri.toString()}`); +- const expected = [...actual].sort((a, b) => a < b ? -1 : a > b ? 1 : 0); +- +- assert.deepStrictEqual(actual, expected); +- for (const directory of directories) { +- const actualFiles = directory.files.map(file => file.uri.toString()); +- const expectedFiles = [...actualFiles].sort((a, b) => a < b ? -1 : a > b ? 1 : 0); +- assert.deepStrictEqual(actualFiles, expectedFiles); +- } - }); - -- test('leaves shell command alone when no working directory provided', () => { -- const request: ITypedPermissionRequest = { -- kind: 'shell', -- fullCommandText: 'cd /repo/project && npm test', -- } as ITypedPermissionRequest; -- const display = getPermissionDisplay(request, undefined); -- assert.strictEqual(display.toolInput, 'cd /repo/project && npm test'); +- test('does not discover agent instruction files outside supported roots', async () => { +- await seed('/workspace/.github/copilot-instructions.md', 'workspace copilot instructions'); +- await seed('/workspace/docs/AGENTS.md', 'unsupported root'); +- await seed('/workspace/.claude/GEMINI.md', 'unsupported filename in .claude'); +- await seed('/home/copilot-instructions.md', 'unsupported home root'); +- +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const files = (await discovery.scan(CancellationToken.None)) +- .flatMap(directory => directory.files.map(file => ({ uri: file.uri, type: directory.type }))) +- .filter(entry => entry.type === DiscoveredType.AgentInstruction) +- .map(entry => entry.uri.toString()) +- .sort((a, b) => a.localeCompare(b)); +- +- assert.deepStrictEqual(files, [ +- URI.from({ scheme: Schemas.inMemory, path: '/workspace/.github/copilot-instructions.md' }).toString(), +- ]); - }); - -- test('strips redundant cd from custom-tool shell permission request', () => { -- const request: ITypedPermissionRequest = { -- kind: 'custom-tool', -- toolName: 'bash', -- args: { command: 'cd /repo/project && echo hi' }, -- } as ITypedPermissionRequest; -- const display = getPermissionDisplay(request, wd); -- assert.strictEqual(display.toolInput, 'echo hi'); -- assert.strictEqual(display.permissionKind, 'shell'); -- }); -- -- test('does not affect non-shell custom-tool requests', () => { -- const request: ITypedPermissionRequest = { -- kind: 'custom-tool', -- toolName: 'some_other_tool', -- args: { command: 'cd /repo/project && echo hi' }, -- } as ITypedPermissionRequest; -- const display = getPermissionDisplay(request, wd); -- // Falls through to the generic branch — toolInput is the JSON-stringified args. -- assert.ok(display.toolInput?.includes('cd /repo/project'), `expected unrewritten args, got: ${display.toolInput}`); -- assert.strictEqual(display.permissionKind, 'custom-tool'); -- }); -- -- test('handles powershell custom-tool with semicolon separator', () => { -- const request: ITypedPermissionRequest = { -- kind: 'custom-tool', -- toolName: 'powershell', -- args: { command: 'cd /repo/project; dir' }, -- } as ITypedPermissionRequest; -- const display = getPermissionDisplay(request, wd); -- assert.strictEqual(display.toolInput, 'dir'); +- test('installs watchers for roots that contain discovered customizations', async () => { +- await seed('/workspace/.github/agents/foo.agent.md', 'workspace agent'); +- await seed('/workspace/.github/skills/bar/SKILL.md', 'workspace skill'); +- await seed('/workspace/.github/instructions/rules.instructions.md', 'workspace instruction'); +- await seed('/workspace/.github/hooks/pre-tool.json', '{"PreToolUse": []}'); +- await seed('/workspace/.github/copilot-instructions.md', 'workspace copilot instructions'); +- await seed('/workspace/.claude/CLAUDE.md', 'workspace claude instruction'); +- await seed('/home/.copilot/agents/user.agent.md', 'user agent'); +- await seed('/home/.agents/skills/user-skill/SKILL.md', 'user skill'); +- await seed('/home/.copilot/instructions/user.instructions.md', 'user instruction'); +- await seed('/home/.copilot/hooks/post-tool.json', '{"PostToolUse": []}'); +- await seed('/home/.copilot/copilot-instructions.md', 'user copilot instructions'); +- +- const watchCalls: Array<{ resource: string; recursive: boolean }> = []; +- const originalWatch = fileService.watch.bind(fileService); +- disposables.add({ dispose: () => { fileService.watch = originalWatch as typeof fileService.watch; } }); +- fileService.watch = ((resource, options) => { +- watchCalls.push({ resource: resource.toString(), recursive: options?.recursive === true }); +- return originalWatch(resource, options); +- }) as typeof fileService.watch; +- +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- await discovery.scan(CancellationToken.None); +- +- const watched = new Map(); +- for (const call of watchCalls) { +- const previous = watched.get(call.resource); +- watched.set(call.resource, previous === true || call.recursive); +- } +- assert.strictEqual(watched.get(workspace.toString()), false); +- assert.strictEqual(watched.get(URI.joinPath(workspace, '.github').toString()), false); +- assert.strictEqual(watched.get(URI.joinPath(workspace, '.claude').toString()), false); +- assert.strictEqual(watched.get(URI.joinPath(workspace, '.github', 'agents').toString()), false); +- assert.strictEqual(watched.get(URI.joinPath(workspace, '.github', 'skills').toString()), true); +- assert.strictEqual(watched.get(URI.joinPath(workspace, '.github', 'instructions').toString()), true); +- assert.strictEqual(watched.get(URI.joinPath(workspace, '.github', 'hooks').toString()), true); +- assert.strictEqual(watched.get(URI.joinPath(userHome, '.copilot').toString()), false); +- assert.strictEqual(watched.get(URI.joinPath(userHome, '.copilot', 'agents').toString()), false); +- assert.strictEqual(watched.get(URI.joinPath(userHome, '.agents', 'skills').toString()), true); +- assert.strictEqual(watched.get(URI.joinPath(userHome, '.copilot', 'instructions').toString()), true); +- assert.strictEqual(watched.get(URI.joinPath(userHome, '.copilot', 'hooks').toString()), true); - }); --}); - --suite('view tool — view_range display', () => { +- test('refresh keeps existing watchers when discovered roots are unchanged', async () => { +- await seed('/workspace/.github/agents/foo.agent.md', 'workspace agent'); - -- ensureNoDisposablesAreLeakedInTestSuite(); +- const watchCalls: string[] = []; +- let watchDisposeCalls = 0; +- const originalWatch = fileService.watch.bind(fileService); +- disposables.add({ dispose: () => { fileService.watch = originalWatch as typeof fileService.watch; } }); +- fileService.watch = ((resource, options) => { +- watchCalls.push(resource.toString()); +- const disposable = originalWatch(resource, options); +- return { +- dispose: () => { +- watchDisposeCalls++; +- disposable.dispose(); +- } +- }; +- }) as typeof fileService.watch; - -- function invocation(parameters: Record | undefined): string { -- const result = getInvocationMessage('view', 'View File', parameters); -- return typeof result === 'string' ? result : result.markdown; -- } +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- await discovery.scan(CancellationToken.None); +- const watchCallsAfterFirstScan = watchCalls.length; - -- function pastTense(parameters: Record | undefined): string { -- const result = getPastTenseMessage('view', 'View File', parameters, true); -- return typeof result === 'string' ? result : result.markdown; -- } +- await discovery.scan(CancellationToken.None); - -- test('renders path-only when view_range is absent', () => { -- assert.ok(invocation({ path: '/repo/file.ts' }).startsWith('Reading [')); -- assert.ok(pastTense({ path: '/repo/file.ts' }).startsWith('Read [')); +- assert.strictEqual(watchCalls.length, watchCallsAfterFirstScan, 'expected no new watch registrations for unchanged roots'); +- assert.strictEqual(watchDisposeCalls, 0, 'expected existing watchers to remain active for unchanged roots'); - }); - -- test('renders "lines X to Y" for a valid two-element range', () => { -- assert.ok(invocation({ path: '/repo/file.ts', view_range: [10, 20] }).endsWith(', lines 10 to 20')); -- assert.ok(pastTense({ path: '/repo/file.ts', view_range: [10, 20] }).endsWith(', lines 10 to 20')); -- }); +- test('fires onDidChange when a new agent file is added under a non-recursively watched root', async () => { +- // Seed an existing agent so `.github/agents` is discovered and watched. +- await seed('/workspace/.github/agents/foo.agent.md', 'workspace agent'); - -- test('renders "line X" when start === end', () => { -- assert.ok(invocation({ path: '/repo/file.ts', view_range: [10, 10] }).endsWith(', line 10')); -- assert.ok(pastTense({ path: '/repo/file.ts', view_range: [10, 10] }).endsWith(', line 10')); -- }); +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- await discovery.scan(CancellationToken.None); - -- test('renders "line X to the end" for the -1 EOF sentinel', () => { -- assert.ok(invocation({ path: '/repo/file.ts', view_range: [10, -1] }).endsWith(', line 10 to the end')); -- assert.ok(pastTense({ path: '/repo/file.ts', view_range: [10, -1] }).endsWith(', line 10 to the end')); -- }); +- // Flush buffered file change events from the initial seed/scan so the +- // assertion below only observes the event triggered by the new file. +- await timeout(50); - -- test('falls back to path-only for invalid ranges', () => { -- // end < start (and not -1) -- assert.ok(!invocation({ path: '/repo/file.ts', view_range: [20, 10] }).includes(',')); -- // negative start -- assert.ok(!invocation({ path: '/repo/file.ts', view_range: [-5, 10] }).includes(',')); -- // non-integer -- assert.ok(!invocation({ path: '/repo/file.ts', view_range: [1.5, 10] }).includes(',')); -- // wrong arity -- assert.ok(!invocation({ path: '/repo/file.ts', view_range: [10] }).includes(',')); -- assert.ok(!invocation({ path: '/repo/file.ts', view_range: [10, 20, 30] }).includes(',')); -- // non-array -- assert.ok(!invocation({ path: '/repo/file.ts', view_range: 'whatever' }).includes(',')); +- let changeCount = 0; +- const fired = new DeferredPromise(); +- disposables.add(discovery.onDidChange(() => { +- changeCount++; +- fired.complete(); +- })); +- +- await seed('/workspace/.github/agents/bar.agent.md', 'new workspace agent'); +- await raceTimeout(fired.p, 500); +- +- assert.strictEqual(changeCount, 1, 'expected onDidChange to fire for a new agent file inside the watched directory'); - }); --}); - --// ---- write_/read_ shell tool display --------------------------------------- --// --// Coverage for the secondary shell helpers (write_bash, read_bash, and their --// powershell siblings). These never appear in a permission dialog (they're --// registered with `skipPermission: true` — see copilotShellTools.ts), but they --// still flow through the tool-execution display pipeline. +- test('fires onDidChange when an existing agent file is modified under a non-recursively watched root', async () => { +- await seed('/workspace/.github/agents/foo.agent.md', 'workspace agent'); - --suite('copilotToolDisplay — write_/read_ shell tools', () => { +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- await discovery.scan(CancellationToken.None); +- await timeout(50); - -- ensureNoDisposablesAreLeakedInTestSuite(); +- let changeCount = 0; +- const fired = new DeferredPromise(); +- disposables.add(discovery.onDidChange(() => { +- changeCount++; +- fired.complete(); +- })); - -- suite('getToolKind', () => { +- // Overwrite the existing agent file to produce an UPDATED event. +- await seed('/workspace/.github/agents/foo.agent.md', 'workspace agent (updated)'); +- await raceTimeout(fired.p, 500); - -- test('returns terminal for bash', () => { -- assert.strictEqual(getToolKind('bash'), 'terminal'); -- }); +- assert.strictEqual(changeCount, 1, 'expected onDidChange to fire when an existing agent file is modified'); +- }); - -- test('returns terminal for powershell', () => { -- assert.strictEqual(getToolKind('powershell'), 'terminal'); -- }); +- test('fires onDidChange when an existing agent file is deleted under a non-recursively watched root', async () => { +- const agentUri = await seed('/workspace/.github/agents/foo.agent.md', 'workspace agent'); +- // Seed a second agent so the parent directory still exists after the deletion. +- await seed('/workspace/.github/agents/bar.agent.md', 'workspace agent bar'); - -- test('returns undefined for write_bash (sending input to a running program, not launching a terminal)', () => { -- assert.strictEqual(getToolKind('write_bash'), undefined); -- }); +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- await discovery.scan(CancellationToken.None); +- await timeout(50); - -- test('returns undefined for write_powershell', () => { -- assert.strictEqual(getToolKind('write_powershell'), undefined); -- }); +- let changeCount = 0; +- const fired = new DeferredPromise(); +- disposables.add(discovery.onDidChange(() => { +- changeCount++; +- fired.complete(); +- })); - -- test('returns undefined for read_bash (reading output, not launching a terminal)', () => { -- assert.strictEqual(getToolKind('read_bash'), undefined); -- }); +- await fileService.del(agentUri); +- await raceTimeout(fired.p, 500); - -- test('returns undefined for read_powershell', () => { -- assert.strictEqual(getToolKind('read_powershell'), undefined); -- }); +- assert.strictEqual(changeCount, 1, 'expected onDidChange to fire when an existing agent file is deleted'); +- }); - -- test('returns subagent for task', () => { -- assert.strictEqual(getToolKind('task'), 'subagent'); -- }); +- test('fires onDidChange when AGENTS.md in the workspace root is modified', async () => { +- // AGENTS.md lives directly under the workspace root, which is watched non-recursively. +- await seed('/workspace/AGENTS.md', 'agents instructions'); - -- test('returns undefined for view', () => { -- assert.strictEqual(getToolKind('view'), undefined); -- }); +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- await discovery.scan(CancellationToken.None); +- await timeout(50); - -- test('returns search for glob', () => { -- assert.strictEqual(getToolKind('glob'), 'search'); -- }); +- let changeCount = 0; +- const fired = new DeferredPromise(); +- disposables.add(discovery.onDidChange(() => { +- changeCount++; +- fired.complete(); +- })); +- +- await seed('/workspace/AGENTS.md', 'agents instructions (updated)'); +- await raceTimeout(fired.p, 500); +- +- assert.strictEqual(changeCount, 1, 'expected onDidChange to fire when AGENTS.md at the workspace root is modified'); - }); - -- suite('getShellLanguage', () => { +- test('does not fire onDidChange for files outside any trigger URI', async () => { +- // Seed a customization so the workspace + `.github` dirs get watchers. +- await seed('/workspace/.github/agents/foo.agent.md', 'workspace agent'); - -- test('bash returns shellscript', () => { -- assert.strictEqual(getShellLanguage('bash'), 'shellscript'); -- }); +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- await discovery.scan(CancellationToken.None); +- await timeout(50); - -- test('powershell returns powershell', () => { -- assert.strictEqual(getShellLanguage('powershell'), 'powershell'); -- }); +- let changeCount = 0; +- disposables.add(discovery.onDidChange(() => { +- changeCount++; +- })); - -- test('write_bash returns shellscript', () => { -- assert.strictEqual(getShellLanguage('write_bash'), 'shellscript'); -- }); +- // None of these paths intersect any trigger URI: +- // - `.git/HEAD` : `.git` is unrelated (not `.github`) +- // - `.vscode/settings.json` : `.vscode` is unrelated +- // - `README.md` : at workspace root but not AGENTS.md/CLAUDE.md/GEMINI.md +- // - `src/index.ts` : unrelated top-level directory +- await seed('/workspace/.git/HEAD', 'ref: refs/heads/main'); +- await seed('/workspace/.vscode/settings.json', '{}'); +- await seed('/workspace/README.md', '# project'); +- await seed('/workspace/src/index.ts', 'export {};'); - -- test('write_powershell returns powershell', () => { -- assert.strictEqual(getShellLanguage('write_powershell'), 'powershell'); -- }); +- // Give the in-memory provider time to deliver any (stray) events. +- await timeout(100); - -- test('read_bash returns shellscript', () => { -- assert.strictEqual(getShellLanguage('read_bash'), 'shellscript'); -- }); +- assert.strictEqual(changeCount, 0, 'expected onDidChange not to fire for paths outside any trigger URI'); +- }); - -- test('read_powershell returns powershell', () => { -- assert.strictEqual(getShellLanguage('read_powershell'), 'powershell'); -- }); +- test('cancellation of one caller does not affect another concurrent caller', async () => { +- await seed('/workspace/.github/agents/foo.agent.md', 'workspace agent'); +- +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const cancelSource = new CancellationTokenSource(); +- disposables.add(cancelSource); +- +- const cancelled = discovery.scan(cancelSource.token); +- const nonCancelled = discovery.scan(CancellationToken.None); +- cancelSource.cancel(); +- +- await assert.rejects(cancelled); +- const directories = await nonCancelled; +- assert.ok(directories.some(directory => directory.type === DiscoveredType.Agent)); - }); - -- suite('getInvocationMessage', () => { +- test('discovers agents, skills, instructions, and hooks across workspace and home roots', async () => { +- const wsAgent = await seed('/workspace/.github/agents/foo.agent.md', 'agent body'); +- const wsSkill = await seed('/workspace/.github/skills/bar/SKILL.md', 'skill body'); +- const wsInstr = await seed('/workspace/.github/instructions/baz.instructions.md', 'instr body'); +- const wsHook = await seed('/workspace/.github/hooks/pre-tool.json', '{"PreToolUse": []}'); +- const userAgent = await seed('/home/.copilot/agents/qux.agent.md', 'user agent'); +- const userSkill = await seed('/home/.agents/skills/zap/SKILL.md', 'user skill'); +- const userHook = await seed('/home/.copilot/hooks/post-tool.json', '{"PostToolUse": []}'); +- // Noise that should not be picked up +- await seed('/workspace/.github/agents/not-an-agent.txt', 'ignored'); +- await seed('/workspace/.github/hooks/not-a-hook.md', 'ignored'); +- +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const directories = await discovery.scan(CancellationToken.None); +- const files = directories.flatMap(directory => directory.files.map(file => ({ uri: file.uri, type: directory.type }))); +- +- assert.deepStrictEqual([...files].sort((a, b) => a.uri.toString().localeCompare(b.uri.toString())), [ +- { uri: userAgent, type: DiscoveredType.Agent }, +- { uri: userHook, type: DiscoveredType.Hook }, +- { uri: userSkill, type: DiscoveredType.Skill }, +- { uri: wsAgent, type: DiscoveredType.Agent }, +- { uri: wsHook, type: DiscoveredType.Hook }, +- { uri: wsInstr, type: DiscoveredType.Instruction }, +- { uri: wsSkill, type: DiscoveredType.Skill }, +- ].sort((a, b) => a.uri.toString().localeCompare(b.uri.toString()))); +- assert.ok(directories.some(directory => directory.uri.toString() === URI.joinPath(workspace, '.github', 'agents').toString())); +- }); - -- function getText(msg: ReturnType): string { -- return typeof msg === 'string' ? msg : msg.markdown; -- } +- test('discovers nested .json hook files', async () => { +- const nestedWsHook = await seed('/workspace/.github/hooks/team/security/pre-tool.json', '{"PreToolUse": []}'); +- const nestedUserHook = await seed('/home/.copilot/hooks/domain/tools/post-tool.json', '{"PostToolUse": []}'); - -- test('write_bash with command includes the command text', () => { -- const msg = getInvocationMessage('write_bash', 'Write Shell Input', { command: 'echo hello' }); -- assert.ok(getText(msg).includes('echo hello'), `expected 'echo hello' in: ${getText(msg)}`); -- }); +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const files = (await discovery.scan(CancellationToken.None)).flatMap(directory => directory.files.map(file => ({ uri: file.uri, type: directory.type }))); - -- test('write_bash without command returns a non-empty fallback message', () => { -- const msg = getInvocationMessage('write_bash', 'Write Shell Input', undefined); -- assert.ok(getText(msg).length > 0); -- assert.ok(!getText(msg).includes('undefined')); -- }); +- assert.deepStrictEqual([...files].sort((a, b) => a.uri.toString().localeCompare(b.uri.toString())), [ +- { uri: nestedUserHook, type: DiscoveredType.Hook }, +- { uri: nestedWsHook, type: DiscoveredType.Hook }, +- ].sort((a, b) => a.uri.toString().localeCompare(b.uri.toString()))); +- }); - -- test('write_powershell with command includes the command text', () => { -- const msg = getInvocationMessage('write_powershell', 'Write Shell Input', { command: 'Get-Date' }); -- assert.ok(getText(msg).includes('Get-Date'), `expected 'Get-Date' in: ${getText(msg)}`); -- }); +- test('discovers hook settings files from fixed workspace locations', async () => { +- const githubSettings = await seed('/workspace/.github/copilot/settings.json', '{"hooks": {"PreToolUse": []}}'); +- const githubLocalSettings = await seed('/workspace/.github/copilot/settings.local.json', '{"hooks": {"PostToolUse": []}}'); +- const claudeSettings = await seed('/workspace/.claude/settings.json', '{"hooks": {"SessionStart": []}}'); +- const claudeLocalSettings = await seed('/workspace/.claude/settings.local.json', '{"hooks": {"SessionEnd": []}}'); +- await seed('/workspace/.github/copilot/settings.dev.json', '{"hooks": {"Ignored": []}}'); +- +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const files = (await discovery.scan(CancellationToken.None)).flatMap(directory => directory.files.map(file => ({ uri: file.uri, type: directory.type }))); +- +- assert.deepStrictEqual([...files].sort((a, b) => a.uri.toString().localeCompare(b.uri.toString())), [ +- { uri: claudeLocalSettings, type: DiscoveredType.Hook }, +- { uri: claudeSettings, type: DiscoveredType.Hook }, +- { uri: githubLocalSettings, type: DiscoveredType.Hook }, +- { uri: githubSettings, type: DiscoveredType.Hook }, +- ].sort((a, b) => a.uri.toString().localeCompare(b.uri.toString()))); +- }); - -- test('read_bash returns a non-empty message', () => { -- const msg = getInvocationMessage('read_bash', 'Read Shell Output', undefined); -- assert.strictEqual(getText(msg), 'Reading Terminal'); -- }); +- test('fires onDidChange when fixed hook settings file is modified', async () => { +- await seed('/workspace/.github/copilot/settings.json', '{"hooks": {"PreToolUse": []}}'); - -- test('read_powershell returns a non-empty message', () => { -- const msg = getInvocationMessage('read_powershell', 'Read Shell Output', undefined); -- assert.strictEqual(getText(msg), 'Reading Terminal'); -- }); +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- await discovery.scan(CancellationToken.None); +- await timeout(50); - -- test('write_bash message differs from bash message (distinct wording)', () => { -- const writeBashMsg = getText(getInvocationMessage('write_bash', 'Write Shell Input', { command: 'echo hi' })); -- const bashMsg = getText(getInvocationMessage('bash', 'Bash', { command: 'echo hi' })); -- // Both include the command, but the surrounding text should differ -- assert.notStrictEqual(writeBashMsg, bashMsg); -- }); +- let changeCount = 0; +- const fired = new DeferredPromise(); +- disposables.add(discovery.onDidChange(() => { +- changeCount++; +- fired.complete(); +- })); +- +- await seed('/workspace/.github/copilot/settings.json', '{"hooks": {"PreToolUse": [{"command": "echo test"}]}}'); +- await raceTimeout(fired.p, 500); +- +- assert.strictEqual(changeCount, 1, 'expected onDidChange to fire when fixed hook settings file is modified'); - }); - -- suite('getPastTenseMessage', () => { +- test('excludes exact-case README.md inside agent folders', async () => { +- const wsAgent = await seed('/workspace/.github/agents/foo.agent.md', 'agent body'); +- const wsPlainAgent = await seed('/workspace/.github/agents/plain.md', 'plain agent body'); +- const wsLowerReadmeAgent = await seed('/workspace/.github/agents/readme.md', 'docs lower'); +- await seed('/workspace/.github/agents/README.md', 'docs'); - -- function getText(msg: ReturnType): string { -- return typeof msg === 'string' ? msg : msg.markdown; -- } +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const files = (await discovery.scan(CancellationToken.None)).flatMap(directory => directory.files.map(file => ({ uri: file.uri, type: directory.type }))); - -- test('write_bash with command includes the command text', () => { -- const msg = getPastTenseMessage('write_bash', 'Write Shell Input', { command: 'echo hello' }, true); -- assert.ok(getText(msg).includes('echo hello'), `expected 'echo hello' in: ${getText(msg)}`); -- }); +- assert.deepStrictEqual([...files].sort((a, b) => a.uri.toString().localeCompare(b.uri.toString())), [ +- { uri: wsAgent, type: DiscoveredType.Agent }, +- { uri: wsLowerReadmeAgent, type: DiscoveredType.Agent }, +- { uri: wsPlainAgent, type: DiscoveredType.Agent }, +- ].sort((a, b) => a.uri.toString().localeCompare(b.uri.toString()))); +- }); - -- test('write_bash without command returns a non-empty fallback message', () => { -- const msg = getPastTenseMessage('write_bash', 'Write Shell Input', undefined, true); -- assert.ok(getText(msg).length > 0); -- }); +- test('includes non-README markdown files inside agent folders', async () => { +- const wsAgent = await seed('/workspace/.github/agents/foo.agent.md', 'agent body'); +- const wsLegacyMode = await seed('/workspace/.github/agents/legacy.chatmode.md', 'legacy mode body'); +- const wsPrompt = await seed('/workspace/.github/agents/bar.prompt.md', 'prompt body'); +- const wsInstruction = await seed('/workspace/.github/agents/baz.instructions.md', 'instruction body'); +- const wsCopilotInstructions = await seed('/workspace/.github/agents/copilot-instructions.md', 'copilot instructions body'); +- const wsSkill = await seed('/workspace/.github/agents/SKILL.md', 'skill body'); +- const wsSkillLowercase = await seed('/workspace/.github/agents/skill.md', 'skill body lowercase'); +- +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const files = (await discovery.scan(CancellationToken.None)).flatMap(directory => directory.files.map(file => ({ uri: file.uri, type: directory.type }))); +- +- assert.deepStrictEqual([...files].sort((a, b) => a.uri.toString().localeCompare(b.uri.toString())), [ +- { uri: wsCopilotInstructions, type: DiscoveredType.Agent }, +- { uri: wsAgent, type: DiscoveredType.Agent }, +- { uri: wsInstruction, type: DiscoveredType.Agent }, +- { uri: wsLegacyMode, type: DiscoveredType.Agent }, +- { uri: wsPrompt, type: DiscoveredType.Agent }, +- { uri: wsSkill, type: DiscoveredType.Agent }, +- { uri: wsSkillLowercase, type: DiscoveredType.Agent }, +- ].sort((a, b) => a.uri.toString().localeCompare(b.uri.toString()))); +- }); - -- test('write_powershell with command includes the command text', () => { -- const msg = getPastTenseMessage('write_powershell', 'Write Shell Input', { command: 'Get-Date' }, true); -- assert.ok(getText(msg).includes('Get-Date'), `expected 'Get-Date' in: ${getText(msg)}`); -- }); +- test('discovers nested .instructions.md files', async () => { +- const nestedWsInstr = await seed('/workspace/.github/instructions/team/security/policy.instructions.md', 'workspace nested instruction'); +- const nestedUserInstr = await seed('/home/.copilot/instructions/domain/tools/deep.instructions.md', 'user nested instruction'); - -- test('read_bash success returns a non-empty message', () => { -- const msg = getPastTenseMessage('read_bash', 'Read Shell Output', undefined, true); -- assert.strictEqual(getText(msg), 'Read Terminal'); -- }); +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const files = (await discovery.scan(CancellationToken.None)).flatMap(directory => directory.files.map(file => ({ uri: file.uri, type: directory.type }))); - -- test('write_bash failure returns a non-empty error message', () => { -- const msg = getPastTenseMessage('write_bash', 'Write Shell Input', { command: 'echo hello' }, false); -- assert.ok(getText(msg).length > 0); -- }); +- assert.deepStrictEqual([...files].sort((a, b) => a.uri.toString().localeCompare(b.uri.toString())), [ +- { uri: nestedUserInstr, type: DiscoveredType.Instruction }, +- { uri: nestedWsInstr, type: DiscoveredType.Instruction }, +- ].sort((a, b) => a.uri.toString().localeCompare(b.uri.toString()))); - }); - -- suite('getToolInputString', () => { - -- test('write_bash extracts command field', () => { -- assert.strictEqual(getToolInputString('write_bash', { command: 'echo hello' }, undefined), 'echo hello'); -- }); - -- test('write_powershell extracts command field', () => { -- assert.strictEqual(getToolInputString('write_powershell', { command: 'Get-Date' }, undefined), 'Get-Date'); -- }); +- test('bundles nested .instructions.md files into rules', async () => { +- await seed('/workspace/.github/instructions/team/security/policy.instructions.md', 'workspace nested instruction'); +- await seed('/home/.copilot/instructions/domain/tools/deep.instructions.md', 'user nested instruction'); - -- test('write_bash falls back to rawArguments when no command field', () => { -- assert.strictEqual(getToolInputString('write_bash', {}, '{"command":"echo hello"}'), '{"command":"echo hello"}'); -- }); +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const bundler = disposables.add(instantiationService.createInstance(SessionPluginBundler, workspace)); +- const result = await bundler.bundle(await discovery.scan(CancellationToken.None)); - -- test('write_bash returns undefined when both parameters and rawArguments are absent', () => { -- assert.strictEqual(getToolInputString('write_bash', undefined, undefined), undefined); -- }); +- assert.ok(result); - -- test('read_bash with no parameters returns undefined', () => { -- assert.strictEqual(getToolInputString('read_bash', undefined, undefined), undefined); -- }); +- const root = bundler.rootUri; +- const workspaceInstr = await fileService.readFile(URI.joinPath(root, 'rules', 'policy.instructions.md')); +- assert.strictEqual(workspaceInstr.value.toString(), 'workspace nested instruction'); +- +- const userInstr = await fileService.readFile(URI.joinPath(root, 'rules', 'deep.instructions.md')); +- assert.strictEqual(userInstr.value.toString(), 'user nested instruction'); - }); --}); - --suite('skill events', () => { +- test('returns undefined when no files were discovered', async () => { +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const bundler = disposables.add(instantiationService.createInstance(SessionPluginBundler, workspace)); +- const directories = await discovery.scan(CancellationToken.None); +- const result = await bundler.bundle(directories); +- assert.strictEqual(result, undefined); +- }); - -- ensureNoDisposablesAreLeakedInTestSuite(); +- test('maps discovered files to parsed plugin preserving source URIs', async () => { +- const agent = await seed('/workspace/.github/agents/foo.agent.md', '---\nname: Workspace Agent\ndescription: Agent description\n---\nbody'); +- const skill = await seed('/workspace/.github/skills/bar/SKILL.md', '---\nname: Workspace Skill\ndescription: Skill description\n---\nbody'); +- const instruction = await seed('/workspace/.github/instructions/baz.instructions.md', '---\nname: Workspace Rule\ndescription: Rule description\nglobs:\n - src/**\n---\nbody'); - -- test('hides the raw `skill` tool call and synthesizes a tool-start/complete pair from `skill.invoked`', () => { -- const withPath = synthesizeSkillToolCall( -- { name: 'plan', path: '/abs/repo/skills/plan/SKILL.md' }, -- 'evt-123', -- ); -- const noPath = synthesizeSkillToolCall( -- { name: 'plan' }, -- undefined, -- ); +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const customizations = await toDiscoveredDirectoryCustomizations(await discovery.scan(CancellationToken.None), fileService); - -- assert.deepStrictEqual({ -- skillIsHidden: isHiddenTool('skill'), -- withPathToolCallId: withPath.toolCallId, -- withPathToolName: withPath.toolName, -- withPathDisplayName: withPath.displayName, -- withPathInvocation: withPath.invocationMessage, -- withPathPastTense: withPath.pastTenseMessage, -- noPathToolCallId: noPath.toolCallId, -- noPathInvocation: noPath.invocationMessage, -- noPathPastTense: noPath.pastTenseMessage, -- }, { -- skillIsHidden: true, -- withPathToolCallId: 'synth-skill-evt-123', -- withPathToolName: 'skill', -- withPathDisplayName: 'Read Skill', -- withPathInvocation: { markdown: 'Reading skill [plan](file:///abs/repo/skills/plan/SKILL.md)' }, -- withPathPastTense: { markdown: 'Read skill [plan](file:///abs/repo/skills/plan/SKILL.md)' }, -- noPathToolCallId: 'synth-skill-2108d652', -- noPathInvocation: 'Reading skill plan', -- noPathPastTense: 'Read skill plan', -- }); +- const plugin = mapToParsedPlugin(customizations); +- +- assert.ok(plugin); +- assert.strictEqual(plugin.agents.length, 1); +- assert.strictEqual(plugin.skills.length, 1); +- assert.strictEqual(plugin.instructions.length, 1); +- assert.deepStrictEqual( +- { +- agentUri: plugin.agents[0].uri.toString(), +- agentDescription: plugin.agents[0].description, +- skillUri: plugin.skills[0].uri.toString(), +- skillDescription: plugin.skills[0].description, +- ruleUri: plugin.instructions[0].uri.toString(), +- ruleDescription: plugin.instructions[0].description, +- }, +- { +- agentUri: agent.toString(), +- agentDescription: 'Agent description', +- skillUri: skill.toString(), +- skillDescription: 'Skill description', +- ruleUri: instruction.toString(), +- ruleDescription: 'Rule description', +- } +- ); - }); --}); - --suite('rg / grep search tool display', () => { +- test('does not include parsed agent-instruction rules in mapToParsedPlugin output', async () => { +- await seed('/workspace/.github/copilot-instructions.md', 'workspace instructions'); +- await seed('/workspace/.agents/skills/bar/SKILL.md', '---\nname: bar\ndescription: Skill description\n---\nbody'); - -- ensureNoDisposablesAreLeakedInTestSuite(); +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const customizations = await toDiscoveredDirectoryCustomizations(await discovery.scan(CancellationToken.None), fileService); - -- function text(msg: ReturnType): string { -- return typeof msg === 'string' ? msg : msg.markdown; -- } +- const plugin = mapToParsedPlugin(customizations); - -- test('rg invocation/past tense use "Searching for {pattern}" wording', () => { -- const inv = text(getInvocationMessage('rg', 'Search', { pattern: 'foo' })); -- const past = text(getPastTenseMessage('rg', 'Search', { pattern: 'foo' }, true)); -- assert.deepStrictEqual({ inv, past }, { -- inv: 'Searching for `foo`', -- past: 'Searched for `foo`', -- }); +- assert.ok(plugin); +- assert.strictEqual(plugin.skills.length, 1); +- assert.strictEqual(plugin.instructions.length, 0); - }); - -- test('rg without a pattern falls back to a generic search message (not the raw tool name)', () => { -- const inv = text(getInvocationMessage('rg', 'Search', undefined)); -- assert.strictEqual(inv, 'Searching files'); -- }); +- test('returns undefined from mapToParsedPlugin when all customizations are agent-instruction files', async () => { +- // Only agent instruction files are discovered — these are excluded from the parsed plugin output. +- await seed('/workspace/.github/copilot-instructions.md', 'workspace instructions'); +- await seed('/home/.copilot/copilot-instructions.md', 'user instructions'); - -- test('grep keeps "Searching for {pattern}" wording', () => { -- const inv = text(getInvocationMessage('grep', 'Search', { pattern: 'bar' })); -- const past = text(getPastTenseMessage('grep', 'Search', { pattern: 'bar' }, true)); -- assert.deepStrictEqual({ inv, past }, { -- inv: 'Searching for `bar`', -- past: 'Searched for `bar`', -- }); -- }); +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const customizations = await toDiscoveredDirectoryCustomizations(await discovery.scan(CancellationToken.None), fileService); +- +- const plugin = mapToParsedPlugin(customizations); - -- test('getToolInputString returns pattern for both grep and rg', () => { -- assert.strictEqual(getToolInputString('grep', { pattern: 'abc' }, undefined), 'abc'); -- assert.strictEqual(getToolInputString('rg', { pattern: 'abc' }, undefined), 'abc'); +- assert.strictEqual(plugin, undefined); - }); -}); - --suite('web_fetch tool display', () => { - -- ensureNoDisposablesAreLeakedInTestSuite(); +-suite('SessionPluginBundler', () => { +- const disposables = new DisposableStore(); +- let fileService: FileService; +- let instantiationService: TestInstantiationService; +- let workspace: URI; +- let userHome: URI; +- let pluginBasePath: URI; - -- function text(msg: ReturnType | ReturnType): string { -- return typeof msg === 'string' ? msg : msg.markdown; -- } +- setup(() => { +- fileService = disposables.add(new FileService(new NullLogService())); +- const memFs = disposables.add(new InMemoryFileSystemProvider()); +- disposables.add(fileService.registerProvider(Schemas.inMemory, memFs)); - -- test('uses the fetched URL for invocation and completion messages', () => { -- const parameters = { url: 'https://example.com/docs' }; -- assert.deepStrictEqual({ -- invocation: text(getInvocationMessage('web_fetch', 'Fetch Web Content', parameters)), -- pastTense: text(getPastTenseMessage('web_fetch', 'Fetch Web Content', parameters, true)), -- input: getToolInputString('web_fetch', parameters, undefined), -- }, { -- invocation: 'Fetching [https://example.com/docs](https://example.com/docs)', -- pastTense: 'Fetched [https://example.com/docs](https://example.com/docs)', -- input: 'https://example.com/docs', -- }); -- }); +- instantiationService = disposables.add(new TestInstantiationService()); +- instantiationService.stub(IFileService, fileService); +- instantiationService.stub(ILogService, new NullLogService()); - -- test('falls back to generic URL wording when the URL is absent', () => { -- assert.deepStrictEqual({ -- invocation: text(getInvocationMessage('web_fetch', 'Fetch Web Content', undefined)), -- pastTense: text(getPastTenseMessage('web_fetch', 'Fetch Web Content', undefined, true)), -- failure: text(getPastTenseMessage('web_fetch', 'Fetch Web Content', { url: 'https://example.com/docs' }, false)), -- }, { -- invocation: 'Fetching URL', -- pastTense: 'Fetched URL', -- failure: '"Fetch Web Content" failed', -- }); +- workspace = URI.from({ scheme: Schemas.inMemory, path: '/workspace' }); +- userHome = URI.from({ scheme: Schemas.inMemory, path: '/home' }); +- pluginBasePath = URI.from({ scheme: Schemas.inMemory, path: '/agentPlugins' }); +- instantiationService.stub(IAgentPluginManager, { basePath: pluginBasePath } as Partial); - }); --}); -- --suite('sql tool display', () => { - +- teardown(() => { +- disposables.clear(); +- }); - ensureNoDisposablesAreLeakedInTestSuite(); - -- function text(msg: ReturnType | ReturnType): string { -- return typeof msg === 'string' ? msg : msg.markdown; +- async function seed(path: string, content = ''): Promise { +- const uri = URI.from({ scheme: Schemas.inMemory, path }); +- await fileService.writeFile(uri, VSBuffer.fromString(content)); +- return uri; - } - -- test('uses the SQL description for invocation and completion messages', () => { -- const parameters = { description: 'Insert agent host study todos', query: 'INSERT INTO todos (title) VALUES (\'Read terminal activation docs\')' }; -- assert.strictEqual(text(getInvocationMessage('sql', 'Execute SQL', parameters)), 'Insert agent host study todos'); -- assert.strictEqual(text(getPastTenseMessage('sql', 'Execute SQL', parameters, true)), 'Insert agent host study todos'); -- }); +- test('bundles discovered files into the synthetic plugin tree', async () => { +- await seed('/workspace/.github/agents/foo.agent.md', 'agent body'); +- await seed('/workspace/.github/skills/bar/SKILL.md', 'skill body'); +- await seed('/workspace/.github/instructions/baz.instructions.md', 'instr body'); +- await seed('/workspace/.github/hooks/pre-tool.json', '{"PreToolUse": []}'); - -- test('falls back to generic SQL wording when description is absent', () => { -- assert.strictEqual(text(getInvocationMessage('sql', 'Execute SQL', { query: 'SELECT 1' })), 'Executing SQL query'); -- assert.strictEqual(text(getPastTenseMessage('sql', 'Execute SQL', { query: 'SELECT 1' }, true)), 'Executed SQL query'); -- }); --}); +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const bundler = disposables.add(instantiationService.createInstance(SessionPluginBundler, workspace)); +- const directories = await discovery.scan(CancellationToken.None); +- const result = await bundler.bundle(directories); - --suite('apply_patch tool display', () => { +- assert.ok(result); +- assert.strictEqual(result.ref.name, 'VS Code Synced Data'); +- assert.ok(result.ref.nonce); - -- ensureNoDisposablesAreLeakedInTestSuite(); +- const root = bundler.rootUri; +- const manifest = await fileService.readFile(URI.joinPath(root, '.plugin', 'plugin.json')); +- assert.match(manifest.value.toString(), /"name": "VS Code Synced Data"/); - -- function text(msg: ReturnType): string { -- return typeof msg === 'string' ? msg : msg.markdown; -- } +- const agent = await fileService.readFile(URI.joinPath(root, 'agents', 'foo.agent.md')); +- assert.strictEqual(agent.value.toString(), 'agent body'); - -- const singleFilePatch = [ -- '*** Begin Patch', -- '*** Update File: /repo/src/foo.ts', -- '@@', -- '-old', -- '+new', -- '*** End Patch', -- ].join('\n'); +- const skill = await fileService.readFile(URI.joinPath(root, 'skills', 'bar', 'SKILL.md')); +- assert.strictEqual(skill.value.toString(), 'skill body'); - -- const multiFilePatch = [ -- '*** Begin Patch', -- '*** Update File: /repo/src/foo.ts', -- '@@', -- '-old', -- '+new', -- '*** Add File: /repo/src/bar.ts', -- '+hello', -- '*** Delete File: /repo/src/baz.ts', -- '*** End Patch', -- ].join('\n'); +- const instr = await fileService.readFile(URI.joinPath(root, 'rules', 'baz.instructions.md')); +- assert.strictEqual(instr.value.toString(), 'instr body'); - -- test('renders a clickable file link for a single-file patch', () => { -- const inv = text(getInvocationMessage('apply_patch', 'Patch', { input: singleFilePatch })); -- const past = text(getPastTenseMessage('apply_patch', 'Patch', { input: singleFilePatch }, true)); -- assert.deepStrictEqual({ inv, past }, { -- inv: 'Editing [foo.ts](file:///repo/src/foo.ts)', -- past: 'Edited [foo.ts](file:///repo/src/foo.ts)', -- }); +- const hook = await fileService.readFile(URI.joinPath(root, 'hooks', 'pre-tool.json')); +- assert.strictEqual(hook.value.toString(), '{"PreToolUse": []}'); - }); - -- test('lists every affected file for a multi-file patch', () => { -- const inv = text(getInvocationMessage('apply_patch', 'Patch', { input: multiFilePatch })); -- const past = text(getPastTenseMessage('apply_patch', 'Patch', { input: multiFilePatch }, true)); -- assert.deepStrictEqual({ inv, past }, { -- inv: 'Editing [foo.ts](file:///repo/src/foo.ts), [bar.ts](file:///repo/src/bar.ts), [baz.ts](file:///repo/src/baz.ts)', -- past: 'Edited [foo.ts](file:///repo/src/foo.ts), [bar.ts](file:///repo/src/bar.ts), [baz.ts](file:///repo/src/baz.ts)', -- }); -- }); - -- test('falls back to a generic message when the patch body is missing or unparseable', () => { -- assert.strictEqual(getInvocationMessage('apply_patch', 'Patch', undefined), 'Editing files'); -- assert.strictEqual(getInvocationMessage('apply_patch', 'Patch', { input: 'not a patch' }), 'Editing files'); -- assert.strictEqual(getPastTenseMessage('apply_patch', 'Patch', undefined, true), 'Edited files'); -- }); +- test('produces a stable nonce for identical content', async () => { +- await seed('/workspace/.github/agents/foo.agent.md', 'agent body'); +- await seed('/workspace/.github/skills/bar/SKILL.md', 'skill body'); - -- test('also accepts the patch text under the `patch` parameter (CLI shape)', () => { -- const inv = text(getInvocationMessage('apply_patch', 'Patch', { patch: singleFilePatch })); -- assert.strictEqual(inv, 'Editing [foo.ts](file:///repo/src/foo.ts)'); -- }); +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const bundler = disposables.add(instantiationService.createInstance(SessionPluginBundler, workspace)); +- const first = await bundler.bundle(await discovery.scan(CancellationToken.None)); - -- test('git_apply_patch shares the same display path', () => { -- const inv = text(getInvocationMessage('git_apply_patch', 'Patch', { input: singleFilePatch })); -- const past = text(getPastTenseMessage('git_apply_patch', 'Patch', { input: singleFilePatch }, true)); -- assert.deepStrictEqual({ inv, past }, { -- inv: 'Editing [foo.ts](file:///repo/src/foo.ts)', -- past: 'Edited [foo.ts](file:///repo/src/foo.ts)', +- let writeCalls = 0; +- let deleteCalls = 0; +- const originalWriteFile = fileService.writeFile.bind(fileService); +- const originalDel = fileService.del.bind(fileService); +- disposables.add({ +- dispose: () => { +- fileService.writeFile = originalWriteFile as typeof fileService.writeFile; +- fileService.del = originalDel as typeof fileService.del; +- } +- }); +- fileService.writeFile = ((...args: Parameters) => { +- writeCalls++; +- return originalWriteFile(...args); +- }) as typeof fileService.writeFile; +- fileService.del = ((...args: Parameters) => { +- deleteCalls++; +- return originalDel(...args); +- }) as typeof fileService.del; +- +- const second = await bundler.bundle(await discovery.scan(CancellationToken.None)); +- assert.ok(first); +- assert.ok(second); +- assert.deepStrictEqual({ +- firstNonce: first.ref.nonce, +- secondNonce: second.ref.nonce, +- writeCalls, +- deleteCalls, +- }, { +- firstNonce: first.ref.nonce, +- secondNonce: first.ref.nonce, +- writeCalls: 0, +- deleteCalls: 0, - }); - }); - -- test('failure still routes through the generic failed message', () => { -- assert.strictEqual(getPastTenseMessage('apply_patch', 'Patch', { input: singleFilePatch }, false), '"Patch" failed'); -- }); +- test('returns undefined without rewriting when cancelled', async () => { +- await seed('/workspace/.github/agents/foo.agent.md', 'agent body'); - -- test('getEditFilePath returns the first affected file from a patch body', () => { -- assert.strictEqual(getEditFilePath({ input: singleFilePatch }), '/repo/src/foo.ts'); -- assert.strictEqual(getEditFilePath({ input: multiFilePatch }), '/repo/src/foo.ts'); -- assert.strictEqual(getEditFilePath({ patch: singleFilePatch }), '/repo/src/foo.ts'); -- assert.strictEqual(getEditFilePath(JSON.stringify({ input: singleFilePatch })), '/repo/src/foo.ts'); -- assert.strictEqual(getEditFilePath({ input: 'not a patch' }), undefined); +- const discovery = disposables.add(instantiationService.createInstance(SessionCustomizationDiscovery, workspace, userHome)); +- const bundler = disposables.add(instantiationService.createInstance(SessionPluginBundler, workspace)); +- +- let writeCalls = 0; +- let deleteCalls = 0; +- const originalWriteFile = fileService.writeFile.bind(fileService); +- const originalDel = fileService.del.bind(fileService); +- disposables.add({ +- dispose: () => { +- fileService.writeFile = originalWriteFile as typeof fileService.writeFile; +- fileService.del = originalDel as typeof fileService.del; +- } +- }); +- fileService.writeFile = ((...args: Parameters) => { +- writeCalls++; +- return originalWriteFile(...args); +- }) as typeof fileService.writeFile; +- fileService.del = ((...args: Parameters) => { +- deleteCalls++; +- return originalDel(...args); +- }) as typeof fileService.del; +- +- const result = await bundler.bundle(await discovery.scan(CancellationToken.None), CancellationToken.Cancelled); +- assert.deepStrictEqual({ result, writeCalls, deleteCalls }, { result: undefined, writeCalls: 0, deleteCalls: 0 }); - }); - -- test('getEditFilePaths returns every affected file from a patch body', () => { -- assert.deepStrictEqual(getEditFilePaths({ input: singleFilePatch }), ['/repo/src/foo.ts']); -- assert.deepStrictEqual(getEditFilePaths({ input: multiFilePatch }), ['/repo/src/foo.ts', '/repo/src/bar.ts', '/repo/src/baz.ts']); -- assert.deepStrictEqual(getEditFilePaths({ patch: multiFilePatch }), ['/repo/src/foo.ts', '/repo/src/bar.ts', '/repo/src/baz.ts']); -- assert.deepStrictEqual(getEditFilePaths(JSON.stringify({ input: multiFilePatch })), ['/repo/src/foo.ts', '/repo/src/bar.ts', '/repo/src/baz.ts']); -- assert.deepStrictEqual(getEditFilePaths({ path: '/repo/src/edit.ts' }), ['/repo/src/edit.ts']); -- assert.deepStrictEqual(getEditFilePaths({ input: 'not a patch' }), []); -- assert.deepStrictEqual(getEditFilePaths(undefined), []); -- // SDK custom-tool format: arguments arrive as a raw V4A patch string, -- // not as a JSON object — exercise the string fallback path. -- assert.deepStrictEqual(getEditFilePaths(multiFilePatch), ['/repo/src/foo.ts', '/repo/src/bar.ts', '/repo/src/baz.ts']); -- assert.deepStrictEqual(getEditFilePaths(singleFilePatch), ['/repo/src/foo.ts']); +- test('different working directories produce different bundle authorities', async () => { +- const otherWorkspace = URI.from({ scheme: Schemas.inMemory, path: '/other-workspace' }); +- const a = disposables.add(instantiationService.createInstance(SessionPluginBundler, workspace)); +- const b = disposables.add(instantiationService.createInstance(SessionPluginBundler, otherWorkspace)); +- assert.notStrictEqual(a.rootUri.toString(), b.rootUri.toString()); - }); -}); From e4acf064b29db5e385441804435f097195c4e957 Mon Sep 17 00:00:00 2001 From: sbs44 <83440025+sbs44@users.noreply.github.com> Date: Sun, 28 Jun 2026 04:45:14 -0400 Subject: [PATCH 4/5] fix(patches): resolve 3 tsgo typecheck errors for VS Code 1.126.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The re-engineered patches now apply cleanly and the build reaches the tsgo typecheck, which surfaced 3 errors (2 pre-existing, exposed now that earlier patch-apply failures are gone): - patch 51 / copilotToolDisplay.ts: the local `PermissionRequest` stub used `kind: string`, too broad for `permissionKind` (TS2322). Narrowed it to the exact union agentService's `IAgentToolPendingConfirmationSignal.permissionKind` expects. - patch 21 / copilotManagedSettingsService.ts: new-in-1.126 file imports `@vscode/policy-watcher`, which patch 21 renames to `@vscodium/policy-watcher`. Added the same rename hunk (matching the existing nativePolicyService.ts rewrite) so the custom policy-watcher lib resolves (TS2307 ×2). All 36 patches apply cleanly in CI order. Refs #21 --- patches/21-policy-use-custom-lib.patch | 22 ++++++++++++++++++++++ patches/51-ext-copilot-remove-it.patch | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/patches/21-policy-use-custom-lib.patch b/patches/21-policy-use-custom-lib.patch index 71381db..149dbdd 100644 --- a/patches/21-policy-use-custom-lib.patch +++ b/patches/21-policy-use-custom-lib.patch @@ -340,3 +340,25 @@ index 6039dbb7..6fe3fe47 100644 this._onDidPolicyChange(update); c(); }); +diff --git a/src/vs/platform/policy/node/copilotManagedSettingsService.ts b/src/vs/platform/policy/node/copilotManagedSettingsService.ts +index 5b76979d..40abf52e 100644 +--- a/src/vs/platform/policy/node/copilotManagedSettingsService.ts ++++ b/src/vs/platform/policy/node/copilotManagedSettingsService.ts +@@ -11,7 +11,7 @@ import { IManagedSettingsPolicyDefinitions, ManagedSettingsData } from '../../.. + import { ILogService } from '../../log/common/log.js'; + import { collectManagedSettingsDefinitions, ICopilotManagedSettingsService } from '../common/copilotManagedSettings.js'; + import { PolicyDefinition, PolicyValue } from '../common/policy.js'; +-import type { Watcher } from '@vscode/policy-watcher'; ++import type { Watcher } from '@vscodium/policy-watcher'; + + export interface ICopilotPolicyWatcherOptions { + readonly registryPath?: string; +@@ -89,7 +89,7 @@ export class CopilotManagedSettingsService extends Disposable implements ICopilo + return; + } + +- const { createWatcher } = this.watcherFactory ? { createWatcher: this.watcherFactory } : (await import('@vscode/policy-watcher') as { createWatcher: CopilotPolicyWatcherFactory }); ++ const { createWatcher } = this.watcherFactory ? { createWatcher: this.watcherFactory } : (await import('@vscodium/policy-watcher') as { createWatcher: CopilotPolicyWatcherFactory }); + await this.throttler.queue(() => new Promise((c, e) => { + try { + this.logService.trace(`Creating Copilot managed-settings watcher for productName ${this.productName}`); diff --git a/patches/51-ext-copilot-remove-it.patch b/patches/51-ext-copilot-remove-it.patch index 5bb92f3..904b0f3 100644 --- a/patches/51-ext-copilot-remove-it.patch +++ b/patches/51-ext-copilot-remove-it.patch @@ -9421,7 +9421,7 @@ index 3a4c2d71..f596430b 100644 +// `PermissionRequest` originally came from '@github/copilot-sdk' (removed in +// !!APP_NAME!!). Only its `kind` discriminator is referenced here, so model it +// as a minimal local shape. -+type PermissionRequest = { kind: string }; ++type PermissionRequest = { kind: 'shell' | 'write' | 'mcp' | 'read' | 'url' | 'custom-tool' | 'hook' | 'memory' | 'extension-management' | 'extension-permission-access' }; import { hasKey } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { appendEscapedMarkdownInlineCode, escapeMarkdownLinkLabel, MarkdownString } from '../../../../base/common/htmlContent.js'; From 3bfc05b98e6997554243efdcf120d61702dcc3fa Mon Sep 17 00:00:00 2001 From: sbs44 <83440025+sbs44@users.noreply.github.com> Date: Sun, 28 Jun 2026 11:34:16 -0400 Subject: [PATCH 5/5] fix(patches): adapt copilotManagedSettings to forked policy-watcher The previous fix renamed @vscode/policy-watcher -> @vscodium/policy-watcher in copilotManagedSettingsService.ts, which let the module resolve and exposed a deeper mismatch (TS2352): the @vscodium fork's createWatcher signature is (vendorName, productName, policies, onDidChange) with no options arg, whereas upstream is (productName, policies, onDidChange, options). nativePolicyService.ts was already adapted by patch 21 (it prepends a vendor name); the new-in-1.126 copilotManagedSettingsService.ts was not. Adapt the production import to the fork's signature while preserving the file's own CopilotPolicyWatcherFactory abstraction, so the 5 injected-factory unit tests stay untouched: wrap the fork's createWatcher to prepend the 'BradfordCode' vendor name (matching nativePolicyService) and ignore the unsupported options. All 36 patches apply cleanly in CI order. Refs #21 --- patches/21-policy-use-custom-lib.patch | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/patches/21-policy-use-custom-lib.patch b/patches/21-policy-use-custom-lib.patch index 149dbdd..88a25bb 100644 --- a/patches/21-policy-use-custom-lib.patch +++ b/patches/21-policy-use-custom-lib.patch @@ -341,7 +341,7 @@ index 6039dbb7..6fe3fe47 100644 c(); }); diff --git a/src/vs/platform/policy/node/copilotManagedSettingsService.ts b/src/vs/platform/policy/node/copilotManagedSettingsService.ts -index 5b76979d..40abf52e 100644 +index 5b76979d..d8223ab9 100644 --- a/src/vs/platform/policy/node/copilotManagedSettingsService.ts +++ b/src/vs/platform/policy/node/copilotManagedSettingsService.ts @@ -11,7 +11,7 @@ import { IManagedSettingsPolicyDefinitions, ManagedSettingsData } from '../../.. @@ -353,12 +353,27 @@ index 5b76979d..40abf52e 100644 export interface ICopilotPolicyWatcherOptions { readonly registryPath?: string; -@@ -89,7 +89,7 @@ export class CopilotManagedSettingsService extends Disposable implements ICopilo +@@ -89,7 +89,22 @@ export class CopilotManagedSettingsService extends Disposable implements ICopilo return; } - const { createWatcher } = this.watcherFactory ? { createWatcher: this.watcherFactory } : (await import('@vscode/policy-watcher') as { createWatcher: CopilotPolicyWatcherFactory }); -+ const { createWatcher } = this.watcherFactory ? { createWatcher: this.watcherFactory } : (await import('@vscodium/policy-watcher') as { createWatcher: CopilotPolicyWatcherFactory }); ++ let createWatcher: CopilotPolicyWatcherFactory; ++ if (this.watcherFactory) { ++ createWatcher = this.watcherFactory; ++ } else { ++ const policyWatcher = await import('@vscodium/policy-watcher'); ++ // `@vscodium/policy-watcher`'s `createWatcher` takes a leading vendor name ++ // and no options argument, unlike upstream `@vscode/policy-watcher`; adapt ++ // it to this service's CopilotPolicyWatcherFactory shape. ++ const createNativeWatcher = policyWatcher.createWatcher as unknown as ( ++ vendorName: string, ++ productName: string, ++ policies: Record, ++ onDidChange: (update: Record) => void, ++ ) => Watcher; ++ createWatcher = (productName, policies, onDidChange) => createNativeWatcher('BradfordCode', productName, policies, onDidChange); ++ } await this.throttler.queue(() => new Promise((c, e) => { try { this.logService.trace(`Creating Copilot managed-settings watcher for productName ${this.productName}`);