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: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.53
0.1.54
4 changes: 2 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/approval-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,6 @@ codex 报告生效 reviewer 为委托时,Target Status 常驻 `approvals:auto-
- `src/harness/adapter.ts`:`InteractionHandler` 宿主交互契约。
- `src/controller/interaction.ts`:Interaction identity、opened/resolved 与 waiter 生命周期。
- `src/event/types.ts`:`interaction.opened` / `interaction.resolved` / `approval_review_update`。
- `src/tui/protocol.ts`:审批卡、review 回执与 Target Status 委托提示投影。
- `src/tui/protocol/`:审批卡、review 回执与 Target Status 委托提示投影。
- chat-tui `src/types/index.ts`:`TranscriptBlockStatus.warning`;`components/transcript.tsx` 的状态图标/配色。
- `tests/approval-contract.test.ts` 等:审批契约与回执归一。
2 changes: 1 addition & 1 deletion docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ ContextProvider Registry 提供候选和当前 turn 的只读上下文;Plugin

### 5.9 TUI

UI 组件层来自 [chat-tui](https://github.com/qiankunli/chat-tui)(从 baton 抽出的开源库,基于 opentui React reconciler):baton 侧实现 ChatProtocol——`tui/protocol.ts` 把 controller/store 状态投影成视图快照、把 intents 翻译成 controller 操作;补全、分层 Ctrl+C、Interaction Dock 等交互语义都在 chat-tui。当前布局为 transcript、可增长 composer、状态栏与贴近 composer 的统一 Interaction Dock;补全、选择、权限、结构化问题、Hook Trust 和 Plugin Proposal 都从各自事实来源投影到 Dock,`/sessions` 提供持久会话切换,`/new` 新建会话。
UI 组件层来自 [chat-tui](https://github.com/compforge/chat-tui)(从 baton 抽出的开源库,基于 opentui React reconciler):baton 侧实现 ChatProtocol——`tui/protocol/` 把 controller/store 状态投影成视图快照、把 intents 翻译成 controller 操作;补全、分层 Ctrl+C、Interaction Dock 等交互语义都在 chat-tui。当前布局为 transcript、可增长 composer、状态栏与贴近 composer 的统一 Interaction Dock;补全、选择、权限、结构化问题、Hook Trust 和 Plugin Proposal 都从各自事实来源投影到 Dock,`/sessions` 提供持久会话切换,`/new` 新建会话。

#### 界面分层:时态与寿命

Expand Down
4 changes: 2 additions & 2 deletions docs/harness-interaction-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ type InteractionView =

这里的 `InteractionView` 是 chat-tui 展示联合,不等于 Baton 内核的持久 `Interaction`:
permission、question、hook trust 从内核 Interaction 投影为 blocking view;Plugin Proposal
保持自己的持久 identity / resolution,只在 `tui/protocol.ts` 投影为 non-blocking
保持自己的持久 identity / resolution,只在 `tui/protocol/` 投影为 non-blocking
`suggested_input`,与前述请求共享 `InteractionDock` 的排序和响应入口,不伪造
`interaction.opened/resolved`。

Expand Down Expand Up @@ -611,7 +611,7 @@ Interaction 统一 resolve 为 `{kind:"cancelled", reason:"turn"}`,先落
| Plugin Proposal | SuggestedInputInteractionView + proposed-input resolution intent |
| controller input state | InputView(requested/effective delivery) |

映射只存在于 baton `tui/protocol.ts` 一处;chat-tui 和 harness adapter 不互相依赖。
映射只存在于 baton `tui/protocol/`;chat-tui 和 harness adapter 不互相依赖。

## 6. 实施顺序

Expand Down
2 changes: 1 addition & 1 deletion docs/user-input-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ baton 保证 effective delivery 如实:只有 harness 确认接受才记录 st

## 6. 代码与测试锚点

- `src/tui/protocol.ts`:busy submit 的 steer / follow-up 选择,Esc intent 到 controller 的映射;`recallQueued` / `historyPrev` / `historyNext` 与历史游标 / stash / 事件流种入。
- `src/tui/protocol/`:busy submit 的 steer / follow-up 选择,Esc intent 到 controller 的映射;`recallQueued` / `historyPrev` / `historyNext` 与历史游标 / stash / 事件流种入。
- chat-tui `components/chat-shell.tsx`(↑/↓ 三级分派)、`components/composer.tsx`(`cursorAtBoundary` 边界门槛)、`protocol/index.ts`(`historyPrev` / `historyNext` 契约)。
- `src/controller/input.ts`:一等 `InputRecord`(身份即 messageId + status)、队列、recall 与只读快照。
- `src/controller/index.ts` / `turn.ts`:active driven turn、steer 降级、cancel 与 finalize 状态迁移;`Control` + `control(signal)`;finalize 时的 Interaction cancel-cascade。
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"@opentui/react": "^0.4.5",
"@qiankun01/baton-plugin": "workspace:*",
"bun": "^1.3.14",
"chat-tui": "github:qiankunli/chat-tui#e9490628704e8ffaac4ec0759738fafc1de2851c",
"chat-tui": "github:compforge/chat-tui#a43d1eb22ba24e5651cc735f1b73f9f7e4f41c7b",
"cron-parser": "^5.6.2",
"react": "^19.2.7",
"yaml": "^2.9.0"
Expand Down
2 changes: 1 addition & 1 deletion src/plugin/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export interface ControllerOptions<TSpec, TStatus> {
onReconcileSuccess?(key: ReconcileKey, nextReconcileAt: Date | null): void;
/** 仅报告实际执行失败,不包含 enqueue 参数校验错误。 */
onReconcileError?(key: ReconcileKey, error: unknown): void;
/** Source materialize Resource 后失效 Board 等派生投影。 */
/** Source 首次 materialize Resource 后失效 Board 等派生投影。 */
onSourceResource?(resource: Readonly<PluginResource<TSpec, TStatus>>): void;
}

Expand Down
8 changes: 7 additions & 1 deletion src/plugin/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ export class Manager {
]);
private readonly controllers = new Map<string, ManagedController>();
private readonly boardSources = new Map<string, ManagedBoardSource>();
private boardItemsCache: readonly BoardItem[] | undefined;
private readonly commandRegistry: PluginCommandRegistry;
private readonly contextProviders: ContextProviderRegistry;
private readonly instances: PluginInstanceRepository;
Expand Down Expand Up @@ -365,6 +366,7 @@ export class Manager {
session: options.session,
});
this.unsubscribeBatonResources = this.batonResources.subscribe((resource) => {
this.notifyBoardChanged();
this.enqueueBuiltinResource(resource);
});
}
Expand Down Expand Up @@ -762,12 +764,14 @@ export class Manager {
}

listBoardItems(): readonly BoardItem[] {
if (this.boardItemsCache) return this.boardItemsCache;
const items: BoardItem[] = [];
for (const source of this.boardSources.values()) {
if (!this.bindings.has(source.pluginInstanceId)) continue;
items.push(...source.present());
}
return Object.freeze(items);
this.boardItemsCache = Object.freeze(items);
return this.boardItemsCache;
}

listCommands(): readonly AvailablePluginCommand[] {
Expand Down Expand Up @@ -1205,6 +1209,8 @@ export class Manager {

private notifyBoardChanged(): void {
if (this.closed) return;
// Board 是 Resource 的派生快照;非 Board 的 view 刷新复用它,避免流式输出逐帧读盘并重跑 present()。
this.boardItemsCache = undefined;
try {
this.onBoardChanged?.();
} catch {
Expand Down
11 changes: 8 additions & 3 deletions src/plugin/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ interface EnsureResource<TSpec> {
spec: TSpec;
}

interface EnsureResourceResult<TSpec, TStatus> {
resource: PluginResource<TSpec, TStatus>;
created: boolean;
}

interface ResourceControl {
nextReconcileAt?: string;
}
Expand Down Expand Up @@ -212,7 +217,7 @@ export class PluginResourceStore {
*/
ensure<TSpec, TStatus = Record<string, unknown>>(
input: EnsureResource<TSpec>,
): PluginResource<TSpec, TStatus> {
): EnsureResourceResult<TSpec, TStatus> {
const type = validateResourceType(input.type);
const name = input.name;
const labels = stringMap("metadata.labels", input.labels);
Expand All @@ -234,7 +239,7 @@ export class PluginResourceStore {
status: {} as TStatus,
});
writeJsonAtomic(path, stored);
return stored.object;
return { resource: stored.object, created: true };
}
const current = this.readCurrentStored<TSpec, TStatus>(type, name).object;
if (
Expand All @@ -246,7 +251,7 @@ export class PluginResourceStore {
`Controller Source input conflicts with existing Resource: ${type.kind}/${name}`,
);
}
return current;
return { resource: current, created: false };
});
}

Expand Down
19 changes: 11 additions & 8 deletions src/plugin/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,17 @@ export class ControllerSources<TSpec, TStatus> {
const emit: SourceContext<TSpec>["emit"] = (input): void => {
if (this.closed || abort.signal.aborted) return;
try {
const resource = this.options.store.ensure<TSpec, TStatus>({
type: this.options.resourceType,
...input,
});
try {
this.options.onResource?.(resource);
} catch {
// Resource is durable; projection invalidation is best-effort.
const { resource, created } =
this.options.store.ensure<TSpec, TStatus>({
type: this.options.resourceType,
...input,
});
if (created) {
try {
this.options.onResource?.(resource);
} catch {
// Resource is durable; projection invalidation is best-effort.
}
}
if (this.ready) {
this.options.enqueue(resource.metadata.name);
Expand Down
2 changes: 1 addition & 1 deletion src/tui/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from "react";

import { PluginScreen } from "./plugins/screen.tsx";
import { BatonChatProtocol } from "./protocol.ts";
import { BatonChatProtocol } from "./protocol/index.ts";

export interface BatonTuiHandle {
openPlugins(): void;
Expand Down
4 changes: 2 additions & 2 deletions src/tui/main.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bun
// chat-first TUI:打开即聊天,体验对齐 claude/codex CLI。
// UI 层来自 chat-tui(github.com/qiankunli/chat-tui):ChatShell 消费视图快照、回传 intents;
// UI 层来自 chat-tui(github.com/compforge/chat-tui):ChatShell 消费视图快照、回传 intents;
// baton 侧只有 BatonChatProtocol(controller/store → 视图投影 + intents → controller 操作)。
// - 直接输入 → 发给当前 agent(默认 codex)
// - /codex、/claude 直接选择输入目标;/model、/effort 分别配置后续 turn
Expand All @@ -17,7 +17,7 @@ import { ensureConfigFile, loadConfig } from "../config/config.ts";
import { openBatonSession, type OpenBatonSessionResult } from "../session/open.ts";
import { SessionStore } from "../store/store.ts";
import { BatonTui, type BatonTuiHandle } from "./app.tsx";
import { BatonChatProtocol } from "./protocol.ts";
import { BatonChatProtocol } from "./protocol/index.ts";
import { SessionPickerScreen } from "./session-picker.tsx";
import { batonTheme } from "./theme.ts";

Expand Down
Loading