loom-agent 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +25 -0
- package/CHANGELOG.md +402 -0
- package/LICENSE +21 -0
- package/LOOM.md +235 -0
- package/README.md +433 -0
- package/bin/loom-tui.js +43 -0
- package/bin/loom.js +44 -0
- package/docs/acp.md +151 -0
- package/docs/web.md +205 -0
- package/package.json +97 -0
- package/scripts/acp-smoke.js +146 -0
- package/src/acp/acp-server.js +287 -0
- package/src/config/provider-cmd.js +37 -0
- package/src/config/settings.js +164 -0
- package/src/core/agents.js +361 -0
- package/src/core/background-tasks.js +103 -0
- package/src/core/cli.js +579 -0
- package/src/core/custom-commands.js +70 -0
- package/src/core/errors.js +29 -0
- package/src/core/events.js +24 -0
- package/src/core/file-diffs.js +282 -0
- package/src/core/format.js +206 -0
- package/src/core/graph.js +257 -0
- package/src/core/hooks.js +82 -0
- package/src/core/lsp.js +385 -0
- package/src/core/memory.js +87 -0
- package/src/core/model-router.js +87 -0
- package/src/core/permissions.js +327 -0
- package/src/core/platform.js +33 -0
- package/src/core/plugin-cmd.js +380 -0
- package/src/core/restore.js +207 -0
- package/src/core/session-store.js +167 -0
- package/src/core/session.js +910 -0
- package/src/core/subagent-log.js +134 -0
- package/src/core/tokens.js +31 -0
- package/src/core/update.js +6 -0
- package/src/core/usage.js +166 -0
- package/src/index.js +41 -0
- package/src/mcp/mcp-client.js +201 -0
- package/src/mcp/mcp-manager.js +193 -0
- package/src/providers/anthropic.js +243 -0
- package/src/providers/google.js +29 -0
- package/src/providers/index.js +175 -0
- package/src/providers/local.js +27 -0
- package/src/providers/nvidia.js +85 -0
- package/src/providers/openai-compat.js +269 -0
- package/src/providers/openai.js +35 -0
- package/src/providers/openrouter.js +43 -0
- package/src/providers/registry.js +196 -0
- package/src/providers/tokenrouter.js +19 -0
- package/src/skills/skill-matcher.js +133 -0
- package/src/skills/skills-manager.js +213 -0
- package/src/tools/index.js +543 -0
- package/src/tui/App.tsx +1578 -0
- package/src/tui/components/BreadcrumbBar.tsx +34 -0
- package/src/tui/components/ChatArea.tsx +518 -0
- package/src/tui/components/InputBar.tsx +354 -0
- package/src/tui/components/MdText.tsx +105 -0
- package/src/tui/components/Modals.tsx +851 -0
- package/src/tui/components/PermissionPopup.tsx +264 -0
- package/src/tui/components/Sidebar.tsx +182 -0
- package/src/tui/components/SplashScreen.tsx +51 -0
- package/src/tui/components/SubagentPanel.tsx +217 -0
- package/src/tui/components/ToastOverlay.tsx +34 -0
- package/src/tui/keybinds.ts +318 -0
- package/src/tui/mcp-presets.ts +189 -0
- package/src/tui/md-render.ts +228 -0
- package/src/tui/store.ts +714 -0
- package/src/tui/suite-home.ts +20 -0
- package/src/tui/theme.ts +313 -0
- package/src/tui/themes.generated.ts +968 -0
- package/src/tui/tool-display.ts +176 -0
- package/src/tui/toolname.ts +60 -0
- package/src/tui/tui-config.ts +28 -0
- package/src/tui-open.tsx +51 -0
- package/src/web/attach.js +242 -0
- package/src/web/graph-view.html +262 -0
- package/src/web/index.html +824 -0
- package/src/web/web-server.js +470 -0
package/.env.example
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Loom Code — environment variables for API keys
|
|
2
|
+
# Copy this file to .env and fill in your keys, or set them as env vars.
|
|
3
|
+
|
|
4
|
+
# Anthropic (Claude)
|
|
5
|
+
ANTHROPIC_API_KEY=
|
|
6
|
+
|
|
7
|
+
# OpenAI (GPT-4o, o1, etc.)
|
|
8
|
+
OPENAI_API_KEY=
|
|
9
|
+
|
|
10
|
+
# NVIDIA NIM / Integrate API
|
|
11
|
+
NVIDIA_API_KEY=
|
|
12
|
+
# Optional: override NVIDIA base URL (defaults to https://integrate.api.nvidia.com/v1)
|
|
13
|
+
# NVIDIA_BASE_URL=https://integrate.api.nvidia.com/v1
|
|
14
|
+
|
|
15
|
+
# Google Gemini
|
|
16
|
+
GOOGLE_API_KEY=
|
|
17
|
+
|
|
18
|
+
# OpenRouter (optional)
|
|
19
|
+
OPENROUTER_API_KEY=
|
|
20
|
+
|
|
21
|
+
# Loom Code debug logging
|
|
22
|
+
# LOOM_DEBUG=1
|
|
23
|
+
|
|
24
|
+
# Skip update check on startup
|
|
25
|
+
# LOOM_NO_UPDATE_CHECK=1
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to **Loom Code** are documented here.
|
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **Data-driven tool display** — the chat renderer no longer checks tool
|
|
11
|
+
*names* anywhere: every tool's icon, pending text, spinner, label,
|
|
12
|
+
block/diff/todos/check/subagent flags and file-vs-bash diff behavior come
|
|
13
|
+
from one display registry (`src/tui/tool-display.ts`), keyed by the tool
|
|
14
|
+
name with a **GenericTool fallback** for anything unregistered (opencode
|
|
15
|
+
and Cline behave the same way — the UI never hardcodes a tool list).
|
|
16
|
+
`src/tui/toolname.ts` keeps only generic formatters. The agent loop no
|
|
17
|
+
longer branches on `name === "write"` / `"bash"` either — it reads the
|
|
18
|
+
registry's `diffs` flag.
|
|
19
|
+
- **Full-chat session restore** — `/sessions` (and `-s <id>`) restores the
|
|
20
|
+
**whole conversation**, not just the text: thinking text, every tool row
|
|
21
|
+
with its saved output, diffs, todos, error/interrupt flags. Older saves
|
|
22
|
+
that only carry `toolCalls` are reconstructed (tool rows rebuilt from the
|
|
23
|
+
calls, results attached), and saving now merges the TUI display parts into
|
|
24
|
+
the session file so the restore is lossless.
|
|
25
|
+
- **Session picker sorted by recency** — `/sessions` now lists sessions by
|
|
26
|
+
their last write time (updatedAt → createdAt → file mtime) instead of
|
|
27
|
+
file name, which had legacy `share-*` exports pinned to the top forever
|
|
28
|
+
and hid the real conversations; legacy files without a timestamp now show
|
|
29
|
+
their file date.
|
|
30
|
+
- **Loop hardening** — the todos listener registers *before* the turn starts
|
|
31
|
+
(a fast first update can't be missed), and a provider that throws
|
|
32
|
+
synchronously now finishes the turn with an error bubble instead of
|
|
33
|
+
leaving the chat stuck on "Thinking" forever.
|
|
34
|
+
- **Companion pets removed** — the ASCII pet, OpenPets desktop sync, and
|
|
35
|
+
`/companion` are gone (declared useless); the sidebar row now shows the
|
|
36
|
+
**Auto-approve** status instead ("on — no asks" / "off — asks per command",
|
|
37
|
+
Shift+Tab toggles). The `@open-pets/client` dependency was dropped.
|
|
38
|
+
- **Tool activity in the chat** — a faithful port of opencode's chat-area
|
|
39
|
+
layout: **every tool call gets its own row** (`→ Read src/a.ts`,
|
|
40
|
+
`← Edit src/b.ts`, `$ git status`, `✱ Glob "**/*.ts"` … — never one merged
|
|
41
|
+
patch for all commands) and rows are packed **tightly**, opencode's
|
|
42
|
+
"always separate" rule: consecutive single-line rows share no gap, only
|
|
43
|
+
the first row after the reasoning/text part and anything around a block
|
|
44
|
+
get one. While running, most tools show the quiet `~ Preparing edit...`
|
|
45
|
+
pending text (deeper indent, no spinner glyph, no accent — opencode only
|
|
46
|
+
spins read/task/execute rows, which show `⠋ Read src/a.ts`). Once done,
|
|
47
|
+
the row flips to a muted `icon label` line that **stays** in the
|
|
48
|
+
transcript. A finished write/edit/bash that actually changed a file
|
|
49
|
+
**replaces its row with the patch** — opencode's Edit renders only its
|
|
50
|
+
BlockTool once the diff exists: a panel on `bgPanelAlt` with a subtle
|
|
51
|
+
left border, titled `← Edit src/b.ts` / `# Wrote {path}`, with the
|
|
52
|
+
`+2 -1` counts and the colored +/- hunks — right where the edit happened,
|
|
53
|
+
then the message continues normally. The assistant answer has **no
|
|
54
|
+
bubble**: reasoning streams as its own full-width part (`⠋ Thinking`
|
|
55
|
+
spinner with the body live while the turn runs, collapsing to a clickable
|
|
56
|
+
`+ Thought · 4.2s` once done), tool rows, then the plain text part. The
|
|
57
|
+
**`# Todos` block** (same BlockTool panel) renders after the last block,
|
|
58
|
+
opencode TodoWrite-style (sidebar keeps its own copy). A `task` subagent
|
|
59
|
+
shows as an opencode InlineTool row — `│ @explore Task` spinning while
|
|
60
|
+
the child runs, flipping to `✓ @explore Task` once done, with `↳ tool
|
|
61
|
+
log` and the streamed body beneath, ending in `↳ finished · done`. Tool
|
|
62
|
+
rows are throttled (first tool of a burst renders immediately, the rest
|
|
63
|
+
ride the 100 ms stream flush) so fast bursts no longer re-render or
|
|
64
|
+
reflow the whole message (that reflow was what made fast output jitter).
|
|
65
|
+
- **Chatbox vibration fixed** — the caret blink now swaps *color*, never
|
|
66
|
+
the character. The cell always renders a full block (`█`); when the blink
|
|
67
|
+
is off its `fg` matches the background behind it (`bgInput`, or `primary`
|
|
68
|
+
inside a selection) so the block is invisible without changing text
|
|
69
|
+
length. TUI renderers commonly trim trailing whitespace per line, which
|
|
70
|
+
made the earlier "space-when-off" caret shorten the last wrapped line at
|
|
71
|
+
blink rate and vibrate the box height while typing; swapping color
|
|
72
|
+
instead means the wrap is identical in both blink phases and the box
|
|
73
|
+
stays smooth while typing, opencode-style.
|
|
74
|
+
- **One-time session permissions prompt** — every new session asks once:
|
|
75
|
+
"Allow all commands in this session?" or "Ask each time" — no more
|
|
76
|
+
per-command prompts slowing the loop. **Shift+Tab** toggles session-wide
|
|
77
|
+
auto-approval on/off any time (a muted `· auto` indicator in the status
|
|
78
|
+
line shows the state; the popup answers it too).
|
|
79
|
+
- **Browser memory graph** — `loom graph open` (or `/graph open`) renders the
|
|
80
|
+
LOOM.md knowledge graph as a force-directed canvas view in the default
|
|
81
|
+
browser: drag nodes, wheel zoom, pan the background, click a node for its
|
|
82
|
+
details, filter by type, and search. The TUI `/graph` modal also now shows
|
|
83
|
+
the `##`/`###` heading hierarchy as a browsable tree (17 nodes, 4 links in
|
|
84
|
+
LOOM.md).
|
|
85
|
+
- **First-run welcome tips** — a small dismissible (✕) card in the sidebar for
|
|
86
|
+
new users only: "Loom supports 180+ providers — /connect to add a key".
|
|
87
|
+
Dismissal persists in `~/.loom/tui.json`, so it never comes back.
|
|
88
|
+
- **Borderless panel UI** — the breadcrumb bar, sidebar, and chatbox no longer
|
|
89
|
+
draw box-drawing borders. Panels separate via background color, so terminal
|
|
90
|
+
text selection (copy) is never snagged on grid lines.
|
|
91
|
+
- **opencode-style layout** — the chatbox now lives in the chat column with a
|
|
92
|
+
breathing gap between it and the sidebar (no shared bottom edge); the
|
|
93
|
+
sidebar is a full-height, locked column with its own internal scroll; the
|
|
94
|
+
chatbox grows to 14 rows and its wrap-width math matches the real box width
|
|
95
|
+
so the text no longer vibrates at wrap boundaries.
|
|
96
|
+
- **Roomier, calmer chatbox** — the resting chatbox is 5 rows tall (splash
|
|
97
|
+
screen included, centered like opencode) and grows smoothly with the draft;
|
|
98
|
+
the mode name ("Build · all tools") sits at the chatbox bottom instead of a
|
|
99
|
+
single-letter "B" header, and the text always starts one row below the top
|
|
100
|
+
edge (the old empty header row collapsed, pushing text against the top).
|
|
101
|
+
The status line below is one clean opencode-style row — cwd · usage
|
|
102
|
+
("53.7K (27%) · $8.17") · "ctrl+p commands" — instead of three rows of
|
|
103
|
+
clutter. Wrap-width math is exact (integer-snapped to the measured box
|
|
104
|
+
width), so the height never oscillates and the box never vibrates the
|
|
105
|
+
screen while typing; past the limit the draft scrolls instead of hiding
|
|
106
|
+
lines. Pasted drafts past 10 lines render compressed — first 10 lines plus
|
|
107
|
+
a "pasted ~N lines" badge — instead of blowing the box up; any keystroke
|
|
108
|
+
expands the full text back.
|
|
109
|
+
- **Instant /models** — the registry cache was re-parsed (a multi-MB JSON
|
|
110
|
+
file) once per provider on every model-list open, stalling the picker for
|
|
111
|
+
seconds. `loadRegistry()` is now memoized per process (invalidated only
|
|
112
|
+
when a fresh fetch lands): `/models` and the provider picker open
|
|
113
|
+
instantly again.
|
|
114
|
+
- **Fewer permission prompts** — bash now auto-allows normal commands by
|
|
115
|
+
default (dangerous ones — `sudo`, `rm -rf`, `git push --force`, global
|
|
116
|
+
installs … — still ask, defense in depth stays). Edit/task/skill asks are
|
|
117
|
+
unchanged; `/auto` still flips full auto-approve on/off.
|
|
118
|
+
- **Extended thinking on every step (opencode-style)** — when the active
|
|
119
|
+
model supports reasoning, Loom now asks it to think before EVERY reply,
|
|
120
|
+
including after each tool result, so complex tasks get multiple thinking
|
|
121
|
+
passes instead of a single one. Anthropic extended thinking
|
|
122
|
+
(`thinking: enabled`) is enabled for Sonnet 4/Opus 4 (temperature is
|
|
123
|
+
dropped, which the API requires); OpenAI effort models (o1/o3/o4/gpt-5)
|
|
124
|
+
get `reasoning_effort: high`; DeepSeek R1 / Qwen QwQ / Kimi K2 already
|
|
125
|
+
emit reasoning on their own. Each pass streams live into the "+Thought"
|
|
126
|
+
panel. The system prompt now tells the model to re-evaluate after every
|
|
127
|
+
tool result on complex tasks instead of rushing the answer.
|
|
128
|
+
- **The ask tool (questions, not permissions)** — the model can now ask you a
|
|
129
|
+
question with up to 3 answer options (or let you type your own) when it
|
|
130
|
+
genuinely needs input. The permission popup splits into two clear modes:
|
|
131
|
+
permissions (bash/write/…) show only **Allow · Always allow · Deny** — and
|
|
132
|
+
for commands a fourth row, **Allow all commands in this session** (flips
|
|
133
|
+
auto-approve on for the rest of the session, `/auto` to turn it back off) —
|
|
134
|
+
while the ask tool shows the question, its options, and a "Type your
|
|
135
|
+
answer…" row. Typing on a permission popup is now ignored instead of
|
|
136
|
+
opening a bogus answer editor, and the answer you pick or type is fed back
|
|
137
|
+
to the model as the tool result.
|
|
138
|
+
- **Floating dialogs (no more "new window")** — `/models`, `/mcp`,
|
|
139
|
+
`/connectors`, `/settings`, and the rest now render as a compact centered
|
|
140
|
+
panel with no full-screen backdrop, so the chat and any running agent stay
|
|
141
|
+
visible and untouched while you browse — the agent keeps working behind the
|
|
142
|
+
dialog, exactly like opencode's palette.
|
|
143
|
+
- **Guided key entry for connectors** — presets that need a secret
|
|
144
|
+
(Supabase, Railway, Vercel, GitHub, …) no longer dump a raw
|
|
145
|
+
`-e KEY= railway -- npx …` command line into your face. They ask one
|
|
146
|
+
masked "Paste your token" field per secret (URLs/IDs are shown unmasked so
|
|
147
|
+
you can see them) and build the server command automatically. Click-free,
|
|
148
|
+
paste-friendly, and the token never lands in the visible command.
|
|
149
|
+
- **Todos live in the sidebar** — the todo list no longer renders inside the
|
|
150
|
+
chat bubble; the chat uses the full area for the conversation while the
|
|
151
|
+
Todos tab shows live progress, opencode-style.
|
|
152
|
+
- **Memory graph (`/graph`)** — type `/graph` in the TUI to open a full-screen
|
|
153
|
+
interactive view of your project's memory graph. Nodes represent decisions,
|
|
154
|
+
conventions, bugfixes, and notes extracted from LOOM.md (and any
|
|
155
|
+
`.loom/graph/nodes/*.md` files); edges show `[[wikilink]]` relationships.
|
|
156
|
+
The graph auto-generates from your memory files and saves a `graph.json`
|
|
157
|
+
to `.loom/graph/` for fast reload. Navigate with arrow keys, PgUp/PgDn,
|
|
158
|
+
Home/End; press ESC to return to the chat view. In the CLI, `/graph`
|
|
159
|
+
prints a text summary and writes `graph.json` for the TUI to consume.
|
|
160
|
+
- **opencode-scale provider registry (models.dev)** — Loom now supports the
|
|
161
|
+
same 180+ providers as opencode via the models.dev dataset, fetched once and
|
|
162
|
+
cached at `~/.loom/models-dev.json` (7-day freshness). Registry providers
|
|
163
|
+
become OpenAI-compatible runtimes with their real model lists, prices, and
|
|
164
|
+
context windows; `/models` shows every model grouped by provider once the
|
|
165
|
+
provider's key env var is set (`/connect` hints the exact var name).
|
|
166
|
+
- Built-in providers (Anthropic, OpenAI, NVIDIA, Google, OpenRouter, Token
|
|
167
|
+
Router, Local) always win the merge; registry entries without models are
|
|
168
|
+
skipped; `/providers` lists everything.
|
|
169
|
+
- The provider picker fetches the registry on first open and re-opens with
|
|
170
|
+
the full list when a fetch lands mid-session.
|
|
171
|
+
- `/connect <provider>` in the CLI accepts any registry provider (not just
|
|
172
|
+
the built-in five).
|
|
173
|
+
- **Editor integration via the Agent Client Protocol (ACP)** — `loom acp`
|
|
174
|
+
subprocess mode (JSON-RPC over stdio) so Zed, JetBrains, and Neovim
|
|
175
|
+
(Avante.nvim / CodeCompanion.nvim) can drive Loom as their coding agent.
|
|
176
|
+
- `docs/acp.md`: transport + methods reference, Zed/JetBrains/Neovim configs,
|
|
177
|
+
protocol walkthrough, ACP Registry publishing notes.
|
|
178
|
+
- `scripts/acp-smoke.js`: end-to-end self-test client (`npm run smoke:acp`)
|
|
179
|
+
that drives initialize → connect → sendChatRequest → fetchAgentEvent.
|
|
180
|
+
- **Web interface** — `loom web` runs Loom in the browser via a zero-
|
|
181
|
+
dependency Node HTTP server that serves a single-page UI and drives the same
|
|
182
|
+
core `Session` loop as the TUI/ACP. Same providers, tools, MCP servers, and
|
|
183
|
+
saved sessions; chat streams live over Server-Sent Events.
|
|
184
|
+
- The UI is terminal-native and on-brand: warm-charcoal + terracotta palette
|
|
185
|
+
lifted from the TUI themes, monospace-led typography, a **thread rail**
|
|
186
|
+
connecting turns in the transcript, a **status row** (provider · model ·
|
|
187
|
+
budget · live stream rate) as the header, light/dark toggle, escape-first
|
|
188
|
+
markdown renderer with copy buttons, and responsive down to mobile.
|
|
189
|
+
- Flags: `--port`, `--hostname`, `--mdns` / `--mdns-domain` (implies
|
|
190
|
+
`0.0.0.0`), `--cors`, `--no-open`; config-file `server` block; CLI flags
|
|
191
|
+
take precedence.
|
|
192
|
+
- Password auth via `LOOM_SERVER_PASSWORD` / `LOOM_SERVER_USERNAME` (cookie
|
|
193
|
+
`loom_token`); mDNS advertising via the new `bonjour-service` dependency.
|
|
194
|
+
- `loom attach <url>`: line-mode terminal client that shares the running
|
|
195
|
+
web server's sessions/state (the OpenTUI SolidJS app still runs in-process).
|
|
196
|
+
- `src/web/web-server.js`, `src/web/index.html`, `src/web/attach.js`;
|
|
197
|
+
- `src/web/web-server.test.js` (13 tests: options, health, sessions, auth,
|
|
198
|
+
CORS, mDNS) registered in `test:unit`.
|
|
199
|
+
- `docs/web.md`.
|
|
200
|
+
- **Configurable keybinds** — every TUI key is rebindable from
|
|
201
|
+
`~/.loom/tui.json` (`keybinds`, `leader`, `leader_timeout`), opencode-style:
|
|
202
|
+
`<leader>X` prefix, string/array/`{key}`/`"none"`/`false` binding values,
|
|
203
|
+
and opencode-compatible action aliases (`session_interrupt`,
|
|
204
|
+
`prompt.autocomplete.next`, `dialog.select.*`, …).
|
|
205
|
+
- All legacy defaults preserved; `modal_cancel` mirrors
|
|
206
|
+
`session_interrupt` so ESC keeps clearing the input and closing dialogs.
|
|
207
|
+
- `/keybinds` prints every action with its current key; unknown action
|
|
208
|
+
names are reported as warnings.
|
|
209
|
+
- `src/tui/keybinds.ts` (dependency-free engine, `reload()` reads
|
|
210
|
+
`LOOM_CONFIG_DIR`), table-driven dispatch in `App.tsx`, dialogs wired
|
|
211
|
+
through the engine.
|
|
212
|
+
- `src/tui/keybinds.test.ts` (16 tests) registered in `test:unit`;
|
|
213
|
+
interactive suite covers custom palette key, custom leader,
|
|
214
|
+
`session_interrupt` rebind, `modal_cancel` mirror, disabled leader, and
|
|
215
|
+
config restore (61 → 62 tests).
|
|
216
|
+
- `docs/keybinds.md`.
|
|
217
|
+
- **Claude-compatible `/mcp add` one-liner** — add any stdio MCP server with
|
|
218
|
+
`claude mcp add` syntax: `/mcp add [-e KEY=V] <name> [--] <command> [args...]`
|
|
219
|
+
(e.g. `/mcp add stm32 -- "C:\stm32-mcp\.venv\Scripts\python.exe" -m
|
|
220
|
+
stm32_mcp.server`). The `--` separator is optional; slash commands now
|
|
221
|
+
tokenize quoted paths so paths with spaces survive; `-e`/`--env` flags set
|
|
222
|
+
server env vars, and `$KEY` placeholders in args resolve from them.
|
|
223
|
+
- **One-line MCP/connector adding in the TUI** — the four-field add-server
|
|
224
|
+
form is gone. In the `/mcp` and `/connectors` browsers, `A` now opens a
|
|
225
|
+
single-line input using the same `claude mcp add` syntax (leading `add` /
|
|
226
|
+
`mcp add` forgiven); presets open pre-filled with their `-e KEY=` placeholders
|
|
227
|
+
and the caret parked right after the last `=` (←/→/Home/End to move), so
|
|
228
|
+
filling a token is a single typing burst. `mcpAddLineCmd`
|
|
229
|
+
in `src/core/plugin-cmd.js` is the shared engine for both the slash command
|
|
230
|
+
and the modal. `src/mcp/mcp-manager.test.js` (+4 tests) registered in
|
|
231
|
+
`test:unit`.
|
|
232
|
+
- **OpenCode-style permission system**.
|
|
233
|
+
- `config.permission` tree with per-key rules, granular pattern objects
|
|
234
|
+
(last-match-wins, `*`/`?` wildcards, `~`/`$HOME` expansion),
|
|
235
|
+
per-agent overrides via `agent.<id>.permission`, and sane defaults
|
|
236
|
+
(most tools allow; `edit`/`bash`/`task`/`skill` ask; `read` denies
|
|
237
|
+
`*.env`/`*.env.*` except `*.env.example`).
|
|
238
|
+
- Gates every tool call in the session loop (`resolve` → allow/ask/deny),
|
|
239
|
+
plus `external_directory` checks for paths outside the working directory
|
|
240
|
+
and a `doom_loop` guard that flags three identical tool calls in a row.
|
|
241
|
+
- `loom --auto` / `/permissions auto` to auto-approve `ask` results
|
|
242
|
+
(explicit denies still block); muted `auto` indicator in the status row.
|
|
243
|
+
- `src/core/permissions.test.js` (10 tests covering defaults, precedence,
|
|
244
|
+
wildcards, expansions, external_directory, doom_loop, legacy API).
|
|
245
|
+
- **New-file writes are visible** — a `write` (or any tool) that creates a
|
|
246
|
+
brand-new file now renders its own block titled `# Wrote {path}` with the
|
|
247
|
+
fresh content marked `+` (previously new files showed nothing, opencode's
|
|
248
|
+
Edit-only rule made a new file indistinguishable from "nothing happened").
|
|
249
|
+
- **Interleaved parts, opencode-style** — the assistant message is no longer
|
|
250
|
+
a fixed layout ("thinking on top, tools below, answer last"). Every
|
|
251
|
+
reasoning, tool and text event streams as its OWN part in exact arrival
|
|
252
|
+
order, so a turn reads as a flow: `⠋ Thinking` → `→ Read src/a.ts` →
|
|
253
|
+
a *second* `⠋ Thinking` below it → `~ Preparing edit...` → the
|
|
254
|
+
`← Edit src/b.ts` patch → the reply — the model "thinks on the read, then
|
|
255
|
+
decides, then edits, then answers". Each settled reasoning part collapses
|
|
256
|
+
to its own clickable `+ Thought` line (expandable independently), tool
|
|
257
|
+
rows keep one-per-line persistence, `~ Preparing edit...` runs in place
|
|
258
|
+
and flips to the patch block (`← Edit` / `# Wrote`) when the diff lands,
|
|
259
|
+
and text appears where the model actually said it.
|
|
260
|
+
- **Thinking collapsible mid-turn** — the streaming `⠋ Thinking` header is
|
|
261
|
+
clickable while the turn is still running: clicking collapses the live
|
|
262
|
+
body (the header stays), clicking again reopens it; after the turn each
|
|
263
|
+
part still settles to its clickable `+ Thought · Ns` line (opencode
|
|
264
|
+
toggles reasoning at any time, even mid-stream).
|
|
265
|
+
- **Tool-use cap removed** — the 50-iteration `MAX_TOOL_ITERATIONS` ceiling
|
|
266
|
+
(and its "(reached tool limit…)" failure message) is gone; a turn runs
|
|
267
|
+
until the model stops calling tools. Esc-interrupt remains the stop
|
|
268
|
+
switch for runaway loops, and the existing doom-loop guard (three
|
|
269
|
+
identical calls in a row) still asks/denies as configured.
|
|
270
|
+
- **Tool output blocks** — a finished `bash` call with output swaps its row
|
|
271
|
+
for the `$ command` block (opencode's Shell BlockTool), collapsed to the
|
|
272
|
+
first 10 lines with a click-to-expand hint; ANY other tool (MCP, custom)
|
|
273
|
+
renders through the generic fallback as a `# {tool} {args}` block with its
|
|
274
|
+
output (3-line preview). Quiet generic tools without output keep their
|
|
275
|
+
single `⚙ label` row. Completed blocks persist in the transcript.
|
|
276
|
+
- **`/details` toggle** — completed tool parts hide when tool details are
|
|
277
|
+
off (opencode's `shouldHide`): running rows, errors, and pending rows
|
|
278
|
+
always stay. Default is on. The chat re-renders from a filtered message
|
|
279
|
+
stream, so the toggle applies instantly without stale rows.
|
|
280
|
+
|
|
281
|
+
### Fixed
|
|
282
|
+
- **Chat froze / crashed on later messages** — an orphan raw-text node
|
|
283
|
+
(same-line whitespace between two JSX elements inside a box) made the
|
|
284
|
+
reconciler throw "Orphan text error" on every message append after the
|
|
285
|
+
splash left, killing the renderer and the test suite mid-run.
|
|
286
|
+
- **`/details` never hid tool rows** — the hide rule read the toggle inside
|
|
287
|
+
the chat subtree, which never re-renders on that signal; the filter now
|
|
288
|
+
lives in a memo over the message stream at the app root, the one path
|
|
289
|
+
that provably re-renders the chat.
|
|
290
|
+
- **Splash logo corruption** — the "LOOM CODE" box-drawing logo had been
|
|
291
|
+
mojibaked by an encoding round-trip (scattered letters on the splash
|
|
292
|
+
screen); restored byte-exact from the last clean tree.
|
|
293
|
+
- **`/restore` was slow and dangerous** — every prompt snapshotted the whole
|
|
294
|
+
project tree into `~/.loom/restore.json` (tens of MB), so `/restore` froze
|
|
295
|
+
for seconds parsing it; worse, files too big to snapshot (> 1 MB) were
|
|
296
|
+
recorded as `null` and then **deleted** on restore (silent data loss).
|
|
297
|
+
Snapshots now cap at 200 KB per file / 2 MB per point, uncaptured files are
|
|
298
|
+
recorded as "leave alone" and never touched by a restore, and the store
|
|
299
|
+
self-compacts on load (oldest points dropped until the file fits an 8 MB
|
|
300
|
+
budget). The existing oversized restore file is compacted automatically on
|
|
301
|
+
the first load.
|
|
302
|
+
- **`/sessions` never jumped** — it only printed a text list of session ids.
|
|
303
|
+
It now opens a searchable picker; Enter loads the chosen session into the
|
|
304
|
+
live chat (same code path as `loom -s <id>` resume on start).
|
|
305
|
+
|
|
306
|
+
### Removed
|
|
307
|
+
- **15 unused slash commands** — `/share`, `/export`, `/undo`, `/redo`,
|
|
308
|
+
`/reset`, `/compact`, `/doctor`, `/diff`, `/debug`, `/editor`, `/fork`,
|
|
309
|
+
`/format`, `/lsp`, `/init`, `/keybinds` are gone from the slash popup and
|
|
310
|
+
the help screen. Kept: build/plan/chat, connect, key, baseurl, model,
|
|
311
|
+
models, providers, status, usage, budget, new, clear, restore, settings,
|
|
312
|
+
sessions, thinking, details, theme, memory, graph, permissions, skills,
|
|
313
|
+
mcp, connectors, help, agents, exit.
|
|
314
|
+
|
|
315
|
+
## [1.2.0] — 2026-08-12
|
|
316
|
+
|
|
317
|
+
### Added
|
|
318
|
+
- **agent system** (`/agents`).
|
|
319
|
+
- Three **primary agents** matched to the active mode: `build` (all tools),
|
|
320
|
+
`plan` (read-only + delegation), `chat` (no tools).
|
|
321
|
+
- Three **subagents**: `explore` (read-only codebase search),
|
|
322
|
+
`scout` (external/web research), `general` (autonomous, all tools except
|
|
323
|
+
delegation). Subagents can never call the `task` tool, so delegation always
|
|
324
|
+
terminates (no recursion).
|
|
325
|
+
- **`task` tool** — the primary agent calls it automatically when a subtask
|
|
326
|
+
warrants a focused subagent. Progress streams into a dedicated chat panel
|
|
327
|
+
showing the child's status, tool log, and streamed text.
|
|
328
|
+
- **`@agent` mentions** — prefix a message with `@explore`, `@scout`, etc. to
|
|
329
|
+
delegate the whole turn manually. The mention is stripped from the rendered
|
|
330
|
+
user bubble; type `@` for an autocomplete of available subagents.
|
|
331
|
+
- **Custom agents** — define your own subagents or override/disable built-ins
|
|
332
|
+
via `~/.loom/config.json` → `agents`. Tool patterns use last-match-wins
|
|
333
|
+
semantics (`["*"]`, `["read","glob"]`, `["*","!task"]`, `["mcp__*"]`).
|
|
334
|
+
- `/agents` slash command listing the active registry (id, mode, tools, model).
|
|
335
|
+
- Sidebar Todos tab — markdown checklists (`- [x]`, `[ ]`, `[+]`, `[-]`,
|
|
336
|
+
`1. [~]`) the model writes in its reply now drive the sidebar todo list and
|
|
337
|
+
are clickable to toggle done; reads/writes back to the session todo store.
|
|
338
|
+
|
|
339
|
+
### Changed
|
|
340
|
+
- Sidebar relayouted for deterministic rendering under the OpenTUI yoga layout:
|
|
341
|
+
info rows, companion, tabs, and content area are now pinned with explicit
|
|
342
|
+
heights and `flexShrink={0}` so the pet's speech-bubble animation no longer
|
|
343
|
+
overlaps or clips the todo/file rows.
|
|
344
|
+
- Companion pet is now a fixed-height block (height 7) so the sidebar content
|
|
345
|
+
area stays stable whether or not the pet is speaking.
|
|
346
|
+
- Interactive test suite hardened with a `waitForFrame` polling helper, fixing
|
|
347
|
+
flaky frame-capture assertions (`child findings`, `finished`, `[free]`,
|
|
348
|
+
`+Thought` toggle).
|
|
349
|
+
|
|
350
|
+
### Fixed
|
|
351
|
+
- `setSidebarTab` was missing from `Sidebar.tsx` imports — clicking a tab threw
|
|
352
|
+
`ReferenceError`, which cascaded and killed the interactive suite at test 27.
|
|
353
|
+
- OpenTUI `TextNode` crash on nested `<text>` inside `<text>` — todo rows now
|
|
354
|
+
emit a single string with a row color instead of nested styled elements.
|
|
355
|
+
- Sidebar text-garble (`Model:er:`, overlapping todo rows) caused by yoga
|
|
356
|
+
shrinking text nodes to zero width — explicit `width={34}` on todo/file rows
|
|
357
|
+
and explicit `height={1}` on info rows.
|
|
358
|
+
|
|
359
|
+
### Tests
|
|
360
|
+
- Interactive suite: **58/58 passing** (splash, slash popup, paste, busy-submit
|
|
361
|
+
hold, caret editing, markdown rendering, permission popup, `/budget`,
|
|
362
|
+
`/skills`, `/mcp`, `/connectors`, sidebar todos/files, `/agents`,
|
|
363
|
+
`@explore` delegation, bash diff detection, restore points, model picker,
|
|
364
|
+
MCP seeding, themes, speed hardening, multiline, caret editing, `/clear`).
|
|
365
|
+
- Unit suite: **97 pass / 2 skip / 0 fail** across 8 files, including the new
|
|
366
|
+
`src/core/agents.test.js` (registry shape, tool-pattern filters, config
|
|
367
|
+
merge/disable, `runSubagent` error paths, usage accumulation, agent-turn
|
|
368
|
+
gating).
|
|
369
|
+
- `lint:core` (`tsc -p tsconfig.core.json`) clean — strict JSDoc typing across
|
|
370
|
+
`src/core`, `src/providers`, `src/mcp`, `src/skills`, `src/tools`,
|
|
371
|
+
`src/config`.
|
|
372
|
+
|
|
373
|
+
### Removed
|
|
374
|
+
- GitLab CI configuration (`.gitlab-ci.yml`) — the project is no longer using
|
|
375
|
+
GitLab CI going forward.
|
|
376
|
+
|
|
377
|
+
## [1.1.0]
|
|
378
|
+
|
|
379
|
+
- Drag-to-copy selections, paste support, blinking cursor.
|
|
380
|
+
- Companion pet (OpenPets) integration.
|
|
381
|
+
- `/themes`, `/companion`, `/budget` (free/cheap/best/auto), `/skills`,
|
|
382
|
+
`/mcp`, `/connectors` slash commands.
|
|
383
|
+
- One-shot print mode: `loom -p "query"`.
|
|
384
|
+
|
|
385
|
+
## [1.0.0]
|
|
386
|
+
|
|
387
|
+
- Initial public release.
|
|
388
|
+
- Full OpenTUI interface with slash commands and autocomplete.
|
|
389
|
+
- Build / Plan / Chat modes.
|
|
390
|
+
- Multi-provider support: Anthropic, OpenAI, NVIDIA, Google Gemini, OpenRouter,
|
|
391
|
+
Local (Ollama).
|
|
392
|
+
- Built-in tools: `read`, `write`, `edit`, `bash`, `grep`, `glob`, `webfetch`,
|
|
393
|
+
`todowrite`.
|
|
394
|
+
- Agentic tool loop (multiple tool calls per turn, up to 50 iterations).
|
|
395
|
+
- MCP (Model Context Protocol) server integration.
|
|
396
|
+
- Session memory, `/undo`, `/redo`, `/compact`, `/reset`, `/fork`, `/share`.
|
|
397
|
+
- LOOM.md project memory file.
|
|
398
|
+
- Usage & billing tracking per session + lifetime.
|
|
399
|
+
|
|
400
|
+
[1.2.0]: https://npmjs.com/package/loom-code
|
|
401
|
+
[1.1.0]: https://npmjs.com/package/loom-code
|
|
402
|
+
[1.0.0]: https://npmjs.com/package/loom-code
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Toshal Kumbhar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|