@rkat/sdk 0.5.1 → 0.6.0

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/client.js CHANGED
@@ -25,9 +25,11 @@
25
25
  * ```
26
26
  */
27
27
  import { spawn } from "node:child_process";
28
+ import { once } from "node:events";
28
29
  import { chmodSync, existsSync, mkdirSync, unlinkSync, writeFileSync, } from "node:fs";
29
30
  import os from "node:os";
30
31
  import path from "node:path";
32
+ import { setTimeout as delay } from "node:timers/promises";
31
33
  import { createInterface } from "node:readline";
32
34
  import { Buffer } from "node:buffer";
33
35
  import { MeerkatError, CapabilityUnavailableError } from "./generated/errors.js";
@@ -40,31 +42,143 @@ import { EventSubscription } from "./subscription.js";
40
42
  const MEERKAT_REPO = "lukacf/meerkat";
41
43
  const MEERKAT_RELEASE_BINARY = "rkat-rpc";
42
44
  const MEERKAT_BINARY_CACHE_ROOT = path.join(os.homedir(), ".cache", "meerkat", "bin", MEERKAT_RELEASE_BINARY);
45
+ const MOB_SPAWN_MANY_FAILURE_CAUSES = new Set([
46
+ "profile_not_found",
47
+ "member_not_found",
48
+ "member_already_exists",
49
+ "not_externally_addressable",
50
+ "invalid_transition",
51
+ "wiring_error",
52
+ "bridge_command_rejected",
53
+ "member_restore_failed",
54
+ "kickoff_wait_timed_out",
55
+ "ready_wait_timed_out",
56
+ "definition_error",
57
+ "flow_not_found",
58
+ "flow_failed",
59
+ "run_not_found",
60
+ "run_canceled",
61
+ "flow_turn_timed_out",
62
+ "frame_depth_limit_exceeded",
63
+ "frame_atomic_persistence_unavailable",
64
+ "spec_revision_conflict",
65
+ "schema_validation",
66
+ "insufficient_targets",
67
+ "topology_violation",
68
+ "bridge_delivery_rejected",
69
+ "supervisor_escalation",
70
+ "unsupported_for_mode",
71
+ "reset_barrier",
72
+ "storage_error",
73
+ "session_error",
74
+ "comms_error",
75
+ "callback_pending",
76
+ "stale_fence_token",
77
+ "stale_event_cursor",
78
+ "work_not_found",
79
+ "internal",
80
+ ]);
81
+ function isMobSpawnManyFailureCause(value) {
82
+ return typeof value === "string" && MOB_SPAWN_MANY_FAILURE_CAUSES.has(value);
83
+ }
43
84
  /**
44
- * Normalize a SkillRef to the wire format { source_uuid, skill_name }.
45
- *
46
- * SkillKey objects are converted from camelCase to snake_case.
47
- * Legacy strings are parsed and emit a console warning.
85
+ * Normalize a structured SkillRef to the bare SkillKey wire format.
48
86
  */
49
- function normalizeSkillRef(ref) {
50
- if (typeof ref !== "string") {
51
- return { source_uuid: ref.sourceUuid, skill_name: ref.skillName };
52
- }
53
- const value = ref.startsWith("/") ? ref.slice(1) : ref;
54
- const [sourceUuid, ...rest] = value.split("/");
55
- if (!sourceUuid || rest.length === 0) {
56
- throw new Error(`Invalid skill reference '${ref}'. Expected '<source_uuid>/<skill_name>'.`);
57
- }
58
- console.warn(`[meerkat-sdk] legacy skill reference '${ref}' is deprecated; pass { sourceUuid, skillName } instead.`);
59
- return { source_uuid: sourceUuid, skill_name: rest.join("/") };
87
+ function normalizeSkillKey(ref) {
88
+ if (ref === null ||
89
+ typeof ref !== "object" ||
90
+ typeof ref.sourceUuid !== "string" ||
91
+ typeof ref.skillName !== "string") {
92
+ throw new Error("Skill references must be SkillKey objects");
93
+ }
94
+ return { source_uuid: ref.sourceUuid, skill_name: ref.skillName };
60
95
  }
61
- function skillRefsToWire(refs) {
96
+ function skillKeysToWire(refs) {
62
97
  if (!refs)
63
98
  return undefined;
64
- return refs.map(normalizeSkillRef);
99
+ return refs.map(normalizeSkillKey);
100
+ }
101
+ function skillRefsToWire(refs) {
102
+ const keys = skillKeysToWire(refs);
103
+ return keys?.map((key) => ({ kind: "structured", ...key }));
104
+ }
105
+ function setIfDefined(payload, key, value) {
106
+ if (value !== undefined) {
107
+ payload[key] = value;
108
+ }
109
+ }
110
+ function mobSpawnPayload(mobId, spec) {
111
+ const payload = {
112
+ mob_id: mobId,
113
+ profile: spec.profile,
114
+ agent_identity: spec.agentIdentity,
115
+ };
116
+ setIfDefined(payload, "initial_message", spec.initialMessage);
117
+ setIfDefined(payload, "runtime_mode", spec.runtimeMode);
118
+ setIfDefined(payload, "backend", spec.backend);
119
+ setIfDefined(payload, "labels", spec.labels);
120
+ setIfDefined(payload, "context", spec.context);
121
+ setIfDefined(payload, "additional_instructions", spec.additionalInstructions);
122
+ setIfDefined(payload, "binding", spec.binding);
123
+ setIfDefined(payload, "shell_env", spec.shellEnv);
124
+ setIfDefined(payload, "auto_wire_parent", spec.autoWireParent);
125
+ setIfDefined(payload, "launch_mode", spec.launchMode);
126
+ setIfDefined(payload, "tool_access_policy", spec.toolAccessPolicy);
127
+ setIfDefined(payload, "budget_split_policy", spec.budgetSplitPolicy);
128
+ setIfDefined(payload, "inherited_tool_filter", spec.inheritedToolFilter);
129
+ setIfDefined(payload, "override_profile", spec.overrideProfile);
130
+ setIfDefined(payload, "auth_binding", spec.authBinding);
131
+ return payload;
132
+ }
133
+ function mobSpawnManySpecPayload(spec) {
134
+ const payload = {
135
+ profile: spec.profile,
136
+ agent_identity: spec.agentIdentity,
137
+ };
138
+ setIfDefined(payload, "initial_message", spec.initialMessage);
139
+ setIfDefined(payload, "runtime_mode", spec.runtimeMode);
140
+ setIfDefined(payload, "backend", spec.backend);
141
+ setIfDefined(payload, "labels", spec.labels);
142
+ setIfDefined(payload, "context", spec.context);
143
+ setIfDefined(payload, "additional_instructions", spec.additionalInstructions);
144
+ setIfDefined(payload, "auth_binding", spec.authBinding);
145
+ return payload;
146
+ }
147
+ function mobTurnStartPayload(mobId, agentIdentity, prompt, options) {
148
+ const payload = {
149
+ mob_id: mobId,
150
+ agent_identity: agentIdentity,
151
+ prompt: typeof prompt === "string"
152
+ ? prompt
153
+ : prompt.map((block) => ({ ...block })),
154
+ };
155
+ const wireRefs = skillRefsToWire(options?.skillRefs);
156
+ if (wireRefs) {
157
+ payload.skill_refs = wireRefs;
158
+ }
159
+ if (options?.flowToolOverlay) {
160
+ payload.flow_tool_overlay = {
161
+ allowed_tools: options.flowToolOverlay.allowedTools,
162
+ blocked_tools: options.flowToolOverlay.blockedTools,
163
+ };
164
+ }
165
+ setIfDefined(payload, "additional_instructions", options?.additionalInstructions);
166
+ setIfDefined(payload, "keep_alive", options?.keepAlive);
167
+ setIfDefined(payload, "model", options?.model);
168
+ setIfDefined(payload, "provider", options?.provider);
169
+ setIfDefined(payload, "max_tokens", options?.maxTokens);
170
+ setIfDefined(payload, "system_prompt", options?.systemPrompt);
171
+ setIfDefined(payload, "output_schema", options?.outputSchema);
172
+ setIfDefined(payload, "structured_output_retries", options?.structuredOutputRetries);
173
+ setIfDefined(payload, "provider_params", options?.providerParams);
174
+ setIfDefined(payload, "clear_provider_params", options?.clearProviderParams);
175
+ setIfDefined(payload, "auth_binding", options?.authBinding);
176
+ setIfDefined(payload, "clear_auth_binding", options?.clearAuthBinding);
177
+ return payload;
65
178
  }
66
179
  export class MeerkatClient {
67
180
  process = null;
181
+ processStderr = "";
68
182
  requestId = 0;
69
183
  _capabilities = [];
70
184
  _methods = new Set();
@@ -118,6 +232,31 @@ export class MeerkatClient {
118
232
  this.process = spawn(this.rkatPath, args, {
119
233
  stdio: ["pipe", "pipe", "pipe"],
120
234
  });
235
+ const child = this.process;
236
+ this.processStderr = "";
237
+ child.stderr?.on("data", (chunk) => {
238
+ this.processStderr = (this.processStderr + String(chunk)).slice(-8192);
239
+ });
240
+ child.once("error", (error) => {
241
+ if (this.process === child) {
242
+ this.process = null;
243
+ this.rejectPendingRequests(new MeerkatError("PROCESS_ERROR", `Failed to start ${this.rkatPath}: ${error.message}`));
244
+ this.closeQueues();
245
+ }
246
+ });
247
+ child.once("close", (code, signal) => {
248
+ const expectedClose = this.process !== child;
249
+ if (this.process === child) {
250
+ this.process = null;
251
+ this.rl?.close();
252
+ this.rl = null;
253
+ }
254
+ if (!expectedClose) {
255
+ const suffix = this.processStderr.trim() ? `: ${this.processStderr.trim()}` : "";
256
+ this.rejectPendingRequests(new MeerkatError("PROCESS_EXITED", `${this.rkatPath} exited before replying (code ${code ?? "null"}, signal ${signal ?? "null"})${suffix}`));
257
+ this.closeQueues();
258
+ }
259
+ });
121
260
  this.rl = createInterface({ input: this.process.stdout });
122
261
  this.rl.on("line", (line) => this.handleLine(line));
123
262
  // Handshake
@@ -153,14 +292,32 @@ export class MeerkatClient {
153
292
  this.rl.close();
154
293
  this.rl = null;
155
294
  }
156
- if (this.process) {
157
- this.process.kill();
158
- this.process = null;
295
+ const process = this.process;
296
+ this.process = null;
297
+ if (process) {
298
+ process.stdin?.end();
299
+ const closed = once(process, "close").catch(() => []);
300
+ process.kill();
301
+ const timeout = delay(5000).then(() => "timeout");
302
+ const outcome = await Promise.race([closed, timeout]);
303
+ if (outcome === "timeout" && process.exitCode == null && process.signalCode == null) {
304
+ process.kill("SIGKILL");
305
+ await closed.catch(() => { });
306
+ }
307
+ process.stdin?.destroy();
308
+ process.stdout?.destroy();
309
+ process.stderr?.destroy();
159
310
  }
311
+ this.rejectPendingRequests(new MeerkatError("CLIENT_CLOSED", "Client closed"));
312
+ this.closeQueues();
313
+ }
314
+ rejectPendingRequests(reason) {
160
315
  for (const [, pending] of this.pendingRequests) {
161
- pending.reject(new MeerkatError("CLIENT_CLOSED", "Client closed"));
316
+ pending.reject(reason);
162
317
  }
163
318
  this.pendingRequests.clear();
319
+ }
320
+ closeQueues() {
164
321
  for (const [, queue] of this.eventQueues) {
165
322
  queue.put(null);
166
323
  }
@@ -251,22 +408,74 @@ export class MeerkatClient {
251
408
  const raw = await this.request("session/create", params);
252
409
  return new DeferredSession(this, String(raw.session_id ?? ""), raw.session_ref != null ? String(raw.session_ref) : undefined);
253
410
  }
411
+ async askHelp(question, options) {
412
+ const params = { question };
413
+ if (options?.prompt !== undefined)
414
+ params.prompt = options.prompt;
415
+ if (options?.executionMode !== undefined) {
416
+ params.execution_mode = options.executionMode;
417
+ }
418
+ if (options?.model !== undefined)
419
+ params.model = options.model;
420
+ if (options?.provider !== undefined)
421
+ params.provider = options.provider;
422
+ if (options?.maxTokens !== undefined)
423
+ params.max_tokens = options.maxTokens;
424
+ const raw = await this.request("help/ask", params);
425
+ return MeerkatClient.parseRunResult(raw);
426
+ }
254
427
  // -- Session queries ----------------------------------------------------
255
- async listSessions() {
256
- const result = await this.request("session/list", {});
428
+ async listSessions(options) {
429
+ const params = {};
430
+ if (options?.labels)
431
+ params.labels = options.labels;
432
+ if (options?.limit !== undefined)
433
+ params.limit = options.limit;
434
+ if (options?.offset !== undefined)
435
+ params.offset = options.offset;
436
+ const result = await this.request("session/list", params);
257
437
  const sessions = result.sessions ?? [];
258
- return sessions.map((s) => ({
259
- sessionId: String(s.session_id ?? ""),
260
- sessionRef: s.session_ref != null ? String(s.session_ref) : undefined,
261
- createdAt: String(s.created_at ?? ""),
262
- updatedAt: String(s.updated_at ?? ""),
263
- messageCount: Number(s.message_count ?? 0),
264
- totalTokens: Number(s.total_tokens ?? 0),
265
- isActive: Boolean(s.is_active),
266
- }));
438
+ return sessions.map((s) => MeerkatClient.parseSessionInfo(s));
267
439
  }
268
440
  async readSession(sessionId) {
269
- return this.request("session/read", { session_id: sessionId });
441
+ const raw = await this.request("session/read", { session_id: sessionId });
442
+ return MeerkatClient.parseSessionInfo(raw);
443
+ }
444
+ async sendExternalEvent(sessionId, eventType, payload, options) {
445
+ const params = {
446
+ session_id: sessionId,
447
+ kind: "generic_json",
448
+ event_type: eventType,
449
+ payload,
450
+ };
451
+ if (options?.blocks !== undefined) {
452
+ params.blocks = options.blocks;
453
+ }
454
+ return this.request("session/external_event", params);
455
+ }
456
+ async sendPeerResponseTerminal(sessionId, peerId, requestId, status, result, options) {
457
+ const params = {
458
+ session_id: sessionId,
459
+ peer_id: peerId,
460
+ request_id: requestId,
461
+ status,
462
+ result,
463
+ };
464
+ if (options?.displayName !== undefined) {
465
+ params.display_name = options.displayName;
466
+ }
467
+ return this.request("session/peer_response_terminal", params);
468
+ }
469
+ async injectContext(sessionId, text, options) {
470
+ const params = { session_id: sessionId, text };
471
+ if (options?.source !== undefined) {
472
+ params.source = options.source;
473
+ }
474
+ if (options?.idempotencyKey !== undefined) {
475
+ params.idempotency_key = options.idempotencyKey;
476
+ }
477
+ const result = await this.request("session/inject_context", params);
478
+ return { status: String(result.status ?? "") };
270
479
  }
271
480
  async readSessionHistory(sessionId, options) {
272
481
  const params = {
@@ -297,21 +506,24 @@ export class MeerkatClient {
297
506
  }
298
507
  // -- Config -------------------------------------------------------------
299
508
  async getConfig() {
300
- return this.request("config/get", {});
509
+ const raw = await this.request("config/get", {});
510
+ return MeerkatClient.parseConfigEnvelope(raw);
301
511
  }
302
512
  async setConfig(config, options) {
303
513
  const params = { config };
304
514
  if (options?.expectedGeneration !== undefined) {
305
515
  params.expected_generation = options.expectedGeneration;
306
516
  }
307
- return this.request("config/set", params);
517
+ const raw = await this.request("config/set", params);
518
+ return MeerkatClient.parseConfigEnvelope(raw);
308
519
  }
309
520
  async patchConfig(patch, options) {
310
521
  const params = { patch };
311
522
  if (options?.expectedGeneration !== undefined) {
312
523
  params.expected_generation = options.expectedGeneration;
313
524
  }
314
- return this.request("config/patch", params);
525
+ const raw = await this.request("config/patch", params);
526
+ return MeerkatClient.parseConfigEnvelope(raw);
315
527
  }
316
528
  async mcpAdd(params) {
317
529
  const raw = await this.request("mcp/add", params);
@@ -340,13 +552,6 @@ export class MeerkatClient {
340
552
  const result = await this.request("skills/list", {});
341
553
  return result.skills ?? [];
342
554
  }
343
- async inspectSkill(id, options) {
344
- const params = { id };
345
- if (options?.source !== undefined) {
346
- params.source = options.source;
347
- }
348
- return this.request("skills/inspect", params);
349
- }
350
555
  async getBlob(blobId) {
351
556
  const result = await this.request("blob/get", { blob_id: blobId });
352
557
  return {
@@ -355,6 +560,115 @@ export class MeerkatClient {
355
560
  dataBase64: String(result.data ?? ""),
356
561
  };
357
562
  }
563
+ async getRuntimeHostInfo() {
564
+ return this.request("runtime/host_info", {});
565
+ }
566
+ async getRuntimeHostCapabilities() {
567
+ return this.request("runtime/capabilities", {});
568
+ }
569
+ async getRuntimeHostHealth() {
570
+ return this.request("runtime/health", {});
571
+ }
572
+ async requestApproval(params) {
573
+ return this.request("approval/request", params);
574
+ }
575
+ async listApprovals(params = {}) {
576
+ return this.request("approval/list", params);
577
+ }
578
+ async getApproval(params) {
579
+ return this.request("approval/get", params);
580
+ }
581
+ async decideApproval(params) {
582
+ return this.request("approval/decide", params);
583
+ }
584
+ async listArtifacts(params = {}) {
585
+ return this.request("artifact/list", params);
586
+ }
587
+ async getArtifact(params) {
588
+ return this.request("artifact/get", params);
589
+ }
590
+ async downloadArtifact(params) {
591
+ return this.request("artifact/download", params);
592
+ }
593
+ async latestEventCursor(params) {
594
+ return this.request("events/latest_cursor", params);
595
+ }
596
+ async listEventsSince(params) {
597
+ return this.request("events/list_since", params);
598
+ }
599
+ async eventSnapshot(params) {
600
+ return this.request("events/snapshot", params);
601
+ }
602
+ async getModelsCatalog() {
603
+ const result = await this.request("models/catalog", {});
604
+ return MeerkatClient.parseModelsCatalog(result);
605
+ }
606
+ async createSchedule(request) {
607
+ const result = await this.request("schedule/create", MeerkatClient.toWireCreateScheduleRequest(request));
608
+ return MeerkatClient.parseSchedule(result);
609
+ }
610
+ async getSchedule(scheduleId) {
611
+ const result = await this.request("schedule/get", { schedule_id: scheduleId });
612
+ return MeerkatClient.parseSchedule(result);
613
+ }
614
+ async listSchedules(_options) {
615
+ const params = {};
616
+ if (_options?.labels)
617
+ params.labels = _options.labels;
618
+ if (_options?.limit !== undefined)
619
+ params.limit = _options.limit;
620
+ if (_options?.offset !== undefined)
621
+ params.offset = _options.offset;
622
+ const result = await this.request("schedule/list", params);
623
+ const schedules = Array.isArray(result.schedules)
624
+ ? result.schedules
625
+ : [];
626
+ return schedules.map((schedule) => MeerkatClient.parseSchedule(schedule));
627
+ }
628
+ async updateSchedule(request) {
629
+ const params = {
630
+ schedule_id: request.scheduleId,
631
+ ...MeerkatClient.toWireUpdateSchedulePatch(request.update),
632
+ };
633
+ const result = await this.request("schedule/update", params);
634
+ return MeerkatClient.parseSchedule(result);
635
+ }
636
+ async pauseSchedule(scheduleId) {
637
+ const result = await this.request("schedule/pause", { schedule_id: scheduleId });
638
+ return MeerkatClient.parseSchedule(result);
639
+ }
640
+ async resumeSchedule(scheduleId) {
641
+ const result = await this.request("schedule/resume", { schedule_id: scheduleId });
642
+ return MeerkatClient.parseSchedule(result);
643
+ }
644
+ async deleteSchedule(scheduleId) {
645
+ const result = await this.request("schedule/delete", { schedule_id: scheduleId });
646
+ return MeerkatClient.parseSchedule(result);
647
+ }
648
+ async listScheduleOccurrences(scheduleId, options) {
649
+ const params = { schedule_id: scheduleId };
650
+ if (options?.includeTerminal !== undefined) {
651
+ params.include_terminal = options.includeTerminal;
652
+ }
653
+ const result = await this.request("schedule/occurrences", params);
654
+ const occurrences = Array.isArray(result.occurrences)
655
+ ? result.occurrences.map((occurrence) => MeerkatClient.parseScheduleOccurrence(occurrence))
656
+ : [];
657
+ return { occurrences };
658
+ }
659
+ async listScheduleTools() {
660
+ const result = await this.request("schedule/tools", {});
661
+ const tools = Array.isArray(result.tools)
662
+ ? result.tools
663
+ : [];
664
+ return { tools };
665
+ }
666
+ async callScheduleTool(request) {
667
+ return this.request("schedule/call", {
668
+ name: request.name,
669
+ arguments: request.arguments ?? {},
670
+ });
671
+ }
358
672
  async subscribeSessionEvents(sessionId) {
359
673
  return this.openEventSubscription("session/stream_open", { session_id: sessionId }, "session/stream_close", MeerkatClient.parseAgentEventEnvelope);
360
674
  }
@@ -381,83 +695,167 @@ export class MeerkatClient {
381
695
  const status = typeof rawStatus === "string"
382
696
  ? rawStatus
383
697
  : (typeof rawStatus === "object" && rawStatus !== null
384
- ? String(Object.keys(rawStatus)[0] ?? "unknown")
385
- : String(rawStatus ?? "unknown"));
698
+ ? Object.keys(rawStatus)[0]
699
+ : undefined);
700
+ if (!status) {
701
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/status response: missing status");
702
+ }
386
703
  return { mobId: String(result.mob_id ?? mobId), status };
387
704
  }
388
705
  async listMobMembers(mobId) {
389
706
  const result = await this.request("mob/members", { mob_id: mobId });
390
707
  const members = result.members ?? [];
391
- return members.map((member) => ({
392
- meerkatId: String(member.meerkat_id ?? member.meerkatId ?? ""),
393
- profile: String(member.profile_name ?? member.profile ?? ""),
394
- memberRef: member.member_ref,
395
- peerId: member.peer_id != null ? String(member.peer_id) : undefined,
396
- externalPeerSpecs: member.external_peer_specs && typeof member.external_peer_specs === "object"
397
- ? Object.fromEntries(Object.entries(member.external_peer_specs).map(([key, value]) => [key, (value ?? {})]))
398
- : undefined,
399
- runtimeMode: member.runtime_mode != null ? String(member.runtime_mode) : undefined,
400
- state: member.state != null ? String(member.state) : undefined,
401
- wiredTo: Array.isArray(member.wired_to)
402
- ? member.wired_to.map((peer) => String(peer))
403
- : undefined,
404
- labels: member.labels && typeof member.labels === 'object'
405
- ? Object.fromEntries(Object.entries(member.labels).map(([key, value]) => [key, String(value)]))
406
- : undefined,
407
- status: member.status != null ? String(member.status) : undefined,
408
- error: member.error != null ? String(member.error) : undefined,
409
- isFinal: member.is_final != null ? Boolean(member.is_final) : undefined,
410
- currentSessionId: member.current_session_id != null ? String(member.current_session_id) : undefined,
411
- sessionId: member.member_ref && typeof member.member_ref === 'object'
412
- ? member.member_ref.session_id != null
413
- ? String(member.member_ref.session_id)
414
- : undefined
415
- : undefined,
416
- }));
708
+ return members.map((member) => {
709
+ const agentIdentity = String(member.agent_identity ?? "");
710
+ const memberRef = typeof member.member_ref === "string" && member.member_ref.length > 0
711
+ ? member.member_ref
712
+ : undefined;
713
+ if (!agentIdentity || !memberRef) {
714
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/members response: missing identity-native member fields");
715
+ }
716
+ return {
717
+ agentIdentity,
718
+ memberRef,
719
+ profile: String(member.profile_name ?? member.profile ?? member.role ?? ""),
720
+ peerId: member.peer_id != null ? String(member.peer_id) : undefined,
721
+ externalPeerSpecs: member.external_peer_specs && typeof member.external_peer_specs === "object"
722
+ ? Object.fromEntries(Object.entries(member.external_peer_specs).map(([key, value]) => [key, (value ?? {})]))
723
+ : undefined,
724
+ runtimeMode: member.runtime_mode != null ? String(member.runtime_mode) : undefined,
725
+ state: member.state != null ? String(member.state) : undefined,
726
+ wiredTo: Array.isArray(member.wired_to)
727
+ ? member.wired_to.map((peer) => String(peer))
728
+ : undefined,
729
+ labels: member.labels && typeof member.labels === "object"
730
+ ? Object.fromEntries(Object.entries(member.labels).map(([key, value]) => [key, String(value)]))
731
+ : undefined,
732
+ status: member.status != null ? String(member.status) : undefined,
733
+ error: member.error != null ? String(member.error) : undefined,
734
+ isFinal: member.is_final != null ? Boolean(member.is_final) : undefined,
735
+ };
736
+ });
417
737
  }
418
- async sendMobMemberContent(mobId, meerkatId, content, options) {
738
+ async sendMobMemberContent(mobId, agentIdentity, content, options) {
419
739
  const result = await this.request("mob/member_send", {
420
740
  mob_id: mobId,
421
- meerkat_id: meerkatId,
741
+ agent_identity: agentIdentity,
422
742
  content,
423
743
  handling_mode: options?.handlingMode,
424
744
  render_metadata: options?.renderMetadata,
425
745
  });
426
- const sessionId = result.session_id;
427
- if (typeof sessionId !== "string" || sessionId.length === 0) {
428
- throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/member_send response: missing session_id");
746
+ const memberRef = typeof result.member_ref === "string" && result.member_ref.length > 0
747
+ ? result.member_ref
748
+ : undefined;
749
+ if (!memberRef) {
750
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/member_send response: missing member_ref");
429
751
  }
430
752
  return {
431
- memberId: typeof result.member_id === "string" && result.member_id.length > 0
432
- ? result.member_id
433
- : meerkatId,
434
- sessionId,
753
+ agentIdentity: typeof result.agent_identity === "string" && result.agent_identity.length > 0
754
+ ? result.agent_identity
755
+ : agentIdentity,
756
+ memberRef,
435
757
  handlingMode: result.handling_mode === "steer" || result.handling_mode === "queue"
436
758
  ? result.handling_mode
437
759
  : (options?.handlingMode ?? "queue"),
438
760
  };
439
761
  }
440
762
  async spawnMobMember(mobId, options) {
441
- return this.request("mob/spawn", {
763
+ const result = await this.request("mob/spawn", mobSpawnPayload(mobId, options));
764
+ const memberRef = typeof result.member_ref === "string" && result.member_ref.length > 0
765
+ ? result.member_ref
766
+ : undefined;
767
+ if (!memberRef) {
768
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/spawn response: missing member_ref");
769
+ }
770
+ return {
771
+ mobId: String(result.mob_id ?? mobId),
772
+ agentIdentity: typeof result.agent_identity === "string" && result.agent_identity.length > 0
773
+ ? result.agent_identity
774
+ : options.agentIdentity,
775
+ memberRef,
776
+ };
777
+ }
778
+ async spawnMobMembers(mobId, specs) {
779
+ const result = await this.request("mob/spawn_many", {
442
780
  mob_id: mobId,
443
- profile: options.profile,
444
- meerkat_id: options.meerkatId,
445
- initial_message: options.initialMessage,
446
- runtime_mode: options.runtimeMode,
447
- backend: options.backend,
448
- resume_session_id: options.resumeSessionId,
449
- labels: options.labels,
450
- context: options.context,
451
- additional_instructions: options.additionalInstructions,
781
+ specs: specs.map(mobSpawnManySpecPayload),
782
+ });
783
+ if (!Array.isArray(result.results)) {
784
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/spawn_many response: results must be a list");
785
+ }
786
+ const entries = result.results;
787
+ return entries.map((entry) => {
788
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
789
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/spawn_many response: malformed result entry");
790
+ }
791
+ const record = entry;
792
+ const entryKeys = Object.keys(record);
793
+ if (entryKeys.some((key) => key !== "status" && key !== "result") ||
794
+ "ok" in record ||
795
+ "error" in record ||
796
+ "agent_identity" in record ||
797
+ "member_ref" in record) {
798
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/spawn_many response: legacy result carrier fields are not allowed");
799
+ }
800
+ const status = record.status;
801
+ const rawResult = record.result;
802
+ if (status !== "spawned" && status !== "failed") {
803
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/spawn_many response: invalid result status");
804
+ }
805
+ if (!rawResult || typeof rawResult !== "object" || Array.isArray(rawResult)) {
806
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/spawn_many response: missing result payload");
807
+ }
808
+ const resultRecord = rawResult;
809
+ if (status === "spawned") {
810
+ if (Object.keys(resultRecord).some((key) => key !== "agent_identity" && key !== "member_ref")) {
811
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/spawn_many response: spawned result has unknown fields");
812
+ }
813
+ const agentIdentity = resultRecord.agent_identity;
814
+ const memberRef = resultRecord.member_ref;
815
+ if (typeof agentIdentity !== "string" || agentIdentity.length === 0) {
816
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/spawn_many response: spawned result missing agent_identity");
817
+ }
818
+ if (typeof memberRef !== "string" || memberRef.length === 0) {
819
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/spawn_many response: spawned result missing member_ref");
820
+ }
821
+ return {
822
+ status,
823
+ result: {
824
+ agent_identity: agentIdentity,
825
+ member_ref: memberRef,
826
+ },
827
+ };
828
+ }
829
+ if (Object.keys(resultRecord).some((key) => key !== "cause" && key !== "message")) {
830
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/spawn_many response: failed result has unknown fields");
831
+ }
832
+ const cause = resultRecord.cause;
833
+ if (!isMobSpawnManyFailureCause(cause)) {
834
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/spawn_many response: failed result has invalid cause");
835
+ }
836
+ const message = resultRecord.message;
837
+ if (typeof message !== "string" || message.length === 0) {
838
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/spawn_many response: failed result missing message");
839
+ }
840
+ return {
841
+ status,
842
+ result: {
843
+ cause,
844
+ message,
845
+ },
846
+ };
452
847
  });
453
848
  }
454
- async retireMobMember(mobId, meerkatId) {
455
- await this.request("mob/retire", { mob_id: mobId, meerkat_id: meerkatId });
849
+ async retireMobMember(mobId, agentIdentity) {
850
+ await this.request("mob/retire", {
851
+ mob_id: mobId,
852
+ agent_identity: agentIdentity,
853
+ });
456
854
  }
457
- async respawnMobMember(mobId, meerkatId, initialMessage) {
855
+ async respawnMobMember(mobId, agentIdentity, initialMessage) {
458
856
  const result = await this.request("mob/respawn", {
459
857
  mob_id: mobId,
460
- meerkat_id: meerkatId,
858
+ agent_identity: agentIdentity,
461
859
  initial_message: initialMessage,
462
860
  });
463
861
  const status = String(result.status ?? "completed");
@@ -468,21 +866,35 @@ export class MeerkatClient {
468
866
  if (!receipt || typeof receipt !== "object") {
469
867
  throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/respawn response: missing receipt");
470
868
  }
869
+ const memberRef = typeof receipt.member_ref === "string" && receipt.member_ref.length > 0
870
+ ? receipt.member_ref
871
+ : undefined;
872
+ if (!memberRef) {
873
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/respawn response: receipt missing member_ref");
874
+ }
471
875
  return {
472
876
  status: status === "topology_restore_failed" ? "topology_restore_failed" : "completed",
473
877
  receipt: {
474
- memberId: String(receipt.member_id ?? meerkatId),
475
- oldSessionId: receipt.old_session_id != null ? String(receipt.old_session_id) : undefined,
476
- newSessionId: receipt.new_session_id != null ? String(receipt.new_session_id) : undefined,
878
+ agentIdentity: receipt.identity != null ? String(receipt.identity) : agentIdentity,
879
+ memberRef,
477
880
  },
478
881
  failedPeerIds: rawFailed.map((peerId) => String(peerId)),
479
882
  };
480
883
  }
481
- async forceCancelMobMember(mobId, meerkatId) {
482
- await this.request("mob/force_cancel", { mob_id: mobId, meerkat_id: meerkatId });
884
+ async forceCancelMobMember(mobId, agentIdentity) {
885
+ await this.request("mob/force_cancel", {
886
+ mob_id: mobId,
887
+ agent_identity: agentIdentity,
888
+ });
889
+ }
890
+ async mobTurnStart(mobId, agentIdentity, prompt, options) {
891
+ return await this.request("mob/turn_start", mobTurnStartPayload(mobId, agentIdentity, prompt, options));
483
892
  }
484
- async mobMemberStatus(mobId, meerkatId) {
485
- const result = await this.request("mob/member_status", { mob_id: mobId, meerkat_id: meerkatId });
893
+ async mobMemberStatus(mobId, agentIdentity) {
894
+ const result = await this.request("mob/member_status", {
895
+ mob_id: mobId,
896
+ agent_identity: agentIdentity,
897
+ });
486
898
  const rawConnectivity = result.peer_connectivity && typeof result.peer_connectivity === "object"
487
899
  ? result.peer_connectivity
488
900
  : undefined;
@@ -492,7 +904,9 @@ export class MeerkatClient {
492
904
  error: result.error != null ? String(result.error) : undefined,
493
905
  tokensUsed: Number(result.tokens_used ?? 0),
494
906
  isFinal: Boolean(result.is_final),
495
- currentSessionId: result.current_session_id != null ? String(result.current_session_id) : undefined,
907
+ liveAttachmentStatus: typeof result.realtime_attachment_status === "string"
908
+ ? result.realtime_attachment_status
909
+ : undefined,
496
910
  peerConnectivity: rawConnectivity
497
911
  ? {
498
912
  reachablePeerCount: Number(rawConnectivity.reachable_peer_count ?? 0),
@@ -508,8 +922,94 @@ export class MeerkatClient {
508
922
  : [],
509
923
  }
510
924
  : undefined,
925
+ currentSessionId: typeof result.current_session_id === "string" && result.current_session_id.length > 0
926
+ ? result.current_session_id
927
+ : undefined,
928
+ };
929
+ }
930
+ /**
931
+ * Point-in-time aggregate of a mob's status plus its member list.
932
+ * Wraps the `mob/snapshot` RPC (DELETE_ME C2).
933
+ */
934
+ async mobSnapshot(mobId) {
935
+ const result = await this.request("mob/snapshot", { mob_id: mobId });
936
+ return {
937
+ mobId: String(result.mob_id ?? mobId),
938
+ status: String(result.status ?? "unknown"),
939
+ members: Array.isArray(result.members) ? result.members : [],
940
+ };
941
+ }
942
+ /**
943
+ * Destroy a mob and surface the structured `MobDestroyReport`.
944
+ * Wraps the `mob/destroy` RPC (DELETE_ME C3). Unlike `mob/lifecycle`
945
+ * with `action: "destroy"`, this dedicated endpoint has a predictable
946
+ * response shape that does not require branching on an action string.
947
+ */
948
+ async mobDestroy(mobId) {
949
+ const result = await this.request("mob/destroy", { mob_id: mobId });
950
+ const report = result.destroy_report && typeof result.destroy_report === "object"
951
+ ? result.destroy_report
952
+ : {};
953
+ return {
954
+ mobId: String(result.mob_id ?? mobId),
955
+ ok: Boolean(result.ok ?? false),
956
+ destroyReport: report,
957
+ };
958
+ }
959
+ /**
960
+ * Rotate the supervisor bridge for all members of a mob.
961
+ * Wraps the `mob/rotate_supervisor` RPC (DELETE_ME C10). Returns the
962
+ * full `SupervisorRotationReport` so operators can inspect per-member
963
+ * rotation outcomes instead of getting a bare `ok: true`.
964
+ */
965
+ async mobRotateSupervisor(mobId) {
966
+ const result = await this.request("mob/rotate_supervisor", { mob_id: mobId });
967
+ return result;
968
+ }
969
+ /**
970
+ * Submit a unit of work to a mob member through the work lane.
971
+ * Wraps the `mob/submit_work` RPC (DELETE_ME C4). Work lane was
972
+ * Rust-only prior to this; `origin` is `"external"` for
973
+ * user-originated turns and `"internal"` for mob-orchestration work.
974
+ * When `workRef` is omitted the server generates a fresh UUID.
975
+ */
976
+ async mobSubmitWork(args) {
977
+ const params = {
978
+ member_ref: args.memberRef,
979
+ content: args.content,
980
+ origin: args.origin ?? "external",
981
+ };
982
+ if (args.workRef !== undefined) {
983
+ params.work_ref = args.workRef;
984
+ }
985
+ const result = await this.request("mob/submit_work", params);
986
+ return {
987
+ mobId: String(result.mob_id ?? ""),
988
+ workRef: String(result.work_ref ?? ""),
989
+ memberRef: String(result.member_ref ?? args.memberRef),
511
990
  };
512
991
  }
992
+ /**
993
+ * Cancel a previously submitted unit of work.
994
+ * Wraps the `mob/cancel_work` RPC (DELETE_ME C4).
995
+ */
996
+ async mobCancelWork(mobId, workRef) {
997
+ const result = await this.request("mob/cancel_work", {
998
+ mob_id: mobId,
999
+ work_ref: workRef,
1000
+ });
1001
+ return { ok: Boolean(result.ok ?? false) };
1002
+ }
1003
+ /**
1004
+ * Cancel all in-flight work for a specific mob member.
1005
+ * Wraps the `mob/cancel_all_work` RPC (DELETE_ME C4).
1006
+ */
1007
+ async mobCancelAllWork(args) {
1008
+ const result = await this.request("mob/cancel_all_work", {
1009
+ member_ref: args.memberRef,
1010
+ });
1011
+ return { ok: Boolean(result.ok ?? false) };
1012
+ }
513
1013
  async waitMobKickoff(mobId, options) {
514
1014
  const params = { mob_id: mobId };
515
1015
  if (options?.memberIds !== undefined) {
@@ -522,17 +1022,70 @@ export class MeerkatClient {
522
1022
  const members = Array.isArray(result.members) ? result.members : [];
523
1023
  return members.map((entry) => {
524
1024
  const member = entry && typeof entry === "object" ? entry : {};
1025
+ const agentIdentity = String(member.agent_identity ?? "");
1026
+ if (!agentIdentity) {
1027
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/wait_kickoff response: member missing agent_identity");
1028
+ }
1029
+ const status = MeerkatClient.requireStringField(member, "status", "Invalid mob/wait_kickoff response");
1030
+ const tokensUsed = MeerkatClient.requireNumberField(member, "tokens_used", "Invalid mob/wait_kickoff response");
1031
+ const isFinal = MeerkatClient.requireBooleanField(member, "is_final", "Invalid mob/wait_kickoff response");
525
1032
  const rawConnectivity = member.peer_connectivity && typeof member.peer_connectivity === "object"
526
1033
  ? member.peer_connectivity
527
1034
  : undefined;
528
1035
  return {
529
- meerkatId: String(member.meerkat_id ?? ""),
530
- status: String(member.status ?? "unknown"),
1036
+ agentIdentity,
1037
+ status,
531
1038
  outputPreview: member.output_preview != null ? String(member.output_preview) : undefined,
532
1039
  error: member.error != null ? String(member.error) : undefined,
533
- tokensUsed: Number(member.tokens_used ?? 0),
534
- isFinal: Boolean(member.is_final),
535
- currentSessionId: member.current_session_id != null ? String(member.current_session_id) : undefined,
1040
+ tokensUsed,
1041
+ isFinal,
1042
+ peerConnectivity: rawConnectivity
1043
+ ? {
1044
+ reachablePeerCount: Number(rawConnectivity.reachable_peer_count ?? 0),
1045
+ unknownPeerCount: Number(rawConnectivity.unknown_peer_count ?? 0),
1046
+ unreachablePeers: Array.isArray(rawConnectivity.unreachable_peers)
1047
+ ? rawConnectivity.unreachable_peers.map((peer) => {
1048
+ const rawPeer = peer && typeof peer === "object" ? peer : {};
1049
+ return {
1050
+ peer: String(rawPeer.peer ?? ""),
1051
+ reason: rawPeer.reason != null ? String(rawPeer.reason) : undefined,
1052
+ };
1053
+ })
1054
+ : [],
1055
+ }
1056
+ : undefined,
1057
+ };
1058
+ });
1059
+ }
1060
+ async waitMobReady(mobId, options) {
1061
+ const params = { mob_id: mobId };
1062
+ if (options?.memberIds !== undefined) {
1063
+ params.member_ids = options.memberIds;
1064
+ }
1065
+ if (options?.timeoutMs !== undefined) {
1066
+ params.timeout_ms = options.timeoutMs;
1067
+ }
1068
+ const result = await this.request("mob/wait_ready", params);
1069
+ const members = Array.isArray(result.members) ? result.members : [];
1070
+ return members.map((entry) => {
1071
+ const member = entry && typeof entry === "object" ? entry : {};
1072
+ const agentIdentity = String(member.agent_identity ?? "");
1073
+ if (!agentIdentity) {
1074
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/wait_ready response: member missing agent_identity");
1075
+ }
1076
+ const status = MeerkatClient.requireStringField(member, "status", "Invalid mob/wait_ready response");
1077
+ const tokensUsed = MeerkatClient.requireNumberField(member, "tokens_used", "Invalid mob/wait_ready response");
1078
+ const isFinal = MeerkatClient.requireBooleanField(member, "is_final", "Invalid mob/wait_ready response");
1079
+ const rawConnectivity = member.peer_connectivity && typeof member.peer_connectivity === "object"
1080
+ ? member.peer_connectivity
1081
+ : undefined;
1082
+ return {
1083
+ agentIdentity,
1084
+ status,
1085
+ outputPreview: member.output_preview != null ? String(member.output_preview) : undefined,
1086
+ error: member.error != null ? String(member.error) : undefined,
1087
+ tokensUsed,
1088
+ isFinal,
536
1089
  peerConnectivity: rawConnectivity
537
1090
  ? {
538
1091
  reachablePeerCount: Number(rawConnectivity.reachable_peer_count ?? 0),
@@ -554,36 +1107,67 @@ export class MeerkatClient {
554
1107
  async wait_mob_kickoff(mobId, options) {
555
1108
  return this.waitMobKickoff(mobId, options);
556
1109
  }
1110
+ async wait_mob_ready(mobId, options) {
1111
+ return this.waitMobReady(mobId, options);
1112
+ }
557
1113
  async spawnMobHelper(mobId, prompt, options) {
1114
+ const roleName = options?.roleName ?? options?.profileName;
558
1115
  const result = await this.request("mob/spawn_helper", {
559
1116
  mob_id: mobId,
560
1117
  prompt,
561
- meerkat_id: options?.meerkatId,
562
- profile_name: options?.profileName,
1118
+ agent_identity: options?.agentIdentity,
1119
+ role_name: roleName,
563
1120
  runtime_mode: options?.runtimeMode,
564
1121
  backend: options?.backend,
565
1122
  });
1123
+ const resultIdentity = typeof result.agent_identity === "string" && result.agent_identity.length > 0
1124
+ ? result.agent_identity
1125
+ : options?.agentIdentity;
1126
+ if (!resultIdentity) {
1127
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/spawn_helper response: missing agent identity");
1128
+ }
1129
+ const memberRef = typeof result.member_ref === "string" && result.member_ref.length > 0
1130
+ ? result.member_ref
1131
+ : undefined;
1132
+ if (!memberRef) {
1133
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/spawn_helper response: missing member_ref");
1134
+ }
566
1135
  return {
567
1136
  output: result.output != null ? String(result.output) : undefined,
568
1137
  tokensUsed: Number(result.tokens_used ?? 0),
569
- sessionId: result.session_id != null ? String(result.session_id) : undefined,
1138
+ agentIdentity: resultIdentity,
1139
+ memberRef,
570
1140
  };
571
1141
  }
572
1142
  async forkMobHelper(mobId, sourceMemberId, prompt, options) {
1143
+ const roleName = options?.roleName ?? options?.profileName;
573
1144
  const result = await this.request("mob/fork_helper", {
574
1145
  mob_id: mobId,
575
1146
  source_member_id: sourceMemberId,
576
1147
  prompt,
577
- meerkat_id: options?.meerkatId,
578
- profile_name: options?.profileName,
1148
+ agent_identity: options?.agentIdentity,
1149
+ role_name: roleName,
579
1150
  fork_context: options?.forkContext,
580
1151
  runtime_mode: options?.runtimeMode,
581
1152
  backend: options?.backend,
582
1153
  });
1154
+ const resultIdentity = typeof result.agent_identity === "string" && result.agent_identity.length > 0
1155
+ ? result.agent_identity
1156
+ : options?.agentIdentity;
1157
+ if (!resultIdentity) {
1158
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/fork_helper response: missing agent identity");
1159
+ }
1160
+ const memberRef = typeof result.member_ref === "string" && result.member_ref.length > 0
1161
+ ? result.member_ref
1162
+ : undefined;
1163
+ if (!memberRef) {
1164
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/fork_helper response: missing member_ref");
1165
+ }
583
1166
  return {
584
1167
  output: result.output != null ? String(result.output) : undefined,
585
1168
  tokensUsed: Number(result.tokens_used ?? 0),
586
- sessionId: result.session_id != null ? String(result.session_id) : undefined,
1169
+ agentIdentity: resultIdentity,
1170
+ memberRef,
587
1171
  };
588
1172
  }
589
1173
  async wireMobMembers(mobId, member, peer) {
@@ -601,15 +1185,68 @@ export class MeerkatClient {
601
1185
  async mobLifecycle(mobId, action) {
602
1186
  await this.request("mob/lifecycle", { mob_id: mobId, action });
603
1187
  }
604
- async appendMobSystemContext(mobId, meerkatId, text, options) {
1188
+ async appendMobSystemContext(mobId, agentIdentity, text, options) {
605
1189
  return this.request("mob/append_system_context", {
606
1190
  mob_id: mobId,
607
- meerkat_id: meerkatId,
1191
+ agent_identity: agentIdentity,
608
1192
  text,
609
1193
  source: options?.source,
610
1194
  idempotency_key: options?.idempotencyKey,
611
1195
  });
612
1196
  }
1197
+ async readMobEvents(mobId, options) {
1198
+ const params = { mob_id: mobId };
1199
+ if (options?.afterCursor !== undefined) {
1200
+ params.after_cursor = options.afterCursor;
1201
+ }
1202
+ if (options?.limit !== undefined) {
1203
+ params.limit = options.limit;
1204
+ }
1205
+ const result = await this.request("mob/events", params);
1206
+ const events = Array.isArray(result.events)
1207
+ ? result.events
1208
+ : [];
1209
+ return { events };
1210
+ }
1211
+ async mobIngressInteraction(params) {
1212
+ return this.request("mob/ingress_interaction", params);
1213
+ }
1214
+ async createMobProfile(name, profile) {
1215
+ const raw = await this.request("mob/profile/create", {
1216
+ name,
1217
+ profile,
1218
+ });
1219
+ return MeerkatClient.parseMobProfileLookup(raw);
1220
+ }
1221
+ async getMobProfile(name) {
1222
+ const raw = await this.request("mob/profile/get", { name });
1223
+ return MeerkatClient.parseMobProfileLookup(raw);
1224
+ }
1225
+ async listMobProfiles() {
1226
+ const raw = await this.request("mob/profile/list", {});
1227
+ const profiles = Array.isArray(raw.profiles)
1228
+ ? raw.profiles
1229
+ : [];
1230
+ return profiles.map((profile) => MeerkatClient.parseMobProfileLookup(profile));
1231
+ }
1232
+ async updateMobProfile(name, profile, expectedRevision) {
1233
+ const raw = await this.request("mob/profile/update", {
1234
+ name,
1235
+ profile,
1236
+ expected_revision: expectedRevision,
1237
+ });
1238
+ return MeerkatClient.parseMobProfileLookup(raw);
1239
+ }
1240
+ async deleteMobProfile(name, expectedRevision) {
1241
+ const raw = await this.request("mob/profile/delete", {
1242
+ name,
1243
+ expected_revision: expectedRevision,
1244
+ });
1245
+ return {
1246
+ name: String(raw.name ?? name),
1247
+ deletedRevision: Number(raw.deleted_revision ?? expectedRevision),
1248
+ };
1249
+ }
613
1250
  async listMobFlows(mobId) {
614
1251
  const result = await this.request("mob/flows", { mob_id: mobId });
615
1252
  return result.flows ?? [];
@@ -628,8 +1265,8 @@ export class MeerkatClient {
628
1265
  async subscribeMobEvents(mobId) {
629
1266
  return this.openEventSubscription("mob/stream_open", { mob_id: mobId }, "mob/stream_close", MeerkatClient.parseAttributedMobEvent);
630
1267
  }
631
- async subscribeMobMemberEvents(mobId, meerkatId) {
632
- return this.openEventSubscription("mob/stream_open", { mob_id: mobId, member_id: meerkatId }, "mob/stream_close", MeerkatClient.parseAgentEventEnvelope);
1268
+ async subscribeMobMemberEvents(mobId, agentIdentity) {
1269
+ return this.openEventSubscription("mob/stream_open", { mob_id: mobId, agent_identity: agentIdentity }, "mob/stream_close", MeerkatClient.parseAgentEventEnvelope);
633
1270
  }
634
1271
  async openEventSubscription(openMethod, params, closeMethod, parse) {
635
1272
  const result = this.process?.stdin
@@ -668,14 +1305,59 @@ export class MeerkatClient {
668
1305
  });
669
1306
  }
670
1307
  static parseAgentEventEnvelope(raw) {
1308
+ const eventId = MeerkatClient.parseOptionalString(raw.event_id ?? raw.eventId);
1309
+ const source = MeerkatClient.parseEventSourceIdentity(raw.source);
1310
+ const sourceId = MeerkatClient.parseOptionalString(raw.source_id ?? raw.sourceId);
1311
+ const seq = MeerkatClient.parseOptionalNumber(raw.seq);
1312
+ const timestampMs = MeerkatClient.parseOptionalNumber(raw.timestamp_ms ?? raw.timestampMs);
1313
+ const payloadRaw = raw.payload;
1314
+ const payload = payloadRaw && typeof payloadRaw === "object"
1315
+ ? parseCoreEvent(payloadRaw)
1316
+ : undefined;
671
1317
  return {
672
- eventId: String(raw.event_id ?? raw.eventId ?? ""),
673
- sourceId: String(raw.source_id ?? raw.sourceId ?? ""),
674
- seq: Number(raw.seq ?? 0),
675
- timestampMs: Number(raw.timestamp_ms ?? raw.timestampMs ?? 0),
676
- payload: parseCoreEvent((raw.payload ?? {})),
1318
+ ...(eventId != null ? { eventId } : {}),
1319
+ ...(source != null ? { source } : {}),
1320
+ ...(sourceId != null ? { sourceId } : {}),
1321
+ ...(seq != null ? { seq } : {}),
1322
+ ...(timestampMs != null ? { timestampMs } : {}),
1323
+ ...(payload ? { payload } : {}),
677
1324
  };
678
1325
  }
1326
+ static parseEventSourceIdentity(raw) {
1327
+ if (!raw || typeof raw !== "object") {
1328
+ return undefined;
1329
+ }
1330
+ const record = raw;
1331
+ const type = MeerkatClient.parseOptionalString(record.type);
1332
+ switch (type) {
1333
+ case "session": {
1334
+ const sessionId = MeerkatClient.parseOptionalString(record.session_id ?? record.sessionId);
1335
+ return sessionId != null ? { type: "session", sessionId } : undefined;
1336
+ }
1337
+ case "runtime": {
1338
+ const runtimeId = MeerkatClient.parseOptionalString(record.runtime_id ?? record.runtimeId);
1339
+ return runtimeId != null ? { type: "runtime", runtimeId } : undefined;
1340
+ }
1341
+ case "interaction": {
1342
+ const interactionId = MeerkatClient.parseOptionalString(record.interaction_id ?? record.interactionId);
1343
+ return interactionId != null ? { type: "interaction", interactionId } : undefined;
1344
+ }
1345
+ case "callback":
1346
+ return { type: "callback" };
1347
+ case "external": {
1348
+ const sourceId = MeerkatClient.parseOptionalString(record.source_id ?? record.sourceId);
1349
+ return sourceId != null ? { type: "external", sourceId } : undefined;
1350
+ }
1351
+ default:
1352
+ return undefined;
1353
+ }
1354
+ }
1355
+ static parseOptionalString(raw) {
1356
+ return typeof raw === "string" ? raw : undefined;
1357
+ }
1358
+ static parseOptionalNumber(raw) {
1359
+ return typeof raw === "number" && Number.isFinite(raw) ? raw : undefined;
1360
+ }
679
1361
  static parseAttributedMobEvent(raw) {
680
1362
  return {
681
1363
  source: String(raw.source ?? ""),
@@ -691,15 +1373,15 @@ export class MeerkatClient {
691
1373
  if (wireRefs) {
692
1374
  params.skill_refs = wireRefs;
693
1375
  }
694
- if (options?.skillReferences) {
695
- params.skill_references = options.skillReferences;
696
- }
697
1376
  if (options?.flowToolOverlay) {
698
1377
  params.flow_tool_overlay = {
699
1378
  allowed_tools: options.flowToolOverlay.allowedTools,
700
1379
  blocked_tools: options.flowToolOverlay.blockedTools,
701
1380
  };
702
1381
  }
1382
+ if (options?.additionalInstructions != null) {
1383
+ params.additional_instructions = options.additionalInstructions;
1384
+ }
703
1385
  if (options?.keepAlive != null)
704
1386
  params.keep_alive = options.keepAlive;
705
1387
  if (options?.model)
@@ -735,15 +1417,32 @@ export class MeerkatClient {
735
1417
  if (wireRefs) {
736
1418
  params.skill_refs = wireRefs;
737
1419
  }
738
- if (options?.skillReferences) {
739
- params.skill_references = options.skillReferences;
740
- }
741
1420
  if (options?.flowToolOverlay) {
742
1421
  params.flow_tool_overlay = {
743
1422
  allowed_tools: options.flowToolOverlay.allowedTools,
744
1423
  blocked_tools: options.flowToolOverlay.blockedTools,
745
1424
  };
746
1425
  }
1426
+ if (options?.additionalInstructions != null) {
1427
+ params.additional_instructions = options.additionalInstructions;
1428
+ }
1429
+ if (options?.keepAlive != null)
1430
+ params.keep_alive = options.keepAlive;
1431
+ if (options?.model)
1432
+ params.model = options.model;
1433
+ if (options?.provider)
1434
+ params.provider = options.provider;
1435
+ if (options?.maxTokens)
1436
+ params.max_tokens = options.maxTokens;
1437
+ if (options?.systemPrompt)
1438
+ params.system_prompt = options.systemPrompt;
1439
+ if (options?.outputSchema)
1440
+ params.output_schema = options.outputSchema;
1441
+ if (options?.structuredOutputRetries != null) {
1442
+ params.structured_output_retries = options.structuredOutputRetries;
1443
+ }
1444
+ if (options?.providerParams)
1445
+ params.provider_params = options.providerParams;
747
1446
  const rpcRequest = { jsonrpc: "2.0", id: requestId, method: "turn/start", params };
748
1447
  this.process.stdin.write(JSON.stringify(rpcRequest) + "\n");
749
1448
  return new EventStream({
@@ -762,6 +1461,51 @@ export class MeerkatClient {
762
1461
  async _archive(sessionId) {
763
1462
  await this.request("session/archive", { session_id: sessionId });
764
1463
  }
1464
+ static retiredRuntimeSessionControlError() {
1465
+ return new MeerkatError("METHOD_NOT_FOUND", "Retired runtime session control methods are no longer supported by the public RPC surface.");
1466
+ }
1467
+ /**
1468
+ * @internal
1469
+ * @deprecated Retired runtime/session control RPC method; always fails before transport.
1470
+ */
1471
+ async _runtimeStatus(_params) {
1472
+ throw MeerkatClient.retiredRuntimeSessionControlError();
1473
+ }
1474
+ /**
1475
+ * @internal
1476
+ * @deprecated Retired runtime/session control RPC method; always fails before transport.
1477
+ */
1478
+ async _runtimeSubmit(_params) {
1479
+ throw MeerkatClient.retiredRuntimeSessionControlError();
1480
+ }
1481
+ /**
1482
+ * @internal
1483
+ * @deprecated Retired runtime/session control RPC method; always fails before transport.
1484
+ */
1485
+ async _runtimeSubmission(_params) {
1486
+ throw MeerkatClient.retiredRuntimeSessionControlError();
1487
+ }
1488
+ /**
1489
+ * @internal
1490
+ * @deprecated Retired runtime/session control RPC method; always fails before transport.
1491
+ */
1492
+ async _runtimeSubmissions(_params) {
1493
+ throw MeerkatClient.retiredRuntimeSessionControlError();
1494
+ }
1495
+ /**
1496
+ * @internal
1497
+ * @deprecated Retired runtime/session control RPC method; always fails before transport.
1498
+ */
1499
+ async _runtimeRetire(_params) {
1500
+ throw MeerkatClient.retiredRuntimeSessionControlError();
1501
+ }
1502
+ /**
1503
+ * @internal
1504
+ * @deprecated Retired runtime/session control RPC method; always fails before transport.
1505
+ */
1506
+ async _runtimeReset(_params) {
1507
+ throw MeerkatClient.retiredRuntimeSessionControlError();
1508
+ }
765
1509
  /** @internal */
766
1510
  async _send(sessionId, command) {
767
1511
  return this.send(sessionId, command);
@@ -770,57 +1514,138 @@ export class MeerkatClient {
770
1514
  async _peers(sessionId) {
771
1515
  return this.peers(sessionId);
772
1516
  }
1517
+ /**
1518
+ * Send a typed comms command. Invalid discriminators (`source`, `stream`,
1519
+ * `handling_mode`, `status`) are rejected at the server's typed-serde
1520
+ * boundary.
1521
+ */
773
1522
  async send(sessionId, command) {
774
- return this.request("comms/send", { session_id: sessionId, ...command });
1523
+ const result = await this.request("comms/send", { session_id: sessionId, ...command });
1524
+ return MeerkatClient.parseCommsSendReceipt(result);
775
1525
  }
776
1526
  async peers(sessionId) {
777
1527
  return this.request("comms/peers", { session_id: sessionId });
778
1528
  }
779
- async runtimeState(sessionId) {
780
- const result = await this.request("runtime/state", { session_id: sessionId });
781
- if (typeof result.state !== "string" || result.state.length === 0) {
782
- throw new MeerkatError("INVALID_RESPONSE", "Invalid runtime/state response: missing state");
1529
+ async runtimeRealtimeAttachmentStatus(sessionId) {
1530
+ const result = await this.request("session/realtime_attachment_status", {
1531
+ session_id: sessionId,
1532
+ });
1533
+ if (typeof result.status !== "string" || result.status.length === 0) {
1534
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid session/realtime_attachment_status response: missing status");
783
1535
  }
784
1536
  return result;
785
1537
  }
786
- async runtimeAccept(sessionId, input) {
787
- const result = await this.request("runtime/accept", { session_id: sessionId, input });
788
- if (typeof result.outcome_type !== "string" || result.outcome_type.length === 0) {
789
- throw new MeerkatError("INVALID_RESPONSE", "Invalid runtime/accept response: missing outcome_type");
790
- }
791
- return result;
1538
+ /** Idempotent spawn: spawns or returns the existing member entry. */
1539
+ async mobEnsureMember(mobId, spec) {
1540
+ return await this.request("mob/ensure_member", {
1541
+ mob_id: mobId,
1542
+ spec,
1543
+ });
1544
+ }
1545
+ /** Declarative reconcile: converge roster to the desired spec list. */
1546
+ async mobReconcile(mobId, desired, options) {
1547
+ const params = { mob_id: mobId, desired };
1548
+ if (options !== undefined)
1549
+ params.options = options;
1550
+ return await this.request("mob/reconcile", params);
1551
+ }
1552
+ /** Label-filtered member listing. */
1553
+ async mobListMembersMatching(mobId, filter) {
1554
+ return await this.request("mob/list_members_matching", {
1555
+ mob_id: mobId,
1556
+ filter,
1557
+ });
792
1558
  }
793
- async runtimeRetire(sessionId) {
794
- const result = await this.request("runtime/retire", { session_id: sessionId });
795
- if (typeof result.inputs_abandoned !== "number") {
796
- throw new MeerkatError("INVALID_RESPONSE", "Invalid runtime/retire response: missing inputs_abandoned");
1559
+ async realtimeOpenInfo(request) {
1560
+ const result = await this.request("realtime/open_info", request);
1561
+ if (typeof result.ws_url !== "string" || result.ws_url.length === 0) {
1562
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid realtime/open_info response: missing ws_url");
797
1563
  }
798
1564
  return result;
799
1565
  }
800
- async runtimeReset(sessionId) {
801
- const result = await this.request("runtime/reset", { session_id: sessionId });
802
- if (typeof result.inputs_abandoned !== "number") {
803
- throw new MeerkatError("INVALID_RESPONSE", "Invalid runtime/reset response: missing inputs_abandoned");
1566
+ async realtimeStatus(params) {
1567
+ const result = await this.request("realtime/status", params);
1568
+ if (typeof result.status !== "object" || result.status === null) {
1569
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid realtime/status response: missing status");
804
1570
  }
805
1571
  return result;
806
1572
  }
807
- async inputState(sessionId, inputId) {
808
- const result = await this.request("input/state", { session_id: sessionId, input_id: inputId });
809
- if (result === null) {
810
- return null;
811
- }
812
- if (typeof result !== "object" || result === null) {
813
- throw new MeerkatError("INVALID_RESPONSE", "Invalid input/state response: expected object or null");
1573
+ async realtimeCapabilities(params) {
1574
+ const result = await this.request("realtime/capabilities", params);
1575
+ if (typeof result.capabilities !== "object" || result.capabilities === null) {
1576
+ throw new MeerkatError("INVALID_RESPONSE", "Invalid realtime/capabilities response: missing capabilities");
814
1577
  }
815
1578
  return result;
816
1579
  }
817
- async inputList(sessionId) {
818
- const result = (await this.request("input/list", {
819
- session_id: sessionId,
820
- }));
821
- return Array.isArray(result.input_ids)
822
- ? result.input_ids.map((inputId) => String(inputId))
823
- : [];
1580
+ // -- Auth + realm (Phase 4d) --------------------------------------------
1581
+ //
1582
+ // These wrappers cover the RPC catalog's auth/* and realm/* methods.
1583
+ // Write-side methods (auth/profile/create, delete, login/start,
1584
+ // login/complete, login/device_start, logout) are currently server-stubbed
1585
+ // with typed INVALID_REQUEST pointing to the CLI; the wrappers surface
1586
+ // whatever the server returns so they stay honest about the state.
1587
+ async realmList() {
1588
+ return this.request("realm/list", {});
1589
+ }
1590
+ async realmGet(realmId) {
1591
+ return this.request("realm/get", { realm_id: realmId });
1592
+ }
1593
+ async authProfileList(realmId) {
1594
+ return this.request("auth/profile/list", { realm_id: realmId });
1595
+ }
1596
+ async authProfileGet(realmId, bindingId, profileId) {
1597
+ const params = {
1598
+ realm_id: realmId,
1599
+ binding_id: bindingId,
1600
+ };
1601
+ if (profileId)
1602
+ params.profile_id = profileId;
1603
+ return this.request("auth/profile/get", params);
1604
+ }
1605
+ async authProfileCreate(params) {
1606
+ return this.request("auth/profile/create", params);
1607
+ }
1608
+ async authProfileDelete(realmId, bindingId, profileId) {
1609
+ const params = {
1610
+ realm_id: realmId,
1611
+ binding_id: bindingId,
1612
+ };
1613
+ if (profileId)
1614
+ params.profile_id = profileId;
1615
+ return this.request("auth/profile/delete", params);
1616
+ }
1617
+ async authLoginStart(params) {
1618
+ return this.request("auth/login/start", params);
1619
+ }
1620
+ async authLoginComplete(params) {
1621
+ return this.request("auth/login/complete", params);
1622
+ }
1623
+ async authLoginDeviceStart(params) {
1624
+ return this.request("auth/login/device_start", params);
1625
+ }
1626
+ async authLoginDeviceComplete(params) {
1627
+ return this.request("auth/login/device_complete", params);
1628
+ }
1629
+ async authLoginProvisionApiKey(params) {
1630
+ return this.request("auth/login/provision_api_key", params);
1631
+ }
1632
+ async authStatusGet(realmId, bindingId, profileId) {
1633
+ const params = {
1634
+ realm_id: realmId,
1635
+ binding_id: bindingId,
1636
+ };
1637
+ if (profileId !== undefined)
1638
+ params.profile_id = profileId;
1639
+ return this.request("auth/status/get", params);
1640
+ }
1641
+ async authLogout(realmId, bindingId, profileId) {
1642
+ const params = {
1643
+ realm_id: realmId,
1644
+ binding_id: bindingId,
1645
+ };
1646
+ if (profileId !== undefined)
1647
+ params.profile_id = profileId;
1648
+ return this.request("auth/logout", params);
824
1649
  }
825
1650
  // -- Transport ----------------------------------------------------------
826
1651
  handleLine(line) {
@@ -961,6 +1786,33 @@ export class MeerkatClient {
961
1786
  }
962
1787
  return String(raw);
963
1788
  }
1789
+ static requireRecord(raw, field, context) {
1790
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1791
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: missing ${field}`);
1792
+ }
1793
+ return raw;
1794
+ }
1795
+ static requireStringField(raw, field, context) {
1796
+ const value = raw[field];
1797
+ if (typeof value !== "string" || value.length === 0) {
1798
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: missing ${field}`);
1799
+ }
1800
+ return value;
1801
+ }
1802
+ static requireNumberField(raw, field, context, displayField = field) {
1803
+ const value = raw[field];
1804
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1805
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: ${displayField} must be number`);
1806
+ }
1807
+ return value;
1808
+ }
1809
+ static requireBooleanField(raw, field, context) {
1810
+ const value = raw[field];
1811
+ if (typeof value !== "boolean") {
1812
+ throw new MeerkatError("INVALID_RESPONSE", `${context}: ${field} must be boolean`);
1813
+ }
1814
+ return value;
1815
+ }
964
1816
  static parseSkillDiagnostics(raw) {
965
1817
  if (!raw || typeof raw !== "object")
966
1818
  return undefined;
@@ -1006,15 +1858,16 @@ export class MeerkatClient {
1006
1858
  }
1007
1859
  }
