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

### Breaking Changes

- `@agent-relay/sdk`: `AgentRelay` events move to a multi-listener registry. Use `relay.addListener('x', handler)` / `removeListener` in place of `relay.onX = handler` — the 13 `on*` fields (`onMessageReceived`, `onMessageSent`, `onAgentSpawned`, `onAgentReleased`, `onAgentExited`, `onAgentReady`, `onWorkerOutput`, `onDeliveryUpdate`, `onAgentExitRequested`, `onAgentIdle`, `onAgentActivityChanged`, `onChannelSubscribed`, `onChannelUnsubscribed`) are removed.
- `@agent-relay/sdk`: `channelSubscribed` / `channelUnsubscribed` handlers receive a single `{ agent, channels }` object instead of positional `(agent, channels)` args.
- `@agent-relay/sdk`: new `beforeAgentSpawn` / `afterAgentSpawn` / `beforeAgentRelease` / `afterAgentRelease` call-site hooks. `beforeAgentSpawn` listeners may return a `SpawnPatch` (shallow-merged in registration order) to mutate the spawn input before the broker POST.
- Broker/SDK wire protocol is now version 2 for delivery terminal events and lifecycle event shape changes.
- `relay.spawn({ task })` now returns `success: false` and terminates the agent when task delivery fails after retries.
- `agent-relay send` now uses the orchestrator identity by default so `agent-relay replies <worker>` can correlate worker DMs.
Expand All @@ -19,6 +22,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Pass `--from` to `agent-relay send` when a script requires a specific sender identity.
- Handle `success: false` from `relay.spawn()` calls that pass `task`; spawns without a task are unchanged.
- Set `POSTHOG_PROJECT_KEY` in GitHub Actions repository variables before publishing telemetry-enabled artifacts.
- Update relay event handlers from field-assignment to `addListener`:

```ts
// Before
relay.onAgentSpawned = (agent) => log(agent.name);

// After
const off = relay.addListener('agentSpawned', (agent) => log(agent.name));
// ...later: off(); // unsubscribe
```

Channel subscribe/unsubscribe handlers receive an object: `({ agent, channels }) => ...`.

### Added

Expand Down Expand Up @@ -90,67 +105,88 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [6.3.3] - 2026-05-21

### Product Perspective

#### User-Impacting Fixes

