Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion .agents/skills/launch/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,31 @@ SESSION_TITLE=<title-from-get_current_session>
"$LAUNCH" --agents --session-title "$SESSION_TITLE"
"$LAUNCH" -- <workspace-path> # forward extra args to code.sh
"$LAUNCH" --source-user-data-dir <path> # pick a specific authed profile
"$LAUNCH" --settings-overrides <json-file> # boolean/string settings applied to the copy before launch
"$LAUNCH" --clean-agent-config # keep auth, omit copied settings/MCP/plugins/Agent Host state
"$LAUNCH" --repo <vscode-repo-root> # if not run from the repo
"$LAUNCH" --clone-extensions # start with a copy of the source extensions/ (~few seconds)
"$LAUNCH" --full # skip slim excludes; copy everything
"$LAUNCH" --skip-prelaunch # reuse already-current build outputs
"$LAUNCH" --disable-workspace-trust # avoid trust prompts for trusted automation inputs
```

On Windows, invoke the PowerShell launcher with the same flags:
On macOS/Linux, `--settings-overrides` accepts a JSON object of boolean/string
settings applied to the throwaway profile before Code OSS starts. Use this for
scenario-specific setup such as disabling scheduled Automations or cloud agents
in a local-only test. Authentication clones retain application-level session and
Automation metadata even in slim mode; isolated directories alone do not disable
those features. The source profile is never changed.

For a controlled local-agent scenario, combine it with `--clean-agent-config`.
This also omits user settings/keybindings, MCP files, prompt/plugin directories,
BYOK model configuration and persisted Agent Host/session data. It retains the
opaque authentication storage, which can still contain cached UI metadata; it is
not a general authentication-only export. This macOS/Linux option is incompatible
with `--full`.

On Windows, invoke the PowerShell launcher with the same flags except the
macOS/Linux-only `--settings-overrides` and `--clean-agent-config`:

```powershell
$skillDir = '<dir-of-this-SKILL.md>'
Expand Down Expand Up @@ -101,6 +118,7 @@ To (re)establish the source session: run `.\scripts\code.bat --user-data-dir=$en

Excluded (transient, regenerable, or known-not-needed):
- `User/workspaceStorage/` - per-workspace state, **including stored chat sessions** (often multi-GB)
- `User/agent-sessions.code-workspace` - the generated Agents workspace, which can otherwise reopen unrelated folders in the copy
- `User/History/` - local file edit history
- `CachedExtensionVSIXs` - backup VSIXs (hundreds of MB)
- `logs`
Expand Down Expand Up @@ -383,6 +401,10 @@ The launcher also passes `--shared-data-dir=<runDir>/shared-data`. This is **req

## Restart after source changes

The macOS/Linux launcher also passes `--agent-plugins-dir=<runDir>/agent-plugins`
so plugin state in the throwaway profile does not share the original profile's
machine-level plugin directory.

Workbench code is loaded when the Code OSS window starts; source changes are not hot-reloaded into an already-running instance. After the build output is current, kill the launched process, launch again, and reattach to the new `cdpPort` from the new JSON blob.

```bash
Expand Down
22 changes: 21 additions & 1 deletion .agents/skills/launch/scripts/launch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ FULL=0
SKIP_PRELAUNCH=0
DISABLE_WORKSPACE_TRUST=0
SESSION_TITLE=""
SETTINGS_OVERRIDES=""
CLEAN_AGENT_CONFIG=0

