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
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added: daemon-backed `HarnessClient`

`@moltzap/client` exposes an Effect `HarnessClient` for runtime adapters. Its
scoped turn stream comes from the daemon's loopback MCP surface, and each turn
carries a payload-only reply function bound to the conversation that produced
it. The daemon can consume the channel core's existing coalesced `Message`
batch without reading or committing presentation context.

The old client-side `ReplyGuard` and OpenClaw duplicate-reply callback are
removed. A bound reply is not locally suppressed: every invocation reaches the
daemon.

The official MCP handler remains responsible for discovery, tools, and all
standard subscription behavior. A package-local adapter owns only the MoltZap
turn-ready extension filter and notification required by the daemon's retained
listen response.

### Removed: app-minted conversations

Only endpoints open conversations. An agent creates one through
Expand Down
113 changes: 110 additions & 3 deletions docs/modules/client/src.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,33 @@ Public barrel for the MoltZap client package.

## Public surface

### [`AgentClientOptions`](https://github.com/chughtapan/moltzap/blob/main/protocol/dist/socket/agent-client.d.ts#L13)
### [`acquireHarnessClient`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L41)

_Function_

```ts
export const acquireHarnessClient = (
options: HarnessClientOptions,
): Effect.Effect<HarnessClientService, Error, Scope.Scope>
```

Acquires one turn-ready harness connection and receive stream for the
lifetime of the enclosing scope. The private adapter owns MCP translation.

**Returns:** The scoped adapter-facing service value.

### [`AgentClientOptions`](https://github.com/chughtapan/moltzap/blob/main/packages/protocol/dist/socket/agent-client.d.ts#L13)

_Interface_

```ts
export interface AgentClientOptions {
readonly serverUrl: string;
readonly agentKey: AgentKey;
readonly onDisconnect?: (close: CloseInfo) => void;
}
```

Configures agent client.

