@agentshouse/skills 0.1.0-c96bb2a2009d2 → 0.1.0-c9924d1fdd3ac
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/agent-instructions.json +1 -1
- package/guide/apps/chat.md +15 -8
- package/guide/apps/example-function.md +1 -1
- package/guide/apps/function-times.md +10 -0
- package/guide/apps/functions.md +4 -4
- package/guide/apps/local-first.md +1 -1
- package/guide/apps/room-drafts.md +17 -11
- package/guide/apps/runtime-conversations.md +10 -0
- package/guide/apps/runtime-files.md +5 -3
- package/guide/apps/runtime-refusals.md +4 -2
- package/guide/apps/search.md +1 -1
- package/guide/apps/start.md +1 -0
- package/guide/reference/access-level.md +1 -1
- package/guide/reference/agent-function.md +1 -1
- package/guide/reference/app-manifest.md +2 -0
- package/guide/reference/group-post.md +7 -0
- package/guide/reference/telegram-link.md +11 -0
- package/guide/reference/triggers.md +5 -3
- package/package.json +1 -1
package/agent-instructions.json
CHANGED
|
@@ -4,6 +4,6 @@
|
|
|
4
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
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, `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
|
|
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 Standing answers there allow; its mail comes back into this conversation."
|
|
8
8
|
]
|
|
9
9
|
}
|
package/guide/apps/chat.md
CHANGED
|
@@ -10,7 +10,8 @@ object House hands the page as `window.houseBridge`, which `houseBridge()` of
|
|
|
10
10
|
resolves to a handle: `chat.update(options)` replaces its options and `chat.unmount()`
|
|
11
11
|
removes it. It fills the element and lays itself out by the width it gives: the list of the
|
|
12
12
|
User's Agents and their chats or one chat on a narrow element, the list beside the chat on a
|
|
13
|
-
wider one
|
|
13
|
+
wider one once the User shows it or the element passes 1000 px, and the chat's details beside
|
|
14
|
+
both on a wide one. With no chat open it shows what
|
|
14
15
|
asks the User and what works now. `conversation` opens one chat, and with it `null` the Chat
|
|
15
16
|
opens a new chat with `agent`, the Agent it goes to, or the list with neither. `onOpen` hears
|
|
16
17
|
each chat it opens as `{ conversation, agent }`, and both `null` when the User goes back to the
|
|
@@ -19,14 +20,20 @@ Or mount it as the Chat button, which takes no element:
|
|
|
19
20
|
`const button = await houseBridge.chat.mountButton(options)` draws House's dot at the
|
|
20
21
|
window's bottom-right corner, which counts what asks the User or, while nothing does, the chats
|
|
21
22
|
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
|
|
23
|
+
Chat in a sheet above the page, and pressing it again hides it. With `agent` among the options
|
|
24
|
+
the dot's first press opens it on a new chat with that Agent instead of a chat kept from an
|
|
25
|
+
earlier visit, and later presses reopen the chat it last showed; the sheet stays hidden until
|
|
26
|
+
the first press. The dot stays in view over the
|
|
27
|
+
sheet, which the User moves, resizes up to the window's edges and lets fill the window, and the
|
|
28
|
+
page under it never changes, so leave nothing of the page to make room for it.
|
|
23
29
|
`button.open(target)` opens the sheet, on `{ conversation }` or on a new chat with
|
|
24
|
-
`{ agent }`, or as it was with no target, and `
|
|
25
|
-
|
|
26
|
-
`
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
`{ agent }`, or as it was with no target, and with `docked: true` docked at full height along
|
|
31
|
+
the window's right edge; `button.hide()` hides it, keeping its chat, scroll and typed text;
|
|
32
|
+
`button.update(options)` and `button.unmount()` work as for a page, with `onOpen` and
|
|
33
|
+
`onLink`. To open on text written in, write it as the chat's draft through the runtime's
|
|
34
|
+
`chatDraft` first, keyed `new:<agent>` for a new chat. In the sheet the input's + holds This
|
|
35
|
+
page, which attaches the page's Page metadata to one message: a new chat's first message starts
|
|
36
|
+
with it ticked. Mount one button per page.
|
|
30
37
|
In either form the Chat draws inside its own shadow root, so no rule of the App's stylesheet
|
|
31
38
|
reaches it. `chat.mount` needs an element that can hold a shadow root, such as a `div` or a
|
|
32
39
|
`section`, and rejects on a `li`, a `td` or a `form`.
|
|
@@ -2,7 +2,7 @@
|
|
|
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 (`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:
|
|
5
|
+
A CRM posts each new lead to the Room's push address (`capture/push-addresses/create`) as `{"event": "lead.created", "card": {"id": 101}}`. Name where the CRM puts its event id as the address's `event_id`, when you create it or later with `capture/push-addresses/edit`, so a lead it sends again writes nothing; an address naming none takes a `webhook-id` header as the id. 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
|
{
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Times in an App function
|
|
2
|
+
|
|
3
|
+
How a function reads its Room's local time and turns a date and time there into a moment and back, with no time-zone library.
|
|
4
|
+
|
|
5
|
+
- **The event.** Beside `moment`, the UTC instant `Date` reads, the event carries the Room's `time_zone`, such as `Europe/Kyiv`, and the moment's `date` (`YYYY-MM-DD`) and `time` (`HH:MM:SS`) there, so the same day is the Room's day: `order.fields.delivery === event.date`.
|
|
6
|
+
- **To a moment.** `await house.toMoment({ date: '2026-11-10', time: '13:00' })` answers the ISO instant of a date and an `HH:MM` or `HH:MM:SS` time in the Room's zone, `'2026-11-10T11:00:00.000Z'` in Kyiv. At daylight saving, a time the clocks skip takes the moment just after the gap, `04:00` for `03:30` on the day Kyiv's clocks jump from `03:00` to `04:00`, and a time they repeat takes its earlier moment.
|
|
7
|
+
- **To the Room's time.** `await house.toLocal({ moment: event.moment })` answers the `time_zone`, `date` and `time` in the Room's zone of a date-time with its offset, written as a document's `date-time` field takes it.
|
|
8
|
+
- **Each is a call to House** that counts as no external call and rejects what it refuses with its `code`: `invalid-time` for what is no date and time, or no date-time with its offset, and `unknown-room` for a `room_ref` naming any Room but the run's own.
|
|
9
|
+
|
|
10
|
+
Next: [App functions](apps/functions)
|
package/guide/apps/functions.md
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
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. Runs share a fixed number of slots on
|
|
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)`. `
|
|
9
|
-
- **Reach.** A run holds no `fetch`, timer, file, `console` or `Intl`:
|
|
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 each server, one Room owner's at most <function-owner-slots>, and a run waits, its action `waiting`, until it may take one.
|
|
7
|
+
- **The event.** `export default async (event, house) => {}` receives `kind`, the condition's `on`, with `room`, `trigger`, `id`, `moment`, the Room's `time_zone`, `date` and `time` ([Times](apps/function-times)) 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)`. `call` answers a refusal as a result whose `kind` is `rejected` and `saveParts` as `outcome: 'refused'`, as on the page, and the others reject it with its `code`. 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`: convert times by `house.toMoment` and `house.toLocal`, which a page lacks. `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
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
|
|
|
@@ -5,7 +5,7 @@ How an App reads and writes its Rooms on the device, offline too.
|
|
|
5
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
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
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.
|
|
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)).
|
|
9
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.
|
|
10
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.
|
|
11
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.
|
|
@@ -15,21 +15,27 @@ current Room Draft.
|
|
|
15
15
|
|
|
16
16
|
Save takes one Room and the Room Drafts of it to save, each with its revision, validates
|
|
17
17
|
locally, and on success appends one Change Batch to that Room's queue and removes those Room
|
|
18
|
-
Drafts. Refused as `stale`, `changed-underneath` naming each part
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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.
|
|
26
32
|
|
|
27
33
|
Outcomes follow the Provider's answer. An Accepted Change Batch stays in the queue, drawn
|
|
28
34
|
over the confirmed layer, until that layer reaches the Room Version it was accepted at.
|
|
29
35
|
Conflicted and Rejected, and a change that no longer applies, each
|
|
30
|
-
leave the queue and return as Room Drafts on their Documents,
|
|
31
|
-
Provider's structured refusal code and fields as returned, not known to have
|
|
32
|
-
withdrawn locally. The rest of the queue keeps synchronizing, and Sync Status requires
|
|
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
|
|
33
39
|
attention until each returned Room Draft is saved or discarded. `observe('queue', room)`
|
|
34
40
|
shows each Pending Change Batch, which keeps no outcome once it leaves. Transport failure,
|
|
35
41
|
Provider unavailability and a refusal marked retryable, until the reset it names, keep
|
|
@@ -12,6 +12,16 @@ option and `conversation.retry(conversation, message, operation)` sends a refuse
|
|
|
12
12
|
again. A release that declares no conversations is refused each with `action-undeclared`
|
|
13
13
|
naming its act.
|
|
14
14
|
|
|
15
|
+
`conversation.attach(file)` attaches a `File` to a message, whatever Rooms the App reaches:
|
|
16
|
+
House writes it into the User's Private Room at `attachments/<YYYY-MM-DD>/<name>` and it
|
|
17
|
+
answers `{ kind: 'attached', link }`, the file's link with its key, which opens that one
|
|
18
|
+
file to whoever holds the link, so write it into the chat draft or the text you send; or
|
|
19
|
+
`{ kind: 'offline' }`, writing nothing, or a refusal or failure.
|
|
20
|
+
`conversation.attachment(link)` answers such a link's file as `{ kind: 'link', url, name,
|
|
21
|
+
mediaType }`, its short-lived address, `rejected` `not_found` for a key that opens nothing,
|
|
22
|
+
or a failure. Without conversations the attach is refused `action-undeclared` and the read
|
|
23
|
+
answers `absent`.
|
|
24
|
+
|
|
15
25
|
`conversation.pin(figures)` keeps the Chat's pinned figures, which every conversation's
|
|
16
26
|
`pins` shows, `conversation.openTurn(conversation, turn)` each chat's last opened turn,
|
|
17
27
|
which its row's `opened` shows beside `answered`, the turn its Agent answered last, and
|
|
@@ -29,9 +29,11 @@ or remove one as a Document, its bytes following.
|
|
|
29
29
|
- `<room-media room path width height alt>` draws an image, video or audio file of the
|
|
30
30
|
Room. It reserves the box `width` and `height` set, in the file's own pixels, or a 16:9
|
|
31
31
|
one, before any byte; asks for the file only as it nears the view; draws a file the
|
|
32
|
-
browser keeps from its bytes, offline too, and reads and keeps an image;
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
browser keeps from its bytes, offline too, and reads and keeps an image; shows a video's
|
|
33
|
+
first frame with a play mark and plays it only in House's full-screen viewer, which a
|
|
34
|
+
press on it opens, and plays an audio file, loading only either's metadata from the
|
|
35
|
+
Provider's address; asks again when a shown file's address lapses; asks again while the
|
|
36
|
+
file cannot be shown yet, such as
|
|
35
37
|
while the Room does not hold it or House did not answer, until House refuses it for
|
|
36
38
|
good; and shows its children where the file cannot be read.
|
|
37
39
|
- `binaryFile.read(room, path, revision?)` answers `{ kind: 'binary', stream, byteLength,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Refusals, deployment facts and the member
|
|
2
2
|
|
|
3
|
-
How the MD Runtime API refuses and fails, and how App code reads the deployment facts
|
|
3
|
+
How the MD Runtime API refuses and fails, and how App code reads the deployment facts, who uses it and their time zone.
|
|
4
4
|
|
|
5
5
|
A refusal or failure carries `code` and `retryable`: each Call and History answer other
|
|
6
6
|
than its result, a refused Save, a returned Room Draft's reason, a Room's refusal and every
|
|
@@ -21,4 +21,6 @@ named; signing out answers `null`, then ends the observation with the authorizat
|
|
|
21
21
|
`observe('authority-changes')` answers how many times House told the runtime that what its
|
|
22
22
|
User may see changed, a Room's members, a Share or an offer among them, and hears each next
|
|
23
23
|
one, never a reconnection, so a view that read such a fact through Call reads it again when
|
|
24
|
-
the count moves.
|
|
24
|
+
the count moves. `observe('time-zone')` answers that User's IANA time zone, `null` until the
|
|
25
|
+
Provider names one and where they hold none, kept and heard like the member; the Chat shows its
|
|
26
|
+
times in it, and a page may too.
|
package/guide/apps/search.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
How `search` finds Documents by words through House, and how a page finds them offline.
|
|
4
4
|
|
|
5
|
-
`search({ text, rooms })` asks House's search
|
|
5
|
+
`search({ text, rooms })` asks House's search once and online: there
|
|
6
6
|
is no observed search and the device keeps no full-text index. `rooms` names Room locators
|
|
7
7
|
and defaults to every Room the page binds; the search never leaves those Rooms, and finds
|
|
8
8
|
words in every Document there.
|
package/guide/apps/start.md
CHANGED
|
@@ -8,5 +8,6 @@ How to get an App's repository, build it and publish every change.
|
|
|
8
8
|
- Only when no App exists yet, clone `<origin>/app/new.git` the same way: it gives mdui, a minimal App in plain TypeScript with its test and `npm run publish`. Run `npm ci --ignore-scripts`, then `npm run publish -- --name "<display name>"`, which creates the App and prints its address and its `.git` address. Then `git remote set-url origin <that .git address>`; every later `npm run publish` goes there, without `--name`.
|
|
9
9
|
- Use any framework and build tool. `npm run build` leaves the whole release in `dist/`: the entry HTML, every file it loads, `app.json` and `schema.yaml`. The entry names files by relative paths (Vite: `base: './'`), carries no inline script and loads nothing from outside the release, so bundle fonts and libraries. A release holds at most <release-files> files and <release-size>.
|
|
10
10
|
- Your User sees a change only once it is published, so an ask to change an App is an ask to publish it: end every change with a commit and `npm run publish`, which builds, pushes `main` and the build, and prints `published` or the reason House refused. A `git push` of `main` alone publishes nothing and says so. A refused push changes nothing and the previous release keeps running.
|
|
11
|
+
- When a House update removes or narrows something your App's code uses, House commits a fix to its `main`, as `House`, and publishes it. A push refused with `fetch first` means someone committed after your clone: `git pull`, then publish again.
|
|
11
12
|
- Without Git, zip the project folder after the build, without `node_modules` and `.env`, `upload` the ZIP to a Room and publish it with `apps/publish-zip`.
|
|
12
13
|
- A page whose first draw is not yet the place it opened on, as one that draws its Room's documents once `observe` answers, registers `bridge.cooperate({ placed })` in its entry script before its first draw, where `placed` resolves once that place shows what it holds, as [the example](apps/example-tasks) does: House keeps its preparation surface over the page until then, and shows a page that registers none at its first draw. An open page keeps the release it loaded and runs the next one at its next load, so it needs no update code; `node_modules/@agentshouse/app-bridge/docs/families.md` describes each bridge member.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
How much of a Room a grant or a Share lets its holder do with its text, and where.
|
|
4
4
|
|
|
5
|
-
Access level, `level` on a Room grant or a Share’s Room item, each level including the one before it: `search`, the Search level, reaches the Room through `
|
|
5
|
+
Access level, `level` on a Room grant or a Share’s Room item, each level including the one before it: `search`, the Search level, reaches the Room through `shell` and `inspect` alone, each reply at most <mcp-read-size> of the Room’s text, with no copy anywhere; `open`, the Open level, also opens the Room in browser Apps while the browser is online, and acts as `search` everywhere else; `keep`, the Keep level, also copies the Room to the devices that keep it, offline included, and is the one `export` needs. Apps need `open` or `keep`.
|
|
6
6
|
|
|
7
7
|
```json
|
|
8
8
|
{ "room_ref": "r_ab12.1", "level": "open", "operations": ["write"] }
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
How an Agent function document in the Private Room is written, one for each Room it serves.
|
|
4
4
|
|
|
5
|
-
Agent function, `/rooms/private/rooms/<room-handle>/agent-functions/<name>.md`, serving the Room `<room-handle>` names: `description`; `input`, a text saying what to send, empty if nothing; `agent`, the identifier of the one of your User's current Agents it starts; and `afk`, `true` for a run nobody watches, off when absent, under which Kit answers every question and permission prompt of the conversation a request starts by itself, for that conversation's life. The body is the instruction, which never leaves the Private Room. Writing it needs your User's write in that Room. House writes its card, the description, input and
|
|
5
|
+
Agent function, `/rooms/private/rooms/<room-handle>/agent-functions/<name>.md`, serving the Room `<room-handle>` names: `description`; `input`, a text saying what to send, empty if nothing; `agent`, the identifier of the one of your User's current Agents it starts; and `afk`, `true` for a run nobody watches, off when absent, under which Kit answers every question and permission prompt of the conversation a request starts by itself, for that conversation's life. The body is the instruction, which never leaves the Private Room. Writing it needs your User's write in that Room. House writes its card, the description, input, Agent and `answer`, at `/rooms/<room-handle>/house/agent-functions/<name>.md`, where every member reads it, and refuses a name a card there already holds as `name_taken`. An Agent function a second Room needs is a copy under that Room's handle. While its document is not written in this form, every request for it is refused `agent_function_unreadable`. Anyone who writes that Room asks it with `agent-functions/send` naming the card, or names the `agent` with their own words, as the Standing answer there allows: the card's `answer`, or the Agent's for own words. `at_once`, the default, starts it; `ask_me` waits for the owner's approval; `off` refuses it, `agent_function_not_admitted` or `own_text_not_admitted`. Only the owner sets an answer, on the Room's House page; no field of this document holds one. The owner, their agents and their Private Room's Triggers start at once under every answer, a Trigger of another Room asks as its writers do, and `inspect` on a Room root names the Agents you may ask there with their answers. An Agent function request's input is text the Agent reads, and House checks no shape. Mail your conversation sends with `agent-functions/mail/send` and a request it sent reaches the conversation that request started at once where that Agent's own-words answer there is `at_once`; otherwise `agent-functions/mail/send` answers `waiting: true` and the mail waits for that Agent's owner, and a declined one comes back to you as House's message saying so. That conversation's mail always comes back to yours. A request's run is the first turn of the conversation it starts: during that turn the Agent names the document it wrote in the request's Room as the request's result with `agent-functions/result/set`, and the turn's end finishes the request. The conversation stays active until it is Done, which `conversations/mark-done` sets at the end of a turn and which nothing undoes, so mail reaches it after its run.
|
|
6
6
|
|
|
7
7
|
A Trigger's `message` action puts its `text`, a link to what matched and its files into the active conversation, one not Done, its Agent function started from that Room for what the Event is about: the chat of a customer's `chat-message`, the matched document or item, or the Trigger for a `cron` clock. Its Event shows `continued` with that `request`; with none, it starts the Agent function as a `agent_function` action does, `requested`. A conversation waiting for approval refuses it, `refused` with `conversation_waiting`. The Agent reads a customer's message as a customer's words, one from a Trigger of its owner's Private Room as the owner's instruction, and any other as what matched, never an instruction. A `agent_function` action starts a new request on every Event. A `done` action marks Done every active conversation a request of its Trigger's Room started for the Event's document, whoever owns its Agent, but one waiting for approval, which stays for its owner to approve or decline; its Event shows `marked` with their number, `conversations`, 0 when there are none, as on a `cron` clock, whose Event names no document.
|
|
8
8
|
|
|
@@ -4,6 +4,8 @@ What an App's `app.json` declares.
|
|
|
4
4
|
|
|
5
5
|
App manifest, `app.json` at an App’s root: `entry`, its HTML page, and `room_slots`, each with a `name`, `access` `read` or `write`, and `types` from the `schema.yaml` beside it, written like a Room schema; `apps/bindings/set` fills a slot with Rooms. Where needed it adds `external_origins`, the https origins its page may reach; `conversations: true` to read your User’s conversations; `inboxes: true` to show customer chats, naming `chat` and `chat-message` in `room_slots`, and answer them through `chats/reply`, since only House writes a chat's documents; `house_acts`, the House acts it calls by their act names, such as `rooms/list` for your User’s Rooms or `rooms/rename`, never one only a person takes or one that changes what an App reaches, except that a Self-hosted Building's App may declare its administrators' acts, `building/people/list`, `building/people/add`, `building/people/remove`, `building/administrators/name`, `building/administrators/take`, `building/factor/require`, `building/factor/stop-requiring` and `building/factor/reset`, which its page calls in an administrator's browser once it has asked them to confirm each; `functions`, each a `name` and the `entry` file a Trigger runs on House's servers ([App functions](apps/functions)); and `theme_colour`, `#rrggbb`. A User who does not own the App is asked nothing when it opens and once to allow each House act it declares, at the App’s first call of that act, which waits on the answer; a declined one is refused. Nobody is asked about an act they have no right to use, and it is refused them unasked, the App's owner included: the administrators' acts `not_building_administrator` to anyone who is no administrator, and `rooms/create`, the acts that change another member's access, `rooms/access/entries/set`, `rooms/access/schema-roles/set` and `rooms/access/revoke`, and the owner acts the Private Room never admits, `rooms/archive`, `rooms/unarchive`, `rooms/public/set`, `rooms/rename`, `rooms/space/set`, `rooms/delete` and `rooms/ownership/cancel`, `operation_denied` to anyone who manages no Room but their Private Room. [Build an App](apps/build) shows how its page works.
|
|
6
6
|
|
|
7
|
+
Where the Building runs its own bot, the administrators' acts include a person's [one-time link](reference/telegram-link).
|
|
8
|
+
|
|
7
9
|
```json
|
|
8
10
|
{
|
|
9
11
|
"entry": "index.html",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Group post
|
|
2
|
+
|
|
3
|
+
How a Trigger's `notify` action posts in its Room's Telegram group, the one `rooms/telegram/attach` attaches, in place of each User's own notices.
|
|
4
|
+
|
|
5
|
+
With `in_group: true`, e.g. `{kind: "notify", title: "Booking today", text: "A same-day booking came in.", to: {roles: ["admin"]}, in_group: true}`, the action posts once in the group's General topic: its title and text, an Open in House button to what matched, and each User it names who reads the Room, as many as one message holds, mentioned through Telegram, or by display name where they have not linked it. Nobody needs to agree to it, nobody is told on their own channels, and the post is not kept in the Room, so no Trigger fires on it.
|
|
6
|
+
|
|
7
|
+
`triggers/fired/list` shows it `posted` with how many it `mentioned`; `no_target` with `no_group` while the Room has no group or its group is paused, at the firing or the send, and with `no_recipient` when it names no reader; or `failed` with `post_not_confirmed` when Telegram refused it, after one try more past a wait it named, or gave no answer. That post is never sent again, and the Room's owner and the Trigger's creator hear that it needs fixing.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# One-time link
|
|
2
|
+
|
|
3
|
+
How a Self-hosted Building's App gives a person a one-time link to the Building's own bot, and undoes one taken wrongly.
|
|
4
|
+
|
|
5
|
+
An App of a Self-hosted Building that runs its own bot may declare `building/people/telegram-link` and `building/people/telegram-link/undo` beside its administrators' other acts, which its page calls in an administrator's browser once it has asked them to confirm each, and which anyone who is no administrator is refused unasked, `not_building_administrator`.
|
|
6
|
+
|
|
7
|
+
`building/people/telegram-link` takes `{person}`, the id `building/people/list` answers, and answers `{link}`, the bot's start address with a code bound to that person, which the administrator sends them in Telegram. It is given a person added by name who has not registered, whose account its first Join registers with that Telegram account, no email and the name they were added under, or a person who holds an account and an email, to whom it adds that Telegram account; anyone else is refused `telegram_link_refused`. The link works once, lapses after a day, and a fresh copy replaces the one before. A Telegram account another person holds is refused in the bot.
|
|
8
|
+
|
|
9
|
+
`building/people/list` answers beside each person their link's `telegram_link`, `waiting`, `lapsed` or `taken`, and `telegram_name`, the name of the Telegram account that took it.
|
|
10
|
+
|
|
11
|
+
`building/people/telegram-link/undo` takes `{person}`. Where the link registered the account, it removes that account as `building/people/remove` does, the administrator who undoes it as successor, and the person is listed again as added, under their id, so a fresh link can be copied and that Telegram account may take it. Where it added Telegram to an account the person held, it unlinks that Telegram account alone.
|
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
How a Trigger's `condition` and `actions` are written for `triggers/create` and `triggers/edit`; House writes a Room's Triggers as read-only `house-trigger` documents at `house/triggers/<trigger>.md`.
|
|
4
4
|
|
|
5
|
-
`condition`: When it fires: `on`, a `query` in the [Local Query](apps/local-query) language, at most one of `types`, `pathPrefix` or `paths` (one document, followed when it moves) and a `where`, naming a member by their member document's path, then the change tests. A task becoming ready: `{on: "documents", query: {types: ["task"], where: {"/status": "review"}}}`, firing as it starts to match, a new one only with `new_documents: true`. A field moving: `changes: {field: "/status", from: ["open"], to: ["done"]}`, or `from: "new"`, `to: "deleted"`; any edit `"text"`. `by: {"/door": "browser", "/user": "<User id>"}` tests who changed it and how; `moved_between_rooms: true` only a move between Rooms. Items: `{on: "items", query: {where: {"/address": "<address>", "/json/ref": "refs/heads/main"}}}`, also `/source`, `/delivery`, `/headers/<name>`. A clock is `{on: "clock", cron: "0 9 * * 1"
|
|
5
|
+
`condition`: When it fires: `on`, a `query` in the [Local Query](apps/local-query) language, at most one of `types`, `pathPrefix` or `paths` (one document, followed when it moves) and a `where`, naming a member by their member document's path, then the change tests. A task becoming ready: `{on: "documents", query: {types: ["task"], where: {"/status": "review"}}}`, firing as it starts to match, a new one only with `new_documents: true`. A field moving: `changes: {field: "/status", from: ["open"], to: ["done"]}`, or `from: "new"`, `to: "deleted"`; any edit `"text"`. `by: {"/door": "browser", "/user": "<User id>"}` tests who changed it and how; `moved_between_rooms: true` only a move between Rooms. Items: `{on: "items", query: {where: {"/address": "<address>", "/json/ref": "refs/heads/main"}}}`, also `/source`, `/delivery`, `/headers/<name>`. A clock is `{on: "clock", cron: "0 9 * * 1"}`, or a day before each task is due: `{on: "clock", query: {types: ["task"]}, date: "/due", days: -1, at: "09:00"}`, in the Room's time zone unless `time_zone: "UTC"` names another. In your Private Room alone, a notification to you: `{on: "notifications", query: {where: {"/kind": "share-offer"}}}` over its `house-notification` document; such a Trigger holds no `notify` action. A refusal names the test and what it takes.
|
|
6
6
|
|
|
7
7
|
A message into a conversation the Room's [Agent function request](reference/agent-function) started: `{on: "messages", query: {where: {"/sender": "requester"}}}`, testing `/sender`, `/agent_function` and `/path`, the request's document, never its words.
|
|
8
8
|
|
|
9
|
-
`actions`: a `notify` action names whom it reaches, e.g. `{kind: "notify", title: "Ready", text: "A task waits.", to: {users: ["<User id>"], fields: ["/members/assignee"], roles: ["owner"]}}
|
|
9
|
+
`actions`: a `notify` action names whom it reaches, e.g. `{kind: "notify", title: "Ready", text: "A task waits.", to: {users: ["<User id>"], fields: ["/members/assignee"], roles: ["owner"]}}`: Users by id, the member a matched document's field names and each Schema Role holder; a `function` action names an App and a function its latest release declares, e.g. `{kind: "function", app: "a_…", function: "assign lead"}`, only for an App you hold with this Room in a write slot of your bindings and run as [App functions](apps/functions) says; `triggers/fired/list` shows each run `ran` with its `release`, or `failed` (`function_failed`, its `error`), never run again, or `function_not_started` while House tries, `final` after its last try.
|
|
10
|
+
|
|
11
|
+
A `notify` action with `in_group` is a [Group post](reference/group-post).
|
|
10
12
|
|
|
11
13
|
In your Private Room alone, a `function` action naming no `app` runs one of your Environment functions ([wizard](skills/wizard)), e.g. `{kind: "function", function: "nightly backup"}`; elsewhere, or for one you do not hold, it is refused `not_found`. Kit runs its script with no terminal, the Event as JSON in the file `HOUSE_INPUT` names, one Event at a time. `triggers/fired/list` shows each run `ran` or `failed` (`function_failed`, its `error`), never run again, with its `run`, `exit_code` and `output`; a Kit not connected at its turn reads `no_target` with `environment_offline`, never run later.
|
|
12
14
|
|
|
13
|
-
A Trigger acts on at most <trigger-events-per-hour> Events in each UTC hour, across its versions, skipped
|
|
15
|
+
A Trigger acts on at most <trigger-events-per-hour> Events in each UTC hour, across its versions, skipped ones not counted; `triggers/fired/list` shows the first past the number `missed: "skipped"` with `code: "cap_reached"`, which starts nothing, and none later that hour; the Trigger shows as failing; narrow the condition of one whose own actions make what it fires on.
|
|
14
16
|
|
|
15
17
|
`{kind: "agent_function", card: "<name>", payload: "<input>"}` starts an [Agent function](reference/agent-function) and `{kind: "message", card: "<name>", text: "<text>"}` messages it; no such card is refused `not_found`, a retired one `agent_function_retired`, and one whose document misses its type's form fails `agent_function_unreadable` for good. `{kind: "done"}` marks Done each active conversation the Room's Agent function requests started for the Event's document, `marked` with their number.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentshouse/skills",
|
|
3
|
-
"version": "0.1.0-
|
|
3
|
+
"version": "0.1.0-c9924d1fdd3ac",
|
|
4
4
|
"description": "The upstream Skills agents.house serves its agents, verbatim, and the texts it writes for them: the Guide, its own Skills among the pages, and the Agent instructions",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|