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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,8 @@ SidePanel 折叠 UI:`ChatView` 把 `SidePanel` 包到 `<div class="ace-side-pane

全局会话搜索面板(`add-webui-search-palette`):`Ctrl+K` / `Cmd+K` 或 TopBar 🔍 按钮触发 `SearchPalette`。**快捷键只有一个定义点** `lib/searchPaletteShortcut.js`(`isSearchPaletteShortcut` 判定,兼容 `e.code === 'KeyK'` 的非拉丁布局;`searchPaletteShortcutLabel` 按平台给 `Ctrl+K` / `⌘K` 文案):App.jsx 经 `useGlobalShortcut`(`window` keydown + preventDefault)监听,TopBar 提示、侧栏快捷菜单「查找内容」右侧的 kbd、ConsoleDock 的 xterm `attachCustomKeyEventHandler` 放行名单都从它取 —— xterm 会把处理过的键 stopPropagation,不放行的话终端聚焦时 Ctrl+K 静默失效(代价是 shell 的 Ctrl+K kill-line 让位,与 VS Code 集成终端默认一致)。帮助文档 `docs/help-source/group6_reference.py` 的快捷键表也列了它。前端**纯聚合**所有 workspace 的 sessions:`api.listAllWorkspaceSessions`(底层 `mergeAllWorkspaceSessions` 纯函数 + `Promise.allSettled`,单 workspace 失败不阻塞其它)→ `lib/searchSessions.js::rankSessions` 加权排序(title 前缀 +1000 / 子串 +500 / summary +200 / workspaceName +100 / fuzzy 兜底 +50,叠 24h/7d/30d 时间衰减 0~50)→ z-300 居中模态。键盘导航 ↑/↓/PgUp/PgDn/Home/End/Enter/Esc;选中同 workspace 直接 `setActiveRef`;跨 workspace 优先 `aceDesktop_activateWorkspace` + 整页 navigate `?open=<sid>`(App.jsx mount 时解析并 `replaceState` 抹掉 query),无 bridge 时降级直接 setActiveRef。数据 60s TTL 缓存,`session_status` / `session_status_snapshot` / `mark_session_read_ack` 任一 WS 帧到达即 invalidate。**后端零路由变更**。第三组「设置」纯本地:复用设置窗口的索引 `settingsSearchEntries` + 打分 `searchSettings`(`searchSessions.js::rankSettingsForPalette` 只截前 8 条,空查询恒为空),`buildSearchResultSequence(tasks, projects, settings)` 三组接成一条键盘序列;选中设置项走 `App.handleSelectSetting` → 关面板、`settingsNavKey = section`、把面板原始查询 + 结果 id 作为 `initialSearch` 种子传给 `SettingsPage`,后者用同一份索引重跑同一查询、`settingsSearchResultIndex` 按 id(退回 section+label)对齐选中项,再由既有的 locate/波浪下划线逻辑滚到该项。SettingsPage 的搜索防抖只在搜索词真的变了才把选中项清回 0(`appliedSearchTermRef`),否则首轮防抖会把种下的选中项冲掉;普通 `openSettingsSection` 一律清空种子。

**`@` 会话引用有查询词时必须走服务端检索,不能本地过滤最近列表。** `/api/session-search/sessions` 对空查询最多只给最近 50 条;`@` 菜单曾拿这 50 条在本地过滤,更早的会话永远搜不到(实测 28456 个会话里,标题完全一致的「随机选择题挑战」排第 9726 位,菜单显示「没有匹配」)。现在 `lib/sessionReference.js::searchSessionReferenceData` 带 `q` + `limit=100` 请求(内存目录过滤,2.8 万会话单次 30–46ms),同一查询词 15 秒缓存,目录预热未完成时有界轮询,查询变化 / 下拉框关闭即 abort。**不要**在 `@` 菜单按键时调 `searchSessionUserMessages`:正文搜索逐项目读 meta、开 SQLite,全量一遍几十秒,而 `@` 词同时用于文件路径;全文搜索留给 Ctrl+K。守卫:`sessionReference.test.js` + `sessionReferenceComposerArchitecture.test.js`。

**对话框键盘约定(Claude Code 风格,全部走 `components/Modal.jsx`)**:Esc 取消、Tab / Shift+Tab 只在对话框内循环、Enter 触发默认操作、删除类确认框打开即默认选中「删除」。判定逻辑全在 `lib/dialogKeyboard.js`(纯函数,Node 单测用鸭子类型假 DOM):默认操作按钮用 `data-ace-dialog-primary="true"` 标出;初始焦点顺序 = 已落在对话框内的焦点(React `autoFocus` 在 commit 阶段先跑,Modal 不再抢——旧实现无条件聚焦第一个可聚焦元素,曾把「添加专家」搜索框的 autoFocus 抢成头部 ×)> 第一个文本输入框(表单型)> primary(确认型)> 第一个可聚焦元素;Enter 只在焦点**不在**会自己消费 Enter 的元素(button / a[href] / textarea / contenteditable / `<form>` 内控件——表单靠隐式提交)上时点 primary,子组件自己处理 Enter 必须 `preventDefault`(Tag 输入框那样),否则会双触发;按住不放的 auto-repeat Enter 一律 `preventDefault`(Chromium 的按钮激活发生在 keypress、且派发给当时聚焦的元素,「按住 Enter 打开确认框 → 焦点落到删除 → 连带确认」就是这么来的;keydown 被阻止就不再产生 keypress),textarea / contenteditable 内放行。输入法合成中的 Esc 不关对话框。焦点环只在 `:focus-visible`(`.ace-modal-dialog :is(button, [role="button"], a[href])`),鼠标点开时脚本聚焦的默认按钮不画环、按 Tab 后才出现,与系统对话框一致。**不要**再手写 `fixed inset-0` 遮罩当对话框(循环编辑表单与 opencode 导入框已收编;`agentBrowserArchitecture.test.js` 的 floatingSurfaceOwners 清单相应减少)。自动弹出的 `WorkspaceCleanupNotice` 故意不标 primary:无人触发的对话框不能带破坏性默认操作,默认落在「保留」。守卫:`lib/dialogKeyboard.test.js` + `lib/dialogKeyboardArchitecture.test.js`(后者还断言取消类文案永远不会被标成 primary)。

排队卡片栈(`redesign-webui-queue-cards`):busy 期间提交的待发送消息**不进 transcript**,改由 `<QueueCardList>`(在 `<InputBar>` 上方)渲染成卡片堆。状态机(`lib/chatInputQueue.js`)与 `enqueueQueuedInput` / `cancelQueuedInput` / `markQueuedInput*` / `nextQueuedInput` / `completeQueuedInputForMessage` 全部不变;只是渲染分支换地方。每张卡片左侧 3px `.ace-queue-card-indicator` 色条标注状态(QUEUED 灰 / FAILED 红),右侧恒挂"取消"(close 图标),FAILED 多一个"重试"。状态↔标签映射收敛在 `lib/queueCardItem.js::buildQueueCardItem`(纯函数,Node 单测覆盖);DOM 端只是把这份结构映射到 className。`Message.jsx::UserBubble` 已剥离 `queued`/`onCancelQueued`/`onRetryQueued` props——transcript 里出现的 user 气泡一定是后端真实落库的消息。
Expand Down
57 changes: 56 additions & 1 deletion docs/daemon-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -794,10 +794,65 @@ rows, so it does not learn the exact post-filter count:
`sessions.length` against `total` — that comparison is wrong whenever
`total_exact` is false.

This bound is why the sidebar's collapsed 5-row list is cheap: a workspace with
This bound keeps legacy flat-list callers cheap: a workspace with
1400 sessions opens ~13 metadata files instead of all 1400, which on Windows is
the difference between roughly 7 s and a few ms per request.

### `GET /api/workspaces/:hash/session-sidebar` and `GET /api/session-sidebar`

The second route lists no-workspace sessions only. These opt-in sidebar routes
exclude archived sessions and background subagents, group scheduled executions
by their persisted `loop_execution.loop_id`, then paginate root items. Existing
flat session-list routes retain their original protocol.

- `limit=N`: maximum root items (0/omitted means all). History rows do not count.
- `expanded`: URL-encoded JSON array of stable node keys whose complete children
should be included, e.g. `["loop:task-id"]`.
- `reveal=session-id`: include the root containing this session, increasing the
requested root page when necessary, and expand that root for a history target.

Response: `{sessions, total, total_exact:true, has_more, sidebar_tree:true,
visible_roots}`. `total` and `visible_roots` count non-pinned roots, while
`sessions` is a flat array containing those roots, the children of expanded
roots, and separately pinned rows. Every non-pinned row adds `sidebar_key`,
`sidebar_parent_key` (empty for roots), `sidebar_child_count`, `sidebar_rank`
(within siblings), and `sidebar_expanded` to the normal session summary.
Clients must not compare `sessions.length` with root totals.

Automatic group keys use `loop:<task-id>`; leaf keys use `session:<session-id>`.
The newest non-detached execution by creation time represents an automatic
group. Selecting old history does not replace it. Metadata parsing is cached
by file modification time and size; transcript bodies are never read here.

### `POST /api/workspaces/:hash/session-sidebar` and `POST /api/session-sidebar`

The same query parameters apply. Body:

```json
{"source":"session:older-run","target":"loop:task-id","placement":"inside"}
```

`placement` is `before`, `after`, or `inside`. Moves remain within the endpoint's
scope. Only a scheduled root with `loop_execution.loop_id` (the alarm marker)
can accept children, including an empty group or a detached scheduled execution.
Ordinary sessions may reorder or become children but cannot become parents.
A root with children can only reorder at the root; a child cannot accept
children. Moving a single-run automatic group into a scheduled parent detaches that
execution; future scheduled executions form their own automatic group.
Self moves, stale targets, and third-level relationships return HTTP 409 with
`SIDEBAR_TARGET_CHANGED` or `SIDEBAR_TWO_LEVELS_ONLY`. An ordinary destination
parent returns 409/`SIDEBAR_SCHEDULED_PARENT_ONLY`; malformed bodies return 400.
Save failures return 500/`SIDEBAR_SAVE_FAILED`. Successful moves return the new
sidebar page and reveal the destination.

Relationships and sibling order are atomically persisted in the scope's
`sidebar_sessions.json`, separately from task provenance and subagent
`parent_session_id`. An explicit empty parent keeps a detached execution at the
root after reload and future runs. Missing or archived parents promote their
manual children; removing an automatic representative selects the next run.
Reading a legacy layout that assigns children to an ordinary session promotes
those children to roots without changing their execution provenance.

### `POST /api/workspaces/:hash/sessions`

Creates a session in the workspace. Body fields are optional:
Expand Down
1 change: 1 addition & 0 deletions docs/help-source/group5.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
figure("AD-05", "定时任务创建与执行时间", "展示名称、工作空间、工作树、模型、权限和频率设置,使用一个无敏感数据的只读检查任务。"),
code("检查此工作区的 Git 状态和最近提交,生成一份简短摘要。只读取本地文件,不修改文件、不提交、不推送。遇到错误时记录原因并结束。", "定时任务提示词示例")),
section("manage", "查看记录、暂停与管理",
'''<p>侧边栏会把同一定时任务的会话收在一起,首行始终显示最近一次执行。点击首行打开该会话并展开或收起历史;历史以缩进、细竖线和浅背景区分,整组只占一个列表名额,展开历史不受五条分页限制。父项左侧默认显示展开或收起箭头,未读绿点或运行状态会覆盖箭头,不显示灰色占位点。定时任务右侧的闹钟始终可见,置顶、归档按钮与普通会话一样在悬停或聚焦时出现。</p><p>拖动父项可移动整组;拖动子项到顶层行间或列表空白处可脱离分组,仅拖到带闹钟的顶层定时会话中间可归入。暂无子会话的定时项也可接收;普通会话可排序或归入定时项,但不能成为父项。关系与排序会保存,手工移出的记录不会在刷新或下一次执行时重新归组。列表只允许父项和子项两层:已有子项的父项不能拖入其他项,子项不能再接收子项。</p>''',
'''<p>列表展示下一次执行与最近运行情况。打开运行记录查看等待执行、执行中、等待用户、已完成、失败或已错过等状态,并检查关联任务的输出。失败应结合原因处理,不能只看定时任务仍为启用状态。</p><p><strong>停用</strong>用于暂停后续计划,重新<strong>启用</strong>后检查新的执行时间;需要停止正在运行的那一轮,应打开对应任务并停止。编辑时可调整提示、模型与时间。删除定时任务也会移除其运行记录,保留需要的信息后再删除。</p><p>daemon 必须在执行时间保持运行,模型和工作目录也要可用。计算机离线、休眠或后台停止期间错过的执行会记录为已错过,不应期待恢复后把每次历史执行逐一补跑。同一工作空间的时间冲突会在保存或启用时提示,运行时占用也可能导致错过。</p><p>远端后台按其时间配置调度。跨时区或系统时区变化后,重新核对下一次执行时间;不要只按浏览器所在设备的时钟推测。</p>''',
figure("AD-06", "定时任务列表与运行记录", "展示下一次执行、启用或停用按钮和一条真实运行记录,标出状态、原因及查看任务入口。"))
], ["web/src/components/LoopPage.jsx", "web/src/lib/loops.js", "src/host/loop/loop_types.hpp", "src/host/loop/loop_scheduler.cpp", "src/host/loop/loop_store.cpp", "src/host/loop/scheduled_task_tool.cpp", "assets/seed/skills/acecode/scheduled-task/SKILL.md"]),
Expand Down
Loading
Loading