fix: 遵守仓库硬约束(禁止修改 DSH 源码)——tasks.output 改为会话事件回放

- 回退对 harness peek 缝隙的依赖:输出查看不再调用注册表,改为从属主
  会话事件日志回放 task_output 的 tool/call+tool/result 对(模型已读内容)
- read:false 时面板提示「模型尚未读取」;'(no new output)' 增量与错误
  结果过滤;截断上限不变;kill 走注册表原生 API(缺席时 503)
- AGENTS.md 新增 §0 硬约束:禁止修改 DSH 源码、挂载只走 cordis.patch.yml
  + profile 机制;设计文档同步实施偏差
- 测试:tasks-routes 重写为回放语义(拼接/过滤/read 标志/截断/503),
  视图新增 read:false 提示用例;全量 369 通过
This commit is contained in:
Menghuan1918
2026-08-12 23:04:30 +08:00
parent 8c521be26e
commit b90251cce5
10 changed files with 304 additions and 166 deletions
+12 -8
View File
@@ -8,7 +8,6 @@
*/
import { encodeHtmlUrl } from '../html-route.ts'
import type { BrowserProbeResult } from './browser.ts'
import type { SidebarTaskStatus } from '../context-types.ts'
/** One wire failure. */
export class SidebarApiError extends Error {
@@ -61,13 +60,19 @@ export interface FsTextResult { kind: 'text'; content: string; truncated: boolea
* `head` carries the first bytes (base64) for viewer detect sniffing. */
export interface FsBinaryResult { kind: 'binary'; size: number; truncated: boolean; head: string }
/** One tasks.output response: the peeked full text (capped) plus the wire status. */
/**
* One tasks.output response: the output the MODEL has read so far for the
* task (replayed from the owner session's event log — the model's
* task_output cursor is never touched, so the pane can never steal the
* agent's bytes). `read` is false until the model actually called
* task_output for the task.
*/
export interface TaskOutputResult {
text: string
/** True when the host capped the text at its output limit. */
truncated: boolean
status: SidebarTaskStatus
detail?: string
/** Whether the model has read the task at least once. */
read: boolean
}
async function call<T>(method: string, payload: Record<string, unknown>, signal?: AbortSignal): Promise<T> {
@@ -156,10 +161,9 @@ export const api = {
agentPtyClose: (uuid: string) =>
call<{ ok: true }>('agent-pty.close', { uuid }),
/**
* Full accumulated output of one background task (the host's NON-CONSUMING
* peek — the model's task_output cursor and report bit are untouched, so a
* human watching a stream never steals the agent's bytes). The scope MUST
* be the task's OWNER session (the tasks registry fences by session id).
* The output the model has read so far for one background task (replayed
* from the owner session's event log — never the model's task_output
* cursor). The scope MUST be the task's OWNER session.
*/
taskOutput: (scope: SessionScope, id: string, signal?: AbortSignal) =>
call<TaskOutputResult>('tasks.output', scopePayload(scope, { id }), signal),