825 Commits
Author SHA1 Message Date
snowspeederandClaude Fable 5 28a2629c43 feat: speak answers sentence by sentence and never speak reasoning
CI / Verify and package (ubuntu-latest) (push) Canceled after 0s
CI / Verify and package (windows-latest) (push) Canceled after 0s
Segment the final answer as it streams and synthesize each completed
sentence immediately, so a listener hears the reply begin while the model
is still writing it. Sentences share the progress clips' FIFO chain and
output-sequence counter; only text left unspoken (a hit budget) is
synthesized after settle, so audio is never sent twice. A message that
grows a tool call stops answer-streaming without re-speaking what already
played.

Reasoning models inline their chain of thought as <think> blocks in the
same delta stream. Strip it while streaming -- carrying a partial tag
across delta boundaries -- so it is neither spoken nor shown as answer
text.

The fixed "Hang on while I work on that." is now a rotation of short
natural phrases that never repeats twice running, and a long silent think
gets a spoken "still on it" roughly every 45 seconds until real speech
begins. Turn timeout rises to five minutes to match the client.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-07 14:20:21 -04:00
snowspeederandClaude Fable 5 a7033f9076 feat: speak a progress update during long voice turns
CI / Verify and package (ubuntu-latest) (push) Canceled after 0s
CI / Verify and package (windows-latest) (push) Canceled after 0s
After two seconds of agent work, send one agent.progress frame with a
synthesized "Hang on while I work on that." clip over the same binary
audio channel (shared output-sequence counter), at most once per turn;
screen-only agent.working heartbeats continue every 15 seconds. This is
the change the 15:11 rebuild already shipped to the running server —
committing it pins the protocol addition to the client that now
tolerates and plays it.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-06 23:10:07 -04:00
snowspeeder a71cb8334a feat: extend voice API workspace and speech controls
CI / Verify and package (ubuntu-latest) (push) Canceled after 0s
CI / Verify and package (windows-latest) (push) Canceled after 0s
2026-08-06 12:09:34 -04:00
snowspeeder 1adb459e04 feat: harden voice API conversations and progress 2026-08-06 00:42:55 -04:00
snowspeeder 0793fa0ac4 feat: add authenticated voice pipeline API docs
CI / Verify and package (ubuntu-latest) (push) Canceled after 0s
CI / Verify and package (windows-latest) (push) Canceled after 0s
2026-08-04 19:09:33 -04:00
snowspeeder a2e4f1d576 chore: document fork workflow and ignore local artifacts
CI / Verify and package (ubuntu-latest) (push) Canceled after 0s
CI / Verify and package (windows-latest) (push) Canceled after 0s
2026-08-04 16:25:32 -04:00
snowspeeder 9627453e23 fix: keep Azure voice mode active between turns
CI / Verify and package (ubuntu-latest) (push) Canceled after 0s
CI / Verify and package (windows-latest) (push) Canceled after 0s
2026-08-04 16:22:55 -04:00
snowspeeder c3e0011378 feat: add Azure Speech voice mode
CI / Verify and package (ubuntu-latest) (push) Canceled after 0s
CI / Verify and package (windows-latest) (push) Canceled after 0s
2026-08-04 07:28:48 -04:00
Federico Jaramillo Martinez c09b67d15a feat(sessions): show thinking level in assistant chat bubble metadata
CI / Verify and package (ubuntu-latest) (push) Canceled after 0s
CI / Verify and package (windows-latest) (push) Canceled after 0s
Attribute the thinking level in effect to each assistant message: from
thinking_level_change branch entries for history, from the live session
level for streamed message.end events and join-time stream snapshots.
The chat metadata row renders it after the model; thinking "off" stays
hidden so non-reasoning bubbles are unchanged.
2026-07-30 12:55:11 +02:00
Federico Jaramillo Martinez c2b7cce2d3 feat(sessions): inherit thinking level in spawned sessions
spawn_session and spawn_subsession now forward the dispatching session's
current thinking level to the new session, clamped by pi to the spawned
model's capabilities, instead of falling back to the configured default.
2026-07-30 12:54:24 +02:00
Federico Jaramillo Martinez 7103bfcb4c feat(sessions): streamline bulk selection toolbar
Merge Select visible / Clear visible / Clear into one binary toggle
(Select visible when empty, Clear selected otherwise) and drop the
redundant Done button; selection mode closes from the same heading
toggle that opened it. Shorten Archive/Delete labels so the toolbar
fits one line on narrow screens.
2026-07-30 00:21:53 +02:00
Federico Jaramillo Martinez 5f4d81352f feat(sessions): optional model for spawn tools and # model completion in composer
spawn_session and spawn_subsession accept an optional model parameter as
an exact provider/model-id (strict matching; unknown specs fail listing
available models; omitting it keeps the inherited model). The chat
composer opens a model completion menu on # and inserts a
#provider/model-id reference into the draft, which agents forward as
the model parameter.
2026-07-29 23:58:38 +02:00
Federico Jaramillo Martinez 9b8728a5d6 chore(release): v1.202607.3 2026-07-29 13:50:42 +02:00
Federico Jaramillo Martinez c3aeef23f3 fix(relays): keep document tab strip scroll position on tab switch 2026-07-29 13:47:24 +02:00
Federico Jaramillo Martinez 93249c8bc4 Merge pull request #109 from jmfederico/fix/issue-106-extension-dialogs
feat(sessions): answerable Pi extension dialogs (ctx.ui confirm/select/input)
2026-07-29 09:07:26 +02:00
Federico Jaramillo Martinez 7a426571b8 docs(sessions): document settled extension dialog cards as dismiss-stay records 2026-07-29 07:52:21 +02:00
Federico Jaramillo Martinez 5420869c52 fix(sessions): close extension dialog lifecycle edge cases
Post-review hardening for the extension dialog feature:

