Skip to content

Commit deea719

Browse files
authored
Merge branch 'main' into agents/investigate-issue-333197-root-cause
2 parents 00e9a40 + aa5dc47 commit deea719

265 files changed

Lines changed: 13104 additions & 2411 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as eslint from 'eslint';
7+
import { TSESTree } from '@typescript-eslint/utils';
8+
import * as ts from 'typescript';
9+
10+
/**
11+
* Disallow bracket notation for accessing properties that are valid identifiers,
12+
* especially private members (starting with underscore). Bracket notation should
13+
* only be used for properties with special characters or computed property names.
14+
*
15+
* Bad: obj['_privateMember']
16+
* Bad: obj['normalProperty']
17+
* Good: obj._privateMember // TypeScript will catch private access
18+
* Good: obj.normalProperty
19+
* Good: obj['property-with-dashes']
20+
* Good: obj[computedKey]
21+
*/
22+
export default new class NoBracketNotationForIdentifiers implements eslint.Rule.RuleModule {
23+
24+
readonly meta: eslint.Rule.RuleMetaData = {
25+
type: 'problem',
26+
docs: {
27+
description: 'Disallow bracket notation for accessing properties that are valid identifiers'
28+
},
29+
messages: {
30+
noBracketNotation: 'Use dot notation instead of bracket notation for property \'{{property}}\'. Bracket notation bypasses TypeScript\'s type checking and access modifiers.'
31+
},
32+
schema: [],
33+
fixable: 'code'
34+
};
35+
36+
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
37+
38+
/**
39+
* Check if a string is a valid JavaScript identifier
40+
*/
41+
function isValidIdentifier(str: string): boolean {
42+
if (str.includes('\\')) {
43+
return false;
44+
}
45+
const scanner = ts.createScanner(ts.ScriptTarget.Latest, false, ts.LanguageVariant.Standard, str);
46+
const token = scanner.scan();
47+
const isIdentifierName = token === ts.SyntaxKind.Identifier
48+
|| (token >= ts.SyntaxKind.FirstKeyword && token <= ts.SyntaxKind.LastKeyword);
49+
return isIdentifierName && scanner.getTokenText() === str && scanner.scan() === ts.SyntaxKind.EndOfFileToken;
50+
}
51+
52+
return {
53+
MemberExpression(node: any) {
54+
const memberExpr = node as TSESTree.MemberExpression;
55+
56+
// Only check computed member expressions (bracket notation)
57+
if (!memberExpr.computed) {
58+
return;
59+
}
60+
61+
// Only check string literals in brackets
62+
if (memberExpr.property.type !== 'Literal' || typeof memberExpr.property.value !== 'string') {
63+
return;
64+
}
65+
66+
const propertyName = memberExpr.property.value;
67+
68+
// If it's a valid identifier, report it
69+
if (isValidIdentifier(propertyName)) {
70+
context.report({
71+
node: memberExpr.property,
72+
messageId: 'noBracketNotation',
73+
data: {
74+
property: propertyName
75+
},
76+
fix(fixer) {
77+
const property = memberExpr.property as unknown as eslint.Rule.Node;
78+
const leftBracket = context.sourceCode.getTokenBefore(property);
79+
const rightBracket = context.sourceCode.getTokenAfter(property);
80+
if (leftBracket?.value !== '[' || rightBracket?.value !== ']') {
81+
return null;
82+
}
83+
const hasComments = context.sourceCode
84+
.getTokensBetween(leftBracket, rightBracket, { includeComments: true })
85+
.some(token => token.type === 'Block' || token.type === 'Line');
86+
if (hasComments) {
87+
return null;
88+
}
89+
90+
const bracketFix = fixer.replaceTextRange(
91+
[leftBracket.range[0], rightBracket.range[1]],
92+
`${memberExpr.optional ? '' : '.'}${propertyName}`
93+
);
94+
if (memberExpr.object.type === 'Literal' && typeof memberExpr.object.value === 'number') {
95+
const object = memberExpr.object as unknown as eslint.Rule.Node;
96+
return [
97+
fixer.insertTextBefore(object, '('),
98+
fixer.insertTextAfter(object, ')'),
99+
bracketFix
100+
];
101+
}
102+
103+
return bracketFix;
104+
}
105+
});
106+
}
107+
}
108+
};
109+
}
110+
};

