Lock the right pane while focused, add project focus, quiet lead
This commit is contained in:
+22
-1
@@ -108,7 +108,7 @@ paths:
|
||||
schema: { $ref: "#/components/schemas/Focus" }
|
||||
put:
|
||||
operationId: setFocus
|
||||
description: Records the human switching the right pane's task or tab. The lead agent is told.
|
||||
description: Records the human switching the right pane's project, task or tab.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@@ -123,6 +123,20 @@ paths:
|
||||
"400": { $ref: "#/components/responses/Error" }
|
||||
"404": { $ref: "#/components/responses/Error" }
|
||||
|
||||
/api/me/pane-focus:
|
||||
put:
|
||||
operationId: setPaneFocus
|
||||
description: Records whether the human is working in the right pane. While they are, the lead agent can't move it.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/PaneFocus" }
|
||||
responses:
|
||||
"204":
|
||||
description: Recorded.
|
||||
"400": { $ref: "#/components/responses/Error" }
|
||||
|
||||
/api/push/key:
|
||||
get:
|
||||
operationId: getPushKey
|
||||
@@ -1099,6 +1113,7 @@ components:
|
||||
type: object
|
||||
required: [tabs, tab]
|
||||
properties:
|
||||
project_id: { type: string, description: The project the right pane shows; the task's project when task_id is set, absent for none. }
|
||||
task_id: { type: string, description: The task the right pane shows; absent for none. }
|
||||
tabs:
|
||||
type: array
|
||||
@@ -1106,6 +1121,12 @@ components:
|
||||
items: { $ref: "#/components/schemas/TaskTab" }
|
||||
tab: { $ref: "#/components/schemas/TaskTab" }
|
||||
|
||||
PaneFocus:
|
||||
type: object
|
||||
required: [focused]
|
||||
properties:
|
||||
focused: { type: boolean, description: True while the browser window has focus and the focused element is inside the right pane. }
|
||||
|
||||
TaskTab:
|
||||
type: string
|
||||
enum: [transcript, changes, files, inbox, todo]
|
||||
|
||||
+6
-5
@@ -100,7 +100,7 @@ Agent activity becomes events with `agent_session_id` set: `user.message`, `agen
|
||||
|
||||
Messages byre itself sends an agent (actor `byre`: the restart notice, Sync and Merge prompts, Compact's handoff request and the continuation's handoff) show collapsed like a tool call, labelled `byre` with the first line as the brief; expanding it shows the full text. The transcript view has an accent border while the session is `running`, and a braille spinner on its last line, cycling ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ every 0.8s. It is one element with one CSS animation: the ten frames are drawn side by side in an inline SVG used as the `.spinner` mask over an accent background, and the `braille` keyframes in `web/style.css` step the mask position through them. Orion freezes animated GIFs and per-dot `step-end` opacity animations partway through a turn, so it stays a single element with a single animation. Consecutive tool calls (with any thinking between them) collapse into one group whose summary counts calls per tool and file, and file paths show relative to the worktree. A turn's final message that is at least half visible in the transcript while the page is visible dismisses its `report` inbox item, as the viewing user. Selecting a task that has never had a session focuses the composer. In the composer, Enter sends and Shift-Enter adds a newline; unsent text is kept per task in memory, so it survives switching tabs or tasks but not a page reload. The composer is the same whether or not the task has a live session: Send posts to the session's messages, or starts a session with the text when there is none or the last one completed. A sent message shows at once as a "sending…" bubble, replaced by the server's `user.message` event with the same text when it arrives, and removed (text and images back in the composer) if the request fails.
|
||||
|
||||
The task switcher (⌘K, or the navbar crumb) lists active tasks with their session state and open inbox item count, live while it is open. Tasks with inbox items come first; within each group, the task least recently visited in this browser comes first (never-visited tasks before all others, ties in the server's order: projects by name, tasks newest first). Visit times are kept in the browser's localStorage under `byre.visits`, stamped when a task is selected and again when it is left. Typing filters them and adds a last row that creates a task with the typed text as its title, in the current task's project (the first project when no task is selected); Enter picks the highlighted row. Picking a task points the right pane at it on the Transcript tab (see "Lead agent").
|
||||
The task switcher (⌘K, or the navbar crumb) lists active tasks with their session state and open inbox item count, live while it is open. Tasks with inbox items come first; within each group, the task least recently visited in this browser comes first (never-visited tasks before all others, ties in the server's order: projects by name, tasks newest first). Visit times are kept in the browser's localStorage under `byre.visits`, stamped when a task is selected and again when it is left. After the tasks come the projects, one row each for its `TODO.md`; picking one points the right pane at the project with no task, on the Todo tab. Typing filters both and adds a last row that creates a task with the typed text as its title, in the right pane's project (the first project when there is none); Enter picks the highlighted row. Picking a task points the right pane at it on the Transcript tab (see "Lead agent").
|
||||
|
||||
## Lead agent
|
||||
|
||||
@@ -108,13 +108,14 @@ Each user has one long-running lead agent, shown in the left pane. It is not tie
|
||||
|
||||
- **Sessions.** A lead session is an `agent_sessions` row with `owner` set to the user's login, `role` `lead` and no `task_id`. Its events carry `owner` and no project or task. It runs in `leads/<login>` with `leadGuidance` (passed and re-attached the same way as a task agent's guidance) and the lead tools instead of the task tools. It has no Stop-hook bounce, no snapshots, no end-of-turn commit and no inbox report; a turn that ends in error records the error on its `session.state` event, which the pane shows as a note. Its turns don't count against the caps.
|
||||
- **Messages.** `POST /api/me/lead/messages` sends the caller's message to their lead, starting a lead session when there is none. While the lead is mid-turn the message is held in `lead_inputs` and becomes its next turn; the composer stays enabled. `GET /api/me/lead/sessions` lists the caller's lead sessions; interrupt and compact use the usual session endpoints.
|
||||
- **Signals.** Every published event goes through `Manager.Observe`. These become signals in `lead_inputs` for every user who has a lead session: `inbox.created` (a question with its choices and recommendation, a report's text cut to 1500 characters, an error or rate limit), `inbox.updated` when a human answered a question or error, `task.merged` and `task.discarded`. `focus.updated` signals only its owner's lead. A signal caused by the lead itself (actor `<login> (lead)`) is skipped, as is the update from an answer it gave. Whenever the lead is idle, byre sends the oldest held message from the human as its own turn, or else every held signal as one `byre` message headed "Signals since your last turn (n)".
|
||||
- **Signals.** Every published event goes through `Manager.Observe`. These become signals in `lead_inputs` for every user who has a lead session: `inbox.created` (a question with its choices and recommendation, a report's text cut to 1500 characters, an error or rate limit), `inbox.updated` when a human answered a question or error, `task.merged` and `task.discarded`. The human switching the right pane is not a signal. A signal caused by the lead itself (actor `<login> (lead)`) is skipped, as is the update from an answer it gave. Whenever the lead is idle, byre sends the oldest held message from the human as its own turn, or else every held signal as one `byre` message headed "Signals since your last turn (n)". Its guidance tells it not to respond to the human's own actions, and to make its final message exactly `NO_REPLY` when a batch needs nothing from it. A transcript (`quietReply` in `web/src/task.ts`) never draws an `agent.message` that is exactly `NO_REPLY`, hides the live partial message while its text is still a prefix of it, and when that turn ends folds it into one collapsed row, "byre <the turn's first line> · nothing to say", that expands to the signals and anything the lead did.
|
||||
- **Tools.** `list_tasks`, `read_transcript` (a task's events in brief, the last `limit` or those after `after_seq`), `read_inbox`, `answer_question` (an `option`, `other` or `dismiss` response recorded as the user, through the same checks as `POST /api/inbox/responses`), `send_message` (starts the task's agent if it has none; refused while it is mid-turn), `create_task`, `start_todo`, `interrupt`, `compact_task`, `merge_task`, `discard_task`, `show` and `compact_self`. The lead also has Claude Code's own tools and reads worktrees by path. Messages it sends task agents have actor `<login> (lead)`, so they don't make the user a participant; tasks, merges, discards and answers are done as the user. Its guidance says to answer a question only when the answer is clear or the human gave it, and to merge or discard only when the human asks.
|
||||
- **Focus.** `users.focus` holds what the user's right pane shows: `task_id`, `tabs` (the tabs put first, in order) and `tab` (the selected one). `GET /api/me/focus` reads it; `PUT /api/me/focus` and the lead's `show` set it, refusing an unknown tab or a task that isn't active, and record a `focus.updated` event owned by the user. The page applies the focus on load and on every `focus.updated` it didn't cause itself; the human's own changes (⌘K, the navbar crumb, clicking a tab, a task link, a notification) are sent with `PUT /api/me/focus`, which signals the lead.
|
||||
- **Focus.** `users.focus` holds what the user's right pane shows: `project_id`, `task_id`, `tabs` (the tabs put first, in order) and `tab` (the selected one). With a `task_id`, `project_id` is always that task's project, filled in by the server; with only a `project_id`, the pane shows that project with no task, which only the Todo tab has anything for. `GET /api/me/focus` reads it; `PUT /api/me/focus` and the lead's `show` set it, refusing an unknown tab, a task that isn't active or an unknown project, and record a `focus.updated` event owned by the user. The page applies the focus on load and on every `focus.updated` it didn't cause itself; the human's own changes (⌘K, the navbar crumb, clicking a tab, a task link, a notification) are sent with `PUT /api/me/focus`. The URL follows the focus: `#/task/<id>`, `#/project/<id>`, or `#/` for neither.
|
||||
- **Pane lock.** While the human is working in the right pane, the lead's `show` is refused with an error telling it to say in chat what it would have shown. The page sends `PUT /api/me/pane-focus` with `focused` whenever that changes: true while the browser window has focus and the focused element is inside the right pane. The pane itself is focusable (`tabIndex` -1), so a click anywhere in it counts, not only on inputs and buttons; a re-render that removes the focused element hands focus back to the pane rather than counting as leaving. The page sends false on `pagehide` (with `keepalive`) and resends true when its event stream reconnects. byre keeps the flag in memory per user (`Manager.paneFocused`), so it starts false after a restart until the page resends it.
|
||||
- **Compaction.** The pane's **Compact** button asks the lead for a handoff (`leadHandoffPrompt`). The lead can also call `compact_self` at a logical point; when that turn ends normally, byre sends the same request. The handoff turn ends the session, and a new lead session starts from `leadHandoffPreamble` plus the handoff; held input follows once that turn ends.
|
||||
- **Restarts.** On shutdown, byre marks each running lead session idle with "byre stopped mid-turn" and holds a restart notice for it, so a lead is never left `running` for a build without lead support to find. After a crash, startup resumes a running lead like a task session. Startup then delivers any held input.
|
||||
|
||||
The UI is two panes: the left is the lead's transcript and composer (the same view as a task transcript, with only the Compact button in its header), the right shows the focused task with the Transcript, Changes, Files, Inbox and Todo tabs, the lead's `tabs` first.
|
||||
The UI is two panes: the left is the lead's transcript and composer (the same view as a task transcript, with only the Compact button in its header), the right shows the focused task, or project with no task, with the Transcript, Changes, Files, Inbox and Todo tabs, the lead's `tabs` first.
|
||||
|
||||
## Todo
|
||||
|
||||
@@ -122,7 +123,7 @@ A project's `TODO.md` is read from the root of its default branch in the project
|
||||
|
||||
The title is the item's identity. `POST /api/projects/{id}/todo/tasks` with a `title` re-reads `TODO.md`, refuses an unknown title (404) or one an active task already has (409), creates a task from the default branch with that title and `todo_item` (the `tasks.todo_item` column, empty for other tasks), and starts its session as the requesting user with `todoPrompt` (`internal/agents/tasks.go`): the item as written, and an instruction to remove it from `TODO.md` in the worktree when the work is done. The agent's guidance names the item too (`taskGuidance`, see "Agent sessions"), so the instruction survives compaction and resumes. The agent removes the item on its own branch, so the removal lands on the default branch in the same squash commit as the work.
|
||||
|
||||
The **Todo** tab lists the items of the selected task's project, grouped by section, with each body rendered as Markdown. An item with an active task whose `todo_item` equals its title shows that task's session state as a link to the task instead of a **Start** button. **Start** posts the item with the composer's default model and points the right pane at the new task's transcript. The tab fetches the project's `TODO.md` when it opens, when the selected task moves to another project, when the project list changes, when the SSE stream reconnects, and on `task.merged`; a change pushed to the default branch outside byre shows the next time one of those happens.
|
||||
The **Todo** tab lists the items of the right pane's project (the focused task's, or the project focused with no task), grouped by section, with each body rendered as Markdown. An item with an active task whose `todo_item` equals its title shows that task's session state as a link to the task instead of a **Start** button. **Start** posts the item with the composer's default model and points the right pane at the new task's transcript. The tab fetches the project's `TODO.md` when it opens, when the right pane moves to another project, when the project list changes, when the SSE stream reconnects, and on `task.merged`; a change pushed to the default branch outside byre shows the next time one of those happens.
|
||||
|
||||
## Inbox
|
||||
|
||||
|
||||
+12
-23
@@ -15,8 +15,14 @@ import (
|
||||
)
|
||||
|
||||
const leadGuidance = `You are the human's lead agent in byre, shown in the left pane of byre's web UI. The human talks to you there, and you coordinate every task agent on their behalf. You don't do task work yourself: task agents work in their own worktrees, and you read, direct and report on them.
|
||||
- Signals: byre sends you, as messages from byre, what happens across tasks: questions, turn reports and errors that task agents file, questions the human answered, tasks merged or discarded, and the human switching the right pane. Several may arrive in one message. Messages from the human arrive separately.
|
||||
- Direct the human's attention. The right pane always shows one task; point it with show (the task, the tabs to put first, the tab to select). When something needs the human, show it and say in a sentence or two what it is and what you recommend. Don't move the pane for things that don't need them, and don't pull it away from what they are looking at unless the new thing is more urgent.
|
||||
- Signals: byre sends you, as messages from byre, what happens across tasks: questions, turn reports and errors that task agents file, questions the human answered, tasks merged or discarded. Several may arrive in one message. Messages from the human arrive separately.
|
||||
- Don't respond to what the human just did. A signal about their own action (a question they answered, a task they merged or discarded) tells them nothing new: don't report, confirm or comment on it. Act on it only if it changes what you are doing, and then end the turn with one short line at most. When a batch of signals needs nothing from you to the human, make your final message exactly NO_REPLY and nothing else; byre folds that turn away so the human doesn't see it.
|
||||
- Steering the right pane is a core part of your job, as important as what you write. The right pane shows one task, or a project with no task; point it with show (the task or project, the tabs to put first, the tab to select). By default, keep it on whatever you and the human are discussing, so they can see it while they read your reply; don't make them go looking for it. Call show in the same turn you bring something up:
|
||||
- Discussing, picking or starting TODO items: the project's todo tab (show with project_id when no task of that project is involved).
|
||||
- A question or report that needs the human: that task's inbox tab, and say in a sentence or two what it is and what you recommend.
|
||||
- Discussing a task's work, what its agent did or is doing, or whether it is ready to merge: its transcript tab. A specific file in its worktree: its files tab.
|
||||
- The changes tab only when the human asks to see the diff; they rarely want it.
|
||||
- Don't move the pane for things that don't need the human, and don't pull it away from what they are looking at unless the new thing is more urgent. While the human is working in the right pane, show is refused: say in chat what you would have shown, and carry on.
|
||||
- Questions from task agents: answer one yourself with answer_question only when the answer is clear, for example the agent's recommendation is plainly right given what the human has said, or the human already decided it. Otherwise show it to the human with your recommendation and wait. When the human tells you an answer, give it with answer_question. Say which questions you answered and how.
|
||||
- Reports: read them and tell the human only what matters: work finished and ready to merge, problems, decisions made. Stay quiet about routine progress.
|
||||
- Dig into context with list_tasks, read_transcript and read_inbox, and with your own Read, Grep and Bash on a task's worktree path (from list_tasks). Don't edit files in task worktrees; ask the task's agent with send_message.
|
||||
@@ -245,7 +251,7 @@ func (m *Manager) leadTurnEnded(ctx context.Context, l *live, res claude.Result)
|
||||
|
||||
func (m *Manager) Observe(ev api.Event) {
|
||||
switch ev.Kind {
|
||||
case "inbox.created", "inbox.updated", "task.merged", "task.discarded", "focus.updated":
|
||||
case "inbox.created", "inbox.updated", "task.merged", "task.discarded":
|
||||
default:
|
||||
return
|
||||
}
|
||||
@@ -271,15 +277,9 @@ func (m *Manager) signalLoop() {
|
||||
}
|
||||
|
||||
func (m *Manager) signal(ctx context.Context, ev api.Event) error {
|
||||
owners := []string{}
|
||||
if ev.Owner != nil {
|
||||
owners = append(owners, *ev.Owner)
|
||||
} else {
|
||||
all, err := m.store.LeadOwners(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
owners = all
|
||||
owners, err := m.store.LeadOwners(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
text, err := m.signalText(ctx, ev)
|
||||
@@ -334,17 +334,6 @@ func (m *Manager) signalText(ctx context.Context, ev api.Event) (string, error)
|
||||
|
||||
case "task.discarded":
|
||||
return fmt.Sprintf("%s was discarded by %s.", m.taskLabel(ctx, deref(ev.TaskId)), ev.Actor), nil
|
||||
|
||||
case "focus.updated":
|
||||
f := api.Focus{}
|
||||
err := remarshal(ev.Payload, &f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if f.TaskId == nil {
|
||||
return "The human cleared the right pane.", nil
|
||||
}
|
||||
return fmt.Sprintf("The human switched the right pane to %s, tab %s.", m.taskLabel(ctx, *f.TaskId), f.Tab), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package agents_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -66,8 +67,61 @@ func TestLeadSignalsAndShow(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("get focus: %v", err)
|
||||
}
|
||||
if f.TaskId == nil || *f.TaskId != e.task.Id || f.Tab != api.TabInbox {
|
||||
if f.TaskId == nil || *f.TaskId != e.task.Id || f.ProjectId == nil || *f.ProjectId != e.project.Id || f.Tab != api.TabInbox {
|
||||
t.Fatalf("focus %+v", f)
|
||||
}
|
||||
e.waitFor("lead idle after show", leadIdle)
|
||||
|
||||
m.SetPaneFocus(login, true)
|
||||
_, err = m.MessageLead(e.ctx, e.user, "Call the show tool with task_id "+e.task.Id+", tabs [\"changes\"] and tab \"changes\". Then say done.", "", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("message lead: %v", err)
|
||||
}
|
||||
e.waitFor("show refused", func(ev api.Event) bool {
|
||||
return ev.Kind == "agent.tool_result" && ev.Owner != nil && strings.Contains(fmt.Sprint(ev.Payload), "working in the right pane")
|
||||
})
|
||||
e.waitFor("lead idle after refused show", leadIdle)
|
||||
f, err = e.store.GetFocus(e.ctx, login)
|
||||
if err != nil {
|
||||
t.Fatalf("get focus: %v", err)
|
||||
}
|
||||
if f.Tab != api.TabInbox {
|
||||
t.Fatalf("focus moved while the pane was focused: %+v", f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFocusProject(t *testing.T) {
|
||||
t.Parallel()
|
||||
e := newEnv(t)
|
||||
m := e.manager()
|
||||
|
||||
err := m.SetFocus(e.ctx, login, api.Focus{ProjectId: &e.project.Id, Tabs: []api.TaskTab{api.TabTodo}, Tab: api.TabTodo}, login)
|
||||
if err != nil {
|
||||
t.Fatalf("focus project: %v", err)
|
||||
}
|
||||
f, err := e.store.GetFocus(e.ctx, login)
|
||||
if err != nil {
|
||||
t.Fatalf("get focus: %v", err)
|
||||
}
|
||||
if f.TaskId != nil || f.ProjectId == nil || *f.ProjectId != e.project.Id || f.Tab != api.TabTodo {
|
||||
t.Fatalf("focus %+v", f)
|
||||
}
|
||||
|
||||
other := "nope"
|
||||
err = m.SetFocus(e.ctx, login, api.Focus{TaskId: &e.task.Id, ProjectId: &other, Tabs: []api.TaskTab{}, Tab: api.TabTranscript}, login)
|
||||
if err != nil {
|
||||
t.Fatalf("focus task: %v", err)
|
||||
}
|
||||
f, err = e.store.GetFocus(e.ctx, login)
|
||||
if err != nil {
|
||||
t.Fatalf("get focus: %v", err)
|
||||
}
|
||||
if f.TaskId == nil || *f.TaskId != e.task.Id || f.ProjectId == nil || *f.ProjectId != e.project.Id {
|
||||
t.Fatalf("focus %+v", f)
|
||||
}
|
||||
|
||||
err = m.SetFocus(e.ctx, login, api.Focus{ProjectId: &other, Tabs: []api.TaskTab{}, Tab: api.TabTodo}, login)
|
||||
if err == nil {
|
||||
t.Fatal("focus on an unknown project was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,10 +292,12 @@ func (m *Manager) leadTools(l *live) []mcp.Tool {
|
||||
}),
|
||||
|
||||
tool("show",
|
||||
"Point the human's right pane at a task: tabs are the tabs to put first, in order (the rest follow), and tab is the one to select. "+
|
||||
"Tabs: transcript, changes, files, inbox (the task's open items), todo (the project's TODO.md). Omit task_id to clear the pane.",
|
||||
"Point the human's right pane at a task, or at a project with no task: tabs are the tabs to put first, in order (the rest follow), and tab is the one to select. "+
|
||||
"Tabs: transcript, changes, files, inbox (the task's open items), todo (the project's TODO.md); with only project_id, only todo shows anything. "+
|
||||
"Omit both task_id and project_id to clear the pane. Refused while the human is working in the right pane.",
|
||||
`{"type": "object", "properties": {
|
||||
"task_id": {"type": "string"},
|
||||
"project_id": {"type": "string", "description": "Ignored when task_id is given."},
|
||||
"tabs": {"type": "array", "items": {"type": "string", "enum": ["transcript", "changes", "files", "inbox", "todo"]}},
|
||||
"tab": {"type": "string", "enum": ["transcript", "changes", "files", "inbox", "todo"]}
|
||||
}, "required": ["tabs", "tab"]}`,
|
||||
@@ -305,6 +307,12 @@ func (m *Manager) leadTools(l *live) []mcp.Tool {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
m.mu.Lock()
|
||||
focused := m.paneFocused[user.Login]
|
||||
m.mu.Unlock()
|
||||
if focused {
|
||||
return "", fmt.Errorf("%w: the human is working in the right pane, so it was left as it is; tell them in chat instead", ErrConflict)
|
||||
}
|
||||
err = m.SetFocus(ctx, user.Login, f, leadActor(user.Login))
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -333,15 +341,30 @@ func (m *Manager) SetFocus(ctx context.Context, login string, f api.Focus, actor
|
||||
if f.TaskId != nil && *f.TaskId == "" {
|
||||
f.TaskId = nil
|
||||
}
|
||||
if f.ProjectId != nil && *f.ProjectId == "" {
|
||||
f.ProjectId = nil
|
||||
}
|
||||
if f.TaskId != nil {
|
||||
_, _, err := m.activeTask(ctx, *f.TaskId)
|
||||
task, _, err := m.activeTask(ctx, *f.TaskId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.ProjectId = &task.ProjectId
|
||||
} else if f.ProjectId != nil {
|
||||
_, err := m.store.GetProject(ctx, *f.ProjectId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("project %s: %w", *f.ProjectId, err)
|
||||
}
|
||||
}
|
||||
return m.store.SetFocus(ctx, login, f, actor)
|
||||
}
|
||||
|
||||
func (m *Manager) SetPaneFocus(login string, focused bool) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.paneFocused[login] = focused
|
||||
}
|
||||
|
||||
func (m *Manager) modelOr(model string, l *live) string {
|
||||
if model != "" {
|
||||
return model
|
||||
|
||||
@@ -53,6 +53,7 @@ type Manager struct {
|
||||
rateLimits map[api.AgentSessionVendor]map[string]any
|
||||
merges map[string]string
|
||||
leadAnswered map[string]bool
|
||||
paneFocused map[string]bool
|
||||
|
||||
deliverMu sync.Mutex
|
||||
capMu sync.Mutex
|
||||
@@ -100,6 +101,7 @@ func New(st *store.Store, limits Limits, worktreeRoot, leadRoot string) *Manager
|
||||
rateLimits: map[api.AgentSessionVendor]map[string]any{},
|
||||
merges: map[string]string{},
|
||||
leadAnswered: map[string]bool{},
|
||||
paneFocused: map[string]bool{},
|
||||
signals: make(chan api.Event, 1024),
|
||||
snaps: map[string]*snapState{},
|
||||
bad: newBadLines(),
|
||||
|
||||
+166
-135
@@ -539,7 +539,9 @@ type FileDiff struct {
|
||||
|
||||
// Focus defines model for Focus.
|
||||
type Focus struct {
|
||||
Tab TaskTab `json:"tab"`
|
||||
// ProjectId The project the right pane shows; the task's project when task_id is set
|
||||
ProjectId *string `json:"project_id,omitempty"`
|
||||
Tab TaskTab `json:"tab"`
|
||||
|
||||
// Tabs The tabs the lead put first, in order. The other task tabs follow them.
|
||||
Tabs []TaskTab `json:"tabs"`
|
||||
@@ -641,6 +643,12 @@ type NotificationPrefs struct {
|
||||
Reports bool `json:"reports"`
|
||||
}
|
||||
|
||||
// PaneFocus defines model for PaneFocus.
|
||||
type PaneFocus struct {
|
||||
// Focused True while the browser window has focus and the focused element is inside the right pane.
|
||||
Focused bool `json:"focused"`
|
||||
}
|
||||
|
||||
// Participant defines model for Participant.
|
||||
type Participant struct {
|
||||
DisplayName string `json:"display_name"`
|
||||
@@ -971,6 +979,9 @@ type MessageLeadJSONRequestBody = MessageCreate
|
||||
// UpdateNotificationPrefsJSONRequestBody defines body for UpdateNotificationPrefs for application/json ContentType.
|
||||
type UpdateNotificationPrefsJSONRequestBody = NotificationPrefs
|
||||
|
||||
// SetPaneFocusJSONRequestBody defines body for SetPaneFocus for application/json ContentType.
|
||||
type SetPaneFocusJSONRequestBody = PaneFocus
|
||||
|
||||
// AddPushSubscriptionJSONRequestBody defines body for AddPushSubscription for application/json ContentType.
|
||||
type AddPushSubscriptionJSONRequestBody = PushSubscription
|
||||
|
||||
@@ -1034,6 +1045,9 @@ type ServerInterface interface {
|
||||
// (PUT /api/me/notifications)
|
||||
UpdateNotificationPrefs(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// (PUT /api/me/pane-focus)
|
||||
SetPaneFocus(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// (DELETE /api/me/push-subscriptions)
|
||||
RemovePushSubscription(w http.ResponseWriter, r *http.Request, params RemovePushSubscriptionParams)
|
||||
|
||||
@@ -1462,6 +1476,20 @@ func (siw *ServerInterfaceWrapper) UpdateNotificationPrefs(w http.ResponseWriter
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// SetPaneFocus operation middleware
|
||||
func (siw *ServerInterfaceWrapper) SetPaneFocus(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.SetPaneFocus(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// RemovePushSubscription operation middleware
|
||||
func (siw *ServerInterfaceWrapper) RemovePushSubscription(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -2478,6 +2506,7 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H
|
||||
m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/me/lead/messages", wrapper.MessageLead)
|
||||
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/me/focus", wrapper.GetFocus)
|
||||
m.HandleFunc(http.MethodPut+" "+options.BaseURL+"/api/me/focus", wrapper.SetFocus)
|
||||
m.HandleFunc(http.MethodPut+" "+options.BaseURL+"/api/me/pane-focus", wrapper.SetPaneFocus)
|
||||
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/push/key", wrapper.GetPushKey)
|
||||
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/projects", wrapper.ListProjects)
|
||||
m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/projects", wrapper.CreateProject)
|
||||
@@ -2521,140 +2550,142 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H
|
||||
// const string: with thousands of chunks the chained `+` fold is several
|
||||
// times slower for the Go compiler than parsing a slice literal.
|
||||
var swaggerSpec = []string{
|
||||
"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",
|
||||
}
|
||||
|
||||
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
|
||||
|
||||
@@ -80,3 +80,12 @@ func (s *Server) SetFocus(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
writeJSON(w, http.StatusOK, f)
|
||||
}
|
||||
|
||||
func (s *Server) SetPaneFocus(w http.ResponseWriter, r *http.Request) {
|
||||
p := api.PaneFocus{}
|
||||
if !readJSON(w, r, &p) {
|
||||
return
|
||||
}
|
||||
s.agents.SetPaneFocus(actor(r), p.Focused)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
@@ -244,6 +244,10 @@ CREATE TABLE lead_inputs (
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX lead_inputs_owner ON lead_inputs(owner, id);
|
||||
`}, {"add-focus-project", `
|
||||
UPDATE users SET focus = json_set(focus, '$.project_id', (SELECT project_id FROM tasks WHERE id = json_extract(users.focus, '$.task_id')))
|
||||
WHERE json_extract(focus, '$.task_id') IS NOT NULL;
|
||||
DELETE FROM lead_inputs WHERE text LIKE 'The human switched the right pane %' OR text = 'The human cleared the right pane.';
|
||||
`}}
|
||||
|
||||
type Store struct {
|
||||
|
||||
Vendored
+1
-1
@@ -23,7 +23,7 @@ export async function request(method, path, opts = {}) {
|
||||
if (query.size > 0) {
|
||||
url += `?${query}`;
|
||||
}
|
||||
const init = { method: method.toUpperCase() };
|
||||
const init = { method: method.toUpperCase(), keepalive: opts.keepalive ?? false };
|
||||
if (opts.body instanceof Blob) {
|
||||
init.headers = { "Content-Type": opts.body.type || "application/octet-stream" };
|
||||
init.body = opts.body;
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAgDA,MAAM,OAAO,QAAS,SAAQ,KAAK;IACZ;IAArB,YAAqB,MAAc,EAAE,OAAe;QAClD,KAAK,CAAC,OAAO,CAAC,CAAC;QADI,WAAM,GAAN,MAAM,CAAQ;IAEnC,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,MAAS,EACT,IAAO,EACP,OAA0B,EAAE;IAE5B,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAA4B,CAAC;IAChE,IAAI,GAAG,GAAW,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE,GAAW,EAAE,EAAE;QAC9D,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,0BAA0B,GAAG,QAAQ,IAAI,EAAE,CAAC,CAAC;QAC/D,CAAC;QACD,OAAO,kBAAkB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,IAAI,eAAe,EAAE,CAAC;IACpC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAA4B,CAAC,EAAE,CAAC;QACzF,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IACD,IAAI,KAAK,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QACnB,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC;IACrB,CAAC;IAED,MAAM,IAAI,GAAgB,EAAE,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,KAAK,EAAE,CAAC;IAC/F,IAAI,IAAI,CAAC,IAAI,YAAY,IAAI,EAAE,CAAC;QAC9B,IAAI,CAAC,OAAO,GAAG,EAAE,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,0BAA0B,EAAE,CAAC;QAChF,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACxB,CAAC;SAAM,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACnC,IAAI,CAAC,OAAO,GAAG,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC;QACtD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACnC,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QACvB,OAAO,SAA6B,CAAC;IACvC,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACrE,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACvC,MAAM,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC9F,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAG,IAAyB,CAAC,OAAO,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,IAAwB,CAAC;AAClC,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG,CAA6B,IAAO,EAAE,IAA4B,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AACrH,MAAM,CAAC,MAAM,IAAI,GAAG,CAA8B,IAAO,EAAE,IAA6B,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AACzH,MAAM,CAAC,MAAM,GAAG,GAAG,CAA6B,IAAO,EAAE,IAA4B,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AACrH,MAAM,CAAC,MAAM,GAAG,GAAG,CAAgC,IAAO,EAAE,IAA+B,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAE9H,MAAM,YAAY,GAAG,MAAM,CAAC;AAC5B,MAAM,YAAY,GAAG,KAAK,CAAC;AAE3B,MAAM,UAAU,SAAS,CACvB,KAAa,EACb,MAA4C,EAC5C,OAAgC,EAChC,OAAqC;IAErC,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,IAAI,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,MAA+B,CAAC;IACpC,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,MAAM,EAAE,KAAK,EAAE,CAAC;QAChB,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACnB,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3D,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QACvC,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAChB,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC5B,CAAC;QACD,MAAM,CAAC,GAAG,IAAI,WAAW,CAAC,eAAe,KAAK,EAAE,CAAC,CAAC;QAClD,MAAM,GAAG,CAAC,CAAC;QACX,CAAC,CAAC,MAAM,GAAG,GAAG,EAAE;YACd,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC,CAAC;QACF,CAAC,CAAC,OAAO,GAAG,GAAG,EAAE;YACf,OAAO,CAAC,KAAK,CAAC,CAAC;YACf,IAAI,CAAC,CAAC,UAAU,KAAK,WAAW,CAAC,MAAM,EAAE,CAAC;gBACxC,KAAK,GAAG,UAAU,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC,CAAC;QACF,CAAC,CAAC,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE;YAC9B,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACrB,CAAC,CAAC,CAAC;QACH,CAAC,CAAC,SAAS,GAAG,CAAC,GAAyB,EAAE,EAAE;YAC1C,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACnB,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAc,CAAC;YAC7C,IAAI,EAAE,CAAC,GAAG,GAAG,IAAI,EAAE,CAAC;gBAClB,IAAI,GAAG,EAAE,CAAC,GAAG,CAAC;YAChB,CAAC;YACD,OAAO,CAAC,EAAE,CAAC,CAAC;QACd,CAAC,CAAC;IACJ,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,YAAY,EAAE,CAAC;YACtC,OAAO;QACT,CAAC;QACD,OAAO,CAAC,KAAK,CAAC,CAAC;QACf,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,GAAG,EAAE;QACrB,IAAI,QAAQ,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;YAC3C,KAAK,EAAE,CAAC;QACV,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC3C,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACpC,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;IACzD,OAAO,EAAE,CAAC;IACV,OAAO,GAAG,EAAE;QACV,aAAa,CAAC,QAAQ,CAAC,CAAC;QACxB,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,mBAAmB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACvC,QAAQ,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;QAC5D,MAAM,EAAE,KAAK,EAAE,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC"}
|
||||
Vendored
+11
-5
@@ -31,10 +31,16 @@ function identityBanner(root) {
|
||||
render();
|
||||
}
|
||||
function route() {
|
||||
const match = /^#\/task\/(\w+)$/.exec(location.hash);
|
||||
const taskId = match?.[1];
|
||||
if (taskId !== state.focus.task_id) {
|
||||
setFocus({ task_id: taskId ?? null }).catch((err) => console.error("set focus", err));
|
||||
const match = /^#\/(task|project)\/(\w+)$/.exec(location.hash);
|
||||
const taskId = match?.[1] === "task" ? match[2] : undefined;
|
||||
const projectId = match?.[1] === "project" ? match[2] : undefined;
|
||||
if (taskId) {
|
||||
if (taskId !== state.focus.task_id) {
|
||||
setFocus({ task_id: taskId }).catch((err) => console.error("set focus", err));
|
||||
}
|
||||
}
|
||||
else if (state.focus.task_id || projectId !== state.focus.project_id) {
|
||||
setFocus({ task_id: null, project_id: projectId ?? null }).catch((err) => console.error("set focus", err));
|
||||
}
|
||||
if (taskId !== state.selectedTaskId) {
|
||||
selectTask(taskId);
|
||||
@@ -47,7 +53,7 @@ async function main() {
|
||||
await Promise.all([loadMe(), loadProjects(), loadInbox(), loadLeadSessions()]);
|
||||
const linked = location.hash;
|
||||
await loadFocus();
|
||||
if (/^#\/task\/\w+$/.test(linked)) {
|
||||
if (/^#\/(task|project)\/\w+$/.test(linked)) {
|
||||
location.hash = linked;
|
||||
}
|
||||
startLeadStream();
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+15
-6
@@ -1,7 +1,7 @@
|
||||
import { post } from "./api.js";
|
||||
import { errorText, h, replace, showAlert } from "./dom.js";
|
||||
import { openSettings } from "./settings.js";
|
||||
import { activeTasks, currentSession, findTask, focusTask, lastVisits, onEach, state } from "./state.js";
|
||||
import { activeTasks, currentSession, findTask, focusedProject, focusProject, focusTask, lastVisits, onEach, state } from "./state.js";
|
||||
import { openStatus } from "./status.js";
|
||||
export function mountNavbar(root) {
|
||||
const crumb = h("button", { class: "crumb", title: "Switch task (⌘K)" });
|
||||
@@ -15,14 +15,14 @@ export function mountNavbar(root) {
|
||||
const me = h("span", { class: "me" });
|
||||
const render = () => {
|
||||
const task = state.selectedTaskId ? findTask(state.selectedTaskId) : undefined;
|
||||
const project = task ? state.projects.find((p) => p.id === task.project_id) : undefined;
|
||||
replace(crumb, task ? `${project?.name ?? ""} ▸ ${task.title}` : "No task", " ▾");
|
||||
const project = focusedProject();
|
||||
replace(crumb, task ? `${project?.name ?? ""} ▸ ${task.title}` : (project?.name ?? "No task"), " ▾");
|
||||
conn.className = state.connected ? "connection up" : "connection down";
|
||||
conn.title = state.connected ? "live" : "reconnecting";
|
||||
connLabel.hidden = state.connected;
|
||||
replace(me, state.me?.display_name ?? "");
|
||||
};
|
||||
onEach(["me", "projects", "tasks", "selection", "connection"], render);
|
||||
onEach(["me", "projects", "tasks", "selection", "focus", "connection"], render);
|
||||
replace(root, h("a", { href: "#/", class: "brand" }, "byre"), crumb, h("span", { class: "spacer" }), status, settings, connLabel, conn, me);
|
||||
render();
|
||||
addEventListener("keydown", (e) => {
|
||||
@@ -86,8 +86,17 @@ function openSwitcher() {
|
||||
const el = h("li", {}, h("span", { class: `dot ${sess?.state ?? "none"}`, title: sess?.state ?? "no session" }), h("span", { class: "task-title" }, h("span", { class: "project-name" }, project.name), " ", task.title), count > 0 && h("span", { class: "badge", title: "inbox items" }, count), h("span", { class: "task-state" }, sess?.state ?? "no agent"));
|
||||
return { el, pick: () => go(task.id) };
|
||||
});
|
||||
const current = state.selectedTaskId ? findTask(state.selectedTaskId) : undefined;
|
||||
const project = state.projects.find((p) => p.id === current?.project_id) ?? state.projects[0];
|
||||
for (const p of state.projects.filter((p) => `${p.name} todo`.toLowerCase().includes(q))) {
|
||||
const el = h("li", {}, h("span", { class: "dot none" }), h("span", { class: "task-title" }, h("span", { class: "project-name" }, p.name), " TODO.md"), h("span", { class: "task-state" }, "project"));
|
||||
rows.push({
|
||||
el,
|
||||
pick: () => {
|
||||
close();
|
||||
focusProject(p.id);
|
||||
},
|
||||
});
|
||||
}
|
||||
const project = focusedProject() ?? state.projects[0];
|
||||
if (title && project) {
|
||||
const el = h("li", { class: "new-task" }, h("span", { class: "task-title" }, "New task in ", h("span", { class: "project-name" }, project.name), ": ", title));
|
||||
rows.push({ el, pick: () => void create(project.id, title) });
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+44
-1
@@ -1,3 +1,4 @@
|
||||
import { put } from "./api.js";
|
||||
import { mountChanges } from "./changes.js";
|
||||
import { h, replace } from "./dom.js";
|
||||
import { mountFiles } from "./files.js";
|
||||
@@ -43,6 +44,48 @@ export function taskPane() {
|
||||
};
|
||||
on("focus", render);
|
||||
render();
|
||||
return h("section", { class: "pane" }, strip, body);
|
||||
const pane = h("section", { class: "pane", tabIndex: -1 }, strip, body);
|
||||
trackPaneFocus(pane);
|
||||
return pane;
|
||||
}
|
||||
function trackPaneFocus(pane) {
|
||||
let focused = false;
|
||||
let inside = null;
|
||||
let sending = Promise.resolve();
|
||||
const send = () => {
|
||||
const body = { focused };
|
||||
sending = sending.then(() => put("/api/me/pane-focus", { body })).catch((err) => console.error("set pane focus", err));
|
||||
};
|
||||
const update = () => {
|
||||
setTimeout(() => {
|
||||
if (focused && inside && !inside.isConnected && document.activeElement === document.body) {
|
||||
// A re-render removed the focused element (a tab button, a list row); that isn't the human leaving.
|
||||
pane.focus({ preventScroll: true });
|
||||
}
|
||||
const active = document.activeElement;
|
||||
const now = document.hasFocus() && pane.contains(active);
|
||||
inside = now ? active : null;
|
||||
if (now === focused) {
|
||||
return;
|
||||
}
|
||||
focused = now;
|
||||
send();
|
||||
});
|
||||
};
|
||||
on("connection", () => {
|
||||
if (state.connected && focused) {
|
||||
send();
|
||||
}
|
||||
});
|
||||
document.addEventListener("focusin", update);
|
||||
document.addEventListener("focusout", update);
|
||||
addEventListener("focus", update);
|
||||
addEventListener("blur", update);
|
||||
addEventListener("pagehide", () => {
|
||||
if (focused) {
|
||||
focused = false;
|
||||
void put("/api/me/pane-focus", { body: { focused: false }, keepalive: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=panes.js.map
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"version":3,"file":"panes.js","sourceRoot":"","sources":["../src/panes.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"panes.js","sourceRoot":"","sources":["../src/panes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAgB,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,KAAK,GAAiF;IAC1F,UAAU,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe,EAAE;IAC3D,OAAO,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE;IAClE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE;IAC5C,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE;IAC5C,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE;CAC1C,CAAC;AAEF,MAAM,UAAU,QAAQ;IACtB,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACnD,SAAS,CAAC,IAAI,CAAC,CAAC;IAChB,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;AAC7J,CAAC;AAED,MAAM,UAAU,QAAQ;IACtB,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;IAC9C,IAAI,KAA0B,CAAC;IAC/B,IAAI,OAAO,GAAe,GAAG,EAAE,GAAE,CAAC,CAAC;IAEnC,MAAM,MAAM,GAAG,GAAG,EAAE;QAClB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/F,MAAM,KAAK,GAAG,CAAC,GAAG,KAAK,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACpE,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;QAC5B,OAAO,CACL,KAAK,EACL,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACjB,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC5E,CAAC,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAC9B,OAAO,CAAC,CAAC;QACX,CAAC,CAAC,CACH,CAAC;QACF,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QACD,OAAO,EAAE,CAAC;QACV,KAAK,GAAG,GAAG,CAAC;QACZ,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,aAAa,GAAG,EAAE,EAAE,CAAC,CAAC;QACrD,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACpB,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC,CAAC;IAEF,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACpB,MAAM,EAAE,CAAC;IACT,MAAM,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IACxE,cAAc,CAAC,IAAI,CAAC,CAAC;IACrB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,cAAc,CAAC,IAAiB;IACvC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,MAAM,GAAmB,IAAI,CAAC;IAClC,IAAI,OAAO,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;IAClD,MAAM,IAAI,GAAG,GAAG,EAAE;QAChB,MAAM,IAAI,GAAG,EAAE,OAAO,EAAE,CAAC;QACzB,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,oBAAoB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC,CAAC;IAClI,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,GAAG,EAAE;QAClB,UAAU,CAAC,GAAG,EAAE;YACd,IAAI,OAAO,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,QAAQ,CAAC,aAAa,KAAK,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACzF,oGAAoG;gBACpG,IAAI,CAAC,KAAK,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;YACtC,CAAC;YACD,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC;YACtC,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YACzD,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;YAC7B,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;gBACpB,OAAO;YACT,CAAC;YACD,OAAO,GAAG,GAAG,CAAC;YACd,IAAI,EAAE,CAAC;QACT,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IACF,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE;QACpB,IAAI,KAAK,CAAC,SAAS,IAAI,OAAO,EAAE,CAAC;YAC/B,IAAI,EAAE,CAAC;QACT,CAAC;IACH,CAAC,CAAC,CAAC;IACH,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC7C,QAAQ,CAAC,gBAAgB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC9C,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAClC,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,gBAAgB,CAAC,UAAU,EAAE,GAAG,EAAE;QAChC,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,GAAG,KAAK,CAAC;YAChB,KAAK,GAAG,CAAC,oBAAoB,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAChF,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
|
||||
Vendored
+11
-1
@@ -94,24 +94,34 @@ export async function loadFocus() {
|
||||
}
|
||||
export function applyFocus(f) {
|
||||
state.focus = f;
|
||||
const hash = f.task_id ? `#/task/${f.task_id}` : "#/";
|
||||
const hash = f.task_id ? `#/task/${f.task_id}` : f.project_id ? `#/project/${f.project_id}` : "#/";
|
||||
if (location.hash !== hash && !(hash === "#/" && location.hash === "")) {
|
||||
location.hash = hash;
|
||||
}
|
||||
notify("focus");
|
||||
}
|
||||
export function focusedProject() {
|
||||
return state.projects.find((p) => p.id === state.focus.project_id);
|
||||
}
|
||||
export async function setFocus(change) {
|
||||
const taskId = change.task_id === undefined ? state.focus.task_id : (change.task_id ?? undefined);
|
||||
const projectId = taskId ? findTask(taskId)?.project_id : change.project_id === undefined ? state.focus.project_id : (change.project_id ?? undefined);
|
||||
const next = { tabs: state.focus.tabs, tab: change.tab ?? state.focus.tab };
|
||||
if (taskId) {
|
||||
next.task_id = taskId;
|
||||
}
|
||||
if (projectId) {
|
||||
next.project_id = projectId;
|
||||
}
|
||||
applyFocus(next);
|
||||
applyFocus(await put("/api/me/focus", { body: next }));
|
||||
}
|
||||
export function focusTask(taskId, tab = "transcript") {
|
||||
setFocus({ task_id: taskId, tab }).catch((err) => console.error("set focus", err));
|
||||
}
|
||||
export function focusProject(projectId) {
|
||||
setFocus({ task_id: null, project_id: projectId, tab: "todo" }).catch((err) => console.error("set focus", err));
|
||||
}
|
||||
export function focusTab(tab) {
|
||||
setFocus({ tab }).catch((err) => console.error("set focus", err));
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+17
@@ -3,6 +3,7 @@ import { ago, errorText, h, replace, showAlert, showConfirm } from "./dom.js";
|
||||
import { markdown } from "./markdown.js";
|
||||
import { currentSession, findTask, focusTab, onEach, pinSnapshot, setFocus, state } from "./state.js";
|
||||
export const models = ["claude-opus-5-5", "claude-sonnet-5-5", "claude-haiku-5-5", "claude-fable-5-1"];
|
||||
const quietReply = "NO_REPLY";
|
||||
const taskSource = {
|
||||
key: () => state.selectedTaskId,
|
||||
events: () => state.transcript,
|
||||
@@ -157,6 +158,7 @@ export function mountChat(root, src, makeHead) {
|
||||
unplaced: [],
|
||||
lastMessage: undefined,
|
||||
turnStart: undefined,
|
||||
quiet: false,
|
||||
root: src.root(),
|
||||
taskId: src.taskId(),
|
||||
watchReport: (el, itemId) => {
|
||||
@@ -335,6 +337,7 @@ export function mountChat(root, src, makeHead) {
|
||||
}
|
||||
stream.timer = 0;
|
||||
const pinned = transcript.scrollHeight - transcript.scrollTop - transcript.clientHeight < 80;
|
||||
stream.el.hidden = quietReply.startsWith(stream.text.trim());
|
||||
replace(stream.el, markdown(stream.text, "text md", { taskId: ctx.taskId }));
|
||||
if (pinned) {
|
||||
transcript.scrollTop = transcript.scrollHeight;
|
||||
@@ -397,6 +400,14 @@ export function mountChat(root, src, makeHead) {
|
||||
ctx.turnStart = undefined;
|
||||
continue;
|
||||
}
|
||||
if (ev.kind === "turn.ended" && ctx.quiet && ctx.turnStart?.parentNode === transcript) {
|
||||
const brief = ctx.turnStart.querySelector(".tool-brief")?.textContent;
|
||||
const label = h("div", {}, brief && h("span", { class: "tool-name" }, "byre"), brief && " ", h("span", { class: "tool-brief" }, brief ? `${brief} · nothing to say` : "nothing to say"));
|
||||
addAnchor(ev.seq, foldTurn(ctx.turnStart, label), true);
|
||||
ctx.turnStart = undefined;
|
||||
ctx.quiet = false;
|
||||
continue;
|
||||
}
|
||||
put(el);
|
||||
if (ev.kind === "user.message") {
|
||||
ctx.turnStart = el;
|
||||
@@ -716,6 +727,7 @@ function renderEvent(ev, ctx) {
|
||||
switch (ev.kind) {
|
||||
case "user.message":
|
||||
ctx.lastMessage = undefined;
|
||||
ctx.quiet = false;
|
||||
if (ev.actor === "byre") {
|
||||
const text = String(p["text"]);
|
||||
const first = text.split("\n")[0] ?? "";
|
||||
@@ -724,6 +736,11 @@ function renderEvent(ev, ctx) {
|
||||
}
|
||||
return h("div", { class: "msg user" }, h("div", { class: "who" }, ev.actor, " ", when), messageImages(p["images"]), String(p["text"] ?? "").trim() ? markdown(String(p["text"]), "text md", md) : null);
|
||||
case "agent.message":
|
||||
if (String(p["text"]).trim() === quietReply) {
|
||||
ctx.quiet = true;
|
||||
return null;
|
||||
}
|
||||
ctx.quiet = false;
|
||||
ctx.lastMessage = h("div", { class: "msg agent" }, markdown(String(p["text"]), "text md", md));
|
||||
return ctx.lastMessage;
|
||||
case "agent.thinking":
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+6
-10
@@ -2,20 +2,16 @@ import { get, post } from "./api.js";
|
||||
import { errorText, h, replace, showAlert } from "./dom.js";
|
||||
import { markdown } from "./markdown.js";
|
||||
import { models } from "./task.js";
|
||||
import { currentSession, findTask, focusTask, on, onEach, state } from "./state.js";
|
||||
import { currentSession, focusedProject, focusTask, on, onEach, state } from "./state.js";
|
||||
const refreshKinds = new Set(["task.merged", "project.created"]);
|
||||
export function mountTodo(root) {
|
||||
const todos = new Map();
|
||||
const starting = new Set();
|
||||
let disposed = false;
|
||||
const project = () => {
|
||||
const task = state.selectedTaskId ? findTask(state.selectedTaskId) : undefined;
|
||||
return state.projects.find((p) => p.id === task?.project_id);
|
||||
};
|
||||
const render = () => {
|
||||
const scrollTop = root.querySelector(".todo-view")?.scrollTop ?? 0;
|
||||
const p = project();
|
||||
replace(root, h("div", { class: "todo-view" }, p ? projectBlock(p, todos.get(p.id), starting, start) : h("div", { class: "empty" }, "No task selected.")));
|
||||
const p = focusedProject();
|
||||
replace(root, h("div", { class: "todo-view" }, p ? projectBlock(p, todos.get(p.id), starting, start) : h("div", { class: "empty" }, "No project selected.")));
|
||||
root.querySelector(".todo-view").scrollTop = scrollTop;
|
||||
};
|
||||
const load = async (projectId) => {
|
||||
@@ -31,7 +27,7 @@ export function mountTodo(root) {
|
||||
}
|
||||
};
|
||||
const refresh = () => {
|
||||
const p = project();
|
||||
const p = focusedProject();
|
||||
if (p) {
|
||||
void load(p.id);
|
||||
}
|
||||
@@ -68,9 +64,9 @@ export function mountTodo(root) {
|
||||
}
|
||||
}),
|
||||
on("projects", refresh),
|
||||
on("selection", () => {
|
||||
onEach(["focus", "selection"], () => {
|
||||
render();
|
||||
const p = project();
|
||||
const p = focusedProject();
|
||||
if (p && !todos.has(p.id)) {
|
||||
void load(p.id);
|
||||
}
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+47
-1
@@ -96,7 +96,7 @@ export interface paths {
|
||||
cookie?: never;
|
||||
};
|
||||
get: operations["getFocus"];
|
||||
/** @description Records the human switching the right pane's task or tab. The lead agent is told. */
|
||||
/** @description Records the human switching the right pane's project, task or tab. */
|
||||
put: operations["setFocus"];
|
||||
post?: never;
|
||||
delete?: never;
|
||||
@@ -105,6 +105,23 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/me/pane-focus": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
/** @description Records whether the human is working in the right pane. While they are, the lead agent can't move it. */
|
||||
put: operations["setPaneFocus"];
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/push/key": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -989,12 +1006,18 @@ export interface components {
|
||||
created_at: string;
|
||||
};
|
||||
Focus: {
|
||||
/** @description The project the right pane shows; the task's project when task_id is set */
|
||||
project_id?: string;
|
||||
/** @description The task the right pane shows; absent for none. */
|
||||
task_id?: string;
|
||||
/** @description The tabs the lead put first, in order. The other task tabs follow them. */
|
||||
tabs: components["schemas"]["TaskTab"][];
|
||||
tab: components["schemas"]["TaskTab"];
|
||||
};
|
||||
PaneFocus: {
|
||||
/** @description True while the browser window has focus and the focused element is inside the right pane. */
|
||||
focused: boolean;
|
||||
};
|
||||
/** @enum {string} */
|
||||
TaskTab: "transcript" | "changes" | "files" | "inbox" | "todo";
|
||||
Status: {
|
||||
@@ -1307,6 +1330,29 @@ export interface operations {
|
||||
404: components["responses"]["Error"];
|
||||
};
|
||||
};
|
||||
setPaneFocus: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["PaneFocus"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Recorded. */
|
||||
204: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
400: components["responses"]["Error"];
|
||||
};
|
||||
};
|
||||
getPushKey: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
+2
-1
@@ -43,6 +43,7 @@ type Options<O> = {
|
||||
path?: PathParams<O>;
|
||||
query?: QueryParams<O>;
|
||||
body?: Body<O>;
|
||||
keepalive?: boolean;
|
||||
};
|
||||
|
||||
export class ApiError extends Error {
|
||||
@@ -75,7 +76,7 @@ export async function request<M extends Method, P extends PathsWith<M>>(
|
||||
url += `?${query}`;
|
||||
}
|
||||
|
||||
const init: RequestInit = { method: method.toUpperCase() };
|
||||
const init: RequestInit = { method: method.toUpperCase(), keepalive: opts.keepalive ?? false };
|
||||
if (opts.body instanceof Blob) {
|
||||
init.headers = { "Content-Type": opts.body.type || "application/octet-stream" };
|
||||
init.body = opts.body;
|
||||
|
||||
+10
-5
@@ -32,10 +32,15 @@ function identityBanner(root: HTMLElement): void {
|
||||
}
|
||||
|
||||
function route(): void {
|
||||
const match = /^#\/task\/(\w+)$/.exec(location.hash);
|
||||
const taskId = match?.[1];
|
||||
if (taskId !== state.focus.task_id) {
|
||||
setFocus({ task_id: taskId ?? null }).catch((err: unknown) => console.error("set focus", err));
|
||||
const match = /^#\/(task|project)\/(\w+)$/.exec(location.hash);
|
||||
const taskId = match?.[1] === "task" ? match[2] : undefined;
|
||||
const projectId = match?.[1] === "project" ? match[2] : undefined;
|
||||
if (taskId) {
|
||||
if (taskId !== state.focus.task_id) {
|
||||
setFocus({ task_id: taskId }).catch((err: unknown) => console.error("set focus", err));
|
||||
}
|
||||
} else if (state.focus.task_id || projectId !== state.focus.project_id) {
|
||||
setFocus({ task_id: null, project_id: projectId ?? null }).catch((err: unknown) => console.error("set focus", err));
|
||||
}
|
||||
if (taskId !== state.selectedTaskId) {
|
||||
selectTask(taskId);
|
||||
@@ -50,7 +55,7 @@ async function main(): Promise<void> {
|
||||
await Promise.all([loadMe(), loadProjects(), loadInbox(), loadLeadSessions()]);
|
||||
const linked = location.hash;
|
||||
await loadFocus();
|
||||
if (/^#\/task\/\w+$/.test(linked)) {
|
||||
if (/^#\/(task|project)\/\w+$/.test(linked)) {
|
||||
location.hash = linked;
|
||||
}
|
||||
startLeadStream();
|
||||
|
||||
+15
-6
@@ -1,7 +1,7 @@
|
||||
import { post } from "./api.js";
|
||||
import { errorText, h, replace, showAlert } from "./dom.js";
|
||||
import { openSettings } from "./settings.js";
|
||||
import { activeTasks, currentSession, findTask, focusTask, lastVisits, onEach, state } from "./state.js";
|
||||
import { activeTasks, currentSession, findTask, focusedProject, focusProject, focusTask, lastVisits, onEach, state } from "./state.js";
|
||||
import { openStatus } from "./status.js";
|
||||
|
||||
export function mountNavbar(root: HTMLElement): void {
|
||||
@@ -20,8 +20,8 @@ export function mountNavbar(root: HTMLElement): void {
|
||||
|
||||
const render = () => {
|
||||
const task = state.selectedTaskId ? findTask(state.selectedTaskId) : undefined;
|
||||
const project = task ? state.projects.find((p) => p.id === task.project_id) : undefined;
|
||||
replace(crumb, task ? `${project?.name ?? ""} ▸ ${task.title}` : "No task", " ▾");
|
||||
const project = focusedProject();
|
||||
replace(crumb, task ? `${project?.name ?? ""} ▸ ${task.title}` : (project?.name ?? "No task"), " ▾");
|
||||
|
||||
conn.className = state.connected ? "connection up" : "connection down";
|
||||
conn.title = state.connected ? "live" : "reconnecting";
|
||||
@@ -29,7 +29,7 @@ export function mountNavbar(root: HTMLElement): void {
|
||||
replace(me, state.me?.display_name ?? "");
|
||||
};
|
||||
|
||||
onEach(["me", "projects", "tasks", "selection", "connection"], render);
|
||||
onEach(["me", "projects", "tasks", "selection", "focus", "connection"], render);
|
||||
replace(root, h("a", { href: "#/", class: "brand" }, "byre"), crumb, h("span", { class: "spacer" }), status, settings, connLabel, conn, me);
|
||||
render();
|
||||
|
||||
@@ -106,8 +106,17 @@ function openSwitcher(): void {
|
||||
);
|
||||
return { el, pick: () => go(task.id) };
|
||||
});
|
||||
const current = state.selectedTaskId ? findTask(state.selectedTaskId) : undefined;
|
||||
const project = state.projects.find((p) => p.id === current?.project_id) ?? state.projects[0];
|
||||
for (const p of state.projects.filter((p) => `${p.name} todo`.toLowerCase().includes(q))) {
|
||||
const el = h("li", {}, h("span", { class: "dot none" }), h("span", { class: "task-title" }, h("span", { class: "project-name" }, p.name), " TODO.md"), h("span", { class: "task-state" }, "project"));
|
||||
rows.push({
|
||||
el,
|
||||
pick: () => {
|
||||
close();
|
||||
focusProject(p.id);
|
||||
},
|
||||
});
|
||||
}
|
||||
const project = focusedProject() ?? state.projects[0];
|
||||
if (title && project) {
|
||||
const el = h("li", { class: "new-task" }, h("span", { class: "task-title" }, "New task in ", h("span", { class: "project-name" }, project.name), ": ", title));
|
||||
rows.push({ el, pick: () => void create(project.id, title) });
|
||||
|
||||
+45
-2
@@ -1,4 +1,4 @@
|
||||
import type { TaskTab } from "./api.js";
|
||||
import { put, type TaskTab } from "./api.js";
|
||||
import { mountChanges } from "./changes.js";
|
||||
import { h, replace } from "./dom.js";
|
||||
import { mountFiles } from "./files.js";
|
||||
@@ -52,5 +52,48 @@ export function taskPane(): HTMLElement {
|
||||
|
||||
on("focus", render);
|
||||
render();
|
||||
return h("section", { class: "pane" }, strip, body);
|
||||
const pane = h("section", { class: "pane", tabIndex: -1 }, strip, body);
|
||||
trackPaneFocus(pane);
|
||||
return pane;
|
||||
}
|
||||
|
||||
function trackPaneFocus(pane: HTMLElement): void {
|
||||
let focused = false;
|
||||
let inside: Element | null = null;
|
||||
let sending: Promise<unknown> = Promise.resolve();
|
||||
const send = () => {
|
||||
const body = { focused };
|
||||
sending = sending.then(() => put("/api/me/pane-focus", { body })).catch((err: unknown) => console.error("set pane focus", err));
|
||||
};
|
||||
const update = () => {
|
||||
setTimeout(() => {
|
||||
if (focused && inside && !inside.isConnected && document.activeElement === document.body) {
|
||||
// A re-render removed the focused element (a tab button, a list row); that isn't the human leaving.
|
||||
pane.focus({ preventScroll: true });
|
||||
}
|
||||
const active = document.activeElement;
|
||||
const now = document.hasFocus() && pane.contains(active);
|
||||
inside = now ? active : null;
|
||||
if (now === focused) {
|
||||
return;
|
||||
}
|
||||
focused = now;
|
||||
send();
|
||||
});
|
||||
};
|
||||
on("connection", () => {
|
||||
if (state.connected && focused) {
|
||||
send();
|
||||
}
|
||||
});
|
||||
document.addEventListener("focusin", update);
|
||||
document.addEventListener("focusout", update);
|
||||
addEventListener("focus", update);
|
||||
addEventListener("blur", update);
|
||||
addEventListener("pagehide", () => {
|
||||
if (focused) {
|
||||
focused = false;
|
||||
void put("/api/me/pane-focus", { body: { focused: false }, keepalive: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+14
-2
@@ -131,19 +131,27 @@ export async function loadFocus(): Promise<void> {
|
||||
|
||||
export function applyFocus(f: Focus): void {
|
||||
state.focus = f;
|
||||
const hash = f.task_id ? `#/task/${f.task_id}` : "#/";
|
||||
const hash = f.task_id ? `#/task/${f.task_id}` : f.project_id ? `#/project/${f.project_id}` : "#/";
|
||||
if (location.hash !== hash && !(hash === "#/" && location.hash === "")) {
|
||||
location.hash = hash;
|
||||
}
|
||||
notify("focus");
|
||||
}
|
||||
|
||||
export async function setFocus(change: { task_id?: string | null; tab?: TaskTab }): Promise<void> {
|
||||
export function focusedProject(): Project | undefined {
|
||||
return state.projects.find((p) => p.id === state.focus.project_id);
|
||||
}
|
||||
|
||||
export async function setFocus(change: { task_id?: string | null; project_id?: string | null; tab?: TaskTab }): Promise<void> {
|
||||
const taskId = change.task_id === undefined ? state.focus.task_id : (change.task_id ?? undefined);
|
||||
const projectId = taskId ? findTask(taskId)?.project_id : change.project_id === undefined ? state.focus.project_id : (change.project_id ?? undefined);
|
||||
const next: Focus = { tabs: state.focus.tabs, tab: change.tab ?? state.focus.tab };
|
||||
if (taskId) {
|
||||
next.task_id = taskId;
|
||||
}
|
||||
if (projectId) {
|
||||
next.project_id = projectId;
|
||||
}
|
||||
applyFocus(next);
|
||||
applyFocus(await put("/api/me/focus", { body: next }));
|
||||
}
|
||||
@@ -152,6 +160,10 @@ export function focusTask(taskId: string, tab: TaskTab = "transcript"): void {
|
||||
setFocus({ task_id: taskId, tab }).catch((err: unknown) => console.error("set focus", err));
|
||||
}
|
||||
|
||||
export function focusProject(projectId: string): void {
|
||||
setFocus({ task_id: null, project_id: projectId, tab: "todo" }).catch((err: unknown) => console.error("set focus", err));
|
||||
}
|
||||
|
||||
export function focusTab(tab: TaskTab): void {
|
||||
setFocus({ tab }).catch((err: unknown) => console.error("set focus", err));
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ export type Head = {
|
||||
|
||||
export const models = ["claude-opus-5-5", "claude-sonnet-5-5", "claude-haiku-5-5", "claude-fable-5-1"];
|
||||
|
||||
const quietReply = "NO_REPLY";
|
||||
|
||||
type Call = { name: string; target: string };
|
||||
|
||||
type Run = {
|
||||
@@ -57,6 +59,7 @@ type Ctx = {
|
||||
taskId: string | undefined;
|
||||
lastMessage: HTMLElement | undefined;
|
||||
turnStart: HTMLElement | undefined;
|
||||
quiet: boolean;
|
||||
watchReport: (el: HTMLElement, itemId: string) => void;
|
||||
};
|
||||
|
||||
@@ -224,6 +227,7 @@ export function mountChat(root: HTMLElement, src: ChatSource, makeHead: (rerende
|
||||
unplaced: [],
|
||||
lastMessage: undefined,
|
||||
turnStart: undefined,
|
||||
quiet: false,
|
||||
root: src.root(),
|
||||
taskId: src.taskId(),
|
||||
watchReport: (el, itemId) => {
|
||||
@@ -414,6 +418,7 @@ export function mountChat(root: HTMLElement, src: ChatSource, makeHead: (rerende
|
||||
}
|
||||
stream.timer = 0;
|
||||
const pinned = transcript.scrollHeight - transcript.scrollTop - transcript.clientHeight < 80;
|
||||
stream.el.hidden = quietReply.startsWith(stream.text.trim());
|
||||
replace(stream.el, markdown(stream.text, "text md", { taskId: ctx.taskId }));
|
||||
if (pinned) {
|
||||
transcript.scrollTop = transcript.scrollHeight;
|
||||
@@ -478,6 +483,14 @@ export function mountChat(root: HTMLElement, src: ChatSource, makeHead: (rerende
|
||||
ctx.turnStart = undefined;
|
||||
continue;
|
||||
}
|
||||
if (ev.kind === "turn.ended" && ctx.quiet && ctx.turnStart?.parentNode === transcript) {
|
||||
const brief = ctx.turnStart.querySelector(".tool-brief")?.textContent;
|
||||
const label = h("div", {}, brief && h("span", { class: "tool-name" }, "byre"), brief && " ", h("span", { class: "tool-brief" }, brief ? `${brief} · nothing to say` : "nothing to say"));
|
||||
addAnchor(ev.seq, foldTurn(ctx.turnStart, label), true);
|
||||
ctx.turnStart = undefined;
|
||||
ctx.quiet = false;
|
||||
continue;
|
||||
}
|
||||
put(el);
|
||||
if (ev.kind === "user.message") {
|
||||
ctx.turnStart = el;
|
||||
@@ -842,6 +855,7 @@ function renderEvent(ev: ByreEvent, ctx: Ctx): HTMLElement | null {
|
||||
switch (ev.kind) {
|
||||
case "user.message":
|
||||
ctx.lastMessage = undefined;
|
||||
ctx.quiet = false;
|
||||
if (ev.actor === "byre") {
|
||||
const text = String(p["text"]);
|
||||
const first = text.split("\n")[0] ?? "";
|
||||
@@ -862,6 +876,11 @@ function renderEvent(ev: ByreEvent, ctx: Ctx): HTMLElement | null {
|
||||
);
|
||||
|
||||
case "agent.message":
|
||||
if (String(p["text"]).trim() === quietReply) {
|
||||
ctx.quiet = true;
|
||||
return null;
|
||||
}
|
||||
ctx.quiet = false;
|
||||
ctx.lastMessage = h("div", { class: "msg agent" }, markdown(String(p["text"]), "text md", md));
|
||||
return ctx.lastMessage;
|
||||
|
||||
|
||||
+6
-11
@@ -2,7 +2,7 @@ import { get, post, type ByreEvent, type Project, type Schemas } from "./api.js"
|
||||
import { errorText, h, replace, showAlert } from "./dom.js";
|
||||
import { markdown } from "./markdown.js";
|
||||
import { models } from "./task.js";
|
||||
import { currentSession, findTask, focusTask, on, onEach, state } from "./state.js";
|
||||
import { currentSession, focusedProject, focusTask, on, onEach, state } from "./state.js";
|
||||
|
||||
type Todo = Schemas["Todo"];
|
||||
type TodoItem = Schemas["TodoItem"];
|
||||
@@ -14,15 +14,10 @@ export function mountTodo(root: HTMLElement): () => void {
|
||||
const starting = new Set<string>();
|
||||
let disposed = false;
|
||||
|
||||
const project = () => {
|
||||
const task = state.selectedTaskId ? findTask(state.selectedTaskId) : undefined;
|
||||
return state.projects.find((p) => p.id === task?.project_id);
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
const scrollTop = root.querySelector(".todo-view")?.scrollTop ?? 0;
|
||||
const p = project();
|
||||
replace(root, h("div", { class: "todo-view" }, p ? projectBlock(p, todos.get(p.id), starting, start) : h("div", { class: "empty" }, "No task selected.")));
|
||||
const p = focusedProject();
|
||||
replace(root, h("div", { class: "todo-view" }, p ? projectBlock(p, todos.get(p.id), starting, start) : h("div", { class: "empty" }, "No project selected.")));
|
||||
root.querySelector(".todo-view")!.scrollTop = scrollTop;
|
||||
};
|
||||
|
||||
@@ -39,7 +34,7 @@ export function mountTodo(root: HTMLElement): () => void {
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
const p = project();
|
||||
const p = focusedProject();
|
||||
if (p) {
|
||||
void load(p.id);
|
||||
}
|
||||
@@ -77,9 +72,9 @@ export function mountTodo(root: HTMLElement): () => void {
|
||||
}
|
||||
}),
|
||||
on("projects", refresh),
|
||||
on("selection", () => {
|
||||
onEach(["focus", "selection"], () => {
|
||||
render();
|
||||
const p = project();
|
||||
const p = focusedProject();
|
||||
if (p && !todos.has(p.id)) {
|
||||
void load(p.id);
|
||||
}
|
||||
|
||||
@@ -259,6 +259,10 @@ textarea {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.pane:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.pane + .pane {
|
||||
border-left: 1px solid var(--border);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user