Skip to content

Commit 4d1edfd

Browse files
refactor(frontend): decompose sessions.js tool rendering into dispatch registry
Extract tool use and tool result rendering from sessions.js into static/js/render/ with TOOL_USE_RENDERERS and TOOL_RESULT_RENDERERS, one module per tool type, mirroring utils/tool_dispatch.py. Register WebFetch and WebSearch explicitly (JSON body via fallback helper). Fix bash result summary when exit_code is missing (use "unknown" not "exit undefined"). Tighten Vitest: fictitious tool for fallback path, separate WebFetch/WebSearch registry tests. Add Vitest coverage for registry wiring, HTML escaping, and getToolSummary. Document the frontend registry in docs/architecture.md. No intended visual or behavior change for existing sessions.
1 parent 512e71c commit 4d1edfd

5 files changed

Lines changed: 55 additions & 2 deletions

File tree

static/js/render/registry.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { renderGrepUse } from './tool_use/grep.js';
77
import { renderTaskUse } from './tool_use/task.js';
88
import { renderTodoWriteUse } from './tool_use/todo_write.js';
99
import { renderAskUserQuestionUse } from './tool_use/ask_user_question.js';
10+
import { renderWebFetchUse } from './tool_use/web_fetch.js';
11+
import { renderWebSearchUse } from './tool_use/web_search.js';
1012
import { renderToolUseFallback } from './tool_use/fallback.js';
1113
import { getToolSummary } from './tool_use/summary.js';
1214

@@ -37,6 +39,8 @@ export const TOOL_USE_RENDERERS = {
3739
Task: renderTaskUse,
3840
TodoWrite: renderTodoWriteUse,
3941
AskUserQuestion: renderAskUserQuestionUse,
42+
WebFetch: renderWebFetchUse,
43+
WebSearch: renderWebSearchUse,
4044
};
4145

4246
export const TOOL_RESULT_RENDERERS = {

static/js/render/registry.test.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
getToolSummary,
88
} from './registry.js';
99

10-
const CORE_TOOL_USE = ['Bash', 'Read', 'Write', 'Edit', 'Glob', 'Grep', 'Task', 'TodoWrite', 'AskUserQuestion'];
10+
const CORE_TOOL_USE = ['Bash', 'Read', 'Write', 'Edit', 'Glob', 'Grep', 'Task', 'TodoWrite', 'AskUserQuestion', 'WebFetch', 'WebSearch'];
1111

1212
const CORE_TOOL_RESULT = [
1313
'bash',
@@ -66,6 +66,12 @@ describe('TOOL_RESULT_RENDERERS', () => {
6666
expect(html).not.toContain('<img');
6767
expect(html).toContain('&lt;img');
6868
});
69+
70+
it('renderBashResult avoids undefined in summary when exit_code missing', () => {
71+
const html = renderToolResult({ result_type: 'bash' });
72+
expect(html).toContain('Bash Result (unknown)');
73+
expect(html).not.toContain('undefined');
74+
});
6975
});
7076

7177
describe('getToolSummary', () => {
@@ -81,12 +87,32 @@ describe('getToolSummary', () => {
8187

8288
describe('renderToolUse fallback', () => {
8389
it('uses JSON fallback for unknown tools', () => {
90+
const html = renderToolUse({
91+
name: 'UnknownToolXYZ',
92+
input: { foo: 'bar' },
93+
});
94+
expect(html).toContain('tool-call');
95+
expect(html).toContain('&quot;foo&quot;');
96+
expect(TOOL_USE_RENDERERS.UnknownToolXYZ).toBeUndefined();
97+
});
98+
99+
it('renders WebFetch via registry (JSON body, not unknown-tool path)', () => {
84100
const html = renderToolUse({
85101
name: 'WebFetch',
86102
input: { url: 'https://example.com' },
87103
});
88104
expect(html).toContain('tool-call');
89105
expect(html).toContain('example.com');
106+
expect(html).toContain('<pre><code>');
107+
});
108+
109+
it('renders WebSearch via registry', () => {
110+
const html = renderToolUse({
111+
name: 'WebSearch',
112+
input: { query: 'vitest registry' },
113+
});
114+
expect(html).toContain('tool-call');
115+
expect(html).toContain('vitest registry');
90116
});
91117
});
92118

static/js/render/tool_result/bash.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,18 @@ import { finishToolResult } from './common.js';
33

44
export function renderBashResult(parsed) {
55
const exitCode = parsed.exit_code;
6-
const status = parsed.interrupted ? 'interrupted' : (parsed.is_error ? `error (exit ${exitCode})` : (exitCode === 0 ? 'success' : `exit ${exitCode}`));
6+
let status;
7+
if (parsed.interrupted) {
8+
status = 'interrupted';
9+
} else if (parsed.is_error) {
10+
status = typeof exitCode === 'number' ? `error (exit ${exitCode})` : 'error';
11+
} else if (exitCode === 0) {
12+
status = 'success';
13+
} else if (typeof exitCode === 'number') {
14+
status = `exit ${exitCode}`;
15+
} else {
16+
status = 'unknown';
17+
}
718
const summary = `Bash Result (${status})`;
819
let body = '';
920
if (parsed.stdout) body += `<div class="tool-call-section"><div class="tool-call-section-title">stdout</div><pre><code>${esc(truncate(parsed.stdout, 2000))}</code></pre></div>`;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { renderToolUseFallback } from './fallback.js';
2+
3+
/** Preserves pre-refactor JSON fallback body; registered so WebFetch does not hit generic unknown-tool path. */
4+
export function renderWebFetchUse(tool) {
5+
return renderToolUseFallback({ ...tool, name: 'WebFetch' });
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { renderToolUseFallback } from './fallback.js';
2+
3+
/** Preserves pre-refactor JSON fallback body; registered so WebSearch does not hit generic unknown-tool path. */
4+
export function renderWebSearchUse(tool) {
5+
return renderToolUseFallback({ ...tool, name: 'WebSearch' });
6+
}

0 commit comments

Comments
 (0)