Skip to content

feat(console): friendly chat views for shell::* function calls#253

Merged
andersonleal merged 2 commits into
mainfrom
feat/console-shell-chat-views
Jun 12, 2026
Merged

feat(console): friendly chat views for shell::* function calls#253
andersonleal merged 2 commits into
mainfrom
feat/console-shell-chat-views

Conversation

@andersonleal

@andersonleal andersonleal commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

shell::* function calls in the console chat currently fall back to raw REQUEST/RESPONSE JSON panes. This adds a shell/ renderer module covering all 16 shell worker functions with the same terminal-style visualization quality as the existing sandbox/coder/web modules.

What's included

  • console/web/src/components/chat/shell/ — new module (parsers.ts, format.ts, shared.tsx, index.tsx, 16 view components, 2 test suites)
    • exec/job family: ExecView (+ approval preview), ExecBgView (↻ started <job_id> with copyable full job id, host-bg timeout_ms ignored hint), StatusView (full job record terminal), KillView (+ preview), ListView (jobs table), ConfigStatusView
    • fs family: ls / stat / mkdir / rm / chmod / mv / grep / sed / write (single + batch) / read (stream-only channel ref)
  • Wire-faithful schemas — verified against the shell worker Rust source: target optional-not-nullable (serde default), kill reason skip-serialized, transparent fs::stat, chmod updatedentries_changed alias, args null-vs-[] tokenization semantics preserved, explicit stdout_truncated/stderr_truncated flags
  • Error renderingparseShellErrorDisplay recovers S-codes from flat {code, message} bodies and from harness gate-denial envelopes whose reason embeds the code as text (trigger_failed: IIIInvocationError: S211: …) — the path real shell errors take today; falls back to the shared sandbox error normalizer for denials
  • Sandbox refactors (additive only) — export collectErrorCandidates, extract FsEntriesTable, GrepMatchList, SedResultsTable as reusable components; existing views become thin wrappers, suites stay green

Test plan

  • pnpm test — 38 files, 717/717 (86 new tests pinning schema decisions + error paths)
  • pnpm typecheck — clean
  • pnpm lint — feature files clean
  • Visual: run a conversation invoking shell::exec / shell::exec_bg and confirm terminal cards + S-code error cards (raw json tab still shows original payloads)

Out of scope (flagged for follow-up)

shell::fs::sed/mv/chmod/mkdir are currently auto-acceptable in lib/backend/auto-accept-policy.ts despite mutating the host fs — intentionally left untouched per discussion.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Full shell tool UI: exec (foreground/background), status, kill, list, config-status, and rich filesystem ops (ls/stat/read/mkdir/rm/mv/chmod/grep/sed/write) with improved terminal/chips/footers and previews.
  • Refactor
    • Extracted and reorganized view/table components for cleaner rendering and reuse.
  • Tests
    • Added unit tests for shell formatting and parser/validation behavior.
  • Documentation
    • Added Storybook fixtures and a Shell family story for visual testing.

Add a shell renderer module to the console chat, covering all 16
shell worker functions (exec, exec_bg, status, kill, list,
config-status and the 10 fs::* operations) with terminal-style
visualizations matching the existing sandbox/coder/web modules,
instead of the raw REQUEST/RESPONSE JSON fallback.

- zod schemas mirror the shell worker wire types exactly
  (serde-faithful optionality: target optional-not-nullable, kill
  reason skip-serialized, transparent fs::stat, chmod updated alias,
  stream-only fs::read)
- parseShellErrorDisplay recovers S-codes both from flat error
  bodies and from harness gate-denial envelopes whose reason embeds
  the code as text (the path real shell errors take today)
- approval previews for shell::exec, exec_bg and kill
- reuse sandbox primitives; extract FsEntriesTable, GrepMatchList,
  SedResultsTable and export collectErrorCandidates from the sandbox
  module (verbatim moves, additive only)
- 86 tests pinning wire-shape decisions and error paths

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview, Comment Jun 12, 2026 2:57pm

Request Review

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 15 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5b516bfa-b2ca-444c-a942-51fdca6dc833

📥 Commits

Reviewing files that changed from the base of the PR and between 8fbe7a1 and c283185.

📒 Files selected for processing (2)
  • console/web/src/components/chat/FunctionCallMessage.stories.tsx
  • console/web/src/stories/fixtures/shell-fixtures.ts

📝 Walkthrough

Walkthrough