.github/skills/sessions/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Start with `src/vs/sessions/README.md`, then read only the specifications releva
2525
|------|---------------|
2626
| Layering, folder ownership, cross-module imports | `src/vs/sessions/LAYERS.md` |
2727
| Session/chat model, services, provider contract, core data flow | `src/vs/sessions/SESSIONS.md` |
28+
| Automations ownership, routing, migration, persistence, and run lifecycle | `src/vs/sessions/AUTOMATIONS.md` |
2829
| Workbench parts, grid, title bar, editor presentation | `src/vs/sessions/LAYOUT.md` |
2930
| Session-aware layout state and restoration | `src/vs/sessions/LAYOUT_CONTROLLER.md` |
3031
| Single-pane behavior and expected compositions | `src/vs/sessions/SINGLE_PANE_SCENARIOS.md` |

.github/skills/sweeper-fix/SKILL.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
---
22
name: sweeper-fix
3-
description: Fix a microsoft/vscode issue that the VS Code Sweeper reviewed as agent-fixable. Fetches the review's fix spec from the sweeper's public state repo, implements the narrow fix in the current vscode checkout, and — after showing the diff — opens a draft PR. Use when asked to fix a vscode issue with the sweeper-fix skill, a vscodesweeper record, or a sweeper fix spec.
3+
description: Implement a VS Code Sweeper fix spec — fetch the sweeper's agent-fixable review record from its public state repo, implement the narrow fix in the current vscode checkout, and — after showing the diff — open a draft PR. Use ONLY when the request explicitly asks for the sweeper — "sweeper-fix", "sweeper", "vscodesweeper", a sweeper record, or a sweeper fix spec. Do NOT use for a plain "fix this issue" request that doesn't mention the sweeper; fix those directly with your normal tools instead.
44
---
55

6-
<!-- Generated by vscodesweeper (sweeper-fix skill v4) — do not edit by hand.
7-
Source: prompts/sweeper-fix-skill.md in the vscodesweeper repo; getting started:
6+
<!-- Generated by vscodesweeper (sweeper-fix skill v5) — do not edit by hand.
7+
Source: skills/sweeper-fix/SKILL.template.md in the vscodesweeper repo; getting started:
88
https://egamma.github.io/vscodesweeper-state/fix-skill.html -->
99