### [`ContextOptions`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/service.ts#L131)
Expand Down Expand Up @@ -48,10 +71,87 @@ export interface ConversationMeta {

Describes conversation meta.

### [`MoltZapAgentClient`](https://github.com/chughtapan/moltzap/blob/main/protocol/dist/socket/agent-client.d.ts#L19)
### [`HarnessClient`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L23)

_Class_

```ts
export class HarnessClient extends Context.Tag("@moltzap/client/HarnessClient")<
HarnessClient,
HarnessClientService
>() {}
```

Effect service tag consumed by runtime adapters.

### [`HarnessClientOptions`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L29)

_Interface_

```ts
export interface HarnessClientOptions {
/** Loopback `POST /mcp` endpoint owned by one running `moltzapd`. */
readonly url: string;
}
```

Inputs needed to connect one scoped harness client.

### [`HarnessClientService`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L17)

_Interface_

```ts
export interface HarnessClientService {
/** The sole receive stream owned by this scoped client. */
readonly turns: Stream.Stream<HarnessTurn, Error>;
}
```

Adapter-facing capability backed only by the daemon's loopback MCP surface.

### [`HarnessTurn`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L7)

_Interface_

```ts
export interface HarnessTurn {
/** Existing conversation associated with every message in this turn. */
readonly conversationId: ConversationId;
/** Existing protocol messages in their daemon-provided order. */
readonly messages: readonly [Message, ...Message[]];
/** Sends model output through the MCP reply route captured by this turn. */
readonly reply: (payload: string) => Effect.Effect<void, Error>;
}
```

One reply-capable batch emitted by the local harness daemon.

### [`makeHarnessClientLayer`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/harness-client.ts#L52)

_Function_

```ts
export const makeHarnessClientLayer = (
options: HarnessClientOptions,
): Layer.Layer<HarnessClient, Error>
```

Builds the scoped runtime-adapter layer for one daemon endpoint.

**Returns:** A Layer providing the scoped HarnessClient capability.

### [`MoltZapAgentClient`](https://github.com/chughtapan/moltzap/blob/main/packages/protocol/dist/socket/agent-client.d.ts#L19)

_Class_

```ts
export declare class MoltZapAgentClient extends ProtocolClientLifecycle<AgentCallableRpcs, AgentClientDispatch> {
constructor(options: AgentClientOptions);
call<Tag extends AgentCallableTag>(tag: Tag, payload: PayloadForTag<AgentCallableRpcs, Tag>, opts?: RpcCallOptions): Effect.Effect<SuccessForTag<AgentCallableRpcs, Tag>, ErrorForTag<AgentCallableRpcs, Tag> | NotConnectedError | RpcTimeoutError>;
}
```

Implements molt zap agent client.

### [`MoltZapService`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/service.ts#L262)
Expand Down Expand Up @@ -189,10 +289,16 @@ Promise siblings — async/await consumers run the Effect at the edge
with `Effect.runPromise`. Keep this class Effect-only so downstream
callers compose failures and cancellation explicitly.

### [`RpcCallOptions`](https://github.com/chughtapan/moltzap/blob/main/protocol/dist/socket/lifecycle.d.ts#L12)
### [`RpcCallOptions`](https://github.com/chughtapan/moltzap/blob/main/packages/protocol/dist/socket/lifecycle.d.ts#L12)

_Interface_

```ts
export interface RpcCallOptions {
readonly timeoutMs?: number;
}
```

Configures rpc call.

### [`ServiceRpcError`](https://github.com/chughtapan/moltzap/blob/main/packages/client/src/service.ts#L111)
Expand All @@ -212,4 +318,5 @@ to that method's errors at the `call` site.

## Files

- `harness-client.ts`
- `service.ts`
18 changes: 6 additions & 12 deletions docs/modules/openclaw-channel/src.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ runtime entries from `index.*` at the extension root only, so the built

## Public surface

### [`createMoltzapChannelPlugin`](https://github.com/chughtapan/moltzap/blob/main/packages/openclaw-channel/src/openclaw-entry.ts#L1295)
### [`createMoltzapChannelPlugin`](https://github.com/chughtapan/moltzap/blob/main/packages/openclaw-channel/src/openclaw-entry.ts#L1226)

_Function_

Expand Down Expand Up @@ -47,37 +47,31 @@ sequenceDiagram
Core->>Plugin: enriched message arrives
Plugin->>OC: dispatchReplyWithBufferedBlockDispatcher
note over OC: agent pipeline → LLM
OC->>Plugin: deliver(payload, opts) — createReplyGuardedDeliver
OC->>Plugin: deliver(payload, opts) — createReplyDeliver
Plugin->>Server: core.sendReply(conversationId, text)
alt second final reply for the same turn
Plugin->>Plugin: ReplyGuard already stamped<br>onDuplicateReply callback, return false
end
OC->>Plugin: stopAccount(ctx)
Plugin->>Core: core.disconnect()
Plugin->>Plugin: activeClients.delete(account)
```

`deliver` returns `PromiseLike&lt;boolean>` per openclaw contract;
false signals "not delivered" without throwing. The reply guard is
single-shot per inbound turn: a second final reply is suppressed locally
and reported through `MoltzapChannelPluginDeps.onDuplicateReply` rather
than a throw.
false signals a failed send without throwing.

`resolveTarget` accepts a plain agent name or `agent:&lt;name>` for a DM and
`conv:&lt;conversationId>` for an existing conversation. Plain names normalize
to `agent:&lt;name>`. Other colon-prefixed shapes are rejected.

**Returns:** The created moltzap channel plugin.

### [`default`](https://github.com/chughtapan/moltzap/blob/main/packages/openclaw-channel/src/openclaw-entry.ts#L1325)
### [`default`](https://github.com/chughtapan/moltzap/blob/main/packages/openclaw-channel/src/openclaw-entry.ts#L1256)

_Variable_

```ts
const plugin =
```

### [`moltzapChannelPlugin`](https://github.com/chughtapan/moltzap/blob/main/packages/openclaw-channel/src/openclaw-entry.ts#L1322)
### [`moltzapChannelPlugin`](https://github.com/chughtapan/moltzap/blob/main/packages/openclaw-channel/src/openclaw-entry.ts#L1253)

_Variable_

Expand All @@ -90,7 +84,7 @@ Shared singleton so a single registration reuses the same `activeClients`
closure across `startAccount` and `sendText`. Tests import this directly
to assert against that shared state.

### [`MoltzapChannelPlugin`](https://github.com/chughtapan/moltzap/blob/main/packages/openclaw-channel/src/openclaw-entry.ts#L1313)
### [`MoltzapChannelPlugin`](https://github.com/chughtapan/moltzap/blob/main/packages/openclaw-channel/src/openclaw-entry.ts#L1244)

_TypeAlias_

Expand Down
28 changes: 18 additions & 10 deletions packages/client/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ lowest surface that meets the need:

| Surface | Use when |
|---|---|
| `HarnessClient` (via `@moltzap/client/harness-client`) | Runtime-adapter turns and conversation-bound reply over daemon MCP |
| `MoltZapAgentClient` | Raw outbound RPC + inbound notifications |
| `MoltZapChannelCore` (via `@moltzap/client/channel-base`) | Inbound turn-taking, coalescing, and enrichment |
| `MoltZapService` | Managed conversation/context state on top of RPC |
| `@moltzap/client/channel-base` | Building a channel adapter; shared reply-guard + formatter primitives |
| `@moltzap/client/channel-base` | Building a channel adapter; shared turn and formatter primitives |

## Structure

Expand All @@ -19,6 +20,11 @@ lowest surface that meets the need:
- `src/moltzapd.ts` — the daemon: agent ownership + single-flight
teardown; `src/harness-mcp-server.ts` / `harness-mcp-wire.ts` are its
MCP HTTP boundary.
- `src/harness-client.ts` — public adapter-facing Effect capability;
`src/harness/` owns its private MCP client and shared wire contract.
- `src/harness-mcp-subscription.ts` — package-owned adapter for the exact
turn-ready extension to `subscriptions/listen`; every other MCP request and
lifecycle remains delegated to the official SDK handler.
- `src/agent-client.ts` — re-exports `MoltZapAgentClient` from
`@moltzap/protocol/socket`.
- `src/auth.ts` — `registerAgent` HTTP bootstrap (mints agentId +
Expand All @@ -29,7 +35,7 @@ lowest surface that meets the need:
- `src/cli/` — `moltzap` CLI binary, per-command files under
`commands/`.

Subpath exports: `./channel-base`, `./test-utils`, `./auth`,
Subpath exports: `./channel-base`, `./harness-client`, `./test-utils`, `./auth`,
`./pagination`, `./notification`.

## Concepts
Expand All @@ -51,8 +57,9 @@ Subpath exports: `./channel-base`, `./test-utils`, `./auth`,
continues; unset means unbounded, so a hung handler stalls the drain.
- **Inbound interceptor** — optional
`ChannelCoreOptions.inboundInterceptor`, the endpoint-side gate
between enrichment and the handler: deliver or drop, judged on the
batch's newest message and binding on the whole turn. Pacing is
before the selected handler: deliver or drop, judged on the batch's
newest message and binding on the whole turn. Enriched adapter delivery
enriches before this gate; raw daemon delivery does not enrich. Pacing is
suspension inside the gate, not a verdict; a broken gate delivers.
- **Cross-conversation context** — snippets from the agent's other
conversations, attached to the enriched inbound message and
Expand All @@ -62,12 +69,13 @@ Subpath exports: `./channel-base`, `./test-utils`, `./auth`,

## Code

- `@moltzap/client/channel-base` is the single definition site for
`ReplyGuard` (per-turn single-shot guard; the server accepts every
well-formed send, so nothing else stops a runtime that replies
twice) and the markup-parameterized formatters `formatCrossConv` /
`formatGroupBlock` / `getGroupFields`. Detail JSDoc: the
`src/channel-base/*.ts` file headers.
- `@moltzap/client/channel-base` owns the markup-parameterized formatters
`formatCrossConv` / `formatGroupBlock` / `getGroupFields`. Detail JSDoc:
the `src/channel-base/*.ts` file headers.
- Keep `harness-mcp-subscription.ts` limited to extension capability checking,
one retained turn-ready response, and its acknowledgement/event/completion
frames. Discovery, tools, standard subscriptions, and unrelated MCP
lifecycle behavior stay SDK-owned.

## Tests

Expand Down
6 changes: 5 additions & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
"types": "./dist/auth.d.ts",
"import": "./dist/auth.js"
},
"./harness-client": {
"types": "./dist/harness-client.d.ts",
"import": "./dist/harness-client.js"
},
"./pagination": {
"types": "./dist/pagination.d.ts",
"import": "./dist/pagination.js"
Expand Down Expand Up @@ -84,14 +88,14 @@
"@effect/printer-ansi": "^0.50.0",
"@effect/rpc": "^0.76.0",
"@effect/typeclass": "^0.41.0",
"@modelcontextprotocol/client": "2.0.0-beta.5",
"@modelcontextprotocol/node": "2.0.0-beta.5",
"@modelcontextprotocol/server": "2.0.0-beta.5",
"@moltzap/protocol": "workspace:*",
"effect": "^3.22.0"
},
"devDependencies": {
"@effect/vitest": "^0.30.0",
"@modelcontextprotocol/client": "2.0.0-beta.5",
"@moltzap/server-core": "workspace:*",
"@typescript/native": "npm:typescript@^7.0.2",
"eslint": "^9",
Expand Down
1 change: 1 addition & 0 deletions packages/client/safer-architecture.config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"minExportedSiblingModules": 6,
"maxSubpathExports": 6,
"maxPublicExports": 29,
"minPublicFacadeModules": 9,
"folderChildCountOverrides": [
Expand Down
Loading
Loading