Concepts
Core concepts
Section titled “Core concepts”Commands, contributions and leases
Section titled “Commands, contributions and leases”A command has an id, a title, a category, an optional description, a
handler, and optional projections, action and availability. A command
shows up only on the surfaces its projections name.
contribute admits a batch of commands from one sourceId into one
namespace, optionally inside a focus scope such as one open window.
Admission is atomic: the whole contribution is validated before any of it goes
live. It throws when a namespace already belongs to another source, when a
canonical ID or a slash alias in the same scope repeats, or when a field is
blank.
Each contribution returns a lease for that exact generation. lease.update
swaps its commands atomically. lease.dispose removes it. Disposing a stale
lease never removes a replacement. Both calls are idempotent.
Snapshots and projections
Section titled “Snapshots and projections”snapshot() returns six frozen arrays: bindings, palette, slash, help,
menu and agent. Rows are immutable copies. Each surface sorts by its
explicit order, then by command ID in UTF-8 byte order, so the order never
depends on insertion. A snapshot keeps its identity until the graph or some
availability changes. subscribe(listener) tells you when the graph changes.
The agent view drops presentation metadata and keeps the semantic
inputSchema and outputSchema, so an agent gets a tool description and never
a UI layout. scopeSnapshot(snapshot, scope) gives the human view for one
focused scope: global rows plus that scope’s rows, with a focused slash alias or
binding shadowing the same global one. The agent view stays whole.
Execution
Section titled “Execution”execute(commandId, input) is the only way a handler runs. input.origin is
one of eight closed origins: agent, browser, keyboard, menu, palette,
programmatic, slash and terminal. The outcome is one of:
handled: the handler finished. Its return value is inresult. This does not mean physical work or a durable workflow completed.unavailable: the command’savailability()said no. The reason comes back.failed: the handler threw.erroris JSON:{ code, message, details? }.missing: no live command has that ID.invalid: the command ID is blank.closed: the runtime was closed first.
Availability
Section titled “Availability”A command can supply a synchronous availability() reader over current host
facts. snapshot() keeps unavailable commands visible with their reason, and
execute() reads availability again right before calling the handler.
Availability is a convenience check. It is not authorization, so the handler
still admits the real request. The host updates its own facts and publishes
snapshots over its own connection. Whistle does no polling or broadcast.
Failures
Section titled “Failures”Throw WhistleActionError(code, message, details?) to choose the failure code
and attach JSON details. Any other Error becomes command_failed. Stacks and
non-Error thrown values never cross the execution result.
Interactions
Section titled “Interactions”A running command can talk to its human caller through input.interaction.
prompt asks one choice, text (optionally secret) or confirmation
question. notify shows info, progress, handoff (open a URL, optionally
enter a code) or success. The vocabulary names the kind of answer, never a
widget.
A caller can pass its own interaction. Otherwise present(presenter) registers
the mounted client that opens one for each human-origin execution. Agent and
programmatic executions get none. Whistle settles the session with handled,
cancelled or failed when the handler ends, and the presenter can retry the
same execution. The newest presenter wins until its disposer runs.
createWhistleInteractionHost from ./interaction is a renderer-neutral store
for one interaction. It parses every prompt, notice and answer with the ./wire
schemas, and a terminal or browser renderer draws its snapshot.
Slash arguments
Section titled “Slash arguments”A slash projection can carry arguments, a data-only grammar for the text after
the alias. Fixed words map to complete argument objects. text puts the rest
of the line into one field of a fixed argument object, optionally with a request
ID field for idempotent retries. Clients parse, complete and hint from this
data, so no client names a command in code.
Catalogs
Section titled “Catalogs”A backend advertises its commands as a whistle.catalog/v1 catalog of the same
data-only definitions. parseCatalog validates it at the client boundary.
catalogCommands(catalog, invoke) turns it into commands whose handlers call
your invoke function. Each reply is a step: done (optional notice and
result) or prompt (one question plus the complete arguments of the next call,
with the answer going into a named field). Multi-step flows stay stateless on
the wire and run through the caller’s interaction.
Closing
Section titled “Closing”close() is synchronous and idempotent and returns undefined. The first call
releases every live contribution and leaves the shared empty snapshot. Leases
issued earlier become harmless. An execute Promise returned before close
still settles, and may settle handled. A later execute returns closed, and
a later valid contribute throws WhistleClosedError.
Typing a reusable handler
Section titled “Typing a reusable handler”Use indexed access on WhistleCommand to declare a handler apart from a command
literal:
import type { WhistleCommand } from "@fungi.computer/whistle";
type SaveArguments = Readonly<{ path: string }>;type SaveResult = Readonly<{ saved: string }>;
const save: WhistleCommand<SaveArguments, SaveResult>["handler"] = ({ arguments: input,}) => (input === undefined ? undefined : { saved: input.path });The handler receives WhistleExecutionInput<SaveArguments>: the origin,
optional arguments, a cancellation signal and an optional interaction. It
returns SaveResult, undefined, or a Promise of either.
WhistleCommandHandler names this callback type. Commands with different
argument and result types can share one readonly WhistleCommand<never>[]
contribution. Contributions erase those generics at runtime, so the handler
still validates the arguments it actually gets.
Presentation hints
Section titled “Presentation hints”action.inputSchema and action.outputSchema describe structured data and keep
opaque JSON annotations. action.presentation is an optional local hint:
action, choice, form, confirmation, or custom with opaque JSON for an
owning client’s own field or gesture bindings. A server capability must not
prescribe its form, menu or other rendering. Human projections keep the hint.
The agent projection drops it.