1008
1860
  static parseRunResult(data) {
1009
- const usageRaw = data.usage;
1861
+ const context = "Invalid run result";
1862
+ const usageRaw = MeerkatClient.requireRecord(data.usage, "usage", context);
1010
1863
  const usage = {
1011
- inputTokens: Number(usageRaw?.input_tokens ?? 0),
1012
- outputTokens: Number(usageRaw?.output_tokens ?? 0),
1864
+ inputTokens: MeerkatClient.requireNumberField(usageRaw, "input_tokens", context, "usage.input_tokens"),
1865
+ outputTokens: MeerkatClient.requireNumberField(usageRaw, "output_tokens", context, "usage.output_tokens"),
1013
1866
  cacheCreationTokens: usageRaw?.cache_creation_tokens != null
1014
- ? Number(usageRaw.cache_creation_tokens)
1867
+ ? MeerkatClient.requireNumberField(usageRaw, "cache_creation_tokens", context, "usage.cache_creation_tokens")
1015
1868
  : undefined,
1016
1869
  cacheReadTokens: usageRaw?.cache_read_tokens != null
1017
- ? Number(usageRaw.cache_read_tokens)
1870
+ ? MeerkatClient.requireNumberField(usageRaw, "cache_read_tokens", context, "usage.cache_read_tokens")
1018
1871
  : undefined,
1019
1872
  };
1020
1873
  const rawWarnings = data.schema_warnings;
@@ -1023,18 +1876,258 @@ export class MeerkatClient {
1023
1876
  path: String(w.path ?? ""),
1024
1877
  message: String(w.message ?? ""),
1025
1878
  }));
