Skip to content

(MOT-4472) feat(console): unify shared UI patterns across workers - #837

Merged
sergiofilhowz merged 5 commits into
mainfrom
feat/console-consistency
Aug 19, 2026
Merged

(MOT-4472) feat(console): unify shared UI patterns across workers#837
sergiofilhowz merged 5 commits into
mainfrom
feat/console-consistency

Conversation

@sergiofilhowz

Copy link
Copy Markdown
Contributor

Summary

  • Add shared primitives for selectors, tooltips, tabs, tables, lists, chips, surfaces, and icon buttons.
  • Standardize typography, icon sizing, selection states, and transitions across the console and injected worker UIs.
  • Document UI conformance rules and harden the Shell terminal lifecycle.

Validation

  • pnpm --dir console/web typecheck
  • pnpm --dir console/web test
  • pnpm --dir shell/ui build
  • pnpm --dir shell/ui test

Fixes MOT-4472
Refs MOT-4477
Refs MOT-4481

@vercel

vercel Bot commented Aug 19, 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 Aug 19, 2026 5:52pm
workers-tech-spec Ready Ready Preview Aug 19, 2026 5:52pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 61 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 290 files, which is 190 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: db8635d3-95ba-4504-8f65-4580bb02c476

📥 Commits

Reviewing files that changed from the base of the PR and between a3d8479 and 844c55c.

