@zerox1/sdk 0.1.3
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/index.d.ts +117 -0
- package/dist/index.js +330 -0
- package/package.json +24 -0
- package/packages/darwin-arm64/bin/zerox1-node +0 -0
- package/packages/darwin-arm64/package.json +14 -0
- package/packages/darwin-x64/bin/zerox1-node +0 -0
- package/packages/darwin-x64/package.json +14 -0
- package/packages/linux-x64/bin/zerox1-node +0 -0
- package/packages/linux-x64/package.json +14 -0
- package/packages/win32-x64/package.json +14 -0
- package/src/index.ts +427 -0
- package/tsconfig.json +13 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export interface Zerox1AgentConfig {
|
|
2
|
+
/**
|
|
3
|
+
* 32-byte Ed25519 secret key as Uint8Array, OR a path to an existing
|
|
4
|
+
* key file (raw 32 bytes). If the path does not exist, the node
|
|
5
|
+
* generates a new key and writes it there.
|
|
6
|
+
*/
|
|
7
|
+
keypair: Uint8Array | string;
|
|
8
|
+
/** Display name broadcast in BEACON/ADVERTISE. Default: 'zerox1-agent'. */
|
|
9
|
+
name?: string;
|
|
10
|
+
/**
|
|
11
|
+
* SATI mint address as hex (32 bytes). Required for mainnet.
|
|
12
|
+
* Omit to run in dev mode (SATI checks are advisory only).
|
|
13
|
+
*/
|
|
14
|
+
satiMint?: string;
|
|
15
|
+
/** Solana RPC URL. Default: mainnet-beta. */
|
|
16
|
+
rpcUrl?: string;
|
|
17
|
+
/** Directory for per-epoch envelope logs. Default: current dir. */
|
|
18
|
+
logDir?: string;
|
|
19
|
+
/** Additional bootstrap peer multiaddrs. */
|
|
20
|
+
bootstrap?: string[];
|
|
21
|
+
}
|
|
22
|
+
export type MsgType = 'ADVERTISE' | 'DISCOVER' | 'PROPOSE' | 'COUNTER' | 'ACCEPT' | 'REJECT' | 'DELIVER' | 'NOTARIZE_BID' | 'NOTARIZE_ASSIGN' | 'VERDICT' | 'FEEDBACK' | 'DISPUTE';
|
|
23
|
+
export interface SendParams {
|
|
24
|
+
msgType: MsgType;
|
|
25
|
+
/** Hex-encoded 32-byte agent ID. Omit for broadcast types. */
|
|
26
|
+
recipient?: string;
|
|
27
|
+
/** Hex-encoded 16-byte conversation ID. */
|
|
28
|
+
conversationId: string;
|
|
29
|
+
payload: Buffer | Uint8Array;
|
|
30
|
+
}
|
|
31
|
+
export interface SentConfirmation {
|
|
32
|
+
nonce: number;
|
|
33
|
+
payloadHash: string;
|
|
34
|
+
}
|
|
35
|
+
export interface FeedbackPayload {
|
|
36
|
+
conversationId: string;
|
|
37
|
+
targetAgent: string;
|
|
38
|
+
score: number;
|
|
39
|
+
outcome: number;
|
|
40
|
+
isDispute: boolean;
|
|
41
|
+
role: number;
|
|
42
|
+
}
|
|
43
|
+
export interface NotarizeBidPayload {
|
|
44
|
+
bidType: number;
|
|
45
|
+
conversationId: string;
|
|
46
|
+
opaqueB64: string;
|
|
47
|
+
}
|
|
48
|
+
export interface InboundEnvelope {
|
|
49
|
+
msgType: MsgType;
|
|
50
|
+
sender: string;
|
|
51
|
+
recipient: string;
|
|
52
|
+
conversationId: string;
|
|
53
|
+
slot: number;
|
|
54
|
+
nonce: number;
|
|
55
|
+
payloadB64: string;
|
|
56
|
+
feedback?: FeedbackPayload;
|
|
57
|
+
notarizeBid?: NotarizeBidPayload;
|
|
58
|
+
}
|
|
59
|
+
export interface SendFeedbackParams {
|
|
60
|
+
conversationId: string;
|
|
61
|
+
targetAgent: string;
|
|
62
|
+
/** -100 to +100 */
|
|
63
|
+
score: number;
|
|
64
|
+
outcome: 'negative' | 'neutral' | 'positive';
|
|
65
|
+
role: 'participant' | 'notary';
|
|
66
|
+
}
|
|
67
|
+
type Handler = (env: InboundEnvelope) => void | Promise<void>;
|
|
68
|
+
export declare class Zerox1Agent {
|
|
69
|
+
private proc;
|
|
70
|
+
private ws;
|
|
71
|
+
private handlers;
|
|
72
|
+
private port;
|
|
73
|
+
private nodeUrl;
|
|
74
|
+
private constructor();
|
|
75
|
+
/**
|
|
76
|
+
* Create an Zerox1Agent instance.
|
|
77
|
+
* Call `agent.on(...)` to register handlers, then `agent.start()` to join
|
|
78
|
+
* the mesh. The node binary is bundled — no separate install required.
|
|
79
|
+
*/
|
|
80
|
+
static create(config: Zerox1AgentConfig): Zerox1Agent;
|
|
81
|
+
private _config;
|
|
82
|
+
/**
|
|
83
|
+
* Start the node, wait for it to be ready, connect the inbox stream.
|
|
84
|
+
* Safe to await — resolves once the agent is live on the mesh.
|
|
85
|
+
*/
|
|
86
|
+
start(): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Disconnect from the mesh and stop the node process.
|
|
89
|
+
*/
|
|
90
|
+
disconnect(): void;
|
|
91
|
+
/**
|
|
92
|
+
* Register a handler for a message type.
|
|
93
|
+
* Use `'*'` to catch all inbound message types.
|
|
94
|
+
* Chain multiple `.on()` calls — all handlers for a type are called in order.
|
|
95
|
+
*/
|
|
96
|
+
on(msgType: MsgType | '*', handler: Handler): this;
|
|
97
|
+
private _dispatch;
|
|
98
|
+
/**
|
|
99
|
+
* Send an envelope. The node signs it and routes via libp2p.
|
|
100
|
+
* Returns the assigned nonce and payload hash for tracking.
|
|
101
|
+
*/
|
|
102
|
+
send(params: SendParams): Promise<SentConfirmation>;
|
|
103
|
+
/**
|
|
104
|
+
* Send a FEEDBACK envelope with CBOR-encoded payload.
|
|
105
|
+
* Protocol rule 9 requires CBOR — this method handles the encoding.
|
|
106
|
+
*/
|
|
107
|
+
sendFeedback(params: SendFeedbackParams): Promise<SentConfirmation>;
|
|
108
|
+
/** Generate a random 16-byte conversation ID as hex. */
|
|
109
|
+
newConversationId(): string;
|
|
110
|
+
/**
|
|
111
|
+
* Encode a bid value (i128 LE) into the first 16 bytes of a payload,
|
|
112
|
+
* followed by optional extra bytes (your terms).
|
|
113
|
+
*/
|
|
114
|
+
encodeBidValue(value: bigint, rest?: Buffer): Buffer;
|
|
115
|
+
private _connectInbox;
|
|
116
|
+
}
|
|
117
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.Zerox1Agent = void 0;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const net = __importStar(require("net"));
|
|
42
|
+
const os = __importStar(require("os"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const child_process_1 = require("child_process");
|
|
45
|
+
const ws_1 = __importDefault(require("ws"));
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// CBOR encoding for FEEDBACK payload
|
|
48
|
+
//
|
|
49
|
+
// FEEDBACK payloads must be CBOR-encoded. Receiving nodes run
|
|
50
|
+
// FeedbackPayload::decode() which is a strict CBOR parser — any other
|
|
51
|
+
// encoding fails validation rule 9 and the message is silently dropped.
|
|
52
|
+
//
|
|
53
|
+
// Structure: CBOR array of 6 items:
|
|
54
|
+
// [0] bstr(16) conversation_id
|
|
55
|
+
// [1] bstr(32) target_agent
|
|
56
|
+
// [2] int score (-100..100)
|
|
57
|
+
// [3] uint outcome (0..2)
|
|
58
|
+
// [4] bool is_dispute
|
|
59
|
+
// [5] uint role (0..1)
|
|
60
|
+
// ============================================================================
|
|
61
|
+
function cborInt(n) {
|
|
62
|
+
if (n >= 0 && n <= 23)
|
|
63
|
+
return Buffer.from([n]);
|
|
64
|
+
if (n >= 24 && n <= 255)
|
|
65
|
+
return Buffer.from([0x18, n]);
|
|
66
|
+
if (n >= -24 && n < 0)
|
|
67
|
+
return Buffer.from([0x20 + (-n - 1)]);
|
|
68
|
+
if (n >= -256 && n < -24)
|
|
69
|
+
return Buffer.from([0x38, -n - 1]);
|
|
70
|
+
throw new RangeError(`CBOR int out of range: ${n}`);
|
|
71
|
+
}
|
|
72
|
+
function encodeFeedbackCbor(conversationIdHex, targetAgentHex, score, outcome, isDispute, role) {
|
|
73
|
+
const convId = Buffer.from(conversationIdHex, 'hex'); // 16 bytes
|
|
74
|
+
const targetAgent = Buffer.from(targetAgentHex, 'hex'); // 32 bytes
|
|
75
|
+
return Buffer.concat([
|
|
76
|
+
Buffer.from([0x86]), // array(6)
|
|
77
|
+
Buffer.from([0x50]), convId, // bytes(16)
|
|
78
|
+
Buffer.from([0x58, 0x20]), targetAgent, // bytes(32)
|
|
79
|
+
cborInt(score),
|
|
80
|
+
cborInt(outcome),
|
|
81
|
+
Buffer.from([isDispute ? 0xF5 : 0xF4]),
|
|
82
|
+
cborInt(role),
|
|
83
|
+
]);
|
|
84
|
+
}
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// Binary resolution
|
|
87
|
+
// ============================================================================
|
|
88
|
+
function getBinaryPath() {
|
|
89
|
+
const platform = process.platform; // 'win32' | 'darwin' | 'linux'
|
|
90
|
+
const arch = process.arch; // 'x64' | 'arm64'
|
|
91
|
+
const binName = platform === 'win32' ? 'zerox1-node.exe' : 'zerox1-node';
|
|
92
|
+
const pkgName = `@zerox1/sdk-${platform}-${arch}`;
|
|
93
|
+
try {
|
|
94
|
+
const pkgJson = require.resolve(`${pkgName}/package.json`);
|
|
95
|
+
return path.join(path.dirname(pkgJson), 'bin', binName);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Optional platform package not installed — fall back to PATH.
|
|
99
|
+
// This allows developers to use a locally built binary during development.
|
|
100
|
+
return binName;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// Port + process utilities
|
|
105
|
+
// ============================================================================
|
|
106
|
+
function getFreePort() {
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
const srv = net.createServer();
|
|
109
|
+
srv.listen(0, '127.0.0.1', () => {
|
|
110
|
+
const port = srv.address().port;
|
|
111
|
+
srv.close(() => resolve(port));
|
|
112
|
+
});
|
|
113
|
+
srv.on('error', reject);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function resolveKeypairPath(keypair) {
|
|
117
|
+
if (typeof keypair === 'string') {
|
|
118
|
+
// Caller passed a file path — use it directly.
|
|
119
|
+
return keypair;
|
|
120
|
+
}
|
|
121
|
+
// Caller passed raw bytes — write to a temp file with restrictive permissions.
|
|
122
|
+
// mode 0o600: owner read/write only — prevents other users from reading the key.
|
|
123
|
+
const tmpPath = path.join(os.tmpdir(), `zerox1-identity-${Date.now()}.key`);
|
|
124
|
+
fs.writeFileSync(tmpPath, Buffer.from(keypair), { mode: 0o600 });
|
|
125
|
+
return tmpPath;
|
|
126
|
+
}
|
|
127
|
+
async function waitForReady(port, timeoutMs = 15000) {
|
|
128
|
+
const deadline = Date.now() + timeoutMs;
|
|
129
|
+
while (Date.now() < deadline) {
|
|
130
|
+
try {
|
|
131
|
+
const res = await fetch(`http://127.0.0.1:${port}/peers`);
|
|
132
|
+
if (res.ok)
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// not ready yet
|
|
137
|
+
}
|
|
138
|
+
await new Promise(r => setTimeout(r, 200));
|
|
139
|
+
}
|
|
140
|
+
throw new Error(`zerox1-node did not become ready within ${timeoutMs}ms`);
|
|
141
|
+
}
|
|
142
|
+
class Zerox1Agent {
|
|
143
|
+
constructor() {
|
|
144
|
+
this.proc = null;
|
|
145
|
+
this.ws = null;
|
|
146
|
+
this.handlers = new Map();
|
|
147
|
+
this.port = 0;
|
|
148
|
+
this.nodeUrl = '';
|
|
149
|
+
}
|
|
150
|
+
// ── Factory ───────────────────────────────────────────────────────────────
|
|
151
|
+
/**
|
|
152
|
+
* Create an Zerox1Agent instance.
|
|
153
|
+
* Call `agent.on(...)` to register handlers, then `agent.start()` to join
|
|
154
|
+
* the mesh. The node binary is bundled — no separate install required.
|
|
155
|
+
*/
|
|
156
|
+
static create(config) {
|
|
157
|
+
const agent = new Zerox1Agent();
|
|
158
|
+
agent._config = config;
|
|
159
|
+
return agent;
|
|
160
|
+
}
|
|
161
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
162
|
+
/**
|
|
163
|
+
* Start the node, wait for it to be ready, connect the inbox stream.
|
|
164
|
+
* Safe to await — resolves once the agent is live on the mesh.
|
|
165
|
+
*/
|
|
166
|
+
async start() {
|
|
167
|
+
this.port = await getFreePort();
|
|
168
|
+
this.nodeUrl = `http://127.0.0.1:${this.port}`;
|
|
169
|
+
const keypairPath = resolveKeypairPath(this._config.keypair);
|
|
170
|
+
const binaryPath = getBinaryPath();
|
|
171
|
+
const args = [
|
|
172
|
+
'--keypair-path', keypairPath,
|
|
173
|
+
'--api-addr', `127.0.0.1:${this.port}`,
|
|
174
|
+
'--agent-name', this._config.name ?? 'zerox1-agent',
|
|
175
|
+
];
|
|
176
|
+
if (this._config.satiMint)
|
|
177
|
+
args.push('--sati-mint', this._config.satiMint);
|
|
178
|
+
if (this._config.rpcUrl)
|
|
179
|
+
args.push('--rpc-url', this._config.rpcUrl);
|
|
180
|
+
if (this._config.logDir)
|
|
181
|
+
args.push('--log-dir', this._config.logDir);
|
|
182
|
+
for (const b of this._config.bootstrap ?? []) {
|
|
183
|
+
args.push('--bootstrap', b);
|
|
184
|
+
}
|
|
185
|
+
this.proc = (0, child_process_1.spawn)(binaryPath, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
186
|
+
this.proc.on('error', (err) => {
|
|
187
|
+
throw new Error(`Failed to start zerox1-node (${binaryPath}): ${err.message}\n` +
|
|
188
|
+
`Make sure the binary is installed or in your PATH.`);
|
|
189
|
+
});
|
|
190
|
+
// Surface node logs prefixed so they're distinguishable in agent output.
|
|
191
|
+
this.proc.stderr?.on('data', (d) => {
|
|
192
|
+
process.stderr.write(`[zerox1-node] ${d}`);
|
|
193
|
+
});
|
|
194
|
+
// Wait until the HTTP server is accepting connections.
|
|
195
|
+
await waitForReady(this.port);
|
|
196
|
+
// Open the inbox WebSocket.
|
|
197
|
+
this._connectInbox();
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Disconnect from the mesh and stop the node process.
|
|
201
|
+
*/
|
|
202
|
+
disconnect() {
|
|
203
|
+
this.ws?.close();
|
|
204
|
+
this.ws = null;
|
|
205
|
+
this.proc?.kill();
|
|
206
|
+
this.proc = null;
|
|
207
|
+
}
|
|
208
|
+
// ── Handlers ──────────────────────────────────────────────────────────────
|
|
209
|
+
/**
|
|
210
|
+
* Register a handler for a message type.
|
|
211
|
+
* Use `'*'` to catch all inbound message types.
|
|
212
|
+
* Chain multiple `.on()` calls — all handlers for a type are called in order.
|
|
213
|
+
*/
|
|
214
|
+
on(msgType, handler) {
|
|
215
|
+
const key = msgType === '*' ? '__all__' : msgType;
|
|
216
|
+
const list = this.handlers.get(key) ?? [];
|
|
217
|
+
list.push(handler);
|
|
218
|
+
this.handlers.set(key, list);
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
_dispatch(env) {
|
|
222
|
+
const specific = this.handlers.get(env.msgType) ?? [];
|
|
223
|
+
const wildcard = this.handlers.get('__all__') ?? [];
|
|
224
|
+
for (const h of [...specific, ...wildcard]) {
|
|
225
|
+
try {
|
|
226
|
+
void h(env);
|
|
227
|
+
}
|
|
228
|
+
catch { /* handler errors are isolated */ }
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// ── Sending ───────────────────────────────────────────────────────────────
|
|
232
|
+
/**
|
|
233
|
+
* Send an envelope. The node signs it and routes via libp2p.
|
|
234
|
+
* Returns the assigned nonce and payload hash for tracking.
|
|
235
|
+
*/
|
|
236
|
+
async send(params) {
|
|
237
|
+
const body = {
|
|
238
|
+
msg_type: params.msgType,
|
|
239
|
+
recipient: params.recipient ?? null,
|
|
240
|
+
conversation_id: params.conversationId,
|
|
241
|
+
payload_b64: Buffer.from(params.payload).toString('base64'),
|
|
242
|
+
};
|
|
243
|
+
const res = await fetch(`${this.nodeUrl}/envelopes/send`, {
|
|
244
|
+
method: 'POST',
|
|
245
|
+
headers: { 'Content-Type': 'application/json' },
|
|
246
|
+
body: JSON.stringify(body),
|
|
247
|
+
});
|
|
248
|
+
const json = await res.json();
|
|
249
|
+
if (!res.ok) {
|
|
250
|
+
throw new Error(json['error'] ?? `HTTP ${res.status}`);
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
nonce: json['nonce'],
|
|
254
|
+
payloadHash: json['payload_hash'],
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Send a FEEDBACK envelope with CBOR-encoded payload.
|
|
259
|
+
* Protocol rule 9 requires CBOR — this method handles the encoding.
|
|
260
|
+
*/
|
|
261
|
+
async sendFeedback(params) {
|
|
262
|
+
const outcomeMap = { negative: 0, neutral: 1, positive: 2 };
|
|
263
|
+
const roleMap = { participant: 0, notary: 1 };
|
|
264
|
+
const payload = encodeFeedbackCbor(params.conversationId, params.targetAgent, params.score, outcomeMap[params.outcome], false, roleMap[params.role]);
|
|
265
|
+
return this.send({
|
|
266
|
+
msgType: 'FEEDBACK',
|
|
267
|
+
conversationId: params.conversationId,
|
|
268
|
+
payload,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
// ── Utilities ─────────────────────────────────────────────────────────────
|
|
272
|
+
/** Generate a random 16-byte conversation ID as hex. */
|
|
273
|
+
newConversationId() {
|
|
274
|
+
const bytes = new Uint8Array(16);
|
|
275
|
+
crypto.getRandomValues(bytes);
|
|
276
|
+
return Buffer.from(bytes).toString('hex');
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Encode a bid value (i128 LE) into the first 16 bytes of a payload,
|
|
280
|
+
* followed by optional extra bytes (your terms).
|
|
281
|
+
*/
|
|
282
|
+
encodeBidValue(value, rest = Buffer.alloc(0)) {
|
|
283
|
+
const buf = Buffer.alloc(16);
|
|
284
|
+
buf.writeBigInt64LE(value & 0xffffffffffffffffn, 0);
|
|
285
|
+
buf.writeBigInt64LE(value >> 64n, 8);
|
|
286
|
+
return Buffer.concat([buf, rest]);
|
|
287
|
+
}
|
|
288
|
+
// ── Internal ──────────────────────────────────────────────────────────────
|
|
289
|
+
_connectInbox() {
|
|
290
|
+
const wsUrl = `ws://127.0.0.1:${this.port}/ws/inbox`;
|
|
291
|
+
const ws = new ws_1.default(wsUrl);
|
|
292
|
+
this.ws = ws;
|
|
293
|
+
ws.on('message', (data) => {
|
|
294
|
+
try {
|
|
295
|
+
const raw = JSON.parse(data.toString());
|
|
296
|
+
const env = {
|
|
297
|
+
msgType: raw.msg_type,
|
|
298
|
+
sender: raw.sender,
|
|
299
|
+
recipient: raw.recipient,
|
|
300
|
+
conversationId: raw.conversation_id,
|
|
301
|
+
slot: raw.slot,
|
|
302
|
+
nonce: raw.nonce,
|
|
303
|
+
payloadB64: raw.payload_b64,
|
|
304
|
+
feedback: raw.feedback ? {
|
|
305
|
+
conversationId: raw.feedback.conversation_id,
|
|
306
|
+
targetAgent: raw.feedback.target_agent,
|
|
307
|
+
score: raw.feedback.score,
|
|
308
|
+
outcome: raw.feedback.outcome,
|
|
309
|
+
isDispute: raw.feedback.is_dispute,
|
|
310
|
+
role: raw.feedback.role,
|
|
311
|
+
} : undefined,
|
|
312
|
+
notarizeBid: raw.notarize_bid ? {
|
|
313
|
+
bidType: raw.notarize_bid.bid_type,
|
|
314
|
+
conversationId: raw.notarize_bid.conversation_id,
|
|
315
|
+
opaqueB64: raw.notarize_bid.opaque_b64,
|
|
316
|
+
} : undefined,
|
|
317
|
+
};
|
|
318
|
+
this._dispatch(env);
|
|
319
|
+
}
|
|
320
|
+
catch { /* malformed — ignore */ }
|
|
321
|
+
});
|
|
322
|
+
ws.on('close', () => {
|
|
323
|
+
// Reconnect only if the process is still running.
|
|
324
|
+
if (this.proc)
|
|
325
|
+
setTimeout(() => this._connectInbox(), 1000);
|
|
326
|
+
});
|
|
327
|
+
ws.on('error', () => { });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
exports.Zerox1Agent = Zerox1Agent;
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zerox1/sdk",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "0x01 mesh agent SDK — zero-config, binary bundled, works on every platform",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"dev": "tsc --watch"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"ws": "^8.18.0"
|
|
13
|
+
},
|
|
14
|
+
"optionalDependencies": {
|
|
15
|
+
"@zerox1/sdk-darwin-arm64": "0.1.3",
|
|
16
|
+
"@zerox1/sdk-darwin-x64": "0.1.3",
|
|
17
|
+
"@zerox1/sdk-linux-x64": "0.1.3"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^22.0.0",
|
|
21
|
+
"@types/ws": "^8.5.13",
|
|
22
|
+
"typescript": "^5.7.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import * as fs from 'fs'
|
|
2
|
+
import * as net from 'net'
|
|
3
|
+
import * as os from 'os'
|
|
4
|
+
import * as path from 'path'
|
|
5
|
+
import { spawn, ChildProcess } from 'child_process'
|
|
6
|
+
import WebSocket from 'ws'
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Public config / types
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
export interface Zerox1AgentConfig {
|
|
13
|
+
/**
|
|
14
|
+
* 32-byte Ed25519 secret key as Uint8Array, OR a path to an existing
|
|
15
|
+
* key file (raw 32 bytes). If the path does not exist, the node
|
|
16
|
+
* generates a new key and writes it there.
|
|
17
|
+
*/
|
|
18
|
+
keypair: Uint8Array | string
|
|
19
|
+
/** Display name broadcast in BEACON/ADVERTISE. Default: 'zerox1-agent'. */
|
|
20
|
+
name?: string
|
|
21
|
+
/**
|
|
22
|
+
* SATI mint address as hex (32 bytes). Required for mainnet.
|
|
23
|
+
* Omit to run in dev mode (SATI checks are advisory only).
|
|
24
|
+
*/
|
|
25
|
+
satiMint?: string
|
|
26
|
+
/** Solana RPC URL. Default: mainnet-beta. */
|
|
27
|
+
rpcUrl?: string
|
|
28
|
+
/** Directory for per-epoch envelope logs. Default: current dir. */
|
|
29
|
+
logDir?: string
|
|
30
|
+
/** Additional bootstrap peer multiaddrs. */
|
|
31
|
+
bootstrap?: string[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type MsgType =
|
|
35
|
+
| 'ADVERTISE' | 'DISCOVER'
|
|
36
|
+
| 'PROPOSE' | 'COUNTER' | 'ACCEPT' | 'REJECT'
|
|
37
|
+
| 'DELIVER'
|
|
38
|
+
| 'NOTARIZE_BID' | 'NOTARIZE_ASSIGN'
|
|
39
|
+
| 'VERDICT'
|
|
40
|
+
| 'FEEDBACK'
|
|
41
|
+
| 'DISPUTE'
|
|
42
|
+
|
|
43
|
+
export interface SendParams {
|
|
44
|
+
msgType: MsgType
|
|
45
|
+
/** Hex-encoded 32-byte agent ID. Omit for broadcast types. */
|
|
46
|
+
recipient?: string
|
|
47
|
+
/** Hex-encoded 16-byte conversation ID. */
|
|
48
|
+
conversationId: string
|
|
49
|
+
payload: Buffer | Uint8Array
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface SentConfirmation {
|
|
53
|
+
nonce: number
|
|
54
|
+
payloadHash: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface FeedbackPayload {
|
|
58
|
+
conversationId: string
|
|
59
|
+
targetAgent: string
|
|
60
|
+
score: number
|
|
61
|
+
outcome: number
|
|
62
|
+
isDispute: boolean
|
|
63
|
+
role: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface NotarizeBidPayload {
|
|
67
|
+
bidType: number
|
|
68
|
+
conversationId: string
|
|
69
|
+
opaqueB64: string
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface InboundEnvelope {
|
|
73
|
+
msgType: MsgType
|
|
74
|
+
sender: string
|
|
75
|
+
recipient: string
|
|
76
|
+
conversationId: string
|
|
77
|
+
slot: number
|
|
78
|
+
nonce: number
|
|
79
|
+
payloadB64: string
|
|
80
|
+
feedback?: FeedbackPayload
|
|
81
|
+
notarizeBid?: NotarizeBidPayload
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface SendFeedbackParams {
|
|
85
|
+
conversationId: string
|
|
86
|
+
targetAgent: string
|
|
87
|
+
/** -100 to +100 */
|
|
88
|
+
score: number
|
|
89
|
+
outcome: 'negative' | 'neutral' | 'positive'
|
|
90
|
+
role: 'participant' | 'notary'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// CBOR encoding for FEEDBACK payload
|
|
95
|
+
//
|
|
96
|
+
// FEEDBACK payloads must be CBOR-encoded. Receiving nodes run
|
|
97
|
+
// FeedbackPayload::decode() which is a strict CBOR parser — any other
|
|
98
|
+
// encoding fails validation rule 9 and the message is silently dropped.
|
|
99
|
+
//
|
|
100
|
+
// Structure: CBOR array of 6 items:
|
|
101
|
+
// [0] bstr(16) conversation_id
|
|
102
|
+
// [1] bstr(32) target_agent
|
|
103
|
+
// [2] int score (-100..100)
|
|
104
|
+
// [3] uint outcome (0..2)
|
|
105
|
+
// [4] bool is_dispute
|
|
106
|
+
// [5] uint role (0..1)
|
|
107
|
+
// ============================================================================
|
|
108
|
+
|
|
109
|
+
function cborInt(n: number): Buffer {
|
|
110
|
+
if (n >= 0 && n <= 23) return Buffer.from([n])
|
|
111
|
+
if (n >= 24 && n <= 255) return Buffer.from([0x18, n])
|
|
112
|
+
if (n >= -24 && n < 0) return Buffer.from([0x20 + (-n - 1)])
|
|
113
|
+
if (n >= -256 && n < -24) return Buffer.from([0x38, -n - 1])
|
|
114
|
+
throw new RangeError(`CBOR int out of range: ${n}`)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function encodeFeedbackCbor(
|
|
118
|
+
conversationIdHex: string,
|
|
119
|
+
targetAgentHex: string,
|
|
120
|
+
score: number,
|
|
121
|
+
outcome: number,
|
|
122
|
+
isDispute: boolean,
|
|
123
|
+
role: number,
|
|
124
|
+
): Buffer {
|
|
125
|
+
const convId = Buffer.from(conversationIdHex, 'hex') // 16 bytes
|
|
126
|
+
const targetAgent = Buffer.from(targetAgentHex, 'hex') // 32 bytes
|
|
127
|
+
return Buffer.concat([
|
|
128
|
+
Buffer.from([0x86]), // array(6)
|
|
129
|
+
Buffer.from([0x50]), convId, // bytes(16)
|
|
130
|
+
Buffer.from([0x58, 0x20]), targetAgent, // bytes(32)
|
|
131
|
+
cborInt(score),
|
|
132
|
+
cborInt(outcome),
|
|
133
|
+
Buffer.from([isDispute ? 0xF5 : 0xF4]),
|
|
134
|
+
cborInt(role),
|
|
135
|
+
])
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ============================================================================
|
|
139
|
+
// Binary resolution
|
|
140
|
+
// ============================================================================
|
|
141
|
+
|
|
142
|
+
function getBinaryPath(): string {
|
|
143
|
+
const platform = process.platform // 'win32' | 'darwin' | 'linux'
|
|
144
|
+
const arch = process.arch // 'x64' | 'arm64'
|
|
145
|
+
const binName = platform === 'win32' ? 'zerox1-node.exe' : 'zerox1-node'
|
|
146
|
+
const pkgName = `@zerox1/sdk-${platform}-${arch}`
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const pkgJson = require.resolve(`${pkgName}/package.json`)
|
|
150
|
+
return path.join(path.dirname(pkgJson), 'bin', binName)
|
|
151
|
+
} catch {
|
|
152
|
+
// Optional platform package not installed — fall back to PATH.
|
|
153
|
+
// This allows developers to use a locally built binary during development.
|
|
154
|
+
return binName
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ============================================================================
|
|
159
|
+
// Port + process utilities
|
|
160
|
+
// ============================================================================
|
|
161
|
+
|
|
162
|
+
function getFreePort(): Promise<number> {
|
|
163
|
+
return new Promise((resolve, reject) => {
|
|
164
|
+
const srv = net.createServer()
|
|
165
|
+
srv.listen(0, '127.0.0.1', () => {
|
|
166
|
+
const port = (srv.address() as net.AddressInfo).port
|
|
167
|
+
srv.close(() => resolve(port))
|
|
168
|
+
})
|
|
169
|
+
srv.on('error', reject)
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function resolveKeypairPath(keypair: Uint8Array | string): string {
|
|
174
|
+
if (typeof keypair === 'string') {
|
|
175
|
+
// Caller passed a file path — use it directly.
|
|
176
|
+
return keypair
|
|
177
|
+
}
|
|
178
|
+
// Caller passed raw bytes — write to a temp file with restrictive permissions.
|
|
179
|
+
// mode 0o600: owner read/write only — prevents other users from reading the key.
|
|
180
|
+
const tmpPath = path.join(os.tmpdir(), `zerox1-identity-${Date.now()}.key`)
|
|
181
|
+
fs.writeFileSync(tmpPath, Buffer.from(keypair), { mode: 0o600 })
|
|
182
|
+
return tmpPath
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function waitForReady(port: number, timeoutMs = 15_000): Promise<void> {
|
|
186
|
+
const deadline = Date.now() + timeoutMs
|
|
187
|
+
while (Date.now() < deadline) {
|
|
188
|
+
try {
|
|
189
|
+
const res = await fetch(`http://127.0.0.1:${port}/peers`)
|
|
190
|
+
if (res.ok) return
|
|
191
|
+
} catch {
|
|
192
|
+
// not ready yet
|
|
193
|
+
}
|
|
194
|
+
await new Promise(r => setTimeout(r, 200))
|
|
195
|
+
}
|
|
196
|
+
throw new Error(`zerox1-node did not become ready within ${timeoutMs}ms`)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ============================================================================
|
|
200
|
+
// Zerox1Agent
|
|
201
|
+
// ============================================================================
|
|
202
|
+
|
|
203
|
+
type Handler = (env: InboundEnvelope) => void | Promise<void>
|
|
204
|
+
|
|
205
|
+
export class Zerox1Agent {
|
|
206
|
+
private proc: ChildProcess | null = null
|
|
207
|
+
private ws: WebSocket | null = null
|
|
208
|
+
private handlers: Map<string, Handler[]> = new Map()
|
|
209
|
+
private port: number = 0
|
|
210
|
+
private nodeUrl: string = ''
|
|
211
|
+
|
|
212
|
+
private constructor() {}
|
|
213
|
+
|
|
214
|
+
// ── Factory ───────────────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Create an Zerox1Agent instance.
|
|
218
|
+
* Call `agent.on(...)` to register handlers, then `agent.start()` to join
|
|
219
|
+
* the mesh. The node binary is bundled — no separate install required.
|
|
220
|
+
*/
|
|
221
|
+
static create(config: Zerox1AgentConfig): Zerox1Agent {
|
|
222
|
+
const agent = new Zerox1Agent()
|
|
223
|
+
agent._config = config
|
|
224
|
+
return agent
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private _config!: Zerox1AgentConfig
|
|
228
|
+
|
|
229
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Start the node, wait for it to be ready, connect the inbox stream.
|
|
233
|
+
* Safe to await — resolves once the agent is live on the mesh.
|
|
234
|
+
*/
|
|
235
|
+
async start(): Promise<void> {
|
|
236
|
+
this.port = await getFreePort()
|
|
237
|
+
this.nodeUrl = `http://127.0.0.1:${this.port}`
|
|
238
|
+
|
|
239
|
+
const keypairPath = resolveKeypairPath(this._config.keypair)
|
|
240
|
+
const binaryPath = getBinaryPath()
|
|
241
|
+
|
|
242
|
+
const args: string[] = [
|
|
243
|
+
'--keypair-path', keypairPath,
|
|
244
|
+
'--api-addr', `127.0.0.1:${this.port}`,
|
|
245
|
+
'--agent-name', this._config.name ?? 'zerox1-agent',
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
if (this._config.satiMint) args.push('--sati-mint', this._config.satiMint)
|
|
249
|
+
if (this._config.rpcUrl) args.push('--rpc-url', this._config.rpcUrl)
|
|
250
|
+
if (this._config.logDir) args.push('--log-dir', this._config.logDir)
|
|
251
|
+
for (const b of this._config.bootstrap ?? []) {
|
|
252
|
+
args.push('--bootstrap', b)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this.proc = spawn(binaryPath, args, { stdio: ['ignore', 'pipe', 'pipe'] })
|
|
256
|
+
|
|
257
|
+
this.proc.on('error', (err) => {
|
|
258
|
+
throw new Error(
|
|
259
|
+
`Failed to start zerox1-node (${binaryPath}): ${err.message}\n` +
|
|
260
|
+
`Make sure the binary is installed or in your PATH.`
|
|
261
|
+
)
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
// Surface node logs prefixed so they're distinguishable in agent output.
|
|
265
|
+
this.proc.stderr?.on('data', (d: Buffer) => {
|
|
266
|
+
process.stderr.write(`[zerox1-node] ${d}`)
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
// Wait until the HTTP server is accepting connections.
|
|
270
|
+
await waitForReady(this.port)
|
|
271
|
+
|
|
272
|
+
// Open the inbox WebSocket.
|
|
273
|
+
this._connectInbox()
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Disconnect from the mesh and stop the node process.
|
|
278
|
+
*/
|
|
279
|
+
disconnect(): void {
|
|
280
|
+
this.ws?.close()
|
|
281
|
+
this.ws = null
|
|
282
|
+
this.proc?.kill()
|
|
283
|
+
this.proc = null
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ── Handlers ──────────────────────────────────────────────────────────────
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Register a handler for a message type.
|
|
290
|
+
* Use `'*'` to catch all inbound message types.
|
|
291
|
+
* Chain multiple `.on()` calls — all handlers for a type are called in order.
|
|
292
|
+
*/
|
|
293
|
+
on(msgType: MsgType | '*', handler: Handler): this {
|
|
294
|
+
const key = msgType === '*' ? '__all__' : msgType
|
|
295
|
+
const list = this.handlers.get(key) ?? []
|
|
296
|
+
list.push(handler)
|
|
297
|
+
this.handlers.set(key, list)
|
|
298
|
+
return this
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private _dispatch(env: InboundEnvelope): void {
|
|
302
|
+
const specific = this.handlers.get(env.msgType) ?? []
|
|
303
|
+
const wildcard = this.handlers.get('__all__') ?? []
|
|
304
|
+
for (const h of [...specific, ...wildcard]) {
|
|
305
|
+
try { void h(env) } catch { /* handler errors are isolated */ }
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ── Sending ───────────────────────────────────────────────────────────────
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Send an envelope. The node signs it and routes via libp2p.
|
|
313
|
+
* Returns the assigned nonce and payload hash for tracking.
|
|
314
|
+
*/
|
|
315
|
+
async send(params: SendParams): Promise<SentConfirmation> {
|
|
316
|
+
const body = {
|
|
317
|
+
msg_type: params.msgType,
|
|
318
|
+
recipient: params.recipient ?? null,
|
|
319
|
+
conversation_id: params.conversationId,
|
|
320
|
+
payload_b64: Buffer.from(params.payload).toString('base64'),
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const res = await fetch(`${this.nodeUrl}/envelopes/send`, {
|
|
324
|
+
method: 'POST',
|
|
325
|
+
headers: { 'Content-Type': 'application/json' },
|
|
326
|
+
body: JSON.stringify(body),
|
|
327
|
+
})
|
|
328
|
+
const json = await res.json() as Record<string, unknown>
|
|
329
|
+
|
|
330
|
+
if (!res.ok) {
|
|
331
|
+
throw new Error((json['error'] as string) ?? `HTTP ${res.status}`)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
nonce: json['nonce'] as number,
|
|
336
|
+
payloadHash: json['payload_hash'] as string,
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Send a FEEDBACK envelope with CBOR-encoded payload.
|
|
342
|
+
* Protocol rule 9 requires CBOR — this method handles the encoding.
|
|
343
|
+
*/
|
|
344
|
+
async sendFeedback(params: SendFeedbackParams): Promise<SentConfirmation> {
|
|
345
|
+
const outcomeMap = { negative: 0, neutral: 1, positive: 2 } as const
|
|
346
|
+
const roleMap = { participant: 0, notary: 1 } as const
|
|
347
|
+
|
|
348
|
+
const payload = encodeFeedbackCbor(
|
|
349
|
+
params.conversationId,
|
|
350
|
+
params.targetAgent,
|
|
351
|
+
params.score,
|
|
352
|
+
outcomeMap[params.outcome],
|
|
353
|
+
false,
|
|
354
|
+
roleMap[params.role],
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
return this.send({
|
|
358
|
+
msgType: 'FEEDBACK',
|
|
359
|
+
conversationId: params.conversationId,
|
|
360
|
+
payload,
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ── Utilities ─────────────────────────────────────────────────────────────
|
|
365
|
+
|
|
366
|
+
/** Generate a random 16-byte conversation ID as hex. */
|
|
367
|
+
newConversationId(): string {
|
|
368
|
+
const bytes = new Uint8Array(16)
|
|
369
|
+
crypto.getRandomValues(bytes)
|
|
370
|
+
return Buffer.from(bytes).toString('hex')
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Encode a bid value (i128 LE) into the first 16 bytes of a payload,
|
|
375
|
+
* followed by optional extra bytes (your terms).
|
|
376
|
+
*/
|
|
377
|
+
encodeBidValue(value: bigint, rest: Buffer = Buffer.alloc(0)): Buffer {
|
|
378
|
+
const buf = Buffer.alloc(16)
|
|
379
|
+
buf.writeBigInt64LE(value & 0xFFFFFFFFFFFFFFFFn, 0)
|
|
380
|
+
buf.writeBigInt64LE(value >> 64n, 8)
|
|
381
|
+
return Buffer.concat([buf, rest])
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// ── Internal ──────────────────────────────────────────────────────────────
|
|
385
|
+
|
|
386
|
+
private _connectInbox(): void {
|
|
387
|
+
const wsUrl = `ws://127.0.0.1:${this.port}/ws/inbox`
|
|
388
|
+
const ws = new WebSocket(wsUrl)
|
|
389
|
+
this.ws = ws
|
|
390
|
+
|
|
391
|
+
ws.on('message', (data) => {
|
|
392
|
+
try {
|
|
393
|
+
const raw = JSON.parse(data.toString())
|
|
394
|
+
const env: InboundEnvelope = {
|
|
395
|
+
msgType: raw.msg_type,
|
|
396
|
+
sender: raw.sender,
|
|
397
|
+
recipient: raw.recipient,
|
|
398
|
+
conversationId: raw.conversation_id,
|
|
399
|
+
slot: raw.slot,
|
|
400
|
+
nonce: raw.nonce,
|
|
401
|
+
payloadB64: raw.payload_b64,
|
|
402
|
+
feedback: raw.feedback ? {
|
|
403
|
+
conversationId: raw.feedback.conversation_id,
|
|
404
|
+
targetAgent: raw.feedback.target_agent,
|
|
405
|
+
score: raw.feedback.score,
|
|
406
|
+
outcome: raw.feedback.outcome,
|
|
407
|
+
isDispute: raw.feedback.is_dispute,
|
|
408
|
+
role: raw.feedback.role,
|
|
409
|
+
} : undefined,
|
|
410
|
+
notarizeBid: raw.notarize_bid ? {
|
|
411
|
+
bidType: raw.notarize_bid.bid_type,
|
|
412
|
+
conversationId: raw.notarize_bid.conversation_id,
|
|
413
|
+
opaqueB64: raw.notarize_bid.opaque_b64,
|
|
414
|
+
} : undefined,
|
|
415
|
+
}
|
|
416
|
+
this._dispatch(env)
|
|
417
|
+
} catch { /* malformed — ignore */ }
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
ws.on('close', () => {
|
|
421
|
+
// Reconnect only if the process is still running.
|
|
422
|
+
if (this.proc) setTimeout(() => this._connectInbox(), 1000)
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
ws.on('error', () => { /* close event handles reconnect */ })
|
|
426
|
+
}
|
|
427
|
+
}
|
package/tsconfig.json
ADDED