feat: file system access, behind a path policy and an approval #25
@@ -17,13 +17,14 @@ Hit a global shortcut, type a question, get a streamed answer without leaving wh
|
|||||||
- **Readable code.** Fenced blocks render in a dark panel with the language named in a header, syntax colouring, and a Copy button.
|
- **Readable code.** Fenced blocks render in a dark panel with the language named in a header, syntax colouring, and a Copy button.
|
||||||
- **Real markdown.** Tables, bulleted and numbered lists, headings, blockquotes, bold, italics and links all render natively, and the default prompt asks for the shape that fits the answer rather than flattening everything into prose.
|
- **Real markdown.** Tables, bulleted and numbered lists, headings, blockquotes, bold, italics and links all render natively, and the default prompt asks for the shape that fits the answer rather than flattening everything into prose.
|
||||||
- **Maths, real HTML, and diagrams.** LaTeX renders, so an equation arrives as an equation instead of a wall of backslashes. A small amount of HTML draws as well, for the status chip or tiny grid a sentence is worse at, and a diagram drawn with box characters keeps its alignment instead of being reflowed. See [Rendering](#rendering).
|
- **Maths, real HTML, and diagrams.** LaTeX renders, so an equation arrives as an equation instead of a wall of backslashes. A small amount of HTML draws as well, for the status chip or tiny grid a sentence is worse at, and a diagram drawn with box characters keeps its alignment instead of being reflowed. See [Rendering](#rendering).
|
||||||
- **Tools.** Lam decides when to call them: current time, location context, web search, and page fetching. A turn that uses up its tool budget still ends in an answer, written from what it gathered, rather than a notice that it stopped.
|
- **Tools.** Lam decides when to call them: current time, location context, web search, page fetching, and, when you switch file access on, seven tools that list, search, read, write, edit, move and delete files.
|
||||||
|
- **Files you allow, and nothing else.** Off by default. When it is on, Lam can read under your home directory except for credential stores, and can only change files in Downloads, `Documents/Lam Agent`, its own folder, and wherever you add. Every change is shown to you with the exact path before it happens, a delete or a move asks every time, and a credential file is refused by name. See [Files](#files).
|
||||||
- **Sources you can check.** A search result and a fetched page each get a number, and those numbers are the ones Lam cites, so `[1]` in the answer and `[1]` under it are the same page. The list sits under the answer it belongs to, each number opens the page it names, and a number Lam was never shown stays as plain text rather than becoming a link to somewhere else.
|
- **Sources you can check.** A search result and a fetched page each get a number, and those numbers are the ones Lam cites, so `[1]` in the answer and `[1]` under it are the same page. The list sits under the answer it belongs to, each number opens the page it names, and a number Lam was never shown stays as plain text rather than becoming a link to somewhere else.
|
||||||
- **Tool calls you can watch and stop.** What Lam is doing right now stays on screen, including when it asks the same question twice, the round limit is a preference, Force Stop is always one keystroke away, and a call that repeats itself with identical arguments ends the turn instead of running forever.
|
- **Tool calls you can watch and stop.** What Lam is doing right now stays on screen, including when it asks the same question twice, the round limit is a preference, Force Stop is always one keystroke away, and a call that repeats itself with identical arguments ends the turn instead of running forever.
|
||||||
- **Models, from a list.** Browse every model your host can reach, pick one and start using it immediately, and on a server you run, pull new ones or delete old ones. Tag names are picked, never typed.
|
- **Models, from a list.** Browse every model your host can reach, pick one and start using it immediately, and on a server you run, pull new ones or delete old ones. Tag names are picked, never typed.
|
||||||
- **Optional auto-deletion.** Set a retention window and Lam clears out the conversations you have stopped using the next time you open a command, taking their screenshots with them and collecting any capture you never sent. Off by default, and nothing at all is read or written while it is off. The setup screen also has both tidy-ups as explicit actions, confirmed first, for when you would rather press the button yourself.
|
- **Optional auto-deletion.** Set a retention window and Lam clears out the conversations you have stopped using the next time you open a command, taking their screenshots with them and collecting any capture you never sent. Off by default, and nothing at all is read or written while it is off. The setup screen also has both tidy-ups as explicit actions, confirmed first, for when you would rather press the button yourself.
|
||||||
- **Screenshots, two ways.** Snip a region into the conversation you are already in from the action panel, or run **Snip to Lam** to start a new chat about it. The launcher window gets out of the way first, so it never ends up in the shot, and the capture appears in the transcript as a thumbnail with Open Screenshot for the full image. Neither is offered unless the current model can actually see images.
|
- **Screenshots, two ways.** Snip a region into the conversation you are already in from the action panel, or run **Snip to Lam** to start a new chat about it. The launcher window gets out of the way first, so it never ends up in the shot, and the capture appears in the transcript as a thumbnail with Open Screenshot for the full image. Neither is offered unless the current model can actually see images.
|
||||||
- **Chat only.** Lam answers questions. It does not touch your files or run commands. The one exception is capture, and it only ever happens because you pressed the key for it.
|
- **Chat answers, and nothing else happens unless you allow it.** Lam does not run commands, open URLs or touch the clipboard, and the one thing it can change on the machine is a file, which it can only do when you turn File Access on and approve each change.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@@ -132,6 +133,9 @@ vicinae 'vicinae://launch/lam-agent/chat?fallbackText=summarise%20this'
|
|||||||
| **IP Geolocation** | off | Off by default. When on, and only when **Location** is empty, your IP is sent to ipwho.is to resolve a rough area. |
|
| **IP Geolocation** | off | Off by default. When on, and only when **Location** is empty, your IP is sent to ipwho.is to resolve a rough area. |
|
||||||
| **System Prompt** | Lam's default | Replaces the built-in prompt entirely. The default asks for plain prose: no em dashes, no generated-text tells, no restating the question. |
|
| **System Prompt** | Lam's default | Replaces the built-in prompt entirely. The default asks for plain prose: no em dashes, no generated-text tells, no restating the question. |
|
||||||
| **Web Tools** | on | Enables `web_search` and `web_fetch`. |
|
| **Web Tools** | on | Enables `web_search` and `web_fetch`. |
|
||||||
|
| **File Access** | off | Off by default. Enables the seven file tools. Off means the model never learns they exist. See [Files](#files). |
|
||||||
|
| **Extra Readable Folders** | None | One absolute path per line. Added to your home directory, which is always readable except for credential stores. |
|
||||||
|
| **Extra Writable Folders** | None | One absolute path per line. Added to Downloads and `Documents/Lam Agent`. Every change is still approved one at a time. |
|
||||||
| **Reasoning Effort** | Off | `low`/`medium`/`high` for models that support thinking. |
|
| **Reasoning Effort** | Off | `low`/`medium`/`high` for models that support thinking. |
|
||||||
| **Show Tool Calls** | on | Lists which tools Lam called, each named once, with a count of any that failed. Off hides the names; a failed or repeating call is still noted in one line. |
|
| **Show Tool Calls** | on | Lists which tools Lam called, each named once, with a count of any that failed. Off hides the names; a failed or repeating call is still noted in one line. |
|
||||||
| **Show Reasoning** | on | Includes the model's thinking trace under each answer. Turn off to keep replies short. Either way the trace streams live during a turn and disappears when the turn ends. |
|
| **Show Reasoning** | on | Includes the model's thinking trace under each answer. Turn off to keep replies short. Either way the trace streams live during a turn and disappears when the turn ends. |
|
||||||
@@ -188,7 +192,7 @@ The download runs inside this command's worker, so **closing the launcher cancel
|
|||||||
|
|
||||||
## Tools
|
## Tools
|
||||||
|
|
||||||
Lam calls these on its own when they would improve an answer. All four are read-only, and all four are the entire list of things Lam is allowed to do without being asked twice. Screen capture is deliberately not among them.
|
Lam calls these on its own when they would improve an answer. The four below are read-only, and they are the entire list of things Lam is allowed to do without being asked first. The file tools are a separate family, because they are the only ones that can change the machine: they are absent from the model's list until you switch **File Access** on, and every change they make is confirmed by you. Screen capture is deliberately not among any of them.
|
||||||
|
|
||||||
| Tool | What it does |
|
| Tool | What it does |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
@@ -233,6 +237,38 @@ When tool calls are off, nothing is listed. A call that failed, or that was cut
|
|||||||
|
|
||||||
**That line is rendered in exactly one place, on the live message.** It is not a view-level note above the transcript, and it is not a substitute for missing text either: it is shown whether or not tokens have arrived, because a tool that starts mid-answer would otherwise be invisible for the rest of the turn. The first version had both, a note in the view and the placeholder in the transcript, and a tool running before any text arrived printed "Reading a page" twice, one line above the other. The cursor rides on whichever line is written last, so it always trails the text actually arriving.
|
**That line is rendered in exactly one place, on the live message.** It is not a view-level note above the transcript, and it is not a substitute for missing text either: it is shown whether or not tokens have arrived, because a tool that starts mid-answer would otherwise be invisible for the rest of the turn. The first version had both, a note in the view and the placeholder in the transcript, and a tool running before any text arrived printed "Reading a page" twice, one line above the other. The cursor rides on whichever line is written last, so it always trails the text actually arriving.
|
||||||
|
|
||||||
|
### Files
|
||||||
|
|
||||||
|
Off by default, and off means absent rather than refused: with **File Access** unticked the seven tools are not in the list the model is given, so it never learns they exist and cannot ask for one. That is the honest way to say "Lam cannot read files" when the setting is off.
|
||||||
|
|
||||||
|
| Tool | What it does |
|
||||||
|
| --- | --- |
|
||||||
|
| `list_directory` | Names, sizes and modification times in a folder, directories first. Dotfiles are hidden unless asked for. |
|
||||||
|
| `search_files` | Finds files by a name glob, optionally only those whose contents contain a string. |
|
||||||
|
| `read_file` | Numbered lines, 200 at a time, with the offset to continue from. |
|
||||||
|
| `write_file` | Creates a file, or replaces one that exists. |
|
||||||
|
| `edit_file` | Replaces one exact string, refusing when it appears more than once unless `all` is set. |
|
||||||
|
| `delete_path` | One file, or one empty folder. |
|
||||||
|
| `move_path` | Moves or renames, refusing when the destination exists. |
|
||||||
|
|
||||||
|
**Where it may read.** Everything under your home directory, except the credential stores below, plus anything you add under **Extra Readable Folders**.
|
||||||
|
|
||||||
|
**Where it may write.** `~/Downloads`, `~/Documents/Lam Agent`, the extension's own support directory, and anything you add under **Extra Writable Folders**. A file outside those is refused before any dialog appears, because there is nothing to approve.
|
||||||
|
|
||||||
|
**Every change is a dialog.** It names the tool's intent and the exact path, and states what is being replaced: the size and age of the file it is about to overwrite, or the substitution itself for an edit. Allow is the default button. A refusal comes back to the model as a refusal, and the model is told it is final, so it should say what it could not do rather than trying again.
|
||||||
|
|
||||||
|
The ledger behind that dialog is per answer, not per session. The second write to the same path while answering one question goes through without asking, and the next question asks again, because "yes, edit that file while we work on it" is not consent to whatever happens to it twenty minutes later. A delete or a move skips the ledger entirely and asks every time: an approval given for an edit is not consent to remove the thing that was just edited.
|
||||||
|
|
||||||
|
**A delete never recurses.** A folder with anything in it is refused, with the count, so the worst a misread path can do is bounded. A move is refused when the destination exists, so a move can never destroy the file it lands on.
|
||||||
|
|
||||||
|
**The denylist.** These are refused by name, on the resolved path, so a symlink is judged by where it lands: `~/.ssh`, `~/.gnupg`, `~/.aws`, `~/.config/gh`, `~/.netrc`, `~/.docker/config.json`, `id_rsa` and its siblings, `credentials.json`, `.env` and `.env.*`, and `.pem`, `.key` and `.p12` files anywhere. It is a guard, not a proof: a key file with a name nobody thought of is still readable. It exists because of the sentence below.
|
||||||
|
|
||||||
|
**Reading is disclosure.** Everything Lam reads is sent to the model provider with the conversation. That is the same disclosure the web tools already make, and it is why the read boundary is as considered as the write boundary, why the denylist exists, and why the prompt tells the model to read what the question needs and nothing more. If that trade is not one you want, leave File Access off: nothing else in this extension reads your machine, and the switch is the whole of the permission.
|
||||||
|
|
||||||
|
**Caps, stated in the output when they bite.** 200 lines and 8 KB per read, 200 entries per listing, 50 results and 8 levels deep per search, 5 MB per file. A binary file is refused with its size rather than returned as unreadable text, and a file too large to read is refused rather than streamed: this is a launcher, not a file manager.
|
||||||
|
|
||||||
|
**What the boundary does not do.** Containment is checked after resolving symlinks, but the check and the write are not one atomic step, so a symlink swapped in between them by another process running as you would not be noticed. That is a threat from a process that already has your account, and closing it needs handle-based operations rather than a path policy. It is written down here so nobody assumes otherwise.
|
||||||
|
|
||||||
## Auto-deletion
|
## Auto-deletion
|
||||||
|
|
||||||
Off by default. When it is on, Lam deletes a conversation once you have not used it for the configured period, and deletes the screenshots that belonged to it.
|
Off by default. When it is on, Lam deletes a conversation once you have not used it for the configured period, and deletes the screenshots that belonged to it.
|
||||||
@@ -330,6 +366,8 @@ src/
|
|||||||
retention.ts # what gets deleted, and when
|
retention.ts # what gets deleted, and when
|
||||||
cleanup.ts # the sweep + its notification
|
cleanup.ts # the sweep + its notification
|
||||||
history.ts # turns -> API messages
|
history.ts # turns -> API messages
|
||||||
|
files.ts # the path policy + every file operation
|
||||||
|
approval.ts # the approval dialog + its per-answer ledger
|
||||||
location.ts # time/place, derived locally
|
location.ts # time/place, derived locally
|
||||||
thread.ts # turns -> flat message list
|
thread.ts # turns -> flat message list
|
||||||
markdown.ts # messages -> detail-panel markdown
|
markdown.ts # messages -> detail-panel markdown
|
||||||
@@ -418,7 +456,7 @@ npm test
|
|||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- **Actions.** Opening links, clipboard access, launching apps. These need an approval step first. An assistant that can silently act on your machine is a different risk profile from one that answers questions, and this release does not pretend otherwise.
|
- **Actions.** Opening links, clipboard access, launching apps. This release ships the approval step they were waiting for, with the file tools, so the remaining work is the actions themselves rather than the permission model under them.
|
||||||
- **`@` mentions** for files and clipboard content.
|
- **`@` mentions** for files and clipboard content.
|
||||||
- **Per-conversation model** overrides.
|
- **Per-conversation model** overrides.
|
||||||
- **Annotations on a snip.** The capture backends are there, but drawing on the result before sending it needs a second tool and a wait for the editor to save.
|
- **Annotations on a snip.** The capture backends are there, but drawing on the result before sending it needs a second tool and a wait for the editor to save.
|
||||||
|
|||||||
Reference in New Issue
Block a user