⛔ Files ignored due to path filters (2)
  • eval/Cargo.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (290)
  • browser/ui/src/configuration/index.tsx
  • browser/ui/src/function-trigger-message/BrowserViews.tsx
  • browser/ui/src/function-trigger-message/index.tsx
  • browser/ui/src/lib/errors.tsx
  • browser/ui/src/lib/icons.tsx
  • browser/ui/src/page/SessionRail.tsx
  • browser/ui/src/page/SessionView.tsx
  • browser/ui/src/page/index.tsx
  • browser/ui/styles.css
  • canvas/ui/src/page/ExportMenu.tsx
  • canvas/ui/src/page/MermaidPane.tsx
  • canvas/ui/src/page/icons.tsx
  • canvas/ui/src/page/index.tsx
  • canvas/ui/styles.css
  • computer/ui/src/page/index.tsx
  • computer/ui/styles.css
  • console/DESIGN.md
  • console/PRODUCT.md
  • console/SKILL.md
  • console/skills/design-console-ui/SKILL.md
  • console/ui/src/catalog/ActivityFeed.tsx
  • console/ui/src/catalog/FunctionsPage.tsx
  • console/ui/src/catalog/HttpTester.tsx
  • console/ui/src/catalog/InvokePanel.tsx
  • console/ui/src/catalog/SchemaTable.tsx
  • console/ui/src/catalog/TriggersPage.tsx
  • console/ui/src/catalog/live.tsx
  • console/ui/src/catalog/widgets.tsx
  • console/ui/src/injectable-ui-form/index.tsx
  • console/ui/styles.css
  • console/web/DESIGN.md
  • console/web/README.md
  • console/web/e2e/multi-turn-traces.spec.ts
  • console/web/package.json
  • console/web/public/vendor/console-ui.js
  • console/web/scripts/generate-vendor-shims.mjs
  • console/web/src/App.tsx
  • console/web/src/components/CommandPalette.tsx
  • console/web/src/components/chat/AttachmentChip.tsx
  • console/web/src/components/chat/ChatPanel.tsx
  • console/web/src/components/chat/ChatSettingsSheet.tsx
  • console/web/src/components/chat/ChatView.tsx
  • console/web/src/components/chat/ContextUsage.tsx
  • console/web/src/components/chat/DirectoryPicker.tsx
  • console/web/src/components/chat/EmptyState.tsx
  • console/web/src/components/chat/LexicalShell.tsx
  • console/web/src/components/chat/MemoryChip.tsx
  • console/web/src/components/chat/Message.tsx
  • console/web/src/components/chat/MessageList.test.tsx
  • console/web/src/components/chat/MessageList.tsx
  • console/web/src/components/chat/ModePicker.tsx
  • console/web/src/components/chat/ModelPicker.tsx
  • console/web/src/components/chat/ProviderSettingsForm.tsx
  • console/web/src/components/chat/SessionAddonsPicker.tsx
  • console/web/src/components/chat/SessionTriggers.tsx
  • console/web/src/components/chat/SystemPromptPicker.test.tsx
  • console/web/src/components/chat/SystemPromptPicker.tsx
  • console/web/src/components/chat/ThoughtMessage.test.tsx
  • console/web/src/components/chat/ThoughtMessage.tsx
  • console/web/src/components/chat/WorktreeBadge.tsx
  • console/web/src/components/chat/coder/CreateFileView.tsx
  • console/web/src/components/chat/coder/DeleteFileView.tsx
  • console/web/src/components/chat/coder/InfoView.tsx
  • console/web/src/components/chat/coder/ListFolderView.tsx
  • console/web/src/components/chat/coder/MoveView.tsx
  • console/web/src/components/chat/coder/OpenInEditorButton.tsx
  • console/web/src/components/chat/coder/ReadFileView.tsx
  • console/web/src/components/chat/coder/SearchView.tsx
  • console/web/src/components/chat/coder/TreeView.tsx
  • console/web/src/components/chat/coder/UpdateFileView.tsx
  • console/web/src/components/chat/coder/entryShared.tsx
  • console/web/src/components/chat/composer-resize.test.ts
  • console/web/src/components/chat/composer-resize.ts
  • console/web/src/components/chat/engine/RegisterTriggerView.tsx
  • console/web/src/components/chat/engine/__tests__/parsers.test.ts
  • console/web/src/components/chat/engine/shared.tsx
  • console/web/src/components/chat/harness/SpawnView.tsx
  • console/web/src/components/chat/lexical/FileMentionNode.tsx
  • console/web/src/components/chat/sandbox/terminal/CopyCommandButton.tsx
  • console/web/src/components/chat/scrapling/CrawlView.tsx
  • console/web/src/components/chat/scrapling/FetchView.tsx
  • console/web/src/components/chat/scrapling/MarkdownView.tsx
  • console/web/src/components/chat/scrapling/ParseViews.tsx
  • console/web/src/components/chat/scrapling/ScreenshotView.tsx
  • console/web/src/components/chat/scrapling/SearchViews.tsx
  • console/web/src/components/chat/scrapling/SessionViews.tsx
  • console/web/src/components/chat/tail-scroll.test.ts
  • console/web/src/components/chat/tail-scroll.ts
  • console/web/src/components/chat/web/FetchView.tsx
  • console/web/src/components/chat/worker/WorkerOpView.tsx
  • console/web/src/components/chat/worker/WorkerStatusView.tsx
  • console/web/src/components/chat/workflow/NodeResultView.tsx
  • console/web/src/components/chat/workflow/StatusView.tsx
  • console/web/src/components/chat/workflow/shared.tsx
  • console/web/src/components/function-trigger/FunctionTriggerCard.tsx
  • console/web/src/components/function-trigger/redact-raw.test.tsx
  • console/web/src/components/permissions/FilesystemAccessDialog.tsx
  • console/web/src/components/permissions/FilesystemAccessPrompt.tsx
  • console/web/src/components/permissions/FunctionAllowlistTree.tsx
  • console/web/src/components/sidebar/ConversationRow.tsx
  • console/web/src/components/sidebar/ConversationSidebar.tsx
  • console/web/src/components/ui/Badge.tsx
  • console/web/src/components/ui/BottomSheet.tsx
  • console/web/src/components/ui/Button.stories.tsx
  • console/web/src/components/ui/Button.tsx
  • console/web/src/components/ui/Cell.tsx
  • console/web/src/components/ui/Chip.tsx
  • console/web/src/components/ui/Dialog.tsx
  • console/web/src/components/ui/DropdownMenu.tsx
  • console/web/src/components/ui/EmptyState.tsx
  • console/web/src/components/ui/IconButton.tsx
  • console/web/src/components/ui/Input.tsx
  • console/web/src/components/ui/List.tsx
  • console/web/src/components/ui/ModeToggle.stories.tsx
  • console/web/src/components/ui/ModeToggle.test.tsx
  • console/web/src/components/ui/ModeToggle.tsx
  • console/web/src/components/ui/PageChrome.tsx
  • console/web/src/components/ui/PageSidebar.stories.tsx
  • console/web/src/components/ui/PageSidebar.test.tsx
  • console/web/src/components/ui/PageSidebar.tsx
  • console/web/src/components/ui/Pagination.tsx
  • console/web/src/components/ui/Select.tsx
  • console/web/src/components/ui/Selector.stories.tsx
  • console/web/src/components/ui/Selector.test.ts
  • console/web/src/components/ui/Selector.tsx
  • console/web/src/components/ui/SharedPrimitives.stories.tsx
  • console/web/src/components/ui/SheetNavigation.tsx
  • console/web/src/components/ui/StatusPanel.tsx
  • console/web/src/components/ui/Surface.tsx
  • console/web/src/components/ui/TabIcon.tsx
  • console/web/src/components/ui/Table.stories.tsx
  • console/web/src/components/ui/Table.test.tsx
  • console/web/src/components/ui/Table.tsx
  • console/web/src/components/ui/Tabs.stories.tsx
  • console/web/src/components/ui/Tabs.tsx
  • console/web/src/components/ui/TerminalCommandLine.tsx
  • console/web/src/components/ui/Tooltip.stories.tsx
  • console/web/src/components/ui/Tooltip.tsx
  • console/web/src/components/ui/Wordmark.tsx
  • console/web/src/components/workspace/EmptyPane.tsx
  • console/web/src/components/workspace/MobileWorkspaceMenu.tsx
  • console/web/src/components/workspace/TabStrip.tsx
  • console/web/src/components/workspace/pane-controls.tsx
  • console/web/src/demo/LandingDemo.tsx
  • console/web/src/hooks/use-conversations.test.ts
  • console/web/src/hooks/use-conversations.ts
  • console/web/src/hooks/use-sidebar-width.test.ts
  • console/web/src/hooks/use-sidebar-width.ts
  • console/web/src/index.css
  • console/web/src/lib/console-api.ts
  • console/web/src/lib/console-ui-conformance.test.ts
  • console/web/src/lib/icon-size-conformance.test.ts
  • console/web/src/lib/markdown-table.test.tsx
  • console/web/src/lib/markdown.tsx
  • console/web/src/lib/monaco.ts
  • console/web/src/lib/selection-conformance.test.ts
  • console/web/src/lib/table-conformance.test.ts
  • console/web/src/lib/typography-conformance.test.ts
  • console/web/src/lib/ui-loader.test.tsx
  • console/web/src/lib/ui-loader.tsx
  • console/web/src/lib/ui-scope.test.tsx
  • console/web/src/lib/ui-scope.tsx
  • console/web/src/pages/Configuration/index.tsx
  • console/web/src/pages/Configuration/tabs/WorkersTab/EmptyState.tsx
  • console/web/src/pages/Configuration/tabs/WorkersTab/SaveBar.tsx
  • console/web/src/pages/Configuration/tabs/WorkersTab/WorkersList.tsx
  • console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/RowChrome.tsx
  • console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/env-lexical/EnvPlaceholderNode.tsx
  • console/web/src/pages/Ext/index.test.tsx
  • console/web/src/pages/Ext/index.tsx
  • console/web/src/pages/TracesV2/components/AttributesFilter.tsx
  • console/web/src/pages/TracesV2/components/GroupByPicker.tsx
  • console/web/src/pages/TracesV2/components/GroupedTraceList.tsx
  • console/web/src/pages/TracesV2/components/SpanBaggageTab.tsx
  • console/web/src/pages/TracesV2/components/SpanErrorsTab.tsx
  • console/web/src/pages/TracesV2/components/SpanInfoTab.tsx
  • console/web/src/pages/TracesV2/components/SpanLinksTab.tsx
  • console/web/src/pages/TracesV2/components/SpanOtelLogsTab.tsx
  • console/web/src/pages/TracesV2/components/SpanPanel.tsx
  • console/web/src/pages/TracesV2/components/SpanTagsTab.tsx
  • console/web/src/pages/TracesV2/components/TraceDetailSkeleton.tsx
  • console/web/src/pages/TracesV2/components/TraceFilters.tsx
  • console/web/src/pages/TracesV2/components/TraceHeader.tsx
  • console/web/src/pages/TracesV2/components/TraceListRow.tsx
  • console/web/src/pages/TracesV2/components/ViewsDropdown.tsx
  • console/web/src/pages/TracesV2/components/WaterfallChart.tsx
  • console/web/src/pages/TracesV2/components/WorkerBreakdown.tsx
  • console/web/src/pages/TracesV2/components/timeline/SpanFilterMenu.tsx
  • console/web/src/pages/TracesV2/components/timeline/SpanHoverCard.tsx
  • console/web/src/pages/TracesV2/components/timeline/TimelineStrip.tsx
  • console/web/src/pages/TracesV2/components/timeline/TraceTimeline.tsx
  • console/web/src/pages/TracesV2/components/timeline/spanVisuals.tsx
  • console/web/src/pages/TracesV2/index.tsx
  • console/web/src/pages/TracesV2/lib/traceColors.ts
  • console/web/src/pages/Workers/components/WorkerConfigurationDialog.tsx
  • console/web/src/pages/Workers/components/WorkerConfigurationPanel.tsx
  • console/web/src/pages/Workers/components/WorkerSurface.tsx
  • console/web/src/pages/Workers/components/WorkersFilters.tsx
  • console/web/src/pages/Workers/components/WorkersTable.tsx
  • console/web/src/pages/Workers/index.tsx
  • console/web/src/styles/ui-recipes.css
  • console/web/src/types/injectable-ui.ts
  • database/ui/src/configuration/index.tsx
  • database/ui/src/function-trigger-message/index.tsx
  • database/ui/src/lib/rpc.ts
  • database/ui/src/page/CellInspector.tsx
  • database/ui/src/page/ChangesPanel.tsx
  • database/ui/src/page/ColumnStatsPanel.tsx
  • database/ui/src/page/ErdPanel.tsx
  • database/ui/src/page/FilterBar.tsx
  • database/ui/src/page/HealthPanel.tsx
  • database/ui/src/page/JsonTree.tsx
  • database/ui/src/page/PlanTree.tsx
  • database/ui/src/page/RowDetail.tsx
  • database/ui/src/page/SchemaTree.tsx
  • database/ui/src/page/SqlPanel.tsx
  • database/ui/src/page/TableDataPanel.tsx
  • database/ui/src/page/icons.tsx
  • database/ui/src/page/index.tsx
  • database/ui/src/page/pagination.tsx
  • database/ui/src/page/result-grid.tsx
  • database/ui/styles.css
  • docs/sops/console-ui-conformance.md
  • docs/sops/injectable-console-ui.md
  • editor/ui/page.tsx
  • editor/ui/src/page/index.tsx
  • editor/ui/styles.css
  • eval/ui/page.tsx
  • eval/ui/src/page/EvaluationDetail.tsx
  • eval/ui/src/page/History.tsx
  • eval/ui/src/page/NewEvaluationForm.tsx
  • eval/ui/src/page/index.tsx
  • eval/ui/styles.css
  • github/ui/src/page/GitGraph.tsx
  • github/ui/src/page/GithubPage.tsx
  • github/ui/src/page/icons.tsx
  • github/ui/src/page/index.tsx
  • github/ui/src/page/result-views.tsx
  • github/ui/styles.css
  • harness/ui/styles.css
  • iii-directory/ui/src/configuration/index.tsx
  • iii-directory/ui/src/function-trigger/DownloadView.tsx
  • iii-directory/ui/src/function-trigger/RegistryViews.tsx
  • iii-directory/ui/src/page/browser.tsx
  • iii-directory/ui/src/page/index.tsx
  • iii-directory/ui/src/session-chip/index.tsx
  • iii-directory/ui/styles.css
  • memory/ui/src/page/BankRail.tsx
  • memory/ui/src/page/MemoriesPanel.tsx
  • memory/ui/src/page/MemoryGraph.tsx
  • memory/ui/src/page/ModeToggle.tsx
  • memory/ui/src/page/RecallPanel.tsx
  • memory/ui/src/page/RulesPanel.tsx
  • memory/ui/src/page/icons.tsx
  • memory/ui/src/page/index.tsx
  • memory/ui/styles.css
  • packages/console-ui/README.md
  • packages/console-ui/component-names.mjs
  • packages/console-ui/index.d.ts
  • packages/console-ui/package.json
  • packages/console-ui/token-names.d.mts
  • packages/console-ui/token-names.mjs
  • packages/console-ui/ui-classes.d.mts
  • packages/console-ui/ui-classes.mjs
  • pdf/ui/src/page/index.tsx
  • pdf/ui/styles.css
  • sandbox-code-runner/ui/src/page/FleetRail.tsx
  • sandbox-code-runner/ui/src/page/SandboxPage.tsx
  • sandbox-code-runner/ui/src/page/icons.tsx
  • sandbox-code-runner/ui/src/styles/family.css
  • sandbox-code-runner/ui/src/styles/page.css
  • shell/ui/page.tsx
  • shell/ui/src/function-trigger/AgentRunView.tsx
  • shell/ui/src/page/HoverTip.tsx
  • shell/ui/src/page/TerminalPane.tsx
  • shell/ui/src/page/WorkspaceBrowser.tsx
  • shell/ui/src/page/__tests__/terminal-router-lifecycle.test.ts
  • shell/ui/src/page/__tests__/terminal-workspace.test.tsx
  • shell/ui/src/page/index.tsx
  • shell/ui/styles.css
  • state/ui/src/page/browser.tsx
  • state/ui/src/page/index.tsx
  • state/ui/styles.css
  • storage/ui/src/page.tsx
  • storage/ui/styles.css
  • worktree/ui/src/page/WorktreeDetailPanel.tsx
  • worktree/ui/src/page/WorktreeGraph.tsx
  • worktree/ui/src/page/icons.tsx
  • worktree/ui/src/page/index.tsx
  • worktree/ui/styles.css

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


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.