- dispose() and closeActive() now settle startup-parked session_start
  dialogs before awaiting pending opens, so daemon shutdown or closing a
  session whose open is parked on a dialog can no longer block behind the
  dialog timeout (infinite with extensionDialogsTimeoutMs: 0).
- A failed create now drops its dead dialog cards, closes the
  early-subscribed socket, and ignores late dialog frames instead of
  leaving an unanswerable card on the failed row.
- The pending-start status resync checks its staleness guard before
  applying the unordered snapshot, so a late response can no longer
  clobber the post-swap session state.
- The dialog countdown no longer queues one screen-reader announcement
  per second (decorative; the daemon-owned dialog.closed event is the
  real signal) and no longer renders "1h 60m" near hour boundaries.
2026-07-29 07:52:21 +02:00
Federico Jaramillo Martinez 5759201a39 docs: document Pi extension dialogs and extensionDialogsTimeoutMs
Add the close-out documentation for extension dialog support:

- docs/config.md (+ synchronized config.html): extensionDialogsTimeoutMs
  in the config matrix, reload/restart guidance, the global config
  example, and a new Extension dialogs key-detail section covering the
  unattended-dialog safety valve (default 5 min, 0 = forever, effective
  deadline is the sooner of the extension's own timeout and this knob).
- docs/plugins.md (+ synchronized plugins.html): new Pi extension
  dialogs in PI WEB behavior note for extension authors — confirm/select/
  input render inline in the transcript and resolve with the real answer,
  answers use a dedicated daemon channel (never the prompt queue, so
  tool_call hooks park safely), session_start dialogs are reachable
  during create and open, reload rehydration, first-answer-wins across
  tabs, abort/runtime-replacement settlement, and the reload-mid-startup
  browser-local caveat.
- Add the extension-dialogs changeset (patch) for the release notes.
2026-07-29 07:52:21 +02:00
Federico Jaramillo Martinez 346607e8bc fix(sessions): make session_start extension dialogs answerable mid-startup
A dialog opened from a session_start hook parks session construction
before the session ever becomes active, but every servable path gated on
readiness: the answer/cancel routes and status 404'd (or parked behind
the in-flight open), and the client only subscribed once its create
request resolved — so the dialog that gated readiness could never be
answered and always rode to the daemon timeout.

Daemon: hold a startupSessions registry for the duration of extension
binding and let status, answerDialog, and cancelDialog resolve active →
startup → getOrOpen fallback. getOrOpen itself is untouched, so prompts
and other mutations still cannot reach a half-constructed session.
status() no longer parks behind an in-flight open; it resolves from the
startup window (intended semantics change, lifecycle test updated).