1010
# sweeper-fix — implement a sweeper-reviewed fix
@@ -34,8 +34,17 @@ access):
3434
gh api "repos/egamma/vscodesweeper-state/contents/records/microsoft/vscode/items/<issue-number>.md?ref=state" -H "Accept: application/vnd.github.raw"
3535
```
3636

37-
No record → stop: this issue hasn't been reviewed by the sweeper; the skill only fixes
38-
reviewed, agent-fixable issues.
37+
No record → this skill does not apply: the issue hasn't been reviewed by the sweeper, and
38+
the skill only implements sweeper fix specs. Say so in one line, then **continue fixing the
39+
issue by your normal means** — fetch it with the repo pinned explicitly (never bare
40+
`gh issue view`, which a fork remote can redirect to the wrong repo's issue `<n>`), then
41+
analyze and implement:
42+
43+
```
44+
gh issue view <issue-number> --repo microsoft/vscode
45+
```
46+
47+
The absence of a sweeper record is never a reason to refuse the fix itself.
3948

4049
## 2 · Gate — every check against LIVE GitHub state, not just the record
4150

.vscode/notebooks/endgame.github-issues

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
{
88
"kind": 2,
99
"language": "github-issues",
10-
"value": "$MILESTONE=milestone:\"1.135.0\"\n\n$TPI_CREATION=2026-03-23 // Used to find fixes that need to be verified"
10+
"value": "$MILESTONE=milestone:\"1.136.0\"\n\n$TPI_CREATION=2026-03-23 // Used to find fixes that need to be verified"
1111
},
1212
{
1313
"kind": 1,

.vscode/notebooks/my-endgame.github-issues

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
{
88
"kind": 2,
99
"language": "github-issues",
10-
"value": "$MILESTONE=milestone:\"1.135.0\"\n\n$MINE=assignee:@me"
10+
"value": "$MILESTONE=milestone:\"1.136.0\"\n\n$MINE=assignee:@me"
1111
},
1212
{
1313
"kind": 2,

build/lib/preLaunch.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ async function getElectron() {
4040
// directory is being removed and re-extracted. Skip the refresh when the
4141
// already-present Electron matches the expected version; any detection
4242
// failure falls back to a (re)download to preserve the previous behavior.
43-
if (await isExpectedElectronInstalled()) {
43+
if (!process.env['VSCODE_FORCE_PRELAUNCH'] && await isExpectedElectronInstalled()) {
4444
return;
4545
}
4646
await runProcess(npm, ['run', 'electron']);
@@ -66,9 +66,6 @@ async function ensureCompiled() {
6666
async function main() {
6767
await ensureNodeModules();
6868
await getElectron();
69-
if (process.argv.includes('--only-electron')) {
70-
return;
71-
}
7269
await ensureCompiled();
7370

7471
// Can't require this until after dependencies are installed

build/linux/rpm/dep-lists.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ export const referenceGeneratedDepsByArch = {
8181
'libgtk-3.so.0()(64bit)',
8282
'libm.so.6()(64bit)',
8383
'libm.so.6(GLIBC_2.2.5)(64bit)',
84-
'libm.so.6(GLIBC_2.27)(64bit)',
8584
'libnspr4.so()(64bit)',
8685
'libnss3.so()(64bit)',
8786
'libnss3.so(NSS_3.11)(64bit)',
@@ -276,7 +275,6 @@ export const referenceGeneratedDepsByArch = {
276275
'libgtk-3.so.0()(64bit)',
277276
'libm.so.6()(64bit)',
278277
'libm.so.6(GLIBC_2.17)(64bit)',
279-
'libm.so.6(GLIBC_2.27)(64bit)',
280278
'libnspr4.so()(64bit)',
281279
'libnss3.so()(64bit)',
282280
'libnss3.so(NSS_3.11)(64bit)',

cli/src/commands/agent_host.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ fn decide_foreground_action(
148148
) {
149149
return ForegroundAction::ConflictError(format!(
150150
"Agent host already running on {host_str}:{port} (PID {pid}), but {conflict}.\n\
151-
Use `code agent kill` to stop it, or pass `--replace` to take over.",
151+
Use `{application_name} agent kill` to stop it, or pass `--replace` to take over.",
152+
application_name = constants::APPLICATION_NAME,
152153
host_str = host.as_deref().unwrap_or("127.0.0.1"),
153154
));
154155
}
@@ -461,7 +462,7 @@ async fn run_supervisor(mut ctx: CommandContext, mut args: AgentHostArgs) -> Res
461462
.and_then(|h| h.parse::<std::net::IpAddr>().ok())
462463
.unwrap_or(std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST));
463464
output::print_network_lines(bound_port, banner_listen_ip, &token_suffix);
464-
output::print_banner_line("Manage", "code agent ps | code agent kill");
465+
print_manage_banner_line();
465466
output::print_banner_footer();
466467
let _ = std::io::stdout().flush();
467468

@@ -554,15 +555,24 @@ fn print_reuse_banner(
554555
.and_then(|h| h.parse::<std::net::IpAddr>().ok())
555556
.unwrap_or(std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST));
556557
output::print_network_lines(port, banner_listen_ip, &token_suffix);
557-
output::print_banner_line("Manage", "code agent ps | code agent kill");
558+
print_manage_banner_line();
558559
output::print_banner_footer();
559560
let _ = std::io::stdout().flush();
560561
log.result(format!(
561562
"Agent host supervisor already running (PID {pid}). \
562-
Use `code agent kill` to stop it, or `code agent host --replace` to start a fresh one."
563+
Use `{application_name} agent kill` to stop it, or `{application_name} agent host --replace` to start a fresh one.",
564+
application_name = constants::APPLICATION_NAME,
563565
));
564566
}
565567

568+
fn print_manage_banner_line() {
569+
let application_name = constants::APPLICATION_NAME;
570+
output::print_banner_line(
571+
"Manage",
572+
&format!("{application_name} agent ps | {application_name} agent kill"),
573+
);
574+
}
575+
566576
/// Compare the user's requested supervisor configuration with what's
567577
/// recorded for the running supervisor's registry entry. Returns a short
568578
/// human description of the first conflict found (e.g. `"--host 0.0.0.0
@@ -665,6 +675,15 @@ async fn daemonize_supervisor() -> Result<i32, AnyError> {
665675
// passed in foreground.
666676
cmd.args(std::env::args_os().skip(1));
667677
cmd.env(SUPERVISOR_ENV, "1");
678+
#[cfg(windows)]
679+
cmd.env(
680+
output::PARENT_STDOUT_SUPPORTS_UTF8_ENV,
681+
if output::stdout_supports_utf8() {
682+
"1"
683+
} else {
684+
"0"
685+
},
686+
);
668687
cmd.stdin(std::process::Stdio::null());
669688
cmd.stdout(std::process::Stdio::piped());
670689
cmd.stderr(std::process::Stdio::piped());

cli/src/commands/agent_logs.rs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::util::errors::AnyError;
1515
use super::agent;
1616
use super::agent_discovery;
1717
use super::args::AgentLogsArgs;
18-
use super::output::Styles;
18+
use super::output::{self, Styles};
1919
use super::CommandContext;
2020

2121
/// Subscribes to a session and streams actions/notifications in real time.
@@ -55,7 +55,10 @@ pub async fn agent_logs(ctx: CommandContext, args: AgentLogsArgs) -> Result<i32,
5555
"\n{}",
5656
header.apply_to("Streaming events (Ctrl+C to quit)...")
5757
);
58-
println!("{}", header.apply_to("─".repeat(50)));
58+
println!(
59+
"{}",
60+
header.apply_to(output::utf8_or_ascii("─", "-").repeat(50))
61+
);
5962

