Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 49 additions & 23 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,25 +491,50 @@ unless the "Asynchronous
questions" setting is off. Codex asks natively (`delivery: "async"`), and
Boite draws those cards the same way.

`settings` helps a lost user set Boite up. Bare, it lists the settings an agent
may ask about, one row each: key, value as JSON, the page and card it sits on
(`agentCpuCapPercent 75 resources#limits`). The list is `REQUESTABLE_SETTINGS`
in `packages/contracts/src/setting-requests.ts`; credentials, account ids and
browser profiles are not in it. `settings request <key> <value> --why <text>`
draws a settings card the same way `ask` draws a question: the agent keeps
working and the card outlives the turn and a restart. The value is JSON when it
parses (`60`, `false`, `null`, `["https://a.example"]`) and the bare word
otherwise. It is refused when the key is not in the list (the refusal lists
them), when `settings.set` would refuse the value, when the setting already
holds it, or when a card on that key is already open in the thread.

The card shows the reason, the setting's name, the current value struck
through, the proposed one and where it sits in Settings. Apply calls
`settings.set`, the call the Settings page makes, so only the owner can use it.
A change card's button to Settings (Show in Settings beside Apply, Open
Settings without it) also needs the owner, so a paired device answers a change
card with Refuse only. An open card's Open Settings is offered to any client
whose Settings draws that card. No button to Settings appears where the card is
`settings` helps a lost user set Boite up. Bare, it starts with the app the
user last used and the language it speaks (`app: the desktop app on "PC"
speaks French; ...`), then lists the settings an agent may ask about, one row
each: key, value as JSON, the page and card it sits on and, quoted, the name
that app shows (`agentCpuCapPercent 75 resources#limits "Plafond CPU de tous
les agents"`). The list is `REQUESTABLE_SETTINGS` in
`packages/contracts/src/setting-requests.ts`, then that app's experiments as
`experiment.<id>` (`experiment.resident-agents true experiments#resident-agents
"Agents"`); credentials, account ids and browser profiles are not in it.

Language, experiments and names come from the apps: each says them after every
hello and on every change (`client.preferences`, a paired phone included), with
an id it keeps on the device (`boite.appId`). The core keeps them in memory
only, and reads the app that last sent a prompt or changed one of them; once
that app's socket closes, the next app to report takes its place. The language also rides on each prompt
(`SentFrom.language`), so the one-line note on where a prompt came from says
`which shows Boite in French` when a session starts and when the language
changes. Before any app reported, the first line says so and no experiment is
listed.

`settings request <key> <value> --why <text>` draws a settings card the same
way `ask` draws a question: the agent keeps working and the card outlives the
turn and a restart. The value is JSON when it parses (`60`, `false`, `null`,
`["https://a.example"]`) and the bare word otherwise. It is refused when the
key is not in the list (the refusal lists them), when `settings.set` would
refuse the value, when the setting already holds it (`experiment.resident-agents
is already true`: the agent learns the state before the user has to say it),
when a card on that key is already open in the thread, and for an experiment
when the value is not true or false or no app has reported yet.

The card makes the change in the chat. A true-or-false setting, an experiment
included, draws its switch showing the value now; any other value shows the
current value struck through, the proposed one and Apply. The switch and Apply
call `settings.set`, the call the Settings page makes, so only the owner can
use them; an experiment's switch flips the device's own experiment, which any
of the user's devices may do, and stays free while the core has not heard it.
An experiment's card settles when an app flips that switch since its last
report, compared by the app's id so a reconnect still counts, or when the app
it was asked from reports another value, a flip made offline or across a
restart. Another app that merely has a different value, a language change or a
core setting moving settles nothing.
Show in Settings stays as a secondary button for whoever can apply. A paired
device answers a core setting's card with Refuse only. An open card's Open
Settings is offered to any client whose Settings draws that card. No button to Settings appears where the card is
missing: a paired device's Settings lacks the owner pages (Providers,
Plugins, Protection, Brain) and the owner's cards on shared pages (worktrees,
privacy, execution, browser origins, auto-compact), and a phone browser's
Expand All @@ -518,10 +543,11 @@ itself: the core closes every open card whose setting left the value it was
asked from, whoever moved it, from the card or from the page. The agent then
reads `applied: <key> is now <value>` or `changed: the user set <key> to
<value>, not <proposed>`. Refuse sends `refused: <key> stays <value>`. A
sensitive setting has no Apply: the network ones, the guards that keep agents
quiet and their memory protection, history retention and where worktrees go.
Its card opens Settings on the setting's card, which rings once in the accent
colour, and closes when the user changes it there.
sensitive setting (the network ones, the guards that keep agents quiet and
their memory protection, history retention and where worktrees go) is changed
in the card too, under a red shield, its heading "Sensitive setting" and a
line asking the user to check it first. Show in Settings opens the setting's
card, which rings once in the accent colour.

`settings open <page>[#<card>] --why <text>` points at a page for what is not
one value, a provider login (`accounts#provider-claude`) or a device to add
Expand Down
5 changes: 5 additions & 0 deletions docs/language.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ way, a storage a browser refuses, a stored value from a build that had more
languages, a sentence one catalogue is missing, ends in English rather than in
an error.

Each app also tells the cores it is connected to which language it speaks,
with its experiments and the names it gives settings (`client.preferences`,
[cli.md](cli.md)). An agent then names a setting the way the user's screen
does, and the note ahead of a prompt says which language the app shows.