1879
+ const rawExtractionError = data.extraction_error;
1880
+ const extractionError = rawExtractionError && typeof rawExtractionError === "object"
1881
+ ? {
1882
+ lastOutput: String(rawExtractionError.last_output ?? ""),
1883
+ attempts: MeerkatClient.requireNumberField(rawExtractionError, "attempts", context, "extraction_error.attempts"),
1884
+ reason: String(rawExtractionError.reason ?? ""),
1885
+ }
1886
+ : undefined;
1026
1887
  return {
1027
1888
  sessionId: String(data.session_id ?? ""),
1028
1889
  sessionRef: data.session_ref != null ? String(data.session_ref) : undefined,
1029
1890
  text: String(data.text ?? ""),
1030
- turns: Number(data.turns ?? 0),
1031
- toolCalls: Number(data.tool_calls ?? 0),
1891
+ turns: MeerkatClient.requireNumberField(data, "turns", context),
1892
+ toolCalls: MeerkatClient.requireNumberField(data, "tool_calls", context),
1032
1893
  usage,
1894
+ terminalCauseKind: typeof data.terminal_cause_kind === "string"
1895
+ ? data.terminal_cause_kind
1896
+ : undefined,
1033
1897
  structuredOutput: data.structured_output,
1898
+ extractionError,
1034
1899
  schemaWarnings,
1035
1900
  skillDiagnostics: MeerkatClient.parseSkillDiagnostics(data.skill_diagnostics),
1036
1901
  };
