@unifedev/thread-pages 0.3.2
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 +230 -0
- package/PLUGIN_OVERVIEW.md +83 -0
- package/README.md +153 -0
- package/authoring.ts +368 -0
- package/bridge.ts +1721 -0
- package/dist/server.js +12830 -0
- package/dist/server.meta.json +11 -0
- package/docs/MODEL.md +211 -0
- package/docs/ROADMAP.md +96 -0
- package/home.ts +419 -0
- package/package.json +66 -0
- package/page.ts +782 -0
- package/server.ts +2179 -0
- package/theme.ts +676 -0
- package/tsconfig.json +15 -0
package/docs/MODEL.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# How Thread Pages works
|
|
2
|
+
|
|
3
|
+
This answers four questions: what instructions exist and where they live, where
|
|
4
|
+
the HTML lives, where the template lives, and how you reach a page from a phone.
|
|
5
|
+
|
|
6
|
+
## The one-paragraph version
|
|
7
|
+
|
|
8
|
+
Each bb thread gets one HTML file in its own thread storage. The agent edits that
|
|
9
|
+
file directly; saving it *is* publishing. A plugin route serves the file inside a
|
|
10
|
+
sandboxed iframe on the bb origin you are already logged into, so the same URL
|
|
11
|
+
works on your laptop and your phone with no extra port, tunnel, or password.
|
|
12
|
+
Forms in the page post back to the thread as your next message.
|
|
13
|
+
|
|
14
|
+
## Every instruction, and where it lives
|
|
15
|
+
|
|
16
|
+
There are three, and only the first is loaded into every session.
|
|
17
|
+
|
|
18
|
+
| # | What | Where it is stored | Loaded when |
|
|
19
|
+
| --- | --- | --- | --- |
|
|
20
|
+
| 1 | **The contract** — the page is the conversation, how to ask well, what belongs on a page | `authoring.ts` → `DEFAULT_AGENT_INSTRUCTION`, overridable in the `agentInstructionText` setting (stored in bb's settings DB) | Injected into every eligible root thread, if `agentInstructions` is on |
|
|
21
|
+
| 2 | **The seed comment** — the three class names, the theme attributes, the escape-hatch rule | Inside each page's own HTML, put there by the seed | Read by the agent when it opens the file |
|
|
22
|
+
| 3 | **The authoring guide** — charts, branching, swipe decks, files, assets, the bridge | `authoring.ts` → `AUTHORING_GUIDE`, printed by `bb thread-page guide` | Only when an agent runs that command |
|
|
23
|
+
|
|
24
|
+
The split is deliberate. #1 is paid for by every session, so it holds only what
|
|
25
|
+
changes behaviour. #2 costs nothing because the agent is already reading the
|
|
26
|
+
file. #3 is unbounded and free to the sessions that never need it.
|
|
27
|
+
|
|
28
|
+
There is **no** skill, no agent tool, and no `CLAUDE.md` entry. Check what is
|
|
29
|
+
live with:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
bb plugin config thread-pages # all three settings and their values
|
|
33
|
+
bb thread-page guide # print #3
|
|
34
|
+
bb instructions get # bb-wide instructions (empty; not us)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
To change the contract for all future sessions, edit `agentInstructionText`. To
|
|
38
|
+
change what new pages start from, edit `pageSeedHtml`. Neither touches a page
|
|
39
|
+
that already exists.
|
|
40
|
+
|
|
41
|
+
## Where the HTML lives
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
~/.bb/thread-storage/<threadId>/
|
|
45
|
+
thread-page.html the page — one file, ~28 KB seeded
|
|
46
|
+
thread-page-assets/ optional: images, css, fonts, json you show
|
|
47
|
+
thread-page-uploads/ files the user attached, named by the plugin
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
That is the whole storage model. The page is a normal file: read it, diff it,
|
|
51
|
+
edit it with any tool. There is no database of pages, no revision history, and
|
|
52
|
+
no separate publish step.
|
|
53
|
+
|
|
54
|
+
The plugin also keeps two small things in bb's existing `plugin_kv` table:
|
|
55
|
+
|
|
56
|
+
- `page-signing-key:v2` — 32 bytes, so open browser sessions survive a reload;
|
|
57
|
+
- `cache:<threadId>` — a best-effort last-good copy, so a page still opens
|
|
58
|
+
read-only when its source machine is offline.
|
|
59
|
+
|
|
60
|
+
No plugin-owned SQLite database, no background service.
|
|
61
|
+
|
|
62
|
+
## Where the template lives
|
|
63
|
+
|
|
64
|
+
There is no template file on disk. The seed is a string in the plugin:
|
|
65
|
+
|
|
66
|
+
- `theme.ts` → `THEME_CSS` — the design system: five worlds, one resolver.
|
|
67
|
+
- `authoring.ts` → `DEFAULT_PAGE_SEED` — the document that wraps it.
|
|
68
|
+
|
|
69
|
+
`bb thread-page init` writes that seed **only when the file does not exist**. It
|
|
70
|
+
never overwrites. The consequence worth understanding: **each page carries its
|
|
71
|
+
own copy of the stylesheet.** The agent can therefore change any rule for one
|
|
72
|
+
page, and a plugin update can never restyle a page you have already read. The
|
|
73
|
+
cost is that improving the design system only affects pages created afterwards.
|
|
74
|
+
|
|
75
|
+
Five worlds are available; pick one with `data-theme` on `<html>`:
|
|
76
|
+
`paper`, `terminal`, `atrium`, `volume` (default), `bloom`. Also
|
|
77
|
+
`data-mode` (`system`/`light`/`dark`) and `data-atmos` (`on`/`off`).
|
|
78
|
+
|
|
79
|
+
## How a turn flows
|
|
80
|
+
|
|
81
|
+
1. Agent runs `bb thread-page init` → path, link, and `NEW`/`EXISTING`/`SKIP`.
|
|
82
|
+
2. Agent edits `thread-page.html`.
|
|
83
|
+
3. Your open tab notices the changed ETag and reloads itself.
|
|
84
|
+
4. You answer a form. The page posts to `/submit`.
|
|
85
|
+
5. The plugin turns the answers into a message and sends it to the thread.
|
|
86
|
+
6. It arrives as the agent's next turn.
|
|
87
|
+
|
|
88
|
+
`SKIP` means the thread is a helper — a child, a fork, or a hidden worker. Only
|
|
89
|
+
threads you started get a page, because those are the ones you talk to.
|
|
90
|
+
|
|
91
|
+
## Reaching it from your phone
|
|
92
|
+
|
|
93
|
+
**Nothing to expose.** Thread Pages has no server of its own — its routes are
|
|
94
|
+
part of the bb server:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
https://<your-handle>.getbb.app/api/v1/plugins/thread-pages/http/page?threadId=<id>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Whatever origin reaches bb reaches your pages, behind the **same getbb.app
|
|
101
|
+
login**, with no second port and no separate share. Verified: an unauthenticated
|
|
102
|
+
request to that URL returns the getbb.app sign-in wall and leaks no page content.
|
|
103
|
+
|
|
104
|
+
`bb thread-page init` now prints that absolute URL when this bb is connected, so
|
|
105
|
+
the link an agent hands you is already the one you can open on a phone. When bb
|
|
106
|
+
is local-only it prints a relative path instead, which resolves against whatever
|
|
107
|
+
you are browsing from.
|
|
108
|
+
|
|
109
|
+
If you are not paired: `bb connect` once, from the dashboard. After that every
|
|
110
|
+
page is remote automatically — there is no per-page or per-port step, and
|
|
111
|
+
nothing for a new user to configure.
|
|
112
|
+
|
|
113
|
+
For a private alternative, point Tailscale Serve at the whole bb loopback origin.
|
|
114
|
+
Never Funnel it and never wildcard-bind bb.
|
|
115
|
+
|
|
116
|
+
## The security model
|
|
117
|
+
|
|
118
|
+
The page is written by an agent, so it is treated as untrusted code.
|
|
119
|
+
|
|
120
|
+
**Inside the iframe** (`sandbox="allow-scripts allow-forms"`, opaque origin):
|
|
121
|
+
your page's HTML, CSS and JavaScript. It has no bb cookie, no mutation token, no
|
|
122
|
+
parent DOM, no `localStorage`, no raw bb API, no CLI, no arbitrary file access,
|
|
123
|
+
and CSP blocks ordinary `fetch` and subresources.
|
|
124
|
+
|
|
125
|
+
**Outside the iframe**, plugin-authored code on the bb origin holds the action
|
|
126
|
+
token, performs same-origin calls, and renders confirmations.
|
|
127
|
+
|
|
128
|
+
**The bridge.** `window.threadPage.invoke(method, params)` reaches a fixed list
|
|
129
|
+
of named capabilities, each with its own validator, size limit, and effect class.
|
|
130
|
+
There is no generic "call bb" escape.
|
|
131
|
+
|
|
132
|
+
Anything that mutates state outside this thread requires a confirmation the page
|
|
133
|
+
cannot fake: the server answers once with a signed challenge carrying **its own**
|
|
134
|
+
summary, the trusted shell shows that summary in a dialog, and the server
|
|
135
|
+
re-verifies the signature before acting. The challenge is bound to one request
|
|
136
|
+
id, method, parameter fingerprint, page revision and thread, and expires in two
|
|
137
|
+
minutes — so it cannot be forged, replayed, or reused to approve different
|
|
138
|
+
parameters.
|
|
139
|
+
|
|
140
|
+
All capabilities are enabled except voice: `context.get`, `thread.activity`,
|
|
141
|
+
`thread.reply`, `threads.snapshot`, `threads.continue`, `threads.spawn`,
|
|
142
|
+
`threads.archive`, `threads.stop`, `threads.openPage`, `threads.openBb`,
|
|
143
|
+
`navigation.openExternal`, `projects.list`, `projects.browse`,
|
|
144
|
+
`projects.create`, `providers.list`, `storage.get`, `storage.set`.
|
|
145
|
+
`voice.captureAndTranscribe` has a contract but no handler, by your decision.
|
|
146
|
+
|
|
147
|
+
`projects.browse` opens the host's native folder picker and returns an **opaque,
|
|
148
|
+
single-use token** plus a display string — never a filesystem path the page could
|
|
149
|
+
reuse or leak. `projects.create` redeems that token.
|
|
150
|
+
|
|
151
|
+
**The honest limitation.** Page JavaScript can navigate its own frame and put
|
|
152
|
+
data in that URL. Browsers cannot prevent this while still allowing page scripts.
|
|
153
|
+
It grants no bb authority, but a page is code you are choosing to run.
|
|
154
|
+
|
|
155
|
+
## The home page, and the Sessions link
|
|
156
|
+
|
|
157
|
+
One thread's page is designated home:
|
|
158
|
+
|
|
159
|
+
```sh
|
|
160
|
+
bb thread-page home # in the thread that should own it
|
|
161
|
+
bb thread-page home --clear # remove the link everywhere
|
|
162
|
+
bb plugin config thread-pages # shows homeThreadId
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`/home` then redirects there, and **every other page shows a "← Sessions" link
|
|
166
|
+
back to it automatically**. That link is chrome in the trusted shell, so no page
|
|
167
|
+
authors it and no agent spends instruction budget on it. Home does not link to
|
|
168
|
+
itself.
|
|
169
|
+
|
|
170
|
+
`bb thread-page home` writes a real session hub when that thread has no page
|
|
171
|
+
yet, and never touches one that exists. The default groups sessions by project,
|
|
172
|
+
gives each group its own look, and offers filter, open, prompt, spawn, stop and
|
|
173
|
+
archive per row.
|
|
174
|
+
|
|
175
|
+
**Groups are not projects.** A group is a label, a look, and a *set* of project
|
|
176
|
+
ids, stored in the page's own scoped storage under `home.groups`. One group per
|
|
177
|
+
project is only the default. Ask the owning agent for "Work" and "Side projects",
|
|
178
|
+
or to put one project in two groups, and it edits the page — no schema, no plugin
|
|
179
|
+
change. Each group carries `data-world`, which re-resolves the design tokens for
|
|
180
|
+
that subtree, so a project genuinely looks different without a second document.
|
|
181
|
+
|
|
182
|
+
Home is an ordinary Thread Page: it calls `threads.snapshot`, `projects.list` and
|
|
183
|
+
`providers.list` and renders what it likes. You can ask the owning agent to
|
|
184
|
+
redesign it like any other page.
|
|
185
|
+
|
|
186
|
+
## The working indicator
|
|
187
|
+
|
|
188
|
+
While the owning thread is mid-turn, the page header shows a pulsing dot and
|
|
189
|
+
wording you control:
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
bb plugin config thread-pages set workingLabel "Thinking…"
|
|
193
|
+
bb plugin config thread-pages set workingLabel "" # hides it
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The point is the sentence: what you are reading is the last *saved* version, and
|
|
197
|
+
another one is coming. It costs nothing to run — the state rides on the
|
|
198
|
+
`x-thread-page-activity` header of the revision poll the shell already makes
|
|
199
|
+
every 10 seconds, so there is no extra request, nothing for a page to implement,
|
|
200
|
+
and nothing in any agent's instructions.
|
|
201
|
+
|
|
202
|
+
## Files in this repository
|
|
203
|
+
|
|
204
|
+
| File | Role |
|
|
205
|
+
| --- | --- |
|
|
206
|
+
| `server.ts` | Settings, CLI, HTTP routes, capability handlers, caching |
|
|
207
|
+
| `page.ts` | Token signing, HTML parsing/injection, both browser runtimes, form and upload serialization |
|
|
208
|
+
| `bridge.ts` | The capability contract and validators. No SDK, DOM, fetch, or filesystem imports |
|
|
209
|
+
| `authoring.ts` | The instruction, the seed, the guide |
|
|
210
|
+
| `home.ts` | The default home page: markup, styles, and script |
|
|
211
|
+
| `theme.ts` | The design system |
|
package/docs/ROADMAP.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
Version 0.3.0 · September 2026
|
|
4
|
+
|
|
5
|
+
## Done
|
|
6
|
+
|
|
7
|
+
Everything needed for daily use is implemented and verified in a browser.
|
|
8
|
+
|
|
9
|
+
**The page.** Complete HTML/CSS/JS mini-apps in an opaque sandbox, parsed with
|
|
10
|
+
parse5 and kernel-injected before authored scripts. Five-world design system
|
|
11
|
+
carried in each page. Automatic semantic forms with independent per-form state,
|
|
12
|
+
blank-safe answers, manual opt-out, and update protection. File attachments in a
|
|
13
|
+
confined upload directory. A confined asset directory with a matching CSP.
|
|
14
|
+
|
|
15
|
+
**The chrome.** Title bar, Sessions link, working indicator — all outside the
|
|
16
|
+
sandbox, none of it costing an agent anything.
|
|
17
|
+
|
|
18
|
+
**The home page.** `bb thread-page home` designates any thread's page and writes
|
|
19
|
+
a session hub grouped by project, with a look per group and grouping stored in
|
|
20
|
+
the page rather than hard-wired.
|
|
21
|
+
|
|
22
|
+
**The bridge.** Seventeen of eighteen capabilities, with trusted confirmation for
|
|
23
|
+
every cross-thread, destructive, device and external-navigation effect.
|
|
24
|
+
|
|
25
|
+
**Access.** Remote by default through the existing bb origin, with no port share.
|
|
26
|
+
|
|
27
|
+
## Left
|
|
28
|
+
|
|
29
|
+
### Before wide release
|
|
30
|
+
|
|
31
|
+
**A hostile-page corpus.** The only item I would call blocking for a public
|
|
32
|
+
audience. The confirmation flow is tested against forgery, replay and
|
|
33
|
+
parameter-swapping, but no test yet plays an attacker: trying to read the bb
|
|
34
|
+
cookie, reach `parent`/`top`, forge another thread's action, or reach the network.
|
|
35
|
+
The design says these are impossible; a test should prove it, and should also
|
|
36
|
+
demonstrate the self-frame navigation limitation rather than leave it as prose.
|
|
37
|
+
|
|
38
|
+
### Capabilities
|
|
39
|
+
|
|
40
|
+
**`voice.captureAndTranscribe`** has a contract and validators but no handler.
|
|
41
|
+
Recording happens in trusted chrome and transcription goes through bb's own voice
|
|
42
|
+
service. Deferred as unused; the contract is there when it is wanted.
|
|
43
|
+
|
|
44
|
+
### Worth doing
|
|
45
|
+
|
|
46
|
+
- **Asset and upload cleanup.** Neither directory is ever pruned. A page that
|
|
47
|
+
receives many attachments grows without bound.
|
|
48
|
+
- **`threads.snapshot` paging.** `nextCursor` is always `null`; the limit is 200.
|
|
49
|
+
Fine for now, wrong eventually.
|
|
50
|
+
- **Design-system updates for existing pages.** Each page carries its own
|
|
51
|
+
stylesheet, which is what makes per-page restyling safe and plugin updates
|
|
52
|
+
harmless. The cost is that improvements only reach new pages. If that becomes
|
|
53
|
+
annoying, the answer is a command that re-splices the current stylesheet into a
|
|
54
|
+
page on request — not injection at render time, which would take the property
|
|
55
|
+
away.
|
|
56
|
+
- **A second reference page.** The home page is the only worked example. One
|
|
57
|
+
more — a diagram or a multi-screen flow — would show the range better than the
|
|
58
|
+
guide's prose.
|
|
59
|
+
|
|
60
|
+
## Non-goals
|
|
61
|
+
|
|
62
|
+
- **A component library or theme picker.** The plugin hosts pages; it does not
|
|
63
|
+
design them. Five worlds exist so a page has a coherent starting point, not so
|
|
64
|
+
it has a menu.
|
|
65
|
+
- **A fixed dashboard.** Home is an ordinary page. Anything the plugin renders
|
|
66
|
+
itself is one thing an agent cannot adapt to the task.
|
|
67
|
+
- **General network access from a page.** When a task needs a remote service it
|
|
68
|
+
should get a named, reviewed capability, not a fetch proxy.
|
|
69
|
+
- **Strict no-exfiltration.** Would require forbidding authored JavaScript, which
|
|
70
|
+
is the product. See ARCHITECTURE.md.
|
|
71
|
+
|
|
72
|
+
## Release
|
|
73
|
+
|
|
74
|
+
1. `npm ci && bb plugin types --check . && npm test && npm run typecheck && npm run build`
|
|
75
|
+
2. Tag `vX.Y.Z` and push. bb records the tag with the commit it pointed at and
|
|
76
|
+
**refuses a tag that later moves** — so publish a fix as a new version rather
|
|
77
|
+
than retagging. If a tag has to move, everyone on it must remove and reinstall.
|
|
78
|
+
3. Verify `bb plugin install git:<url>@^X.Y.0` on a clean machine.
|
|
79
|
+
4. Publish to npm (see below).
|
|
80
|
+
5. Submit to the marketplace with the `submit-a-plugin` skill, which reads the
|
|
81
|
+
current contract from `github.com/get-bb/marketplace` rather than assuming one.
|
|
82
|
+
|
|
83
|
+
### Publishing to npm
|
|
84
|
+
|
|
85
|
+
The scope has to exist and you have to be logged in as a member:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
npm login # as the account that owns @unifedev
|
|
89
|
+
npm publish --access public
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`dist/` is committed and also packed, so neither a git nor an npm install needs a
|
|
93
|
+
build step. `package-lock.json` is committed for the git path — npm strips it from
|
|
94
|
+
tarballs by design, which is fine because npm resolves `parse5` itself.
|
|
95
|
+
|
|
96
|
+
Verify afterwards with `bb plugin install npm:@unifedev/thread-pages@^0.3.0`.
|