## Picking one

Settings, Appearance, at the top: System, English, Français. The first screen
Expand Down
2 changes: 2 additions & 0 deletions packages/contracts/src/access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ export const DEVICE_METHODS: ReadonlySet<RpcMethodName> = new Set<RpcMethodName>
'questions.answer',
// Paired phones can dismiss the same question cards as the owner's chat.
'questions.skip',
// A phone says its own language and experiments, so the agent reads the screen the user reads; nothing the core trusts.
'client.preferences',
// Read-only Changes and Files panels on the phone: the thread's own changes and files.
// The core runs git itself (no device command), refuses a diff against anything but
// HEAD (core access.ts), and holds every path to a thread whose real working directory
Expand Down
10 changes: 6 additions & 4 deletions packages/contracts/src/agent-door.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ContextPreset, ContextRequest } from './context-mode';
import type { AgentAddress } from './coordination';
import type { ProjectId, ProviderId, ThreadId, TurnId } from './ids';
import type { SettingReading } from './setting-requests.ts';
import type { SettingsReading } from './setting-requests.ts';
import type { ThreadSummary } from './threads';

/**
Expand Down Expand Up @@ -129,10 +129,12 @@ export interface AgentDoorRpcMethods {
*/
'agent.projects': { params: { threadId: ThreadId }; result: AgentProject[] };
/**
* The settings an agent may ask about (`REQUESTABLE_SETTINGS`), each with
* its value and the page it sits on, so it reads before it proposes.
* The settings an agent may ask about (`REQUESTABLE_SETTINGS`, then the
* experiments of the app the user last used), each with its value, the page
* it sits on and its name in that app, so it reads before it proposes. `app`
* says which language the user reads Boite in.
*/
'agent.settings': { params: { threadId: ThreadId }; result: SettingReading[] };
'agent.settings': { params: { threadId: ThreadId }; result: SettingsReading };
/**
* The agent registers an existing folder as a project, as the owner does
* from the sidebar, so `agent.spawn` and `agent.move` can name it. `path` is
Expand Down
10 changes: 6 additions & 4 deletions packages/contracts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,12 @@ export { AUTO_COMPACT_MOMENTS, AUTO_COMPACT_TOKENS, BROWSER_ORIGINS_MAX, CACHE_K
export { TITLE_MODEL_DEFAULTS, defaultTitleModel } from './title-models.ts';
export { DEVICE_METHODS, DEVICE_EVENTS, AGENT_EVENTS } from './access.ts';
export {
REQUESTABLE_SETTINGS, REQUESTABLE_SETTING_KEYS, SETTING_REASON_MAX, SETTINGS_PAGES, SETTINGS_SECTION_PATTERN,
checkSettingRequest, effectiveSetting, isRequestableSetting, isSettingsPage, readSettings, sameSetting, settingAnswerText, settledSetting,
type RequestableSetting, type SettingAsk, type SettingNormalizer, type SettingOutcome, type SettingPlace, type SettingReading, type SettingRequestCheck,
type SettingRequestParams, type SettingsPage,
APP_LANGUAGES, CLIENT_APP_PATTERN, CLIENT_LABELS_MAX, CLIENT_LABEL_CHARS, EXPERIMENT_IDS, REQUESTABLE_SETTINGS, REQUESTABLE_SETTING_KEYS, SETTING_REASON_MAX, SETTINGS_PAGES, SETTINGS_SECTION_PATTERN,
checkClientPreferences, checkSettingRequest, settledExperiment, currentSetting, effectiveSetting, experimentOf, isExperimentId, isRequestableSetting, isSettingsPage, readSettings,
sameClientPreferences, sameSetting, settingAnswerText, settledSetting,
type AppLanguage, type ClientPreferences, type ExperimentId, type ExperimentSettingKey, type RequestableKey, type RequestableSetting, type SettingAsk,
type SettingNormalizer, type SettingOutcome, type SettingPlace, type SettingReading, type SettingRequestCheck, type SettingRequestParams, type SettingsPage,
type SettingsReading,
} from './setting-requests.ts';
export {
AGENT_PROJECTS_MAX, AGENT_RUN_MINUTES, AGENT_SETTING_KEYS, agentSettingAnswerText, agentSettingValue, checkAgentSettingRequest, isAgentSettingKey,
Expand Down
4 changes: 3 additions & 1 deletion packages/contracts/src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { NativeAgentUpdate } from './delegation';
import type { MessageId, ProjectId, RequestId, ThreadId, Timestamp, TurnId } from './ids';
import type { RpcMethods } from './index';
import type { QuestionAnswer, QuestionOption } from './questions';
import type { SettingAsk } from './setting-requests.ts';
import type { AppLanguage, SettingAsk } from './setting-requests.ts';
import type { ActivityTurn } from './threads';
import type { Turn } from './turns';
import type { InlineView } from './view.ts';
Expand Down Expand Up @@ -110,6 +110,8 @@ export interface ArtifactContent { url: string; bytes: number; mimeType: string;
export interface SentFrom {
client: 'shell' | 'pwa';
device: string | null;
/** The language the app spoke when the prompt left it (`client.preferences`); unset when it had not said. */
language?: AppLanguage;
}

/** The longest `hello` `client.device` the core keeps. */
Expand Down
Loading
Loading