- Detect opencode api key completion (#934) (#934)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove issue/PR references from this changelog bullet.

Line 109 includes (#934) (#934), which should be removed per changelog style rules.
As per coding guidelines, “Drop issue/PR links… unless that text clearly explains the shipped impact.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` at line 109, Edit the CHANGELOG.md entry that reads "Detect
opencode api key completion (`#934`) (`#934`)" and remove the issue/PR references so
it becomes "Detect opencode api key completion" (remove both "(`#934`)"
instances); ensure no other PR/issue tokens remain on that bullet and save the
file.


### Technical Perspective

#### Releases

- v6.3.3

---

## [6.3.2] - 2026-05-20

### Product Perspective

#### User-Impacting Fixes

- Stop worker stderr from rendering inside agent xterm (#931) (#931)

### Technical Perspective

#### Releases

- v6.3.2

---

## [6.3.1] - 2026-05-20

### Product Perspective

#### User-Impacting Fixes

- Pre-register Claude PTY workers so Relaycast MCP boots fast (#926)

### Technical Perspective

#### Dependencies & Tooling

- Retrigger flaky macOS Rust Tests
- Drop change-implying framing from PTY pre-register note

#### Releases

- v6.3.1

---

## [6.3.0] - 2026-05-20

### Technical Perspective

#### Releases

- v6.3.0

---

## [6.2.8] - 2026-05-20

### Product Perspective

#### User-Impacting Fixes

- Tighten PTY chrome scrubbing, document idle override, tame stale-state warning (#930) (#930)

### Technical Perspective

#### Releases

- v6.2.8

---

## [6.2.7] - 2026-05-20

### Technical Perspective

#### Releases

- v6.2.7

---
Expand Down
33 changes: 14 additions & 19 deletions packages/acp-bridge/src/acp-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@
import { randomUUID } from 'node:crypto';
import * as acp from '@agentclientprotocol/sdk';
import { AgentRelay, type Agent, type Message } from '@agent-relay/sdk';
import type {
ACPBridgeConfig,
SessionState,
RelayMessage,
BridgePromptResult,
} from './types.js';
import type { ACPBridgeConfig, SessionState, RelayMessage, BridgePromptResult } from './types.js';

/**
* Bounded circular cache for message deduplication.
Expand Down Expand Up @@ -145,15 +140,15 @@ export class RelayACPAgent implements acp.Agent {
private setupRelayHandlers(): void {
if (!this.relay) return;

this.relay.onMessageReceived = (msg: Message) => {
this.relay.addListener('messageReceived', (msg: Message) => {
this.handleRelayMessage({
id: msg.eventId,
from: msg.from,
body: msg.text,
thread: msg.threadId,
timestamp: Date.now(),
});
};
});
}

/**
Expand Down Expand Up @@ -408,7 +403,7 @@ export class RelayACPAgent implements acp.Agent {
};
}

if (!await this.ensureRelayReady()) {
if (!(await this.ensureRelayReady())) {
await this.connection.sessionUpdate({
sessionId,
update: {
Expand Down Expand Up @@ -454,7 +449,7 @@ export class RelayACPAgent implements acp.Agent {

// Send "thinking" indicator with target info
const targetInfo = hasTargets
? `Sending to ${targets.map(t => `@${t}`).join(', ')}...\n\n`
? `Sending to ${targets.map((t) => `@${t}`).join(', ')}...\n\n`
: 'Broadcasting to all agents...\n\n';

await this.connection.sessionUpdate({
Expand Down Expand Up @@ -503,13 +498,13 @@ export class RelayACPAgent implements acp.Agent {
await this.connection.sessionUpdate({
sessionId,
update: {
sessionUpdate: 'agent_message_chunk',
content: {
type: 'text',
text: 'Failed to send message to relay agents. Please check the relay broker connection.',
},
sessionUpdate: 'agent_message_chunk',
content: {
type: 'text',
text: 'Failed to send message to relay agents. Please check the relay broker connection.',
},
});
},
});

return {
success: false,
Expand Down Expand Up @@ -731,7 +726,7 @@ export class RelayACPAgent implements acp.Agent {
return true;
}

if (!await this.ensureRelayReady()) {
if (!(await this.ensureRelayReady())) {
await this.sendTextUpdate(sessionId, 'Relay broker is not connected (cannot spawn).');
return true;
}
Expand Down Expand Up @@ -769,7 +764,7 @@ export class RelayACPAgent implements acp.Agent {
return true;
}

if (!await this.ensureRelayReady()) {
if (!(await this.ensureRelayReady())) {
await this.sendTextUpdate(sessionId, 'Relay broker is not connected (cannot release).');
return true;
}
Expand All @@ -794,7 +789,7 @@ export class RelayACPAgent implements acp.Agent {
}

private async handleListAgentsCommand(sessionId: string): Promise<boolean> {
if (!await this.ensureRelayReady()) {
if (!(await this.ensureRelayReady())) {
await this.sendTextUpdate(sessionId, 'Relay broker is not connected (cannot list agents).');
return true;
}
Expand Down
16 changes: 8 additions & 8 deletions packages/openclaw/src/spawn/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export class ProcessSpawnProvider implements SpawnProvider {
},
cwd: workspacePath,
stdio: ['pipe', 'pipe', 'pipe'],
},
}
);

gatewayProcess.stderr?.on('data', (data: Buffer) => {
Expand Down Expand Up @@ -176,22 +176,22 @@ export class ProcessSpawnProvider implements SpawnProvider {
cli: 'node',
args: [bridgePath],
channels,
task: options.systemPrompt
? `${options.systemPrompt}\n\n${identityTask}`
: identityTask,
task: options.systemPrompt ? `${options.systemPrompt}\n\n${identityTask}` : identityTask,
});

relay.onAgentExited = (agent) => {
process.stderr.write(`[spawn:${options.name}] Agent exited: ${agent.name} code=${agent.exitCode ?? 'none'}\n`);
};
relay.addListener('agentExited', (agent) => {
process.stderr.write(
`[spawn:${options.name}] Agent exited: ${agent.name} code=${agent.exitCode ?? 'none'}\n`
);
});
} catch (err) {
// If SDK broker spawn fails, clean up gateway and propagate
gatewayProcess.kill('SIGTERM');
if (relay) {
await relay.shutdown().catch(() => {});
}
throw new Error(
`Failed to start broker for "${options.name}": ${err instanceof Error ? err.message : String(err)}`,
`Failed to start broker for "${options.name}": ${err instanceof Error ? err.message : String(err)}`
);
}

Expand Down
16 changes: 8 additions & 8 deletions packages/sdk/src/__tests__/agent-activity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ function wireRelay(relay: AgentRelay, client: ReturnType<typeof createMockFacade
(relay as any).wireEvents(client);
}

describe('AgentRelay onAgentActivityChanged', () => {
describe('AgentRelay agentActivityChanged listener', () => {
it('emits active on first delivery event', () => {
const relay = new AgentRelay();
const { client, emit } = createMockFacadeClient();
wireRelay(relay, client);
const changes: AgentActivityChange[] = [];
relay.onAgentActivityChanged = (change) => changes.push(change);
relay.addListener('agentActivityChanged', (change) => changes.push(change));

emit({ kind: 'delivery_queued', name: 'worker-1', delivery_id: 'd1', event_id: 'e1', timestamp: 1 });

Expand All @@ -53,7 +53,7 @@ describe('AgentRelay onAgentActivityChanged', () => {
const { client, emit } = createMockFacadeClient();
wireRelay(relay, client);
const changes: AgentActivityChange[] = [];
relay.onAgentActivityChanged = (change) => changes.push(change);
relay.addListener('agentActivityChanged', (change) => changes.push(change));

emit({ kind: 'delivery_queued', name: 'worker-1', delivery_id: 'd1', event_id: 'e1', timestamp: 1 });
emit({ kind: 'delivery_injected', name: 'worker-1', delivery_id: 'd1', event_id: 'e1', timestamp: 2 });
Expand All @@ -69,7 +69,7 @@ describe('AgentRelay onAgentActivityChanged', () => {
const { client, emit } = createMockFacadeClient();
wireRelay(relay, client);
const changes: AgentActivityChange[] = [];
relay.onAgentActivityChanged = (change) => changes.push(change);
relay.addListener('agentActivityChanged', (change) => changes.push(change));

emit({ kind: 'delivery_queued', name: 'worker-1', delivery_id: 'd1', event_id: 'e1', timestamp: 1 });
emit({
Expand All @@ -94,7 +94,7 @@ describe('AgentRelay onAgentActivityChanged', () => {
const { client, emit } = createMockFacadeClient();
wireRelay(relay, client);
const changes: AgentActivityChange[] = [];
relay.onAgentActivityChanged = (change) => changes.push(change);
relay.addListener('agentActivityChanged', (change) => changes.push(change));

emit({ kind: 'delivery_queued', name: 'worker-1', delivery_id: 'd1', event_id: 'e1', timestamp: 1 });
emit({ kind: 'agent_idle', name: 'worker-1', idle_secs: 30 });
Expand All @@ -113,7 +113,7 @@ describe('AgentRelay onAgentActivityChanged', () => {
const { client, emit } = createMockFacadeClient();
wireRelay(relay, client);
const changes: AgentActivityChange[] = [];
relay.onAgentActivityChanged = (change) => changes.push(change);
relay.addListener('agentActivityChanged', (change) => changes.push(change));

emit({ kind: 'delivery_queued', name: 'worker-1', delivery_id: 'd1', event_id: 'e1', timestamp: 1 });
emit({ kind: 'agent_idle', name: 'worker-1', idle_secs: 30 });
Expand Down Expand Up @@ -142,7 +142,7 @@ describe('AgentRelay onAgentActivityChanged', () => {
const { client, emit } = createMockFacadeClient();
wireRelay(relay, client);
const changes: AgentActivityChange[] = [];
relay.onAgentActivityChanged = (change) => changes.push(change);
relay.addListener('agentActivityChanged', (change) => changes.push(change));

emit({ kind: 'delivery_queued', name: 'worker-1', delivery_id: 'd1', event_id: 'e1', timestamp: 1 });
emit({ kind: 'delivery_queued', name: 'worker-1', delivery_id: 'd2', event_id: 'e2', timestamp: 2 });
Expand All @@ -166,7 +166,7 @@ describe('AgentRelay onAgentActivityChanged', () => {
const { client, emit } = createMockFacadeClient();
wireRelay(relay, client);
const changes: AgentActivityChange[] = [];
relay.onAgentActivityChanged = (change) => changes.push(change);
relay.addListener('agentActivityChanged', (change) => changes.push(change));

emit({ kind: 'delivery_queued', name: 'worker-1', delivery_id: 'd1', event_id: 'e1', timestamp: 1 });
emit({ kind: 'agent_exited', name: 'worker-1', code: 0, signal: undefined });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,7 @@ vi.mock('@relaycast/sdk', () => ({
const mockRelayInstance = {
shutdown: vi.fn().mockResolvedValue(undefined),
onBrokerStderr: vi.fn().mockReturnValue(() => {}),
onMessageReceived: null as any,
onAgentSpawned: null as any,
onAgentReleased: null as any,
onAgentExited: null as any,
onAgentIdle: null as any,
onWorkerOutput: null as any,
onDeliveryUpdate: null as any,
addListener: vi.fn(() => () => {}),
};

vi.mock('../relay.js', () => ({
Expand Down
Loading
Loading