6063
// Stream events until Ctrl+C or the subscription closes.
6164
let mut shutdown = ShutdownRequest::create_rx([ShutdownRequest::CtrlC]);
@@ -118,11 +121,14 @@ fn print_initial_state(uri: &str, result: &SubscribeResult) {
118121
for chat in &session.chats {
119122
let status = SessionStatus::from_bits(chat.status);
120123
let marker = if status.contains(SessionStatus::InProgress) {
121-
Style::new().green().bold().apply_to("►")
124+
Style::new()
125+
.green()
126+
.bold()
127+
.apply_to(output::utf8_or_ascii("►", ">"))
122128
} else if status.contains(SessionStatus::Error) {
123-
Styles::error().apply_to("✗")
129+
Styles::error().apply_to(output::utf8_or_ascii("✗", "x"))
124130
} else {
125-
Styles::muted().apply_to("○")
131+
Styles::muted().apply_to(output::utf8_or_ascii("○", "o"))
126132
};
127133
let title = if chat.title.is_empty() {
128134
"(untitled)".to_string()
@@ -190,9 +196,30 @@ fn action_style(type_name: &str) -> Style {
190196

191197
fn truncate(s: &str, max: usize) -> String {
192198
let s = s.replace('\n', " ");
193-
if s.len() <= max {
199+
if s.chars().count() <= max {
194200
s
195201
} else {
196-
format!("{}…", &s[..max - 1])
202+
let suffix = output::utf8_or_ascii("…", "...");
203+
let prefix_length = max.saturating_sub(suffix.chars().count());
204+
format!(
205+
"{}{suffix}",
206+
s.chars().take(prefix_length).collect::<String>()
207+
)
208+
}
209+
}
210+
211+
#[cfg(test)]
212+
mod tests {
213+
use super::*;
214+
215+
#[test]
216+
fn truncate_respects_character_limit() {
217+
assert_eq!(
218+
(
219+
truncate("abcdef", 5).chars().count(),
220+
truncate("áéíóúñ", 5).chars().count()
221+
),
222+
(5, 5)
223+
);
197224
}
198225
}

cli/src/commands/agent_ps.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,13 @@ async fn agent_ps_multi(
201201
/// complete).
202202
fn print_host_outcome_human(outcome: &HostSessions, all: bool) -> Result<(), AnyError> {
203203
let header = Styles::title();
204+
let separator = output::utf8_or_ascii("──", "--");
204205
println!(
205206
"\n{}",
206-
header.apply_to(format!("── {} ──", outcome.endpoint.label()))
207+
header.apply_to(format!(
208+
"{separator} {} {separator}",
209+
outcome.endpoint.label()
210+
))
207211
);
208212

209213
match &outcome.sessions {
@@ -216,7 +220,8 @@ fn print_host_outcome_human(outcome: &HostSessions, all: bool) -> Result<(), Any
216220
}
217221
}
218222
Err(e) => {
219-
println!(" {}", Styles::error().apply_to(format!("⚠ {e}")));
223+
let warning = output::utf8_or_ascii("⚠", "!");
224+
println!(" {}", Styles::error().apply_to(format!("{warning} {e}")));
220225
}
221226
}
222227

@@ -385,13 +390,13 @@ fn format_sessions_list(sessions: &[&SessionSummary]) -> String {
385390
fn status_styled(status: u32) -> console::StyledObject<String> {
386391
let status = SessionStatus::from_bits(status);
387392
if status.contains(SessionStatus::InputNeeded) {
388-
Styles::warning().apply_to("● input needed".to_string())
393+
Styles::warning().apply_to(format!("{} input needed", output::utf8_or_ascii("●", "*")))
389394
} else if status.contains(SessionStatus::InProgress) {
390-
Styles::success().apply_to("● in progress".to_string())
395+
Styles::success().apply_to(format!("{} in progress", output::utf8_or_ascii("●", "*")))
391396
} else if status.contains(SessionStatus::Error) {
392-
Styles::error().apply_to("● error".to_string())
397+
Styles::error().apply_to(format!("{} error", output::utf8_or_ascii("●", "*")))
393398
} else if status.contains(SessionStatus::Idle) {
394-
Styles::muted().apply_to("○ idle".to_string())
399+
Styles::muted().apply_to(format!("{} idle", output::utf8_or_ascii("○", "o")))
395400
} else {
396401
Styles::muted().apply_to(format!("? unknown ({})", status.bits()))
397402
}

0 commit comments

Comments
 (0)