Kotoba

Where to start

What Kotoba isWho she is and where she runs, in one page
InstallingOne package, two commands
First runA key, a model, her first words
The approval gateHow she asks before she acts
The two voice modesLocal voice, or the agent tunnel
The soul fileChange who she is
RoadmapWhat grows next, and what was cut on purpose

Or a section

↑↓ move openesc close124 pages
All pages

The web app6 of 12

Files

About 4 minutes to read

On this page

The Files panel is a window onto one real folder on your disk. It is not a per-call scratchpad and it is not a database.

Screenshot placeholder: the Files panel in the left rail — a search box, four filter chips (All / Code / Images / Docs), a folder row with a yellow "new" badge, and three file rows each with a coloured type icon, an extension tag, an open-in-new-tab control and a bin.

The folder is her working directory

The library lives at ~/.kotoba/files and can be moved with KOTOBA_FILES_DIR (api/src/kotoba/core/file_library.py:32-36). By default the library is her working directory — they are the same folder, not a copy of one (api/src/kotoba/core/file_library.py:1-7).

That has two consequences worth knowing:

  • Anything you drop into that folder yourself is immediately something she can read and work on.
  • Anything she creates or edits appears in this panel, across turns, restarts and page reloads (api/src/kotoba/server.py:899-906).

The disk is the truth. The panel does not merge events into a list it keeps: a files_changed event bumps a counter and the whole list is re-read (components/panels/FilesPanel.tsx:304-321).

Getting files in

There is no upload control in this panel. The three ways a file gets into the library are:

  1. She makes it. Her file tools write into it.
  2. You put it there with your own file manager, terminal or editor — it is an ordinary folder.
  3. The transcript's paperclip, for images, PDFs and text files. That is a different path: an attachment is carried by the next turn, not saved into the library. See The transcript.

Getting files out

Three controls per row (FilesPanel.tsx:79-133):

ControlWhat it does
The row itselfopens the file in the in-app viewer
The arrow iconopens it in a new browser tab
The bindeletes it — two clicks

Delete arms on the first click and fires on the second; if you do not confirm within 3 seconds it disarms itself (FilesPanel.tsx:64-68, 112). There is no undo — the row is removed optimistically and the disk is re-read straight afterwards to correct the list if the delete failed (FilesPanel.tsx:294-300).

The viewer's header has New tab and Save; Save is a plain download of the raw file (FilesPanel.tsx:215-220). Escape closes the viewer, and so does clicking outside it (FilesPanel.tsx:178-183, 204).

The tree

Files are shown one folder deep at a time, with a breadcrumb trail back to the root (FilesPanel.tsx:391-401, 423). A folder row carries a badge when it contains anything unseen at any depth, and new wins over edited (FilesPanel.tsx:337-357).

Files in the current folder are sorted newest-first by modification time (FilesPanel.tsx:358).

Searching switches the panel out of tree mode: it searches the whole library by path and shows full paths in the results (FilesPanel.tsx:325-334, 431).

Four filter chips: All, Code, Images, Docs (FilesPanel.tsx:266-271). The classification is by extension (FilesPanel.tsx:25-40):

  • Codepy js ts tsx jsx json html css scss sh rb go rs java c cpp yml yaml toml
  • Imagespng jpg jpeg gif webp svg ico bmp, or anything the backend tagged as an image
  • Docs — everything else, including md txt rst log csv env pdf

A new or edited badge sits on a file until you open it; opening one tells the backend so the badge does not come back on the next page load (FilesPanel.tsx:284-292).

The extension is pinned outside the truncated part of a long name. An ellipsis eats the end of a name, and report.html and report.pdf used to render as identical cards (FilesPanel.tsx:74-78).

The viewer

What you get depends on the file (FilesPanel.tsx:225-262):

FileView
Imagerendered directly
Markdownrendered, with a Code toggle for the source
HTMLrendered in a sandboxed iframe, with a Code toggle for the source
Anything elseplain monospace text
Binary, or gone"Couldn't open this one — it may have been a binary file or it's no longer around."

The HTML preview runs in an iframe with sandbox="allow-scripts" and without allow-same-origin, which makes it a unique null origin that cannot read the parent page's storage (FilesPanel.tsx:236-243). The Markdown renderer does not emit raw HTML.

Everything the viewer draws — including the "Code" view — is stripped of invisible and bidirectional control characters first (FilesPanel.tsx:162-163, 184-190). A source file that reverses itself on screen is the exact attack this is for.

The path is treated differently from the name. The path is protocol: it round-trips to disk byte-for-byte for open, delete and the raw URL, so it is kept raw — scrubbing it would orphan a file with a hostile name. The gate sits on every drawn name instead (FilesPanel.tsx:1-9, 57-58).

Limits

The backend caps what may be written into the library (api/src/kotoba/core/file_library.py:26-29):

LimitValue
Text file written by a tool200 000 characters
Image written by a tool12 000 000 bytes
File count before pruning500 — and pruning never runs by default, because it only applies when the library mirrors a separate working directory

Paths are jailed: nothing she writes and nothing this panel opens can escape the library folder (api/src/kotoba/core/file_library.py:141-142, server.py:909-921).