Client: the pending-start row learns the real session id from the first
token-matched session.startup event, connects its otherwise idle session
socket to the constructing session, and recovers pre-subscription opens
with a merge-based status resync (the unordered HTTP snapshot only adopts
dialog ids the ordered socket channel never reported). The leg-4 dialog
card renders in the startup view with no component changes, and answers
go out under the real id. Readiness proceeds as before once the hook
settles.
2026-07-29 07:52:21 +02:00
Federico Jaramillo Martinez 8a429e45fa fix(sessions): settle run-scoped extension dialogs at abort-request time
A user abort while a tool_call dialog is parked deadlocked the dialog
until its timeout: pi's agent loop waits for the parked dialog handler
before emitting agent_end, and run-scoped dialogs were settled only on
agent_end. Settle them synchronously at abort-request time, before
awaiting the runtime abort, so a hung or failing abort cannot strand
the parked waiter. Keep the agent_end settlement as the run-crash
backstop; the store makes the double settlement a stale no-op.
2026-07-29 07:52:21 +02:00
Federico Jaramillo Martinez d738d68647 feat(ui): render extension dialogs inline in the transcript 2026-07-29 07:52:21 +02:00
Federico Jaramillo Martinez 87f5df663f feat(ui): wire extension dialogs into the client data layer
Parse pendingDialogs on the session status and the dialog.opened /
dialog.closed socket events, track them per selected session in app
state (array, no supersede; closed dialogs kept with their outcome for
transient rendering), and add answerDialog/cancelDialog session API
methods on the dedicated dialogs routes, allowlisted for remote
machines.
2026-07-29 07:52:21 +02:00
Federico Jaramillo Martinez d31c40db0b feat(sessions): wire extension dialogs into the daemon with answer/cancel routes
ctx.ui.confirm()/select()/input() from extensions now open daemon-owned
pending dialog records, publish dialog.opened/dialog.closed, and park a
Promise that settles on the browser's answer or cancel, the extension's
own signal/timeout, the extensionDialogsTimeoutMs daemon default (5 min,
0 = forever; tuning knob, not a gate), agent_end for run-scoped dialogs,
or session-ended on close/replace/dispose. Answers resolve the parked
extension Promise directly via POST /sessions/:id/dialogs/answer|cancel
- never the prompt queue - with first-wins stale semantics across
browsers, and SessionStatus.pendingDialogs rehydrates reloading clients.
2026-07-29 07:52:21 +02:00
Federico Jaramillo Martinez 50801e4f85 feat(sessions): add pending extension dialog store and wire types
Domain layer for issue #106: a daemon-owned, per-session
PendingExtensionDialogStore (multiple open dialogs, no supersede,
kind-validated answers, stale-tolerant closes) plus the shared
PendingExtensionDialog/ExtensionDialogOutcome wire types,
SessionStatus.pendingDialogs, and dialog.opened/dialog.closed events.

Relay: issue-106-extension-dialogs leg 1
2026-07-28 23:38:44 +02:00
Federico Jaramillo Martinez 87c09982e9 feat(plugins): add bundled relays workspace panel plugin 2026-07-28 23:14:21 +02:00
Federico Jaramillo Martinez d19fca4090 feat(plugins): add files.listFiles directory listing to the plugin API 2026-07-28 23:14:21 +02:00
Federico Jaramillo Martinez 12f25282fc fix(sessions): void the open ask_user form when a chat message arrives
An ordinary chat message answers the session's open ask in the user's
own words, so keeping the form open would invite answers to questions
the conversation has already moved past. The form now closes as
cancelled, browsers clear the live card, and the model is told without
being woken so the notice rides into the turn the message itself
starts. Ignored duplicate queued messages skip the void on purpose:
they must not void an ask posted after the queued original.
2026-07-28 23:14:21 +02:00
Federico Jaramillo Martinez f9d0b2b8d5 Merge pull request #107 from jmfederico/remove-legacy-archive-migration
fix(sessiond): remove legacy session archive migration
2026-07-28 23:11:23 +02:00
Federico Jaramillo Martinez eec40267b4 Merge branch 'main' into remove-legacy-archive-migration 2026-07-28 20:21:22 +02:00
Federico Jaramillo Martinez 79d4a294d6 Merge pull request #108 from jmfederico/fix/windows-session-test-paths
test(sessions): fix Windows-only failures from drive-relative fixture paths
2026-07-28 20:21:06 +02:00
Federico Jaramillo Martinez 31c3cfe43e test(sessions): resolve POSIX fixture paths that are drive-relative on Windows
Five tests failed only on the Windows runner. Their fixtures used bare
POSIX-absolute paths such as /srv/other-worktree and /old-project, which
win32 treats as absolute but drive-relative: resolve() maps them onto the
runner's current drive (D:\...). The code under test canonicalizes stored
cwds by contract, so assertions comparing against the raw fixture string
never matched on Windows:

- parentSessionLocator and crossWorkspace listings annotate
  parentSessionCwd with canonicalizeStoredCwd(header.cwd);
