@loompad/cli 0.1.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/ARCHITECTURE.md +211 -0
- package/CHANGELOG.md +470 -0
- package/LICENSE +21 -0
- package/README.md +630 -0
- package/dist/adapters/base.d.ts +71 -0
- package/dist/adapters/base.js +187 -0
- package/dist/adapters/base.js.map +1 -0
- package/dist/adapters/bridges/antigravity.d.ts +53 -0
- package/dist/adapters/bridges/antigravity.js +106 -0
- package/dist/adapters/bridges/antigravity.js.map +1 -0
- package/dist/adapters/bridges/cdp.d.ts +82 -0
- package/dist/adapters/bridges/cdp.js +222 -0
- package/dist/adapters/bridges/cdp.js.map +1 -0
- package/dist/adapters/bridges/gui-chat.d.ts +113 -0
- package/dist/adapters/bridges/gui-chat.js +498 -0
- package/dist/adapters/bridges/gui-chat.js.map +1 -0
- package/dist/adapters/bridges/kiro.d.ts +19 -0
- package/dist/adapters/bridges/kiro.js +22 -0
- package/dist/adapters/bridges/kiro.js.map +1 -0
- package/dist/adapters/bridges/profiles.d.ts +82 -0
- package/dist/adapters/bridges/profiles.js +125 -0
- package/dist/adapters/bridges/profiles.js.map +1 -0
- package/dist/adapters/claude-code.d.ts +26 -0
- package/dist/adapters/claude-code.js +209 -0
- package/dist/adapters/claude-code.js.map +1 -0
- package/dist/adapters/codex.d.ts +51 -0
- package/dist/adapters/codex.js +291 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/echo.d.ts +20 -0
- package/dist/adapters/echo.js +79 -0
- package/dist/adapters/echo.js.map +1 -0
- package/dist/adapters/grok.d.ts +61 -0
- package/dist/adapters/grok.js +217 -0
- package/dist/adapters/grok.js.map +1 -0
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/index.js +33 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/opencode.d.ts +64 -0
- package/dist/adapters/opencode.js +426 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/cli/common.d.ts +11 -0
- package/dist/cli/common.js +40 -0
- package/dist/cli/common.js.map +1 -0
- package/dist/cli/doctor.d.ts +12 -0
- package/dist/cli/doctor.js +181 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +641 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/tui-model.d.ts +73 -0
- package/dist/cli/tui-model.js +312 -0
- package/dist/cli/tui-model.js.map +1 -0
- package/dist/cli/tui.d.ts +15 -0
- package/dist/cli/tui.js +451 -0
- package/dist/cli/tui.js.map +1 -0
- package/dist/cli/ui.d.ts +10 -0
- package/dist/cli/ui.js +135 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/core/ades.d.ts +79 -0
- package/dist/core/ades.js +130 -0
- package/dist/core/ades.js.map +1 -0
- package/dist/core/baton.d.ts +37 -0
- package/dist/core/baton.js +92 -0
- package/dist/core/baton.js.map +1 -0
- package/dist/core/brain-extract.d.ts +111 -0
- package/dist/core/brain-extract.js +248 -0
- package/dist/core/brain-extract.js.map +1 -0
- package/dist/core/brain-index.d.ts +128 -0
- package/dist/core/brain-index.js +338 -0
- package/dist/core/brain-index.js.map +1 -0
- package/dist/core/brain.d.ts +214 -0
- package/dist/core/brain.js +425 -0
- package/dist/core/brain.js.map +1 -0
- package/dist/core/claude-cli.d.ts +12 -0
- package/dist/core/claude-cli.js +47 -0
- package/dist/core/claude-cli.js.map +1 -0
- package/dist/core/distill.d.ts +19 -0
- package/dist/core/distill.js +88 -0
- package/dist/core/distill.js.map +1 -0
- package/dist/core/eventlog.d.ts +43 -0
- package/dist/core/eventlog.js +211 -0
- package/dist/core/eventlog.js.map +1 -0
- package/dist/core/git.d.ts +203 -0
- package/dist/core/git.js +460 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/logbook.d.ts +59 -0
- package/dist/core/logbook.js +118 -0
- package/dist/core/logbook.js.map +1 -0
- package/dist/core/memory.d.ts +34 -0
- package/dist/core/memory.js +124 -0
- package/dist/core/memory.js.map +1 -0
- package/dist/core/notify.d.ts +11 -0
- package/dist/core/notify.js +30 -0
- package/dist/core/notify.js.map +1 -0
- package/dist/core/projection.d.ts +20 -0
- package/dist/core/projection.js +96 -0
- package/dist/core/projection.js.map +1 -0
- package/dist/core/registry.d.ts +76 -0
- package/dist/core/registry.js +147 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/router.d.ts +42 -0
- package/dist/core/router.js +99 -0
- package/dist/core/router.js.map +1 -0
- package/dist/core/routes.d.ts +93 -0
- package/dist/core/routes.js +399 -0
- package/dist/core/routes.js.map +1 -0
- package/dist/core/search.d.ts +63 -0
- package/dist/core/search.js +0 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/setup.d.ts +70 -0
- package/dist/core/setup.js +250 -0
- package/dist/core/setup.js.map +1 -0
- package/dist/core/suggestions.d.ts +15 -0
- package/dist/core/suggestions.js +38 -0
- package/dist/core/suggestions.js.map +1 -0
- package/dist/core/worktree.d.ts +32 -0
- package/dist/core/worktree.js +83 -0
- package/dist/core/worktree.js.map +1 -0
- package/dist/daemon/app-page.d.ts +32 -0
- package/dist/daemon/app-page.js +6363 -0
- package/dist/daemon/app-page.js.map +1 -0
- package/dist/daemon/auth.d.ts +51 -0
- package/dist/daemon/auth.js +135 -0
- package/dist/daemon/auth.js.map +1 -0
- package/dist/daemon/board.d.ts +117 -0
- package/dist/daemon/board.js +202 -0
- package/dist/daemon/board.js.map +1 -0
- package/dist/daemon/brand-icons.d.ts +29 -0
- package/dist/daemon/brand-icons.js +44 -0
- package/dist/daemon/brand-icons.js.map +1 -0
- package/dist/daemon/client.d.ts +127 -0
- package/dist/daemon/client.js +237 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/geist-font.d.ts +10 -0
- package/dist/daemon/geist-font.js +783 -0
- package/dist/daemon/geist-font.js.map +1 -0
- package/dist/daemon/guards.d.ts +18 -0
- package/dist/daemon/guards.js +40 -0
- package/dist/daemon/guards.js.map +1 -0
- package/dist/daemon/linear.d.ts +59 -0
- package/dist/daemon/linear.js +117 -0
- package/dist/daemon/linear.js.map +1 -0
- package/dist/daemon/push.d.ts +22 -0
- package/dist/daemon/push.js +58 -0
- package/dist/daemon/push.js.map +1 -0
- package/dist/daemon/runtime.d.ts +299 -0
- package/dist/daemon/runtime.js +1016 -0
- package/dist/daemon/runtime.js.map +1 -0
- package/dist/daemon/server.d.ts +125 -0
- package/dist/daemon/server.js +1728 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/tasks.d.ts +153 -0
- package/dist/daemon/tasks.js +264 -0
- package/dist/daemon/tasks.js.map +1 -0
- package/dist/daemon/terminals.d.ts +82 -0
- package/dist/daemon/terminals.js +372 -0
- package/dist/daemon/terminals.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/sdk.d.ts +17 -0
- package/dist/sdk.js +17 -0
- package/dist/sdk.js.map +1 -0
- package/dist/types.d.ts +236 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/docs/SETUP.md +265 -0
- package/docs/adapters.md +132 -0
- package/docs/design-system.md +245 -0
- package/docs/img/board-linear.png +0 -0
- package/docs/img/board-projects.png +0 -0
- package/docs/img/brain.png +0 -0
- package/docs/img/settings.png +0 -0
- package/docs/img/workspace.png +0 -0
- package/docs/integration-notes.md +76 -0
- package/docs/proposals/brain.md +262 -0
- package/package.json +84 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Loom are documented here.
|
|
4
|
+
|
|
5
|
+
The format loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Hardening (production-readiness pass)
|
|
11
|
+
|
|
12
|
+
- **Closed a DNS-rebinding path to the admin token.** `GET /api/bootstrap` handed
|
|
13
|
+
the admin token to any loopback socket; it now also requires a loopback `Host`
|
|
14
|
+
header, so a malicious page (whose hostname rebinds to 127.0.0.1 but is still
|
|
15
|
+
sent as the `Host`) is refused — while the genuine local console still boots.
|
|
16
|
+
- **WebSocket token out of the URL.** The durable bearer token rode in `/ws?token=`
|
|
17
|
+
(browser history, proxy logs); it now travels in a `loom.bearer.<token>`
|
|
18
|
+
subprotocol header, with `?token=` kept as a fallback for the CLI/native clients.
|
|
19
|
+
- **git flag-injection guard.** A ref/branch beginning with `-` (e.g. `-f`, which
|
|
20
|
+
would force-discard the working tree) is rejected in `checkout` and worktree add.
|
|
21
|
+
- **No phantom daemon on an occupied port.** `loom daemon` on a taken port now
|
|
22
|
+
reports "port already in use" and exits non-zero, instead of Express firing the
|
|
23
|
+
listen callback on EADDRINUSE and printing a false "listening".
|
|
24
|
+
- The **security model** section of the README is rewritten to match: the local
|
|
25
|
+
admin bootstrap and its rebinding check, the shared-host caveat, and the honest
|
|
26
|
+
note that a paired client can open a real shell (arbitrary code as the daemon
|
|
27
|
+
user) — bearer + tailnet is the boundary, so pair only devices you control.
|
|
28
|
+
|
|
29
|
+
### `loom tui` — a tabbed workspace, not just a thread
|
|
30
|
+
|
|
31
|
+
- The TUI is now four views: **Thread** (the streamed conversation), **Board**
|
|
32
|
+
(agents, your cards, issues and PRs in the four flow columns), **Brain** (the
|
|
33
|
+
memory the project has learned, grouped by kind with failures first and who
|
|
34
|
+
learned each), and **Diff** (the working tree — changed files and a colourised
|
|
35
|
+
patch). **`shift+tab`** cycles them, `/board /brain /diff /thread` and the
|
|
36
|
+
`ctrl+p` palette jump straight to one, `pgup/pgdn` scrolls, and the viewport
|
|
37
|
+
tracks a terminal resize. Everything the single pane did — routes, handoff,
|
|
38
|
+
`/pair` QR, interrupt, the palette — stays. Backed by real data (new
|
|
39
|
+
`brain`/`board` daemon-client methods) with pure, unit-tested formatters.
|
|
40
|
+
|
|
41
|
+
### Connect a phone — a QR from inside the app
|
|
42
|
+
|
|
43
|
+
- A **Connect a phone** button beside the terminal opens a modal with a QR (or a
|
|
44
|
+
copy link) and a **Local network / Tailnet** toggle. When the daemon is still
|
|
45
|
+
localhost-only, **Enable phone access** adds a *second listener* on the chosen
|
|
46
|
+
LAN/tailnet IP — localhost is never torn down, so there's no dropped-socket
|
|
47
|
+
window and none of the EADDRINUSE races a live rebind to `0.0.0.0` hits. The
|
|
48
|
+
local (loopback) window bootstraps the admin token, so it can pair phones with
|
|
49
|
+
no pairing dance of its own; a paired phone is never admin.
|
|
50
|
+
- **Client errors reach the Console.** `window.onerror` and unhandled rejections
|
|
51
|
+
now stream into the same Console tab as the daemon's own logs, with the error
|
|
52
|
+
dot — nothing dies silently in the browser.
|
|
53
|
+
|
|
54
|
+
### Brain — stronger injection, and it's genuinely shared
|
|
55
|
+
|
|
56
|
+
- The learned memory reaches every agent, not just Claude: the brief rides in
|
|
57
|
+
Grok's `--rules` (a real system channel), and for Codex/OpenCode it's framed as
|
|
58
|
+
an unmissable `LOOM SESSION MEMORY — authoritative, read first` block instead of
|
|
59
|
+
loose preamble. The brain is the **project's** — a fact one agent learns reaches
|
|
60
|
+
whoever takes the baton next ([`brain-shared` test](test/brain-shared.test.ts):
|
|
61
|
+
five agents, five prompts, one shared memory). An opt-in eval
|
|
62
|
+
(`LOOM_TEST_REAL=1`) checks a real model actually *uses* an injected brief.
|
|
63
|
+
|
|
64
|
+
### Native search — one palette over everything (⌘K)
|
|
65
|
+
|
|
66
|
+
- **⌘K / Ctrl+K** opens a command palette from anywhere, even mid-type. One box
|
|
67
|
+
searches across **commands** (go to Thread/Board/Brain, open a panel, New task,
|
|
68
|
+
Settings, toggle terminal/theme…), **agents** (talk to one), **files** and
|
|
69
|
+
**code** (the project's own `/find` + `/grep`), **conversations**, and
|
|
70
|
+
**worktrees** (jump to one — it `cd`s there in the terminal). Commands, agents
|
|
71
|
+
and worktrees filter instantly; the daemon-backed sections stream in as you
|
|
72
|
+
type. ↑↓ navigate a flat list, ↵ acts, esc closes. A ⌘K affordance sits in the
|
|
73
|
+
sidebar search.
|
|
74
|
+
|
|
75
|
+
### Connect GitHub from the status bar
|
|
76
|
+
|
|
77
|
+
- The bottom bar shows whether `gh` is signed in and as whom. When it isn't, a
|
|
78
|
+
**Connect GitHub** button runs the interactive `gh auth login --web` in the
|
|
79
|
+
real terminal (Loom never touches the token — gh stores it) and polls until
|
|
80
|
+
you're in, then lights the board up. The whole GitHub half (PRs, Projects,
|
|
81
|
+
review) depends on this being true, so it lives where you can see it.
|
|
82
|
+
|
|
83
|
+
### GitHub & Linear, native
|
|
84
|
+
|
|
85
|
+
- **One board, three sources.** A segmented control switches the Board between
|
|
86
|
+
**GitHub** (the live kanban), **Projects** (browse the owner's GitHub Project v2
|
|
87
|
+
boards, items laid out by their Status column), and **Linear** (recent issues).
|
|
88
|
+
- **Review and approve PRs in place.** Open a PR's diff in-app and post the three
|
|
89
|
+
reviewer verbs — comment, request changes, approve — through your own `gh`, signed
|
|
90
|
+
as you. Approve asks first, because it publishes.
|
|
91
|
+
- **Open a worktree from any task.** One click cuts a checked-out branch in its own
|
|
92
|
+
sibling directory: a PR worktree checks the branch out (forks included, via
|
|
93
|
+
`gh pr checkout` into a detached worktree); an issue worktree cuts a fresh branch.
|
|
94
|
+
- **File a Linear issue with a team selector.** Loom reads `LINEAR_API_KEY` from the
|
|
95
|
+
daemon's own environment and never stores it — the same bet it makes with `gh`. No
|
|
96
|
+
key → an honest "not connected", never a dead form.
|
|
97
|
+
|
|
98
|
+
### Settings — one sectioned screen
|
|
99
|
+
|
|
100
|
+
- The lone Setup modal is now **Settings**, with a nav rail: **Setup** (what the
|
|
101
|
+
machine still needs), **Diagnostics** (`loom doctor`, run live), **Updates** (build
|
|
102
|
+
rev + how far the checkout is behind its remote), **Preferences** (theme, brain
|
|
103
|
+
extractor, handoff brief style, default agent — read live, no restart), **Devices**
|
|
104
|
+
(paired clients, revoke, pair), and **About**.
|
|
105
|
+
- `/api/setup`, `/api/doctor`, and `/api/updates` moved behind the auth wall — they
|
|
106
|
+
inventory the machine, which matters the moment the daemon binds past localhost.
|
|
107
|
+
|
|
108
|
+
### The Brain learns on its own (mem0-style)
|
|
109
|
+
|
|
110
|
+
- After each turn a small Claude reads what changed and files typed memory **units**
|
|
111
|
+
— constraint, decision, convention, fact, failure — reconciled on write
|
|
112
|
+
(add / update / forget, never a growing blob), the approach mem0 pioneered, adapted
|
|
113
|
+
to Loom's event log. Every unit's evidence is verified against the turn before it's
|
|
114
|
+
kept. Retrieval unions an entity index with BM25.
|
|
115
|
+
- The **Brain tab** shows the learned units by kind; the extractor toggles off per
|
|
116
|
+
project in Settings.
|
|
117
|
+
|
|
118
|
+
### Composer
|
|
119
|
+
|
|
120
|
+
- The agent chip is a real **picker** now — click it to switch which agent the
|
|
121
|
+
composer talks to, instead of hunting the sidebar. Each control (attach, switch
|
|
122
|
+
agent, model, send) is its own labelled button, and the message box starts at two
|
|
123
|
+
lines, grows, then scrolls.
|
|
124
|
+
|
|
125
|
+
### Real agent output
|
|
126
|
+
|
|
127
|
+
- Agent **thinking** is shown in a collapsible block, kept apart from the reply, and
|
|
128
|
+
messages render as **rich markdown** (headings, lists, code, inline code) instead of
|
|
129
|
+
raw text.
|
|
130
|
+
|
|
131
|
+
### Board — everything in flight, and Tasks folded into it
|
|
132
|
+
|
|
133
|
+
- **The Tasks tab is gone.** The Board covers it: search issues and pull
|
|
134
|
+
requests from the board itself, in GitHub's own query language
|
|
135
|
+
(`is:issue is:open label:bug`), and **Start** still hands an issue to an
|
|
136
|
+
agent. Tabs are Thread | Board | Brain.
|
|
137
|
+
- **Add your own cards.** `+ Task`, or the `+` in any column — including Ready
|
|
138
|
+
to merge. These are yours, so the column *is* the state: dragging one really
|
|
139
|
+
moves it and it persists. That's the difference from a PR card, whose truth
|
|
140
|
+
belongs to GitHub and can only be pinned. Click a card's title to retitle it.
|
|
141
|
+
- Issues only appear when you search for them — a repo's whole backlog would
|
|
142
|
+
bury the work actually in flight. And asking for issues no longer also hands
|
|
143
|
+
back unrelated PRs: `gh pr list --search "is:issue …"` ignores the qualifier
|
|
144
|
+
and returns open PRs, so the board now asks for what you actually asked for.
|
|
145
|
+
|
|
146
|
+
### Board — everything in flight, in one place
|
|
147
|
+
|
|
148
|
+
- A **Board** tab replaces Routes: four columns — working → needs you → in
|
|
149
|
+
review → ready to merge — holding every piece of live work in the project.
|
|
150
|
+
- Cards are derived from real state, never stored. Loom supplies the ones with
|
|
151
|
+
no PR yet (which agent is running, which is blocked on a question); the
|
|
152
|
+
project's GitHub remote supplies the rest through your own `gh`: draft,
|
|
153
|
+
review pending, CI failed, changes requested, approved. A card shows the
|
|
154
|
+
agent's own logo, the branch, and links to the PR.
|
|
155
|
+
- **Drag a card** and it stays where you put it (per project, across reloads).
|
|
156
|
+
A pin only moves a card — the badge keeps reporting what GitHub and the daemon
|
|
157
|
+
actually say, because a drag can't approve a review or turn a red build green.
|
|
158
|
+
Drop it back where its state says it belongs, or click its `pinned` mark, and
|
|
159
|
+
it goes back to being placed by reality.
|
|
160
|
+
- No remote, or no `gh`? The agent half of the board is ours and still real —
|
|
161
|
+
it renders, with a line saying why the pull requests are missing.
|
|
162
|
+
- **Routes lost its tab, not its home**: named pipelines and custom step lists
|
|
163
|
+
both live in the New task modal (several agents *is* a pipeline), and live
|
|
164
|
+
route state plus abort live in the Source Control rail. The mobile route sheet
|
|
165
|
+
is untouched.
|
|
166
|
+
|
|
167
|
+
### Chats, and roles you name yourself
|
|
168
|
+
|
|
169
|
+
- **A project holds conversations.** The sidebar nests chats under each
|
|
170
|
+
project; create, rename (double-click), and forget them. Everything else
|
|
171
|
+
stays shared — one brain, one baton, one working tree. Only the talking is
|
|
172
|
+
split. Forgetting a chat unlists it and nothing more: the log is append-only
|
|
173
|
+
and the brain is built from all of it, so deleting a conversation shouldn't
|
|
174
|
+
quietly rewrite what the project decided.
|
|
175
|
+
- Agents moved to the rail's roster, which is where they belong — an agent
|
|
176
|
+
works the whole project, not one conversation.
|
|
177
|
+
- **Roles are free text.** Not planner|executor|reviewer|general any more: call
|
|
178
|
+
an agent "architect" or "the one that writes docs". Click the role and type.
|
|
179
|
+
Those three names still mean something if you use them (they seed the default
|
|
180
|
+
ship pipeline, the rules router prefers a reviewer last, a route step matches
|
|
181
|
+
by role) and nothing if you don't.
|
|
182
|
+
- The event log gained a `chat` column. **The first cut of that migration
|
|
183
|
+
destroyed logs**: the chat index was created before the column was added,
|
|
184
|
+
sqlite threw "no such column", and `EventLog.open` caught it and quietly
|
|
185
|
+
started an *empty* JSONL log beside a database full of history. Migrate
|
|
186
|
+
first, then index — and that catch now only covers the one case it was for
|
|
187
|
+
(a runtime with no `node:sqlite`). If the module is there and the log won't
|
|
188
|
+
open, it throws: losing the thread is worse than failing loudly.
|
|
189
|
+
|
|
190
|
+
### ADE brand marks
|
|
191
|
+
|
|
192
|
+
- Claude Code, Antigravity, opencode, Kiro and Codex now appear as their own
|
|
193
|
+
logos wherever an agent does: the sidebar, the thread, agent chips, the New
|
|
194
|
+
task modal, and board cards. Rendered from
|
|
195
|
+
[@lobehub/icons](https://github.com/lobehub/lobe-icons) (MIT) by
|
|
196
|
+
`scripts/gen-brand-icons.mjs` and frozen into an SVG sprite — the web app has
|
|
197
|
+
no build step and no CDN, so it can't import React components.
|
|
198
|
+
- Keyed by adapter *kind*, not by name: you can call an agent anything, but its
|
|
199
|
+
kind is what it is. A kind with no mark (a custom adapter, `echo`) keeps the
|
|
200
|
+
hue monogram rather than borrowing someone else's brand.
|
|
201
|
+
- **Bridges are visible at last.** Antigravity is a bridge, and every view
|
|
202
|
+
filtered bridges out — so a configured bridge rendered nowhere and Loom looked
|
|
203
|
+
like it had ignored your config. Bridges now show in the sidebar, marked as
|
|
204
|
+
bridges and not clickable, because they never hold the baton.
|
|
205
|
+
|
|
206
|
+
### Tasks — start work from a real GitHub issue
|
|
207
|
+
|
|
208
|
+
- New **Tasks** tab per project: the repo's open issues and pull requests in a
|
|
209
|
+
sortable table (id, title, author, labels, assignees, status, updated), with
|
|
210
|
+
an `Issues`/`PRs` switch, `Open` / `Assigned to me` filters, a query box that
|
|
211
|
+
accepts GitHub's own search syntax (`assignee:@me is:issue is:open`), and
|
|
212
|
+
pagination. Labels wear the colours GitHub reports for them.
|
|
213
|
+
- **Start** on any row opens Create task with the issue number, title, and URL
|
|
214
|
+
already drafted, so a GitHub issue becomes an agent task in two clicks.
|
|
215
|
+
- Data comes from **your own `gh` CLI**, which already holds your auth — Loom
|
|
216
|
+
needs no token, no OAuth app, no PAT of its own. It shells out the same way
|
|
217
|
+
the adapters shell out to the coding agents you have installed.
|
|
218
|
+
- When it can't list, it says why (`gh` not installed, signed out, or no GitHub
|
|
219
|
+
remote) instead of showing an empty table that reads as "no issues". The
|
|
220
|
+
fetch is capped at 60, and a capped list says so rather than letting the last
|
|
221
|
+
page imply it's the last issue. GitLab and Linear appear disabled — the row
|
|
222
|
+
shows which providers exist and which one Loom can actually read.
|
|
223
|
+
|
|
224
|
+
- **The phone gets Tasks too**, which is where the feature always wanted to
|
|
225
|
+
live: see an issue on the train, tap it, confirm, and an agent is working
|
|
226
|
+
before you look up. Same daemon endpoint, same honest unavailable states. It
|
|
227
|
+
asks for confirmation first — the desktop shows the brief in an editable field
|
|
228
|
+
before it goes, and a tap on a scrolling list has no such beat.
|
|
229
|
+
|
|
230
|
+
### New project
|
|
231
|
+
|
|
232
|
+
- A **New project** button in the sidebar (shortcut <kbd>P</kbd>) with a proper
|
|
233
|
+
modal, replacing the inline path field. It reports which ADEs were detected
|
|
234
|
+
on the host after registering, and refuses a bad path out loud.
|
|
235
|
+
- In the desktop app the folder comes from a **native macOS picker**; in a
|
|
236
|
+
browser the path is typed, since the daemon may be on another host. The
|
|
237
|
+
preload exposes only that one call — no `require`, no ipc passthrough.
|
|
238
|
+
|
|
239
|
+
### Fixed
|
|
240
|
+
|
|
241
|
+
- The right rail showed the project you just navigated away from: it renders
|
|
242
|
+
from `state.project`, which only a fetch filled in, so every switch left it
|
|
243
|
+
one project behind until you touched it.
|
|
244
|
+
- **Enter did nothing on the pairing screen.** Paste a token, press Enter — the
|
|
245
|
+
entire gesture that screen exists for — and nothing happened; only the button
|
|
246
|
+
worked. Same in the route form. Both now submit on Enter, like every other
|
|
247
|
+
field in the app.
|
|
248
|
+
- The Tasks `Issues`/`PRs` toggle was dead while the first `gh` fetch was in
|
|
249
|
+
flight — the loading branch rendered the buttons but returned before wiring
|
|
250
|
+
them, so they were inert for exactly as long as anyone would be looking at
|
|
251
|
+
them.
|
|
252
|
+
- The GitHub mark in the Tasks provider row was an enabled button with hover and
|
|
253
|
+
a pointer cursor, and no handler. It's the only provider Loom reads, so it's
|
|
254
|
+
now the indicator it always was, not a control that does nothing.
|
|
255
|
+
- Dead code and CSS removed: an unused `dockShowing`, two write-only state hooks
|
|
256
|
+
no consumer ever read, and five rules for classes nothing renders. `route()`
|
|
257
|
+
now also clears `state.retheme`, which alone survived a view teardown holding
|
|
258
|
+
the previous render's terminals.
|
|
259
|
+
- **Tasks blamed a missing remote for every failure it didn't recognise.** A
|
|
260
|
+
timeout, a 500, a rate limit, or a private repo you lack scope for all told
|
|
261
|
+
you to "add a GitHub remote and reload" — with the remote right there. gh's
|
|
262
|
+
failures are now sorted by what gh actually prints, and the panel shows gh's
|
|
263
|
+
own words. Fixes a live mis-read too: gh's message for a *non-GitHub* remote
|
|
264
|
+
mentions `gh auth login`, so a GitLab remote was reported as "signed out".
|
|
265
|
+
- The terminal routes answered **429 "too many requests" for any failure to
|
|
266
|
+
start a shell**, and `/term/input` could hand a JSON client Express's HTML
|
|
267
|
+
error page. Only the session cap is a 429 now, and it says what the cap is.
|
|
268
|
+
- `cliAvailable()` had no timeout. It runs in front of HTTP handlers (Tasks
|
|
269
|
+
probes `gh` on every request), so one wedged `gh --version` hung that request
|
|
270
|
+
forever with no reply. Bounded at 5s, and the child is reaped.
|
|
271
|
+
|
|
272
|
+
### Documentation
|
|
273
|
+
|
|
274
|
+
- **Every SDK example told you to import from `loom-agents/sdk` — a package that
|
|
275
|
+
has never existed.** It's `threadloom/sdk`. `test/docs.test.ts` now checks that
|
|
276
|
+
the docs only import packages that resolve, and only names the SDK exports.
|
|
277
|
+
- `ARCHITECTURE.md` is the pre-build design record, and read as though it were
|
|
278
|
+
the current map: two surfaces instead of four, an iOS-first app over APNs
|
|
279
|
+
(it's Android-first over Expo), and settled questions still framed as risks.
|
|
280
|
+
It now says what it is, and the corrections are marked inline.
|
|
281
|
+
- `desktop/README.md` claimed the bootstrap was unit-tested (it wasn't — now it
|
|
282
|
+
is) and that `main.js` "only creates the window" (it owns the menu and the
|
|
283
|
+
folder-picker IPC).
|
|
284
|
+
- The `LOOM_*` environment variables are documented, and Loom's own
|
|
285
|
+
`LOOM_TERMINAL=1` marker is written down.
|
|
286
|
+
|
|
287
|
+
### Terminal — real PTYs
|
|
288
|
+
|
|
289
|
+
- Terminals now run on a real pseudo-terminal via **node-pty**, rendered with
|
|
290
|
+
**xterm.js**: the shell is on a tty, so it draws its own prompt, echoes, and
|
|
291
|
+
job control works — `^C`/`^Z`, `less`, `vim`, `htop`, window size. Verified
|
|
292
|
+
end to end in the desktop app: `$(tty)` resolves to a real device, `stty`
|
|
293
|
+
reports the fitted window, and `vi` repaints the alternate screen.
|
|
294
|
+
- node-pty is an **optionalDependency** with a probing loader, so a machine
|
|
295
|
+
that can't build it still installs Loom and quietly gets the previous
|
|
296
|
+
pipe-backed shell (`cd`/vars persist, `^C` works). `npm i -g threadloom`
|
|
297
|
+
never breaks. `LOOM_NO_PTY=1` forces the fallback, and CI runs the suite
|
|
298
|
+
both ways so it can't rot.
|
|
299
|
+
- Fixes a node-pty packaging bug: its prebuilt `spawn-helper` ships without the
|
|
300
|
+
executable bit, so every spawn died with a bare "posix_spawnp failed". The
|
|
301
|
+
loader repairs it, and proves it can spawn rather than trusting the require.
|
|
302
|
+
- Terminal input now travels over the project WebSocket (a tty needs a
|
|
303
|
+
round-trip per keystroke); sessions keep scrollback and replay it, so a
|
|
304
|
+
reload rejoins the session it left. Adds `/term/resize`, tab titles from OSC,
|
|
305
|
+
clipboard keys, and reports the active mode from `/api/health`.
|
|
306
|
+
- Terminal logic moved out of `server.ts` into `src/daemon/terminals.ts`.
|
|
307
|
+
|
|
308
|
+
### Security
|
|
309
|
+
|
|
310
|
+
- **A symlink inside a project could read files anywhere on disk.** The
|
|
311
|
+
Explorer endpoints resolved paths with `path.resolve`, which resolves
|
|
312
|
+
straight *through* symlinks, so the sandbox only stopped lexical `../`
|
|
313
|
+
traversal. Containment is now verified twice — lexically and again against
|
|
314
|
+
`fs.realpathSync`. Found by the new workspace tests.
|
|
315
|
+
|
|
316
|
+
### Testing
|
|
317
|
+
|
|
318
|
+
- `test/workspace.test.ts` (34 tests) covers the surfaces the desktop UI is
|
|
319
|
+
built on and previously had none: Explorer listing/reading/find, the sandbox
|
|
320
|
+
(traversal, absolute paths, symlink escape, unauthenticated access), and the
|
|
321
|
+
terminal end-to-end (streams output, exit codes, no sentinel leakage, `cd`
|
|
322
|
+
and variables persisting, terminal isolation, and Ctrl+C giving exit 130
|
|
323
|
+
while the shell survives).
|
|
324
|
+
- `test/desktop-app.test.ts` covers the Electron bootstrap, which had claimed to
|
|
325
|
+
be unit-tested without being it: the build-rev fingerprint (including that a
|
|
326
|
+
UI-only rebuild moves it), the stale-daemon decision (and that an
|
|
327
|
+
un-fingerprintable daemon is never killed), and the pairing handshake —
|
|
328
|
+
against a fake daemon, so no test can spawn or kill a real one. The
|
|
329
|
+
rev-mirrors-the-daemon check imports the built daemon's own `BUILD_REV`
|
|
330
|
+
rather than re-deriving it, so the two can't drift in agreement.
|
|
331
|
+
- **Two tests couldn't fail**, which is worse than not having them. The
|
|
332
|
+
interrupt test passed in pty mode with `PtySession.interrupt()` gutted: a pty
|
|
333
|
+
echoes what you type, so it matched `/alive/` on the echo of `echo alive`
|
|
334
|
+
while `sleep 30` still held the shell. And `LOOM_EXPECT_PTY` was referenced
|
|
335
|
+
once, set nowhere, and inverted — no value of it could fail the pty suite
|
|
336
|
+
when node-pty was absent, so a failed build (it's optional, and has no Linux
|
|
337
|
+
prebuild) would have downgraded every pty test to a no-op with CI green.
|
|
338
|
+
`LOOM_EXPECT_PTY=1` now asserts the backend and CI sets it; that run confirms
|
|
339
|
+
the runner really does build node-pty.
|
|
340
|
+
- `test/tasks.test.ts` locks gh's failure classification against the strings gh
|
|
341
|
+
actually prints, and `test/docs.test.ts` checks the docs only import things
|
|
342
|
+
that resolve. Every new test above was checked by breaking the code and
|
|
343
|
+
watching it go red.
|
|
344
|
+
- CI now typechecks `app/`, which nothing was compiling.
|
|
345
|
+
- Suite: 96 → 179.
|
|
346
|
+
|
|
347
|
+
### Accessibility
|
|
348
|
+
|
|
349
|
+
- Every icon-only control now carries an `aria-label`, mirrored from its
|
|
350
|
+
tooltip by an observer so string-rendered UI can't miss one. Verified: 17
|
|
351
|
+
icon-only controls on desktop, 6 on mobile, none unnamed.
|
|
352
|
+
|
|
353
|
+
### Design — quiet graphite (2026-07-16)
|
|
354
|
+
|
|
355
|
+
- Every surface redesigned on one system adapted from
|
|
356
|
+
[Orca](https://github.com/stablyai/orca) (MIT): neutral monochrome tokens,
|
|
357
|
+
1px hairlines, three elevation tiers with a glass floating layer, and color
|
|
358
|
+
reserved for state — thread cyan (live), shuttle magenta (baton), selvage
|
|
359
|
+
edges per agent. Spec in `docs/design-system.md`.
|
|
360
|
+
- Web app: light + dark themes with a persisted in-app toggle, Geist variable
|
|
361
|
+
type served by the daemon at `/app/fonts/geist.woff2` (SIL OFL 1.1, embedded
|
|
362
|
+
— no CDN), SVG icon set replacing emoji, Orca button/input/card/chip
|
|
363
|
+
variants, sleek scrollbars, and a readable centered thread column.
|
|
364
|
+
- Desktop web shell (≥900px) is the full Orca workspace: project groups with
|
|
365
|
+
nested agent rows in the sidebar (status dots, baton badge, click-to-target),
|
|
366
|
+
a tab strip over the pane (Thread | Changes | Brain | Routes), per-file diff
|
|
367
|
+
cards with add/delete washes, a Source-control right rail (≥1200px) whose
|
|
368
|
+
file rows jump to their diff, and a status bar (websocket liveness, host,
|
|
369
|
+
baton, working count, total spend).
|
|
370
|
+
- Desktop shell: Orca window chrome — canvas-colored background, macOS
|
|
371
|
+
traffic lights centered in the app's own top strip, 600×400 minimums,
|
|
372
|
+
restyled failure page.
|
|
373
|
+
- Phone app: graphite surfaces, near-white primary CTA, accessory-key agent
|
|
374
|
+
chips, session top bar + neutral-underline tabs, command-dock composer with
|
|
375
|
+
an arming send button, diff washes, numbered pairing steps.
|
|
376
|
+
- Desktop chrome tightened to Orca's: tabs live in the 40px top strip beside
|
|
377
|
+
the project context; the sidebar gains a Search row (filters projects and
|
|
378
|
+
agents), an add-project action, and a bottom utility rail.
|
|
379
|
+
- Split workspace: the Changes pane docks beside the Thread (persisted
|
|
380
|
+
toggle, default on ≥1280px) under a project/branch breadcrumb; diff cards
|
|
381
|
+
gain old/new line-number gutters; `turn_diff` events render as expandable
|
|
382
|
+
terminal-style `Update(n files)` cards; the thread tops with an agent
|
|
383
|
+
header block (monogram, role, baton, project dir); sidebar projects wear
|
|
384
|
+
hash-hued repo glyphs and the status bar a real spend meter. `BUILD_REV`
|
|
385
|
+
now also hashes the served app so UI-only rebuilds bump the rev.
|
|
386
|
+
- Right rail is a 4-view panel (Explorer / Search / Source Control / Tasks)
|
|
387
|
+
with a top icon switcher, open by default on the Explorer. Explorer is a
|
|
388
|
+
lazy project file tree (folders expand on click; files open in the dock —
|
|
389
|
+
changed files as a diff, others as a read-only preview). Search finds files
|
|
390
|
+
by name; Source Control lists the branch + changed files; Tasks holds a
|
|
391
|
+
per-project New-task button and the agent roster. Backed by new sandboxed
|
|
392
|
+
daemon endpoints: `GET /files`, `/file` (400KB cap), `/find` (200 results).
|
|
393
|
+
- The Changes/diff view is no longer an always-on split — it's a dock to the
|
|
394
|
+
right of the chat that opens only when you click a change (an `Update(…)`
|
|
395
|
+
card in the thread, or a file in Explorer / Search / Source Control) and
|
|
396
|
+
closes with an X. The rail itself collapses from the tab strip (PanelRight).
|
|
397
|
+
- Real terminals: each tab owns a long-lived shell in the project directory
|
|
398
|
+
(`POST /term/open|input|signal|close`, streamed over the project
|
|
399
|
+
WebSocket), so `cd` and exported vars persist between commands. A sentinel
|
|
400
|
+
printed after each command carries the exit code and cwd — the daemon
|
|
401
|
+
strips it from the stream, so the prompt tracks the live directory and
|
|
402
|
+
non-zero exits surface. Ctrl+C signals the shell's process group and the
|
|
403
|
+
shell survives via a no-op INT trap, giving real `^C → exit 130 → prompt`
|
|
404
|
+
behaviour. Includes ANSI colour rendering, ArrowUp/Down history, Ctrl+L,
|
|
405
|
+
click-to-focus, multiple tabs, drag-resize, and a Ctrl+backtick toggle.
|
|
406
|
+
- Every column is drag-resizable with persisted widths and double-click to
|
|
407
|
+
reset — sidebar, diff dock, and right rail, each clamped so the chat can't
|
|
408
|
+
be squeezed out.
|
|
409
|
+
- New Task flow (Orca's Create Worktree): a sidebar action, the Tasks rail
|
|
410
|
+
view, and the `n` shortcut open a modal to pick a project, a task, and
|
|
411
|
+
**one ADE or several** — one agent messages it directly; several run it as
|
|
412
|
+
a pipeline hop to hop (e.g. claude → codex).
|
|
413
|
+
- Phone home rebuilt to the Orca mobile layout: Welcome-back hero, stat
|
|
414
|
+
tiles (Projects / Agents / Spend), a Daemon card, a Resume card, hue-glyph
|
|
415
|
+
project cards, and quick-action pills.
|
|
416
|
+
- Fixed: the desktop web shell now renders the hash-addressed project on
|
|
417
|
+
first load; live websocket frames buffer until history hydrates (an early
|
|
418
|
+
event could previously wipe the rendered backlog).
|
|
419
|
+
- Fixed: the desktop shell could open yesterday's UI. `BUILD_REV` is now a
|
|
420
|
+
content hash (mtimes skew across runtimes on exFAT), the shell restarts a
|
|
421
|
+
stale daemon before loading, spawns the daemon under a real Node runtime
|
|
422
|
+
(Electron's bundled Node predates `node:sqlite`, silently degrading the
|
|
423
|
+
event store), and `/app` is served `Cache-Control: no-store`.
|
|
424
|
+
|
|
425
|
+
## [0.1.0] — 2026-07-15
|
|
426
|
+
|
|
427
|
+
### Core engine
|
|
428
|
+
|
|
429
|
+
- Scaffold TypeScript project with verified integration notes.
|
|
430
|
+
- Event log (SQLite + JSONL), agent registry, baton, projections, suggestions, and notify subsystem.
|
|
431
|
+
- Shared claude-cli helper and LLM-synthesized projections (opt-in, template fallback).
|
|
432
|
+
- Echo, claude-code, and opencode adapters with antigravity bridge and factory.
|
|
433
|
+
- Auto-capture `Decision:` lines into shared memory; handoff events snapshot outgoing tree state.
|
|
434
|
+
- Cost telemetry — per-agent ledger rehydrated from the log, route cost attribution, `loom costs`, $ on board/TUI/app.
|
|
435
|
+
- Adapter SDK guide, README, MIT license, and architecture design spec.
|
|
436
|
+
|
|
437
|
+
### Routing
|
|
438
|
+
|
|
439
|
+
- Multi-hop route engine — role/id pipelines, pause-on-question, auto-resume, abort, timeouts, bridge refresh every hop.
|
|
440
|
+
- Dynamic mode — LLM router picks each hop (claude headless, rules fallback), hop budget, reasons in `route_step`; `loom route auto --router --max-hops`.
|
|
441
|
+
- Per-step instructions in named routes (`{step, instruction}`); rich pipeline docs and LLM projections.
|
|
442
|
+
- `loom route` / `loom routes` CLI — live follow, `--status`, `--abort`, `--detach`, route events in chat and board.
|
|
443
|
+
- Route picker sheet on the phone app — auto/named/custom dropdown, start+abort, hop reasons in banner.
|
|
444
|
+
- Routing guide covering pipelines, pause/resume, and precedence rules.
|
|
445
|
+
|
|
446
|
+
### Surfaces (TUI / CLI / phone app)
|
|
447
|
+
|
|
448
|
+
- Full-screen TUI as the default command — tab-based agent switching, slash commands, live stream, route progress, in-TUI QR pairing.
|
|
449
|
+
- Ctrl+P command palette — fuzzy filter over shifts/routes/commands, template insertion, arrow+enter.
|
|
450
|
+
- Complete CLI command set — `init`, `up`/`down`, chat REPL, handoff confirm, pair QR, board, log follow.
|
|
451
|
+
- `loom doctor` — environment, daemon, and project diagnostics with actionable fixes.
|
|
452
|
+
- Phone web app served at `/app` — QR deep-link pairing, board, live thread over WebSocket, agent chips with shift-on-send, route banner, interrupt, install-to-home-screen manifest.
|
|
453
|
+
- Form-submit composer for mobile keyboards; phone-over-Tailscale walkthrough.
|
|
454
|
+
|
|
455
|
+
### Security & operations
|
|
456
|
+
|
|
457
|
+
- Multi-project daemon with REST + WebSocket server, bearer auth, QR pairing tokens, and tailnet binding.
|
|
458
|
+
- Paired-device management — `loom clients`, admin-only revoke, immediate 401 for revoked tokens.
|
|
459
|
+
- Read-modify-write daemon config on claim/revoke so PID/host/port survive pairing.
|
|
460
|
+
- Stale-build auto-restart via health endpoint; `loom up --restart`; pair refuses unreachable localhost QR with exact fix steps.
|
|
461
|
+
- Hot-reload edited `.loom/config.json` when project is quiet.
|
|
462
|
+
- Auto-clear ghost baton holders after agent removal.
|
|
463
|
+
- Sanitize inherited Claude-session env vars (`CLAUDECODE`, `CLAUDE_CODE_*`, `ANTHROPIC_BASE_URL`) when spawning agent CLIs.
|
|
464
|
+
|
|
465
|
+
### Testing & CI
|
|
466
|
+
|
|
467
|
+
- Unit + end-to-end suite (76 tests) covering eventlog, baton, projection, suggestions, auth, costs, and daemon e2e.
|
|
468
|
+
- Routing e2e tests — completion, role/named resolution, pause-on-question + auto-resume, abort, manual-handoff cancel, 409 double-start.
|
|
469
|
+
- GitHub Actions CI (Node 22 and 24, build + test).
|
|
470
|
+
- OpenCode adapter hardening from dogfooding Loom on itself — poll-based turn completion, error surfacing, model validation, orphan serve reaping.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nivesh Gajengi
|
|
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.
|