mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 23:49:41 +02:00
fix(cli-registry): merge-time fixes for the run-menu consolidation (#458)
- test/opencode-resize.test.ts: retarget the launcher guard at the real code (this.selectSession(firstSessionId), any this.activeSessionId assignment) with an anti-vacuity check; the old strings existed nowhere, so it could never fail - session-ui.js: restore as comments the two invariants the merged bodies lost (deepseek leaves statusReporting unset, i.e. ON; no effort field for external CLIs, it is Claude-specific) - docs/cli-registry.md: move the frontend-guard paragraph below the two backend-guard paragraphs so they keep their antecedent, and note the widened comparison shape - test/frontend-cli-no-id-branching.test.ts: the comparison shape accepts any left-hand identifier (const m = this._runMode; m === 'codex' was invisible), normalized to `mode`; the two `m !== 'shell'` display filters are allowlisted and the remaining blind spots documented - test/run-mode-dispatch.test.ts: table-driven pin of run() dispatch (claude to runClaude, each RUN_MODE_LAUNCH id to _runCliMode(id), shell to runShell, unknown to runClaude, lock held and released) - CLAUDE.md: name the second CI-gated guard next to the backend one - server.ts: every </head> injection passes a replacer function; a clis.json label containing $' re-injected the rest of the document past escapeScriptJson (two render tests pin it, proven failing on the string form) - _isAltCliMode(): no reference anywhere in the tree, nothing to fix Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> (cherry picked from commit 1ea363ff808a62861559bc141e724b163cc1c56e)
This commit is contained in:
@@ -23,6 +23,8 @@ import { isDeepSeekAvailable, isDeepSeekRunnable } from '../src/utils/deepseek-c
|
||||
import { isOmpAvailable } from '../src/utils/omp-cli-resolver.js';
|
||||
import { isCloudflaredAvailable } from '../src/utils/cloudflared-resolver.js';
|
||||
import { isGitAvailable } from '../src/git-clone.js';
|
||||
import { enabledClis } from '../src/config/cli-registry/registry.js';
|
||||
import { STOCK_CLIS } from '../src/config/cli-registry/stock.js';
|
||||
|
||||
// renderIndexHtml probes the real PATH for every CLI, which would make the
|
||||
// assertions below depend on whatever happens to be installed on the machine
|
||||
@@ -79,6 +81,14 @@ vi.mock('../src/utils/cloudflared-resolver.js', () => ({
|
||||
vi.mock('../src/git-clone.js', () => ({
|
||||
isGitAvailable: vi.fn(() => false),
|
||||
}));
|
||||
// The custom-model list carries `label`, a string a user's own clis.json can set.
|
||||
// Wrap enabledClis so ONE test below can hand renderIndexHtml a label with `$'`
|
||||
// in it while every other test still reads the real stock registry through the
|
||||
// real implementation.
|
||||
vi.mock('../src/config/cli-registry/registry.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('../src/config/cli-registry/registry.js')>();
|
||||
return { ...actual, enabledClis: vi.fn(actual.enabledClis) };
|
||||
});
|
||||
|
||||
const TEMPLATE = [
|
||||
'<head>',
|
||||
@@ -222,6 +232,35 @@ describe('WebServer.renderIndexHtml', () => {
|
||||
expect(eval(escaped)[0].label).toBe('</script><script>alert(1)</script>');
|
||||
});
|
||||
|
||||
it("inserts a label containing $' verbatim instead of splicing the document into the script", async () => {
|
||||
// `String.replace` with a STRING replacement interprets `$'` as "the text
|
||||
// after the match", so a clis.json label carrying it used to re-inject the
|
||||
// rest of the document (the whole <body>) into the inline script, past
|
||||
// escapeScriptJson, which only neutralizes `<`. Every `</head>` injection
|
||||
// passes a replacer FUNCTION instead, whose return value is inserted
|
||||
// verbatim. The other `$` forms ride along so a partial escape cannot pass.
|
||||
const claude = STOCK_CLIS.find((e) => e.id === 'claude')!;
|
||||
const label = "Claude $' $& $` $1 $$";
|
||||
const real = vi.mocked(enabledClis).getMockImplementation()!;
|
||||
vi.mocked(enabledClis).mockImplementation(() => [{ ...claude, label }]);
|
||||
try {
|
||||
const { server } = makeServer({});
|
||||
const html = await render(server);
|
||||
expect(html.match(/<body>/g)).toHaveLength(1);
|
||||
const clis = JSON.parse(html.match(/window\.__codemanCustomModelClis=(\[.*?\]);/)![1]);
|
||||
expect(clis).toEqual([{ id: 'claude', label }]);
|
||||
} finally {
|
||||
vi.mocked(enabledClis).mockImplementation(real);
|
||||
}
|
||||
});
|
||||
|
||||
it("inserts a solo id containing $' verbatim, under the same replacer rule", async () => {
|
||||
const { server } = makeServer({});
|
||||
const html = await render(server, "sess$'x");
|
||||
expect(html.match(/<body>/g)).toHaveLength(1);
|
||||
expect(html).toContain(`window.__CODEMAN_SOLO__="sess$'x"`);
|
||||
});
|
||||
|
||||
it('still emits the object when nothing at all is installed', async () => {
|
||||
// The all-false case is the one that matters most and the easiest to get
|
||||
// wrong by only injecting when something resolves.
|
||||
|
||||
Reference in New Issue
Block a user