diff --git a/AGENTS.md b/AGENTS.md index 58dc87f..22c2bb1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -69,7 +69,7 @@ needed repair. Two things worth knowing anyway: ## Windows: things that are not obvious -**`node:net` cannot bind a named pipe.** `createServer().listen('\\\\.\\pipe\\x')` +**`node:net` cannot bind a named pipe.** `createServer().listen('\\\\\\\\.\\\\pipe\\\\x')` mangles the path and refuses. Use `Bun.listen({ unix })`, which inspects the string and takes its named-pipe branch โ€” the same call that binds a Unix socket. This is already the production path (`src/panel-server.ts`); the smoke fixture rediscovered @@ -85,7 +85,7 @@ what it can watch, while the server's log says it answered. **`new URL(import.meta.url).pathname` is POSIX-shaped on Windows.** It yields `/C:/Users/...`, and `resolve(dirname(that))` reads it as a directory below the -current drive: `C:\C:\Users\...`. Use `fileURLToPath`. +current drive: `C:\\C:\\Users\\...`. Use `fileURLToPath`. **`powershell.exe` is not reliably on `PATH`.** A POSIX-flavoured parent (Git Bash, MSYS) hands the child a `PATH` that often omits `System32`. Resolve it from @@ -101,12 +101,14 @@ screen. That is why the Windows screen reader streams from one long-lived child (`streamingScreenTextReaderFactory`) instead of using the shared poll loop. If you add a Windows sensor, do the arithmetic before choosing the shape. -**FIFO-backed capabilities do not work on Windows.** The capture gate, terminal -commands, and editor saves each need an absolute FIFO path, and `validateFifoPath` -judges by POSIX rules on every host โ€” deliberately, because the config file is one -dialect everywhere. A Windows run therefore cannot enable pause control, terminal -observation, or editor saves at all. Making them work means named-pipe support in -`fifo-line-reader.ts`, not a looser validator. +**FIFO-shaped capabilities on Windows use Named Pipes.** The capture gate, terminal +commands, and editor saves were originally POSIX FIFO-only, but `mkfifo` has no +Windows equivalent. They now share the transport seam in `src/endpoint-transport.ts`: +an endpoint string names its own transport by spelling. A POSIX-absolute path is tailed +as a file, while a Windows named pipe (`\\\\.\\pipe\\` or `//./pipe/`) is served +by `Bun.listen({ unix })` as a pipe listener (the same transport `parsePanelPipeName` +accepts for the panel wire). A hook or alias connects, writes one newline-terminated line, +and closes. **Browser page reading is macOS-only.** There is no Windows or Linux implementation; the synthetic backend covers the code path anywhere. @@ -166,6 +168,13 @@ the panel ships no bundle), because a run that asked to be interrupted and silently was not looks exactly like one whose daemon is asleep. `off` is the explicit decline, and it wins on every host. +**Notices toast only when the agent acts.** The resident agent wakes on every +surviving event, and models often narrate "no action needed" instead of staying +silent. The watch loop gates notifications on `ResidentSession.actedInLastTurn()` +(latched on side-effecting tool calls like bash/edit/write, ignoring read-only +orientation like `query_recent_events` and `read`), so passive narration stays in +the panel and JSON log without ringing the desktop doorbell. + ## The tray, and what Slint 1.17 will and will not tell you **The feature is off by default in this repository, and on by default in Slint.** diff --git a/README.md b/README.md index d1d51a5..12f29a9 100644 --- a/README.md +++ b/README.md @@ -1,709 +1,284 @@ -# Squid +# ๐Ÿฆ‘ Squid โ€” The Ambient Desktop AI Assistant -Squid is a desktop assistant prototype built around the Pi agent runtime. It watches -what happens on the machine, projects it into a world state, and hands that state to -one long-lived agent that decides for itself what to do. +

+ An ambient, persistent desktop companion with perception and memory.
+ Observes your workspace quietly. Synthesizes context deeply. Acts decisively. Governed by you. +

-```text -activity event -> append-only store -> episode-scoped world state -> resident agent -``` +

+ English โ€ข + ็ฎ€ไฝ“ไธญๆ–‡ +

+ +

+ Bun + Slint + Platform + Pi Agent + Tests + License +