Adds comprehensive shell-function support: Zod schemas, parsing/error normalization, formatting helpers, many shell UI views (exec, exec_bg, status, kill, list, config-status, and 10 fs ops), tests, sandbox view extractions, and wiring into FunctionCallMessage for previews and terminals.

Changes

Shell Tool Support

Layer / File(s) Summary
Shell Foundation — Schemas, Formatting, and Shared Utilities
console/web/src/components/chat/shell/parsers.ts, console/web/src/components/chat/shell/format.ts, console/web/src/components/chat/shell/shared.tsx, console/web/src/components/chat/shell/__tests__/parsers.test.ts, console/web/src/components/chat/shell/__tests__/format.test.ts
Defines comprehensive Zod schemas for shell request/response types, shell error parsing/normalization, deterministic formatting utilities for commands/durations/epochs, and shared UI helpers (target chip, displayPath). Includes parser and formatter test suites.
Sandbox Component Extraction
console/web/src/components/chat/sandbox/FsGrepView.tsx, console/web/src/components/chat/sandbox/FsLsView.tsx, console/web/src/components/chat/sandbox/FsSedView.tsx, console/web/src/components/chat/sandbox/parsers.ts
Extracts reusable GrepMatchList, FsEntriesTable, and SedResultsTable components and exports collectErrorCandidates for broader error-parsing use.
Shell Execution Views
console/web/src/components/chat/shell/ExecView.tsx, console/web/src/components/chat/shell/ExecBgView.tsx
Adds ShellExecView, ShellExecBgView, compact preview rows, ShellExecChips, and ShellExecFooter to render exec+exec_bg requests, started-job info, approval previews, and ANSI output.
Shell Job Management Views
console/web/src/components/chat/shell/StatusView.tsx, console/web/src/components/chat/shell/KillView.tsx, console/web/src/components/chat/shell/ListView.tsx, console/web/src/components/chat/shell/ConfigStatusView.tsx
Adds job lifecycle UIs: detailed status terminal, kill preview/view, background job list with duration/exit/truncation columns, and config-status rendering with optional last_error display.
Shell Filesystem Operation Views
console/web/src/components/chat/shell/FsLsView.tsx, console/web/src/components/chat/shell/FsStatView.tsx, console/web/src/components/chat/shell/FsReadView.tsx, console/web/src/components/chat/shell/FsMkdirView.tsx, console/web/src/components/chat/shell/FsRmView.tsx, console/web/src/components/chat/shell/FsMvView.tsx, console/web/src/components/chat/shell/FsChmodView.tsx, console/web/src/components/chat/shell/FsGrepView.tsx, console/web/src/components/chat/shell/FsSedView.tsx, console/web/src/components/chat/shell/FsWriteView.tsx
Implements 10 filesystem operation views: directory listing (reusing FsEntriesTable), stat, streamed read, mkdir, rm, mv, chmod, grep (reusing GrepMatchList), sed (reusing SedResultsTable), and single/batched writes with provenance.
Shell Tool Dispatcher and Message Integration
console/web/src/components/chat/shell/index.tsx, console/web/src/components/chat/FunctionCallMessage.tsx
Exports ShellToolView with tryRender/tryRenderPreview, ShellFunctionIdLabel, and wires shell rendering into FunctionCallMessage so shell previews and terminal renderers are included in the dispatch chain.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly Related PRs

  • iii-hq/workers#201: Prior refactor of FunctionCallMessage’s family-based renderer dispatch; this PR extends that flow to add the Shell family.

Suggested Reviewers

  • sergiofilhowz

Poem

🐰 A warren of commands, now rendered with care,
Shells and their jobs prance through the air,
From exec to grep, each filesystem call,
The rabbit inspects schemas and highlights them all! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.02% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically describes the main change: adding friendly chat views for shell::* function calls in the console, which is the primary objective of the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/console-shell-chat-views

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@console/web/src/components/chat/shell/FsRmView.tsx`:
- Around line 43-52: The TargetChip is being conditionally hidden when both
recursive and sandboxed are false; update the FsRmView render so TargetChip
(rendering req.data.target) is always displayed and only the "recursive" Chip
remains conditional. Locate the JSX in FsRmView that currently checks
"{recursive || sandboxed ? (...) : null}" and change it to always render
<TargetChip target={req.data.target} /> while keeping the <Chip
label="recursive" ...> block wrapped by the conditional on "recursive".

In `@console/web/src/components/chat/shell/KillView.tsx`:
- Around line 35-37: The headline should use the parsed response instead of only
`running`: change the span's text logic in KillView to: if `running` show
"killing job…", else if `resp` exists show `resp.killed ? 'killed job' : 'did
not kill job'` (or another concise failure message derived from `resp.reason`),
otherwise fall back to the previous default; update the conditional that
currently reads `{running ? 'killing job…' : 'killed job'}` to inspect
`resp?.killed` and leave the job id display using `truncateMiddle(resp?.job_id
?? req.data.job_id, 24)` unchanged.

