@hearthscale/app 0.0.0-stage → 0.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/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Simon Wagenfuehrer
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.
22
+
23
+ The fonts in `dist/fonts` are licensed under the SIL Open Font License 1.1
24
+ (`dist/fonts/OFL.txt`). The Remix Icon font in `dist/remixicon` is licensed
25
+ under the Apache License 2.0 (`dist/remixicon/License`).
package/README.md CHANGED
@@ -1,3 +1,110 @@
1
- # Temporary Holding Version
1
+ # @hearthscale/app
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The types an app imports: its manifest, `app.json` (`Manifest`), what
4
+ `backend.js` exports (`Backend`), what its
5
+ tool bodies receive (`AppContext`, `ToolRun`), and the slots a view
6
+ reserves for the shell's chat, composer, cards, live terminal and the
7
+ tab's web page (`HostSlot`). The package carries no runtime for a
8
+ backend: `hearthscale dev` and `hearthscale pack` bundle an import of
9
+ `@hearthscale/app` as an empty module, so a backend ships nothing of the
10
+ platform and imports only its types. `@hearthscale/app/view` is the one
11
+ runtime part, which a view bundles.
12
+
13
+ ```sh
14
+ npm install --save-dev @hearthscale/app
15
+ ```
16
+
17
+ Type-check a backend with `"lib": ["ES2022", "DOM"]`: `AppContext.signal`
18
+ is an `AbortSignal` and `models.complete` answers an `AsyncIterable`. That
19
+ library also declares `fetch` and `URL`, which the backend's engine does not
20
+ have; a backend reaches the network with `ctx.net`. The manifest's schema is
21
+ `https://hearthscale.com/app.schema.json`.
22
+
23
+ A view draws with the UI kit, `@hearthscale/app-ui` beside this package:
24
+ the tokens, the `hs-*` classes, React parts and the Remix Icon font the host
25
+ loads into every view.
26
+
27
+ A view calls the methods every view may call (`ViewHostMethods`), the
28
+ `hearthscale/*` extensions and `openai/files/open` (`ViewExtensionMethods`)
29
+ as requests of its MCP Apps `App`, and hears the host's notifications
30
+ (`HostSlotNotification`, `ViewExtensionNotification`) on
31
+ `app.fallbackNotificationHandler`. `ViewHostContext` types the keys the host
32
+ adds to the host context, and `ContentBlockMeta`, `ModelContextAnswer`,
33
+ `MessageOptions`, `ResourceReadOptions`, `ResourceVersion` and
34
+ `ResourcePath` the `_meta` of the standard messages. `hostCall` of
35
+ `@hearthscale/app/view` gives one typed call of every request a view sends
36
+ beyond MCP Apps, the host methods and the extensions alike (`HostCall`,
37
+ over `ViewMethods`), and of the MCP Apps requests that take Hearthscale's
38
+ own fields (`ViewStandardMethods`; `ViewCall` covers both):
39
+
40
+ ```ts
41
+ import { App } from '@modelcontextprotocol/ext-apps';
42
+ import { hostCall } from '@hearthscale/app/view';
43
+
44
+ const app = new App({ name: 'Inbox', version: '1.0.0' }, {});
45
+ const call = hostCall(app);
46
+ const { sessions } = await call('hearthscale/sessions/list');
47
+ await call('hearthscale/ui/set-tab', { title: 'Inbox' });
48
+ ```
49
+
50
+ A view keeps a JSON value for its next load with
51
+ `hearthscale/ui/set-widget-state` (`{ state }`, at most 65,536 UTF-16 code units as JSON text) and
52
+ reads it back from the host context as `hearthscale/widgetState`, null
53
+ before the first save. The host loads a view in a tab again when the tab
54
+ comes back after the window discarded its surface, and after a restart.
55
+ A view that fills a tab names it with `hearthscale/ui/set-tab`
56
+ (`{ title?, icon? }`, the icon a picture as a `data:` address); null gives
57
+ back the surface's own title or mark, and the tab keeps the name through a
58
+ restart.
59
+
60
+ A view whose app names `models` in `uses` asks the model with MCP's
61
+ `sampling/createMessage` (text only, on the app's chat card).
62
+ `hearthscale/ui/select-files` (`{ accept?, multiple? }`, on a click) answers
63
+ the files the person picked, which the view reads with `resources/read`;
64
+ `hearthscale/ui/upload-file` (`{ name, mediaType, blob }`, `blob` a `Blob`)
65
+ keeps its bytes as an asset the view reads back as
66
+ `hearthscale-asset://<asset>`, or streamed and by byte range at the address
67
+ `hearthscale/assetBytes` in the host context names, followed by the asset's
68
+ id, as a `fetch` or as the source of a picture, a sound or a video.
69
+ `ui/download-file` saves where the person picks on a click; an embedded
70
+ resource's `blob` may be a `Blob`, which streams into the file, beside the
71
+ text and the base64 of MCP Apps, which hold at most 64 MiB.
72
+
73
+ A view that fills a tab has a web page of its tab: the shell loads it on
74
+ the app's own partition, where it reaches only the hosts of
75
+ `environment.network`, once the view asks for an address with
76
+ `hearthscale/web/navigate`. The page lies over the view's `web` slot, and
77
+ under the view while the view covers it (`hearthscale/ui/cover`), so a
78
+ menu the view opens draws over the page and the page shows through where
79
+ the view draws nothing. The same call lays the chat and the composer of the
80
+ view's slots under it; the terminal and the cards stay above. The view drives the page with the other
81
+ `hearthscale/web/*` methods and hears its state as `hearthscale/web/page`
82
+ (`WebPageState`). A link the page opens in a new window opens a tab of the
83
+ same surface, and a popup with a size of its own opens as a window that
84
+ keeps its opener. The keys the view names with `hearthscale/web/keys` reach
85
+ the view as `hearthscale/web/key` while the page has the keyboard. The page
86
+ keeps its address through a restart, and the app's tools drive it through
87
+ `surface.command`.
88
+
89
+ An app that names `peers` in `uses` talks to itself on other computers in
90
+ rooms. A view opens a room with `hearthscale/peers/create` and shows the
91
+ person its ticket to pass on; `hearthscale/peers/join` asks the person for
92
+ a ticket in a sheet the shell draws, so the view never names one. Both
93
+ need a click. The view and the backend (`ctx.peers`) then send messages,
94
+ JSON of at most 64 KiB, to the room's other members, hear
95
+ theirs (`hearthscale/peers/message`, `ctx.peers.on('message')`) and leave.
96
+ A room carries only these messages: no file, process or credential of
97
+ either platform. It ends for everyone when the computer that opened it
98
+ leaves. A member keeps its place and joins again on its own after a drop
99
+ or a restart; no message is kept.
100
+
101
+ ## Traps
102
+
103
+ - The channel carries JSON only. Bytes cross as base64 inside the host;
104
+ the context's `assets` and `net` methods hide that, but a tool body
105
+ that returns a `Uint8Array` returns nothing useful: publish it as an
106
+ asset and answer the id.
107
+ - `App.request` checks a result against the schema it is given, and drops
108
+ every field the schema does not name unless the schema keeps them.
109
+ `McpUiMessageResultSchema` keeps every field, so an extension's result
110
+ arrives whole.