@jmoyers/harness 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +33 -155
  2. package/package.json +5 -1
  3. package/packages/harness-ai/src/anthropic-client.ts +99 -0
  4. package/packages/harness-ai/src/anthropic-protocol.ts +581 -0
  5. package/packages/harness-ai/src/anthropic-provider.ts +82 -0
  6. package/packages/harness-ai/src/async-iterable-stream.ts +65 -0
  7. package/packages/harness-ai/src/index.ts +36 -0
  8. package/packages/harness-ai/src/json-parse.ts +66 -0
  9. package/packages/harness-ai/src/sse.ts +80 -0
  10. package/packages/harness-ai/src/stream-object.ts +96 -0
  11. package/packages/harness-ai/src/stream-text.ts +1340 -0
  12. package/packages/harness-ai/src/types.ts +330 -0
  13. package/packages/harness-ai/src/ui-stream.ts +217 -0
  14. package/scripts/codex-live-mux-runtime.ts +123 -7
  15. package/scripts/control-plane-daemon.ts +20 -3
  16. package/scripts/harness.ts +566 -133
  17. package/src/cli/gateway-record.ts +16 -1
  18. package/src/control-plane/agent-realtime-api.ts +4 -0
  19. package/src/control-plane/prompt/agent-prompt-extractor.ts +191 -0
  20. package/src/control-plane/prompt/extractors/claude-prompt-extractor.ts +53 -0
  21. package/src/control-plane/prompt/extractors/codex-prompt-extractor.ts +50 -0
  22. package/src/control-plane/prompt/extractors/cursor-prompt-extractor.ts +56 -0
  23. package/src/control-plane/prompt/session-prompt-engine.ts +69 -0
  24. package/src/control-plane/prompt/thread-title-namer.ts +290 -0
  25. package/src/control-plane/stream-command-parser.ts +12 -0
  26. package/src/control-plane/stream-protocol.ts +109 -0
  27. package/src/control-plane/stream-server-command.ts +14 -0
  28. package/src/control-plane/stream-server-session-runtime.ts +12 -0
  29. package/src/control-plane/stream-server.ts +485 -19
  30. package/src/mux/input-shortcuts.ts +9 -0
  31. package/src/mux/live-mux/critique-review.ts +5 -1
  32. package/src/mux/live-mux/git-parsing.ts +24 -0
  33. package/src/mux/live-mux/global-shortcut-handlers.ts +8 -0
  34. package/src/mux/render-frame.ts +1 -1
  35. package/src/pty/pty_host.ts +46 -1
  36. package/src/services/control-plane.ts +22 -0
  37. package/src/services/runtime-control-actions.ts +69 -0
  38. package/src/services/runtime-navigation-input.ts +4 -0
  39. package/src/services/runtime-rail-input.ts +4 -0
  40. package/src/services/runtime-workspace-actions.ts +5 -0
  41. package/src/ui/global-shortcut-input.ts +2 -0
package/README.md CHANGED
@@ -1,75 +1,40 @@
1
1
  # Harness
2
2
 
3
- Harness is a terminal-native workspace for running parallel coding agents on one machine, with project context, fast switching, and shared session state.
3
+ Harness is a terminal-native workspace for running multiple coding-agent threads in parallel, without losing project context.
4
4
 
5
- Use it when you want to move faster than a single chat window: keep multiple threads active, review diffs quickly, and drive work from one keyboard-first control plane.
5
+ It is built for people who want to move faster than a single chat window: implementation, review, and follow-up work can run side by side in one keyboard-first interface.
6
6
 
7
- ## Why teams use it
7
+ ## What matters most
8
8
 
9
- - Run many agent threads in parallel across `codex`, `claude`, `cursor`, `terminal`, and `critique`.
10
- - Keep native CLI ergonomics in one keyboard-first workspace.
11
- - Keep long-running threads alive in the detached gateway so reconnects do not kill work.
12
- - Open a command palette with `ctrl+p`/`cmd+p`, live-filter registered actions, and execute context-aware thread/project/runtime controls.
13
- - Open a thread-scoped command palette from left-rail `[+ thread]` (same matcher/autocomplete as `ctrl+p`) to start/install agent CLIs per project.
14
- - Start in the Home pane by default, then jump to projects/threads from the left rail.
15
- - Open `Set a Theme` from the command palette to launch a second autocomplete picker of canonical OpenCode presets (plus a `default` reset option), with live preview while you navigate.
16
- - Open or create a GitHub pull request for the currently tracked project branch directly from the command palette.
9
+ - Parallel threads across `codex`, `claude`, `cursor`, `terminal`, and `critique`.
10
+ - One command palette (`ctrl+p` / `cmd+p`) to jump threads, run actions, and control workflow quickly.
11
+ - Long-running work survives reconnects through a detached gateway.
12
+ - Gateway control is resilient: lifecycle operations are lock-serialized per session, and missing stale records can be recovered automatically.
13
+ - Fast left-rail navigation with automatic, readable thread titles.
14
+ - Built-in GitHub PR actions (`Open PR` / `Create PR`) from inside Harness.
17
15
 
18
16
  ## Demo
19
17
 
20
- ![Harness multi-thread recording](https://raw.githubusercontent.com/jmoyers/harness/main/assets/poem-recording.gif)
18
+ ![Harness multi-thread recording](assets/harness.gif)
21
19
 
22
20
  ## Quick start
23
21
 
24
- ### Prerequisites
22
+ Prerequisites:
25
23
 
26
24
  - Bun `1.3.9+`
27
- - Rust toolchain
28
- - At least one installed agent CLI (`codex`, `claude`, `cursor`, or `critique`)
25
+ - At least one agent CLI (`codex`, `claude`, `cursor`, or `critique`)
29
26
 
30
- ### Install (npm package)
31
-
32
- > Note: Harness requires Bun. It does not work with Node.js alone.
27
+ Install and run:
33
28
 
34
29
  ```bash
35
- # One-line bootstrap (installs missing Bun/Rust deps, then installs Harness globally)
30
+ # Bootstrap install
36
31
  curl -fsSL https://raw.githubusercontent.com/jmoyers/harness/main/install.sh | bash
37
32
 
38
- # Run directly with bunx (no install needed)
33
+ # Or run directly (no global install)
39
34
  bunx @jmoyers/harness@latest
40
35
 
41
36
  # Or install globally
42
37
  bun add -g --trust @jmoyers/harness
43
-
44
- # Upgrade an existing global install
45
- harness update
46
- # alias: harness upgrade
47
- ```
48
-
49
- ### Install (from source)
50
-
51
- ```bash
52
- bun install
53
- bun link
54
- ```
55
-
56
- ### Uninstall
57
-
58
- ```bash
59
- # Remove global Harness package
60
- bun remove --global @jmoyers/harness
61
-
62
- # Optional: remove workspace runtime artifacts (keeps harness.config.jsonc)
63
- if [ -n "${XDG_CONFIG_HOME:-}" ]; then
64
- rm -rf "$XDG_CONFIG_HOME/harness/workspaces"
65
- else
66
- rm -rf "$HOME/.harness/workspaces"
67
- fi
68
- ```
69
-
70
- ### Run
71
-
72
- ```bash
73
38
  harness
74
39
  ```
75
40
 
@@ -79,120 +44,33 @@ Use a named session when you want isolated state:
79
44
  harness --session my-session
80
45
  ```
81
46
 
82
- ## Common workflow
83
-
84
- 1. Open Harness in your repo.
85
- 2. Start parallel threads for implementation and review.
86
- 3. Use the command palette (`ctrl+p` / `cmd+p`) to jump, run actions, and manage project context.
87
- 4. Open the repo or PR actions from inside Harness when GitHub auth is available.
47
+ For restart/load diagnostics, use a named session with a non-default gateway port so you do not disrupt your active workspace gateway.
88
48
 
89
- ## Critique threads
49
+ ## Typical workflow
90
50
 
91
- - Available in the thread-scoped command palette (`[+ thread]`).
92
- - Runs with `--watch` by default.
93
- - Install actions are availability-aware and config-driven (`*.install.command`), opening a terminal thread to run the configured install command when a tool is missing.
94
- - Global command palette (`ctrl+p` / `cmd+p`) includes:
95
- - `Critique AI Review: Staged Changes`
96
- - `Critique AI Review: Current Branch vs Base`
97
- - These start a terminal thread and run `critique review ...`, preferring `claude` when available and otherwise using `opencode` when installed.
98
- - `mux.conversation.critique.open-or-create` is bound to `ctrl+g` by default.
51
+ 1. Open Harness in your repository.
52
+ 2. Start separate threads for implementation and review.
53
+ 3. Use `ctrl+p` / `cmd+p` to switch context and run project actions.
54
+ 4. Open or create a PR from the same workspace.
99
55
 
100
- `ctrl+g` behavior is project-aware:
56
+ ## User details
101
57
 
102
- - If a critique thread exists for the current project, it selects it.
103
- - If not, it creates and opens one in the main pane.
104
-
105
- `session.interrupt` is also surfaced as a mux keybinding action (`mux.conversation.interrupt`) so teams can bind a dedicated in-client thread interrupt shortcut without overloading quit semantics.
106
-
107
- ## GitHub PR Integration
108
-
109
- When GitHub auth is available (`GITHUB_TOKEN` or an authenticated `gh` CLI), Harness can:
110
-
111
- - Detect the tracked branch for the active project and show `Open PR` (if an open PR exists) or `Create PR` in the command palette.
112
- - Continuously sync open PR CI/check status into the control-plane store for realtime clients.
113
- - If auth is unavailable, PR actions fail quietly and show a lightweight hint instead of surfacing hard errors.
114
-
115
- ## API for Automation
116
-
117
- Harness exposes a typed realtime client for orchestrators, policy agents, and dashboards:
118
-
119
- ```ts
120
- import { connectHarnessAgentRealtimeClient } from './src/control-plane/agent-realtime-api.ts';
121
-
122
- const client = await connectHarnessAgentRealtimeClient({
123
- host: '127.0.0.1',
124
- port: 7777,
125
- subscription: { includeOutput: false },
126
- });
127
-
128
- client.on('session.status', ({ observed }) => {
129
- console.log(observed.sessionId, observed.status);
130
- });
131
-
132
- await client.close();
133
- ```
134
-
135
- Key orchestration calls are available in the same client:
136
-
137
- - `client.tasks.pull(...)`
138
- - `client.projects.status(projectId)`
139
- - `client.projects.settings.get(projectId)` / `client.projects.settings.update(projectId, update)`
140
- - `client.automation.getPolicy(...)` / `client.automation.setPolicy(...)`
58
+ - Thread-scoped command palette (`[+ thread]`) can launch/install supported agent CLIs per project.
59
+ - Critique review actions are available from the global palette and run in a terminal thread.
60
+ - `ctrl+g` opens the project’s critique thread (or creates one if needed).
61
+ - Theme selection is built in (`Set a Theme`) with OpenCode-compatible presets and live preview.
62
+ - PR actions use either `GITHUB_TOKEN` or an authenticated `gh` CLI session.
141
63
 
142
64
  ## Configuration
143
65
 
144
- Runtime behavior is config-first via `harness.config.jsonc`.
145
- GitHub project/PR integration is enabled by default and configured under `github.*`.
146
-
147
- Example (install commands + critique defaults + hotkey override + OpenCode theme selection):
148
-
149
- ```jsonc
150
- {
151
- "codex": {
152
- "install": {
153
- "command": "bunx @openai/codex@latest"
154
- }
155
- },
156
- "claude": {
157
- "install": {
158
- "command": "bunx @anthropic-ai/claude-code@latest"
159
- }
160
- },
161
- "cursor": {
162
- "install": {
163
- "command": null
164
- }
165
- },
166
- "critique": {
167
- "launch": {
168
- "defaultArgs": ["--watch"]
169
- },
170
- "install": {
171
- "command": "bun add --global critique@latest"
172
- }
173
- },
174
- "mux": {
175
- "ui": {
176
- "theme": {
177
- "preset": "tokyonight",
178
- "mode": "dark",
179
- "customThemePath": null
180
- }
181
- },
182
- "keybindings": {
183
- "mux.conversation.critique.open-or-create": ["ctrl+g"]
184
- }
185
- }
186
- }
187
- ```
188
-
189
- `mux.ui.theme.customThemePath` can point to any local JSON file that follows the OpenCode theme schema (`https://opencode.ai/theme.json`).
190
- Built-in presets now mirror the canonical OpenCode set (for example `aura`, `ayu`, `carbonfox`, `catppuccin`, `dracula`, `everforest`, `github`, `gruvbox`, `nightowl`, `nord`, `one-dark`, `opencode`, `tokyonight`, `vesper`, `zenburn`, and more), plus a special `default` picker option for the legacy default mux theme.
66
+ Runtime behavior is controlled by `harness.config.jsonc`.
191
67
 
192
- ## Documentation
68
+ Common customizations:
193
69
 
194
- - `design.md` contains architecture and system design.
195
- - `agents.md` contains execution and quality rules.
70
+ - Set install commands for `codex`, `claude`, `cursor`, and `critique`.
71
+ - Configure critique launch defaults.
72
+ - Customize keybindings.
73
+ - Choose a theme preset or custom OpenCode-compatible theme file.
196
74
 
197
75
  ## License
198
76
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jmoyers/harness",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,6 +22,7 @@
22
22
  "scripts/harness-core.ts",
23
23
  "scripts/harness-inspector.ts",
24
24
  "scripts/harness.ts",
25
+ "packages/harness-ai/src",
25
26
  "scripts/terminal-recording-gif-lib.ts",
26
27
  "scripts/require-bun.js",
27
28
  "native/ptyd/Cargo.lock",
@@ -53,6 +54,7 @@
53
54
  "codex:live:tail": "bun scripts/codex-live-tail.ts",
54
55
  "codex:live:snapshot": "bun scripts/codex-live-snapshot.ts",
55
56
  "control-plane:daemon": "bun scripts/control-plane-daemon.ts",
57
+ "gateway:stress": "bun scripts/gateway-restart-stress.ts",
56
58
  "terminal:parity": "bun scripts/terminal-parity.ts",
57
59
  "terminal:differential": "bun scripts/terminal-differential-checkpoints.ts",
58
60
  "previm:passthrough": "bun run build:ptyd",
@@ -77,6 +79,8 @@
77
79
  "test": "bun run build:ptyd && bun test",
78
80
  "test:integration:codex-status": "bun scripts/integration-codex-status-sequence.ts",
79
81
  "test:integration:codex-status:long": "bun scripts/integration-codex-status-sequence.ts --timeout-ms 180000 --prompt \"Write three short poems titled Dawn, Voltage, and Orbit. Before each poem, perform one repository inspection action and include one factual line from that action. Use at least three total tool actions and do not edit any files.\"",
82
+ "test:integration:agent-prompts": "bun scripts/integration-agent-prompt-parity.ts",
83
+ "test:integration:agent-prompts:long": "bun scripts/integration-agent-prompt-parity.ts --timeout-ms 180000",
80
84
  "smoke:harness-ai": "bun scripts/harness-ai-smoke.ts",
81
85
  "smoke:harness-ai:parity": "bun scripts/harness-ai-parity-smoke.mts",
82
86
  "test:coverage": "bun run build:ptyd && bun test --coverage --coverage-reporter=lcov --coverage-dir .harness/coverage-bun && bun run coverage:check",
@@ -0,0 +1,99 @@
1
+ import { parseAnthropicStreamChunk, type AnthropicStreamChunk } from './anthropic-protocol.ts';
2
+ import { createSseEventStream } from './sse.ts';
3
+ import type { HarnessAnthropicModel } from './types.ts';
4
+
5
+ export interface AnthropicMessagesRequestBody {
6
+ readonly model: string;
7
+ readonly max_tokens?: number;
8
+ readonly temperature?: number;
9
+ readonly top_p?: number;
10
+ readonly stop_sequences?: string[];
11
+ readonly system?: string;
12
+ readonly messages: unknown[];
13
+ readonly tools?: unknown[];
14
+ readonly stream: true;
15
+ }
16
+
17
+ interface ParsedAnthropicStreamEvent {
18
+ readonly rawValue: unknown;
19
+ readonly chunk: AnthropicStreamChunk | null;
20
+ readonly parseError?: string;
21
+ }
22
+
23
+ interface AnthropicStreamResponse {
24
+ readonly requestBody: AnthropicMessagesRequestBody;
25
+ readonly responseHeaders: Headers;
26
+ readonly stream: ReadableStream<ParsedAnthropicStreamEvent>;
27
+ }
28
+
29
+ async function readErrorBody(response: Response): Promise<string> {
30
+ try {
31
+ return await response.text();
32
+ } catch {
33
+ return '';
34
+ }
35
+ }
36
+
37
+ export async function postAnthropicMessagesStream(
38
+ model: HarnessAnthropicModel,
39
+ requestBody: AnthropicMessagesRequestBody,
40
+ abortSignal?: AbortSignal,
41
+ ): Promise<AnthropicStreamResponse> {
42
+ const url = `${model.baseUrl}/messages`;
43
+ const requestInit: RequestInit = {
44
+ method: 'POST',
45
+ headers: {
46
+ 'content-type': 'application/json',
47
+ 'x-api-key': model.apiKey,
48
+ 'anthropic-version': '2023-06-01',
49
+ ...model.headers,
50
+ },
51
+ body: JSON.stringify(requestBody),
52
+ };
53
+ if (abortSignal !== undefined) {
54
+ requestInit.signal = abortSignal;
55
+ }
56
+
57
+ const response = await model.fetch(url, requestInit);
58
+
59
+ if (!response.ok) {
60
+ const errorBody = await readErrorBody(response);
61
+ throw new Error(`anthropic request failed (${response.status}): ${errorBody}`);
62
+ }
63
+
64
+ if (response.body === null) {
65
+ throw new Error('anthropic response body was empty');
66
+ }
67
+
68
+ const sseStream = createSseEventStream(response.body);
69
+ const parsedStream = sseStream.pipeThrough(
70
+ new TransformStream({
71
+ transform(event, controller) {
72
+ if (event.data === '[DONE]') {
73
+ return;
74
+ }
75
+
76
+ try {
77
+ const raw = JSON.parse(event.data) as unknown;
78
+ const parsed = parseAnthropicStreamChunk(raw);
79
+ controller.enqueue({
80
+ rawValue: raw,
81
+ chunk: parsed,
82
+ } satisfies ParsedAnthropicStreamEvent);
83
+ } catch (error) {
84
+ controller.enqueue({
85
+ rawValue: event.data,
86
+ chunk: null,
87
+ parseError: error instanceof Error ? error.message : String(error),
88
+ } satisfies ParsedAnthropicStreamEvent);
89
+ }
90
+ },
91
+ }),
92
+ );
93
+
94
+ return {
95
+ requestBody,
96
+ responseHeaders: response.headers,
97
+ stream: parsedStream,
98
+ };
99
+ }