@relayflows/sdk 2.0.22 → 2.0.24
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.
- package/dist/agent-artifacts.d.ts +18 -9
- package/dist/agent-artifacts.d.ts.map +1 -1
- package/dist/agent-artifacts.js +27 -11
- package/dist/agent-artifacts.js.map +1 -1
- package/dist/authored-flow-error.d.ts +33 -1
- package/dist/authored-flow-error.d.ts.map +1 -1
- package/dist/authored-flow-error.js +20 -1
- package/dist/authored-flow-error.js.map +1 -1
- package/dist/authored-flow-executor.d.ts.map +1 -1
- package/dist/authored-flow-executor.js +4 -1
- package/dist/authored-flow-executor.js.map +1 -1
- package/dist/authored-node-entry.js +5 -1
- package/dist/authored-node-entry.js.map +1 -1
- package/dist/authored-node-runner.d.ts +11 -0
- package/dist/authored-node-runner.d.ts.map +1 -1
- package/dist/authored-node-runner.js +30 -1
- package/dist/authored-node-runner.js.map +1 -1
- package/dist/authored-root.js +3 -1
- package/dist/authored-root.js.map +1 -1
- package/dist/authored-source-authority.js +13 -0
- package/dist/authored-source-authority.js.map +1 -1
- package/dist/authored-step-index.d.ts +82 -0
- package/dist/authored-step-index.d.ts.map +1 -0
- package/dist/authored-step-index.js +120 -0
- package/dist/authored-step-index.js.map +1 -0
- package/dist/authored-step-output.d.ts +23 -2
- package/dist/authored-step-output.d.ts.map +1 -1
- package/dist/authored-step-output.js +47 -5
- package/dist/authored-step-output.js.map +1 -1
- package/dist/authored-worker-step.d.ts +10 -2
- package/dist/authored-worker-step.d.ts.map +1 -1
- package/dist/authored-worker-step.js +81 -7
- package/dist/authored-worker-step.js.map +1 -1
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +67 -12
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/cloud-read.d.ts +31 -0
- package/dist/cli/cloud-read.d.ts.map +1 -0
- package/dist/cli/cloud-read.js +482 -0
- package/dist/cli/cloud-read.js.map +1 -0
- package/dist/cli/run.d.ts +2 -0
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +53 -25
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/status.d.ts +9 -0
- package/dist/cli/status.d.ts.map +1 -1
- package/dist/cli/status.js +14 -0
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/step-failure.d.ts +26 -0
- package/dist/cli/step-failure.d.ts.map +1 -1
- package/dist/cli/step-failure.js +68 -3
- package/dist/cli/step-failure.js.map +1 -1
- package/dist/cli-commands.d.ts +29 -1
- package/dist/cli-commands.d.ts.map +1 -1
- package/dist/cli-commands.js +23 -1
- package/dist/cli-commands.js.map +1 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +19 -2
- package/dist/cli.js.map +1 -1
- package/dist/cloud-http.d.ts +12 -0
- package/dist/cloud-http.d.ts.map +1 -1
- package/dist/cloud-http.js +14 -6
- package/dist/cloud-http.js.map +1 -1
- package/dist/cloud-read.d.ts +154 -0
- package/dist/cloud-read.d.ts.map +1 -0
- package/dist/cloud-read.js +373 -0
- package/dist/cloud-read.js.map +1 -0
- package/dist/cloud-transcript.d.ts +94 -0
- package/dist/cloud-transcript.d.ts.map +1 -0
- package/dist/cloud-transcript.js +342 -0
- package/dist/cloud-transcript.js.map +1 -0
- package/dist/communication/environment.d.ts +3 -0
- package/dist/communication/environment.d.ts.map +1 -0
- package/dist/communication/environment.js +34 -0
- package/dist/communication/environment.js.map +1 -0
- package/dist/communication/history.d.ts +6 -0
- package/dist/communication/history.d.ts.map +1 -0
- package/dist/communication/history.js +38 -0
- package/dist/communication/history.js.map +1 -0
- package/dist/communication/local.d.ts +6 -0
- package/dist/communication/local.d.ts.map +1 -0
- package/dist/communication/local.js +56 -0
- package/dist/communication/local.js.map +1 -0
- package/dist/communication/preflight.d.ts +6 -0
- package/dist/communication/preflight.d.ts.map +1 -0
- package/dist/communication/preflight.js +37 -0
- package/dist/communication/preflight.js.map +1 -0
- package/dist/communication/projection.d.ts +8 -0
- package/dist/communication/projection.d.ts.map +1 -0
- package/dist/communication/projection.js +23 -0
- package/dist/communication/projection.js.map +1 -0
- package/dist/communication/relay.d.ts +78 -0
- package/dist/communication/relay.d.ts.map +1 -0
- package/dist/communication/relay.js +89 -0
- package/dist/communication/relay.js.map +1 -0
- package/dist/communication/session.d.ts +24 -0
- package/dist/communication/session.d.ts.map +1 -0
- package/dist/communication/session.js +100 -0
- package/dist/communication/session.js.map +1 -0
- package/dist/communication/spec.d.ts +20 -0
- package/dist/communication/spec.d.ts.map +1 -0
- package/dist/communication/spec.js +113 -0
- package/dist/communication/spec.js.map +1 -0
- package/dist/communication/tool.d.ts +3 -0
- package/dist/communication/tool.d.ts.map +1 -0
- package/dist/communication/tool.js +23 -0
- package/dist/communication/tool.js.map +1 -0
- package/dist/communication/tools.d.ts +11 -0
- package/dist/communication/tools.d.ts.map +1 -0
- package/dist/communication/tools.js +65 -0
- package/dist/communication/tools.js.map +1 -0
- package/dist/communication/worker.d.ts +6 -0
- package/dist/communication/worker.d.ts.map +1 -0
- package/dist/communication/worker.js +120 -0
- package/dist/communication/worker.js.map +1 -0
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +8 -1
- package/dist/compile.js.map +1 -1
- package/dist/failure-kinds.d.ts +16 -1
- package/dist/failure-kinds.d.ts.map +1 -1
- package/dist/failure-kinds.js +1 -0
- package/dist/failure-kinds.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/journal-client.d.ts +4 -1
- package/dist/journal-client.d.ts.map +1 -1
- package/dist/journal-client.js +11 -2
- package/dist/journal-client.js.map +1 -1
- package/dist/preflight.d.ts +13 -2
- package/dist/preflight.d.ts.map +1 -1
- package/dist/preflight.js +23 -6
- package/dist/preflight.js.map +1 -1
- package/dist/protocol.d.ts +39 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/spec.d.ts +8 -0
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js.map +1 -1
- package/dist/worker-cli.d.ts.map +1 -1
- package/dist/worker-cli.js +29 -50
- package/dist/worker-cli.js.map +1 -1
- package/dist/worker.d.ts +1 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +10 -1
- package/dist/worker.js.map +1 -1
- package/dist/wrapper-session.d.ts +8 -0
- package/dist/wrapper-session.d.ts.map +1 -1
- package/dist/wrapper-session.js +156 -23
- package/dist/wrapper-session.js.map +1 -1
- package/package.json +14 -2
- package/src/agent-artifacts.ts +27 -11
- package/src/authored-flow-error.ts +17 -0
- package/src/authored-flow-executor.ts +4 -1
- package/src/authored-node-entry.ts +5 -1
- package/src/authored-node-runner.ts +33 -1
- package/src/authored-root.ts +2 -1
- package/src/authored-source-authority.ts +13 -0
- package/src/authored-step-index.ts +157 -0
- package/src/authored-step-output.ts +66 -14
- package/src/authored-worker-step.ts +88 -15
- package/src/cli/check.ts +69 -11
- package/src/cli/cloud-read.ts +518 -0
- package/src/cli/run.ts +43 -25
- package/src/cli/status.ts +20 -0
- package/src/cli/step-failure.ts +68 -3
- package/src/cli-commands.ts +23 -1
- package/src/cli.ts +20 -1
- package/src/cloud-http.ts +32 -6
- package/src/cloud-read.ts +515 -0
- package/src/cloud-transcript.ts +422 -0
- package/src/communication/environment.ts +33 -0
- package/src/communication/history.ts +38 -0
- package/src/communication/local.ts +41 -0
- package/src/communication/preflight.ts +34 -0
- package/src/communication/projection.ts +23 -0
- package/src/communication/relay.ts +102 -0
- package/src/communication/session.ts +84 -0
- package/src/communication/spec.ts +106 -0
- package/src/communication/tool.ts +22 -0
- package/src/communication/tools.ts +50 -0
- package/src/communication/worker.ts +95 -0
- package/src/compile.ts +4 -1
- package/src/failure-kinds.ts +16 -0
- package/src/index.ts +10 -1
- package/src/journal-client.ts +12 -2
- package/src/preflight.ts +36 -7
- package/src/protocol.ts +15 -0
- package/src/spec.ts +2 -0
- package/src/worker-cli.ts +28 -47
- package/src/worker.ts +10 -0
- package/src/wrapper-session.ts +159 -24
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
2
|
+
import { mkdir } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { createProjection } from './projection.js';
|
|
5
|
+
import { brokerEnvironment } from './environment.js';
|
|
6
|
+
|
|
7
|
+
export interface RelayEvent { kind: string; name?: string; event_id?: string; delivery_id?: string; verification?: string; [key: string]: unknown }
|
|
8
|
+
export interface RelayHandle {
|
|
9
|
+
name: string;
|
|
10
|
+
generation?: string;
|
|
11
|
+
waitForReady(timeout?: number): Promise<{ reason: string }>;
|
|
12
|
+
release(reason?: string, options?: { deleteIdentity?: boolean }): Promise<unknown>;
|
|
13
|
+
}
|
|
14
|
+
export interface RelayBroker {
|
|
15
|
+
spawnPty(input: Record<string, unknown>): Promise<RelayHandle>;
|
|
16
|
+
onEvent(callback: (event: RelayEvent) => void): () => void;
|
|
17
|
+
shutdown(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export interface RelayMessaging {
|
|
20
|
+
agents: { register(input: { name: string; type: string }): Promise<{ token: string }>; delete(name: string): Promise<void> };
|
|
21
|
+
channels: { create(input: { name: string }): Promise<unknown>; get(name: string): Promise<unknown>; join(name: string): Promise<void> };
|
|
22
|
+
messages: {
|
|
23
|
+
direct(input: { to: string; text: string; idempotencyKey: string; metadata: Record<string, unknown>; mode: 'wait' }): Promise<{ id: string }>;
|
|
24
|
+
send(input: { channel: string; text: string; idempotencyKey: string; metadata: Record<string, unknown> }): Promise<unknown>;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
interface RelayModules {
|
|
28
|
+
HarnessDriverClient: { spawn(options: Record<string, unknown>): Promise<RelayBroker> };
|
|
29
|
+
RelaycastMessagingClient: new (options: Record<string, unknown>) => RelayMessaging;
|
|
30
|
+
}
|
|
31
|
+
export async function loadRelayModules(): Promise<RelayModules> {
|
|
32
|
+
// Variable imports keep optional packages out of ordinary execution and bundles.
|
|
33
|
+
const driver = '@agent-relay/harness-driver';
|
|
34
|
+
const sdk = '@agent-relay/sdk';
|
|
35
|
+
try {
|
|
36
|
+
const [harness, messaging] = await Promise.all([import(driver), import(sdk)]);
|
|
37
|
+
return { HarnessDriverClient: harness.HarnessDriverClient, RelaycastMessagingClient: messaging.RelaycastMessagingClient };
|
|
38
|
+
} catch {
|
|
39
|
+
throw new Error('Agent communication requires optional @agent-relay/harness-driver and @agent-relay/sdk packages (>=12.3.1).');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function relayWorkspaceKey(env = process.env): string {
|
|
43
|
+
const key = env.RELAY_API_KEY?.trim();
|
|
44
|
+
if (!key?.startsWith('rk_live_')) throw new Error('Agent communication requires RELAY_API_KEY for an existing workspace.');
|
|
45
|
+
return key;
|
|
46
|
+
}
|
|
47
|
+
export interface RelayRuntime {
|
|
48
|
+
broker: RelayBroker;
|
|
49
|
+
messaging: RelayMessaging;
|
|
50
|
+
prefix: string;
|
|
51
|
+
channel: string;
|
|
52
|
+
project: ReturnType<typeof createProjection>;
|
|
53
|
+
close(): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
const runtimes = new Map<string, { references: number; runtime: Promise<RelayRuntime> }>();
|
|
56
|
+
export async function acquireRelayRuntime(dataDir: string, runId: string): Promise<RelayRuntime> {
|
|
57
|
+
const key = `${dataDir}\0${runId}`;
|
|
58
|
+
let entry = runtimes.get(key);
|
|
59
|
+
if (!entry) {
|
|
60
|
+
entry = { references: 0, runtime: createRuntime(dataDir, runId) };
|
|
61
|
+
runtimes.set(key, entry);
|
|
62
|
+
}
|
|
63
|
+
entry.references++;
|
|
64
|
+
try {
|
|
65
|
+
const runtime = await entry.runtime;
|
|
66
|
+
let closed = false;
|
|
67
|
+
return { ...runtime, close: async () => {
|
|
68
|
+
if (closed) return;
|
|
69
|
+
closed = true;
|
|
70
|
+
if (--entry!.references === 0) { runtimes.delete(key); await runtime.close(); }
|
|
71
|
+
} };
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (--entry.references === 0) runtimes.delete(key);
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function createRuntime(dataDir: string, runId: string): Promise<RelayRuntime> {
|
|
78
|
+
const workspaceKey = relayWorkspaceKey();
|
|
79
|
+
const { HarnessDriverClient, RelaycastMessagingClient } = await loadRelayModules();
|
|
80
|
+
const hash = createHash('sha256').update(runId).digest('hex').slice(0, 12);
|
|
81
|
+
const prefix = `flow-${hash}-${randomUUID().slice(0, 8)}`;
|
|
82
|
+
const channel = `wf-${runId.toLowerCase()}`;
|
|
83
|
+
const options = { workspaceKey, ...(process.env.RELAY_BASE_URL ? { baseUrl: process.env.RELAY_BASE_URL } : {}) };
|
|
84
|
+
const workspace = new RelaycastMessagingClient(options);
|
|
85
|
+
const publisher = `${prefix}-journal`;
|
|
86
|
+
const registration = await workspace.agents.register({ name: publisher, type: 'agent' });
|
|
87
|
+
let broker: RelayBroker | undefined;
|
|
88
|
+
try {
|
|
89
|
+
const messaging = new RelaycastMessagingClient({ ...options, agentToken: registration.token });
|
|
90
|
+
const cwd = join(dataDir, 'communication', prefix);
|
|
91
|
+
await mkdir(cwd, { recursive: true, mode: 0o700 });
|
|
92
|
+
broker = await HarnessDriverClient.spawn({ workspaceKey, brokerName: prefix, cwd, channels: [],
|
|
93
|
+
...(process.env.RELAYFLOW_RELAY_BROKER_BIN ? { binaryPath: process.env.RELAYFLOW_RELAY_BROKER_BIN } : {}),
|
|
94
|
+
env: brokerEnvironment(process.env) });
|
|
95
|
+
return { broker, messaging, prefix, channel, project: createProjection(messaging, channel, runId), close: async () => {
|
|
96
|
+
try { await broker!.shutdown(); } finally { await workspace.agents.delete(publisher); }
|
|
97
|
+
} };
|
|
98
|
+
} catch (error) {
|
|
99
|
+
try { await broker?.shutdown(); } finally { await workspace.agents.delete(publisher); }
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import type { JournalClient } from '../journal-client.js';
|
|
3
|
+
import type { StepDispatchEvent } from '../protocol.js';
|
|
4
|
+
import type { CommunicationInstruction } from './spec.js';
|
|
5
|
+
import { channelName } from './spec.js';
|
|
6
|
+
import type { RelayRuntime } from './relay.js';
|
|
7
|
+
import type { CommunicationToolRequest } from './tools.js';
|
|
8
|
+
|
|
9
|
+
/** Journal operations belong to the dispatched worker connection, never the broker. */
|
|
10
|
+
export class CommunicationSession {
|
|
11
|
+
private readonly pending = new Map<string, number>();
|
|
12
|
+
private finished = false;
|
|
13
|
+
private serial: Promise<unknown> = Promise.resolve();
|
|
14
|
+
constructor(private readonly client: JournalClient, private readonly dispatch: StepDispatchEvent,
|
|
15
|
+
private readonly spec: CommunicationInstruction, private readonly relay: RelayRuntime,
|
|
16
|
+
private readonly complete: (summary: string) => void,
|
|
17
|
+
private readonly fatal: (error: unknown) => void = () => {}) {}
|
|
18
|
+
|
|
19
|
+
private identity(from: string, to: string) {
|
|
20
|
+
const { run_id, step_id, attempt, idempotency_key } = this.dispatch;
|
|
21
|
+
return { run_id, step_id, attempt, idempotency_key, channel: channelName(from, to) };
|
|
22
|
+
}
|
|
23
|
+
private lock<T>(work: () => Promise<T>): Promise<T> {
|
|
24
|
+
const next = this.serial.then(work);
|
|
25
|
+
this.serial = next.catch(() => {});
|
|
26
|
+
return next;
|
|
27
|
+
}
|
|
28
|
+
private async journal<T>(write: () => Promise<T>): Promise<T> {
|
|
29
|
+
try { return await write(); }
|
|
30
|
+
catch (error) { this.fatal(error); throw error; }
|
|
31
|
+
}
|
|
32
|
+
invoke(request: CommunicationToolRequest): Promise<unknown> {
|
|
33
|
+
return this.lock(async () => {
|
|
34
|
+
if (this.finished) throw new Error('Communication session has completed');
|
|
35
|
+
const { operation, values } = request;
|
|
36
|
+
const [peer, id, text] = values;
|
|
37
|
+
if (operation === 'send') {
|
|
38
|
+
if (values.length !== 3 || !peer || !this.spec.outgoing.includes(peer) || !id || !text)
|
|
39
|
+
throw new Error('Use send DECLARED_PEER STABLE_MESSAGE_ID TEXT');
|
|
40
|
+
// Only the kernel deduplicates sends across worker attempts.
|
|
41
|
+
return this.journal(() => this.client.channelAppend({ ...this.identity(this.dispatch.step_id, peer), message_id: id, message: text }));
|
|
42
|
+
}
|
|
43
|
+
if (operation === 'ack') {
|
|
44
|
+
if (values.length !== 2 || !peer || !this.spec.incoming.includes(peer)
|
|
45
|
+
|| !Number.isSafeInteger(Number(id)) || this.pending.get(peer) !== Number(id))
|
|
46
|
+
throw new Error('Use ack PEER DELIVERY_SEQ from a received message');
|
|
47
|
+
const result = await this.journal(() => this.client.channelAck({ ...this.identity(peer, this.dispatch.step_id), delivery_seq: Number(id) }));
|
|
48
|
+
this.pending.delete(peer);
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
if (operation === 'complete') {
|
|
52
|
+
if (values.length !== 1 || !peer) throw new Error('Use complete SUMMARY');
|
|
53
|
+
if (this.pending.size) throw new Error('Acknowledge processed messages before completing');
|
|
54
|
+
this.finished = true;
|
|
55
|
+
// Let the tool response flush before releasing its managed process.
|
|
56
|
+
setTimeout(() => this.complete(peer), 100);
|
|
57
|
+
return { completed: true };
|
|
58
|
+
}
|
|
59
|
+
throw new Error('Unknown communication operation');
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
pump(): Promise<void> {
|
|
63
|
+
return this.lock(async () => {
|
|
64
|
+
if (this.finished) return;
|
|
65
|
+
for (const peer of this.spec.incoming) {
|
|
66
|
+
if (this.pending.has(peer)) continue;
|
|
67
|
+
const entry = await this.client.channelReceive(this.identity(peer, this.dispatch.step_id));
|
|
68
|
+
if (!entry) continue;
|
|
69
|
+
const key = createHash('sha256').update(JSON.stringify([
|
|
70
|
+
this.dispatch.run_id, this.dispatch.step_id, this.dispatch.attempt, entry.seq,
|
|
71
|
+
])).digest('hex');
|
|
72
|
+
const text = `Flow message from ${peer}; delivery_seq=${entry.seq}.\n${JSON.stringify(entry.payload.message)}\nAfter processing, use the flow helper: ack ${peer} ${entry.seq}.`;
|
|
73
|
+
const metadata = { run_id: this.dispatch.run_id, step_id: this.dispatch.step_id, delivery_seq: entry.seq };
|
|
74
|
+
// A successful injection is not a processing acknowledgement.
|
|
75
|
+
await this.relay.messaging.messages.direct({ to: `${this.relay.prefix}-${this.dispatch.step_id}`,
|
|
76
|
+
text, metadata, idempotencyKey: key, mode: 'wait' });
|
|
77
|
+
this.pending.set(peer, entry.seq);
|
|
78
|
+
this.relay.project({
|
|
79
|
+
text: `${peer} -> ${this.dispatch.step_id}: ${JSON.stringify(entry.payload.message)}`,
|
|
80
|
+
metadata, idempotencyKey: `${key}-projection` });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { StepSpec } from '../spec.js';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
|
|
4
|
+
export const COMMUNICATION_TYPE = 'relayflows.communication.v1';
|
|
5
|
+
export interface CommunicationEdge { from: string; to: string }
|
|
6
|
+
export interface CommunicationInstruction {
|
|
7
|
+
type: typeof COMMUNICATION_TYPE;
|
|
8
|
+
instruction: string;
|
|
9
|
+
incoming: string[];
|
|
10
|
+
outgoing: string[];
|
|
11
|
+
timeoutMs: number;
|
|
12
|
+
}
|
|
13
|
+
const record = (v: unknown): v is Record<string, unknown> => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
14
|
+
const name = (v: unknown): v is string => typeof v === 'string' && /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(v);
|
|
15
|
+
export function channelName(from: string, to: string): string {
|
|
16
|
+
return `a2a-${createHash('sha256').update(JSON.stringify([from, to])).digest('hex').slice(0, 24)}`;
|
|
17
|
+
}
|
|
18
|
+
export function communicationInstruction(instruction: unknown): CommunicationInstruction | undefined {
|
|
19
|
+
if (typeof instruction !== 'string' || !instruction.startsWith('{')) return;
|
|
20
|
+
let value: unknown;
|
|
21
|
+
try { value = JSON.parse(instruction); } catch { return; }
|
|
22
|
+
if (!record(value) || value.type !== COMMUNICATION_TYPE) return;
|
|
23
|
+
if (Object.keys(value).some(k => !['type', 'instruction', 'incoming', 'outgoing', 'timeoutMs'].includes(k))
|
|
24
|
+
|| typeof value.instruction !== 'string' || !Array.isArray(value.incoming) || !value.incoming.every(name)
|
|
25
|
+
|| !Array.isArray(value.outgoing) || !value.outgoing.every(name)
|
|
26
|
+
|| value.incoming.length + value.outgoing.length === 0
|
|
27
|
+
|| !Number.isSafeInteger(value.timeoutMs) || Number(value.timeoutMs) < 1 || Number(value.timeoutMs) > 900_000) {
|
|
28
|
+
throw new Error('Invalid agent communication instruction');
|
|
29
|
+
}
|
|
30
|
+
return value as unknown as CommunicationInstruction;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Authoring sugar only: kernel vocabulary remains agent + declared streams. */
|
|
34
|
+
export function expandCommunication(value: unknown): unknown {
|
|
35
|
+
if (!record(value) || !('communication' in value)) return value;
|
|
36
|
+
const { communication, ...flow } = value;
|
|
37
|
+
if (!record(communication) || Object.keys(communication).some(k => !['links', 'timeoutMs'].includes(k))
|
|
38
|
+
|| !Array.isArray(communication.links) || !Array.isArray(flow.steps)) {
|
|
39
|
+
throw new Error('communication requires links: [{ from, to }] and optional timeoutMs');
|
|
40
|
+
}
|
|
41
|
+
const timeoutMs = communication.timeoutMs ?? 300_000;
|
|
42
|
+
if (!Number.isSafeInteger(timeoutMs) || Number(timeoutMs) < 1 || Number(timeoutMs) > 900_000) {
|
|
43
|
+
throw new Error('communication.timeoutMs must be between 1 and 900000');
|
|
44
|
+
}
|
|
45
|
+
const steps = flow.steps as Record<string, unknown>[];
|
|
46
|
+
const byId = new Map(steps.filter(record).map(step => [step.id, step]));
|
|
47
|
+
const edges: CommunicationEdge[] = [];
|
|
48
|
+
for (const link of communication.links) {
|
|
49
|
+
if (!record(link) || Object.keys(link).some(k => !['from', 'to'].includes(k))
|
|
50
|
+
|| !name(link.from) || !name(link.to) || link.from === link.to
|
|
51
|
+
|| byId.get(link.from)?.type !== 'agent' || byId.get(link.to)?.type !== 'agent') {
|
|
52
|
+
throw new Error('communication links must name two different agent step ids');
|
|
53
|
+
}
|
|
54
|
+
if (edges.some(e => e.from === link.from && e.to === link.to)) throw new Error('Duplicate communication link');
|
|
55
|
+
edges.push({ from: link.from, to: link.to });
|
|
56
|
+
}
|
|
57
|
+
const ancestors = (id: string, visited = new Set<string>()): Set<string> => {
|
|
58
|
+
if (visited.has(id)) return visited;
|
|
59
|
+
visited.add(id);
|
|
60
|
+
const step = byId.get(id);
|
|
61
|
+
for (const dependency of Array.isArray(step?.dependsOn) ? step.dependsOn : []) {
|
|
62
|
+
if (typeof dependency === 'string') ancestors(dependency, visited);
|
|
63
|
+
}
|
|
64
|
+
return visited;
|
|
65
|
+
};
|
|
66
|
+
for (const edge of edges) {
|
|
67
|
+
if (ancestors(edge.from).has(edge.to) || ancestors(edge.to).has(edge.from)) {
|
|
68
|
+
throw new Error('Communicating agents must be concurrent, not dependency ancestors');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { ...flow, steps: steps.map(step => {
|
|
72
|
+
const incoming = edges.filter(e => e.to === step.id).map(e => e.from);
|
|
73
|
+
const outgoing = edges.filter(e => e.from === step.id).map(e => e.to);
|
|
74
|
+
if (!incoming.length && !outgoing.length) return step;
|
|
75
|
+
if (step.transport === 'relay') throw new Error('communication cannot be combined with task transport: relay');
|
|
76
|
+
if (typeof step.instruction !== 'string') throw new Error('Communicating agents require an instruction');
|
|
77
|
+
const surfaces = record(step.surfaces) ? step.surfaces : {};
|
|
78
|
+
const streams = Array.isArray(surfaces.streams) ? surfaces.streams : [];
|
|
79
|
+
return { ...step,
|
|
80
|
+
instruction: JSON.stringify({ type: COMMUNICATION_TYPE, instruction: step.instruction, incoming, outgoing, timeoutMs }),
|
|
81
|
+
// One writer per directed channel; shared writable channels serialize agents.
|
|
82
|
+
surfaces: { ...surfaces, streams: [...streams,
|
|
83
|
+
{ stream: channelName(String(step.id), '$receipts') },
|
|
84
|
+
...outgoing.map(to => ({ stream: channelName(String(step.id), to) }))] },
|
|
85
|
+
};
|
|
86
|
+
}) };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Check normalized dependencies too, including implicit input bindings. */
|
|
90
|
+
export function validateCommunicationTopology(steps: StepSpec[]): void {
|
|
91
|
+
const byId = new Map(steps.map(step => [step.id, step]));
|
|
92
|
+
const communicating = steps.filter(step => step.type === 'agent' && communicationInstruction(step.instruction));
|
|
93
|
+
const depends = (id: string, target: string, visited = new Set<string>()): boolean => {
|
|
94
|
+
if (visited.has(id)) return false;
|
|
95
|
+
visited.add(id);
|
|
96
|
+
return byId.get(id)?.dependsOn?.some(parent => parent === target || depends(parent, target, visited)) ?? false;
|
|
97
|
+
};
|
|
98
|
+
for (const a of communicating) for (const b of communicating) {
|
|
99
|
+
if (a.id === b.id || a.type !== 'agent' || b.type !== 'agent') continue;
|
|
100
|
+
if (depends(a.id, b.id)) throw new Error('Communicating agents must be concurrent, not dependency ancestors');
|
|
101
|
+
if (a.surfaces?.streams?.some(s => b.surfaces?.streams?.some(t => s.stream === t.stream))
|
|
102
|
+
|| a.surfaces?.workspace?.some(s => b.surfaces?.workspace?.some(t => s.surface === t.surface))) {
|
|
103
|
+
throw new Error('Communicating agents cannot share writable surfaces: the kernel would serialize them');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Standalone helper source also works when the SDK is bundled into a binary. */
|
|
2
|
+
export const toolSource = String.raw`
|
|
3
|
+
import { createConnection } from 'node:net';
|
|
4
|
+
import { once } from 'node:events';
|
|
5
|
+
async function main() {
|
|
6
|
+
const path = process.env.RELAYFLOW_COMMUNICATION_SOCKET;
|
|
7
|
+
const token = process.env.RELAYFLOW_COMMUNICATION_TOKEN;
|
|
8
|
+
if (!path || !token) throw new Error('No communication session attached to this agent');
|
|
9
|
+
const [operation, ...values] = process.argv.slice(2);
|
|
10
|
+
if (!['send', 'ack', 'complete'].includes(operation)) throw new Error('Use send PEER ID TEXT, ack PEER SEQ, or complete SUMMARY');
|
|
11
|
+
const socket = createConnection(path);
|
|
12
|
+
await once(socket, 'connect');
|
|
13
|
+
socket.setTimeout(30_000, () => socket.destroy(new Error('Communication tool timed out')));
|
|
14
|
+
socket.end(JSON.stringify({ token, operation, values }) + '\n');
|
|
15
|
+
let text = '';
|
|
16
|
+
for await (const chunk of socket) text += chunk;
|
|
17
|
+
const result = JSON.parse(text);
|
|
18
|
+
if (typeof result.error === 'string') throw new Error(result.error);
|
|
19
|
+
process.stdout.write(JSON.stringify(result.result) + '\n');
|
|
20
|
+
}
|
|
21
|
+
void main().catch(error => { console.error(error.message); process.exitCode = 1; });
|
|
22
|
+
`;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { toolSource } from './tool.js';
|
|
2
|
+
import { createServer, type Socket } from 'node:net';
|
|
3
|
+
import { chmod, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
7
|
+
|
|
8
|
+
export interface CommunicationToolRequest { operation: string; values: string[] }
|
|
9
|
+
export async function openCommunicationTools(invoke: (request: CommunicationToolRequest) => Promise<unknown>) {
|
|
10
|
+
const directory = await mkdtemp(join(tmpdir(), 'flows-comm-'));
|
|
11
|
+
const path = join(directory, 'agent.sock');
|
|
12
|
+
const helperPath = join(directory, 'tool.mjs');
|
|
13
|
+
const token = randomBytes(32).toString('hex');
|
|
14
|
+
const sockets = new Set<Socket>();
|
|
15
|
+
let busy = false;
|
|
16
|
+
const server = createServer({ allowHalfOpen: true }, socket => {
|
|
17
|
+
sockets.add(socket);
|
|
18
|
+
socket.on('close', () => sockets.delete(socket));
|
|
19
|
+
socket.on('error', () => {});
|
|
20
|
+
socket.setTimeout(30_000, () => socket.destroy());
|
|
21
|
+
let text = '';
|
|
22
|
+
socket.on('data', chunk => {
|
|
23
|
+
text += chunk;
|
|
24
|
+
if (Buffer.byteLength(text) > 32_768) socket.destroy();
|
|
25
|
+
});
|
|
26
|
+
socket.on('end', async () => {
|
|
27
|
+
if (busy) { socket.end(JSON.stringify({ error: 'Call communication tools sequentially' })); return; }
|
|
28
|
+
busy = true;
|
|
29
|
+
try {
|
|
30
|
+
const input = JSON.parse(text);
|
|
31
|
+
if (typeof input.token !== 'string' || Buffer.byteLength(input.token) !== Buffer.byteLength(token)
|
|
32
|
+
|| !timingSafeEqual(Buffer.from(input.token), Buffer.from(token))) throw new Error('Unauthorized communication session');
|
|
33
|
+
if (typeof input.operation !== 'string' || !Array.isArray(input.values)
|
|
34
|
+
|| !input.values.every((value: unknown) => typeof value === 'string')) throw new Error('Invalid communication tool request');
|
|
35
|
+
socket.end(JSON.stringify({ result: await invoke({ operation: input.operation, values: input.values }) }));
|
|
36
|
+
} catch (error) { socket.end(JSON.stringify({ error: error instanceof Error ? error.message : 'Communication tool failed' })); }
|
|
37
|
+
finally { busy = false; }
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
try {
|
|
41
|
+
await new Promise<void>((resolve, reject) => { server.once('error', reject); server.listen(path, resolve); });
|
|
42
|
+
await chmod(path, 0o600);
|
|
43
|
+
await writeFile(helperPath, toolSource, { mode: 0o600 });
|
|
44
|
+
} catch (error) { await rm(directory, { recursive: true, force: true }); throw error; }
|
|
45
|
+
return { path, helperPath, token, close: async () => {
|
|
46
|
+
for (const socket of sockets) socket.destroy();
|
|
47
|
+
await new Promise<void>((resolve, reject) => server.close(error => error ? reject(error) : resolve()));
|
|
48
|
+
await rm(directory, { recursive: true, force: true });
|
|
49
|
+
} };
|
|
50
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { communicationHistory } from './history.js';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
4
|
+
import type { JournalClient } from '../journal-client.js';
|
|
5
|
+
import type { StepDispatchEvent } from '../protocol.js';
|
|
6
|
+
import type { KernelAgentStep } from '../spec.js';
|
|
7
|
+
import { withWorkerLease } from '../worker-lease.js';
|
|
8
|
+
import { workerInstruction } from '../worker-input.js';
|
|
9
|
+
import { resolveCliModel } from '../cli-adapter.js';
|
|
10
|
+
import { channelName, type CommunicationInstruction } from './spec.js';
|
|
11
|
+
import { acquireRelayRuntime, type RelayHandle } from './relay.js';
|
|
12
|
+
import { CommunicationSession } from './session.js';
|
|
13
|
+
import { openCommunicationTools } from './tools.js';
|
|
14
|
+
import { agentEnvironment } from './environment.js';
|
|
15
|
+
|
|
16
|
+
export function requireCommunicationCli(cli: string | undefined): void {
|
|
17
|
+
if (!cli?.trim()) throw new Error('Agent communication requires a declared CLI executable');
|
|
18
|
+
}
|
|
19
|
+
export async function completeCommunicationDispatch(client: JournalClient, dispatch: StepDispatchEvent,
|
|
20
|
+
instruction: CommunicationInstruction, dataDir: string): Promise<void> {
|
|
21
|
+
const spec = dispatch.spec as KernelAgentStep;
|
|
22
|
+
requireCommunicationCli(spec.cli);
|
|
23
|
+
let output: unknown;
|
|
24
|
+
let completionReason: 'success' | 'worker_error' = 'success';
|
|
25
|
+
try {
|
|
26
|
+
output = await withWorkerLease(client, dispatch, signal => run(client, dispatch, instruction, spec, dataDir, signal));
|
|
27
|
+
} catch (error) {
|
|
28
|
+
completionReason = 'worker_error';
|
|
29
|
+
output = { error: error instanceof Error ? error.message : String(error) };
|
|
30
|
+
}
|
|
31
|
+
await client.stepComplete(dispatch.run_id, dispatch.step_id, dispatch.attempt, dispatch.idempotency_key,
|
|
32
|
+
completionReason, { output, usage: { tokens_in: 0, tokens_out: 0, dollars_unmetered: true },
|
|
33
|
+
started_pins: dispatch.pins, end_pins: dispatch.pins });
|
|
34
|
+
}
|
|
35
|
+
async function run(client: JournalClient, dispatch: StepDispatchEvent, instruction: CommunicationInstruction,
|
|
36
|
+
spec: KernelAgentStep, dataDir: string, lease: AbortSignal): Promise<unknown> {
|
|
37
|
+
const controller = new AbortController();
|
|
38
|
+
const signal = AbortSignal.any([lease, controller.signal, AbortSignal.timeout(instruction.timeoutMs)]);
|
|
39
|
+
const relay = await acquireRelayRuntime(dataDir, dispatch.run_id);
|
|
40
|
+
let handle: RelayHandle | undefined;
|
|
41
|
+
let tools: Awaited<ReturnType<typeof openCommunicationTools>> | undefined;
|
|
42
|
+
let pumping: Promise<void> | undefined;
|
|
43
|
+
let receipts = Promise.resolve();
|
|
44
|
+
let unsubscribe: (() => void) | undefined;
|
|
45
|
+
try {
|
|
46
|
+
let resolve!: (value: unknown) => void;
|
|
47
|
+
let reject!: (reason: unknown) => void;
|
|
48
|
+
const result = new Promise<unknown>((yes, no) => { resolve = yes; reject = no; });
|
|
49
|
+
// Install the rejection handler before spawn/readiness can fail.
|
|
50
|
+
void result.catch(() => {});
|
|
51
|
+
const abort = () => reject(signal.reason);
|
|
52
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
53
|
+
if (signal.aborted) abort();
|
|
54
|
+
const session = new CommunicationSession(client, dispatch, instruction, relay, summary => resolve({ summary }), reject);
|
|
55
|
+
tools = await openCommunicationTools(request => session.invoke(request));
|
|
56
|
+
const quote = (value: string) => "'" + value.replaceAll("'", "'\"'\"'") + "'";
|
|
57
|
+
const helper = `node ${quote(tools.helperPath)}`;
|
|
58
|
+
const history = await communicationHistory(client, dispatch, instruction);
|
|
59
|
+
const prompt = `${workerInstruction(instruction.instruction, dispatch)}${history}\n\nYou are flow agent ${dispatch.step_id}. Concurrent peers send messages automatically into your session through Relay. Do not poll for messages. Use your shell/terminal execution tool with ${helper} for flow communication:\n- send PEER STABLE_ID 'message text' (outgoing peers: ${instruction.outgoing.join(', ') || 'none'})\n- ack PEER DELIVERY_SEQ only after processing that message (incoming peers: ${instruction.incoming.join(', ') || 'none'})\n- complete 'summary' when your task and conversation are finished.\nUse these helpers for all peer communication; do not use Relay MCP messaging tools. Stay available for injected messages until the conversation is complete. Use stable semantic message IDs so retries deduplicate sends.`;
|
|
60
|
+
const name = `${relay.prefix}-${dispatch.step_id}`;
|
|
61
|
+
unsubscribe = relay.broker.onEvent(event => {
|
|
62
|
+
if (event.name !== name || !['delivery_injected', 'delivery_verified'].includes(event.kind)) return;
|
|
63
|
+
receipts = receipts.then(async () => {
|
|
64
|
+
await client.streamAppend(dispatch.run_id, channelName(dispatch.step_id, '$receipts'), {
|
|
65
|
+
kind: event.kind, delivery_id: event.delivery_id, verification: event.verification,
|
|
66
|
+
attempt: dispatch.attempt, processing_ack: false,
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
void receipts.catch(reject);
|
|
70
|
+
});
|
|
71
|
+
// Relay supplies each CLI's launch flags and injection behavior.
|
|
72
|
+
handle = await relay.broker.spawnPty({ name, cli: basename(spec.cli!).replace(/\.exe$/i, ''), task: prompt, channels: [], skipRelayPrompt: true,
|
|
73
|
+
model: resolveCliModel(spec.cli!, spec.model), cwd: spec.cwd ?? process.cwd(),
|
|
74
|
+
harnessConfig: { runtime: 'pty', command: quote(spec.cli!), args: [],
|
|
75
|
+
cwd: spec.cwd ?? process.cwd(), env: { ...agentEnvironment(spec.cli!),
|
|
76
|
+
RELAYFLOW_COMMUNICATION_SOCKET: tools.path, RELAYFLOW_COMMUNICATION_TOKEN: tools.token },
|
|
77
|
+
delivery: { mode: 'pty-injection', format: 'relay-block' } } });
|
|
78
|
+
const ready = await handle.waitForReady(Math.min(instruction.timeoutMs, 90_000));
|
|
79
|
+
if (ready.reason !== 'ready') throw new Error(`Communication agent did not become ready: ${ready.reason}`);
|
|
80
|
+
pumping = (async () => {
|
|
81
|
+
while (!signal.aborted) { await session.pump(); await delay(200, undefined, { signal }); }
|
|
82
|
+
})();
|
|
83
|
+
void pumping.catch(error => { if (!controller.signal.aborted) reject(error); });
|
|
84
|
+
try { return await result; } finally { signal.removeEventListener('abort', abort); }
|
|
85
|
+
} finally {
|
|
86
|
+
controller.abort();
|
|
87
|
+
unsubscribe?.();
|
|
88
|
+
try { await pumping?.catch(error => { if (error?.name !== 'AbortError') throw error; }); }
|
|
89
|
+
finally {
|
|
90
|
+
try { await receipts; }
|
|
91
|
+
finally { try { await handle?.release('Flow communication attempt ended', { deleteIdentity: true }); }
|
|
92
|
+
finally { try { await tools?.close(); } finally { await relay.close(); } } }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
package/src/compile.ts
CHANGED
|
@@ -43,6 +43,7 @@ import { validateOutputDeclaration } from './output-schema.js';
|
|
|
43
43
|
import { validateSpec, type ValidationResult } from './validate.js';
|
|
44
44
|
import { snapshotJsonValue } from './json-value.js';
|
|
45
45
|
import { expandYamlHelpers } from './yaml-helpers.js';
|
|
46
|
+
import { expandCommunication, validateCommunicationTopology } from './communication/spec.js';
|
|
46
47
|
|
|
47
48
|
export class CompileError extends Error {
|
|
48
49
|
readonly errors: string[];
|
|
@@ -125,7 +126,7 @@ export function compileSpec(spec: unknown): CompiledFlowSpec {
|
|
|
125
126
|
let snapshot: unknown;
|
|
126
127
|
try {
|
|
127
128
|
snapshot = snapshotJsonValue(spec, 'spec');
|
|
128
|
-
snapshot = expandYamlHelpers(snapshot);
|
|
129
|
+
snapshot = expandCommunication(expandYamlHelpers(snapshot));
|
|
129
130
|
} catch (error) {
|
|
130
131
|
throw new CompileError([
|
|
131
132
|
error instanceof Error ? error.message : 'spec: expected JSON-compatible data',
|
|
@@ -155,6 +156,8 @@ export function compileSpec(spec: unknown): CompiledFlowSpec {
|
|
|
155
156
|
// They are resolved exactly once at the kernel boundary, after public
|
|
156
157
|
// preflight has validated every declaration with truthful provenance.
|
|
157
158
|
const steps = input.steps.map(compileStep);
|
|
159
|
+
try { validateCommunicationTopology(steps); }
|
|
160
|
+
catch (error) { throw new CompileError([error instanceof Error ? error.message : String(error)]); }
|
|
158
161
|
const flow: CompiledFlowSpec = {
|
|
159
162
|
version: input.version,
|
|
160
163
|
...(input.name !== undefined ? { name: input.name } : {}),
|
package/src/failure-kinds.ts
CHANGED
|
@@ -77,6 +77,7 @@ export const CHECK_FAILURE_KINDS = [
|
|
|
77
77
|
*/
|
|
78
78
|
export const PREFLIGHT_WARNING_KINDS = [
|
|
79
79
|
'unprovable_effects',
|
|
80
|
+
'managed_cli_unverified',
|
|
80
81
|
'command_unresolved',
|
|
81
82
|
'command_unprovable',
|
|
82
83
|
'vacuous_gate',
|
|
@@ -160,6 +161,21 @@ export interface StepFailedDetails {
|
|
|
160
161
|
stepType?: string;
|
|
161
162
|
/** The kernel's per-step reason, e.g. `worker_error`, `retries_exhausted`. */
|
|
162
163
|
completionReason?: string;
|
|
164
|
+
/**
|
|
165
|
+
* The attempt this failure was recorded on, from the journal entry envelope.
|
|
166
|
+
* Absent when the journal did not name one — never defaulted to 1, because
|
|
167
|
+
* "how many attempts ran is unknown" and "exactly one ran" are different
|
|
168
|
+
* answers and only the second is evidence.
|
|
169
|
+
*/
|
|
170
|
+
attempt?: number;
|
|
171
|
+
/**
|
|
172
|
+
* The step's attempt budget, from `step.attempt.started`'s `max_iterations`.
|
|
173
|
+
* Reported beside `retries_exhausted` so that reason cannot be read as
|
|
174
|
+
* "retries happened": a deterministic step's default budget is 1, so a
|
|
175
|
+
* single failed attempt with no retry at all terminates as
|
|
176
|
+
* `retries_exhausted`.
|
|
177
|
+
*/
|
|
178
|
+
maxIterations?: number;
|
|
163
179
|
exitCode?: number;
|
|
164
180
|
/** Terminal-safe UTF-8 excerpt, at most 1,024 bytes. */
|
|
165
181
|
stdoutTail?: string;
|
package/src/index.ts
CHANGED
|
@@ -58,7 +58,16 @@ export type { HeaderBudget } from './budget.js';
|
|
|
58
58
|
export type { StepSpend } from './protocol.js';
|
|
59
59
|
export { SPEC_SCHEMA_VERSION } from './spec.js';
|
|
60
60
|
|
|
61
|
-
export { CloudFlowError, type CloudConnectionOptions } from './cloud-http.js';
|
|
61
|
+
export { CloudFlowError, type CloudConnectionOptions, type CloudConfigurationReason } from './cloud-http.js';
|
|
62
|
+
export {
|
|
63
|
+
listCloudRuns, getCloudRunDetail, getCloudRunSteps, getCloudRunLog,
|
|
64
|
+
type CloudRunSummary, type CloudRunList, type CloudRunDetail, type CloudStep, type CloudStepAttempt,
|
|
65
|
+
type CloudStepTranscript, type CloudToolCall, type CloudToolCount, type CloudRunLog,
|
|
66
|
+
} from './cloud-read.js';
|
|
67
|
+
export {
|
|
68
|
+
parseAgentTranscript, renderAgentTranscript,
|
|
69
|
+
type ParsedTranscript, type TranscriptEntry,
|
|
70
|
+
} from './cloud-transcript.js';
|
|
62
71
|
export {
|
|
63
72
|
runInCloud, getCloudFlowRun, waitForCloudFlowRun,
|
|
64
73
|
type CloudFlowSource, type RunInCloudOptions, type CloudRunReceipt, type CloudRunState,
|
package/src/journal-client.ts
CHANGED
|
@@ -237,8 +237,8 @@ export class JournalClient extends EventEmitter {
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
/** Connection becomes a worker; receives `step.dispatch` events. */
|
|
240
|
-
workerAttach(workerId: string, stepTypes: StepType[], pins?: Pins, capacity?: number): Promise<VerbContract['worker.attach']['result']> {
|
|
241
|
-
return this.request('worker.attach', { worker_id: workerId, step_types: stepTypes, pins, capacity });
|
|
240
|
+
workerAttach(workerId: string, stepTypes: StepType[], pins?: Pins, capacity?: number, requiredStreams?: string[]): Promise<VerbContract['worker.attach']['result']> {
|
|
241
|
+
return this.request('worker.attach', { worker_id: workerId, step_types: stepTypes, pins, capacity, required_streams: requiredStreams });
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
/**
|
|
@@ -421,6 +421,16 @@ export class JournalClient extends EventEmitter {
|
|
|
421
421
|
return this.request('event.submit', { spec, event });
|
|
422
422
|
}
|
|
423
423
|
|
|
424
|
+
channelAppend(params: VerbContract['channel.append']['params']): Promise<VerbContract['channel.append']['result']> {
|
|
425
|
+
return this.request('channel.append', params);
|
|
426
|
+
}
|
|
427
|
+
channelReceive(params: VerbContract['channel.receive']['params']): Promise<VerbContract['channel.receive']['result']> {
|
|
428
|
+
return this.request('channel.receive', params);
|
|
429
|
+
}
|
|
430
|
+
channelAck(params: VerbContract['channel.ack']['params']): Promise<VerbContract['channel.ack']['result']> {
|
|
431
|
+
return this.request('channel.ack', params);
|
|
432
|
+
}
|
|
433
|
+
|
|
424
434
|
/** Durable channel write; journals `stream.appended`. */
|
|
425
435
|
streamAppend(runId: string, stream: string, message: unknown): Promise<VerbContract['stream.append']['result']> {
|
|
426
436
|
return this.request('stream.append', { run_id: runId, stream, message });
|