1037
1902
  }
1903
+ static parseSessionInfo(data) {
1904
+ const labelsRaw = data.labels && typeof data.labels === "object"
1905
+ ? data.labels
1906
+ : {};
1907
+ const labels = Object.fromEntries(Object.entries(labelsRaw).map(([key, value]) => [key, String(value)]));
1908
+ return {
1909
+ sessionId: String(data.session_id ?? ""),
1910
+ sessionRef: data.session_ref != null ? String(data.session_ref) : undefined,
1911
+ createdAt: Number(data.created_at ?? 0),
1912
+ updatedAt: Number(data.updated_at ?? 0),
1913
+ messageCount: Number(data.message_count ?? 0),
1914
+ isActive: Boolean(data.is_active),
1915
+ totalTokens: data.total_tokens != null ? Number(data.total_tokens) : undefined,
1916
+ model: data.model != null ? String(data.model) : undefined,
1917
+ provider: data.provider != null ? String(data.provider) : undefined,
1918
+ lastAssistantText: data.last_assistant_text != null ? String(data.last_assistant_text) : undefined,
1919
+ labels,
1920
+ };
1921
+ }
1922
+ static parseConfigEnvelope(data) {
1923
+ const rawConfig = data.config && typeof data.config === "object"
1924
+ ? data.config
1925
+ : {};
1926
+ const rawResolvedPaths = data.resolved_paths && typeof data.resolved_paths === "object"
1927
+ ? data.resolved_paths
1928
+ : undefined;
1929
+ const resolvedPaths = rawResolvedPaths
1930
+ ? Object.fromEntries(Object.entries(rawResolvedPaths).map(([key, value]) => [key, String(value)]))
1931
+ : undefined;
1932
+ return {
1933
+ config: rawConfig,
1934
+ generation: Number(data.generation ?? 0),
1935
+ realmId: data.realm_id != null ? String(data.realm_id) : undefined,
1936
+ instanceId: data.instance_id != null ? String(data.instance_id) : undefined,
1937
+ backend: data.backend != null ? String(data.backend) : undefined,
1938
+ resolvedPaths,
1939
+ };
1940
+ }
1941
+ static parseCommsSendReceipt(data) {
1942
+ return {
1943
+ ...data,
1944
+ requestId: data.request_id != null ? String(data.request_id) : undefined,
1945
+ interactionId: data.interaction_id != null ? String(data.interaction_id) : undefined,
1946
+ inputId: data.input_id != null ? String(data.input_id) : undefined,
1947
+ };
1948
+ }
1949
+ static parseModelsCatalog(data) {
1950
+ const providersRaw = Array.isArray(data.providers)
1951
+ ? data.providers
1952
+ : [];
1953
+ let contractVersion = { major: 0, minor: 0, patch: 0 };
1954
+ if (data.contract_version && typeof data.contract_version === "object") {
1955
+ const contractVersionRaw = data.contract_version;
1956
+ contractVersion = {
1957
+ major: Number(contractVersionRaw.major ?? 0),
1958
+ minor: Number(contractVersionRaw.minor ?? 0),
1959
+ patch: Number(contractVersionRaw.patch ?? 0),
1960
+ };
1961
+ }
1962
+ else if (typeof data.contract_version === "string") {
1963
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(data.contract_version);
1964
+ if (match) {
1965
+ contractVersion = {
1966
+ major: Number(match[1]),
1967
+ minor: Number(match[2]),
1968
+ patch: Number(match[3]),
1969
+ };
1970
+ }
1971
+ }
1972
+ return {
1973
+ contractVersion,
1974
+ providers: providersRaw.map((provider) => ({
1975
+ provider: String(provider.provider ?? ""),
1976
+ defaultModelId: String(provider.default_model_id ?? ""),
1977
+ models: Array.isArray(provider.models)
1978
+ ? provider.models.map((model) => ({
1979
+ id: String(model.id ?? ""),
1980
+ displayName: String(model.display_name ?? ""),
1981
+ tier: String(model.tier ?? "supported") === "recommended"
1982
+ ? "recommended"
1983
+ : "supported",
1984
+ contextWindow: model.context_window != null ? Number(model.context_window) : undefined,
1985
+ maxOutputTokens: model.max_output_tokens != null ? Number(model.max_output_tokens) : undefined,
1986
+ serverId: model.server_id != null ? String(model.server_id) : undefined,
1987
+ profile: model.profile && typeof model.profile === "object"
1988
+ ? {
1989
+ modelFamily: String(model.profile.model_family ?? ""),
1990
+ supportsTemperature: Boolean(model.profile.supports_temperature),
1991
+ supportsThinking: Boolean(model.profile.supports_thinking),
1992
+ supportsReasoning: Boolean(model.profile.supports_reasoning),
1993
+ inlineVideo: Boolean(model.profile.inline_video),
1994
+ paramsSchema: model.profile.params_schema,
1995
+ }
1996
+ : undefined,
1997
+ }))
1998
+ : [],
1999
+ })),
2000
+ };
2001
+ }
2002
+ static parseSchedule(data) {
2003
+ const labelsRaw = data.labels && typeof data.labels === "object"
2004
+ ? data.labels
2005
+ : {};
2006
+ return {
2007
+ scheduleId: String(data.schedule_id ?? ""),
2008
+ phase: String(data.phase ?? ""),
2009
+ revision: typeof data.revision === "object" && data.revision !== null
2010
+ ? Number(data.revision["0"] ?? 0)
2011
+ : Number(data.revision ?? 0),
2012
+ name: data.name != null ? String(data.name) : undefined,
2013
+ description: data.description != null ? String(data.description) : undefined,
2014
+ trigger: data.trigger && typeof data.trigger === "object"
2015
+ ? data.trigger
2016
+ : {},
2017
+ target: data.target && typeof data.target === "object"
2018
+ ? data.target
2019
+ : {},
2020
+ misfirePolicy: data.misfire_policy != null
2021
+ ? data.misfire_policy
2022
+ : undefined,
2023
+ overlapPolicy: data.overlap_policy != null ? String(data.overlap_policy) : undefined,
2024
+ missingTargetPolicy: data.missing_target_policy != null ? String(data.missing_target_policy) : undefined,
2025
+ planningHorizonDays: data.planning_horizon_days != null ? Number(data.planning_horizon_days) : undefined,
2026
+ planningHorizonOccurrences: data.planning_horizon_occurrences != null
2027
+ ? Number(data.planning_horizon_occurrences)
2028
+ : undefined,
2029
+ nextOccurrenceOrdinal: typeof data.next_occurrence_ordinal === "object"
2030
+ ? Number(data.next_occurrence_ordinal["0"] ?? 0)
2031
+ : data.next_occurrence_ordinal != null
2032
+ ? Number(data.next_occurrence_ordinal)
2033
+ : undefined,
2034
+ planningCursorUtc: data.planning_cursor_utc != null ? String(data.planning_cursor_utc) : undefined,
2035
+ createdAtUtc: data.created_at_utc != null ? String(data.created_at_utc) : undefined,
2036
+ updatedAtUtc: data.updated_at_utc != null ? String(data.updated_at_utc) : undefined,
2037
+ deletedAtUtc: data.deleted_at_utc != null ? String(data.deleted_at_utc) : undefined,
2038
+ labels: Object.fromEntries(Object.entries(labelsRaw).map(([key, value]) => [key, String(value)])),
2039
+ };
2040
+ }
2041
+ static parseScheduleOccurrence(data) {
2042
+ return {
2043
+ occurrenceId: String(data.occurrence_id ?? ""),
2044
+ scheduleId: String(data.schedule_id ?? ""),
2045
+ scheduleRevision: typeof data.schedule_revision === "object" && data.schedule_revision !== null
2046
+ ? Number(data.schedule_revision["0"] ?? 0)
2047
+ : Number(data.schedule_revision ?? 0),
2048
+ occurrenceOrdinal: typeof data.occurrence_ordinal === "object" && data.occurrence_ordinal !== null
2049
+ ? Number(data.occurrence_ordinal["0"] ?? 0)
2050
+ : Number(data.occurrence_ordinal ?? 0),
2051
+ phase: String(data.phase ?? ""),
2052
+ dueAtUtc: String(data.due_at_utc ?? ""),
2053
+ triggerSnapshot: data.trigger_snapshot && typeof data.trigger_snapshot === "object"
2054
+ ? data.trigger_snapshot
2055
+ : {},
2056
+ targetSnapshot: data.target_snapshot && typeof data.target_snapshot === "object"
2057
+ ? data.target_snapshot
2058
+ : {},
2059
+ misfirePolicy: data.misfire_policy != null
2060
+ ? data.misfire_policy
2061
+ : undefined,
2062
+ overlapPolicy: data.overlap_policy != null ? String(data.overlap_policy) : undefined,
2063
+ missingTargetPolicy: data.missing_target_policy != null ? String(data.missing_target_policy) : undefined,
2064
+ claimedBy: data.claimed_by != null ? String(data.claimed_by) : undefined,
2065
+ leaseExpiresAtUtc: data.lease_expires_at_utc != null ? String(data.lease_expires_at_utc) : undefined,
2066
+ deliveryCorrelationId: data.delivery_correlation_id != null ? String(data.delivery_correlation_id) : undefined,
2067
+ lastReceipt: data.last_receipt && typeof data.last_receipt === "object"
2068
+ ? data.last_receipt
2069
+ : undefined,
2070
+ failureClass: data.failure_class != null ? String(data.failure_class) : undefined,
2071
+ failureDetail: data.failure_detail != null ? String(data.failure_detail) : undefined,
2072
+ attemptCount: data.attempt_count != null ? Number(data.attempt_count) : undefined,
2073
+ createdAtUtc: data.created_at_utc != null ? String(data.created_at_utc) : undefined,
2074
+ claimedAtUtc: data.claimed_at_utc != null ? String(data.claimed_at_utc) : undefined,
2075
+ dispatchedAtUtc: data.dispatched_at_utc != null ? String(data.dispatched_at_utc) : undefined,
2076
+ completedAtUtc: data.completed_at_utc != null ? String(data.completed_at_utc) : undefined,
2077
+ supersededByRevision: typeof data.superseded_by_revision === "object" && data.superseded_by_revision !== null
2078
+ ? Number(data.superseded_by_revision["0"] ?? 0)
2079
+ : data.superseded_by_revision != null
2080
+ ? Number(data.superseded_by_revision)
2081
+ : undefined,
2082
+ };
2083
+ }
2084
+ static parseMobProfileLookup(data) {
2085
+ if (Boolean(data.not_found)) {
2086
+ return {
2087
+ notFound: true,
2088
+ name: String(data.name ?? ""),
2089
+ };
2090
+ }
2091
+ return {
2092
+ notFound: false,
2093
+ name: String(data.name ?? ""),
2094
+ profile: data.profile && typeof data.profile === "object"
2095
+ ? data.profile
2096
+ : undefined,
2097
+ revision: data.revision != null ? Number(data.revision) : undefined,
2098
+ createdAt: data.created_at != null ? String(data.created_at) : undefined,
2099
+ updatedAt: data.updated_at != null ? String(data.updated_at) : undefined,
2100
+ };
2101
+ }
2102
+ static toWireCreateScheduleRequest(request) {
2103
+ return {
2104
+ name: request.name,
2105
+ description: request.description,
2106
+ trigger: request.trigger,
2107
+ target: request.target,
2108
+ misfire_policy: request.misfirePolicy,
2109
+ overlap_policy: request.overlapPolicy,
2110
+ missing_target_policy: request.missingTargetPolicy,
2111
+ labels: request.labels,
2112
+ planning_horizon_days: request.planningHorizonDays,
2113
+ planning_horizon_occurrences: request.planningHorizonOccurrences,
2114
+ };
2115
+ }
2116
+ static toWireUpdateSchedulePatch(update) {
2117
+ return {
2118
+ expected_revision: update.expectedRevision,
2119
+ name: update.name,
2120
+ description: update.description,
2121
+ trigger: update.trigger,
2122
+ target: update.target,
2123
+ misfire_policy: update.misfirePolicy,
2124
+ overlap_policy: update.overlapPolicy,
2125
+ missing_target_policy: update.missingTargetPolicy,
2126
+ planning_horizon_days: update.planningHorizonDays,
2127
+ planning_horizon_occurrences: update.planningHorizonOccurrences,
2128
+ labels: update.labels,
2129
+ };
2130
+ }
1038
2131
  static parseSessionHistory(data) {
1039
2132
  const rawMessages = Array.isArray(data.messages)
1040
2133
  ? data.messages
@@ -1050,6 +2143,10 @@ export class MeerkatClient {
1050
2143
  };
1051
2144
  }
