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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ changes will be called out under **Breaking changes**.

## Unreleased

## [0.18.1] — 2026-09-15

Full notes: [`docs/releases/0.18.1.md`](docs/releases/0.18.1.md).

### Fixed

- **Native text selection in chat.** Mouse capture is off by default, restoring terminal drag selection, double-click word selection, and normal copying. Use `--mouse` to opt into chat wheel scrolling; `--no-mouse` retains native selection.

## [0.18.0] — 2026-09-15

Full notes: [`docs/releases/0.18.0.md`](docs/releases/0.18.0.md).
Expand Down Expand Up @@ -554,6 +562,7 @@ Initial alpha. Core room protocol, SQLite-backed persistence, multi-process
contention coverage, MCP smoke coverage, human guardian flow, harness
installers, and the portable `talking-stick` skill.

[0.18.1]: https://github.com/mostlydev/talking-stick/releases/tag/v0.18.1
[0.18.0]: https://github.com/mostlydev/talking-stick/releases/tag/v0.18.0
[0.17.0]: https://github.com/mostlydev/talking-stick/releases/tag/v0.17.0
[0.16.0]: https://github.com/mostlydev/talking-stick/releases/tag/v0.16.0
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ tt state [path] [--all] # compact room state; -
tt health [path] [--verbose|--all] # concise safety/action check; verbose shows diagnostics
tt status [path] [--verbose|--all] # alias for health
tt events [path] [--all] [--after N] [--limit N] [--wait|--follow] [--event TYPE[,TYPE]] [--target self|any|agent] # audit/debug event log; --wait/--follow lower-level streams
tt chat [path] [--history N] [--events] [--no-mouse] # operator chat console for the room
tt chat [path] [--history N] [--events] [--mouse|--no-mouse] # operator chat console for the room
tt msg send <recipient|room> <body...> [--interrupt] [--stdin] [--path DIR] # send an OOB message
tt msg recv [--wait|--follow] [--from agent] [--after N] [--target self|any|agent] [--path DIR] # receive OOB messages
tt kick <agent_id> [path] [--reason TEXT] [--force] # remove a member (live ones need --force)
Expand Down Expand Up @@ -310,7 +310,7 @@ claude → you 12:05
3 members │ codex holding 12m · claude idle 3m
```

Scroll with the mouse wheel, Page Up/Page Down, or Shift+Up/Down. The input stays fixed and editable. New messages do not pull you away from older history; a count appears in the footer. Ctrl+End or `/bottom` returns to live messages. The in-memory buffer retains up to 2,000 message/notice blocks and rewraps on resize. Use `--no-mouse` for keyboard-only scrolling and native text selection; otherwise hold your terminal's selection modifier (usually Shift) when dragging.
Scroll with Page Up/Page Down or Shift+Up/Down. The input stays fixed and editable. New messages do not pull you away from older history; a count appears in the footer. Ctrl+End or `/bottom` returns to live messages. The in-memory buffer retains up to 2,000 message/notice blocks and rewraps on resize. Mouse capture is off by default: drag to select text, double-click to select a word, and copy using your terminal's usual shortcut or menu. For wheel scrolling inside the conversation, opt in with `tt chat --mouse`; this captures mouse gestures, so native selection then requires your terminal's selection modifier (often Shift). `--no-mouse` explicitly restores the default and wins if both flags are supplied.

Typing `/`, `@`, or `!@` opens a suggestion list drawn over the bottom of the conversation, so nothing moves while you type. Up/Down choose, Tab or Enter accept, and Enter still sends once the word is complete (an exact `/quit` still quits). Escape closes the list first and clears the draft on a second press; Ctrl+C clears the draft. Neither quits. Alt+Enter (or Shift+Enter where the terminal supports it) adds a new line, and with the list closed Up/Down move through a multi-line draft at the same column. Pasted multiline text stays in the draft until Enter. On exit, the console restores the original terminal screen.

Expand Down
18 changes: 18 additions & 0 deletions docs/releases/0.18.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Talking Stick 0.18.1

Date: 2026-09-15

## Fixed

- **Native text selection in chat.** Mouse capture is off by default, restoring terminal drag selection, double-click word selection, and normal copying. Use `--mouse` to opt into chat wheel scrolling; `--no-mouse` retains native selection.

## Verification

```bash
npm run typecheck
npm test
npm run build
node dist/cli.js --help
git diff --check
npm pack --dry-run
```
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "talking-stick",
"version": "0.18.0",
"version": "0.18.1",
"description": "CLI coordination tool for path-scoped agent handoffs.",
"type": "module",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion src/cli/chat-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export function formatChatHelp(width: number, color: boolean, keys = false): str
["Esc", "Dismiss suggestions; press again to clear"],
["Ctrl+C", "Clear the draft"],
["PgUp / PgDn", "Scroll the conversation"],
["Shift+↑ / ↓ · wheel", "Scroll a few lines"],
["Shift+↑ / ↓", "Scroll a few lines (wheel with --mouse)"],
["Ctrl+End", "Return to the latest messages"],
["Ctrl+D", "Quit when the draft is empty"]
] : CHAT_COMMANDS.map((command) => [
Expand Down
4 changes: 2 additions & 2 deletions src/cli/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export async function handleChatCommand(
color: terminal && !process.env.NO_COLOR,
history: parseOptionalInteger(parsed, "history") ?? DEFAULT_HISTORY,
show_turn_events: hasOption(parsed, "events"),
mouse: !hasOption(parsed, "no-mouse")
mouse: hasOption(parsed, "mouse") && !hasOption(parsed, "no-mouse")
});
}

Expand Down Expand Up @@ -553,7 +553,7 @@ export async function runChatSession(
screenActive = true;
output.write(
"\u001b[?1049h\u001b[?2004h" +
(options.mouse !== false ? "\u001b[?1000h\u001b[?1006h" : "")
(options.mouse === true ? "\u001b[?1000h\u001b[?1006h" : "")
);
editor = new ChatInputController({
input: options.input,
Expand Down
1 change: 1 addition & 0 deletions src/cli/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const BOOLEAN_FLAGS = new Set([
"link",
"no-guard",
"no-mouse",
"mouse",
"operator-requested",
"park",
"print",
Expand Down
2 changes: 1 addition & 1 deletion src/cli/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ export const COMMAND_REGISTRY: CommandEntry[] = [
needsRuntime: true,
startupMaintenance: true,
internal: false,
usage: "tt chat [path] [--history N] [--events] [--no-mouse]",
usage: "tt chat [path] [--history N] [--events] [--mouse|--no-mouse]",
description: "Open an operator chat console for a room's agents.",
handler: ({ runtime, parsed }) => handleChatCommand(requireRuntime(runtime), parsed)
},
Expand Down
15 changes: 15 additions & 0 deletions tests/chat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1289,3 +1289,18 @@ test("chat kicks a persistently ended member without force and protects live mem
await until(() => transcript.includes("In the room: codex"));
} finally { input.write("/quit\n"); await session; }
});

test.each([undefined, false, true])("chat enables terminal mouse capture only when requested (mouse=%s)", async (mouse) => {
const { root, service } = setupService();
const input = new PassThrough(); const output = new PassThrough(); let captured = "";
output.on("data", (chunk) => { captured += chunk.toString(); });
const session = runChatSession({ runtime: { commands: new TalkingStickCommands(service), close() {} },
identity: observerIdentity(), context_path: root, input, output, terminal: true, color: false, history: 0,
show_turn_events: false, poll_ms: 5, mouse });
try {
await until(() => captured.includes("Room · "));
expect(captured.includes("\u001b[?1000h")).toBe(mouse === true);
expect(captured.includes("\u001b[?1006h")).toBe(mouse === true);
} finally { input.write("/quit\r"); await session; }
expect(captured).toContain("\u001b[?1000l\u001b[?1006l");
});
2 changes: 1 addition & 1 deletion tests/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from "../src/cli/parser.js";

describe("parseCommand", () => {
test.each(["json", "no-mouse"])(
test.each(["json", "no-mouse", "mouse"])(
"boolean --%s preserves the following path",
(flag) => {
const parsed = parseCommand(["chat", `--${flag}`, "/repo"]);
Expand Down
Loading