- cleanup forgets unread via canonicalizeStoredCwd(record.cwd), which
  missed the marker the test seeded with the raw path.

Resolve the fixture paths once at declaration, matching the existing
WORKSPACE_CWD = resolve("/workspace") convention, so fixtures model what
a real Windows Pi would record. Linux behavior is unchanged.
2026-07-28 19:59:27 +02:00
Federico Jaramillo Martinez 612ea74720 docs(agents): add positive-phrasing principle to documentation guide
Record that docs state what the software does and reserve "does not"
statements for behavior that contradicts the most obvious expectation.
2026-07-28 19:43:32 +02:00
Federico Jaramillo Martinez 5b717ed542 docs(config): state data directory behavior positively
Documentation should describe what the software does, not enumerate
what it does not do. Rephrase the data-directory wording around the
positive facts (each directory is independent; a new root starts with
empty state) and drop the "never moves or copies" constructions from
config.md, the config.html env table, and the changeset.
2026-07-28 19:43:22 +02:00
Federico Jaramillo Martinez 5c3461d508 chore(changeset): record legacy session archive migration removal 2026-07-28 16:22:00 +02:00
Federico Jaramillo Martinez 741b55117b docs(config): document independent data directories
Changing PI_WEB_DATA_DIR selects where managed state lives but never
moves or copies existing state. Explain how to carry session archives
over manually, and fix the stale "moves managed state location" claim
in the config.html env table.
2026-07-28 16:21:51 +02:00
Federico Jaramillo Martinez 1a191bfa13 fix(sessiond): remove legacy session archive migration
The migration moved ~/.pi-web/archived-sessions* into a custom
PI_WEB_DATA_DIR at session daemon startup. Remove it outright: data
directories are independent, and anyone who sets a custom data
directory can copy archived-sessions.json and archived-sessions/
manually while the daemon is stopped.

Without the migration, runSessionDaemonStartup's only remaining
contract was sequencing create/register/listen, so inline those steps
into sessiond.ts and drop the wrapper module and its tests.
2026-07-28 16:13:59 +02:00
Federico Jaramillo Martinez 69b125b001 feat(sessions): surface cross-worktree parent and child sessions
A session spawned into another worktree recorded a parent that no
listing contained, so the row showed only "parent unavailable" and its
parent's row looked childless. Both facts were accurate and useless:
neither said where the related session actually was.

Report both directions from the session store instead. A missing
parent's cwd and id come from its own file header, so one 4 KB read per
distinct missing parent resolves it without listing other workspaces;
children are counted by listing sibling workspaces and matching the
parent path they already recorded, needing no header reads. Reads are
memoized per path because Pi writes headers once, and the cache is
released on dispose. Both directions are best-effort: an unreadable
header or an unlistable worktree leaves a session unannotated rather
than failing the listing.

