Skip to content

Commit e2cddfa

Browse files
fix(web): preserve literal launch arguments (#635)
* fix(web): preserve literal launch arguments * chore: add PR 635 changeset
1 parent 2c28363 commit e2cddfa

3 files changed

Lines changed: 26 additions & 10 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"agent-bundle": patch
3+
---
4+
5+
Preserve literal stdio arguments when `agent-bundle dev` selects `/web` launches with AB8023 (#635)

‎packages/agent-bundle/src/dev/web-host-launch-selection.ts‎

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { readFile } from 'node:fs/promises';
2-
import { isAbsolute, join, resolve } from 'node:path';
2+
import { join, resolve } from 'node:path';
33

44
import type { TargetRegistry } from '../adapters/registry.ts';
55
import { digest } from '../core/digest.ts';
@@ -64,18 +64,11 @@ interface LaunchCandidate {
6464
readonly target: string;
6565
}
6666

67-
const normalizedStdioArgument = (
67+
const normalizedStdioEntry = (
6868
value: string,
6969
artifactRoot: string,
7070
cwd: string,
7171
): string => {
72-
if (
73-
!isAbsolute(value) &&
74-
!value.startsWith('./') &&
75-
!value.startsWith('../') &&
76-
!value.includes('/') &&
77-
!value.includes('\\')
78-
) return value;
7972
const resolved = resolve(cwd, value);
8073
return isInsideOrEqual(artifactRoot, resolved) ? resolved : value;
8174
};
@@ -137,10 +130,12 @@ const launchIdentityOf = async (
137130
const cwd = resolved.cwd === undefined
138131
? artifactRoot
139132
: assertInside(artifactRoot, resolve(artifactRoot, resolved.cwd));
133+
const [entry, ...args] = resolved.args;
140134
return digest({
141-
args: resolved.args.map((argument) => normalizedStdioArgument(argument, artifactRoot, cwd)),
135+
args,
142136
command: resolved.command,
143137
cwd,
138+
entry: entry === undefined ? null : normalizedStdioEntry(entry, artifactRoot, cwd),
144139
env: resolved.env ?? {},
145140
kind: 'stdio',
146141
});

‎packages/agent-bundle/tests/web-host-launch-selection.test.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,22 @@ describe('selectWebLaunch', () => {
115115
expect(selection.sharedTargets).toEqual(['claude', 'portable']);
116116
});
117117

118+
it('preserves path-looking literal arguments after the executable entry', async () => {
119+
const root = await artifactRoot();
120+
await writeManifest(root, '.mcp.json', {
121+
status: claudeServer({
122+
args: ['${CLAUDE_PLUGIN_ROOT}/mcp/mcp-status.mjs', '--label', './team/red'],
123+
}),
124+
});
125+
await writeManifest(root, 'mcp.json', {
126+
status: emittedPortableServer({
127+
args: ['mcp/mcp-status.mjs', '--label', join(root, 'team', 'red')],
128+
}),
129+
});
130+
const error = await failure(root, { declaredTargets: ['claude', 'portable'] });
131+
expect(error.code).toBe('launch-ambiguous');
132+
});
133+
118134
it('keeps an emitted portable entry distinct when it resolves to another artifact path', async () => {
119135
const root = await artifactRoot();
120136
await writeManifest(root, '.mcp.json', { status: claudeServer() });

0 commit comments

Comments
 (0)