test(subagent): exercise native Windows Claude shim

This commit is contained in:
pku-xht
2026-08-10 13:34:10 +08:00
parent aee58c73e0
commit 9f6ec0ead6
5 changed files with 16 additions and 39 deletions
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.md
2026-08-04-claude-code-and-codex-subagent-backends.md: 783a8a6537d2b05ab99f1962bda88787e2e4e938
2026-08-04-claude-code-and-codex-subagent-backends.zh.md: f331dc698001e24df62733d219f28b59d2358fac
2026-08-04-claude-code-and-codex-subagent-backends.md: eb4c4ab0116cdf1e8b9e6dd53655035a78afdfa0
2026-08-04-claude-code-and-codex-subagent-backends.zh.md: c14b7977a267c2b7325e4a4382593c8f30dddea4
@@ -79,10 +79,6 @@ The project owner's distribution authorization is scoped to the official `@anthr
**A model-visible product selector.** Product availability and authentication are deployment facts. Two fixed tools keep each schema and provider binding explicit and avoid adding dynamic selection state to the common service.
**Global product enable settings and a product-specific Web page.** Those controls make Codex and Claude Code exceptions to the Agent Preset composition that already owns one agent's tool set, and one process-wide choice cannot represent two sessions using different presets. The host always supplies the providers; the preset alone decides which fixed tools its agent receives.
**One shipped preset per product combination.** Four preset variants encode a two-boolean choice in preset identities and multiply every future standard-preset change. Independent ordinary rows express the same result in the user's copied preset without adding a roster taxonomy.
**Product doubles as required evidence.** Doubles cover exhaustive private protocol branches but do not prove package exports, official distributions, authentication, or real process behavior. Required evidence drives each official product against a loopback model fixture.
**Plugin-managed login, product home, models, settings, or permissions.** Those choices would create another authority beside each product's native configuration and enlarge a one-shot provider into account management. The providers expose only an explicit environment overlay and teardown grace; unattended interaction fails closed.
@@ -79,10 +79,6 @@ Claude Code 证据锁定 Agent SDK 0.3.220 及与其查询协议兼容的原生
**面向模型的产品选择器。** 产品可用性和身份验证属于部署事实。两个固定工具使各自的 schema 与提供方绑定保持明确,也避免在通用服务中添加动态选择状态。
**全局产品启用设置与产品专属 Web 页面。** 这类控制会让 Codex 与 Claude Code 成为 Agent Preset 组装的例外,而后者本就拥有单个 agent 的工具集;一个进程级选择也无法表达两个会话使用不同 preset。宿主始终提供 provider,只有 preset 决定其 agent 获得哪些固定工具。
**为每种产品组合交付一份 preset。** 四个 preset 变体把两个布尔选择编码成 preset 身份,并让未来每次标准 preset 修改都要同步多份副本。用户复制的 preset 中两条独立普通行已经能表达同一结果,无需新增名单分类。
**以产品替身作为强制证据。** 替身可以穷尽覆盖私有协议分支,但无法证明包导出、官方发行版、身份验证或真实进程行为。强制证据会驱动每个官方产品连接回环模型 fixture(测试前置数据)。
**由插件管理登录、产品主目录、模型、设置或权限。** 这些选择会在每个产品的原生配置之外建立另一套权威来源,并将一次性提供方扩张为账户管理功能。提供方只公开显式环境覆盖项和清理宽限期;无人值守交互会以默认拒绝方式失败。
-18
View File
@@ -132,24 +132,6 @@ describe('the shipped Web composition', () => {
expect(ctx.agentPresets.defaultId).toBe('standard')
})
it('keeps product providers on the host while shipped presets leave their tools disabled', async () => {
expect(ctx.subagents.list()).toEqual(expect.arrayContaining([
'spawn', 'fork', 'codex', 'claude-code',
]))
const handle = await ctx.agents.create({
sessionId: SessionId('preset-products-disabled'),
setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'standard').then(() => undefined),
})
try {
expect(toolNames(ctx, handle.agent)).not.toEqual(expect.arrayContaining([
'subagent_codex', 'subagent_claude_code',
]))
} finally {
await handle.dispose()
}
})
it('composes the full agent from `standard`', async () => {
const handle = await ctx.agents.create({
sessionId: SessionId('preset-standard'),
@@ -1,6 +1,5 @@
import { execFile } from 'node:child_process'
import {
copyFileSync,
mkdirSync,
mkdtempSync,
readFileSync,
@@ -116,14 +115,17 @@ async function realHarness(behavior: MessagesBehavior): Promise<{
const workspace = join(root, 'workspace')
const claudeConfig = join(root, 'claude-config')
const xdgConfig = join(root, 'xdg')
const nativeBin = join(root, 'native-bin')
const nativeBin = join(root, 'native bin')
mkdirSync(workspace)
mkdirSync(claudeConfig)
mkdirSync(xdgConfig)
mkdirSync(nativeBin)
const executable = join(nativeBin, process.platform === 'win32' ? 'claude.exe' : 'claude')
if (process.platform === 'win32') copyFileSync(claudeBin, executable)
else symlinkSync(claudeBin, executable)
const executable = join(nativeBin, process.platform === 'win32' ? 'claude.cmd' : 'claude')
if (process.platform === 'win32') {
writeFileSync(executable, `@echo off\r\n"${claudeBin}" %*\r\n`)
} else {
symlinkSync(claudeBin, executable)
}
writeFileSync(
join(claudeConfig, 'settings.json'),
`${JSON.stringify({ model: settingsModel }, null, 2)}\n`,
@@ -207,7 +209,7 @@ describe('real Claude Agent SDK 0.3.220 and Claude Code 2.1.220', {
expect(sdkPackage.version).toBe('0.3.220')
expect(sdkPackage.claudeCodeVersion).toBe('2.1.220')
expect(sdkPackage.optionalDependencies[platformPackage]).toBe('0.3.220')
const version = await execFileAsync(harness.executable, ['--version'], {
const version = await execFileAsync(process.platform === 'win32' ? claudeBin : harness.executable, ['--version'], {
env: { ...process.env, ...harness.env },
})
expect(version.stdout.trim()).toBe('2.1.220 (Claude Code)')
@@ -224,11 +226,12 @@ describe('real Claude Agent SDK 0.3.220 and Claude Code 2.1.220', {
message.type === 'system' && message.subtype === 'init',
)
expect(initMessage?.claude_code_version).toBe('2.1.220')
const spawnedExecutable = harness.spawnSpecs[0]?.argv[0]
expect(spawnedExecutable).toBeDefined()
if (spawnedExecutable !== undefined) {
expect(process.platform === 'win32' ? spawnedExecutable.toLowerCase() : spawnedExecutable)
.toBe(process.platform === 'win32' ? harness.executable.toLowerCase() : harness.executable)
if (process.platform === 'win32') {
expect(harness.spawnSpecs[0]?.argv.slice(0, 5)).toEqual([
'cmd.exe', '/d', '/s', '/c', harness.executable,
])
} else {
expect(harness.spawnSpecs[0]?.argv[0]).toBe(harness.executable)
}
expect(fixture.requests).toHaveLength(1)