@agentshouse/skills 0.1.0-c9759dfd79fe1 → 0.1.0-c99fae1f41245
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/README.md +4 -4
- package/agent-instructions.json +4 -5
- package/dist/index.d.ts +1 -2
- package/dist/index.js +5 -12
- package/guide/{routines → agent-functions}/work-on-ticket.md +6 -8
- package/guide/apps/build.md +1 -1
- package/guide/apps/chat.md +28 -8
- package/guide/apps/client.md +1 -1
- package/guide/apps/content-safety.md +4 -4
- package/guide/apps/data.md +9 -7
- package/guide/apps/example-function.md +3 -4
- package/guide/apps/example-tasks.md +1 -1
- package/guide/apps/functions.md +5 -5
- package/guide/apps/local-first.md +10 -9
- package/guide/apps/local-query-results.md +13 -4
- package/guide/apps/local-query.md +20 -12
- package/guide/apps/page.md +2 -1
- package/guide/apps/room-drafts.md +20 -13
- package/guide/apps/runtime-api.md +16 -10
- package/guide/apps/runtime-conversations.md +28 -0
- package/guide/apps/runtime-files.md +7 -5
- package/guide/apps/runtime-refusals.md +10 -5
- package/guide/apps/runtime.md +1 -1
- package/guide/apps/search.md +21 -0
- package/guide/apps/start.md +4 -2
- package/guide/apps/verify.md +4 -4
- package/guide/index.md +3 -3
- package/guide/owner-rules.md +9 -8
- package/guide/reference/agent-function.md +15 -0
- package/guide/reference/app-manifest.md +1 -1
- package/guide/reference/schema.md +22 -11
- package/guide/reference/triggers.md +8 -6
- package/guide/sites/build.md +7 -6
- package/guide/sites/entry-data-contract.md +1 -1
- package/guide/sites/runtime.md +1 -1
- package/guide/skills/build-app.md +1 -1
- package/guide/skills/house-onboarding.md +8 -12
- package/guide/skills/prototype-app.md +6 -6
- package/guide/skills/wizard.md +3 -3
- package/owner-rules-earlier.json +5 -1
- package/package.json +1 -1
- package/set/code-review/SKILL.md +1 -1
- package/set/codebase-design/DESIGN-IT-TWICE.md +1 -1
- package/set/grilling/SKILL.md +1 -1
- package/set/implement-spec/SKILL.md +4 -2
- package/set/improve-codebase-architecture/SKILL.md +1 -1
- package/set/research/SKILL.md +1 -1
- package/set/wayfinder/SKILL.md +1 -1
- package/upstream.json +2 -2
- package/guide/apps/local-search.md +0 -35
- package/guide/reference/routine.md +0 -14
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ The Skills agents.house serves its agents, and the texts its agents read.
|
|
|
11
11
|
an App, `sites/` what a Site adds, and `skills/` agents.house's own Skills, `house-onboarding`,
|
|
12
12
|
`prototype-app`, `build-app` and `wizard`, each page carrying the `name` and `description` a
|
|
13
13
|
`SKILL.md` carries.
|
|
14
|
-
`
|
|
14
|
+
`agent-functions/` holds the Agent functions agents.house writes into every Private Room, frontmatter and
|
|
15
15
|
all. `owner-rules` is the Owner rules' text, and `owner-rules-earlier.json` the SHA-256 of every
|
|
16
16
|
earlier text.
|
|
17
17
|
- `agent-instructions.json` is the lines that open an agent's first prompt.
|
|
@@ -23,9 +23,9 @@ for (const { name } of listSkills()) console.log(name, readSkill(name).length);
|
|
|
23
23
|
console.log(OWNER_RULES);
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
`agentInstructions(
|
|
27
|
-
with its path, title, first line and text
|
|
28
|
-
|
|
26
|
+
`agentInstructions()` returns the first prompt's lines, which name no Local copy,
|
|
27
|
+
`GUIDE` every Guide page with its path, title, first line and text and
|
|
28
|
+
`EARLIER_OWNER_RULES_SHA256` the earlier texts' digests.
|
|
29
29
|
|
|
30
30
|
The skills come from [mattpocock/skills](https://github.com/mattpocock/skills); thank you, Matt
|
|
31
31
|
Pocock.
|
package/agent-instructions.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"lines": [
|
|
3
3
|
"From House:",
|
|
4
|
-
"Work with House through the `house` CLI: run `house --help`, and `house
|
|
5
|
-
"
|
|
4
|
+
"Work with House through the `house` CLI: run `house --help`, and `house run_command '{\"command\":\"guide/open\"}'` opens `index`, the root page of House's guides and formats.",
|
|
5
|
+
"A file reaches the User as a Room Path written with `house upload` and linked in the answer. House serves an App, a Site or a website built by the Guide from `index` (`guide/open`), with no server of your own.",
|
|
6
6
|
"Optional per-Room instructions: `/rooms/private/rooms/<handle>/AGENTS.md`.",
|
|
7
|
-
"For another Agent's help, `
|
|
8
|
-
]
|
|
9
|
-
"copies": "House Local copies; before work run `house fetch` and rebase onto `<house-ref>`; after it commit, then `house push`:"
|
|
7
|
+
"For another Agent's help, `agent-functions/send` naming one of the Room's Agent function cards under `house/agent-functions/`, or the Agent and what you ask, as its Trust level there allows; its mail comes back into this conversation."
|
|
8
|
+
]
|
|
10
9
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -15,8 +15,7 @@ export interface GuidePage {
|
|
|
15
15
|
export declare const GUIDE_ROOT = "index";
|
|
16
16
|
export declare const GUIDE: readonly GuidePage[];
|
|
17
17
|
export declare const OWNER_RULES: string;
|
|
18
|
-
export declare const WORK_ON_TICKET: string;
|
|
19
18
|
export declare const EARLIER_OWNER_RULES_SHA256: readonly string[];
|
|
20
|
-
export declare function agentInstructions(
|
|
19
|
+
export declare function agentInstructions(): string[];
|
|
21
20
|
export declare function listSkills(): readonly Skill[];
|
|
22
21
|
export declare function readSkill(name: string): readonly SkillFile[];
|
package/dist/index.js
CHANGED
|
@@ -4,20 +4,20 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
const PACKAGE = fileURLToPath(new URL('..', import.meta.url));
|
|
5
5
|
const SET = join(PACKAGE, 'set');
|
|
6
6
|
const ENTRY = 'SKILL.md';
|
|
7
|
-
const REF_PLACEHOLDER = '<house-ref>';
|
|
8
7
|
const TREE = join(PACKAGE, 'guide');
|
|
9
8
|
const PAGE = '.md';
|
|
10
9
|
const FRONTMATTER = /^---\n[\s\S]*?\n---\n\n?/u;
|
|
11
10
|
const HEADING = /^# /u;
|
|
11
|
+
const LIST_ITEM = /^\s*[-*+] /u;
|
|
12
12
|
export const GUIDE_ROOT = 'index';
|
|
13
13
|
const OWNER_RULES_PAGE = 'owner-rules';
|
|
14
|
-
const WORK_ON_TICKET_PAGE = 'routines/work-on-ticket';
|
|
15
14
|
function packaged(name) {
|
|
16
15
|
return readFileSync(join(PACKAGE, name), 'utf8');
|
|
17
16
|
}
|
|
18
17
|
function guidePage(file) {
|
|
19
18
|
const text = readFileSync(join(TREE, file), 'utf8');
|
|
20
|
-
const [heading = '', ,
|
|
19
|
+
const [heading = '', , ...rest] = text.replace(FRONTMATTER, '').split('\n');
|
|
20
|
+
const line = rest.find((held) => held !== '' && !LIST_ITEM.test(held)) ?? '';
|
|
21
21
|
return {
|
|
22
22
|
path: file.slice(0, -PAGE.length).split(sep).join('/'),
|
|
23
23
|
title: heading.replace(HEADING, ''),
|
|
@@ -33,17 +33,10 @@ export const GUIDE = readdirSync(TREE, {
|
|
|
33
33
|
.map((entry) => guidePage(relative(TREE, join(entry.parentPath, entry.name))))
|
|
34
34
|
.sort((left, right) => (left.path < right.path ? -1 : 1));
|
|
35
35
|
export const OWNER_RULES = GUIDE.find((page) => page.path === OWNER_RULES_PAGE).text;
|
|
36
|
-
export const WORK_ON_TICKET = GUIDE.find((page) => page.path === WORK_ON_TICKET_PAGE).text;
|
|
37
36
|
export const EARLIER_OWNER_RULES_SHA256 = JSON.parse(packaged('owner-rules-earlier.json'));
|
|
38
37
|
const INSTRUCTIONS = JSON.parse(packaged('agent-instructions.json'));
|
|
39
|
-
export function agentInstructions(
|
|
40
|
-
|
|
41
|
-
return [...INSTRUCTIONS.lines];
|
|
42
|
-
return [
|
|
43
|
-
...INSTRUCTIONS.lines,
|
|
44
|
-
INSTRUCTIONS.copies.replaceAll(REF_PLACEHOLDER, houseRef),
|
|
45
|
-
...copies,
|
|
46
|
-
];
|
|
38
|
+
export function agentInstructions() {
|
|
39
|
+
return [...INSTRUCTIONS.lines];
|
|
47
40
|
}
|
|
48
41
|
export function listSkills() {
|
|
49
42
|
return readdirSync(SET, { withFileTypes: true })
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Work the Wayfinding ticket this request names, from its Question to its Answer.
|
|
3
|
-
input: The ticket to work on.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
1
|
# Work on ticket
|
|
7
2
|
|
|
8
|
-
|
|
3
|
+
- **Description**: Work the Wayfinding ticket this request names, from its Question to its Answer.
|
|
4
|
+
- **Input**: The ticket to work on.
|
|
5
|
+
|
|
6
|
+
The Agent function an Agent runs when a person sets a Wayfinding ticket in progress with that Agent as its assignee.
|
|
9
7
|
|
|
10
8
|
Work the one ticket this request names, then stop:
|
|
11
9
|
|
|
12
|
-
1. Open the `wayfinding-ticket` the request names and set its `conversation` to this conversation.
|
|
10
|
+
1. Open the `wayfinding-ticket` the request names and set its `conversation` to the id of this conversation, which House's first message names.
|
|
13
11
|
2. Work it by its `kind`: for `research`, find out and say where you found it; for `prototype`, build the smallest thing that answers its Question; for `grilling`, ask your User until its Question is settled; for `task`, do what it asks.
|
|
14
|
-
3. Write what you found or made
|
|
12
|
+
3. Write what you found or made as its Answer and set its `status` to `done`.
|
|
15
13
|
4. Start no other ticket, not even one this ticket unblocks.
|
package/guide/apps/build.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
How to build an App over your User's Rooms and publish it, one page per step.
|
|
4
4
|
|
|
5
|
-
An App is a web page over your User's Rooms, for your User and whom they share it with (`
|
|
5
|
+
An App is a web page over your User's Rooms, for your User and whom they share it with (`shares/create`). House serves the built files and gives the page the App runtime, which holds every bound Room in the browser and syncs it with House. There is no server, backend or House API of yours to call.
|
|
6
6
|
|
|
7
7
|
- [Start an App](apps/start)
|
|
8
8
|
- [An App's data](apps/data)
|
package/guide/apps/chat.md
CHANGED
|
@@ -8,16 +8,36 @@ no dependency. `const chat = await houseBridge.chat.mount(element, options)`, on
|
|
|
8
8
|
object House hands the page as `window.houseBridge`, which `houseBridge()` of
|
|
9
9
|
`@agentshouse/app-bridge` returns, draws it in `element` over the App's own client and
|
|
10
10
|
resolves to a handle: `chat.update(options)` replaces its options and `chat.unmount()`
|
|
11
|
-
removes it.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
removes it. It fills the element and lays itself out by the width it gives: the list of the
|
|
12
|
+
User's Agents and their chats or one chat on a narrow element, the list beside the chat on a
|
|
13
|
+
wider one, and the chat's details beside both on a wide one. With no chat open it shows what
|
|
14
|
+
asks the User and what works now. `conversation` opens one chat, and with it `null` the Chat
|
|
15
|
+
opens a new chat with `agent`, the Agent it goes to, or the list with neither. `onOpen` hears
|
|
16
|
+
each chat it opens as `{ conversation, agent }`, and both `null` when the User goes back to the
|
|
17
|
+
list, and `onLink` takes a link without a scheme in place of the browser.
|
|
18
|
+
Or mount it as the Chat button, which takes no element:
|
|
19
|
+
`const button = await houseBridge.chat.mountButton(options)` draws House's dot at the
|
|
20
|
+
window's bottom-right corner, which counts what asks the User or, while nothing does, the chats
|
|
21
|
+
that replied since the User opened them, and breathes while a turn runs; pressing it opens the
|
|
22
|
+
Chat in a sheet above the page that the User moves, resizes, lets fill the window and hides.
|
|
23
|
+
`button.open(target)` opens the sheet, on `{ conversation }` or on a new chat with
|
|
24
|
+
`{ agent }`, or as it was with no target, and `button.hide()` hides it, keeping its chat,
|
|
25
|
+
scroll and typed text; `button.update(options)` and `button.unmount()` work as for a page, with
|
|
26
|
+
`onOpen` and `onLink`. To open on text written in, write it as the chat's draft through the
|
|
27
|
+
runtime's `chatDraft` first, keyed `new:<agent>` for a new chat. In the sheet the input carries
|
|
28
|
+
a toggle named after the App that attaches the page's Page metadata to one message: a new chat's
|
|
29
|
+
first message starts ticked. Mount one button per page.
|
|
30
|
+
In either form the Chat draws inside its own shadow root, so no rule of the App's stylesheet
|
|
31
|
+
reaches it. `chat.mount` needs an element that can hold a shadow root, such as a `div` or a
|
|
32
|
+
`section`, and rejects on a `li`, a `td` or a `form`.
|
|
16
33
|
Theme its own styles with CSS custom properties on the element or an ancestor:
|
|
17
34
|
`--chat-background`, `--chat-surface`, `--chat-text`, `--chat-text-muted`,
|
|
18
35
|
`--chat-accent`, `--chat-line`, `--chat-attention`, `--chat-danger`, `--chat-font`,
|
|
19
36
|
`--chat-font-mono`, `--chat-font-size`, `--chat-radius` and `--chat-density` (a factor for
|
|
20
|
-
spacing); an unset one keeps House's light or dark look.
|
|
21
|
-
the
|
|
37
|
+
spacing); an unset one keeps House's light or dark look. For the Chat button, which takes no
|
|
38
|
+
element, set them on the document's root element. Its dot is House's and takes none of them.
|
|
39
|
+
It shows conversations only where the release declares them;
|
|
40
|
+
otherwise it lists no conversation and no Agent. A secret
|
|
22
41
|
question is never answered here; typed answers live in the Presentation State members named
|
|
23
|
-
`typed
|
|
42
|
+
`typed:…`, whether the list shows in `chat:page-list`, and the sheet's size, place, list and
|
|
43
|
+
open chat in `chat:sheet`: leave them.
|
package/guide/apps/client.md
CHANGED
|
@@ -18,4 +18,4 @@ and returns `{ state: 'loading' }` until the topic's first value arrives, then `
|
|
|
18
18
|
parts)`, which saves through `saveParts` and rejects with a recorded failure.
|
|
19
19
|
`saveWritten(room, roomDrafts)` saves Room Drafts the App wrote, each named with the
|
|
20
20
|
revision its last write returned: one another tab wrote since is refused as `stale`, and
|
|
21
|
-
nothing is saved.
|
|
21
|
+
nothing is saved.
|
|
@@ -10,9 +10,9 @@ images and other resources only from origins the Provider's policy allows. An ag
|
|
|
10
10
|
never read the authorization, load code from a Room, call the Provider directly, or rebase
|
|
11
11
|
a refused Change Batch.
|
|
12
12
|
|
|
13
|
-
MD UI uses semantic HTML, is fully operable from a keyboard with
|
|
14
|
-
|
|
15
|
-
are English only, dates and numbers follow the browser locale, and content is
|
|
16
|
-
translated. Light and dark themes come from CSS custom properties, defaulting to the
|
|
13
|
+
MD UI uses semantic HTML, is fully operable from a keyboard with labelled controls and
|
|
14
|
+
the browser's own focus mark, and lays out from a 360 pixel wide viewport to a desktop.
|
|
15
|
+
Its strings are English only, dates and numbers follow the browser locale, and content is
|
|
16
|
+
never translated. Light and dark themes come from CSS custom properties, defaulting to the
|
|
17
17
|
browser's preference and respecting an App's override. What an agent adds carries none of
|
|
18
18
|
these promises by itself. Data lives only in the App Creator's browser and the Provider.
|
package/guide/apps/data.md
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
How an App keeps what it shows as typed documents in the Rooms bound to it.
|
|
4
4
|
|
|
5
|
-
- Design the Room schema first. Each thing the App shows is one Markdown document in a Room: its frontmatter `type` names its MD type
|
|
6
|
-
- Keep each type in its own folder, such as `tasks/`, and read it by `pathPrefix
|
|
5
|
+
- Design the Room schema first. Each thing the App shows is one Markdown document in a Room: its frontmatter `type` names its MD type and its body writes the values: its `name` as the `#` line, each short value as a `- **Label**: value` line and long text under its own heading ([Schema](reference/schema)). `schema.yaml` declares the types ([Schema](reference/schema)).
|
|
6
|
+
- Keep each type in its own folder, such as `tasks/`, and read it by `pathPrefix`. A query naming `types` fails with `unknown-type` over bound Rooms none of which declares them, and answers `{ pending }` instead while the App reaches no bound Room yet; an observed query that has already answered answers `{ pending }` while its bound Rooms refuse it, and answers again once they declare what it names.
|
|
7
7
|
- Link types with `reference` fields instead of copying values; a query follows them with `hop` and `backlinks`.
|
|
8
|
-
- `app.json` `room_slots` names the Rooms the App needs, each with `access` `read` or `write` and the `types` it uses ([App manifest](reference/app-manifest)). Bind Rooms with `
|
|
9
|
-
- A bound Room must hold each slot type identically in its own `settings/schema.yaml`, beside any other types, or
|
|
8
|
+
- `app.json` `room_slots` names the Rooms the App needs, each with `access` `read` or `write` and the `types` it uses ([App manifest](reference/app-manifest)). Bind Rooms with `apps/bindings/set`; App code cannot bind and reaches only bound Rooms, so it opens `bridge.ceremony('/bind/<app_ref>?slot=<name>')`, the House page where your User binds Rooms, which returns to the App.
|
|
9
|
+
- A bound Room must hold each slot type identically in its own `settings/schema.yaml`, beside any other types, or its binding warns the User who bound it, and `apps/bindings/list` names that release and slot in its `mismatch` line, to the App's owner too. Write it with `files_batch` before binding; `rooms/create` makes a new Room.
|
|
10
|
+
- A slot may hold a Space instead (`apps/bindings/set` with `space_ref`): every Room of it your User holds, now and later. An App its owner puts into a Space (`apps/space/set`) uses that Space's schema instead of its own `schema.yaml`, and every member of the Space uses it, each slot holding the Space's Rooms.
|
|
10
11
|
- Read an existing Room's `settings/schema.yaml` before inventing types. Two Apps over one schema are two interfaces over the same data, and your User's agents read and write those documents too.
|
|
11
|
-
- House's own facts, such as your User's Rooms, come from House acts: name each in `app.json` `house_acts` and call it with `runtime.call('<act>', input)`, such as `runtime.call('rooms/
|
|
12
|
-
-
|
|
13
|
-
-
|
|
12
|
+
- House's own facts, such as your User's Rooms and the Space each is in, come from House acts: name each in `app.json` `house_acts` and call it with `runtime.call('<act>', input)`, such as `runtime.call('rooms/list', {})`. A call needs a connection, so offline show what the bound Rooms hold. A slot holding the Private Room brings its Triggers and none of the other documents House writes under its `house/`.
|
|
13
|
+
- `shares/create` lets a person into Rooms and Apps: its answer's `added` is `true` when their _Who can add me_ setting admits your User, so they are in at once, and `false` when the Share waits for their Accept.
|
|
14
|
+
- What no browser can do, such as acting when documents change or at a set time, is a Trigger (`triggers/create`) that notifies or runs one of the App's [functions](apps/functions) on House's servers; what it writes reaches the App at once.
|
|
15
|
+
- A Trigger may also start an [Agent function](reference/agent-function) on an Agent of your User.
|
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
How a function hands each new lead to the manager on shift with the fewest leads since their shift began, records its decision and calls the CRM.
|
|
4
4
|
|
|
5
|
-
A CRM posts each new lead to the Room's push address (`
|
|
5
|
+
A CRM posts each new lead to the Room's push address (`capture/push-addresses/create`) as `{"event": "lead.created", "card": {"id": 101}}`. The Room holds three types: `staff/<key>.md` with `manager_id`, the manager's id at the CRM; `shifts/<key>.md` with `staff`, the key, and `starts` and `ends` as ISO instants; and `decisions/<card>.md` with `card`, `staff` and `decided_at`. The App's `external_origins` lists the CRM's origin, the Room holds a Service key for it, and a Trigger runs the function for each lead:
|
|
6
6
|
|
|
7
7
|
```json
|
|
8
8
|
{
|
|
9
9
|
"on": "items",
|
|
10
|
-
"address": "<push address>",
|
|
11
|
-
"match": { "path": "/json/event", "eq": "lead.created" }
|
|
10
|
+
"query": { "where": { "/address": "<push address>", "/json/event": "lead.created" } }
|
|
12
11
|
}
|
|
13
12
|
```
|
|
14
13
|
|
|
@@ -64,4 +63,4 @@ export default async (event, house) => {
|
|
|
64
63
|
};
|
|
65
64
|
```
|
|
66
65
|
|
|
67
|
-
A lead the CRM delivers again finds its decision and calls nothing. Two leads that arrive together run one after another, so the second counts the first's decision and goes to another manager when two are tied. Each count is a query's `total`, so a long shift is never cut at a page. A failed call fails the run with the CRM's answer
|
|
66
|
+
A lead the CRM delivers again finds its decision and calls nothing. Two leads that arrive together run one after another, so the second counts the first's decision and goes to another manager when two are tied. Each count is a query's `total`, so a long shift is never cut at a page. A failed call fails the run with the CRM's answer; the next lead still runs.
|
|
@@ -12,7 +12,7 @@ task:
|
|
|
12
12
|
required: [title, done]
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
`app.json`, then `
|
|
15
|
+
`app.json`, then `apps/bindings/set` with slot `tasks`:
|
|
16
16
|
|
|
17
17
|
```json
|
|
18
18
|
{ "entry": "index.html", "room_slots": [{ "name": "tasks", "access": "write", "types": ["task"] }] }
|
package/guide/apps/functions.md
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
How an App declares a function that House runs on its servers when a Trigger fires, with no page open and no model.
|
|
4
4
|
|
|
5
5
|
- **Declare.** `app.json` lists `functions`, each `{"name": "assign lead", "entry": "functions/assign.js"}` ([App manifest](reference/app-manifest)). The entry is one ES module whose default export is the function and which imports nothing, so your build bundles each function, with all it imports, into `dist/`, such as `esbuild src/assign.ts --bundle --format=esm --outfile=dist/functions/assign.js`. A publish is refused whole for an entry that is missing, imports, has no default export or is over <function-size>, for a name used twice, and for more than <release-functions> functions.
|
|
6
|
-
- **Run.** A Trigger's `function` action names the App and one of its functions ([Triggers](reference/triggers)). House runs the App's latest release when the Event's turn comes, one Event of a Trigger after another, so a run reads what the run before it wrote.
|
|
7
|
-
- **The event.** `export default async (event, house) => {}` receives `kind`, the condition's `on`, with `room`, `trigger`, `id`, `moment` and what matched: `path` and `reference`, a document change's `transition`, an arriving item's `item` with its `headers` and raw `body` text, a dated document's `document`,
|
|
8
|
-
- **The App API.** `house` is the page's App API over the Trigger's Room alone: `query(q)` ([Local Query](apps/local-query)), `saveParts(room, parts)`, `call(name, input)` for the Room acts such as `
|
|
9
|
-
- **Reach.** A run holds no `fetch`, timer, file, `console` or `Intl`: keep times as ISO instants, or convert a local time by its UTC offset yourself. `callExternal` takes the request `bridge.callExternal` takes and is the one way out, to an origin the release's `external_origins` lists; naming its Room, `room_ref: event.room`, carries that Room's Service key, which the function never sees, or is refused `
|
|
6
|
+
- **Run.** A Trigger's `function` action names the App and one of its functions ([Triggers](reference/triggers)). House runs the App's latest release when the Event's turn comes, one Event of a Trigger after another, so a run reads what the run before it wrote. Runs share a fixed number of slots on House's servers, and a run waits, its action `waiting`, until one is free.
|
|
7
|
+
- **The event.** `export default async (event, house) => {}` receives `kind`, the condition's `on`, with `room`, `trigger`, `id`, `moment` and what matched: `path` and `reference`, a document change's `transition`, an arriving item's `item` with its `headers` and raw `body` text, a dated document's `document`, a delivered notification's `notification`, the facts of its web document, or a message's `message`, its `sender` and its request's `agent_function`, never its words, with the document that request was started for as `path` and `reference`. `Date` reads `moment`. The return value is ignored, so write what you keep into the Room.
|
|
8
|
+
- **The App API.** `house` is the page's App API over the Trigger's Room alone: `query(q)` ([Local Query](apps/local-query)), which reads only the documents of the `types`, folders (`folder`, `pathPrefix`) and `paths` it names and answers over them alone, so the rest of a large Room is never read: name the `types` or `pathPrefix` of its `hop` and `backlinks` too, and select its parts, since a query or `hop` or `backlinks` naming none, or selecting `backlinks` or `'all'`, is refused `invalid-query`, `saveParts(room, parts)`, `call(name, input)` for the Room acts, each by its House act name, such as `rooms/members/list` and `triggers/fired/list`, and `callExternal(request)`. `query` and `callExternal` reject a refusal with its `code` for the function to catch, while `call` answers it as a result whose `kind` is `rejected` and `saveParts` as `outcome: 'refused'`, as on the page, so check what they answer. Its writes are the App's, by `App <App> (function <function>)`, fire Triggers, its own included, and count against the Room owner's Write limit.
|
|
9
|
+
- **Reach.** A run holds no `fetch`, timer, file, `console` or `Intl`: keep times as ISO instants, or convert a local time by its UTC offset yourself. `callExternal` takes the request `bridge.callExternal` takes and is the one way out, to an origin the release's `external_origins` lists; naming its Room, `room_ref: event.room`, carries that Room's Service key, which the function never sees, or is refused `not_found` naming the `origin`. Its calls count against the Room owner's `external_calls` limit.
|
|
10
10
|
- **Never repeated.** A run that started is never run again, even when it failed after a call reached the service. Send `event.id` as the service's idempotency key where it takes one, and name what you write by what the event carries, so a second delivery of the same thing finds it.
|
|
11
|
-
- **Failure and limits.** A throw settles the run `failed` with `function_failed` and its text, which `
|
|
11
|
+
- **Failure and limits.** A throw settles the run `failed` with `function_failed` and its text, which `triggers/fired/list` shows and the panel shows the Room's owner, with no notification; the next Event still runs. A run ends `failed` at <function-time>, at <function-memory> or past <function-calls> external calls: `time_cap_reached`, `memory_cap_reached` or `external_call_cap_reached`.
|
|
12
12
|
|
|
13
13
|
Next: [Example: assign a lead](apps/example-function)
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
# Local first
|
|
2
2
|
|
|
3
|
-
How an App reads and writes its Rooms on the device, offline
|
|
3
|
+
How an App reads and writes its Rooms on the device, offline too.
|
|
4
4
|
|
|
5
|
-
- The runtime holds every bound Room on the device and streams in every change
|
|
6
|
-
- Draw from `observe` at once, with no
|
|
7
|
-
-
|
|
5
|
+
- The runtime holds every bound Room on the device and streams in every change from any device, App or agent. `query` and `observe` answer from the device and never wait on the network; `search` asks House ([Search rules](apps/search)).
|
|
6
|
+
- Draw from `observe` at once, with no request of your own; an observation hears each later change by itself, so never poll, refetch or subscribe. An answer is `{ pending }` while a Room it reads has not reached this device or been prepared on it: draw the page without those documents; keep the empty state for a complete answer. `rooms` answers once the runtime knows the Rooms the page reaches. Offer binding a Room when it is empty while the sync status's `providerUnreachable` is false and `observe('connection-refusal')` answers `null`; any other empty list says only that nothing can be listed yet.
|
|
7
|
+
- Show no loading state: draw the last state the device holds. A wait shows only after an act the User pressed whose answer comes from outside the stream, such as `bridge.callExternal`, until it arrives.
|
|
8
|
+
- Write optimistically: write a Room Draft, then `save`. The change shows in every query at once as a Pending Change and reaches House when it can. `save` refuses at once, with its `code`, a change that is `stale`, `changed-underneath` or against the schema: say so and keep the form. After `changed-underneath`, draw the joined Room Draft and its `marks` ([Room Drafts](apps/room-drafts)).
|
|
8
9
|
- Show what the runtime reports: `observe('queue', room)` lists changes House has not confirmed yet, and `observe('sync-status')`'s `attentionRequired` lists each change House refused, returned as a Room Draft with why, to save again or discard.
|
|
9
10
|
- Keep what the User types in the Room Draft (`roomDraft.write`) as they type; it survives a new release replacing the page, a reload and another tab. Draw a form again only when its page changes, never on a streamed change.
|
|
10
11
|
- A document has one Room Draft, shared by every tab and App, and `save` takes all of it. A write replaces every part and names the revision it read, so write onto the parts it holds; one from an older revision answers `stale` with the current Room Draft: show that one. A field part names as its `base` the value the page showed, `{ value }`, or `null` when the document has no such field.
|
|
11
12
|
- Save a change the User does not type, such as a checkbox, with `saveParts(room, [{ path, parts }])`: it writes no Room Draft, so the one another tab holds stays.
|
|
12
|
-
- Offline, all of this works and saves wait
|
|
13
|
-
- A Room your User holds at the Open level locks once the browser has been offline 2 minutes, and from the moment it opens until House confirms the access. While it is locked, `rooms` lists it `locked: true`, a query
|
|
14
|
-
- An outside service is reached only at the origins `app.json` lists in `external_origins`, directly or through `bridge.callExternal`,
|
|
15
|
-
- The page's own storage is a disposable cache, the Room the only durable home of the App's data; handle any code or value you do not know
|
|
16
|
-
- React: `useObserve` and `useSave` from `@agentshouse/mdruntime/react`.
|
|
13
|
+
- Offline, all of this works and saves wait; `bridge.onStatus` reports `offline`: say so quietly.
|
|
14
|
+
- A Room your User holds at the Open level locks once the browser has been offline 2 minutes, and from the moment it opens until House confirms the access. While it is locked, `rooms` lists it `locked: true`, a query that does not name it leaves it out, one naming it answers `{ pending }` with `awaits: 'unlock'`, and every other read and write naming it, `save` included, answers the code `room-locked` and keeps nothing. Draw it locked until it unlocks and keep what the User types in the page; what was saved before stays and is sent then.
|
|
15
|
+
- An outside service is reached only at the origins `app.json` lists in `external_origins`, directly or through `bridge.callExternal`, sent by House's server. A call that names a Room bound to the App in a `write` slot with `room_ref` carries that Room's Service key for the origin, which the page never sees; any other call carries no key. A call through a Room that holds no key for the origin is refused `not_found` naming the `origin`: tell the person that the Room's owner or a manager enters the key under Connected on the Room's Settings page, since no App opens that page.
|
|
16
|
+
- The page's own storage is a disposable cache, the Room the only durable home of the App's data; handle any code or value you do not know by its `code` ([Refusals](apps/runtime-refusals)).
|
|
17
|
+
- React: `useObserve` and `useSave` from `@agentshouse/mdruntime/react`. The whole client: [The App runtime](apps/runtime).
|
|
@@ -11,11 +11,20 @@ How a Local Query answers each change, orders, groups and pages its result, and
|
|
|
11
11
|
one Path for another is the same file moved, and an unsent creation has neither until it
|
|
12
12
|
is Accepted. `revision` is the Provider's head revision, `null` for an unsent creation or edit,
|
|
13
13
|
and moves with an unsent move. A Room's last change is its Document with the latest `time`.
|
|
14
|
-
- **
|
|
15
|
-
and
|
|
14
|
+
- **Size and Created.** `size` is the bytes a row holds, a text's UTF-8 length or a file's
|
|
15
|
+
`byteLength`, and `created` the ISO 8601 time its file was created, or `null` where the
|
|
16
|
+
Provider names none; an unsent creation takes the time it was saved.
|
|
17
|
+
- **Order and groups.** `orderBy` sorts by `room`, `path`, `type`, `folder`, `size`, `created`,
|
|
18
|
+
`updated`, which is its change's `time`, or `{ field }`: missing
|
|
19
|
+
and null first, last with `nulls: 'last'`, then numbers and booleans, then date-times as instants, then text by `locale` or
|
|
16
20
|
code point with `stringSort: 'lexical'`, then lists item by item; ties end with Room, then Path.
|
|
17
|
-
`groupBy` by `room`, `type`, `folder` or `{ field }` answers `{ groups: [{ key, count }] }`,
|
|
18
|
-
counted over every match
|
|
21
|
+
`groupBy` by `room`, `type`, `folder`, `subfolder` or `{ field }` answers `{ groups: [{ key, count }] }`,
|
|
22
|
+
counted over every match. `subfolder` is the child folder of `pathPrefix`, or of the Room's root,
|
|
23
|
+
that holds a row, `null` for a row directly in it, so one query totals every child folder. A
|
|
24
|
+
group's `select: { size: true }` adds `documents` and `files`, each `{ count, size }`,
|
|
25
|
+
`updated: true` its latest `updated`, and `greatest: { field }` that field's greatest value. A
|
|
26
|
+
group orders by `count`, `size`, `updated`, `greatest` or a key, each with `nulls`. Grouped by
|
|
27
|
+
`folder` or `subfolder` alone, a folder holding no match answers a count of 0. A sort or group takes only a
|
|
19
28
|
single-value pointer. `limit`, at most 1,000 whatever a query names, bounds the Documents or the
|
|
20
29
|
groups, and a result it cuts short names `next`: the same query with it as `after` reads the next
|
|
21
30
|
window, in the same order, ties included. `total: true` adds `total`, every match's count. An
|
|
@@ -11,19 +11,27 @@ draw it as loading and observe the query to hear the complete answer. A query is
|
|
|
11
11
|
|
|
12
12
|
- **Filter.** `rooms` names Room locators and defaults to every Room. `types` names MD
|
|
13
13
|
Types, and `any` names untyped Markdown Documents. `pathPrefix` is a directory: `notes`
|
|
14
|
-
keeps `notes/a.md` and not `notes2/a.md`; `
|
|
14
|
+
keeps `notes/a.md` and not `notes2/a.md`; `outside` names directories whose contents stay
|
|
15
|
+
out, `['house']` leaving `house/` out; `folder` keeps only what lies directly in it.
|
|
15
16
|
`paths` keeps only the Paths it names: read named Documents so, never from a list.
|
|
16
|
-
|
|
17
|
+
`fileIds` keeps only the Documents of the `fileId`s it names, wherever they moved, on
|
|
18
|
+
the device alone: an App function refuses it.
|
|
19
|
+
`text` keeps the Documents whose text contains that phrase, ignoring case, ranking
|
|
20
|
+
nothing.
|
|
21
|
+
- **Fields.** Every value of a typed Document is named by JSON Pointer: `/status`,
|
|
17
22
|
`/shipping/city`, `~1` for a `/` and `~0` for a `~` in a key. A segment that meets a
|
|
18
23
|
list applies to every item and a number names one item. `where` names pointers, ANDs
|
|
19
24
|
them, and nests `$and` and `$or`; a pointer through a list holds if any item satisfies
|
|
20
25
|
it. A bare value is equality; the operators are `$eq`, `$ne`, `$in`, `$nin`, `$gt`,
|
|
21
|
-
`$gte`, `$lt`, `$lte`, `$all`, `$size` and `$
|
|
26
|
+
`$gte`, `$lt`, `$lte`, `$all`, `$size`, `$exists` and `$prefix`, which keeps text starting
|
|
27
|
+
with it, case kept. Equality on a list asks if it
|
|
22
28
|
contains the value. Missing, null and empty differ: `$exists: false` keeps a missing
|
|
23
|
-
field, `null` an explicit null, `''` empty text and `$size: 0` an empty list. A
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
field, `null` an explicit null, `''` empty text and `$size: 0` an empty list. A test on a
|
|
30
|
+
missing value is false and `$ne`, `$nin` and `$exists: false` true. A date
|
|
31
|
+
compares in calendar order and never with a date-time. A date-time compares as an instant,
|
|
32
|
+
whatever offset it is written in. A reference compares by the
|
|
33
|
+
Room Path it resolves to. An untyped Document's frontmatter, and a value under `oneOf` or a
|
|
34
|
+
conditional, is plain values. There is no regular expression.
|
|
27
35
|
- **Relations.** Within one Room, `hop: { field, types, where, pathPrefix }` keeps Documents
|
|
28
36
|
whose reference at pointer `field`, at any depth, points to a matching Document, and
|
|
29
37
|
`backlinks` with the same keys those a matching Document references, each once. A body
|
|
@@ -31,14 +39,14 @@ draw it as loading and observe the query to hear the complete answer. A query is
|
|
|
31
39
|
`resolve(room, path, destination)` answers its `document`, the `deleted` one's last
|
|
32
40
|
`revision`, or `unreadable` where the App does not bind it or it is locked.
|
|
33
41
|
- **Parts.** A Document query must `select`: `'all'`, or `fields`, `sections`,
|
|
34
|
-
`preamble`, `markdown`, `backlinks`, `change`, `fileId` and `
|
|
35
|
-
or a list of pointers and `sections` `'all'` or a list of names
|
|
36
|
-
return the
|
|
42
|
+
`preamble`, `markdown`, `backlinks`, `change`, `fileId`, `revision`, `size` and `created`, where `fields` is `'all'`
|
|
43
|
+
or a list of pointers and `sections` `'all'` or a list of names of sections whose heading
|
|
44
|
+
names no field. Selected pointers return the values holding only them, nested as written. A backlink names the
|
|
37
45
|
referring Path, MD Type and the pointer it came from as `field`. Room and Path always
|
|
38
46
|
return; a Markdown Document also returns its MD Type, and at most its first 1,000
|
|
39
47
|
backlinks: a query on the referring field reads the rest. A non-Markdown Document
|
|
40
|
-
returns only Room, Path and a selected `fileId` and `
|
|
41
|
-
record too, and a text file its text as a selected `markdown
|
|
48
|
+
returns only Room, Path and a selected `change`, `fileId`, `revision`, `size` and `created`, a binary Path entry its `binaryFile`
|
|
49
|
+
record too, and a text file its text as a selected `markdown`. An unselected part is absent. Select only what a screen shows: a list
|
|
42
50
|
that selects the Markdown hears every edit of it.
|
|
43
51
|
|
|
44
52
|
Next: [Local Query changes, order and results](apps/local-query-results)
|
package/guide/apps/page.md
CHANGED
|
@@ -6,8 +6,9 @@ What an App's page draws and how it behaves in the browser.
|
|
|
6
6
|
- The address bar is the shell's, not your page's: read your place from `bridge.address()` and `bridge.onAddress`, and move with `bridge.push` or `bridge.replace`, so Back, Forward, reload and a shared link keep it; a push to the address already shown replaces it, so a double click adds no history entry. After an act that ends what the page shows, and when a dialog closes, `bridge.replace` the next address; a replace with the address this page was opened from goes back to it, so Back returns to where the person came from. Keep the open item, tab, filter and search in the address. Addresses under `/render` and `/info` are House's.
|
|
7
7
|
- Files: take one in with `binaryFile.attach` the moment it is chosen and keep the Path it answers, hand one out with `binaryFile.download`, and show an image or video in `<room-media>` naming its width and height, so nothing waits for media and nothing moves when it arrives; never read a file whole to show or download it ([files](apps/runtime-files)).
|
|
8
8
|
- Mobile first: lay out from 360 pixels wide up, with no action behind hover alone.
|
|
9
|
-
- Every control works from the keyboard
|
|
9
|
+
- Every control works from the keyboard and has a label; give it no focus style of your own and never remove the browser's focus mark.
|
|
10
10
|
- An empty list says what it will hold and invites the first document.
|
|
11
11
|
- Long names truncate with an ellipsis and show in full on hover.
|
|
12
12
|
- The element is the link: a row, card or name opens its item; no "Open" buttons.
|
|
13
13
|
- Offer installation yourself: `bridge.install()` from a click shows the browser's prompt, or answers `manual` where you show how.
|
|
14
|
+
- Offer a way out where your User expects one: `bridge.ceremony('/sign-out')` signs them out of House and opens sign-in.
|
|
@@ -5,7 +5,8 @@ How App code writes a Room Draft, saves it and learns what became of each change
|
|
|
5
5
|
The MD Runtime owns one durable Room Draft per Room and Path, shared by every App and tab
|
|
6
6
|
across reloads; a Room the Provider reports lost takes its Room Drafts and queue with it.
|
|
7
7
|
Write one with `roomDraft.write(room, path, revision, parts)`, naming the revision you read
|
|
8
|
-
or `null`, and every part it now changes: fields,
|
|
8
|
+
or `null`, and every part it now changes: fields, long text and lists included, sections
|
|
9
|
+
whose heading names no field and the preamble, or the whole
|
|
9
10
|
Markdown, a creation, a move, into another Room when it names `room`, or a removal. A new
|
|
10
11
|
part names its base, the value you showed when it was opened, a field's as `{ value }` or
|
|
11
12
|
`null` for no such field, and keeps its first base; a binary Path entry's move or removal
|
|
@@ -14,21 +15,27 @@ current Room Draft.
|
|
|
14
15
|
|
|
15
16
|
Save takes one Room and the Room Drafts of it to save, each with its revision, validates
|
|
16
17
|
locally, and on success appends one Change Batch to that Room's queue and removes those Room
|
|
17
|
-
Drafts. Refused as `stale`, `changed-underneath` naming each part
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
Drafts. Refused as `stale`, `changed-underneath` naming each part changed underneath, or a
|
|
19
|
+
local validation rule, it creates nothing and removes no Room Draft. On `changed-underneath`
|
|
20
|
+
it first joins each one over the current Document under a new revision: show that one. A
|
|
21
|
+
field, a move, a removal and a replaced file keep yours; a section, the preamble, and a
|
|
22
|
+
whole Markdown whose sections do not match hold the current text, then yours; a whole
|
|
23
|
+
Markdown otherwise joins section by section. `marks`, keyed like `parts`,
|
|
24
|
+
names each part holding both sides with the `change`, `{ author, time }` or `null`, that
|
|
25
|
+
saved the current side, and a field's or file's `current`. Offer only Save and Discard; a
|
|
26
|
+
write keeps the marks of the parts it still names. A directory move or removal is that part
|
|
27
|
+
on each Document and binary Path entry beneath it, saved together; moves into another Room,
|
|
28
|
+
saved alone, are one Change Batch in both Rooms' queues. `saveParts(room, [{ path, parts
|
|
29
|
+
}])` saves parts, each Path once and each new part with its base, the same way but writes no
|
|
30
|
+
Room Draft: one held at a saved Path stays, one at a moved Path moves with it to a
|
|
31
|
+
destination that holds none. Never save by writing back a Room Draft read before.
|
|
25
32
|
|
|
26
33
|
Outcomes follow the Provider's answer. An Accepted Change Batch stays in the queue, drawn
|
|
27
34
|
over the confirmed layer, until that layer reaches the Room Version it was accepted at.
|
|
28
|
-
Conflicted and Rejected,
|
|
29
|
-
leave the queue and return as Room Drafts on their Documents,
|
|
30
|
-
Provider's structured refusal code and fields as returned, not known to have
|
|
31
|
-
withdrawn locally. The rest of the queue keeps synchronizing, and Sync Status requires
|
|
35
|
+
Conflicted and Rejected, and a change that no longer applies, each
|
|
36
|
+
leave the queue and return as Room Drafts on their Documents, joined as above and marked
|
|
37
|
+
with why: the Provider's structured refusal code and fields as returned, not known to have
|
|
38
|
+
applied, or withdrawn locally. The rest of the queue keeps synchronizing, and Sync Status requires
|
|
32
39
|
attention until each returned Room Draft is saved or discarded. `observe('queue', room)`
|
|
33
40
|
shows each Pending Change Batch, which keeps no outcome once it leaves. Transport failure,
|
|
34
41
|
Provider unavailability and a refusal marked retryable, until the reset it names, keep
|
|
@@ -4,9 +4,10 @@ What App code reaches through the runtime's client: its surfaces, its reach, obs
|
|
|
4
4
|
|
|
5
5
|
App code reaches the MD Runtime through the remote client. The surfaces are Rooms, Query,
|
|
6
6
|
Search, Observe, Types, Room Draft, Save, History, Resolve, binary files, Call,
|
|
7
|
-
Reached,
|
|
7
|
+
Reached, the reported persistence answer, reauthorization, conversation, the chat
|
|
8
8
|
draft, and Presentation State. Sync Status, readiness, the Service Mode, the storage-loss
|
|
9
|
-
statement, the deployment facts
|
|
9
|
+
statement, the deployment facts, the signed-in member and the count of authority changes
|
|
10
|
+
are observations only.
|
|
10
11
|
|
|
11
12
|
The host attaches each port with its reach, and every member and observation answers
|
|
12
13
|
within it: a Room outside it answers as one the replica does not hold, Rooms lists only
|
|
@@ -21,7 +22,8 @@ pending call and observation; a reconnected client observes again.
|
|
|
21
22
|
|
|
22
23
|
`observe('agents')` answers every Agent a new conversation can go to, each `{ identity,
|
|
23
24
|
name }`, a retired one left out: online every Agent of the User, with a conversation yet or
|
|
24
|
-
not,
|
|
25
|
+
not, each with its `cli` and `environment` `{ identity, name, state }`, `state` `online` or
|
|
26
|
+
`offline`; offline every Agent a conversation names. Each conversation the Provider lists is held
|
|
25
27
|
complete in the browser. `conversation.list()` and `observe('conversations')` answer each
|
|
26
28
|
with its Agent's identity and name, its name, created and finished times, last outcome,
|
|
27
29
|
live `state` (`asking` while a question waits, `working` while a turn runs, else `idle`)
|
|
@@ -30,14 +32,18 @@ limit, total: true })` holds its latest `limit` messages, at most 1,000, oldest
|
|
|
30
32
|
`total` their count, and `next` while older ones exist, which `after` takes, there or in
|
|
31
33
|
`conversation.read(id, { limit, after })`, for the window before; its chat draft; and,
|
|
32
34
|
only in memory, what the Provider streams whether or not anything views it: the running
|
|
33
|
-
`turn` with its
|
|
34
|
-
the
|
|
35
|
-
|
|
36
|
-
the
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
`turn` with its Session content parts, which the message accepted under the turn's id
|
|
36
|
+
replaces, the session's `state` with its context, limits, cost and what still runs, the open
|
|
37
|
+
questions, the process state, the CLI's commands and options, and `deliveryWaiting`, the
|
|
38
|
+
messages that wait in the Agent's queue, none stored or searchable. A saved message's
|
|
39
|
+
`content` holds its turn's parts, tokens and state, and one its owner took out of the queue
|
|
40
|
+
carries `removed: true`. `chatDraft.write` and `chatDraft.discard` keep what
|
|
41
|
+
the App Creator composes, `chatDraft.prepend(conversation, draft)` puts a returned
|
|
42
|
+
message ahead of it as it stands and `chatDraft.append(conversation, draft)` a text after it.
|
|
43
|
+
Sending it is a Provider Action that needs the Provider reachable and is never queued.
|
|
44
|
+
A new conversation's chat draft is keyed `new:<Agent
|
|
39
45
|
identity>`: no conversation list removes it, a retired Agent's goes, and the first answer
|
|
40
46
|
to `conversation.create({ agent_id, operation_id })` moves it to the created conversation,
|
|
41
47
|
where `conversation.send(conversation)` sends it; a replay of that create moves nothing.
|
|
42
48
|
|
|
43
|
-
Next: [
|
|
49
|
+
Next: [Conversation acts and the Chat's device state](apps/runtime-conversations)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Conversation acts and the Chat's device state
|
|
2
|
+
|
|
3
|
+
The conversation acts beyond sending, and what the browser keeps for the Chat.
|
|
4
|
+
|
|
5
|
+
Each act takes the operation id its caller makes once and hands again on a retry, so
|
|
6
|
+
House runs it once. `conversation.takeOut(conversation, message, operation)` takes a
|
|
7
|
+
message that waits in the Agent's queue out. `conversation.decide(request, 'approve' |
|
|
8
|
+
'decline', operation)` decides a waiting request, its `{ request, room, message? }` as the
|
|
9
|
+
conversation shows it. `conversation.rename(conversation, name, operation)` renames a
|
|
10
|
+
conversation, `conversation.option(conversation, option, value, operation)` sets a session
|
|
11
|
+
option and `conversation.retry(conversation, message, operation)` sends a refused message
|
|
12
|
+
again. A release that declares no conversations is refused each with `action-undeclared`
|
|
13
|
+
naming its act.
|
|
14
|
+
|
|
15
|
+
`conversation.pin(figures)` keeps the Chat's pinned figures, which every conversation's
|
|
16
|
+
`pins` shows, `conversation.openTurn(conversation, turn)` each chat's last opened turn,
|
|
17
|
+
which its row's `opened` shows beside `answered`, the turn its Agent answered last, and
|
|
18
|
+
`conversation.dismissSuggestion(conversation, suggestion)` each chat's suggested next
|
|
19
|
+
prompt the User dismissed, which that conversation's `dismissedSuggestion` shows, on this
|
|
20
|
+
browser for House and every App alike; none of it is sent to House. Without conversations
|
|
21
|
+
they read none and keep nothing.
|
|
22
|
+
|
|
23
|
+
`conversation.pageMetadata(appName, address)` answers the Page metadata block a message
|
|
24
|
+
carries after the User's text: the App's name, the page's address and, while the page
|
|
25
|
+
holds at most ten Local Queries open, each one's definition on its own line, never its
|
|
26
|
+
results.
|
|
27
|
+
|
|
28
|
+
Next: [Presentation State, binary files and the network](apps/runtime-files)
|