Skip to content

Cell - Ta Web ​

The Web client included with ta serve: a Cell frontend component (@celljs/react) whose state comes entirely from @celljs/ta-client, so it shares the same session state machine as the desktop and mobile clients.

bash
ta serve --web     # Start and open in browser, token already included

Install as Application (PWA) ​

The Web client can be installed as an application on desktop or mobile: use the install icon in the Chrome or Edge address bar, or "Install as app" from the account menu, or "Add to Home Screen" in iOS Safari. After installation, it opens in a separate window with the Cell dinosaur icon.

  • Files are in src/browser/public/ (manifest.webmanifest, sw.js, icons/) and placed at the site root during build; the manifest and Service Worker only work in secure contexts: localhost (127.0.0.1 or localhost) or HTTPS. Accessing via HTTP from another machine using --host displays only as a web page.
  • The app remembers the address (including port) and token, so ta serve reuses the last port and token by default (see @celljs/ta).
  • The Service Worker caches nothing under /api: pages come from the network and fall back to the last page on failure; hashed scripts under /assets/ come from the cache first, and old scripts are cleared once the page has a new build. While ta serve is not running the app still opens, says "The Ta service is not running" and connects once it starts. The interface calls ta serve "the Ta service" and names the command only where the user has to run it in a terminal.

Interface ​

The main layout is IDE-style: the navigation bar on the far left (with logo doubling as a sidebar toggle, pages for web extensions, and settings and account menu at the bottom), the sidebar next to it (resizable and collapsible; a row of icon tabs at the top switches between web extension sidebar views and the conversation list, with a search box and + button to the right of the tabs) containing all open sessions on the server (which can be created, created in a specified directory, or closed), the current conversation in the middle, and a collapsible panel on the right for web extension cards. On narrow screens, the navigation bar and conversation list become drawers, and the panel floats above the conversation.

Initially, the interface shows only conversations. You tell Ta what you want this page to do and display, and Ta builds it as a pi package—cards beside the conversation, a standalone page, or a new Ta command—which appears on the page immediately.