1052
2145
  static parseSessionMessage(data) {
2146
+ const role = String(data.role ?? "");
2147
+ const contentValue = role === "system_notice" && data.content == null && data.body != null
2148
+ ? String(data.body)
2149
+ : data.content;
1053
2150
  const rawToolCalls = Array.isArray(data.tool_calls)
1054
2151
  ? data.tool_calls
1055
2152
  : [];
@@ -1060,8 +2157,9 @@ export class MeerkatClient {
1060
2157
  ? data.results
1061
2158
  : [];
1062
2159
  return {
1063
- role: String(data.role ?? ""),
1064
- content: data.content != null ? MeerkatClient.parseContentInput(data.content) : undefined,
2160
+ role,
2161
+ createdAt: String(data.created_at ?? ""),
2162
+ content: contentValue != null ? MeerkatClient.parseContentInput(contentValue) : undefined,
1065
2163
  toolCalls: rawToolCalls.map((toolCall) => ({
1066
2164
  id: String(toolCall.id ?? ""),
1067
2165
  name: String(toolCall.name ?? ""),
@@ -1119,12 +2217,22 @@ export class MeerkatClient {
1119
2217
  }
1120
2218
  static parseSessionAssistantBlock(data) {
1121
2219
  const blockData = data.data ?? {};
2220
+ const blobRef = blockData.blob_ref;
2221
+ const revisedPrompt = blockData.revised_prompt != null && typeof blockData.revised_prompt === "object"
2222
+ ? blockData.revised_prompt
2223
+ : undefined;
1122
2224
  return {
1123
2225
  blockType: String(data.block_type ?? ""),
1124
2226
  text: blockData.text != null ? String(blockData.text) : undefined,
1125
2227
  id: blockData.id != null ? String(blockData.id) : undefined,
1126
2228
  name: blockData.name != null ? String(blockData.name) : undefined,
1127
2229
  args: blockData.args,
2230
+ imageId: blockData.image_id != null ? String(blockData.image_id) : undefined,
2231
+ blobId: blobRef?.blob_id != null ? String(blobRef.blob_id) : undefined,
2232
+ mediaType: blockData.media_type != null ? String(blockData.media_type) : undefined,
2233
+ width: blockData.width != null ? Number(blockData.width) : undefined,
2234
+ height: blockData.height != null ? Number(blockData.height) : undefined,
2235
+ revisedPrompt,
1128
2236
  meta: blockData.meta,
1129
2237
  };
1130
2238
  }
@@ -1226,13 +2334,23 @@ export class MeerkatClient {
1226
2334
  params.budget_limits = options.budgetLimits;
1227
2335
  if (options.providerParams != null)
1228
2336
  params.provider_params = options.providerParams;
1229
- if (options.preloadSkills != null)
1230
- params.preload_skills = options.preloadSkills;
2337
+ if (options.preloadSkills != null) {
2338
+ params.preload_skills = skillKeysToWire(options.preloadSkills);
2339
+ }
1231
2340
  const wireRefs = skillRefsToWire(options.skillRefs);
1232
2341
  if (wireRefs)
1233
2342
  params.skill_refs = wireRefs;
1234
- if (options.skillReferences != null)
1235
- params.skill_references = options.skillReferences;
2343
+ if (options.labels != null)
2344
+ params.labels = options.labels;
2345
+ if (options.additionalInstructions != null) {
2346
+ params.additional_instructions = options.additionalInstructions;
2347
+ }
2348
+ if (options.appContext !== undefined)
2349
+ params.app_context = options.appContext;
2350
+ if (options.shellEnv != null)
2351
+ params.shell_env = options.shellEnv;
2352
+ if (options.externalTools != null)
2353
+ params.external_tools = options.externalTools;
1236
2354
  return params;
1237
2355
  }
1238
2356
  // -- Binary resolution --------------------------------------------------
@@ -1393,9 +2511,9 @@ export class MeerkatClient {
1393
2511
  static buildArgs(legacy, options) {
1394
2512
  if (legacy)
1395
2513
  return ["rpc"];
2514
+ const args = ["--realtime-ws", "127.0.0.1:0"];
1396
2515
  if (!options)
1397
- return [];
1398
- const args = [];
2516
+ return args;
1399
2517
  if (options.isolated)
1400
2518
  args.push("--isolated");
1401
2519
  if (options.realmId)