In `@console/web/src/components/chat/shell/ListView.tsx`:
- Around line 13-17: The helper function formatDurationMs is duplicated in
ListView and StatusView; extract it into a new shared module (e.g., format.ts)
that imports/uses formatAgeSecs, export formatDurationMs from that module, then
replace the local definitions by importing formatDurationMs into ListView and
StatusView (remove the duplicate functions). Make sure the new format.ts
preserves the current behavior (ms < 10_000 → `${ms}ms`, otherwise call
formatAgeSecs(Math.floor(ms/1000))) and update any imports so ListView and
StatusView reference the shared function.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e644c572-a069-499f-bf27-ec317136229b

📥 Commits

Reviewing files that changed from the base of the PR and between 4fb36bd and 8fbe7a1.

📒 Files selected for processing (27)
  • console/web/src/components/chat/FunctionCallMessage.tsx
  • console/web/src/components/chat/sandbox/FsGrepView.tsx
  • console/web/src/components/chat/sandbox/FsLsView.tsx
  • console/web/src/components/chat/sandbox/FsSedView.tsx
  • console/web/src/components/chat/sandbox/parsers.ts
  • console/web/src/components/chat/shell/ConfigStatusView.tsx
  • console/web/src/components/chat/shell/ExecBgView.tsx
  • console/web/src/components/chat/shell/ExecView.tsx
  • console/web/src/components/chat/shell/FsChmodView.tsx
  • console/web/src/components/chat/shell/FsGrepView.tsx
  • console/web/src/components/chat/shell/FsLsView.tsx
  • console/web/src/components/chat/shell/FsMkdirView.tsx
  • console/web/src/components/chat/shell/FsMvView.tsx
  • console/web/src/components/chat/shell/FsReadView.tsx
  • console/web/src/components/chat/shell/FsRmView.tsx
  • console/web/src/components/chat/shell/FsSedView.tsx
  • console/web/src/components/chat/shell/FsStatView.tsx
  • console/web/src/components/chat/shell/FsWriteView.tsx
  • console/web/src/components/chat/shell/KillView.tsx
  • console/web/src/components/chat/shell/ListView.tsx
  • console/web/src/components/chat/shell/StatusView.tsx
  • console/web/src/components/chat/shell/__tests__/format.test.ts
  • console/web/src/components/chat/shell/__tests__/parsers.test.ts
  • console/web/src/components/chat/shell/format.ts
  • console/web/src/components/chat/shell/index.tsx
  • console/web/src/components/chat/shell/parsers.ts
  • console/web/src/components/chat/shell/shared.tsx

Comment on lines +43 to +52
{recursive || sandboxed ? (
<div className="flex flex-wrap items-center gap-1.5">
{recursive ? (
<Chip label="recursive" className="border-warn text-warn">
true
</Chip>
) : null}
<TargetChip target={req.data.target} />
</div>
) : null}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Always show the target for rm results.

This is the only shell fs view here that hides TargetChip for the default host/non-recursive case. That makes host vs sandbox removals harder to disambiguate, especially since the surrounding copy already branches on target-specific semantics (was_present is host-only). Render the target unconditionally and keep only the recursive chip conditional.