Everything new in the interface comes from web extensions within packages, using the same mechanism and terminology as pi extensions:

  • Packages are pi packages. In the pi field of package.json, extensions are extensions for Ta (terminal and server), and web are extensions for this page; when not declared, modules in the web/ directory are used. Packages can be placed in ~/.ta/agent/extensions/<name>/ or the project's .ta/extensions/ to take effect, or installed with ta install.
  • Web extensions are ES modules without import statements, with a default export of a function that the page calls with pi; the returned function is called on unload. The pi interface follows pi's ExtensionAPI naming: ui.setWidget (default above the input box, same as pi; placement: 'panel' puts it in the right workbench's card tabs), registerView (pages in the navigation bar, or a tab at the top of the sidebar; location: 'panel' is a tool in the right workbench), registerMessageRenderer (displays custom messages sent by pi extensions with sendMessage), registerTool (tools executed by the browser in conversations opened on the page), registerOpener (opens links in replies: files and their lines, directories, web pages, asked in registration order, returns true if opened), on (events of the current conversation), sendUserMessage (sends to current conversation; /commands execute pi extension commands), switchSession (displays another open session in ta serve as the current conversation), callHost (calls methods of this package's pi.host, via callPackageHost), reloadExtensions (after the extension itself installs, imports or removes packages, reload web extensions and pi extensions of the current conversation like at the end of a reply), ui.showView / ui.useView (show, view any extension's view), ui.Composer / ui.Markdown (input box and Markdown same as conversation), ui.Dialog (full-page modal), ui.openEditor's preview (preview tab that opens on click, title in italics, next preview replaces it, double-click or ui.pinEditor pins it), ui.setEditorDirty (tab shows unsaved dot, confirm before close), ui.useActiveEditor (current tab in workbench), ui.DiffEditor (Monaco diff editor, side-by-side or inline, right side editable), ui.confirm's confirmLabel / danger, view's head (its own title bar), ui.input / ui.editor (ask user for one or multiple lines of text, returns undefined if canceled), page's menu (floating … menu when the page is listed in sidebar) and pages (pages listed in sidebar, navigation bar no longer lists them separately), ui.notify (notifications in bottom right of page), ui.confirm (confirm before continuing, returns whether user agreed), ui.theme, ui.language, ui.supportsWebview (whether a view can show a site in a <webview>, as in Ta Desktop, rather than an iframe), ui.debugWebview (Ta Desktop: a command of the Chrome DevTools Protocol to the page of such a webview, limited to the page itself), ui.desktop.browserData (Ta Desktop on macOS and Windows: the cookies of a profile of the user's browser taken into the browser of Ta once the user says yes in a dialog of the app, and clearing that browser's data), and page services: server (session, pi's RPC commands), workspace (files and commands in directory), terminals (terminals), packages (packages) (types see WebExtensionAPI).
  • Make packages with natural language: the built-in extension @celljs/ta-extensions includes the skill ta-package (also available in the Ta terminal) and adds an "Extensions" page in the navigation bar (create an extension in one sentence, export or import). It documents package format, the web extension interface, and validation steps. At the end of each reply in the current conversation, the page reloads web extensions: new ones load, changed ones reload, and deleted ones unload; when tools change, the conversation reopens that part in the same session file (only for conversations opened on this page); when packages change, the session reloads pi extensions. The page checks again when redisplayed (packages might change in the terminal too).
  • Stable: each extension owns what it registers and removes it all on unload; if one extension fails to load or render, only that extension's area shows an error, without affecting the page or other extensions.

Settings → More → Extensions (the "Extensions" in the gear menu) lists installed extensions (each is a package with web extensions that can be removed or reloaded); Built-in Extensions lists packages and web extensions provided with Ta and can be toggled here:

  • My Double: a double that can be given tasks by voice or text. The main conversation is a regular Ta session with role instructions (appendSystemPrompt) and five client tools (such as start_task); each task runs as a new session in the background and ends with a <task_update> report. The appearance is the dinosaur from the Cell logo (DinoAvatar, using the same path as assets/logo.svg), conveying states such as standby, listening, thinking, working, speaking, needing confirmation, done, and asleep using only eye expressions and small markers.

Conversation and Input Box ​

  • Conversation flow: continuous thinking, tool calls, and interim explanations in a turn are folded into a single line "Executed N steps": this expands while in progress, shows only recent steps, and collapses after completion (remaining expanded and marking failed steps if any); the final text is the reply. Layout is a pure function ProcessUtil (util/process.ts); each message uses a stable key and memos per message, so streaming output only redraws changed messages.
  • Input box: Enter sends, Shift+Enter inserts a newline; during IME composition, Enter does not send. While Ta works, Enter suggests, Alt+Enter queues, Esc stops, and send and stop use the same button. / lists commands, @ finds and inserts @path by filename (using the server's findFiles; the menu explains if unsupported); images can be pasted, dragged, or selected (PNG, JPEG, GIF, WebP; max 5 MB per image, max 8 per message); queued messages show above the input box and can be undone back to the box (clearQueue). Logic is in ComposerUtil (util/composer.ts).
  • Avatar: TaAvatar is the Cell logo, and mark is a 1–2 character badge to distinguish agents (in web extensions it's pi.ui.Avatar); each reply starts with a small avatar (once per turn): during work, eye expression shows thinking, execution, or output; on the last turn when done, eyes squint with a checkmark; on error, it shows an alert badge; an empty conversation page displays a large avatar that blinks and occasionally looks at the input box. With reduced motion, everything is static.
  • Status and hints: four levels (info, success, warning, and error), each with a fixed icon and color, with the body always in body color. By ownership, there are three types: global connection status bar (a capsule at the top of the main area that appears after 1 second of disconnect, showing reason, countdown, and "Retry now"; shows "Reconnected" for 2 seconds after recovery); notices (TaNotices, bottom right, top on phone; success 3s, info 4s, warning 6s, error 8s; add 4s for buttons; pause on hover or focus; merge same messages with count; max 4 notices); inline hints (Callout, staying at the place of issue, such as when a turn reply fails, an extension view errors, or a token is rejected). Operation failures (send, switch model, open conversation, etc.) uniformly use notices.error: server errors with hint put the suggestion on the second line; failures from disconnect merge into one "Did not complete: ta serve not connected" message. Operations needing user confirmation use notices.confirm; irreversible operations have red buttons with default focus on "Cancel".
  • Links: all links in replies are clickable (TaLinks, parsed in LinkUtil). Web links, bare URLs in text, and <URL> open by default in the browser on the right workbench; Cmd/Ctrl+click (or middle button) opens in the system browser. File paths ([text](https://github.com/cellbang/cell/blob/main/ta-packages/ta-web/src/a.ts#L10-L20), src/a.ts:10:5, file:// address, and inline code like src/a.ts:12 and package.json) are parsed relative to the conversation directory, open and locate in the workbench, and select corresponding lines and columns (syntax like VS Code: #L10, #L10C5-L12C3, :10:5, :10-20); directories (docs/) open in the explorer at that directory. Open actions are provided by web extensions (registerOpener; the built-in @celljs/ta-workspace handles files, directories, and the built-in browser). For security, following VS Code: only http(s), mailto, and paths are recognized; javascript:, data:, command:, etc. are plain text; rendering results are React elements, not HTML; paths are confirmed to exist on the server first, though read/write are still subject to execution environment constraints. Before opening in the system browser, a popup shows the full address with the domain highlighted (domains with similar appearance are shown in browser's punycode format); you can copy, open, or trust the domain. Localhost addresses (localhost, 127.0.0.1, and the host where ta serve runs) and Settings → Links → Trusted Domains (github.com, *.github.com, localhost:3000, *) skip confirmation; new windows always have noopener,noreferrer. In the built-in browser, URLs same-origin with the Ta page do not get allow-same-origin and cannot reach the page or token.
  • Account menu: the round avatar at the foot of the navigation bar (AccountMenu) shows the initial of the user name on the computer of ta serve (TaServerInfo.user), with a yellow dot at its corner while disconnected. The menu shows the user and computer name (host), the connection state and version of the Ta service, "Install as app" (when the browser supports it) and sign out.

Settings ​

The gear above the avatar (ManageMenu) is a two-level menu: Settings (⌘/Ctrl+,), Extensions, Keyboard shortcuts; Model accounts…, Open working directory…, Use on your phone…; Theme and Language expand on the right as a second level, with About Ta last. Settings is a view in the main area (SettingsView; it occupies the conversation and workbench when open; Esc or × closes it). The left side is a two-level directory (Interface, Ta, and More, then groups below; it follows highlight when scrolling); the right side shows group cards. The search bar at the top filters by name and description of each line:

  • Interface: theme, reduce motion, interface language, and read aloud replies; stored in server user data (TaSettings), consistent across browsers and devices.
  • Ta: Ta's own settings; reads and writes pi's settings.json (via the server's getSettings and updateSettings; editable keys are listed in ta-protocol's TA_SETTINGS): default model, thinking strength, transport, guide and queue message delivery, auto-compress and retry, code mode, skill commands, shell and command prefix, and images. At the top, switch between User (agent directory; affects all projects) and Project (.ta/settings.json in the current conversation directory; overrides user settings). Unset items show inherited values ("Default (…)" or "Use user setting (…)"); items set in the current level have a blue bar on the left; hover to reset. A project that is untrusted is read-only with the reason shown.
  • More: Extensions, Built-in extensions, Keyboard shortcuts, and About (version, working directory, model accounts, phone access, and log out).

Sign In to Model Service, Working Directory and Phone ​

  • Model accounts: similar to pi's /login in the terminal, you can sign in with an account (OAuth, Claude, ChatGPT subscriptions) or API Key. Sign-in occurs on ta serve (TaAuthServer): the login page to open, device code, and key to fill are sent only to the requesting page; credentials are stored in auth.json on the ta serve machine, not in the browser. When no model is available (CELL_TA_NOT_SIGNED_IN), the page opens the login page directly instead of pi's /login prompt; the same applies to the desktop app on first launch.
  • Working directory: when no directory is specified, new conversations and doubles open in ta serve's working directory. "Open working directory…" (TaServer.setWorkingDirectory) affects all clients and persists across restarts; already open conversations are not changed.
  • Use on your phone: pair a device via Relay (@celljs/ta-relay); the pairing link is displayed as a QR code (util/qr-code.ts draws it itself with no dependencies) and shown only once. Below, it lists paired devices and whether ta serve is connected to Relay. The default Relay is https://relay.cellbang.com.

Structure ​

TaWorkbench (TaWorkbenchImpl)One connection and open sessions on the server; token from URL fragment or localStorage; if unreachable, probe the server first: a response means the token is wrong (back to login page); no response means ta serve is not running (keep token, show hint, and auto-retry)
TaPwa (TaPwaImpl)Make the page an installable app: link the manifest and icons, register Service Worker, and provide "Install as app" in the account menu (when the browser offers an install prompt)
TaWebExtensions (TaWebExtensionsImpl)Load, reload, and unload web extensions; aggregate pages, cards, message renderers, and tools they register
BuiltinWebExtensionExtension point for built-in web extensions: AssistantWebExtension (the double)
TaChat (TaChatImpl)Current conversation: create, switch, or close; open a session with web extension tools; reload extensions after a reply; remember the last session file and new conversation directory (user level)
TaAssistant (TaAssistantImpl)The double: main conversation, tasks, and client tools for task dispatch; session file and task list are user-level state, with the same double across browsers
TaState (TaStateImpl)Interface state saved by level; see below "Interface State"
TaSettings (TaSettingsImpl)Theme, language, read aloud, reduce motion, and toggled built-in extensions; language is switched via @celljs/widget's LocaleManager
TaNotices (TaNoticesImpl)Page's notices and confirmations; shared by Ta and web extensions
TaAppPageHome page registered with @View (AppView)

The server (@celljs/ta-host) adds getSettings and updateSettings (Ta's settings) and three methods: listWebExtensions, readWebExtension, and removeWebExtension, which find web extensions the same way pi loads packages (packages setting and extensions/ directory; project's only when trusted); and reload, which is the same as pi's /reload.

Interface State ​

Things on the interface that should persist after refresh, grouped by "who should see it again", are saved uniformly by TaState (in components use usePersisted(spec); spec specifies the key, level, initial value, and validation on read):

LevelWhereFor
tabThis page's sessionStorageCurrent page and sidebar tabs: two pages can each see their own
deviceThis browser's localStoragePanel widths, collapse state, and whether the right workbench is open: follows the screen
userServer's data for @celljs/ta-web package (getExtensionData); browser keeps a copySettings, last conversation, new conversation directory, the double's session and tasks, and workbench open tools: consistent across browsers and devices

user level first uses the browser copy (no flash on first screen), then reads from the server and updates if the server value differs; changes merge and write after ~300 ms, write immediately when the page is hidden or closed, changes during disconnect are sent after reconnect, and changes from other browsers are pushed via onExtensionData. On read, values already changed on this page are not overwritten; between browsers, the last write wins. Old values existing only in the browser are moved to the server on first read.

State for a specific directory (expanded folders in the explorer) is saved by the web extension displaying it with pi.storage.directory(cwd); the server stores it by directory (plus execution environment; with ta serve --exec, same-named directories on remote machines are separate) in separate files. Open files are recorded by @celljs/ta-workspace itself in pi.storage; loading reopens them in the background with openEditor(editor, { preserveFocus: true }) and does not pop out the workbench.

The token is a login credential, not interface state, and is still stored by TaWorkbench in localStorage (after being installed as an app, you can sign in in its own window).

To add a built-in web extension, write a @Component(BuiltinWebExtension). To replace the whole interface, rebind the home page with @View({ index: true, component: MyView }, true).