@sergiofilhowz
sergiofilhowz merged commit e643405 into main Aug 19, 2026
75 checks passed
andersonleal added a commit that referenced this pull request Aug 20, 2026
…ills root

Skills gain full CRUD parity with prompts: directory::skills::create
{id, content} writes <skills_folder>/<id>.md with a two-layer conflict
check (visible-set resolution incl. the <id>/index alias, then raw
dest.exists(); D114), and directory::skills::delete removes a resolved
skill plus any parent directories the removal left empty, so a deleted
namespace can't keep shadowing a lower-precedence root. Both fan out
{op, namespace, id} on directory::skills::on-change, and deletes (skill
AND prompt) now self-write-mark so the watcher stops firing a spurious
op:"external" on top of the precise op.

A third scan root, agents_skills_folder (default ~/.agents/skills),
serves system-installed agent skills read-only: scanned shallowly (only
<skill>/SKILL.md, id <skill>/index) so support payload never floods
list; namespaces shadowed by the same namespace under the global/local
roots; exempt from filter_unregistered by namespace NAME (dirs actually
carrying a SKILL.md) so manual global-root forks stay visible; excluded
from the per-worker skills::index by file provenance; refused by
update/delete (D116) and reserved against create (D115). The watcher
picks the root up only when it already exists — the worker never
creates ~/.agents/skills. SkillFrontmatter learns name: as a title
fallback (title -> name -> H1) for the agents/SKILL.md convention.