+ +--- + +## ๐Ÿ’ก What is Squid? + +Most AI tools today are **passive chat boxes**: you hit an error, copy-paste terminal outputs, re-explain your project architecture for the tenth time, and coax the model into understanding what you were doing. + +**Squid flips this paradigm.** + +Squid is an **ambient, long-lived desktop AI companion** built on the [Pi agent runtime](https://github.com/earendil-works/pi-ai). Instead of waiting for you to copy-paste context, Squid quietly runs in your background and perceives your workstation activity in real time: +- The project files you save in your editor +- The commands you run in your shell and their exit codes +- The active window, focused UI control, and selected text on your screen +- The API reference or GitHub issue you are reading in your browser + +Squid continuously distills these observations into a coherent **episodic world state**. When things go wrong or when assistance is needed, Squid already knows the full story โ€” stepping in with timely insights and actions, while remaining strictly under your control. + +--- + +## ๐ŸŒŸ A Day with Squid: Real-World Scenarios + +### 1. โšก Terminal Errors? No More Copy-Pasting Stacks +You run a test suite or deployment command in your terminal (`cargo test`, `pytest`, `npm run build`) and hit a compiler error or assertion failure. +> **Without Squid**: You select text in the terminal, switch windows to a browser or chat window, paste the trace, and type *"why did this fail?"*. +> +> **With Squid**: Squid hooks terminal completions. It already knows the command, the failure exit code, and the files you edited right before running it. It synthesizes the root cause immediately without you copying a single line. + +### 2. ๐Ÿ” Cross-Window Context: It Connects the Dots +You browse an open-source documentation site in Chrome, then switch back to VS Code to implement a function. +> **With Squid**: Squid notices the active browser tab URL and the code you are saving. When asked for suggestions or generating completions, it pulls context from the exact documentation page you were just looking at. + +### 3. ๐Ÿ”• Zero Distraction & Smart Notifications +Nobody likes an AI that interrupts every three seconds like an over-eager assistant. +> **With Squid**: Squid operates under an explicit *permission to do nothing* philosophy. It only triggers a desktop toast notification (Linux `notify-send`, Windows `Shell_NotifyIcon`, macOS `osascript`) **when it has actually executed a real action or file change**. If it is merely reading, thinking, or deciding no action is required, it stays 100% quiet. -The agent is the subject, not a service Squid calls. Squid owns the senses โ€” capture, -history, projection โ€” and the agent owns the judgement and the acting. What the agent -may reach is governed by Pi's sandbox, which is why there is no tool registry, no -admission rule engine, and no confirmation surface in this repository. +### 4. ๐Ÿ›ก๏ธ Automatic Privacy Shields +You open your password manager or banking portal to grab an API token or verify a billing detail. +> **With Squid**: Squid detects the foreground application (`1Password`, `Bitwarden`, `KeePassXC`, or custom exclusions via `SQUID_EXCLUDE`). Screen and window capture immediately suspend until you switch back. Secrets, passwords, and tokens are scrubbed at the ingress before ever touching the database. -The store and the projection work with no model at all, so capture and replay stay -testable offline. Everything except the agent's own turns runs without one. +--- -## Requirements +## ๐Ÿš€ Quick Start -- Bun `1.4.2` or later -- A configured Pi model, for anything that starts a resident agent. The store, the - projection, and the whole test suite are offline. +### Option A: Pre-built Release (Recommended for Everyday Users) -## Commands +Get started in under 60 seconds with no compilers or complex dependencies required: + +1. Download the latest release zip for your OS from [GitHub Releases](https://github.com/salmonloop/squid/releases): + - **Linux**: `squid-x86_64-unknown-linux-gnu.zip` + - **Windows**: `squid-x86_64-pc-windows-msvc.zip` +2. Extract the archive. You will see two companion binaries: + - `squid-ui`: The high-performance desktop panel (double-click to open). + - `squid`: The background sensory engine (supervised automatically by `squid-ui`). +3. Launch `squid-ui`: + - It will open the sleek Fluent desktop panel and dock an icon in your system tray. + - On first launch, navigate to **SETTINGS** to connect your favorite LLM provider: + - **Cloud Providers**: OpenAI, Anthropic, DeepSeek, OpenRouter, etc. + - **Local & Private Models**: [Ollama](https://ollama.ai/), vLLM, LM Studio, or any OpenAI-compatible `/v1` endpoint. + +--- + +### Option B: Build from Source (For Developers & Contributors) + +#### Prerequisites +- [Bun](https://bun.sh) `1.4.2` or later +- [Rust](https://rustup.rs/) (Cargo 1.80+) ```bash +# 1. Clone the repository and install dependencies +git clone https://github.com/salmonloop/squid.git +cd squid bun install --frozen-lockfile -bun run typecheck -bun test -bun run build + +# 2. Build the native Slint UI desktop panel +cargo build --release --manifest-path ui/Cargo.toml + +# 3. Launch Squid bun run start -bun run dev -bun run history:delete -- /absolute/path/events.db -bun run watch:desktop -- /absolute/project/root -bun run watch:files -- /absolute/project/root -bun run watch:screen ``` -`bun run start` is the app: it watches the home directory, computes the platform -panel endpoint, finds the panel binary โ€” beside the executable in a release, in -`ui/target/release` from a checkout โ€” and needs no environment. `bun run dev` is a -forty-line demo that ingests three invented events and prints the world state they -build up; it starts no sensors and no agent. - -The screen backend defaults to whatever the host can actually read: UI Automation on -Windows, `xdotool` on Linux, the accessibility API on macOS. `SQUID_SCREEN_READER=synthetic` -asks for the built-in demo script by name, which is what the tests and an offline -demo want โ€” it is deliberately no longer what an unconfigured run gets, because demo -frames presented as observations of this machine are a lie nothing downstream can -see through. - -Squid speaks up wherever the host can show a notice. When a reply lands, what the -agent said also goes to the host's own notification mechanism, so a person who -closed the window still learns it spoke. `SQUID_NOTIFY=off` (or `"notify": "off"` -in the config file โ€” the settings form's switch) declines it; `desktop` says the -default out loud. Linux speaks through `notify-send`; Windows speaks through a -`Shell_NotifyIcon` balloon shown by a Windows PowerShell helper, which needs no -installer and no AppUserModelID โ€” the transient tray icon is its own identity, and -the toast is attributed to the messenger; macOS speaks through `osascript`, whose -bundle the notice is attributed to โ€” the same visible-messenger trade, taken for the -same no-installer reason. A host with no backend starts quiet when -nothing was asked, and the refusals in `resolve-desktop-notice.ts` name what is -missing when `desktop` is asked for by name. - -### Visual panel smoke harness - -`bun run ui:smoke` runs the real release panel against an in-process fixture -speaking the production wire, waits for its window, saves PNGs, and drives its -buttons with real clicks. Build the binary first: +`bun run start` runs `src/squid-app.ts`: it defaults your home directory as the watch root, sets up the platform domain socket or Windows named pipe, launches the release panel binary, and connects the ambient sensory loop. -```bash -cargo build --release --manifest-path ui/Cargo.toml -bun run ui:smoke -# Or test a different binary: -bun run ui:smoke -- /absolute/path/to/squid-ui +--- + +## ๐ŸŽ›๏ธ The Native Desktop Experience + +Squid delivers a native desktop UI built in **Rust with Slint 1.17** that is lightweight, snappy, and respectful of system resources. + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿฆ‘ SQUID [_] [โ–ก] [โœ•] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ STATUS: Active ยท Watching /home/user/projects โ”‚ +โ”‚ Agent: Idle (Observed 14 events across 2 episodes) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [ACTIVITY EVIDENCE] โ”‚ +โ”‚ โ€ข VS Code: saved src/api-client.ts (2m ago) โ”‚ +โ”‚ โ€ข Terminal: `bun test` exited with code 0 (1m ago) โ”‚ +โ”‚ โ”‚ +โ”‚ [TRIGGER LEDGER] โ”‚ +โ”‚ Triggered: 3 | Duplicate: 8 | Excluded: 1 | Coalesced: 4โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [LLM REQUEST DEBUGGER] โ”‚ +โ”‚ #12 ยท deepseek-chat ยท 200 OK ยท 1,420ms ยท [View Preview] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [ SOURCES ] [ SETTINGS ] [ PAUSE RECORDING ] โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` -The assertions are the test and they are platform-free; what the machine has to -provide โ€” where a fixture can listen, how a window is found, how a control is -pressed, how the result is photographed โ€” is a `SmokeHost`, one file per platform. -A host it has none for is refused rather than approximated. - -**Linux** (`ui-smoke-host-x11.ts`) starts an isolated Xvfb and needs `Xvfb`, -`xdotool`, `xwininfo` (`x11-utils`), and `ffmpeg` โ€” the last grabs and crops the -window from the same `xwininfo` geometry that proved it viewable. Missing commands -are detected up front with an actionable error rather than assumed present. Because -the display is private to the run, no other Squid window can exist on it. - -**Windows** (`ui-smoke-host-windows.ts`) works the interactive desktop, since there -is no Xvfb equivalent a compiled winit window will draw into. It needs no measured -coordinates: controls are found by accessible name through UI Automation and clicked -at the rectangle it reports for them, which exercises the accessibility tree and -keeps hit-testing in the test in a way `InvokePattern.Invoke` would not. Sharing a -desktop costs three guards Xvfb makes unnecessary โ€” the window is matched by process -id so a leaked window from an earlier run cannot be tested instead, every click and -screenshot raises the panel and then verifies the pixel under the cursor still -belongs to it, and the run refuses a non-interactive session outright. It also reads -the status header's own words, so the header's claims are asserted rather than left -to the screenshots. - -Output goes to a unique temporary directory; every screenshot path is printed, and -the panel's own stdout and stderr land in `panel.log` beside them. The run polls for -the fixture subscription, the window, and each reply instead of sleeping; cleanup -terminates the panel, releases the host, and closes the endpoint on both success and -failure. - -The run asserts three things about a confirmation: **Later** does not answer, a -deferred decision can be brought back, and a settled prompt refuses a second -answer. On Linux it drives the buttons with clicks at window-relative coordinates, -and the reason is measured rather than preferred: under a bare Xvfb there is no -window manager to give the window focus, so a synthetic `Return` is delivered to -nothing. Each coordinate asserts *which* answer it produced, so a layout change that -moves a button makes the expected answer wrong or absent instead of silently hitting -a neighbour. - -Two things the harness therefore cannot claim. On Linux the header assertions the -Windows host makes now run too โ€” the X11 host reads the panel's AT-SPI cache off -the session bus (enabled by flipping `org.a11y.Status.IsEnabled`, whose order -relative to the panel launch does not matter). What it still cannot verify is the -no-default-button rule โ€” a `Return` that produces no answer here proves only that -no key arrived โ€” so that rule is enforced in `ui/confirmation.slint`, where no -button is `primary` and none is given focus. And it renders in whatever colour -scheme the display reports, which under Xvfb is the light one: there is no desktop -portal to ask, and Slint 1.17 exposes no environment override, so dark mode is -verified by reading `ui/theme.slint` rather than by screenshot. - -`typecheck` covers `src` and `tests` in two passes. The build config compiles only -`src`, so checking the suites needs its own config โ€” without it a test could assert -against a type that no longer existed and still pass. - -## Architecture - -### Contracts and history - -- `src/protocol.ts` โ€” versioned activity contracts plus compiled runtime validation at - every ingress boundary. `PredictionKind` survives as a *vocabulary*: a supplementary - tool declares which kind of work it does, so the agent can tell "run the tests" from - "look at the error". It no longer names a ranked proposal. -- `src/chronicle-ordering.ts` โ€” canonical event identity and stable replay ordering. -- `src/event-store.ts` โ€” the `EventStore` contract plus in-memory and JSONL append-only - stores, including recovery diagnostics for an interrupted final JSONL record. -- `src/sqlite-event-store.ts` โ€” append-only store on SQLite, with structural uniqueness - on both `eventId` and source-local event identity. -- `src/episode.ts` โ€” deterministic, time-gap-based episode projection. -- `src/world-state.ts` โ€” deterministic, ownership-safe reducer from events to world state. -- `src/state-digest.ts` โ€” canonical JSON over the full `WorldState` and its `sha256:` - digest. Identical across ordered, late, and restored replays. -- `src/squid-core.ts` โ€” serializes ingestion and state reduction. Holds the projection - and hands out only isolated snapshots. - -### The agent - -- `src/resident-agent.ts` โ€” the resident agent: one long-lived Pi session that is the - system's subject. Owns the lifecycle shell (prompt, observe, idle, abort, dispose) - and the system prompt. `observe(state)` updates the live sense buffer and appends a - breadcrumb to history without triggering a turn, so an event arriving mid-turn still - reaches the agent's view of the world. -- `src/agent-senses.ts` โ€” the agent's eyes and ears: a pure `(WorldState, tools) -> - string[]` rendering, injected before every LLM call. Line-oriented and labelled - rather than a JSON dump. It reports state and nothing else โ€” no instructions, no - "you should" โ€” because those belong in the system prompt. -- `src/tool-catalog.ts` โ€” the prompt-facing view of any *supplementary* capability, as - data. `ToolSummary` carries an id, a title, a category, the argument schema verbatim, - and a risk class. No `render`, no `guard`: Pi's own tools are the execution surface - and its sandbox is the safety boundary, so there is nothing here to build or police. -- `src/pi-session-factory.ts` โ€” the shared spine of every Squid-Pi session: resolve the - model, load resources with a system prompt, create the session, bind disposal. The - `history` option decides where the conversation is kept, and that is not a - convenience: **the agent's session history is the audit trail**. A durable run - continues the most recent session for its working directory, so a restart resumes the - agent's memory rather than waking it up amnesiac. - - Two model resolutions live here, and the distinction is load-bearing. - `resolvePiModel` is the **default** โ€” what a write falls back to and what - first-run offers as a suggestion. `resolveConfiguredModel` is the **answer**: - the id stored in Pi's `models.json` for the provider, which is what the - settings form writes and therefore what a session must open. Conflating them - made the form's `modelId` field save successfully and then stop the agent from - starting, because the lookup used a default the write had just replaced. An - explicit `modelId` still outranks both โ€” a caller that knows which model it - means is not second-guessed by a file. - -### Deciding what the agent reads - -A fast decision model (TypeSafe's [Jev](https://typesafe.ai), or a free -self-hosted replica like OpenJev/Kev) trims the resident agent's context before -each thought, so the model spends attention on what a future turn will actually -need. It answers the two questions the design started from โ€” "is this worth -recording" and "is this high value" โ€” as one: worth the agent's view, or not. - -The gate lives on the agent's context, **never on capture**. Capture is the -irreversible boundary (`capture-policy.ts`); the store keeps everything, and a -wrong "drop" here costs the agent a fact it can re-derive, never a lost -observation. This is the same posture the egress side already has: bound the -view, not the record. - -- `src/decision-curator-extension.ts` โ€” a headless inline extension wired into - the resident agent's session beside the heartbeat and LLM-log ones. On every - `context` event it groups messages into units, asks the decision model which - new units a future response needs (`noul` questions), and drops the ones it - says no to. Every failure path fails **open**: no key stored, any HTTP - error, a timeout, or the switch turned off all leave the context untouched, - so the agent never loses a turn to it. Off is `on`-by-default with a visible - switch, per this repo's rule. -- `src/jev-curator/` โ€” the decision logic itself, vendored near-verbatim from - the MIT `pi-jev-context-curator` package with the TUI stripped and config - injected. See `src/jev-curator/README.md` for exactly what changed and why it - is in-tree rather than a dependency. - -The curator is configured through four settings that all appear in the panel's -settings form under **Decision curation** and all read from the merged env, so -either an environment variable or the GUI works. The decision model has no -provider, credential, or login of its own: it declares the *chat* model's -provider, so Pi resolves the one credential the run already has โ€” the same -`auth.json` entry the chat model's requests carry, with no second key anywhere. -The extension registers Pi's own `typesafe-system-one` classifier transport -onto that provider at session start, so even a models.json-only provider -learns to speak the `/v1/systemone` wire. +- **Ambient Status Card**: Shows what Squid is watching right now, current activity highlights, and agent status (Idle, Thinking, or Acting). +- **System Tray Keepalive**: Closing the window puts it away to the system tray rather than killing the process. Right-click the tray icon to quickly Pause/Resume, Open Panel, or Quit. +- **LLM Request Debugger & Preview**: Inspect outbound LLM calls, latencies, status codes, and click **Details** to open a scrubbed, sanitized prompt preview modal (`preview`) to see exactly what context was sent. +- **Live Trigger Ledger**: Tracks decisions and filters in real-time (`triggered`, `duplicate`, `excluded`, `coalesced`). +- **Confirmation Modals**: Consequential actions prompt an explicit confirmation modal before executing. You can approve, decline, or defer ("Later"). + +--- + +## โš™๏ธ Configuration Reference + +Squid can be configured via the **SETTINGS** tab in the desktop UI, through JSON configuration files (`~/.config/squid/settings.json`), or using environment variables. + +### General & Runtime +| Environment Variable | Config Key | Default | Description | +|---|---|---|---| +| `SQUID_WATCH_ROOT` | `watchRoot` | `$HOME` | Directory watched by the filesystem sensor. | +| `SQUID_PANEL_SOCKET` | `panelEndpoint` | Auto | IPC endpoint (`/tmp/squid-panel.sock` or `\\.\pipe\squid-panel`). | +| `SQUID_NOTIFY` | `notify` | `desktop` | Notification mode (`desktop` or `off`). Only alerts when the agent acts. | + +### Model & Decision Curation +| Environment Variable | Config Key | Default | Description | +|---|---|---|---| +| `SQUID_MODEL_PROVIDER` | `modelProvider` | `default` | Provider name stored in Pi's `models.json` / `auth.json`. | +| `SQUID_ATTENTION_WINDOW_MINUTES` | `attentionWindowMinutes` | `15` | Minutes of observation history preserved before sliding-window pruning. | +| `SQUID_DECISION_CURATOR` | `decisionCuration` | `on` | `on` / `off`. Enables fast classifier gate before LLM turns. Fails open on errors. | +| `SQUID_DECISION_BASE_URL` | `decisionCuratorBaseUrl` | Chat base URL | `/v1/systemone` endpoint for decision curation (e.g. TypeSafe Jev or local Kev). | +| `SQUID_DECISION_MODEL` | `decisionCuratorModel` | Auto | Decision model id (`jev-latest`, `openjev`, `kev-4b`, etc.). | +| `SQUID_DECISION_THRESHOLD` | `decisionCuratorThreshold` | `0.5` | Sensitivity threshold between `0.0` and `1.0`. | + +### Sensors & Privacy Controls +| Environment Variable | Config Key | Default | Description | +|---|---|---|---| +| `SQUID_SCREEN_READER` | `screenReaderBackend` | Host native | Screen reader backend: UI Automation (Windows), `xdotool` (Linux), `synthetic` (test). | +| `SQUID_SCREEN_POLL_MS` | `screenPollIntervalMs` | `1000` | Screen text polling frequency in milliseconds. | +| `SQUID_TERMINAL` | `terminalSource` | `off` | Enable terminal shell integration (`fifo`). | +| `SQUID_TERMINAL_FIFO` | `terminalSourcePath` | โ€” | Path to terminal FIFO or Windows Named Pipe (`\\.\pipe\squid-term`). | +| `SQUID_EDITOR_SAVES` | `editorSaveSource` | `off` | Enable editor save tracking (`fifo`). | +| `SQUID_EDITOR_FIFO` | `editorSavePath` | โ€” | Path to editor save FIFO or Windows Named Pipe (`\\.\pipe\squid-editor`). | +| `SQUID_BROWSER` | `browserSource` | `off` | Enable browser active tab tracking (`darwin` on macOS, or `synthetic`). | +| `SQUID_CONTROL` | `captureGateBackend` | `off` | Enable pause/resume gate (`fifo`). | +| `SQUID_CONTROL_FIFO` | `captureGatePath` | โ€” | Path to pause gate FIFO or Windows Named Pipe (`\\.\pipe\squid-control`). | +| `SQUID_EXCLUDE` | `excludeApplications` | โ€” | Comma-separated list of application names to exclude (e.g. `1Password,Vault`). | +| `SQUID_RETENTION_HOURS`| `retentionHours` | `off` | Hours of activity events retained in SQLite at startup. | + +--- + +## ๐Ÿ› ๏ธ Under the Hood: Architecture & Philosophy + +For engineers, contributors, and curious hackers, Squid is built with rigorous local-first and deterministic engineering principles. -```bash -# Free, local: run kev (or openjev) and point Squid at it. The decision call -# carries the chat provider's key; a keyless local replica answers without -# checking it, so nothing extra needs to be set. -SQUID_DECISION_BASE_URL=http://127.0.0.1:8009 \ -SQUID_DECISION_MODEL=kev-4b \ - bun run start - -# Turn it off entirely (the switch, not a variable, is the front door): -SQUID_DECISION_CURATOR=off bun run start +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Sensory Ingress Adapters โ”‚ +โ”‚ Filesystem Screen Text Terminal Hook Editor Saves โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ โ”‚ โ”‚ + โ–ผ โ–ผ โ–ผ โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Capture Policy (Exclusion & Credential Redaction) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Append-Only Event Store (SQLite WAL / In-Memory) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Deterministic Episode Segmentation & World State โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Context Filter: Attention Window (15m) + Decision Curator โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Resident Agent (Pi Session) โ”‚ +โ”‚ Judgement, Tool Invocations, Persistent History โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ โ”‚ + โ–ผ โ–ผ โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Desktop Toast โ”‚ โ”‚ Confirmation โ”‚ โ”‚ Slint Panel & โ”‚ +โ”‚ (Mutations) โ”‚ โ”‚ Dialog Modal โ”‚ โ”‚ System Tray โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` -| Setting (`SQUID_*`) | Config id | What it does | -| --- | --- | --- | -| `SQUID_DECISION_CURATOR` | `decisionCuration` | `on`/`off`. Unset is on; fails open to a no-op when no endpoint is reachable. | -| `SQUID_DECISION_BASE_URL` | `decisionCuratorBaseUrl` | The `/v1/systemone` endpoint. Defaults to the chat provider's own base URL; a local replica costs nothing. | -| `SQUID_DECISION_MODEL` | `decisionCuratorModel` | Model id: `jev-latest`, `openjev`, `kev-4b`, โ€ฆ | -| `SQUID_DECISION_THRESHOLD` | `decisionCuratorThreshold` | Keep threshold `0`โ€“`1`; higher trims more. | - -### Capture and the watch loop - -- `src/activity-source.ts` โ€” the source contract every capture adapter implements. -- `src/capture-policy.ts` โ€” ingress boundary for capture: `CapturePolicy`, - `PassthroughCapturePolicy`, and `RedactingCapturePolicy`, which strips credential - content before an event reaches the store. Capture is irreversible, so this narrows - what is *persisted*. -- `src/capture-exclusion.ts` โ€” `ExcludingCapturePolicy`, per-application exclusion at - the ingress boundary, wrapping redaction. The unit of exclusion is the **foreground**: - only `application_activated` carries a name, so an activation latches the answer and - every later event describing the foreground โ€” window title, focused control, selection, - page, keyframe โ€” is dropped until another activation says otherwise. Facts from watching - a path or a pipe pass through. A matched event is dropped outright and diagnostics carry - counts only: recording "excluded app X" would recreate exactly what the user asked to - hide. `resolveCaptureExclusion` fails closed on a malformed list. -- `src/prediction-runtime.ts` โ€” the capture boundary in front of the core: apply the - policy, then ingest. Nothing more. It used to own prediction requests, attempt - records, and an `admitAndClaim` linearization point, all of which assumed Squid - decided what happened next. -- `src/create-watch-runtime.ts` โ€” the store, core, and capture runtime every watch entry - point needs, built once. Carries no source-specific assumptions and touches no - `process.env`. -- `src/watch-session.ts` โ€” everything a watch entry point does apart from choosing - sources: retention, restore, agent creation, the ingest loop, and shutdown ordering. - Sources are a **list**, because what the agent can conclude is a function of the whole - projection โ€” telling "the tests just failed" from "a file changed" needs a terminal - source and a filesystem source feeding one core. Sources start one at a time and a - failure unwinds the ones already running; stop runs in reverse start order. -- `src/audit-text.ts` โ€” `sanitizeAuditText` and `describeErrorForAudit`, the deterministic - choke point for any text reaching a log: control characters stripped, credentials - redacted, fixed length ceiling. Load-bearing because an error message routinely quotes - the thing that failed โ€” a path, a window title, a tool's own stderr. -- `src/resolve-retention.ts` โ€” resolves `SQUID_RETENTION_HOURS`. Off by default, and a - malformed value fails startup rather than running a session whose deletion policy was - silently not applied. -- `src/retention-prune.ts` โ€” ranged deletion of captured events, shared by the startup - retention pass and `history:delete`. -- `src/history-delete.ts` โ€” `bun run history:delete`, the stated interface for deleting a - time range. Requires an absolute database path: deleting the wrong file's history is - not recoverable, so the command refuses to guess which file is meant. - -### Sources - -- `src/filesystem-source.ts` โ€” filesystem watcher emitting validated file activity for - one project root. -- `src/screen-text-source.ts` โ€” the `ScreenTextReader` seam, the neutral - `ScreenTextFrame` shape, and the adapter projecting frames onto host activity events. -- `src/screen-text-reader-macos.ts` โ€” macOS accessibility backend over `osascript`. - Real, darwin-only, not exercised by the offline suite. -- `src/screen-text-reader-synthetic.ts` โ€” scripted frame replay plus the built-in demo - script. Zero OS calls, so the screen-text path is reproducible anywhere. -- `src/resolve-screen-reader.ts` โ€” chooses a backend from `SQUID_SCREEN_READER` / - `SQUID_SCREEN_POLL_MS`, defaulting to whichever real backend the host has and - falling back to the demo script only where there is none. -- `src/screen-text-reader-linux.ts` โ€” `xdotool` backend; window identity only. -- `src/screen-text-reader-windows.ts` โ€” UI Automation backend over a long-lived - PowerShell child: focused control type, element name, and selection. -- `src/terminal-command-source.ts` โ€” the `TerminalCommandReader` seam and the adapter - projecting completed shell commands onto `terminal_command_completed`. - `classifyCommand` reduces a raw command line to one of a closed set of classes, and - that reduction *is* the capture boundary: the raw line is classified and discarded, - never emitted. A closed vocabulary rather than "the first word", because the first - word is user text โ€” `./deploy-to-prod-acme-corp.sh` names a client and - `psql postgres://user:pw@host/db` is a credential. `succeeded` is derived from the - exit code rather than trusted, and an unreadable exit code counts as failure. -- `src/endpoint-transport.ts` โ€” the transport seam the FIFO-shaped readers share: - an endpoint names its transport by spelling, POSIX-absolute path or Windows - named pipe, and one line-reader call serves either. The pipe dialect exists - because `mkfifo` has no Windows equivalent โ€” without it the three - FIFO-backed capabilities were unusable on the platform the product ships to. -- `src/terminal-command-reader-fifo.ts` โ€” reads one JSON completion per line from a path - a shell hook appends to. A file rather than a wrapper process, so a crashed - integration cannot take the user's terminal with it. The module doc carries a working - zsh hook. -- `src/resolve-terminal-reader.ts` โ€” resolves `SQUID_TERMINAL` / `SQUID_TERMINAL_FIFO` / - `SQUID_TERMINAL_POLL_MS`. Off by default: observing a shell is a capability a run asks - for. -- `src/browser-page-source.ts` โ€” the `BrowserPageReader` seam, the neutral - `BrowserPageFrame` shape, and the adapter projecting frames onto `browser_navigated`. - A frame whose URL yields no host is refused rather than given an invented host, and a - backend that cannot tell which window owns a tab omits the owner instead of guessing. -- `src/browser-page-reader-macos.ts` โ€” macOS accessibility backend over `osascript`: - owning pid, window title, active-tab URL in one poll. Darwin-only; the parser is - tested. -- `src/browser-page-reader-synthetic.ts` โ€” scripted page replay whose owner matches the - screen demo's final window. -- `src/resolve-browser-reader.ts` โ€” resolves `SQUID_BROWSER` / `SQUID_BROWSER_POLL_MS`. - Off by default. -- `src/editor-save-source.ts` โ€” the `EditorSaveReader` seam and the adapter projecting an - editor's own save reports onto `file_saved` with `source.kind: "ide"`. The only source - that reports *ownership* rather than observation: the editor names itself as writer, - so its saves carry a real `applicationId`. Frames are refused, never repaired. -- `src/editor-save-reader-fifo.ts` โ€” reads one save report per line from a path an editor - extension appends to. The module doc carries a VS Code snippet; no packaged extension - ships here. -- `src/resolve-editor-save-reader.ts` โ€” resolves `SQUID_EDITOR_SAVES` / `SQUID_EDITOR_FIFO` - / `SQUID_EDITOR_POLL_MS`. Off by default. -- `src/capture-gate-source.ts` โ€” the `CaptureGateReader` seam and the adapter projecting - control commands onto `capture_gate_changed`. The pause event is the trace that makes - a deliberate gap distinguishable from an idle machine. -- `src/capture-gate-reader-fifo.ts` โ€” reads one gate command per line from a path a shell - alias appends to. -- `src/resolve-capture-gate.ts` โ€” resolves `SQUID_CONTROL` / `SQUID_CONTROL_FIFO` / - `SQUID_CONTROL_POLL_MS`. Off by default; enabling it declares that this run can be - controlled. -- `src/path-containment.ts` โ€” whether a path resolves inside a root. -- `src/project-detect.ts` โ€” what a project declares it can run, read from its manifest. - -### Speaking up - -The senses are inputs; this is the one capability that talks. It exists because the -panel was the only surface Squid had, so an agent reply reached a row in a window -nobody had open and stopped there. - -- `src/desktop-notice.ts` โ€” the `DesktopNotifier` seam and the `DesktopNotice` - shape. A notice carries no action and no button on purpose: a consequential - answer belongs on the confirmation surface, which has one-answer-per-prompt - correlation, an expiry, and a decline on close. Delivery failure is a diagnostic, - never an exception โ€” the turn's work is already done and recorded. -- `src/desktop-notice-command.ts` โ€” the shared loop for every host that can be - asked from a child process: one delivery at a time with the newest notice winning - the slot, one diagnostic per failure transition, and a deadline on each delivery. - The deadline is not defensive: with no notification daemon, `notify-send` waits - out D-Bus's 120-second activation timeout before failing, which would otherwise - hold the slot through hundreds of turns and present as silence. -- `src/desktop-notice-linux.ts` โ€” the `notify-send` arguments. `--` terminates the - options before any text, because a summary beginning with a dash is otherwise - parsed as short flags, and a model summarising its work routinely starts with one. -- `src/desktop-notice-macos.ts` โ€” the `osascript` notice. The script is a constant - run handler and the notice arrives as two argv values after `--`, as data โ€” no - user text ever reaches the AppleScript parser. Notification Center attributes - the notice to osascript's own bundle; the same visible-messenger trade the - Windows balloon took, for the same no-installer reason. -- `src/desktop-notice-windows.ts` โ€” the Windows balloon, shown by a PowerShell - helper the seam resolves from `%SystemRoot%`. The script is a constant and the - notice travels on stdin as one JSON line decoded as UTF-8 โ€” no user text reaches - the PowerShell parser, and none of it is decoded in the OEM code page. The - helper outlives the system's ten-second balloon floor, then removes its icon. -- `src/resolve-desktop-notice.ts` โ€” resolves `SQUID_NOTIFY`. On by default wherever - the host has a backend, `off` the explicit decline on every host; unset on a host - with no backend starts quiet, while `desktop` asked for by name there refuses - rather than resolving to off: a run that asked to be interrupted and silently - was not is indistinguishable from a sleeping daemon. - -Where the popup appears is the desktop's decision, not Squid's. The notification -spec describes passive popups and says nothing about placement โ€” the daemon decides, -and it is also what honours do-not-disturb and keeps a missed notice in a history. -Drawing our own window in a chosen corner would give up all three, and on Wayland a -client cannot position its own window at all. - -### The tray - -`ui/tray.slint` puts a `SystemTrayIcon` beside the panel window โ€” a Slint 1.17 -element with real backends on all three platforms (`Shell_NotifyIcon` on Windows, -`NSStatusItem` on macOS, `StatusNotifierItem` over D-Bus on Linux), which is why it -costs no new crate on the ship target. - -It answers what the window cannot while it is buried under other windows: whether -Squid is running, and what it is doing. The hover text is the status card's own -sentence, published by `panel.slint` as `status-title` from the one expression in -`status.slint` that decides what Squid is doing, so the two surfaces cannot come to -different conclusions. The icon is drawn in Rust rather than shipped as a file: the -repository has no asset pipeline, and a dome with three tapering tentacles is small -enough that the code is the source. - -**Closing the window is putting it away, not ending the run.** A visible tray icon -keeps Slint's event loop alive by itself, so a hidden window is still a live panel -with a way back and a way out: the tray menu carries **Show Squid** and **Quit -Squid**. The menu was measured to export on Linux โ€” an earlier "the menu is empty -on Linux" verdict turned out to be the test stand-in's own doing (it answered the -panel's handshake so late that the tray never finished spawning, then reported the -empty menu it had caused), and once the stand-in answered first, the same panel -returned both entries. The whole chain was then measured end to end on a stand-in -host: real `Window::hide()` unmaps the panel while the process stays alive, a -dbusmenu click on "Show Squid" brings the window back, and a click on "Quit Squid" -ends the run โ€” quitting without first hunting for a window behind six others. - -Pause is not offered in the tray: it stays in the panel's Recording section, which -owns the prose that goes with it, because pause control is FIFO-backed and -unavailable on Windows entirely. - -### Entry points - -- `src/watch-desktop.ts` โ€” the only entry point observing more than one part of the - desktop: filesystem saves, foreground window text, and (opt-in) browser pages, - terminal commands, and editor saves into one core. -- `src/watch-files.ts` โ€” live filesystem ingestion; durable when `SQUID_DB` is set. -- `src/watch-screen.ts` โ€” live foreground-window-text ingestion. -- `eval/eval-agent.ts` โ€” the live corpus runner (`bun run eval:agent`): each scenario's - events project through a real core, and a fresh toolless resident agent is asked what - the user is doing; its answer prints beside the scenario's expected reading. A - hand-run harness โ€” live judgment is not a CI assertion. -- `src/main.ts` โ€” demo: three events through the core, printing the projection each one - builds. - -## The closed loop - -One ingest does three things: - -1. **Capture and project.** The capture policy runs first โ€” it is the last chance to - redact before history โ€” then `core.ingest` reduces the event into the projection. -2. **Observe.** `agent.observe(state)` updates the live sense buffer and appends a - one-line breadcrumb to the agent's session history. This happens for every event that - survives capture and changed something, including events arriving while a turn is - already running. -3. **Prompt.** The agent takes a turn. - -Turns are serialized. Two prompts overlapping on one session is not two opinions โ€” it is -one conversation with interleaved turns, and the agent would be reasoning about a -half-written history. Events keep arriving during the wait and land in the sense buffer, -so a busy agent sees the *latest* world when it next thinks rather than a queue of stale -ones. For a sensor stream that is the right trade: the newest observation is the useful -one. - -A duplicate event gets no turn โ€” it changed nothing, so there is nothing to think about. -An excluded event reaches neither history nor the agent's senses, and the log line says -only that something was excluded. - -The turn prompt deliberately carries no task. The world state and the tool catalog -arrive through the sense injection, so restating them would give the model two versions -of the same facts. What the prompt supplies is the one thing the injection cannot: -*permission to do nothing*. Without it, a model handed a fresh observation every few -seconds reads each one as a request for action, and the loop's steady state becomes -activity rather than attention. - -### Shutdown ordering - -`stop()` runs in exactly one order, and each step is needed for a different reason: - -1. **Sources**, in reverse start order, so a later source never emits into one already - torn down. -2. **In-flight agent turns**, while the session is still open. A turn cut off mid-flight - loses whatever the agent was doing, and its session history is the only record that - it happened. -3. **The agent.** -4. **The store**, last โ€” an in-flight ingest still needs somewhere to land. - -## Persistence - -Three `EventStore` implementations satisfy one contract. `all()` returns every appended -event exactly once, but **return order is not part of the contract** โ€” callers that need -a timeline apply `orderActivityEvents`, which is total on events with distinct -`eventId`s. `since()` compares epoch milliseconds on both sides, so timezone spellings -of the same instant are equal. - -`SqliteEventStore` stores the full event as JSON and rehydrates every read through -`parseActivityEvent`, so `ingestedAt`, `correlationId`, and sub-millisecond `occurredAt` -spellings survive a round trip. Its scalar columns are a lossy projection kept for -filtering and integrity only. A primary key on `eventId` and a unique index mirroring -source-local event identity make duplicates a structural error rather than a convention: -both raise `EventStoreConstraintError`. That is deliberately one error type rather than -two, because a row violating both constraints reports only the identity index's `UNIQUE` -violation โ€” the error code cannot tell "same event twice" from "identity reused by a -different event". - -`journal_mode = WAL` with `synchronous = NORMAL` means committed events survive process -death without an explicit checkpoint. They are **not** guaranteed to survive OS or power -loss โ€” that would need `synchronous = FULL`. - -`JsonlEventStore` has no production caller. It is kept because it exercises the contract -under a different failure model โ€” partial line writes and crash-tail repair โ€” which the -SQLite implementation never encounters. - -The watch entry points use `SqliteEventStore` when `SQUID_DB` names an absolute path, and -`InMemoryEventStore` otherwise. `restore()` runs before observing, so a restart rebuilds -the dedupe index and projection from history instead of re-ingesting it. `main.ts` is a -demo and stays in memory. - -A database inside the watched root would feed its own writes back into the timeline: each -append updates `-wal`, the watcher reports that write, and ingesting it appends again. -`sqliteWatchExclusions` returns the root-relative paths of the database and its two WAL -sidecars so the watcher skips them; it returns nothing when the database lives outside -the root, so an unrelated sibling path can never be excluded by accident. - -**The agent's history is persisted separately, by Pi.** Squid keeps one durable store of -its own โ€” the event timeline โ€” and does not maintain a second record of what the agent -did. That record is the Pi session, which is why a durable run continues its most recent -session rather than starting a fresh one. - -## Episode projection - -Episodes are derived, not stored: a gap longer than the threshold between consecutive -canonical events starts a new one. The projection is therefore a pure function of -history, and a replay reconstructs the same episodes without a second source of truth. - -## Capture policy: exclusion, pause, and retention - -Three independent controls, resolved before any sink opens so a typo fails startup rather -than silently running a session whose policy was never applied: +### 1. Ingress Boundary & Credential Redaction +All events enter via [`src/capture-policy.ts`](file:///home/ubuntu/.t3/worktrees/squid/t3-cf8f03cb/src/capture-policy.ts). `RedactingCapturePolicy` deterministically scrubs API keys, AWS tokens, GitHub credentials, PEM blocks, and passwords before anything touches SQLite or memory. `ExcludingCapturePolicy` halts observation whenever blacklisted apps take the foreground. -```bash -# Drop everything observed while a named application is in front, before the store. -SQUID_EXCLUDE='1Password,Vault' bun run watch:desktop -- /absolute/project/root +### 2. Append-Only SQLite Event Store & Stable Ordering +Events are stored in SQLite using WAL mode with `synchronous = NORMAL` ([`src/sqlite-event-store.ts`](file:///home/ubuntu/.t3/worktrees/squid/t3-cf8f03cb/src/sqlite-event-store.ts)). Events have canonical monotonic ordering ([`src/chronicle-ordering.ts`](file:///home/ubuntu/.t3/worktrees/squid/t3-cf8f03cb/src/chronicle-ordering.ts)), making offline replay, crash recovery, and state reconstruction 100% deterministic. -# Let the run be paused and resumed through a FIFO a shell alias appends to. -SQUID_CONTROL=fifo SQUID_CONTROL_FIFO=/tmp/squid-control bun run watch:screen +### 3. Episodic World State +Time gaps between bursts of activity delineate discrete episodes ([`src/episode.ts`](file:///home/ubuntu/.t3/worktrees/squid/t3-cf8f03cb/src/episode.ts)). Raw events are reduced into a compact `WorldState` snapshot ([`src/world-state.ts`](file:///home/ubuntu/.t3/worktrees/squid/t3-cf8f03cb/src/world-state.ts)). The agent inspects this world state rather than querying raw event logs. -# Delete history older than the bound, once, at startup. -SQUID_RETENTION_HOURS=24 SQUID_DB=/absolute/path/events.db bun run watch:files -- /root -``` +### 4. Attention Sliding Window & Fast Decision Curation +- **Attention Window** ([`src/attention-window-extension.ts`](file:///home/ubuntu/.t3/worktrees/squid/t3-cf8f03cb/src/attention-window-extension.ts)): Before each turn, observations older than `attentionWindowMinutes` (default 15m) are trimmed from the working context, preventing context exhaustion over days of uptime. +- **Decision Curator** ([`src/decision-curator-extension.ts`](file:///home/ubuntu/.t3/worktrees/squid/t3-cf8f03cb/src/decision-curator-extension.ts)): Uses a lightweight classifier (`/v1/systemone` via Jev or Kev) to filter non-essential turns. Fails open on timeout or network error so the agent never hangs. -Retention prunes *before* restore, so the projection is rebuilt from the history that -actually remains โ€” an event the policy deleted must not be re-ingested into world state. -The prune runs once per process start; there is no in-run timer, because a long-running -session would otherwise delete under the user's feet mid-episode. +### 5. Unified Cross-Platform Transport +[`src/endpoint-transport.ts`](file:///home/ubuntu/.t3/worktrees/squid/t3-cf8f03cb/src/endpoint-transport.ts) provides a uniform abstraction across operating systems: +- **Unix**: Domain sockets for IPC, FIFOs (`mkfifo`) for shell and editor hooks. +- **Windows**: Full support for Named Pipes (`\\.\pipe\squid-panel`, `\\.\pipe\squid-control`), powered by `Bun.listen({ unix })`. -Deleting a specific range is a separate, deliberate act: +### 6. Clean Shutdown Sequence +`stop()` executes in strict reverse dependency order: +1. Stop sensory sources (preventing late events during teardown). +2. Drain and complete in-flight agent turns. +3. Dispose resident agent session. +4. Close SQLite store and persist final state. -```bash -bun run history:delete -- /absolute/path/events.db \ - 2026-08-21T12:00:00.000Z 2026-08-21T13:00:00.000Z -``` +--- -Both bounds are inclusive, so an event landing exactly at the end of the range goes. - -## Testing - -`bun test` is offline and starts no model runtime. Every suite either exercises a pure -function or injects a seam: - -- The resident agent takes a `ResidentSessionFactory`, so its tests drive a recording - fake rather than a live Pi session. -- `startWatchSession` takes the same factory, which is what makes the closed loop - assertable end to end: an event arrives, the agent's senses are updated, and it is - given a turn โ€” in that order. -- The sense rendering and the tool catalog are pure functions of their inputs, so what a - model is told is asserted without loading a model. - -That last point is worth stating as a rule. A green gate cannot see a requirement that -was never wired: every module could typecheck and every other suite pass while the ingest -closure quietly never reached the agent. The loop tests exist to assert the wiring itself. - -The panel's supervision path is held to the same rule, because it was the clearest -example of the failure. `spawn_brain`, `supervise`, and the probe had no test caller at -all: the retry ladder and the crash window were covered as pure functions, while the -process handling around them rested on review. Three things are asserted against real -behavior now. `child_env` states the inherited environment as data, so the allowlist -that makes the settings file the authority โ€” a stray `SQUID_BROWSER` in the parent must -not outrank the form โ€” is checkable without a child. `supervise` runs actual children -that exit 1 and 2, proving a refused startup is not retried and a crash loop stops at -the guard. And the probe is exercised against a live Unix listener and a stale socket -file, since it is the double-start guard and a false positive there would put two brains -on one endpoint. - -## Continuous integration and releases - -Two workflows, and the split between them matters. `.github/workflows/ci.yml` runs the -gate on every pull request and every push to `main`: `typecheck`, `bun test`, and -`bun run build` on the TypeScript side, and `cargo fmt`, `cargo clippy -D warnings`, and -`cargo test` on the panel. Both halves run as separate jobs so a red run names which one -broke. The whole gate is offline and needs no secret, which is what lets it run on a -pull request from a fork. - -`.github/workflows/release.yml` triggers on a tag matching `v*.*.*-*` โ€” semver with a -mandatory pre-release suffix, so a tag claiming stability has to change that file first. -It re-runs the same gate on its own checkout, builds the panel in release profile, and -publishes a GitHub pre-release carrying the binary plus a `sha256` of it: +## ๐Ÿงช Developer Commands & Testing + +Squid features an extensive, offline-first automated test suite: ```bash -# Bump `version` in package.json and ui/Cargo.toml to the exact release, commit, -# then tag that commit. -git tag v0.1.0-alpha.1 -git push origin v0.1.0-alpha.1 +# Typecheck TypeScript (src/ and tests/ separately) +bun run typecheck + +# Run offline unit and integration test suite (810+ tests) +bun test + +# Build TypeScript output +bun run build + +# Run end-to-end automated UI smoke test +cargo build --release --manifest-path ui/Cargo.toml +bun run ui:smoke ``` -The tag is the version ledger. A `version` job reconciles it against the two manifests -before anything is built โ€” the tag minus its leading `v` must equal what `package.json` -and `ui/Cargo.toml` already say, or the run goes red in seconds and nothing ships. The -panel binary itself answers `--version` with the version compiled in from `Cargo.toml`, -so a downloaded binary can say which release it belongs to without trusting the filename. - -The gate runs again there rather than trusting the pull request's green run, because this -workflow builds the binary it uploads. A tag can be pushed at any commit; without its own -gate, the release job would be publishing an artifact built from code nothing had tested. -`workflow_dispatch` runs everything except the publish step, which is how the build gets -proven on a clean runner without spending a tag on it. - -Only the publish job holds `contents: write`, and it shells out to the `gh` already on -the runner instead of delegating that token to a third-party action. Every action that -is third-party is pinned by commit SHA. This is the argument `ui/Cargo.toml` makes about -the panel's own dependencies, applied to the pipeline that signs its releases. - -The published assets are one zip per platform โ€” `x86_64-unknown-linux-gnu` and -`x86_64-pc-windows-msvc`, each with a `sha256` beside it. The zip holds two -binaries side by side: `squid`, the watch session compiled to a standalone -executable, and `squid-ui`, the panel. The Linux panel needs `libfontconfig1`, -`libfreetype6`, and `libxkbcommon0` at runtime โ€” femtovg and winit link them, and -the workflow installs the matching `-dev` packages to build. The Windows build -needs no runtime packages; its panel transport is a named pipe -(`SQUID_PANEL_SOCKET=\\.\pipe\squid-panel`), where Unix uses a filesystem socket -โ€” the protocol document names the spelling each platform accepts, and why the -pipe carries no access boundary the way the socket's file mode does. - -**Unpack the zip and open `squid-ui`; that is the whole download.** It needs -no arguments and is the recommended way in. With `SQUID_PANEL_SOCKET` unset the -panel computes the platform endpoint, makes a short probe to avoid a double -start, then starts the sibling `squid` beside it. The supervised brain watches -the user's home directory by default, writes an empty Squid settings file only -when none exists yet, and leaves every business setting to that file โ€” no parent -environment silently wins over what the form saves. - -With no model configured, the panel starts the sensors in `needs configuration` -mode. Fill the model base URL, model id, and API key in **SETTINGS**; the key goes -only to Pi's `models.json`/`auth.json` and is never displayed back. **Model -provider** names the key those two files are stored under, so it lives in -Squid's config file rather than in Pi's โ€” storing a key name is not keeping a -copy of what it addresses โ€” and it takes effect on restart, because it decides -which entry the settings service reads before that service is built. Everything -else lives in Squid's own config file. A saved setting that says `restart` takes -effect when you click **Restart brain**; the brain drains in its ordinary order -and the panel starts its replacement. Three crashes inside a minute stop the -retries and leave the service reading `failed` โ€” a broken install should not -burn a battery being respawned. That is terminal for a round of supervision, -not for the panel: **Start brain** takes the place of Restart there and begins -a fresh round with a clean crash log, because the user asking again is the -signal the guard was holding out for. Restart is hidden in that state, since -it writes to a socket with no brain on it and an unconnected write is dropped. The **SOURCES** list independently starts -and stops observers, except the last running observer (the session needs one) -and the capture gate (which is a pause switch, not a source). - -The three FIFO-backed capabilities โ€” capture gate, terminal commands, editor -saves โ€” each take a backend and the endpoint path it reads, and both halves are -rows in the same form. They have to be: each backend offers exactly one value, -and that value is refused at startup without its path, so a form that offered -the backend alone could write a setting that stopped the brain from starting โ€” -and with no brain there is no dump, so no form to undo it in. An endpoint names -its transport by its spelling โ€” a POSIX-absolute path is tailed as an -append-only file, the Windows named-pipe spelling `\\.\pipe\` is served -as a pipe listener โ€” and the path rows accept both dialects on every host, -because the config file is written in one dialect no matter which platform -reads it. The pipe dialect is what makes the three capabilities exist on -Windows at all: `mkfifo` has no equivalent there. - -You can still run `squid` by hand. Its panel child attaches normally. You can -also start `squid-ui` with a non-empty `SQUID_PANEL_SOCKET` to attach to a session -someone else started. An endpoint set to an empty string is refused rather than -guessed at, because that is a launcher losing a value, not a panel-first request. -If neither a live session nor the sibling `squid` binary exists, the panel names -what it looked for and exits 2. A window that cannot be created โ€” no display, no -OpenGL โ€” exits 3. On Windows these failures also appear in a dialog when the -panel owns its short-lived double-click console. - -## What is not yet implemented - -- No supplementary tools ship. The agent works with Pi's built-ins plus its own - `query_recent_events` sensory-history tool; `ToolSummary` is the shape a run would - describe extras in, and the catalog renders a stated absence when none arrive. -- Browser page reading is darwin-only; the synthetic backend covers the path anywhere. - Screen text reads on all three platforms, and no longer reads the same amount on - each. Windows runs full UI Automation โ€” focused control type, element name, and - selected text where the element exposes a text pattern โ€” from one long-lived - PowerShell child (`streamingScreenTextReaderFactory`), because a per-sample spawn - costs twenty times the automation query it wraps. macOS and Linux still share the - poll loop (`screen-text-reader-subprocess.ts`), and Linux on `xdotool` still - reports window identity only: its focus roles and selection text wait for an - AT-SPI backend. What a Windows selection actually yields depends on the - application โ€” a native edit control answers fully, Chromium exposes the pattern - and ignores a programmatic selection. -- The evaluation corpus (`eval/corpus.ts`) is a start, not an endpoint: five scenarios - with a deterministic CI consumer and a live runner (`bun run eval:agent`) that prints - a real agent's reading beside each expected one. What no run has yet measured is a - second model: on the current default, the agent reads every scenario's state correctly - but answers in conversation rather than the requested reading phrase, so the runner - records the mismatch and a human judges. - -## Archived documents - -`docs/architecture-hardening-plan.md` describes the architecture this one replaced โ€” -a five-step dispatch pipeline over one-shot model calls, with a prediction ledger -recording why each one ran. Kept for the reasoning, not as a description of the code. +### Visual UI Smoke Harness (`bun run ui:smoke`) +The smoke harness validates that the compiled `squid-ui` binary renders and behaves correctly without human intervention: +- **On Linux**: Spawns an isolated `Xvfb` display, simulates mouse clicks with `xdotool`, and inspects AT-SPI accessibility trees over an isolated private D-Bus session. +- **On Windows**: Uses Windows UI Automation to locate controls by accessible name, executes native clicks, and verifies button hit-testing and status card updates. + +--- + +## ๐Ÿ”’ Security & Privacy Guarantees + +1. **100% Offline Core**: Ingestion, state projection, database storage, and the test suite run completely offline with zero telemetry. +2. **Credential Redaction**: Passwords, API tokens, and authorization headers are expunged before persistence. +3. **Application Exclusion**: Sensitive applications (password managers, banking sites) are dropped immediately at the ingress. +4. **Instant Pause Gate**: You can pause and resume recording at any time via the UI, a shell shortcut, or a pipe write. +5. **Human in the Loop**: Sensitive actions require explicit confirmation through modal dialogs. + +--- + +## ๐Ÿ“„ License + +This project is licensed under the [MIT License](LICENSE). diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 0000000..b87cdd4 --- /dev/null +++ b/README_zh.md @@ -0,0 +1,284 @@ +# ๐Ÿฆ‘ Squid โ€” ๅ…จๅคฉๅ€™ๆกŒ้ข็บง่‡ชไธป AI ๆ™บ่ƒฝไฝ“ + +