while [[ $# -gt 0 ]]; do
case "$1" in
Expand All @@ -57,6 +59,8 @@ while [[ $# -gt 0 ]]; do
shift 2
;;
--source-user-data-dir) SOURCE_UDD="$2"; shift 2 ;;
--settings-overrides) SETTINGS_OVERRIDES="$2"; shift 2 ;;
--clean-agent-config) CLEAN_AGENT_CONFIG=1; shift ;;
--repo) REPO="$2"; shift 2 ;;
--clone-extensions|--copy-extensions) CLONE_EXTENSIONS=1; shift ;;
--full) FULL=1; shift ;;
Expand All @@ -67,6 +71,11 @@ while [[ $# -gt 0 ]]; do
esac
done

if [[ "$CLEAN_AGENT_CONFIG" == "1" && "$FULL" == "1" ]]; then
echo "--clean-agent-config cannot be combined with --full." >&2
exit 2
fi

monotonic_ms() {
node -e 'process.stdout.write(String(process.hrtime.bigint() / 1_000_000n))'
}
Expand Down Expand Up @@ -134,6 +143,7 @@ mkdir -p "$DEST_UDD" "$SHARED_DATA_DIR"
EXCLUDES=(
'/extensions' # handled separately below
'/workspaceStorage' 'User/workspaceStorage' # per-workspace state, incl. chat sessions
'User/agent-sessions.code-workspace' # generated Agents workspace, not authentication
'User/History' # local file edit history
'/CachedExtensionVSIXs' # backup VSIXs
'/logs'
Expand All @@ -146,6 +156,15 @@ EXCLUDES=(
'/Singleton*'
'*.lock' '*.sock'
)
if [[ "$CLEAN_AGENT_CONFIG" == "1" ]]; then
EXCLUDES+=(
'User/settings.json' 'User/keybindings.json' 'User/mcp.json' 'User/prompts'
'User/chatLanguageModels.json' 'User/agentHostCustomizations'
'User/profiles/*/settings.json' 'User/profiles/*/keybindings.json'
'User/profiles/*/mcp.json' 'User/profiles/*/prompts'
'/agent-host' '/agentSessionData' '/agentPlugins' '/agent-plugins'
)
fi

if [[ "$FULL" == "1" ]]; then
echo "[launch.sh] full copy: $SOURCE_UDD -> $DEST_UDD" >&2
Expand Down Expand Up @@ -182,7 +201,7 @@ SETTINGS_SESSION_TITLE="$SESSION_TITLE"
if [[ "$AGENTS" == "1" ]]; then
SETTINGS_SESSION_TITLE=""
fi
if ! node "$SETTINGS_SCRIPT" "$SETTINGS_FILE" "$SETTINGS_SESSION_TITLE" "$SOURCE_SETTINGS_FILE"; then
if ! node "$SETTINGS_SCRIPT" "$SETTINGS_FILE" "$SETTINGS_SESSION_TITLE" "$SOURCE_SETTINGS_FILE" "$SETTINGS_OVERRIDES"; then
echo "[launch.sh] failed to update launch settings in $SETTINGS_FILE" >&2
exit 1
fi
Expand Down Expand Up @@ -210,6 +229,7 @@ ARGS=(
"--user-data-dir=$DEST_UDD"
"--extensions-dir=$EXT_DIR"
"--shared-data-dir=$SHARED_DATA_DIR"
"--agent-plugins-dir=$RUN_DIR/agent-plugins"
"--remote-debugging-port=$CDP_PORT"
"--inspect-extensions=$EXTHOST_PORT"
"--inspect=$MAIN_PORT"
Expand Down
15 changes: 14 additions & 1 deletion .agents/skills/launch/scripts/updateSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import * as fs from 'node:fs';
const settingsFile = process.argv[2];
const sessionTitle = process.argv[3]?.replace(/\s+/g, ' ').trim().replaceAll('$', '\uFF04');
const sourceSettingsFile = process.argv[4];
const overridesFile = process.argv[5];

if (!settingsFile) {
throw new Error('Usage: updateSettings.ts <settings-file> [session-title] [source-settings-file]');
throw new Error('Usage: updateSettings.ts <settings-file> [session-title] [source-settings-file] [boolean-or-string-overrides-file]');
}

let settingsStat;
Expand Down Expand Up @@ -38,6 +39,18 @@ if (!text.trim()) {
text = '{}\n';
}

if (overridesFile) {
const overrides: Record<string, unknown> = JSON.parse(fs.readFileSync(overridesFile, 'utf8'));
if (!overrides || typeof overrides !== 'object' || Array.isArray(overrides)) {
throw new Error(`Settings overrides must be a JSON object: ${overridesFile}`);
}
for (const [key, value] of Object.entries(overrides)) {
if (typeof value !== 'string' && typeof value !== 'boolean') {
throw new Error(`Setting override '${key}' must be a boolean or a string.`);
}
text = setJsoncProperty(text, key, value);
}
}
text = setJsoncProperty(text, 'files.simpleDialog.enable', true);
if (sessionTitle) {
text = setJsoncProperty(
Expand Down
5 changes: 5 additions & 0 deletions .eslint-allowed-javascript-files
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ src/vs/editor/test/node/diffing/fixtures/difficult-move/1.js
src/vs/editor/test/node/diffing/fixtures/difficult-move/2.js
src/vs/editor/test/node/diffing/fixtures/just-whitespace/1.js
src/vs/editor/test/node/diffing/fixtures/just-whitespace/2.js
src/vs/platform/agentHost/test/node/providerIntegration/fixtures/liveCanvas/extension.mjs
src/vs/platform/agentHost/test/node/providerIntegration/fixtures/localCanvas/client.js
src/vs/platform/agentHost/test/node/providerIntegration/fixtures/localCanvas/extension.mjs
src/vs/platform/files/test/node/fixtures/resolver/examples/company.js
src/vs/platform/files/test/node/fixtures/resolver/examples/conway.js
src/vs/platform/files/test/node/fixtures/resolver/examples/employee.js
Expand All @@ -124,6 +127,8 @@ src/vs/platform/files/test/node/fixtures/service/deep/company.js
src/vs/platform/files/test/node/fixtures/service/deep/conway.js
src/vs/platform/files/test/node/fixtures/service/deep/employee.js
src/vs/platform/files/test/node/fixtures/service/deep/small.js
src/vs/sessions/contrib/providers/agentHost/canvasAuthoringTemplate/client.js
src/vs/sessions/contrib/providers/agentHost/canvasAuthoringTemplate/extension.mjs
src/vs/sessions/test/e2e/common.cjs
src/vs/sessions/test/e2e/extensions/sessions-e2e-mock/extension.js
src/vs/sessions/test/e2e/generate.cjs
Expand Down
1 change: 1 addition & 0 deletions build/gulpfile.vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ const vscodeResourceIncludes = [
'out-build/vs/sessions/contrib/welcome/browser/media/themePreviews/*.svg',
'out-build/vs/sessions/prompts/*.prompt.md',
'out-build/vs/sessions/skills/**/SKILL.md',
'out-build/vs/sessions/contrib/providers/agentHost/canvasAuthoringTemplate/**',

// Extensions
'out-build/vs/workbench/contrib/extensions/browser/media/{theme-icon.png,language-icon.svg}',
Expand Down
4 changes: 4 additions & 0 deletions build/lib/i18n.resources.json
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,10 @@
"name": "vs/sessions/contrib/changes",
"project": "vscode-sessions"
},
{
"name": "vs/sessions/contrib/canvases",
"project": "vscode-sessions"
},
{
"name": "vs/sessions/contrib/chat",
"project": "vscode-sessions"
Expand Down
33 changes: 33 additions & 0 deletions build/lib/stylelint/vscode-known-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,39 @@
"--vscode-modernSash-gripForeground"
],
"others": [
"--bgColor-accent-emphasis",
"--bgColor-accent-muted",
"--bgColor-attention-emphasis",
"--bgColor-attention-muted",
"--bgColor-danger-emphasis",
"--bgColor-danger-muted",
"--bgColor-default",
"--bgColor-disabled",
"--bgColor-inset",
"--bgColor-muted",
"--bgColor-success-emphasis",
"--bgColor-success-muted",
"--borderColor-accent-emphasis",
"--borderColor-attention-emphasis",
"--borderColor-danger-emphasis",
"--borderColor-default",
"--borderColor-disabled",
"--borderColor-muted",
"--borderColor-success-emphasis",
"--fgColor-accent",
"--fgColor-attention",
"--fgColor-danger",
"--fgColor-default",
"--fgColor-disabled",
"--fgColor-done",
"--fgColor-link",
"--fgColor-muted",
"--fgColor-neutral",
"--fgColor-onEmphasis",
"--fgColor-severe",
"--fgColor-sponsors",
"--fgColor-success",
"--focus-outline-color",
"--action-widget-close-start-opacity",
"--action-widget-close-start-transform",
"--activity-bar-action-gap",
Expand Down
83 changes: 83 additions & 0 deletions scripts/launch-local-canvas-poc.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { spawn } from 'node:child_process';
import { readFile, realpath } from 'node:fs/promises';
import { homedir } from 'node:os';
import { join } from 'node:path';
import { parseArgs } from 'node:util';
import { fileURLToPath } from 'node:url';
import { prepareLocalCanvasPoc, type ILocalCanvasPoc } from './prepare-local-canvas-poc.mts';

async function launch() {
const { values } = parseArgs({
options: {
'root': { type: 'string' },
'source-user-data-dir': { type: 'string' },
'session-title': { type: 'string', default: 'Local canvases PoC' },
'skip-prelaunch': { type: 'boolean', default: false },
'help': { type: 'boolean', default: false },
},
});
if (values.help) {
console.log('Usage: node scripts/launch-local-canvas-poc.mts [--root existing-demo-directory] [--source-user-data-dir authenticated-profile] [--skip-prelaunch]');
console.log('Without --root, prepares a fresh reviewed demo. With --root, reuses its document data.');
console.log('The source profile is copied by the standard Code OSS dev launcher, never edited.');
return;
}
if (process.platform === 'win32') {
throw new Error('This local PoC launcher currently supports macOS and Linux. Use the existing PowerShell development launcher with the documented isolated environment on Windows.');
}

const sourceProfile = values['source-user-data-dir'] ?? join(homedir(), '.vscode-oss-dev');
const root = values.root ? await realpath(values.root) : (await prepareLocalCanvasPoc()).root;
const manifest: ILocalCanvasPoc = JSON.parse(await readFile(join(root, 'poc.json'), 'utf8'));
if (!manifest || manifest.version !== 1 || manifest.extensionId !== 'user:local-canvas-demo' || manifest.root !== root) {
throw new Error('The requested root is not a directory prepared by prepare-local-canvas-poc.mts.');
}

const repository = fileURLToPath(new URL('../', import.meta.url));
const launcher = join(repository, '.agents', 'skills', 'launch', 'scripts', 'launch.sh');
const home = join(root, 'home');
const args = [
launcher, '--agents', '--repo', repository,
'--clean-agent-config',
'--session-title', values['session-title'],
'--source-user-data-dir', sourceProfile,
'--settings-overrides', join(root, 'profile-settings.json'),
...(values['skip-prelaunch'] ? ['--skip-prelaunch'] : []),
'--', join(root, 'workspace'),
];
console.error(`Local canvas demo: ${root}`);
console.error('Only the reviewed demo extension is intended for this development home. Tool approvals do not sandbox Node extension code.');
const child = spawn('bash', args, {
cwd: repository,
stdio: 'inherit',
env: {
...process.env,
VSCODE_LOCAL_CANVAS_POC_ROOT: root,
COPILOT_HOME: join(root, 'copilot-home'),
XDG_CONFIG_HOME: join(home, '.config'),
XDG_CACHE_HOME: join(home, '.cache'),
XDG_DATA_HOME: join(home, '.local', 'share'),
},
});
const exitCode = await new Promise<number>((resolve, reject) => {
child.once('error', reject);
child.once('exit', (code, signal) => {
if (signal) {
reject(new Error(`Code OSS launcher stopped by ${signal}. Demo data remains at ${root}.`));
} else {
resolve(code ?? 1);
}
});
});
process.exitCode = exitCode;
}

await launch().catch(error => {
console.error(error);
process.exitCode = 1;
});
Loading
Loading