In the browser, an orphan child keeps the same child marker as a nested
one, dimmed, so it no longer renders as a root; whereabouts are stated
once on the meta line ("parent in feature/foo", "2 children
elsewhere"), where a clamped title cannot hide them. A "Go to parent
session" action switches to the owning workspace and selects the
parent. Live session.created events keep child counts current instead
of leaving them stale until the next listing.

Session and workspace paths reach the browser from two producers: store
enumeration for a listing, and the live runtime for a broadcast. They
are now compared through one normalizing helper, so tree nesting and
child counts cannot silently miss a link when only a trailing separator
differs.

Extract the shared "workspaces of the project containing this cwd"
lookup out of ProjectScopedSpawnTargetResolver so spawn targeting and
child counting share one implementation, and register it regardless of
whether spawning is enabled: children can predate a config change, and
the tree should stay honest about them either way.
2026-07-28 11:56:21 +02:00
Federico Jaramillo Martinez a4f513dbd1 docs(test): record happy-dom as the standard Lit component test harness
The harness landed in 4973ada and spread to 11 component test files
without the guide mentioning it, while relays cited the guide as
sanctioning it. Document the per-file @vitest-environment opt-in, the
seam-selection order (pure exported seam, happy-dom, TemplateResult
extraction), happy-dom conventions and limits, and the opportunistic
migration stance for existing extraction tests.
2026-07-27 20:42:58 +02:00
Federico Jaramillo Martinez 4a5150309d feat(ui): add copy buttons to workspace menu details 2026-07-27 19:31:15 +02:00
Federico Jaramillo Martinez 8517800a24 feat(plugins): turn Info panel into a PI WEB status view
Rework the bundled Info plugin from a demo into an always-available
status view: running and installed versions, installation details,
release state, and per-service health rendered from host-provided
state, plus machine and workspace details. Replace the window.alert
demo action with a Copy PI WEB Diagnostics action that copies a
plain-text summary for bug reports.

Add state.selectedMachine to the stable plugin runtime state so
actions and other runtime callbacks can read the selected machine's
identity, and split the plugin into a copy-paste-friendly skeleton
(pi-web-plugin.ts) and replaceable internals (infoInternals.ts).
2026-07-27 18:03:25 +02:00
Federico Jaramillo Martinez 0a35748478 Merge pull request #105 from jmfederico/feat/unread-ux
feat(ui): improve unread-messages experience
2026-07-27 15:58:55 +02:00
Federico Jaramillo Martinez f76a9fabc8 feat(ui): unify row indicators into one mark with an unread ring
Unread is no longer a competing ActivityIndicatorKind or a separate
name-adjacent dot: every row renders a single indicator. An accent ring
wraps the still-pulsing work dot when a row is both busy and unread, a
filled accent dot shows while idle and unread, and activity kinds keep
their existing precedence (sending > session > terminal). Session rows
surface unread state even while busy, so the unread header count and
mobile Sessions badge now count busy unread sessions too.
2026-07-27 15:55:06 +02:00
Federico Jaramillo Martinez 8172defca3 Merge pull request #104 from jmfederico/fix/session-list-scroll-reveal
fix(ui): scroll session/workspace lists only on positive reveal triggers
2026-07-27 15:46:50 +02:00
Federico Jaramillo Martinez f2307b8c62 test(ui): explain the double updateComplete await in WorkspaceList scroll tests 2026-07-27 15:41:32 +02:00
Federico Jaramillo Martinez 8af637b00e feat(ui): bubble unread presence up to workspace, project, and machine rows
Wire the derived UnreadPresence into dot indicators (no counts) across the
navigation panel: workspace, project, and machine rows (machine-switcher and
machine-list) now show a static accent dot whenever a session beneath them is
unread, including offline machines with stale-but-present state. Presence
flows PiWebApp -> AppNavigationPanel -> leaf id sets, mirroring the
unreadSessionIds chain, and is covered by happy-dom component tests per list
plus panel- and app-level wiring tests. Adds changesets for the mark-as-read
actions and the bubble-up indicators.
2026-07-27 15:08:31 +02:00
Federico Jaramillo Martinez 76f292cfde fix(ui): scroll session/workspace lists only on positive reveal triggers
Live data refreshes (messageCount churn from session status publication,
workspace topology refreshes) replace the sessions/workspaces arrays and
the selected object with same-id copies, and each replacement re-scrolled
the selected row into view.

Replace the broad updated() scroll triggers with positive reveal triggers:
a different row becoming selected (first render included), the archived
reveal path, a restore moving the selected row from archived back to
current (same id, archived flag cleared), and section expansion. The
sessions/workspaces array triggers are removed, not guarded.
2026-07-27 14:46:25 +02:00
Federico Jaramillo Martinez 9267174736 feat(ui): derive bubble-up unread presence from unread projections
Add pure unreadPresence helpers mapping unread summary cwds to machine,
project, and workspace presence (machine = any summary, workspace = exact
path match, project = known workspace ownership), plus a PiWebApp seam
that keeps the derived UnreadPresence state current on any machine's
projection change and on app-state transitions. No UI consumption yet.
2026-07-27 14:41:40 +02:00
Federico Jaramillo Martinez b4a7a9230b feat(ui): add mark-as-read actions for unread sessions
Add a "Mark as read" item to the session row menu (shown only for
unread, non-archived, non-transient sessions) and a bulk "Mark read"
action to the current-selection toolbar (enabled when any selected
session is unread). Both flow through AppNavigationPanel to PiWebApp,
which acknowledges via SessionUnreadController with the exact observed
completion order.
2026-07-27 14:22:17 +02:00
Federico Jaramillo Martinez 0a080f4390 test(sessions): cover unread cleanup across all archive paths
Relay unread-ux leg 1: audit verified every archive path (single, bulk,
tree, cleanup, restore, delete, rebind) clears unread state in the right
order with no resurrection vector. Add regression tests for the uncovered
paths: bulk archive, archive with descendants, cleanup, bulk delete, and
archiving an active session with a pending activity latch.
2026-07-27 13:59:29 +02:00
Federico Jaramillo Martinez 4191d526f4 Merge pull request #103 from jmfederico/feat/model-questions-ux
feat(sessions): add ask_user question forms
2026-07-27 13:01:59 +02:00