+ ๅ…ทๆœ‰ๅ…จๅคฉๅ€™ๆ„Ÿ็ŸฅไธŽๆŒ็ปญ่ฎฐๅฟ†็š„่‡ชไธปๆกŒ้ขไผดไพฃ
+ ้™้ป˜ๆ„Ÿ็ŸฅๅทฅไฝœๅŒบ ยท ๆทฑๅบฆ็†่งฃไธŠไธ‹ๆ–‡ ยท ๅ…ณ้”ฎๆ—ถๅˆป่‡ชไธปๅ†ณๆ–ญ ยท ไธ€ๅˆ‡ๅ…จ็”ฑไฝ ๆŽŒๆŽง +

+ +

+ English โ€ข + ็ฎ€ไฝ“ไธญๆ–‡ +

+ +

+ Bun + Slint + Platform + Pi Agent + Tests + License +

+ +--- + +## ๐Ÿ’ก ไธบไป€ไนˆ้œ€่ฆ Squid๏ผŸ + +็Žฐๅœจ็š„็ปๅคงๅคšๆ•ฐ AI ็ผ–็จ‹ไธŽๆกŒ้ขๅŠฉๆ‰‹้ƒฝๆ˜ฏ**่ขซๅŠจ็š„ๅฏน่ฏๆก†**๏ผš +ไฝ ๅœจ็ปˆ็ซฏ่ท‘ๆต‹่ฏ•้‡ๅˆฐๆŠฅ้”™๏ผŒๅฟ…้กปๆ‰‹ๅŠจ้€‰ไธญๅคๅˆถ้”™่ฏฏๅ †ๆ ˆ๏ผŒๅˆ‡ๅˆฐๆต่งˆๅ™จๆˆ–่Šๅคฉ่ฝฏไปถ๏ผŒ็ฌฌๅๆฌกๅ‘ๅฎƒ่งฃ้‡Š้กน็›ฎๆžถๆž„๏ผŒๅ†ๅฐๅฟƒ็ฟผ็ฟผๅœฐๆ็คบๅฎƒโ€œๅธฎๆˆ‘็œ‹็œ‹่ฟ™ๆ˜ฏไธบไป€ไนˆโ€ใ€‚ + +**Squid ๅฝปๅบ•ๆ‰“็ ดไบ†่ฟ™็งๅ‰ฒ่ฃ‚็š„ไฝ“้ชŒใ€‚** + +Squid ๆ˜ฏไธ€ไธช**้•ฟ้ฉปๆœฌๅœฐใ€ๅ…ทๅค‡่‡ชไธปๆ„Ÿ็Ÿฅ่ƒฝๅŠ›็š„ๆกŒ้ข AI ไผดไพฃ**๏ผŒๅŸบไบŽ [Pi Agent ่ฟ่กŒๆ—ถ](https://github.com/earendil-works/pi-ai) ๆž„ๅปบใ€‚ๅฎƒไธๅ†่ขซๅŠจ็ญ‰ๅพ…ไฝ ๅ–‚้€ไธŠไธ‹ๆ–‡๏ผŒ่€Œๆ˜ฏๅœจๅŽๅฐ้™้ป˜ใ€่ฝป้‡ๅœฐๆ„Ÿ็Ÿฅไฝ ็š„ๆ—ฅๅธธๅผ€ๅ‘ๅทฅไฝœ๏ผš +- ไฝ ๅœจ็ผ–่พ‘ๅ™จไธญไฟๅญ˜็š„้กน็›ฎไปฃ็ ๆ–‡ไปถ๏ผ› +- ไฝ ๅœจ็ปˆ็ซฏ้‡Œๆ‰ง่กŒ็š„ๅ„็งๅ‘ฝไปคไธŽๅ…ถ้€€ๅ‡บ็Šถๆ€็ ๏ผ› +- ๅฝ“ๅ‰ๆกŒ้ขๅ‰ๅฐ็š„ๆดป่ทƒ็ช—ๅฃใ€่š็„ฆๆŽงไปถไธŽ้€‰ไธญๆ–‡ๆœฌ๏ผ› +- ไฝ ๅœจๆต่งˆๅ™จ้‡ŒๆญฃๅœจๆŸฅ้˜…็š„ API ๆ–‡ๆกฃๆˆ– GitHub Issue ่ฎจ่ฎบใ€‚ + +Squid ๅฐ†่ฟ™ไบ›ๆŒ็ปญ็š„ๆ„Ÿๅฎ˜ไฟกๅท่šๅˆไธบ**่ฟž่ดฏ็š„ๅœบๆ™ฏๅŒ–ไธ–็•Œ็Šถๆ€๏ผˆEpisodic World State๏ผ‰**ใ€‚ๅฝ“้‡ๅˆฐ้”™่ฏฏๆˆ–้œ€่ฆๅธฎๅŠฉๆ—ถ๏ผŒSquid ๅทฒ็ป็Ÿฅๆ™“ๆฅ้พ™ๅŽป่„‰๏ผŒๆ— ้œ€ไฝ ๅšไปปไฝ•ๅคๅˆถ็ฒ˜่ดดๅณๅฏ่‡ชไธป็ป™ๅ‡บ่ฏŠๆ–ญๆˆ–่พ…ๅŠฉๆ‰ง่กŒ โ€”โ€” ๅนถไธ”ๅ…จ่ฟ‡็จ‹ไธฅๆ ผๅ—ไฝ ๆŽŒๆŽงใ€‚ + +--- + +## ๐ŸŒŸ ๆ—ฅๅธธไฝฟ็”จๅœบๆ™ฏ๏ผˆA Day with Squid๏ผ‰ + +### 1. โšก ็ปˆ็ซฏๆŠฅ้”™๏ผŸๅ‘Šๅˆซๆ‰‹ๅŠจๅคๅˆถ้•ฟ็ฏ‡ๅ †ๆ ˆ +ไฝ ๅœจ็ปˆ็ซฏ่ฟ่กŒ `cargo test`ใ€`pytest` ๆˆ– `npm run build`๏ผŒๆต‹่ฏ•ๅคฑ่ดฅๅนถๆŠ›ๅ‡บๅคง้‡ๆŠฅ้”™ใ€‚ +> **ไผ ็ปŸๅšๆณ•**๏ผšๅœจ็ปˆ็ซฏ้‡Œ้•ฟๆŒ‰้ผ ๆ ‡้€‰ไธญใ€ๅคๅˆถใ€ๅˆ‡็ช—ๅฃ็ฒ˜่ดด็ป™ AIใ€ๆ•ฒ้”ฎ็›˜้—ฎโ€œไธบไป€ไนˆๆŒ‚ไบ†โ€ใ€‚ +> +> **Squid ไฝ“้ชŒ**๏ผšSquid ้€š่ฟ‡็ปˆ็ซฏ้’ฉๅญๅฎžๆ—ถๆ„Ÿ็Ÿฅๅˆฐ่ฏฅๅ‘ฝไปคๅคฑ่ดฅๅŠ้ž้›ถ้€€ๅ‡บ็ ๏ผŒๅนถ่‡ชๅŠจๅ…ณ่”ไฝ ๅˆšๅˆšๅœจ็ผ–่พ‘ๅ™จไธญไฟฎๆ”น็š„ๆ–‡ไปถใ€‚ๅฝ“ไฝ ้œ€่ฆๆ—ถ๏ผŒๅฎƒๅทฒ็†ๆธ…ๆ นๆœฌๅŽŸๅ› ๏ผŒ็”š่‡ณ็›ดๆŽฅๅ‡†ๅค‡ๅฅฝไฟฎๆ”น่กฅไธใ€‚ + +### 2. ๐Ÿ” ่ทจ็ช—ๅฃ่”ๅŠจ๏ผšๆดžๆ‚‰ไฝ ็š„็œŸๅฎžไธŠไธ‹ๆ–‡ +ไฝ ๅœจๆต่งˆๅ™จ้‡ŒๆŸฅ้˜…ไธ€ไปฝๅผ€ๆบๆก†ๆžถ็š„ๆœ€ๆ–ฐๆŽฅๅฃๆ–‡ๆกฃ๏ผŒๅˆ‡ๅ›ž VS Code ๅผ€ๅง‹ๅฎž็Žฐๅ…ทไฝ“ๅŠŸ่ƒฝๅนถไฟๅญ˜ใ€‚ +> **Squid ไฝ“้ชŒ**๏ผšSquid ่ƒฝๅŒๆ—ถๆ•่Žทไฝ ๅˆš็œ‹็š„ๆ–‡ๆกฃ็ฝ‘้กต URL ๅ’Œไฟๅญ˜็š„ไปฃ็ ใ€‚ๅœจๆไพ›ไปฃ็ ๅปบ่ฎฎๆˆ–่งฃ็ญ”็–‘้šพๆ—ถ๏ผŒๅฎƒ็›ดๆŽฅ็ป“ๅˆไฝ ๆŸฅ้˜…็š„ๅ‚่€ƒ่ต„ๆ–™่ฟ›่กŒๅ›ž็ญ”๏ผŒไธๅ†็›ฒ็›ฎ็žŽ็Œœใ€‚ + +### 3. ๐Ÿ”• ็ปไธๆ‰“ๆ‰ฐ๏ผšๆ™บ่ƒฝ้™้ป˜้€š็Ÿฅ +่ฐ้ƒฝไธๆƒณ่ฆไธ€ไธชๆฏ้š”ไธ‰็ง’ๅฐฑ่ทณๅ‡บๆฅๅˆทๅญ˜ๅœจๆ„Ÿ็š„่ฏ็—จๅŠฉๆ‰‹ใ€‚ +> **Squid ไฝ“้ชŒ**๏ผšSquid ๆ‹ฅๆœ‰ๆ˜Ž็กฎ็š„โ€œๆ— ไบ‹ไธๆ‰ฐโ€ๅ“ฒๅญฆใ€‚ๅฎƒ้€š่ฟ‡็ณป็ปŸ้€š็Ÿฅไธญๅฟƒ๏ผˆLinux `notify-send`ใ€Windows `Shell_NotifyIcon` ๆฐ”ๆณกใ€macOS `osascript`๏ผ‰ๆ้†’ไฝ ๏ผŒไธ”**ไป…ๅฝ“ๆ™บ่ƒฝไฝ“ๅฎž้™…ๆ‰ง่กŒไบ†ไบง็”Ÿๅฝฑๅ“็š„ๆ“ไฝœ๏ผˆๅฆ‚ไฟฎๆ”นไบ†ๆ–‡ไปถๆˆ–ๆ‰ง่กŒไบ†ไฟฎๅคๅ‘ฝไปค๏ผ‰ๆ—ถๆ‰ไผšๅผนๅ‡บๆกŒ้ข้€š็Ÿฅ**ใ€‚่‹ฅๅฎƒๅชๆ˜ฏๅœจ้˜…่ฏปใ€ๆ€่€ƒๆˆ–ๅˆคๅฎšๆ— ้œ€ๅนฒ้ข„๏ผŒ็ณป็ปŸไฟๆŒ 100% ้™้ป˜ใ€‚ + +### 4. ๐Ÿ›ก๏ธ ๆ™บ่ƒฝ้š็ง้ฎ็ฝฉ๏ผš้‡ๅˆฐๅฏ†็ ่‡ชๅŠจโ€œ้—ญ็œผโ€ +ไฝ ้œ€่ฆๅˆ‡ๅˆฐๅฏ†็ ็ฎก็†ๅ™จๆŸฅๆ‰พๆ•ฐๆฎๅบ“ๅฏ†้’ฅ๏ผŒๆˆ–่€…ๆ‰“ๅผ€็ฝ‘้“ถๆŸฅ็œ‹่ดฆๅ•ใ€‚ +> **Squid ไฝ“้ชŒ**๏ผšSquid ่‡ชๅŠจๆฃ€ๆต‹ๅฝ“ๅ‰ๅ‰ๅฐ็ช—ๅฃใ€‚ไธ€ๆ—ฆ่ฟ›ๅ…ฅๆ•ๆ„Ÿ่ฝฏไปถ๏ผˆๅฆ‚ `1Password`ใ€`Bitwarden`ใ€`KeePassXC` ๆˆ–่‡ชๅฎšไน‰้ป‘ๅๅ•๏ผ‰๏ผŒๅฑๅน•ไธŽๆ–‡ๅญ—้‡‡้›†็ซ‹ๅณ่‡ชๅŠจๆŒ‚่ตทใ€‚ๅ„็ฑปๅฏ†็ ใ€API Token ๅœจ่ฟ›ๅ…ฅๆ•ฐๆฎๅบ“ๅ‰ๅฐฑๅทฒ่ขซๅบ•ๅฑ‚ๆญฃๅˆ™่กจ่พพๅผๅฝปๅบ•ๆธ…ๆด—๏ผŒ็ปไธ่ฝ็›˜ใ€‚ + +--- + +## ๐Ÿš€ ๆž้€ŸไธŠๆ‰‹ + +### ๆ–นๅผ A๏ผš้ข„็ผ–่ฏ‘็‰ˆๆœฌ่งฃๅŽ‹ๅณ็”จ๏ผˆๆ™ฎ้€š็”จๆˆท้ฆ–้€‰๏ผ‰ + +ๆ— ้œ€ๅฎ‰่ฃ…ๅคๆ‚็š„็ผ–่ฏ‘ๅ™จไธŽ็Žฏๅขƒ๏ผŒ60 ็ง’ๅ†…ๅฟซ้€Ÿ่ท‘่ตทๆฅ๏ผš + +1. ๅ‰ๅพ€ [GitHub Releases](https://github.com/salmonloop/squid/releases) ไธ‹่ฝฝ้€‚ๅˆไฝ ็ณป็ปŸ็š„ๅ‘ๅธƒๅŒ…๏ผš + - **Linux**๏ผš`squid-x86_64-unknown-linux-gnu.zip` + - **Windows**๏ผš`squid-x86_64-pc-windows-msvc.zip` +2. ่งฃๅŽ‹ๅŽ‹็ผฉๅŒ…๏ผŒไฝ ไผšๅพ—ๅˆฐไธคไธชๆ ธๅฟƒ็จ‹ๅบ๏ผš + - `squid-ui`๏ผš้ซ˜ๆ€ง่ƒฝๅŽŸ็”ŸๆกŒ้ข้ขๆฟ๏ผˆๅŒๅ‡ป็›ดๆŽฅ่ฟ่กŒ๏ผ‰ใ€‚ + - `squid`๏ผšๅŽๅฐๆ„Ÿ็ŸฅไธŽๅ†ณ็ญ–ๆ ธๅฟƒ๏ผˆ็”ฑ `squid-ui` ่‡ชๅŠจๆ‰˜็ฎก๏ผ‰ใ€‚ +3. ๅฏๅŠจ `squid-ui`๏ผš + - ๆกŒ้ขๅฐ†ๅผนๅ‡บ็ฒพ่‡ด็š„ Fluent ้ฃŽๆ ผๆŽงๅˆถ้ขๆฟ๏ผŒๅนถๅœจ็ณป็ปŸๆ‰˜็›˜ๅธธ้ฉปๅ›พๆ ‡ใ€‚ + - ้ฆ–ๆฌกไฝฟ็”จๆ—ถ๏ผŒ็‚นๅ‡ป้ขๆฟไธ‹ๆ–น็š„ **SETTINGS**๏ผŒ้…็ฝฎไฝ ๅธธ็”จ็š„ๅคงๆจกๅž‹ๆไพ›ๅ•†๏ผš + - **ไบ‘็ซฏไธปๆตๆจกๅž‹**๏ผšOpenAIใ€Anthropic Claudeใ€DeepSeekใ€OpenRouter ็ญ‰ใ€‚ + - **ๆœฌๅœฐ็งๆœ‰ๅŒ–ๆจกๅž‹**๏ผšๆ”ฏๆŒ [Ollama](https://ollama.ai/)ใ€vLLMใ€LM Studio ๆˆ–ไปปๆ„ๅ…ผๅฎน OpenAI ็š„ๆœฌๅœฐๆœๅŠกๆŽฅๅฃใ€‚ + +--- + +### ๆ–นๅผ B๏ผšไปŽๆบ็ ็ผ–่ฏ‘ไธŽ่ฐƒ่ฏ•๏ผˆๅผ€ๅ‘่€…ไธŽๆžๅฎข๏ผ‰ + +#### ็Žฏๅขƒ่ฆๆฑ‚ +- [Bun](https://bun.sh) `1.4.2` ๆˆ–ๆ›ด้ซ˜็‰ˆๆœฌ +- [Rust](https://rustup.rs/)๏ผˆCargo 1.80+๏ผ‰ + +```bash +# 1. ๅ…‹้š†ไปฃ็ ไป“ๅบ“ๅนถๅฎ‰่ฃ…ไพ่ต– +git clone https://github.com/salmonloop/squid.git +cd squid +bun install --frozen-lockfile + +# 2. ็ผ–่ฏ‘ๅŽŸ็”Ÿ Slint UI ้ขๆฟ +cargo build --release --manifest-path ui/Cargo.toml + +# 3. ๅฏๅŠจๅบ”็”จ +bun run start +``` + +`bun run start` ๅฐ†ๆ‰ง่กŒ `src/squid-app.ts`๏ผš่‡ชๅŠจๅฐ† `$HOME` ไฝœไธบ็›‘ๅฌๆ น็›ฎๅฝ•๏ผŒๅปบ็ซ‹่ทจๅนณๅฐ้€šไฟก็ฎก้“๏ผˆLinux/macOS ไธบ Unix Domain Socket๏ผŒWindows ไธบ Named Pipe๏ผ‰๏ผŒๆ‹‰่ตท UI ้ขๆฟๅนถๅฏๅŠจๅ…จๅฅ—ๆ„Ÿ็Ÿฅ็ณป็ปŸใ€‚ + +--- + +## ๐ŸŽ›๏ธ ๅŽŸ็”ŸๆกŒ้ขไบคไบ’ไฝ“้ชŒ + +Squid ้‡‡็”จ **Rust + Slint 1.17** ๆ‰“้€ ๅŽŸ็”ŸๆกŒ้ขๅฎขๆˆท็ซฏ๏ผŒ็ง’็บงๅ†ทๅฏ๏ผŒๆžๅบฆ่Š‚็œๅ†…ๅญ˜ไธŽ CPU ๅผ€้”€ใ€‚ + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿฆ‘ SQUID [_] [โ–ก] [โœ•] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ STATUS: Active ยท ๆญฃๅœจ็›‘ๅฌ /home/user/projects โ”‚ +โ”‚ ๆ™บ่ƒฝไฝ“็Šถๆ€: ็ฉบ้—ฒ (ๅœจ 2 ไธชๆ—ถ้—ดๆฎตๅ†…็ดฏ่ฎกๆ„Ÿ็Ÿฅ 14 ไธชไบ‹ไปถ) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [ๆดปๅŠจ่ฏๆฎ (EVIDENCE)] โ”‚ +โ”‚ โ€ข VS Code: ไฟๅญ˜ไบ† src/api-client.ts (2ๅˆ†้’Ÿๅ‰) โ”‚ +โ”‚ โ€ข ็ปˆ็ซฏ: `bun test` ๆ‰ง่กŒๅฎŒๆฏ•๏ผŒ็Šถๆ€็  0 (1ๅˆ†้’Ÿๅ‰) โ”‚ +โ”‚ โ”‚ +โ”‚ [่งฆๅ‘่ดฆๆœฌ (TRIGGER LEDGER)] โ”‚ +โ”‚ ่งฆๅ‘ๆ€่€ƒ: 3 | ๅŽป้‡ๅฟฝ็•ฅ: 8 | ้š็งๆŽ’้™ค: 1 | ้˜ฒๆŠ–ๅˆๅนถ: 4 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [ๅคงๆจกๅž‹่ฏทๆฑ‚่ฐƒ่ฏ•ๅ™จ (LLM DEBUGGER)] โ”‚ +โ”‚ #12 ยท deepseek-chat ยท 200 OK ยท 1,420ms ยท [ๆŸฅ็œ‹่„ฑๆ•้ข„่งˆ] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [ ๆ„Ÿ็Ÿฅๆบๅผ€ๅ…ณ ] [ ็ณป็ปŸ่ฎพ็ฝฎ ] [ ๆš‚ๅœๆ„Ÿ็Ÿฅ่ฎฐๅฝ• ] โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +- **ๅฎžๆ—ถ็Šถๆ€ๅก็‰‡**๏ผš็›ด่ง‚ๅฑ•็คบ Squid ๅฝ“ๅ‰ๅœจ่ง‚ๅฏŸไป€ไนˆ๏ผŒไปฅๅŠๆ™บ่ƒฝไฝ“ๅฝ“ๅ‰ๆ˜ฏ็ฉบ้—ฒใ€ๆ€่€ƒไธญ่ฟ˜ๆ˜ฏๆญฃๅœจๆ‰ง่กŒไปปๅŠกใ€‚ +- **็ณป็ปŸๆ‰˜็›˜ๅธธ้ฉปๅฎˆๆŠค**๏ผš็‚นๅ‡ป็ช—ๅฃๅ…ณ้—ญๆŒ‰้’ฎๅชๆ˜ฏๅฐ†ๅ…ถๆœ€ๅฐๅŒ–่‡ณๆ‰˜็›˜๏ผŒๅŽๅฐๆŒ็ปญๅฎˆๆŠคไธไธญๆ–ญใ€‚ๅณ้”ฎๆ‰˜็›˜ๅ›พๆ ‡ๅฏๅฟซ้€Ÿๆš‚ๅœ/ๆขๅคใ€่ฐƒๅ‡บ้ขๆฟๆˆ–้€€ๅ‡บใ€‚ +- **LLM ่ฏทๆฑ‚่ฐƒ่ฏ•ไธŽ่„ฑๆ•้ข„่งˆ**๏ผš้€ๆ˜Žๅฑ•็คบๆฏๆฌกๅ‘ๆจกๅž‹ๅ‘ๅ‡บ็š„่ฏทๆฑ‚่€—ๆ—ถใ€็Šถๆ€็ ไธŽๅญ—็ฌฆ้‡๏ผŒๆ”ฏๆŒ็‚นๅ‡ปๆŸฅ็œ‹่„ฑๆ•ๅŽ็š„ Prompt ้ข„่งˆ๏ผˆ`preview`๏ผ‰๏ผŒ็กฎ่ฎคๅ‘้€ๅ†…ๅฎนๆ— ๆ•ๆ„Ÿไฟกๆฏใ€‚ +- **ๅฎžๆ—ถ่งฆๅ‘่ดฆๆœฌ**๏ผš้€ๆ˜Žๅฑ•็Žฐๅ†ณ็ญ–่ฟ‡ๆปค่ฟ‡็จ‹๏ผˆ่งฆๅ‘ๆ€่€ƒใ€้‡ๅคๅฟฝ็•ฅใ€้š็งๅฑ่”ฝใ€็Ÿญ้ข‘ๅˆๅนถ๏ผ‰ใ€‚ +- **ๆ•ๆ„Ÿๆ“ไฝœไบบๅทฅ็กฎ่ฎค**๏ผšๆ™บ่ƒฝไฝ“ๆ‰ง่กŒๆฝœๅœจๅฝฑๅ“ๆ“ไฝœๅ‰ๅฟ…้กป็ป่ฟ‡ๅฏน่ฏๆก†็กฎ่ฎค๏ผŒๆ”ฏๆŒๅ…่ฎธใ€ๆ‹’็ปๆˆ–็จๅŽๅค„็†ใ€‚ + +--- + +## โš™๏ธ ๅฎŒๆ•ด้…็ฝฎๅ‚่€ƒ + +ๆ‰€ๆœ‰่ฎพ็ฝฎๅ‡ๅฏๅœจ `squid-ui` ็š„ **SETTINGS** ้€‰้กนๅกไธญๅ›พๅฝขๅŒ–้…็ฝฎ๏ผŒไบฆๅฏไฟๅญ˜ๅœจ `~/.config/squid/settings.json` ๆˆ–้€š่ฟ‡็Žฏๅขƒๅ˜้‡ๆณจๅ…ฅใ€‚ + +### ่ฟ่กŒๆ—ถๅŸบ็ก€้…็ฝฎ +| ็Žฏๅขƒๅ˜้‡ | ้…็ฝฎ้”ฎๅ | ้ป˜่ฎคๅ€ผ | ่ฏดๆ˜Ž | +|---|---|---|---| +| `SQUID_WATCH_ROOT` | `watchRoot` | `$HOME` | ๆ–‡ไปถ็ณป็ปŸๆ„Ÿ็Ÿฅๅ™จ็›‘ๅฌ็š„ๆ น็›ฎๅฝ•็ปๅฏน่ทฏๅพ„ใ€‚ | +| `SQUID_PANEL_SOCKET` | `panelEndpoint` | ่‡ชๅŠจๅŒน้… | IPC ้€šไฟก็ซฏ็‚น๏ผˆLinux ไธบ `/tmp/squid-panel.sock`๏ผŒWindows ไธบ `\\.\pipe\squid-panel`๏ผ‰ใ€‚ | +| `SQUID_NOTIFY` | `notify` | `desktop` | ๆกŒ้ข้€š็Ÿฅ้€š้“๏ผˆ`desktop` ๆˆ– `off`๏ผ‰ใ€‚ไป…ๅœจๆ™บ่ƒฝไฝ“ๅฎž้™…ๅŠจไฝœๆ—ถๅผนๅ‡บใ€‚ | + +### ๆจกๅž‹ไธŽไธŠไธ‹ๆ–‡ๅ†ณ็ญ– +| ็Žฏๅขƒๅ˜้‡ | ้…็ฝฎ้”ฎๅ | ้ป˜่ฎคๅ€ผ | ่ฏดๆ˜Ž | +|---|---|---|---| +| `SQUID_MODEL_PROVIDER` | `modelProvider` | `default` | ๅœจ Pi ็š„ `models.json` / `auth.json` ไธญ็™ป่ฎฐ็š„ๆไพ›ๅ•†ๅ็งฐใ€‚ | +| `SQUID_ATTENTION_WINDOW_MINUTES` | `attentionWindowMinutes` | `15` | ไธŠไธ‹ๆ–‡ๆณจๆ„ๅŠ›ไฟ็•™็ช—ๅฃ๏ผˆๅˆ†้’Ÿ๏ผ‰๏ผŒ่ถ…ๆ—ถๅކๅฒ่ง‚ๆต‹่‡ชๅŠจไฟฎๅ‰ชใ€‚ | +| `SQUID_DECISION_CURATOR` | `decisionCuration` | `on` | ๅ†ณ็ญ–ๆจกๅž‹่ฃๅ‰ชๅผ€ๅ…ณ๏ผˆ`on` ๆˆ– `off`๏ผ‰๏ผŒๅผ‚ๅธธๆ—ถ่‡ชๅŠจ้™็บงๆ”พ่กŒใ€‚ | +| `SQUID_DECISION_BASE_URL` | `decisionCuratorBaseUrl` | ่Šๅคฉๆจกๅž‹ Base URL | ๅ†ณ็ญ–ๅพฎๆจกๅž‹ `/v1/systemone` ็ซฏ็‚น๏ผˆๆ”ฏๆŒ TypeSafe Jev ๆˆ–ๆœฌๅœฐ Kev๏ผ‰ใ€‚ | +| `SQUID_DECISION_MODEL` | `decisionCuratorModel` | ่‡ชๅŠจๆŽจๅฏผ | ๅ†ณ็ญ–ๆจกๅž‹ๆ ‡่ฏ†็ฌฆ๏ผˆๅฆ‚ `jev-latest`ใ€`openjev`ใ€`kev-4b`๏ผ‰ใ€‚ | +| `SQUID_DECISION_THRESHOLD` | `decisionCuratorThreshold` | `0.5` | ็ญ›้€‰ไฟ็•™้˜ˆๅ€ผ๏ผˆ`0.0` ๅˆฐ `1.0`๏ผŒ่ถŠ้ซ˜่ฃๅ‰ช่ถŠๆฟ€่ฟ›๏ผ‰ใ€‚ | + +### ๆ„Ÿ็ŸฅๆบไธŽๅฎ‰ๅ…จๆŽงๅˆถ +| ็Žฏๅขƒๅ˜้‡ | ้…็ฝฎ้”ฎๅ | ้ป˜่ฎคๅ€ผ | ่ฏดๆ˜Ž | +|---|---|---|---| +| `SQUID_SCREEN_READER` | `screenReaderBackend` | ็ณป็ปŸๅŽŸ็”Ÿ | ๅฑๅน•ๆ•่ŽทๅŽ็ซฏ๏ผšๅŽŸ็”Ÿ๏ผˆWindows UIA / Linux `xdotool`๏ผ‰๏ผŒๆˆ–ๆต‹่ฏ•ๅˆๆˆ `synthetic`ใ€‚ | +| `SQUID_SCREEN_POLL_MS` | `screenPollIntervalMs` | `1000` | ๅฑๅน•็Šถๆ€่ฝฎ่ฏข้‡‡ๆ ทๅ‘จๆœŸ๏ผˆๆฏซ็ง’๏ผ‰ใ€‚ | +| `SQUID_TERMINAL` | `terminalSource` | `off` | ็ปˆ็ซฏๅ‘ฝไปคๆ„Ÿ็Ÿฅๅผ€ๅ…ณ๏ผˆ`fifo`๏ผ‰ใ€‚ | +| `SQUID_TERMINAL_FIFO` | `terminalSourcePath` | โ€” | ็ปˆ็ซฏ้’ฉๅญๅ†™ๅ…ฅ่ทฏๅพ„๏ผˆUnix ไธบ FIFO๏ผŒWindows ไธบๅ‘ฝๅ็ฎก้“ๅฆ‚ `\\.\pipe\squid-term`๏ผ‰ใ€‚ | +| `SQUID_EDITOR_SAVES` | `editorSaveSource` | `off` | ็ผ–่พ‘ๅ™จไฟๅญ˜ๆ„Ÿ็Ÿฅๅผ€ๅ…ณ๏ผˆ`fifo`๏ผ‰ใ€‚ | +| `SQUID_EDITOR_FIFO` | `editorSavePath` | โ€” | ็ผ–่พ‘ๅ™จไฟๅญ˜้’ฉๅญๅ†™ๅ…ฅ่ทฏๅพ„๏ผˆๆˆ– Windows ๅ‘ฝๅ็ฎก้“ `\\.\pipe\squid-editor`๏ผ‰ใ€‚ | +| `SQUID_BROWSER` | `browserSource` | `off` | ๆต่งˆๅ™จๆ ‡็ญพๆ„Ÿ็Ÿฅๅผ€ๅ…ณ๏ผˆmacOS ไธ‹ไธบ `darwin`๏ผŒๅ…ถไป–ๅนณๅฐไธบ `synthetic`๏ผ‰ใ€‚ | +| `SQUID_CONTROL` | `captureGateBackend` | `off` | ๆ•่Žท้—จๆŽงๅผ€ๅ…ณ๏ผˆ`fifo`๏ผ‰๏ผŒๆ”ฏๆŒ้€š่ฟ‡็ฎก้“ๅ‘้€ pause/resume ๅ‘ฝไปคใ€‚ | +| `SQUID_CONTROL_FIFO` | `captureGatePath` | โ€” | ้—จๆŽงๅ‘ฝไปค่ทฏๅพ„๏ผˆๆˆ– Windows ๅ‘ฝๅ็ฎก้“ `\\.\pipe\squid-control`๏ผ‰ใ€‚ | +| `SQUID_EXCLUDE` | `excludeApplications` | โ€” | ้š็งๅฑ่”ฝ็š„ๅบ”็”จๅๅ•๏ผŒ้€—ๅทๅˆ†้š”๏ผˆไพ‹ๅฆ‚ `1Password,Bitwarden`๏ผ‰ใ€‚ | +| `SQUID_RETENTION_HOURS`| `retentionHours` | `off` | ๅฏๅŠจๆ—ถ่‡ชๅŠจๆธ…็†่ถ…่ฟ‡่ฏฅๆ—ถ้•ฟ๏ผˆๅฐๆ—ถ๏ผ‰็š„ๅކๅฒไบ‹ไปถ่ฎฐๅฝ•ใ€‚ | + +--- + +## ๐Ÿ› ๏ธ ๆทฑๅบฆๆžถๆž„ไธŽๅฎž็Žฐ็ป†่Š‚๏ผˆUnder the Hood๏ผ‰ + +้’ˆๅฏนๅผ€ๅ‘่€…ไธŽ็ณป็ปŸ่ดก็Œฎ่€…๏ผŒSquid ้ตๅพชไธฅ่ฐจ็š„ๆœฌๅœฐไผ˜ๅ…ˆใ€็กฎๅฎšๆ€งไธŽ้ซ˜ๅฏ้ ๆ€งๆžถๆž„๏ผš + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๅคš็ปดๅบฆๆ„Ÿๅฎ˜ๆŽฅๅ…ฅๅฑ‚ โ”‚ +โ”‚ ๆ–‡ไปถ็ณป็ปŸ็›‘ๅฌ ๅฑๅน•/ๆ— ้šœ็ขๆ–‡ๅญ— ็ปˆ็ซฏๆ‰ง่กŒ้’ฉๅญ ็ผ–่พ‘ๅ™จไฟๅญ˜้€š็Ÿฅ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ โ”‚ โ”‚ + โ–ผ โ–ผ โ–ผ โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๆ•่Žทๅฎ‰ๅ…จ็ญ–็•ฅ (ๅบ”็”จๅฑ่”ฝไธŽๅ‡ญๆฎ่„ฑๆ•ๆธ…ๆด—) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ่ฟฝๅŠ ๅผๆ—ถๅบไบ‹ไปถๅญ˜ๅ‚จ (SQLite WAL / ๅ†…ๅญ˜ๆต‹่ฏ•ๅฎž็Žฐ) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ็กฎๅฎšๆ€งๅœบๆ™ฏๆ—ถๅบๅˆ‡็‰‡ไธŽไธ–็•Œ็Šถๆ€่šๅˆๆŠ•ๅฝฑ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ไธŠไธ‹ๆ–‡ไผ˜ๅŒ–๏ผš15ๅˆ†้’Ÿๆณจๆ„ๅŠ›ๆป‘ๅŠจ็ช—ๅฃ + ๆฏซ็ง’็บงๅ†ณ็ญ–ๆจกๅž‹็ญ›้€‰ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๅธธ้ฉป้•ฟๆ•ˆๆ™บ่ƒฝไฝ“ (Pi ไผš่ฏ) โ”‚ +โ”‚ ่‡ชไธปๅ†ณ็ญ–ๅˆ†ๆžใ€ๅทฅๅ…ท่ฐƒ็”จๆ‰ง่กŒใ€่ฟž่ดฏไธŠไธ‹ๆ–‡ๅฎก่ฎก โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ โ”‚ + โ–ผ โ–ผ โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ็ณป็ปŸๆกŒ้ข้€š็Ÿฅ โ”‚ โ”‚ ๆ“ไฝœ็กฎ่ฎคๅฏน่ฏๆก†โ”‚ โ”‚ Slint ๅŽŸ็”Ÿ็•Œ้ขโ”‚ +โ”‚(ๆ‰ง่กŒๅ˜ๆ›ดๆ—ถ่งฆๅ‘)โ”‚ โ”‚ (้ซ˜้ฃŽ้™ฉๅŠจไฝœ) โ”‚ โ”‚ ไธŽ็ณป็ปŸๅธธ้ฉปๆ‰˜็›˜โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### 1. ๅ…ฅๅฃ็บงๆ•ๆ„Ÿๅ‡ญๆฎๆธ…ๆด—ไธŽๅบ”็”จ่ฟ‡ๆปค +ๆ‰€ๆœ‰ไบ‹ไปถ็ปŸไธ€่ฟ›ๅ…ฅ [`src/capture-policy.ts`](file:///home/ubuntu/.t3/worktrees/squid/t3-cf8f03cb/src/capture-policy.ts)ใ€‚`RedactingCapturePolicy` ๅœจๆ•ฐๆฎๆŽฅ่งฆๅญ˜ๅ‚จๅ‰๏ผŒ็กฎๅฎšๆ€งๆŠน้™ค API Keyใ€AWS ่ฎฟ้—ฎๅ‡ญ่ฏใ€GitHub ไปค็‰Œใ€PEM ็ง้’ฅๅŠๅฏ†็ ใ€‚`ExcludingCapturePolicy` ๅœจ้ป‘ๅๅ•ๅบ”็”จๅค„ไบŽๅ‰ๅฐๆ—ถ็žฌ้—ด็†”ๆ–ญๆ‰€ๆœ‰ๆ•่Žทใ€‚ + +### 2. ่ฟฝๅŠ ๅผ SQLite WAL ไบ‹ไปถๅญ˜ๅ‚จไธŽๅ›žๆ”พ้กบๅบ +ไบ‹ไปถ้€š่ฟ‡ WAL ๆจกๅผ็š„ SQLite ๅญ˜ๅ‚จ๏ผˆ`synchronous = NORMAL`๏ผŒ่ง [`src/sqlite-event-store.ts`](file:///home/ubuntu/.t3/worktrees/squid/t3-cf8f03cb/src/sqlite-event-store.ts)๏ผ‰ใ€‚้€š่ฟ‡่ง„่ŒƒๅŒ–ๆŽ’ๅบๅฅ‘็บฆ๏ผˆ[`src/chronicle-ordering.ts`](file:///home/ubuntu/.t3/worktrees/squid/t3-cf8f03cb/src/chronicle-ordering.ts)๏ผ‰๏ผŒ็ฆป็บฟ้‡ๆ”พใ€ๅดฉๆบƒๆขๅคไธŽ็Šถๆ€่ฟ˜ๅŽŸๅ…ทๆœ‰ 100% ็กฎๅฎšๆ€งใ€‚ + +### 3. ๅœบๆ™ฏๅŒ–ไธ–็•Œ็Šถๆ€ๅฝ’็บฆ (Episodic World State) +ไพๆฎๆดปๅŠจไบ‹ไปถ็š„ๆ—ถ้—ดๅœ้กฟ่‡ชๅŠจๅˆ‡ๅˆ†็‹ฌ็ซ‹ๅœบๆ™ฏ๏ผˆEpisode๏ผŒ่ง [`src/episode.ts`](file:///home/ubuntu/.t3/worktrees/squid/t3-cf8f03cb/src/episode.ts)๏ผ‰ใ€‚ๅŽŸๅง‹ๆ—ถๅบไบ‹ไปถ็”ฑๆ— ๅ‰ฏไฝœ็”จ็š„ๅฝ’็บฆๅ™จ่šๅˆไธบๅฎ่ง‚็š„ `WorldState` ๅฟซ็…ง๏ผˆ[`src/world-state.ts`](file:///home/ubuntu/.t3/worktrees/squid/t3-cf8f03cb/src/world-state.ts)๏ผ‰ใ€‚ๆ™บ่ƒฝไฝ“ๆฏๆฌกๅ†ณ็ญ–ๅช้œ€ๅฎก้˜…ๅฎ่ง‚ไธ–็•Œ็Šถๆ€๏ผŒ้ฟๅ…้ๅކๆตท้‡ๆ—ฅๅฟ—ใ€‚ + +### 4. ๆณจๆ„ๅŠ›ๆป‘ๅŠจ็ช—ๅฃไธŽไบš็ง’็บงๅ†ณ็ญ–ๅพฎๆจกๅž‹ +- **ๆณจๆ„ๅŠ›ๆป‘ๅŠจ็ช—ๅฃ**๏ผˆ[`src/attention-window-extension.ts`](file:///home/ubuntu/.t3/worktrees/squid/t3-cf8f03cb/src/attention-window-extension.ts)๏ผ‰๏ผšๆฏๆฌก่ฐƒ็”จๆจกๅž‹ๅ‰่‡ชๅŠจๆธ…็†่ถ…่ฟ‡ๆ—ถๆ•ˆ๏ผˆ้ป˜่ฎค 15 ๅˆ†้’Ÿ๏ผ‰็š„ๆ—ง่ง‚ๆต‹่ฝจ่ฟน๏ผŒๆœ็ป Token ๆ— ้™ๅขž้•ฟใ€‚ +- **ๅ†ณ็ญ–่ฃๅ‰ชๅพฎๆจกๅž‹**๏ผˆ[`src/decision-curator-extension.ts`](file:///home/ubuntu/.t3/worktrees/squid/t3-cf8f03cb/src/decision-curator-extension.ts)๏ผ‰๏ผšๅˆฉ็”จ `/v1/systemone` ๆŽฅๅฃ๏ผˆJev / Kev๏ผ‰ๆฏซ็ง’็บง่ฟ‡ๆปคๆ— ๆ•ˆ่ฝฎๆฌกใ€‚้‡็ฝ‘็ปœ้”™่ฏฏ่‡ชๅŠจๆ”พ่กŒ๏ผˆFail-Open๏ผ‰๏ผŒไธ้˜ปๆ–ญไธปๆต็จ‹ใ€‚ + +### 5. ่ทจๅนณๅฐ็ปŸไธ€ไผ ่พ“้€‚้… +[`src/endpoint-transport.ts`](file:///home/ubuntu/.t3/worktrees/squid/t3-cf8f03cb/src/endpoint-transport.ts) ็ปŸไธ€ไบ†ๆ“ไฝœ็ณป็ปŸ้—ด็š„็ฎก้“ๅทฎๅผ‚๏ผš +- **Unix ็ณป็ปŸ**๏ผšไฝฟ็”จๆ–‡ไปถๅŸŸๅฅ—ๆŽฅๅญ—่ฟ›่กŒ IPC๏ผŒไฝฟ็”จๆ ‡ๅ‡† FIFO๏ผˆ`mkfifo`๏ผ‰ๆŽฅๅ…ฅ Shell ไธŽ็ผ–่พ‘ๅ™จ้’ฉๅญใ€‚ +- **Windows ็ณป็ปŸ**๏ผšๅŽŸ็”Ÿๆ”ฏๆŒๅ‘ฝๅ็ฎก้“๏ผˆ`\\.\pipe\squid-panel`ใ€`\\.\pipe\squid-control`๏ผ‰๏ผŒๅบ•ๅฑ‚็”ฑ `Bun.listen({ unix })` ้ฉฑๅŠจใ€‚ + +### 6. ไธฅ่ฐจ็š„ๅ…ณๆœบไธŽๆณจ้”€ๆ—ถๅบ +`stop()` ไธฅๆ ผไพไพ่ต–ๅๅบ้€€ๅ‡บ๏ผš +1. ๅœๆญขๅ„็ฑปๆ„Ÿๅฎ˜ๆบ๏ผˆ้˜ฒๆญขๅธ่ฝฝ่ฟ‡็จ‹ไธญไบง็”Ÿๆฎ‹ไฝ™ไบ‹ไปถ๏ผ‰๏ผ› +2. ็ญ‰ๅพ…ๅนถๆŽ’ๅนฒๆญฃๅœจๆ‰ง่กŒไธญ็š„ๆ™บ่ƒฝไฝ“่ฝฎๆฌก๏ผ› +3. ๆณจ้”€้•ฟ้ฉปๆ™บ่ƒฝไฝ“ไผš่ฏๅนถ้‡Šๆ”พ่ต„ๆบ๏ผ› +4. ๅ…ณ้—ญ SQLite ๅญ˜ๅ‚จๅนถไฟๅญ˜ๆœ€็ปˆ็Šถๆ€ใ€‚ + +--- + +## ๐Ÿงช ๅผ€ๅ‘่€…ๅ‘ฝไปคไธŽ่‡ชๅŠจๅŒ–ๆต‹่ฏ• + +Squid ๆ‹ฅๆœ‰ๅ…จ็ฆป็บฟ็š„ๅฎŒๅค‡่‡ชๅŠจๅŒ–ๆต‹่ฏ•ไฝ“็ณป๏ผš + +```bash +# ็ฑปๅž‹ๆฃ€ๆŸฅ๏ผˆๅˆ†ๅˆซ่ฆ†็›– src/ ไธŽ tests/๏ผ‰ +bun run typecheck + +# ่ฟ่กŒๅ…จๅฅ—็ฆป็บฟๆต‹่ฏ•๏ผˆ810+ ็ฆป็บฟๆต‹่ฏ•็”จไพ‹๏ผ‰ +bun test + +# ็ผ–่ฏ‘ TypeScript ไปฃ็  +bun run build + +# ่ฟ่กŒ่‡ชๅŠจๅŒ– UI ๅ†’็ƒŸๆต‹่ฏ• +cargo build --release --manifest-path ui/Cargo.toml +bun run ui:smoke +``` + +### ่‡ชๅŠจๅŒ– UI ๅ†’็ƒŸๆต‹่ฏ• (`bun run ui:smoke`) +ๅ†’็ƒŸๆต‹่ฏ•้ชŒ่ฏ็ผ–่ฏ‘ๅ‡บ็š„็œŸๅฎž `squid-ui` ไบŒ่ฟ›ๅˆถ็จ‹ๅบๅœจๆ— ไบบๅทฅๅนฒ้ข„ไธ‹็š„็•Œ้ขๆธฒๆŸ“ไธŽไบคไบ’ๅ‡†็กฎๆ€ง๏ผš +- **ๅœจ Linux ็Žฏๅขƒไธ‹**๏ผš่‡ชๅŠจๆ‹‰่ตท็‹ฌ็ซ‹็š„ `Xvfb` ่™šๆ‹Ÿๆ˜พ็คบๅ™จ๏ผŒ้€š่ฟ‡ `xdotool` ่งฆๅ‘็ณป็ปŸ็บง้ผ ๆ ‡็‚นๅ‡ป๏ผŒๅนถ้€š่ฟ‡็งๆœ‰ D-Bus ๆ ก้ชŒ AT-SPI ๆ— ้šœ็ขๆ ‘๏ผŒๆˆชๅ–็ช—ๅฃๆธฒๆŸ“ๆˆชๅ›พใ€‚ +- **ๅœจ Windows ็Žฏๅขƒไธ‹**๏ผšๅˆฉ็”จ Windows ๅŽŸ็”Ÿ UI Automation ๅฎšไฝๆŒ‡ๅฎšๆ— ้šœ็ขๅ็งฐ็š„ๆŽงไปถ๏ผŒ่งฆๅ‘ๅŽŸ็”Ÿ็‚นๅ‡ป๏ผŒ้ชŒ่ฏๅ‘ฝไธญๆต‹่ฏ•ไธŽ็Šถๆ€ๆ›ดๆ–ฐใ€‚ + +--- + +## ๐Ÿ”’ ้š็งไธŽๅฎ‰ๅ…จๆ€งไฟ้šœ + +1. **ๆ ธๅฟƒ้“พ่ทฏๅฎŒๅ…จ็ฆป็บฟๅฏๆต‹**๏ผšไบ‹ไปถๆ”ถ้›†ใ€ๆ—ถๅบๅญ˜ๅ‚จใ€ไธ–็•Œ็Šถๆ€ๅฝ’็บฆไปฅๅŠ 810+ ๆต‹่ฏ•็”จไพ‹ๅ…จ้ƒจ็บฏๆœฌๅœฐ็ฆป็บฟๆ‰ง่กŒ๏ผŒๆ— ไปปไฝ•ๆœช็ปๅ…่ฎธ็š„ๅค–่ฟž่ฏทๆฑ‚ใ€‚ +2. **ๅคšๅฑ‚ๆ•ๆ„Ÿๅ‡ญๆฎ่„ฑๆ•ๆธ…ๆด—**๏ผšๆ‰€ๆœ‰่ฟ›ๅ…ฅ็ณป็ปŸ็š„็ช—ๅฃๆ ‡้ข˜ใ€ไปฃ็ ็‰‡ๆฎตไธŽ็ปˆ็ซฏๅ‘ฝไปคๅฟ…้กป้€š่ฟ‡ `RedactingCapturePolicy` ๆญฃๅˆ™่ฟ‡ๆปค๏ผŒๆœ็ปๅ„็ฑปๅฏ†็ ใ€Token ไธŽๅฏ†้’ฅ่ขซๅ†™ๅ…ฅ็ฃ็›˜ใ€‚ +3. **ๅบ”็”จๆฟ€ๆดป้š็ง็†”ๆ–ญ**๏ผš้…็ฝฎ้ป‘ๅๅ•ๅบ”็”จ๏ผˆๅฆ‚ๅฏ†็ ็ฎก็†ๅ™จใ€็ฝ‘้“ถ็ญ‰๏ผ‰ๅค„ไบŽๅ‰ๅฐๆ—ถ๏ผŒSquid ็ซ‹ๅณ้˜ปๆ–ญไธ€ๅˆ‡ๅฑๅน•้‡‡้›†ใ€‚ +4. **้€ๆ˜Žๅ—ๆŽง็š„ๆš‚ๅœๆœบๅˆถ**๏ผšๆ— ่ฎบๆ˜ฏ้€š่ฟ‡้ขๆฟ็•Œ้ข็š„โ€œๆš‚ๅœ่ฎฐๅฝ•โ€ๆŒ‰้’ฎ๏ผŒ่ฟ˜ๆ˜ฏ้€š่ฟ‡ๅ‘ฝไปคๅ‘็ฎก้“ๅ†™ๅ…ฅๆŒ‡ไปค๏ผŒ้šๆ—ถๅฏๅฝปๅบ•ๅˆ‡ๆ–ญๅค–้ƒจๆ„Ÿ็Ÿฅใ€‚ + +--- + +## ๐Ÿ“„ ๅผ€ๆบ่ฎธๅฏ่ฏ + +ๆœฌ้กน็›ฎๅŸบไบŽ [MIT ่ฎธๅฏ่ฏ](LICENSE) ๅผ€ๆบใ€‚ diff --git a/docs/panel-protocol.md b/docs/panel-protocol.md index 4821d0c..ff06a5f 100644 --- a/docs/panel-protocol.md +++ b/docs/panel-protocol.md @@ -235,6 +235,36 @@ Pushed when the agent finishes a turn. | `status` | `"replied"` \| `"failed"` | yes | | | `text` | string | no | **sanitized** assistant text or error | +### `agent_thinking` + +Pushed to announce the agent's active thinking/processing state. Used by the panel to render an animated thinking indicator while a prompt turn is in flight. + +```jsonc +{"type":"agent_thinking","thinking":1} +``` + +| field | type | required | notes | +|---|---|---|---| +| `type` | `"agent_thinking"` | yes | | +| `thinking` | `0` \| `1` | yes | `1` when prompt turn is active, `0` when completed or aborted | + +### `agent_triggers` + +Pushed to report decision counts and gate attribution from the resident watch loop (explaining "what has Squid reacted to or filtered?"). + +```jsonc +{"type":"agent_triggers","triggered":5,"duplicate":2,"excluded":1,"coalesced":3,"last":"triggered"} +``` + +| field | type | required | notes | +|---|---|---|---| +| `type` | `"agent_triggers"` | yes | | +| `triggered` | integer | yes | count of events that provoked an agent turn | +| `duplicate` | integer | yes | count of events dropped as duplicates | +| `excluded` | integer | yes | count of events dropped by capture exclusion | +| `coalesced` | integer | yes | count of events coalesced during an active turn | +| `last` | `"triggered"` \| `"duplicate"` \| `"excluded"` \| `"coalesced"` \| `"no_agent"` | no | most recent disposition token | + ### `service` The brain's own standing state, announced to each panel **as it subscribes** โ€” @@ -441,12 +471,15 @@ once with `outcome: "pending"` when the request fires, and once with `"ok"` or `"error"` when the response arrives. The `id` correlates the two: the panel updates the pending row in place. -Privacy: captures metadata only โ€” model id, message count, payload size, HTTP -status, duration. No prompt content, API keys, or response bodies. +Privacy: captures request metadata, plus one bounded, scrubbed exception โ€” +`preview`, a capped, control-stripped, credential-redacted excerpt of prompt messages +(`sanitizeLlmPreview` in `src/audit-text.ts`), allowing users to inspect the context +sent to the provider without exposing secrets or unbounded payloads. +No API keys or raw response bodies. ```jsonc -{"type":"llm_log","id":1,"requestedAt":"2026-09-14T12:30:00.000Z","model":"deepseek-v4-flash","messageCount":7,"promptChars":4200,"outcome":"pending"} -{"type":"llm_log","id":1,"requestedAt":"2026-09-14T12:30:00.000Z","respondedAt":"2026-09-14T12:30:02.500Z","httpStatus":200,"durationMs":2500,"outcome":"ok"} +{"type":"llm_log","id":1,"requestedAt":"2026-09-14T12:30:00.000Z","model":"deepseek-v4-flash","messageCount":7,"promptChars":4200,"preview":"user: run test suite...","outcome":"pending"} +{"type":"llm_log","id":1,"requestedAt":"2026-09-14T12:30:00.000Z","respondedAt":"2026-09-14T12:30:02.500Z","model":"deepseek-v4-flash","messageCount":7,"promptChars":4200,"preview":"user: run test suite...","httpStatus":200,"durationMs":2500,"outcome":"ok"} {"type":"llm_log","id":2,"requestedAt":"2026-09-14T12:31:00.000Z","respondedAt":"2026-09-14T12:31:01.200Z","httpStatus":401,"durationMs":1200,"outcome":"error","error":"HTTP 401"} ``` @@ -463,6 +496,7 @@ status, duration. No prompt content, API keys, or response bodies. | `durationMs` | integer | no | round-trip latency in ms | | `outcome` | `"pending"` \| `"ok"` \| `"error"` | yes | | | `error` | string | no | error summary if failed, **sanitized** | +| `preview` | string | no | bounded, sanitized prompt preview excerpt | ## Panel โ†’ TS @@ -617,16 +651,45 @@ export interface PanelAgentLine { readonly status: "replied" | "failed"; readonly text?: string; } +export interface PanelAgentThinking { + readonly type: "agent_thinking"; + readonly thinking: 0 | 1; +} +export interface PanelAgentTriggers { + readonly type: "agent_triggers"; + readonly triggered: number; + readonly duplicate: number; + readonly excluded: number; + readonly coalesced: number; + readonly last?: "triggered" | "duplicate" | "excluded" | "coalesced" | "no_agent"; +} +export interface PanelLlmLogLine { + readonly type: "llm_log"; + readonly id: number; + readonly requestedAt: string; + readonly outcome: "pending" | "ok" | "error"; + readonly respondedAt?: string; + readonly model?: string; + readonly messageCount?: number; + readonly promptChars?: number; + readonly httpStatus?: number; + readonly durationMs?: number; + readonly error?: string; + readonly preview?: string; +} export type PanelMessage = | PanelWorldState | PanelAgentLine + | PanelAgentThinking + | PanelAgentTriggers | PanelServiceLine | PanelConfigLine | PanelConfigEnd | PanelConfigResult | PanelSourceLine | PanelSourceEnd - | PanelSourceResult; + | PanelSourceResult + | PanelLlmLogLine; export interface PanelServiceLine { readonly type: "service"; readonly agentState: "ready" | "needs_config";