Harness spec 3.6.0: phone_action, done on the person's phone and confirmed there
ci / check (push) Waiting to run
ci / check (push) Waiting to run
harness/phone.md, tools/phone_action.json, loop.json phone, conformance/phone.json. The LLeMbas server's tool alone (scope llembas, sessions chat), offered once a message came from the LLeMbas app with the capability phone_actions (new requirement phone). Actions: alarm, timer, calendar_event, sms_draft, email_draft, dial, navigate, add_stop, search_nearby, stop_navigation, open_app, open_url, media, torch, settings_panel, automation_intent. Each reads its own fields; required fields and value checks give the call's refusal in the spec's words. The person confirms on the phone every time; the result is done, declined or failed with what the phone said, or no answer in time. add_stop carries the route's destination: no map app adds to a running route, so the phone plans again via the stop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
8 files changed
+1221
-7
No files matched your search
@@ -4,6 +4,17 @@ All notable changes to LLeMbas CLI. The format follows
|
|||||||
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
|
||||||
[Semantic Versioning](https://semver.org/).
|
[Semantic Versioning](https://semver.org/).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Harness spec 3.6.0: phone actions.** The spec now describes `phone_action`, the LLeMbas
|
||||||
|
server's tool for doing something on the person's phone when they write from the LLeMbas app on
|
||||||
|
it — alarms and timers, calendar events, SMS and email drafts, the dialer, routes (start one,
|
||||||
|
add a stop, search nearby, stop), opening an app or an address, media, the torch, settings
|
||||||
|
panels and automations — confirmed on the phone every time (`harness/phone.md`). The server's
|
||||||
|
alone: the CLI does not offer it, and its conformance area is the server's.
|
||||||
|
|
||||||
## [2.0.0-beta.7] — 2026-11-10
|
## [2.0.0-beta.7] — 2026-11-10
|
||||||
|
|
||||||
Harness spec 3.5.0: scheduled work on a linked computer.
|
Harness spec 3.5.0: scheduled work on a linked computer.
|
||||||
|
|||||||
+17
-3
@@ -22,11 +22,12 @@ page *Harness-parity* in LLeMbas CLI's wiki.
|
|||||||
| `permission/defaults.json` | The rules every session starts from |
|
| `permission/defaults.json` | The rules every session starts from |
|
||||||
| `permission/modes.json` | The four modes — manual, edit, auto, plan — as what each does with each risk class |
|
| `permission/modes.json` | The four modes — manual, edit, auto, plan — as what each does with each risk class |
|
||||||
| `models/schema.json` | Model metadata: context, output limit, efforts and the thinking switch, vision, tools, notes, capacity (`parallel`); `levels` — the effort levels a chat offers and what each sends |
|
| `models/schema.json` | Model metadata: context, output limit, efforts and the thinking switch, vision, tools, notes, capacity (`parallel`); `levels` — the effort levels a chat offers and what each sends |
|
||||||
| `loop.json` | How one prompt runs: the step backstop, budgets, wrap-up, nudges, parallel calls, capacity; `jobs` — background work, its marks and limits; `subagents` — the model refusals, what names a model, the setting's label; `team` — a team's size, names, marks, refusals and texts; `progress` — the phases' texts; `context` — result ids, stubs, the output cap, the compaction table, the environment lines, the session notes; `scheduled` (3.5.0) — scheduled work on a linked computer: the setting, the modes, the approval wait, the sentinel, the watch command's limits and the refusals |
|
| `loop.json` | How one prompt runs: the step backstop, budgets, wrap-up, nudges, parallel calls, capacity; `jobs` — background work, its marks and limits; `subagents` — the model refusals, what names a model, the setting's label; `team` — a team's size, names, marks, refusals and texts; `progress` — the phases' texts; `context` — result ids, stubs, the output cap, the compaction table, the environment lines, the session notes; `scheduled` (3.5.0) — scheduled work on a linked computer: the setting, the modes, the approval wait, the sentinel, the watch command's limits and the refusals; `phone` (3.6.0) — phone actions: the capability, each action's fields, what is required and checked, the wait and the texts |
|
||||||
| `filesystem.md` | The file system a session works in: paths, node kinds, the four access levels, errors, listings, search and indexing by tokens, stacked instruction files, skills, memory |
|
| `filesystem.md` | The file system a session works in: paths, node kinds, the four access levels, errors, listings, search and indexing by tokens, stacked instruction files, skills, memory |
|
||||||
| `context.md` | Context hygiene: what happens to a tool result when it is inserted, and never afterwards; the prefix that stays still; reasoning sent back during its turn; no absolute paths in the prompt |
|
| `context.md` | Context hygiene: what happens to a tool result when it is inserted, and never afterwards; the prefix that stays still; reasoning sent back during its turn; no absolute paths in the prompt |
|
||||||
| `team.md` | Teams: a lead and the members it adds, the size setting, the room, who receives what, reports, a manager's refusals, capacity, stopping |
|
| `team.md` | Teams: a lead and the members it adds, the size setting, the room, who receives what, reports, a manager's refusals, capacity, stopping |
|
||||||
| `subagents.md` | Subagents: the word, which model one runs on (the session's, or one the person named), the capacity refusals, and when the Subagents setting is offered; from 3.4.0 (§5–§13) the main agent and its tree — only it adds, permissions inherited and never widened, request options, a subagent's life and its findings, what clients show |
|
| `subagents.md` | Subagents: the word, which model one runs on (the session's, or one the person named), the capacity refusals, and when the Subagents setting is offered; from 3.4.0 (§5–§13) the main agent and its tree — only it adds, permissions inherited and never widened, request options, a subagent's life and its findings, what clients show |
|
||||||
|
| `phone.md` | Phone actions (3.6.0): `phone_action`, carried out by the LLeMbas app on the person's phone and confirmed there — who offers it, how a call is checked, the answer, navigation |
|
||||||
| `progress.md` | Progress (3.4.0): the thirteen phases every client shows the same way, the data each carries, their texts, and where they are sent |
|
| `progress.md` | Progress (3.4.0): the thirteen phases every client shows the same way, the data each carries, their texts, and where they are sent |
|
||||||
| `quirks.json` | What real servers do, and how both harnesses cope |
|
| `quirks.json` | What real servers do, and how both harnesses cope |
|
||||||
| `conformance/<area>.json` | Cases with their expected results, run by both test suites |
|
| `conformance/<area>.json` | Cases with their expected results, run by both test suites |
|
||||||
@@ -52,7 +53,8 @@ page *Harness-parity* in LLeMbas CLI's wiki.
|
|||||||
`forge`: the session's view holds a repository mounted from a forge; `plan`: the session is in
|
`forge`: the session's view holds a repository mounted from a forge; `plan`: the session is in
|
||||||
plan mode; `team` (3.2.0): the session's team setting allows more than one agent, and a model could
|
plan mode; `team` (3.2.0): the session's team setting allows more than one agent, and a model could
|
||||||
serve as a subagent (3.3.0: `subagents.md` §4; before, the chat's model serving two requests at
|
serve as a subagent (3.3.0: `subagents.md` §4; before, the chat's model serving two requests at
|
||||||
once). A requirement that goes away between the offer and the call makes the call answer the
|
once); `phone` (3.6.0): a message in the chat came from an app on the person's phone that
|
||||||
|
carries out phone actions (`phone.md` §1). A requirement that goes away between the offer and the call makes the call answer the
|
||||||
tool's `unavailable` text, word for word, as its result. **From 3.1.0 a requirement that can come
|
tool's `unavailable` text, word for word, as its result. **From 3.1.0 a requirement that can come
|
||||||
and go during a session does not take the tool out of the list** (`context.md` §10, rule 4): the
|
and go during a session does not take the tool out of the list** (`context.md` §10, rule 4): the
|
||||||
tool is offered from the start and answers `unavailable` while the requirement is not met.
|
tool is offered from the start and answers `unavailable` while the requirement is not met.
|
||||||
@@ -71,7 +73,19 @@ page *Harness-parity* in LLeMbas CLI's wiki.
|
|||||||
approval in plan mode and answers its `unavailable` text outside it. Their permission key is
|
approval in plan mode and answers its `unavailable` text outside it. Their permission key is
|
||||||
`plan` (`permission/defaults.json`), which a rule written for the former `todo` key still reaches.
|
`plan` (`permission/defaults.json`), which a rule written for the former `todo` key still reaches.
|
||||||
|
|
||||||
## 3.5.0
|
## 3.6.0
|
||||||
|
|
||||||
|
- **Phone actions** (`phone.md`, `tools/phone_action.json`, `loop.json` `phone`): a chat's model
|
||||||
|
may do something on the person's phone when they write from the LLeMbas app on it — an alarm or a
|
||||||
|
timer, a calendar event, an SMS or email draft, the dialer, a route (`navigate`, `add_stop`,
|
||||||
|
`search_nearby`, `stop_navigation`), an app or an address, media, the torch, a settings panel,
|
||||||
|
an automation. The app declares the capability `phone_actions` with each message; the person
|
||||||
|
confirms every call on the phone (`confirm: always`); the result is `done`, `declined` or
|
||||||
|
`failed` with what the phone said. The server's alone (`scope: llembas`, `sessions: chat`); new
|
||||||
|
requirement `phone`, offered once a message came from the phone and kept, answering
|
||||||
|
`unavailable` when the message being answered did not. Conformance area **`phone`** (the checks
|
||||||
|
and the result texts).
|
||||||
|
|
||||||
|
|
||||||
Written ahead of both implementations; the server vendors it as written, both carry it in the same
|
Written ahead of both implementations; the server vendors it as written, both carry it in the same
|
||||||
round (`acp.md` *Scheduled work on a linked computer*, `loop.json` `scheduled`).
|
round (`acp.md` *Scheduled work on a linked computer*, `loop.json` `scheduled`).
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
3.5.0
|
3.6.0
|
||||||
@@ -0,0 +1,595 @@
|
|||||||
|
{
|
||||||
|
"about": "Spec 3.6.0 (phone.md §2-§3): `check` — a phone_action call's arguments, and either the action the phone is sent (`action`: its own fields only, strings trimmed, `summary` the action's name when empty) or the call's refusal (`refused`, loop.json phone.texts); `result` — the phone's answer (`status`, `message`; `status` null is no answer in time) as the text the model is told. LLeMbas only: the CLI has no runner for this area.",
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"about": "A known action, its fields only: a stray field is dropped and strings are trimmed.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "alarm",
|
||||||
|
"summary": " Alarm at 7:00 on weekdays ",
|
||||||
|
"time": "07:00",
|
||||||
|
"days": [
|
||||||
|
"mon",
|
||||||
|
"tue",
|
||||||
|
"wed",
|
||||||
|
"thu",
|
||||||
|
"fri"
|
||||||
|
],
|
||||||
|
"label": " Work ",
|
||||||
|
"seconds": 60
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"action": {
|
||||||
|
"action": "alarm",
|
||||||
|
"summary": "Alarm at 7:00 on weekdays",
|
||||||
|
"time": "07:00",
|
||||||
|
"days": [
|
||||||
|
"mon",
|
||||||
|
"tue",
|
||||||
|
"wed",
|
||||||
|
"thu",
|
||||||
|
"fri"
|
||||||
|
],
|
||||||
|
"label": "Work"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "No summary: the action's name stands in.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "torch",
|
||||||
|
"on": false
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"action": {
|
||||||
|
"action": "torch",
|
||||||
|
"summary": "torch",
|
||||||
|
"on": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "An action that is not one.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "call",
|
||||||
|
"summary": "Call mum",
|
||||||
|
"number": "+421 900 123 456"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "\"call\" is not an action. Use one of: alarm, timer, calendar_event, sms_draft, email_draft, dial, navigate, add_stop, search_nearby, stop_navigation, open_app, open_url, media, torch, settings_panel, automation_intent. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A required field missing.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "timer",
|
||||||
|
"summary": "Tea"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "timer needs seconds. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "Both of add_stop's missing: all of them, in order, in one sentence.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "add_stop",
|
||||||
|
"summary": "Add a stop"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "add_stop needs stop and destination. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A group: an SMS draft needs a recipient or a text.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "sms_draft",
|
||||||
|
"summary": "Text"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "sms_draft needs to or body. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A group met by one of its fields.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "sms_draft",
|
||||||
|
"summary": "Text Jana",
|
||||||
|
"to": [
|
||||||
|
"Jana"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"action": {
|
||||||
|
"action": "sms_draft",
|
||||||
|
"summary": "Text Jana",
|
||||||
|
"to": [
|
||||||
|
"Jana"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "An empty string is missing, whitespace included.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "open_app",
|
||||||
|
"summary": "Open",
|
||||||
|
"app": " "
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "open_app needs app. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A time out of range.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "alarm",
|
||||||
|
"summary": "Alarm",
|
||||||
|
"time": "24:00"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "time is not a time as HH:MM (24-hour). Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A timer of zero seconds.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "timer",
|
||||||
|
"summary": "Timer",
|
||||||
|
"seconds": 0
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "seconds is not a whole number of seconds from 1 to 86400. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A boolean is not a number of seconds.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "timer",
|
||||||
|
"summary": "Timer",
|
||||||
|
"seconds": true
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "seconds is not a whole number of seconds from 1 to 86400. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A calendar event with a date for a whole day.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "calendar_event",
|
||||||
|
"summary": "Holiday",
|
||||||
|
"title": "Holiday",
|
||||||
|
"start": "2026-12-24",
|
||||||
|
"all_day": true
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"action": {
|
||||||
|
"action": "calendar_event",
|
||||||
|
"summary": "Holiday",
|
||||||
|
"title": "Holiday",
|
||||||
|
"start": "2026-12-24",
|
||||||
|
"all_day": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A start that is not a date.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "calendar_event",
|
||||||
|
"summary": "Meet",
|
||||||
|
"title": "Meet",
|
||||||
|
"start": "tomorrow at 3"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "start is not a date and time as YYYY-MM-DDTHH:MM, or a date as YYYY-MM-DD for a whole day. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A number with too few digits.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "dial",
|
||||||
|
"summary": "Dial",
|
||||||
|
"number": "12"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "number is not a phone number. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A number with letters in it.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "dial",
|
||||||
|
"summary": "Dial",
|
||||||
|
"number": "0900 ABC"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "number is not a phone number. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A web address that is not http or https.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "open_url",
|
||||||
|
"summary": "Open",
|
||||||
|
"url": "javascript:alert(1)"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "url is not an http or https address. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A web address in capitals is still one.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "open_url",
|
||||||
|
"summary": "Open",
|
||||||
|
"url": "HTTPS://example.org/"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"action": {
|
||||||
|
"action": "open_url",
|
||||||
|
"summary": "Open",
|
||||||
|
"url": "HTTPS://example.org/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A route: a destination, stops in order, a mode.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "navigate",
|
||||||
|
"summary": "Drive home via the shop",
|
||||||
|
"destination": {
|
||||||
|
"name": "Home",
|
||||||
|
"query": "Hlavná 1, Trenčín"
|
||||||
|
},
|
||||||
|
"stops": [
|
||||||
|
{
|
||||||
|
"name": "Shop",
|
||||||
|
"lat": 48.89,
|
||||||
|
"lon": 18.04
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mode": "drive"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"action": {
|
||||||
|
"action": "navigate",
|
||||||
|
"summary": "Drive home via the shop",
|
||||||
|
"destination": {
|
||||||
|
"name": "Home",
|
||||||
|
"query": "Hlavná 1, Trenčín"
|
||||||
|
},
|
||||||
|
"stops": [
|
||||||
|
{
|
||||||
|
"name": "Shop",
|
||||||
|
"lat": 48.89,
|
||||||
|
"lon": 18.04
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mode": "drive"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A destination with coordinates out of range and nothing else is not a place.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "navigate",
|
||||||
|
"summary": "Go",
|
||||||
|
"destination": {
|
||||||
|
"lat": 95,
|
||||||
|
"lon": 18
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "destination is not a place: lat and lon together, or a query, or a name. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A stop that is not a place, inside the list.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "navigate",
|
||||||
|
"summary": "Go",
|
||||||
|
"destination": {
|
||||||
|
"name": "Home",
|
||||||
|
"query": "Hlavná 1, Trenčín"
|
||||||
|
},
|
||||||
|
"stops": [
|
||||||
|
{
|
||||||
|
"name": "Shop"
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "stops is not a list of places, each lat and lon together, or a query, or a name. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "add_stop with its destination, as the person told it.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "add_stop",
|
||||||
|
"summary": "Add the petrol station",
|
||||||
|
"stop": {
|
||||||
|
"query": "Slovnaft Trenčín"
|
||||||
|
},
|
||||||
|
"destination": {
|
||||||
|
"name": "Home",
|
||||||
|
"query": "Hlavná 1, Trenčín"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"action": {
|
||||||
|
"action": "add_stop",
|
||||||
|
"summary": "Add the petrol station",
|
||||||
|
"stop": {
|
||||||
|
"query": "Slovnaft Trenčín"
|
||||||
|
},
|
||||||
|
"destination": {
|
||||||
|
"name": "Home",
|
||||||
|
"query": "Hlavná 1, Trenčín"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A mode the schema does not have.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "navigate",
|
||||||
|
"summary": "Go",
|
||||||
|
"destination": {
|
||||||
|
"name": "Home",
|
||||||
|
"query": "Hlavná 1, Trenčín"
|
||||||
|
},
|
||||||
|
"mode": "fly"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "mode is not one of drive, walk, bike, transit. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "search_nearby along the route.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "search_nearby",
|
||||||
|
"summary": "Petrol on the way",
|
||||||
|
"query": "petrol station",
|
||||||
|
"near": "route"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"action": {
|
||||||
|
"action": "search_nearby",
|
||||||
|
"summary": "Petrol on the way",
|
||||||
|
"query": "petrol station",
|
||||||
|
"near": "route"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "stop_navigation needs nothing.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "stop_navigation",
|
||||||
|
"summary": "Stop the route"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"action": {
|
||||||
|
"action": "stop_navigation",
|
||||||
|
"summary": "Stop the route"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "The checks run in the spec's order: time is reported before days.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "alarm",
|
||||||
|
"summary": "Alarm",
|
||||||
|
"time": "7",
|
||||||
|
"days": [
|
||||||
|
"monday"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "time is not a time as HH:MM (24-hour). Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A field another action reads is not checked here.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "media",
|
||||||
|
"summary": "Pause",
|
||||||
|
"command": "pause",
|
||||||
|
"url": "nope"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"action": {
|
||||||
|
"action": "media",
|
||||||
|
"summary": "Pause",
|
||||||
|
"command": "pause"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "An automation with its values.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "automation_intent",
|
||||||
|
"summary": "Run Arrive home",
|
||||||
|
"task": "Arrive home",
|
||||||
|
"values": {
|
||||||
|
"lights": "on"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"action": {
|
||||||
|
"action": "automation_intent",
|
||||||
|
"summary": "Run Arrive home",
|
||||||
|
"task": "Arrive home",
|
||||||
|
"values": {
|
||||||
|
"lights": "on"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "Values must be text.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "automation_intent",
|
||||||
|
"summary": "Run",
|
||||||
|
"task": "x",
|
||||||
|
"values": {
|
||||||
|
"n": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "values is not named text values. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "An email draft with a list of recipients.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "email_draft",
|
||||||
|
"summary": "Email",
|
||||||
|
"to": [
|
||||||
|
"a@example.org"
|
||||||
|
],
|
||||||
|
"subject": "Hi"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"action": {
|
||||||
|
"action": "email_draft",
|
||||||
|
"summary": "Email",
|
||||||
|
"to": [
|
||||||
|
"a@example.org"
|
||||||
|
],
|
||||||
|
"subject": "Hi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A recipient list holding an empty name.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "email_draft",
|
||||||
|
"summary": "Email",
|
||||||
|
"to": [
|
||||||
|
"a@example.org",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"subject": "Hi"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "to is not a list of recipients. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "torch: on must be a boolean.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "torch",
|
||||||
|
"summary": "Torch",
|
||||||
|
"on": "yes"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "on is not true or false. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A panel the schema does not have.",
|
||||||
|
"kind": "check",
|
||||||
|
"args": {
|
||||||
|
"action": "settings_panel",
|
||||||
|
"summary": "Open",
|
||||||
|
"panel": "battery"
|
||||||
|
},
|
||||||
|
"expect": {
|
||||||
|
"refused": "panel is not one of wifi, bluetooth, internet, volume, nfc, location, do_not_disturb. Nothing was sent to the phone."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "Done, with nothing said.",
|
||||||
|
"kind": "result",
|
||||||
|
"status": "done",
|
||||||
|
"message": "",
|
||||||
|
"expect": "Done on the phone."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "Done, with what the phone said.",
|
||||||
|
"kind": "result",
|
||||||
|
"status": "done",
|
||||||
|
"message": "Alarm set for 07:00, weekdays.",
|
||||||
|
"expect": "Done on the phone.\n\nThe phone says: Alarm set for 07:00, weekdays."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "Declined.",
|
||||||
|
"kind": "result",
|
||||||
|
"status": "declined",
|
||||||
|
"message": "",
|
||||||
|
"expect": "The person declined it on the phone: nothing was done. Do not try again unless they ask."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "Declined, with the person's words.",
|
||||||
|
"kind": "result",
|
||||||
|
"status": "declined",
|
||||||
|
"message": "Not now.",
|
||||||
|
"expect": "The person declined it on the phone: nothing was done. Do not try again unless they ask.\n\nThe phone says: Not now."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "Failed, with why.",
|
||||||
|
"kind": "result",
|
||||||
|
"status": "failed",
|
||||||
|
"message": "OsmAnd is not installed.",
|
||||||
|
"expect": "The phone could not do it: nothing was done.\n\nThe phone says: OsmAnd is not installed."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A status the phone does not know is failed.",
|
||||||
|
"kind": "result",
|
||||||
|
"status": "maybe",
|
||||||
|
"message": "?",
|
||||||
|
"expect": "The phone could not do it: nothing was done.\n\nThe phone says: ?"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A message is trimmed.",
|
||||||
|
"kind": "result",
|
||||||
|
"status": "done",
|
||||||
|
"message": " Torch on. \n",
|
||||||
|
"expect": "Done on the phone.\n\nThe phone says: Torch on."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "No answer in time.",
|
||||||
|
"kind": "result",
|
||||||
|
"status": null,
|
||||||
|
"message": "",
|
||||||
|
"expect": "The phone did not answer in time: nothing was done."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"about": "A message is cut at message_max_chars.",
|
||||||
|
"kind": "result",
|
||||||
|
"status": "failed",
|
||||||
|
"message": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
"expect": "The phone could not do it: nothing was done.\n\nThe phone says: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -483,5 +483,208 @@
|
|||||||
"monitor_denied": "The watch command is refused here: {{reason}}.",
|
"monitor_denied": "The watch command is refused here: {{reason}}.",
|
||||||
"no_reply": "The run ended without a reply."
|
"no_reply": "The run ended without a reply."
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"phone": {
|
||||||
|
"about": "Spec 3.6.0 (phone.md): `phone_action`, carried out by the LLeMbas app on the person's phone and confirmed there. `capability` is what the app declares with each message it sends; `fields` what each action reads (anything else in a call is dropped before the phone sees it); `required` the fields each action needs (a list inside the list: any one of them); `check` the order the fields' values are checked in; `wait_seconds` how long a call waits for the phone's answer; `texts` what the model is told, `what` what a bad field should have been. conformance/phone.json.",
|
||||||
|
"capability": "phone_actions",
|
||||||
|
"actions": [
|
||||||
|
"alarm",
|
||||||
|
"timer",
|
||||||
|
"calendar_event",
|
||||||
|
"sms_draft",
|
||||||
|
"email_draft",
|
||||||
|
"dial",
|
||||||
|
"navigate",
|
||||||
|
"add_stop",
|
||||||
|
"search_nearby",
|
||||||
|
"stop_navigation",
|
||||||
|
"open_app",
|
||||||
|
"open_url",
|
||||||
|
"media",
|
||||||
|
"torch",
|
||||||
|
"settings_panel",
|
||||||
|
"automation_intent"
|
||||||
|
],
|
||||||
|
"fields": {
|
||||||
|
"alarm": [
|
||||||
|
"time",
|
||||||
|
"days",
|
||||||
|
"label"
|
||||||
|
],
|
||||||
|
"timer": [
|
||||||
|
"seconds",
|
||||||
|
"label"
|
||||||
|
],
|
||||||
|
"calendar_event": [
|
||||||
|
"title",
|
||||||
|
"start",
|
||||||
|
"end",
|
||||||
|
"all_day",
|
||||||
|
"location",
|
||||||
|
"notes"
|
||||||
|
],
|
||||||
|
"sms_draft": [
|
||||||
|
"to",
|
||||||
|
"body"
|
||||||
|
],
|
||||||
|
"email_draft": [
|
||||||
|
"to",
|
||||||
|
"cc",
|
||||||
|
"subject",
|
||||||
|
"body"
|
||||||
|
],
|
||||||
|
"dial": [
|
||||||
|
"number"
|
||||||
|
],
|
||||||
|
"navigate": [
|
||||||
|
"destination",
|
||||||
|
"stops",
|
||||||
|
"mode",
|
||||||
|
"replace_route"
|
||||||
|
],
|
||||||
|
"add_stop": [
|
||||||
|
"stop",
|
||||||
|
"destination"
|
||||||
|
],
|
||||||
|
"search_nearby": [
|
||||||
|
"query",
|
||||||
|
"near"
|
||||||
|
],
|
||||||
|
"stop_navigation": [],
|
||||||
|
"open_app": [
|
||||||
|
"app"
|
||||||
|
],
|
||||||
|
"open_url": [
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"media": [
|
||||||
|
"command"
|
||||||
|
],
|
||||||
|
"torch": [
|
||||||
|
"on"
|
||||||
|
],
|
||||||
|
"settings_panel": [
|
||||||
|
"panel"
|
||||||
|
],
|
||||||
|
"automation_intent": [
|
||||||
|
"task",
|
||||||
|
"app",
|
||||||
|
"values"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"required": {
|
||||||
|
"alarm": [
|
||||||
|
"time"
|
||||||
|
],
|
||||||
|
"timer": [
|
||||||
|
"seconds"
|
||||||
|
],
|
||||||
|
"calendar_event": [
|
||||||
|
"title",
|
||||||
|
"start"
|
||||||
|
],
|
||||||
|
"sms_draft": [
|
||||||
|
[
|
||||||
|
"to",
|
||||||
|
"body"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"email_draft": [
|
||||||
|
[
|
||||||
|
"to",
|
||||||
|
"subject",
|
||||||
|
"body"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"dial": [
|
||||||
|
"number"
|
||||||
|
],
|
||||||
|
"navigate": [
|
||||||
|
"destination"
|
||||||
|
],
|
||||||
|
"add_stop": [
|
||||||
|
"stop",
|
||||||
|
"destination"
|
||||||
|
],
|
||||||
|
"search_nearby": [
|
||||||
|
"query"
|
||||||
|
],
|
||||||
|
"stop_navigation": [],
|
||||||
|
"open_app": [
|
||||||
|
"app"
|
||||||
|
],
|
||||||
|
"open_url": [
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"media": [
|
||||||
|
"command"
|
||||||
|
],
|
||||||
|
"torch": [
|
||||||
|
"on"
|
||||||
|
],
|
||||||
|
"settings_panel": [
|
||||||
|
"panel"
|
||||||
|
],
|
||||||
|
"automation_intent": [
|
||||||
|
"task"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"statuses": [
|
||||||
|
"done",
|
||||||
|
"declined",
|
||||||
|
"failed"
|
||||||
|
],
|
||||||
|
"wait_seconds": 300,
|
||||||
|
"message_max_chars": 1000,
|
||||||
|
"texts": {
|
||||||
|
"done": "Done on the phone.",
|
||||||
|
"declined": "The person declined it on the phone: nothing was done. Do not try again unless they ask.",
|
||||||
|
"failed": "The phone could not do it: nothing was done.",
|
||||||
|
"no_answer": "The phone did not answer in time: nothing was done.",
|
||||||
|
"says": "The phone says: {{message}}",
|
||||||
|
"unknown_action": "\"{{action}}\" is not an action. Use one of: {{actions}}. Nothing was sent to the phone.",
|
||||||
|
"missing": "{{action}} needs {{fields}}. Nothing was sent to the phone.",
|
||||||
|
"bad": "{{field}} is not {{what}}. Nothing was sent to the phone."
|
||||||
|
},
|
||||||
|
"what": {
|
||||||
|
"time": "a time as HH:MM (24-hour)",
|
||||||
|
"days": "a list of days from mon, tue, wed, thu, fri, sat, sun",
|
||||||
|
"seconds": "a whole number of seconds from 1 to 86400",
|
||||||
|
"start": "a date and time as YYYY-MM-DDTHH:MM, or a date as YYYY-MM-DD for a whole day",
|
||||||
|
"end": "a date and time as YYYY-MM-DDTHH:MM, or a date as YYYY-MM-DD for a whole day",
|
||||||
|
"to": "a list of recipients",
|
||||||
|
"cc": "a list of recipients",
|
||||||
|
"number": "a phone number",
|
||||||
|
"destination": "a place: lat and lon together, or a query, or a name",
|
||||||
|
"stop": "a place: lat and lon together, or a query, or a name",
|
||||||
|
"stops": "a list of places, each lat and lon together, or a query, or a name",
|
||||||
|
"mode": "one of drive, walk, bike, transit",
|
||||||
|
"near": "one of me, route",
|
||||||
|
"url": "an http or https address",
|
||||||
|
"command": "one of play, pause, toggle, next, previous",
|
||||||
|
"on": "true or false",
|
||||||
|
"panel": "one of wifi, bluetooth, internet, volume, nfc, location, do_not_disturb",
|
||||||
|
"values": "named text values"
|
||||||
|
},
|
||||||
|
"check": [
|
||||||
|
"time",
|
||||||
|
"days",
|
||||||
|
"seconds",
|
||||||
|
"start",
|
||||||
|
"end",
|
||||||
|
"to",
|
||||||
|
"cc",
|
||||||
|
"number",
|
||||||
|
"destination",
|
||||||
|
"stop",
|
||||||
|
"stops",
|
||||||
|
"mode",
|
||||||
|
"near",
|
||||||
|
"url",
|
||||||
|
"command",
|
||||||
|
"on",
|
||||||
|
"panel",
|
||||||
|
"values"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# Phone actions
|
||||||
|
|
||||||
|
Spec 3.6.0. `phone_action` (`tools/phone_action.json`) lets a chat's model do something on the
|
||||||
|
person's phone — set an alarm, start a route, draft a message — when the person is writing from the
|
||||||
|
LLeMbas app on that phone. The model proposes; **the app shows the person a card and does nothing
|
||||||
|
until they confirm it there**; the app carries it out with the phone's own apps and answers what
|
||||||
|
happened, which is the call's result. The texts and numbers are `loop.json` `phone`.
|
||||||
|
|
||||||
|
It is the server's tool alone (`scope: llembas`, `sessions: chat`). The CLI never offers it: a
|
||||||
|
terminal has no phone behind it.
|
||||||
|
|
||||||
|
## 1. Who offers it
|
||||||
|
|
||||||
|
- **The capability.** The app declares `phone_actions` (`loop.json` `phone.capability`) with each
|
||||||
|
message it sends. A message sent from anywhere else — the web, Desktop, another app — declares
|
||||||
|
nothing.
|
||||||
|
- **`requires: phone`.** The tool is offered in a chat once a message has been sent to it with the
|
||||||
|
capability, and stays offered for the rest of the chat (the tool list is the session's,
|
||||||
|
`context.md` §10): a chat started on the phone and continued on the web keeps the tool, and a web
|
||||||
|
chat continued on the phone gains it from that message on (the list changes once, so the prompt
|
||||||
|
is made again once).
|
||||||
|
- **Unavailable.** A call answers the tool's `unavailable` text, word for word, when the message
|
||||||
|
being answered was not sent with the capability (there is no phone to confirm on), and in a chat
|
||||||
|
nobody attends (a schedule's, a subagent's). Nothing reaches any phone then.
|
||||||
|
- **Confirm: always.** Every call is put to the person, in every mode; nothing remembers an answer.
|
||||||
|
The card is the phone's own: the server never asks a second time on another screen. Its risk is
|
||||||
|
`interact`: no mode refuses it, Plan mode included, because nothing on disk changes and the
|
||||||
|
person decides on the phone.
|
||||||
|
|
||||||
|
## 2. A call
|
||||||
|
|
||||||
|
`action` is one of `phone.actions`; `summary` is the line the phone shows. Each action reads only
|
||||||
|
its `phone.fields`; anything else in the call is dropped before the phone sees it. The call is
|
||||||
|
checked before anything is sent, in this order, and the first problem is the call's result:
|
||||||
|
|
||||||
|
1. `action` not one of the actions: `texts.unknown_action` (`{{actions}}` the list, joined with
|
||||||
|
`, `).
|
||||||
|
2. The action's `phone.required`, each entry in turn: a field is present when it is not null, not
|
||||||
|
an empty string (after trimming) and not an empty list or object; a list inside the list is
|
||||||
|
present when any one of its fields is. Every entry that is missing, in order, goes in one
|
||||||
|
`texts.missing`: a field by its name, a group as its names joined with ` or `, the entries
|
||||||
|
joined with ` and ` (`add_stop needs stop and destination.`).
|
||||||
|
3. The fields present, in `phone.check` order (only those the action reads): one that is not what
|
||||||
|
it should be is `texts.bad` with `{{field}}` its name and `{{what}}` from `phone.what`:
|
||||||
|
- `time`: `HH:MM`, hours 00–23, minutes 00–59.
|
||||||
|
- `days`: a list, each one of `mon` … `sun`.
|
||||||
|
- `seconds`: an integer (never a boolean) from 1 to 86400.
|
||||||
|
- `start`, `end`: `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM` (not checked as a calendar date).
|
||||||
|
- `to`, `cc`: a list of strings, each non-empty after trimming.
|
||||||
|
- `number`: at least three digits, and nothing but digits, spaces and `+ ( ) - . / # *`.
|
||||||
|
- `destination`, `stop`: an object with numeric `lat` and `lon` together (each in its range),
|
||||||
|
or a non-empty `query`, or a non-empty `name`. `stops`: a list of such places.
|
||||||
|
- `mode`, `near`, `command`, `panel`: one of the schema's values.
|
||||||
|
- `url`: begins `http://` or `https://` (any case) and has something after it.
|
||||||
|
- `on`: a boolean.
|
||||||
|
- `values`: an object of strings.
|
||||||
|
|
||||||
|
A call that passes is the **action** the phone receives: `action`, `summary` (trimmed; the action's
|
||||||
|
name when empty) and the action's fields that are present, as the model gave them, strings trimmed.
|
||||||
|
|
||||||
|
## 3. The answer
|
||||||
|
|
||||||
|
The server waits up to `phone.wait_seconds` for the phone's answer: `status` (`done`, `declined`
|
||||||
|
or `failed`) and an optional `message`, at most `phone.message_max_chars` characters (cut there).
|
||||||
|
The call's result is the status's text — `texts.done`, `texts.declined`, `texts.failed` — and,
|
||||||
|
when there is a message, a blank line and `texts.says` with it. A status the phone does not know is
|
||||||
|
`failed`. No answer in time is `texts.no_answer`, with no message; the card goes away.
|
||||||
|
|
||||||
|
What the person pressed is theirs: **declined** means they said no, and the model is told not to
|
||||||
|
try again unless asked. **Failed** is the phone's: an app missing, a permission refused, a map app
|
||||||
|
that cannot do what was asked.
|
||||||
|
|
||||||
|
## 4. Navigation
|
||||||
|
|
||||||
|
Map apps differ, and the phone says when one cannot: the model is not told which app the person
|
||||||
|
uses.
|
||||||
|
|
||||||
|
- `navigate` starts a route to `destination` through `stops`, in order; `mode` is how the person
|
||||||
|
travels (`drive` when not said). `replace_route` replaces a route already running without the
|
||||||
|
phone asking first.
|
||||||
|
- `add_stop`: **no map app adds a stop to a running route**, so the phone plans again — from where
|
||||||
|
the person is, to `stop`, then to `destination`. The model gives `destination` as the person told
|
||||||
|
it when the route began: the tool's description tells it to keep it.
|
||||||
|
- `search_nearby` looks for `query` around the person (`near: me`) or along the running route
|
||||||
|
(`near: route`).
|
||||||
|
- `stop_navigation` ends the route where the map app allows it; one that does not answers
|
||||||
|
`failed` with why.
|
||||||
|
|
||||||
|
## 5. The other actions
|
||||||
|
|
||||||
|
- `alarm` and `timer` are the phone's clock app; `days` repeats an alarm.
|
||||||
|
- `calendar_event` opens the calendar's new-event screen filled in; the person saves it.
|
||||||
|
- `sms_draft` and `email_draft` are **drafts**: the messaging or mail app opens with them, and the
|
||||||
|
person sends. `dial` opens the dialer with the number; the person places the call.
|
||||||
|
- `open_app` names an app as the person does; `open_url` opens an address in the browser.
|
||||||
|
- `media` controls what is playing; `torch` switches the torch.
|
||||||
|
- `settings_panel` opens the phone's quick panel or page for a setting; the person changes it.
|
||||||
|
- `automation_intent` runs an automation the person made in an automation app (Tasker when `app`
|
||||||
|
is not said), by its name, with `values` as its inputs.
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
{
|
||||||
|
"name": "phone_action",
|
||||||
|
"scope": "llembas",
|
||||||
|
"sessions": "chat",
|
||||||
|
"risk": "interact",
|
||||||
|
"exclusive": true,
|
||||||
|
"block": "tool",
|
||||||
|
"purpose": false,
|
||||||
|
"requires": [
|
||||||
|
"phone"
|
||||||
|
],
|
||||||
|
"unavailable": "This needs the LLeMbas app on the person's phone, and this message did not come from it: nothing was done on any phone. Say what you would have done, and that they can ask again from the app.",
|
||||||
|
"confirm": "always",
|
||||||
|
"aliases": {
|
||||||
|
"cli": [],
|
||||||
|
"llembas": []
|
||||||
|
},
|
||||||
|
"description": "Do something on the person's phone: they are writing to you from the LLeMbas app on it, and the app carries it out with the phone's own apps. The person confirms every call on the phone before anything happens; the result says done, declined or failed, with what the phone said.\n\nUse it only for what the person asked for, one action per call. `summary` is the one line the phone shows them: say what will happen, in their language.\n\n- alarm: `time` (HH:MM, 24-hour); `days` to repeat it; `label`.\n- timer: `seconds`; `label`.\n- calendar_event: `title`, `start` (YYYY-MM-DDTHH:MM, the phone's local time; YYYY-MM-DD with all_day); `end`, `all_day`, `location`, `notes`.\n- sms_draft: `to` (numbers, or names as the person said them), `body`. A draft: the person sends it.\n- email_draft: `to`, `cc`, `subject`, `body`. A draft: the person sends it.\n- dial: `number`. Opens the dialer with it; the person places the call.\n- navigate: `destination`; `stops` on the way, in order; `mode` (drive, walk, bike, transit); `replace_route` to start over rather than ask.\n- add_stop: `stop`, and `destination` — the route's destination as the person told you. Keep the destination of a route you started: no map app can add to a running route, so the phone plans again to the stop and then the destination.\n- search_nearby: `query` (\"petrol station\"), `near` me or the route.\n- stop_navigation: ends the route. Not every map app can; the phone says so if not.\n- open_app: `app`, as the person names it.\n- open_url: `url` (http or https).\n- media: `command` (play, pause, toggle, next, previous) for what is playing.\n- torch: `on`.\n- settings_panel: `panel` — the phone's quick panel or page for it; the person flips the switch.\n- automation_intent: `task`, the name of an automation the person made (Tasker by default, or `app`); `values` it is given.\n\nA place is lat and lon when you know them, else a `query` the map app searches; `name` is what the person calls it.\n\nDeclined means they said no on the phone: do not try again unless they ask. Failed says why; nothing was done.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"action": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"alarm",
|
||||||
|
"timer",
|
||||||
|
"calendar_event",
|
||||||
|
"sms_draft",
|
||||||
|
"email_draft",
|
||||||
|
"dial",
|
||||||
|
"navigate",
|
||||||
|
"add_stop",
|
||||||
|
"search_nearby",
|
||||||
|
"stop_navigation",
|
||||||
|
"open_app",
|
||||||
|
"open_url",
|
||||||
|
"media",
|
||||||
|
"torch",
|
||||||
|
"settings_panel",
|
||||||
|
"automation_intent"
|
||||||
|
],
|
||||||
|
"description": "What to do"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "One line the phone shows the person: what will happen"
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "alarm: HH:MM, 24-hour"
|
||||||
|
},
|
||||||
|
"days": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"mon",
|
||||||
|
"tue",
|
||||||
|
"wed",
|
||||||
|
"thu",
|
||||||
|
"fri",
|
||||||
|
"sat",
|
||||||
|
"sun"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"description": "alarm: the days it repeats on; none for once"
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "alarm, timer: its name on the phone"
|
||||||
|
},
|
||||||
|
"seconds": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 86400,
|
||||||
|
"description": "timer: how long"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "calendar_event: the event's title"
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "calendar_event: YYYY-MM-DDTHH:MM, local; YYYY-MM-DD with all_day"
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "calendar_event: as start"
|
||||||
|
},
|
||||||
|
"all_day": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "calendar_event: a whole day"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "calendar_event: where"
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "calendar_event: its description"
|
||||||
|
},
|
||||||
|
"to": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "sms_draft: numbers or names; email_draft: addresses"
|
||||||
|
},
|
||||||
|
"cc": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "email_draft: addresses"
|
||||||
|
},
|
||||||
|
"subject": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "email_draft"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "sms_draft, email_draft: the text"
|
||||||
|
},
|
||||||
|
"number": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "dial: the phone number"
|
||||||
|
},
|
||||||
|
"destination": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "navigate, add_stop: where the route ends",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "What the person calls it: \"Home\", \"Lidl on Main Street\""
|
||||||
|
},
|
||||||
|
"lat": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": -90,
|
||||||
|
"maximum": 90,
|
||||||
|
"description": "Latitude, when known"
|
||||||
|
},
|
||||||
|
"lon": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": -180,
|
||||||
|
"maximum": 180,
|
||||||
|
"description": "Longitude, when known"
|
||||||
|
},
|
||||||
|
"query": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "What the map app searches when there are no coordinates"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stops": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "A place: lat and lon when you know them, else a query the map app searches (an address, or a name and a town); name is what the person calls it",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "What the person calls it: \"Home\", \"Lidl on Main Street\""
|
||||||
|
},
|
||||||
|
"lat": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": -90,
|
||||||
|
"maximum": 90,
|
||||||
|
"description": "Latitude, when known"
|
||||||
|
},
|
||||||
|
"lon": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": -180,
|
||||||
|
"maximum": 180,
|
||||||
|
"description": "Longitude, when known"
|
||||||
|
},
|
||||||
|
"query": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "What the map app searches when there are no coordinates"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "navigate: places on the way, in order"
|
||||||
|
},
|
||||||
|
"stop": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "add_stop: the place to add",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "What the person calls it: \"Home\", \"Lidl on Main Street\""
|
||||||
|
},
|
||||||
|
"lat": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": -90,
|
||||||
|
"maximum": 90,
|
||||||
|
"description": "Latitude, when known"
|
||||||
|
},
|
||||||
|
"lon": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": -180,
|
||||||
|
"maximum": 180,
|
||||||
|
"description": "Longitude, when known"
|
||||||
|
},
|
||||||
|
"query": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "What the map app searches when there are no coordinates"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"drive",
|
||||||
|
"walk",
|
||||||
|
"bike",
|
||||||
|
"transit"
|
||||||
|
],
|
||||||
|
"description": "navigate: how the person travels (drive when not said)"
|
||||||
|
},
|
||||||
|
"replace_route": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "navigate: replace a route already running without asking"
|
||||||
|
},
|
||||||
|
"query": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "search_nearby: what to look for"
|
||||||
|
},
|
||||||
|
"near": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"me",
|
||||||
|
"route"
|
||||||
|
],
|
||||||
|
"description": "search_nearby: around the person, or along the running route"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "open_app: the app, as the person names it; automation_intent: the automation app (Tasker when not said)"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "open_url: an http or https address"
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"play",
|
||||||
|
"pause",
|
||||||
|
"toggle",
|
||||||
|
"next",
|
||||||
|
"previous"
|
||||||
|
],
|
||||||
|
"description": "media"
|
||||||
|
},
|
||||||
|
"on": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "torch: on or off"
|
||||||
|
},
|
||||||
|
"panel": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"wifi",
|
||||||
|
"bluetooth",
|
||||||
|
"internet",
|
||||||
|
"volume",
|
||||||
|
"nfc",
|
||||||
|
"location",
|
||||||
|
"do_not_disturb"
|
||||||
|
],
|
||||||
|
"description": "settings_panel"
|
||||||
|
},
|
||||||
|
"task": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "automation_intent: the automation's name"
|
||||||
|
},
|
||||||
|
"values": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "automation_intent: named values it is given"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"action",
|
||||||
|
"summary"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-3
@@ -28,6 +28,7 @@ import t_mv from "../harness/tools/mv.json"
|
|||||||
import t_note_manage from "../harness/tools/note_manage.json"
|
import t_note_manage from "../harness/tools/note_manage.json"
|
||||||
import t_note_view from "../harness/tools/note_view.json"
|
import t_note_view from "../harness/tools/note_view.json"
|
||||||
import t_notes_search from "../harness/tools/notes_search.json"
|
import t_notes_search from "../harness/tools/notes_search.json"
|
||||||
|
import t_phone_action from "../harness/tools/phone_action.json"
|
||||||
import t_plan_submit from "../harness/tools/plan_submit.json"
|
import t_plan_submit from "../harness/tools/plan_submit.json"
|
||||||
import t_plan_update from "../harness/tools/plan_update.json"
|
import t_plan_update from "../harness/tools/plan_update.json"
|
||||||
import t_plan_write from "../harness/tools/plan_write.json"
|
import t_plan_write from "../harness/tools/plan_write.json"
|
||||||
@@ -91,8 +92,10 @@ export interface ToolSpecDef {
|
|||||||
* can be opened: over the link, to a client that offers `show`), `page`, `forge` (the chat's);
|
* can be opened: over the link, to a client that offers `show`), `page`, `forge` (the chat's);
|
||||||
* `plan` (3.1.0): plan mode. From 3.1.0 one that can come and go during a session does not
|
* `plan` (3.1.0): plan mode. From 3.1.0 one that can come and go during a session does not
|
||||||
* leave the tool out: it is offered and answers `unavailable` while unmet (context.md §10).
|
* leave the tool out: it is offered and answers `unavailable` while unmet (context.md §10).
|
||||||
* `team` (3.2.0): the team setting allows more than one agent (harness/team.md §2). */
|
* `team` (3.2.0): the team setting allows more than one agent (harness/team.md §2). `phone`
|
||||||
requires?: ("display" | "page" | "forge" | "plan" | "team")[]
|
* (3.6.0): a message came from the LLeMbas app on the person's phone (harness/phone.md) — the
|
||||||
|
* server's alone. */
|
||||||
|
requires?: ("display" | "page" | "forge" | "plan" | "team" | "phone")[]
|
||||||
/** What a call answers, word for word, when a requirement went away after the offer. */
|
/** What a call answers, word for word, when a requirement went away after the offer. */
|
||||||
unavailable?: string
|
unavailable?: string
|
||||||
/** `always`: put to the person in every mode, auto included; never remembered. */
|
/** `always`: put to the person in every mode, auto included; never remembered. */
|
||||||
@@ -313,7 +316,7 @@ export function narrowParameters(parameters: Record<string, unknown>, narrow: Na
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const TOOL_SPECS: Record<string, ToolSpecDef> = Object.fromEntries(
|
export const TOOL_SPECS: Record<string, ToolSpecDef> = Object.fromEntries(
|
||||||
([t_apply_patch, t_ask_user, t_bash, t_branch, t_commit, t_decisions, t_edit, t_glob, t_grep, t_job_list, t_job_output, t_job_send, t_job_stop, t_knowledge_get, t_knowledge_search, t_list, t_memory, t_merge, t_mkdir, t_multiedit, t_mv, t_note_manage, t_note_view, t_notes_search, t_plan_submit, t_plan_update, t_plan_write, t_pr, t_python, t_read, t_recall, t_release, t_report, t_rm, t_search, t_session_search, t_settings, t_show, t_skill_manage, t_skill_view, t_skills_list, t_tag, t_task, t_tasks, t_team_add, t_team_assign, t_team_decide, t_team_findings, t_team_finish, t_team_read, t_team_replace, t_team_request, t_team_send, t_team_stop, t_team_whoami, t_view_image, t_web_fetch, t_web_search, t_write] as ToolSpecDef[]).map((t) => [t.name, t]),
|
([t_apply_patch, t_ask_user, t_bash, t_branch, t_commit, t_decisions, t_edit, t_glob, t_grep, t_job_list, t_job_output, t_job_send, t_job_stop, t_knowledge_get, t_knowledge_search, t_list, t_memory, t_merge, t_mkdir, t_multiedit, t_mv, t_note_manage, t_note_view, t_notes_search, t_phone_action, t_plan_submit, t_plan_update, t_plan_write, t_pr, t_python, t_read, t_recall, t_release, t_report, t_rm, t_search, t_session_search, t_settings, t_show, t_skill_manage, t_skill_view, t_skills_list, t_tag, t_task, t_tasks, t_team_add, t_team_assign, t_team_decide, t_team_findings, t_team_finish, t_team_read, t_team_replace, t_team_request, t_team_send, t_team_stop, t_team_whoami, t_view_image, t_web_fetch, t_web_search, t_write] as ToolSpecDef[]).map((t) => [t.name, t]),
|
||||||
)
|
)
|
||||||
|
|
||||||
/** Whether the spec gives a tool to this CLI: not the server's alone (`scope: llembas`), and
|
/** Whether the spec gives a tool to this CLI: not the server's alone (`scope: llembas`), and
|
||||||
|
|||||||
Reference in new issue
Block a user