Create also rejects ids the reader could never serve: filter-hidden
namespaces while filter_unregistered is on, and prompts/system-prompts
path segments the classifier would route away from skills (D115).

The directory UI's skills tab gains the new/delete buttons (id-shaped
namePattern, applied to creates only — frontmatter titles are display
fields on update), skills::create renders as a trigger card, and the
config form gains the new knob. The form is also brought in line with
the #837 shared-UI conventions: sentence-case copy end to end, no CSS
case transforms, sans panel with mono confined to path/URL inputs, and
the shared Input + Chip(tone=warning) primitives replacing hand-rolled
controls.

Tests pin agents_skills_folder into tempdirs everywhere (the default
resolves to a REAL directory on dev machines): unit cfg helpers, the
BDD harness, and the e2e config. cargo test --lib 354 passing; BDD
unchanged at its pre-existing baseline.
andersonleal added a commit that referenced this pull request Aug 20, 2026
…stem-installed ~/.agents/skills (#851)

* docs(agents): wire issue-tracker, triage-label, and domain-doc guides into AGENTS.md

Adds docs/agents/{issue-tracker,triage-labels,domain}.md — how agent
skills reach Linear (iii team, MOT-###), the five canonical triage
labels, and the CONTEXT.md/ADR reading order — and points AGENTS.md at
them under a new 'Agent skills' section.

* feat(iii-directory): skill create/delete and a read-only ~/.agents/skills root

Skills gain full CRUD parity with prompts: directory::skills::create
{id, content} writes <skills_folder>/<id>.md with a two-layer conflict
check (visible-set resolution incl. the <id>/index alias, then raw
dest.exists(); D114), and directory::skills::delete removes a resolved
skill plus any parent directories the removal left empty, so a deleted
namespace can't keep shadowing a lower-precedence root. Both fan out
{op, namespace, id} on directory::skills::on-change, and deletes (skill
AND prompt) now self-write-mark so the watcher stops firing a spurious
op:"external" on top of the precise op.

A third scan root, agents_skills_folder (default ~/.agents/skills),
serves system-installed agent skills read-only: scanned shallowly (only
<skill>/SKILL.md, id <skill>/index) so support payload never floods
list; namespaces shadowed by the same namespace under the global/local
roots; exempt from filter_unregistered by namespace NAME (dirs actually
carrying a SKILL.md) so manual global-root forks stay visible; excluded
from the per-worker skills::index by file provenance; refused by
update/delete (D116) and reserved against create (D115). The watcher
picks the root up only when it already exists — the worker never
creates ~/.agents/skills. SkillFrontmatter learns name: as a title
fallback (title -> name -> H1) for the agents/SKILL.md convention.

Create also rejects ids the reader could never serve: filter-hidden
namespaces while filter_unregistered is on, and prompts/system-prompts
path segments the classifier would route away from skills (D115).

The directory UI's skills tab gains the new/delete buttons (id-shaped
namePattern, applied to creates only — frontmatter titles are display
fields on update), skills::create renders as a trigger card, and the
config form gains the new knob. The form is also brought in line with
the #837 shared-UI conventions: sentence-case copy end to end, no CSS
case transforms, sans panel with mono confined to path/URL inputs, and
the shared Input + Chip(tone=warning) primitives replacing hand-rolled
controls.

Tests pin agents_skills_folder into tempdirs everywhere (the default
resolves to a REAL directory on dev machines): unit cfg helpers, the
BDD harness, and the e2e config. cargo test --lib 354 passing; BDD
unchanged at its pre-existing baseline.

* (MOT-4506) fix(iii-directory): show the function-search card inline in chat

The search renderer declared no presentation metadata, so the console
kept a successful DiscoverCard behind "show raw request and response"
and chat only showed the generic compact card for
directory::search_functions.

Declare metadata.display, matching the focused result-bearing renderers
in browser (screenshot), shell (agent-run, file-changes), and web. The
general catch-all renderers (browser/page.js#calls, shell/page.js#shell,
and iii-directory's own #directory) deliberately stay compact, so this
is scoped to the search card only.

Safe by construction: every non-render path in tryRender already returns
null, so a pending-approval, errored, or unparseable call still falls
through to the compact card. Arrived with the discovery-worker
absorption (#839); shipping here since it rides the same PR.

* (MOT-4506) feat(iii-directory): announce a skill's base directory so payload skills can run

Live session console-1f62d4cf showed why a payload skill (impeccable:
SKILL.md + scripts/ + reference/) degrades to prose-only: the console
freezes only the SKILL.md body into the session addon, so the model has
no way to locate scripts/context.mjs and probes .agents/skills/ relative
to the project cwd, which doesn't exist.

- directory::skills::get now returns `path`, the absolute on-disk file;
  its parent dir is the skill's base directory per the agent-skills
  convention.
- The console appends a 'Skill base directory: <dir>' line at both
  freeze sites (session addon picker and /skill:<id> slash expansion)
  via one shared helper; body-only when the worker predates the field.

* feat(harness): copy the session id from the context chip popover

* (MOT-4511) feat(state)!: default the kv store to file_based persistence and flush on shutdown

The in_memory default silently lost every scope (harness turn records,
context snapshots, barriers, namespace claims) on each worker restart.
Defaults now: store_method file_based under ./data/state (worker-cwd
relative). Shutdown flushes pending dirty scopes instead of relying on
the next save-loop tick, and main handles SIGTERM (workers-dev's stop
signal) so that flush actually runs. Explicit in_memory pins (CI e2e)
keep their behavior; tests that leaned on the volatile default now pin
it explicitly.

BREAKING CHANGE: a bare state worker now persists to ./data/state; pin
store_method: in_memory to keep the old volatile behavior.

* (MOT-4515) fix(iii-directory): keep unsaved skill/prompt drafts across tab switches

The console unmounts the directory page on every tab switch, so a
half-typed new skill (or an unsaved edit to an existing one) died with
the component — the editor came back empty with no warning.

Unsaved work now mirrors to localStorage under `${storageKey}:draft`
(already per-tab and per-collection) and is restored on mount. A
creating draft is self-contained; a draft over an existing entry
re-fetches its on-disk baseline so dirty tracking and save() still diff
against the real file.

The write/clear/keep decision is a pure function in draft-storage.ts so
it is testable without a DOM: notably it KEEPS storage while a baseline
load is in flight, since clearing there would destroy the work just
restored. Every deliberate discard (open another entry, start a create,
drill out) clears immediately rather than waiting for the load to land.

Storage only ever holds work that differs from disk, so a save, delete,
or discard leaves nothing behind.

* (MOT-4511) fix(state): make the shutdown flush actually durable

Three defects in the file_based persistence path, from CodeRabbit review
on #851. Each one loses exactly the write the file_based default exists
to keep.

- Periodic and explicit flushes both drained the whole dirty map with no
  mutual exclusion, so the shutdown flush could find it already empty
  while the save loop's write was still in flight — and process exit
  then cancelled that write. A shared async mutex now spans drain,
  persist, and requeue for both paths.
- A failed write requeued its scope with a plain insert, clobbering any
  newer op a concurrent set/delete had queued meanwhile. A failed Delete
  landing on a fresh Upsert deletes live data on the next flush. Requeue
  is now entry().or_insert(), so newer intent always wins.
- flush_dirty only logged failures, so KvStoreAdapter::flush always
  returned Ok and BootHandle::shutdown's warn branch was dead code. It
  now attempts every scope, then reports how many failed; the adapter
  propagates instead of discarding.

Shutdown also disconnects before flushing. shutdown_async clears the
SDK's running flag, which stops its receive loop dispatching further
invocations, so no request accepted after the signal can mutate the
store behind the flush. Requests already executing remain a race the SDK
gives no way to await — closing that needs in-flight tracking, which is
out of scope here.

Two tests: the requeue ordering rule, and that a failed persist surfaces
as an error and leaves the scope dirty (a directory planted where the
index file belongs makes the write fail without touching permissions).

* (MOT-4506) fix(iii-directory): correct the base-dir helper, trigger contract, and README anchor

CodeRabbit review on #851.

- skillBodyWithBaseDir cut the directory at the last '/' only. The
  worker ships Windows binaries, so path can be C:\...\SKILL.md; worse,
  with no separator at all lastIndexOf returned -1 and slice(0, -1)
  produced the whole path minus its last character, presented to the
  model as a directory. Now cuts on either separator and falls back to
  body-only when there is none.
- All three on-change trigger descriptions listed only write ops, but
  the fs watcher also fires them with { op: "external" }; subscribers
  read these descriptions as the event contract. The prompts one also
  omitted delete, which it has fired since the prompt CRUD landed.
- README linked #layout; the heading anchor is #on-disk-layout.
- Long session ids could not ellipse in the harness context chip: a flex
  item will not shrink past its content without min-width: 0, so the id
  pushed the copy button out of the popover. Same pairing the sibling
  .harness-ui-pop-model already uses.

Also documents what happens when the agents skills root is created after
boot — it stays unwatched until a restart, and reads still serve it
because every read re-scans disk, so only the live doorbell is missing.
Re-arming the watcher for a root that may never appear is not worth the
machinery.
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.

1 participant