Proposed fix
-        {recursive || sandboxed ? (
-          <div className="flex flex-wrap items-center gap-1.5">
-            {recursive ? (
-              <Chip label="recursive" className="border-warn text-warn">
-                true
-              </Chip>
-            ) : null}
-            <TargetChip target={req.data.target} />
-          </div>
-        ) : null}
+        <div className="flex flex-wrap items-center gap-1.5">
+          {recursive ? (
+            <Chip label="recursive" className="border-warn text-warn">
+              true
+            </Chip>
+          ) : null}
+          <TargetChip target={req.data.target} />
+        </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{recursive || sandboxed ? (
<div className="flex flex-wrap items-center gap-1.5">
{recursive ? (
<Chip label="recursive" className="border-warn text-warn">
true
</Chip>
) : null}
<TargetChip target={req.data.target} />
</div>
) : null}
<div className="flex flex-wrap items-center gap-1.5">
{recursive ? (
<Chip label="recursive" className="border-warn text-warn">
true
</Chip>
) : null}
<TargetChip target={req.data.target} />
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@console/web/src/components/chat/shell/FsRmView.tsx` around lines 43 - 52, The
TargetChip is being conditionally hidden when both recursive and sandboxed are
false; update the FsRmView render so TargetChip (rendering req.data.target) is
always displayed and only the "recursive" Chip remains conditional. Locate the
JSX in FsRmView that currently checks "{recursive || sandboxed ? (...) : null}"
and change it to always render <TargetChip target={req.data.target} /> while
keeping the <Chip label="recursive" ...> block wrapped by the conditional on
"recursive".

Comment on lines +35 to +37
<span>{running ? 'killing job…' : 'killed job'}</span>
<code className="bg-paper-2 border border-rule-2 px-1.5 py-0.5 text-[12px] text-ink">
{truncateMiddle(resp?.job_id ?? req.data.job_id, 24)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the response outcome in the headline, not just running.

Once a parsed response exists, this line still renders killed job for the documented killed: false / reason: "not running" case, so the primary message contradicts the footer. Drive the verb from resp.killed when resp is present and reserve running only for the in-flight state.

Proposed fix
-          <span>{running ? 'killing job…' : 'killed job'}</span>
+          <span>
+            {running
+              ? 'killing job…'
+              : resp
+                ? resp.killed
+                  ? 'killed job'
+                  : 'job not running'
+                : 'kill requested'}
+          </span>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<span>{running ? 'killing job…' : 'killed job'}</span>
<code className="bg-paper-2 border border-rule-2 px-1.5 py-0.5 text-[12px] text-ink">
{truncateMiddle(resp?.job_id ?? req.data.job_id, 24)}
<span>
{running
? 'killing job…'
: resp
? resp.killed
? 'killed job'
: 'job not running'
: 'kill requested'}
</span>
<code className="bg-paper-2 border border-rule-2 px-1.5 py-0.5 text-[12px] text-ink">
{truncateMiddle(resp?.job_id ?? req.data.job_id, 24)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@console/web/src/components/chat/shell/KillView.tsx` around lines 35 - 37, The
headline should use the parsed response instead of only `running`: change the
span's text logic in KillView to: if `running` show "killing job…", else if
`resp` exists show `resp.killed ? 'killed job' : 'did not kill job'` (or another
concise failure message derived from `resp.reason`), otherwise fall back to the
previous default; update the conditional that currently reads `{running ?
'killing job…' : 'killed job'}` to inspect `resp?.killed` and leave the job id
display using `truncateMiddle(resp?.job_id ?? req.data.job_id, 24)` unchanged.

Comment on lines +13 to +17
/** `1234` → `"1234ms"`; ≥ 10 s humanize via `formatAgeSecs` so
long-lived jobs read as `2m`/`3h` instead of raw millis. */
function formatDurationMs(ms: number): string {
return ms < 10_000 ? `${ms}ms` : formatAgeSecs(Math.floor(ms / 1000))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Duplicate helper: extract formatDurationMs to shared format module.

formatDurationMs is defined identically in both StatusView.tsx (lines 33-35) and here. Extract it to format.ts to eliminate duplication.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@console/web/src/components/chat/shell/ListView.tsx` around lines 13 - 17, The
helper function formatDurationMs is duplicated in ListView and StatusView;
extract it into a new shared module (e.g., format.ts) that imports/uses
formatAgeSecs, export formatDurationMs from that module, then replace the local
definitions by importing formatDurationMs into ListView and StatusView (remove
the duplicate functions). Make sure the new format.ts preserves the current
behavior (ms < 10_000 → `${ms}ms`, otherwise call
formatAgeSecs(Math.floor(ms/1000))) and update any imports so ListView and
StatusView reference the shared function.

Add shell-fixtures.ts with 28 wire-accurate fixtures covering all 16
shell worker functions and a ShellFamily gallery story alongside the
existing sandbox/coder/web families.

- mirrors the serde wire shapes pinned by the shell parsers: transparent
  fs::stat, channel-ref-only fs::read, batch + streamed fs::write,
  kill reason skip-serialized, sandbox target variant
- state variants: running, approval previews for exec/exec_bg/kill
- both error paths: flat S215 ErrorBody and the harness gate-denial
  envelope with S211 embedded in the reason text

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sergiofilhowz

Copy link
Copy Markdown
Contributor
image

is this expected?

@andersonleal andersonleal merged commit 39d48a0 into main Jun 12, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants