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: 6 additions & 3 deletions .github/workflows/lifecycle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 10
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -31,12 +31,12 @@ jobs:
bun-version: '1.3.14'
- uses: astral-sh/setup-uv@v6
- run: uv sync --extra dev
- run: bun install --frozen-lockfile
working-directory: ts
# Dependency installation above may use the network. All tests below use
# deterministic local children, no provider CLI or credentials.
- name: Python offline conformance
run: PYTHONPATH=src uv run --offline pytest tests/
- run: bun install --frozen-lockfile
working-directory: ts
- name: TypeScript offline conformance (Bun)
run: bun test
working-directory: ts
Expand Down Expand Up @@ -66,6 +66,9 @@ jobs:
- name: Built-package Claude SDK conformance (Node)
run: node tests/node-claude-sdk.mjs
working-directory: ts
- name: Built-package Cline SDK conformance (Node)
run: node tests/node-cline-sdk.mjs
working-directory: ts
- name: Built-package caller-owned OpenHands conformance (Node)
run: node tests/node-openhands.mjs
working-directory: ts
8 changes: 8 additions & 0 deletions ADAPTER-MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,14 @@ worker per operation. Exact thread resume and interruption preserve the native
thread; approval replies and bypass are unsupported. Local execution still
requires the native Amp thread service. See the
[Amp SDK contract and separate qualification evidence](SPEC.md#optional-amp-sdk-sessions).
Cline SDK sessions use `getSessionCapabilities("cline", "sdk")` /
`get_session_capabilities("cline", "sdk")`: the official TypeScript SDK 0.0.82
in explicit local mode, through the same Node >=22.14 bridge in both languages.
`features: "builtin-only"` is required; detached hooks/plugins/MCP/subagents
are excluded, and the Harness parent owns command execution through the
public bash hook. Native identity, raw usage/events and callback approvals
are retained. Native synthetic-provider checks do not imply authenticated
provider success. See the [Cline SDK contract](SPEC.md#optional-cline-sdk-sessions).
OpenCode HTTP sessions use `getSessionCapabilities("opencode", "rpc")` /
`get_session_capabilities("opencode", "rpc")` against an explicitly selected
caller-owned 1.18.29 endpoint. Both languages implement native HTTP/SSE,
Expand Down
30 changes: 20 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Contributing to harness

Harness is a small library for uniform coding-agent integration. CLI execution, Pi RPC, optional OMP/Amp SDK bridges, native Claude/Factory Droid SDK sessions and caller-owned OpenCode/OpenHands sessions ship today. Additional backends must satisfy the shared [SPEC](SPEC.md#backend-and-session-implementation-gates) without adding a fleet manager or application.
Harness is a small library for uniform coding-agent integration. CLI execution, Pi RPC, optional OMP/Amp/Cline SDK bridges, native Claude/Factory Droid SDK sessions and caller-owned OpenCode/OpenHands sessions ship today. Additional backends must satisfy the shared [SPEC](SPEC.md#backend-and-session-implementation-gates) without adding a fleet manager or application.

## Before you open a PR

Expand All @@ -22,6 +22,10 @@ PYTHONPATH=src uv run pytest tests/
cd ts && bun test
```

Install TypeScript development dependencies (`bun install` from `ts/`) before
the Python suite as well: both language Cline cases use its optional pinned
`@cline/sdk` package with a finite synthetic loopback provider.

All tests must pass in both. If you add a fixture, both impls must parse it.

### Offline conformance versus live smoke
Expand All @@ -38,14 +42,20 @@ The `subprocess lifecycle` workflow gates macOS and Linux (GitHub
After `bun run build`, run `node tests/node-lifecycle.mjs` and
`node tests/node-sessions.mjs`, `node tests/node-omp-sdk.mjs`,
`node tests/node-claude-sdk.mjs`, `node tests/node-amp-sdk.mjs`,
`node tests/node-opencode.mjs`, `node tests/node-openhands.mjs` and
`node tests/node-droid-sdk.mjs` from `ts/` to check packaged subprocess, RPC,
SDK and HTTP/SSE/WebSocket behavior under Node as well as source under Bun.
OMP uses the real Bun worker with a synthetic SDK; Amp uses the real Node
worker with a synthetic SDK and finite CLI fixture. Claude and Factory use
pinned real SDK development dependencies with finite synthetic CLIs, never
bundled CLIs or providers. Install Python development dependencies with
`uv sync --extra dev` before these cases. No Windows lifecycle support is claimed.
`node tests/node-cline-sdk.mjs`, `node tests/node-opencode.mjs`,
`node tests/node-openhands.mjs` and `node tests/node-droid-sdk.mjs` from `ts/`
to check packaged subprocess, RPC, SDK and HTTP/SSE/WebSocket behavior under
Node as well as source under Bun. OMP uses the real Bun worker with a synthetic
SDK; Amp uses the real Node worker with a synthetic SDK and finite CLI fixture.
Claude and Factory use pinned real SDK development dependencies with finite
synthetic CLIs, never bundled CLIs or providers. Install Python development
dependencies with `uv sync --extra dev` before these cases.
No Windows lifecycle support is claimed.
Cline uses the pinned real SDK in a Node >=22.14 worker for Python, Bun and
packaged Node. `tests/cline_sdk_cases.json` shares expected outcomes; its
provider peer is finite and synthetic. Test-only event/failure injection is
separate from private unmodified native-runtime and authenticated-provider
qualification.
The remote cases share `tests/opencode_cases.json` and `tests/openhands_cases.json`
with isolated synthetic peers, finite lifetimes and handle-owned cleanup.
They are not native OpenCode/OpenHands or authenticated-provider qualification;
Expand Down Expand Up @@ -205,7 +215,7 @@ Add a row to [ADAPTER-MATRIX.md](ADAPTER-MATRIX.md) covering: CLI binary name, i
Streaming, controlled sessions and optional agent SDK integrations are eligible
when they implement the [SPEC gates](SPEC.md#backend-and-session-implementation-gates)
in both languages. This supersedes the historical blanket SDK exclusion.
Pi RPC, OMP/Amp SDK bridges, native Claude/Factory Droid SDK sessions and
Pi RPC, OMP/Amp/Cline SDK bridges, native Claude/Factory Droid SDK sessions and
caller-owned OpenCode/OpenHands sessions are implemented; other protocols/SDKs need qualification.
Keep optional SDK loading isolated from ordinary CLI imports and preserve
existing caller-selected configuration.
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,75 @@ that operation before allowing follow-up in the same thread. Native events,
usage and terminal results stay verbatim. See the
[Amp dependency, lifecycle and qualification contract](SPEC.md#optional-amp-sdk-sessions).

### Optional Cline SDK sessions

Select `harness="cline", backend="sdk"` with the official
`@cline/sdk@0.0.82` installed separately. Python and TypeScript use the same
**Node >=22.14 bridge**, not a native Python SDK or the Cline CLI.

```python
from pathlib import Path
from harness import ClineSdkOptions, SessionSpec, open_session

async def review(workdir: Path, sdk_package: Path, profile: Path):
session = await open_session(SessionSpec(
harness="cline", backend="sdk", workdir=workdir,
cline_sdk=ClineSdkOptions(
package_root=sdk_package, config_dir=profile,
provider="openai-compatible", features="builtin-only",
approval="callback",
),
))
try:
turn = session.start_turn("Review the repository without editing files.")
async for event in turn.events:
if event.type == "cline_permission":
await session.respond_approval(event.raw["id"], "reject")
return await turn.result
finally:
await session.close()
```

```typescript
import { openSession } from '@twaldin/harness-ts'

async function review(workdir: string, packageRoot: string, configDir: string) {
const session = await openSession({
harness: 'cline', backend: 'sdk', workdir,
clineSdk: {
packageRoot, configDir, provider: 'openai-compatible',
features: 'builtin-only', approval: 'callback',
},
})
try {
const turn = session.startTurn('Review the repository without editing files.')
for await (const event of turn.events) {
if (event.type === 'cline_permission' && typeof event.raw.id === 'string') {
await session.respondApproval(event.raw.id, 'reject')
}
}
return await turn.result
} finally {
await session.close()
}
}
```

Use absolute paths and a dedicated writable Cline profile with the selected
provider/model configured. Explicit `model` overrides that profile's model;
`executable` selects Node. The SDK remains local: no hub attachment/startup.
`features: "builtin-only"` deliberately excludes hooks, plugins, MCP,
subagents and other unqualified native extensions. Harness owns command
execution through the public bash hook and cancels command groups even when
the SDK worker dies. This is process ownership, not a sandbox.

The example rejects observed tool requests. Use `"once"` only after your
application approves the unchanged native request. Omitted `approval` means
`"upstream"` and retains Cline SDK's **auto-approved defaults**; no interactive
permission guarantee is implied. Native events, exact resume and per-turn/
cumulative usage remain distinct. See the
[Cline SDK contract and evidence limits](SPEC.md#optional-cline-sdk-sessions).

### Caller-owned OpenCode HTTP sessions

Select `harness="opencode", backend="rpc"` with an explicit `OpenCodeOptions`
Expand Down
6 changes: 6 additions & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@
resume; interruption and disposal use the existing session lifecycle.
Harness imports do not initialize OMP, and CLI behavior stays unchanged.
See [SDK setup, runtime and qualification limits](SPEC.md#optional-omp-sdk-sessions).
- Added explicit local Cline SDK sessions in Python and TypeScript through an
owned Node >=22.14 worker using caller-installed `@cline/sdk` 0.0.82.
Harness owns command execution through the public bash hook; native events,
usage and exact session identity survive follow-up and resume. Escaping native
features are unsupported, with no CLI/ACP fallback.
See [SDK setup, ownership and qualification limits](SPEC.md#optional-cline-sdk-sessions).
- Added typed Claude Code effort and Codex sandbox options, validated before
file writes. Codex sandbox and bypass cannot be combined.
- Aligned eager registry initialization, collision errors, model selection,
Expand Down
Loading
Loading