@rkat/sdk 0.5.2 → 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,6 +408,22 @@ 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
428
  async listSessions(options) {
256
429
  const params = {};
@@ -268,13 +441,31 @@ export class MeerkatClient {
268
441
  const raw = await this.request("session/read", { session_id: sessionId });
269
442
  return MeerkatClient.parseSessionInfo(raw);
270
443
  }
271
- async sendExternalEvent(sessionId, payload, options) {
272
- const params = { session_id: sessionId, payload };
273
- if (options?.source !== undefined) {
274
- params.source = options.source;
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;
275
453
  }
276
454
  return this.request("session/external_event", params);
277
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
+ }
278
469
  async injectContext(sessionId, text, options) {
279
470
  const params = { session_id: sessionId, text };
280
471
  if (options?.source !== undefined) {
@@ -361,13 +552,6 @@ export class MeerkatClient {
361
552
  const result = await this.request("skills/list", {});
362
553
  return result.skills ?? [];
363
554
  }
364
- async inspectSkill(id, options) {
365
- const params = { id };
366
- if (options?.source !== undefined) {
367
- params.source = options.source;
368
- }
369
- return this.request("skills/inspect", params);
370
- }
371
555
  async getBlob(blobId) {
372
556
  const result = await this.request("blob/get", { blob_id: blobId });
373
557
  return {
@@ -376,6 +560,45 @@ export class MeerkatClient {
376
560
  dataBase64: String(result.data ?? ""),
377
561
  };
378
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
+ }
379
602
  async getModelsCatalog() {
380
603
  const result = await this.request("models/catalog", {});
381
604
  return MeerkatClient.parseModelsCatalog(result);
@@ -472,110 +695,167 @@ export class MeerkatClient {
472
695
  const status = typeof rawStatus === "string"
473
696
  ? rawStatus
474
697
  : (typeof rawStatus === "object" && rawStatus !== null
475
- ? String(Object.keys(rawStatus)[0] ?? "unknown")
476
- : 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
+ }
477
703
  return { mobId: String(result.mob_id ?? mobId), status };
478
704
  }
479
705
  async listMobMembers(mobId) {
480
706
  const result = await this.request("mob/members", { mob_id: mobId });
481
707
  const members = result.members ?? [];
482
- return members.map((member) => ({
483
- meerkatId: String(member.meerkat_id ?? member.meerkatId ?? ""),
484
- profile: String(member.profile_name ?? member.profile ?? ""),
485
- memberRef: member.member_ref,
486
- peerId: member.peer_id != null ? String(member.peer_id) : undefined,
487
- externalPeerSpecs: member.external_peer_specs && typeof member.external_peer_specs === "object"
488
- ? Object.fromEntries(Object.entries(member.external_peer_specs).map(([key, value]) => [key, (value ?? {})]))
489
- : undefined,
490
- runtimeMode: member.runtime_mode != null ? String(member.runtime_mode) : undefined,
491
- state: member.state != null ? String(member.state) : undefined,
492
- wiredTo: Array.isArray(member.wired_to)
493
- ? member.wired_to.map((peer) => String(peer))
494
- : undefined,
495
- labels: member.labels && typeof member.labels === 'object'
496
- ? Object.fromEntries(Object.entries(member.labels).map(([key, value]) => [key, String(value)]))
497
- : undefined,
498
- status: member.status != null ? String(member.status) : undefined,
499
- error: member.error != null ? String(member.error) : undefined,
500
- isFinal: member.is_final != null ? Boolean(member.is_final) : undefined,
501
- currentSessionId: member.current_session_id != null ? String(member.current_session_id) : undefined,
502
- sessionId: member.member_ref && typeof member.member_ref === 'object'
503
- ? member.member_ref.session_id != null
504
- ? String(member.member_ref.session_id)
505
- : undefined
506
- : undefined,
507
- }));
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
+ });
508
737
  }
509
- async sendMobMemberContent(mobId, meerkatId, content, options) {
738
+ async sendMobMemberContent(mobId, agentIdentity, content, options) {
510
739
  const result = await this.request("mob/member_send", {
511
740
  mob_id: mobId,
512
- meerkat_id: meerkatId,
741
+ agent_identity: agentIdentity,
513
742
  content,
514
743
  handling_mode: options?.handlingMode,
515
744
  render_metadata: options?.renderMetadata,
516
745
  });
517
- const sessionId = result.session_id;
518
- if (typeof sessionId !== "string" || sessionId.length === 0) {
519
- 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");
520
751
  }
521
752
  return {
522
- memberId: typeof result.member_id === "string" && result.member_id.length > 0
523
- ? result.member_id
524
- : meerkatId,
525
- sessionId,
753
+ agentIdentity: typeof result.agent_identity === "string" && result.agent_identity.length > 0
754
+ ? result.agent_identity
755
+ : agentIdentity,
756
+ memberRef,
526
757
  handlingMode: result.handling_mode === "steer" || result.handling_mode === "queue"
527
758
  ? result.handling_mode
528
759
  : (options?.handlingMode ?? "queue"),
529
760
  };
530
761
  }
531
762
  async spawnMobMember(mobId, options) {
532
- return this.request("mob/spawn", {
533
- mob_id: mobId,
534
- profile: options.profile,
535
- meerkat_id: options.meerkatId,
536
- initial_message: options.initialMessage,
537
- runtime_mode: options.runtimeMode,
538
- backend: options.backend,
539
- resume_session_id: options.resumeSessionId,
540
- labels: options.labels,
541
- context: options.context,
542
- additional_instructions: options.additionalInstructions,
543
- });
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
+ };
544
777
  }
545
778
  async spawnMobMembers(mobId, specs) {
546
779
  const result = await this.request("mob/spawn_many", {
547
780
  mob_id: mobId,
548
- specs: specs.map((spec) => ({
549
- profile: spec.profile,
550
- meerkat_id: spec.meerkatId,
551
- initial_message: spec.initialMessage,
552
- runtime_mode: spec.runtimeMode,
553
- backend: spec.backend,
554
- resume_session_id: spec.resumeSessionId,
555
- labels: spec.labels,
556
- context: spec.context,
557
- additional_instructions: spec.additionalInstructions,
558
- })),
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
+ };
559
847
  });
560
- const entries = Array.isArray(result.results)
561
- ? result.results
562
- : [];
563
- return entries.map((entry) => ({
564
- ok: Boolean(entry.ok),
565
- memberRef: entry.member_ref && typeof entry.member_ref === "object"
566
- ? entry.member_ref
567
- : undefined,
568
- sessionId: entry.session_id != null ? String(entry.session_id) : undefined,
569
- error: entry.error != null ? String(entry.error) : undefined,
570
- }));
571
848
  }
572
- async retireMobMember(mobId, meerkatId) {
573
- 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
+ });
574
854
  }
575
- async respawnMobMember(mobId, meerkatId, initialMessage) {
855
+ async respawnMobMember(mobId, agentIdentity, initialMessage) {
576
856
  const result = await this.request("mob/respawn", {
577
857
  mob_id: mobId,
578
- meerkat_id: meerkatId,
858
+ agent_identity: agentIdentity,
579
859
  initial_message: initialMessage,
580
860
  });
581
861
  const status = String(result.status ?? "completed");
@@ -586,21 +866,35 @@ export class MeerkatClient {
586
866
  if (!receipt || typeof receipt !== "object") {
587
867
  throw new MeerkatError("INVALID_RESPONSE", "Invalid mob/respawn response: missing receipt");
588
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
+ }
589
875
  return {
590
876
  status: status === "topology_restore_failed" ? "topology_restore_failed" : "completed",
591
877
  receipt: {
592
- memberId: String(receipt.member_id ?? meerkatId),
593
- oldSessionId: receipt.old_session_id != null ? String(receipt.old_session_id) : undefined,
594
- newSessionId: receipt.new_session_id != null ? String(receipt.new_session_id) : undefined,
878
+ agentIdentity: receipt.identity != null ? String(receipt.identity) : agentIdentity,
879
+ memberRef,
595
880
  },
596
881
  failedPeerIds: rawFailed.map((peerId) => String(peerId)),
597
882
  };
598
883
  }
599
- async forceCancelMobMember(mobId, meerkatId) {
600
- 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
+ });
601
889
  }
602
- async mobMemberStatus(mobId, meerkatId) {
603
- const result = await this.request("mob/member_status", { mob_id: mobId, meerkat_id: meerkatId });
890
+ async mobTurnStart(mobId, agentIdentity, prompt, options) {
891
+ return await this.request("mob/turn_start", mobTurnStartPayload(mobId, agentIdentity, prompt, options));
892
+ }
893
+ async mobMemberStatus(mobId, agentIdentity) {
894
+ const result = await this.request("mob/member_status", {
895
+ mob_id: mobId,
896
+ agent_identity: agentIdentity,
897
+ });
604
898
  const rawConnectivity = result.peer_connectivity && typeof result.peer_connectivity === "object"
605
899
  ? result.peer_connectivity
606
900
  : undefined;
@@ -610,7 +904,9 @@ export class MeerkatClient {
610
904
  error: result.error != null ? String(result.error) : undefined,
611
905
  tokensUsed: Number(result.tokens_used ?? 0),
612
906
  isFinal: Boolean(result.is_final),
613
- 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,
614
910
  peerConnectivity: rawConnectivity
615
911
  ? {
616
912
  reachablePeerCount: Number(rawConnectivity.reachable_peer_count ?? 0),
@@ -626,8 +922,94 @@ export class MeerkatClient {
626
922
  : [],
627
923
  }
628
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),
629
990
  };
630
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
+ }
631
1013
  async waitMobKickoff(mobId, options) {
632
1014
  const params = { mob_id: mobId };
633
1015
  if (options?.memberIds !== undefined) {
@@ -640,17 +1022,70 @@ export class MeerkatClient {
640
1022
  const members = Array.isArray(result.members) ? result.members : [];
641
1023
  return members.map((entry) => {
642
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");
1032
+ const rawConnectivity = member.peer_connectivity && typeof member.peer_connectivity === "object"
1033
+ ? member.peer_connectivity
1034
+ : undefined;
1035
+ return {
1036
+ agentIdentity,
1037
+ status,
1038
+ outputPreview: member.output_preview != null ? String(member.output_preview) : undefined,
1039
+ error: member.error != null ? String(member.error) : 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");
643
1079
  const rawConnectivity = member.peer_connectivity && typeof member.peer_connectivity === "object"
644
1080
  ? member.peer_connectivity
645
1081
  : undefined;
646
1082
  return {
647
- meerkatId: String(member.meerkat_id ?? ""),
648
- status: String(member.status ?? "unknown"),
1083
+ agentIdentity,
1084
+ status,
649
1085
  outputPreview: member.output_preview != null ? String(member.output_preview) : undefined,
650
1086
  error: member.error != null ? String(member.error) : undefined,
651
- tokensUsed: Number(member.tokens_used ?? 0),
652
- isFinal: Boolean(member.is_final),
653
- currentSessionId: member.current_session_id != null ? String(member.current_session_id) : undefined,
1087
+ tokensUsed,
1088
+ isFinal,
654
1089
  peerConnectivity: rawConnectivity
655
1090
  ? {
656
1091
  reachablePeerCount: Number(rawConnectivity.reachable_peer_count ?? 0),
@@ -672,20 +1107,36 @@ export class MeerkatClient {
672
1107
  async wait_mob_kickoff(mobId, options) {
673
1108
  return this.waitMobKickoff(mobId, options);
674
1109
  }
1110
+ async wait_mob_ready(mobId, options) {
1111
+ return this.waitMobReady(mobId, options);
1112
+ }
675
1113
  async spawnMobHelper(mobId, prompt, options) {
676
1114
  const roleName = options?.roleName ?? options?.profileName;
677
1115
  const result = await this.request("mob/spawn_helper", {
678
1116
  mob_id: mobId,
679
1117
  prompt,
680
- meerkat_id: options?.meerkatId,
1118
+ agent_identity: options?.agentIdentity,
681
1119
  role_name: roleName,
682
1120
  runtime_mode: options?.runtimeMode,
683
1121
  backend: options?.backend,
684
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
+ }
685
1135
  return {
686
1136
  output: result.output != null ? String(result.output) : undefined,
687
1137
  tokensUsed: Number(result.tokens_used ?? 0),
688
- sessionId: result.session_id != null ? String(result.session_id) : undefined,
1138
+ agentIdentity: resultIdentity,
1139
+ memberRef,
689
1140
  };
690
1141
  }
691
1142
  async forkMobHelper(mobId, sourceMemberId, prompt, options) {
@@ -694,16 +1145,29 @@ export class MeerkatClient {
694
1145
  mob_id: mobId,
695
1146
  source_member_id: sourceMemberId,
696
1147
  prompt,
697
- meerkat_id: options?.meerkatId,
1148
+ agent_identity: options?.agentIdentity,
698
1149
  role_name: roleName,
699
1150
  fork_context: options?.forkContext,
700
1151
  runtime_mode: options?.runtimeMode,
701
1152
  backend: options?.backend,
702
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
+ }
703
1166
  return {
704
1167
  output: result.output != null ? String(result.output) : undefined,
705
1168
  tokensUsed: Number(result.tokens_used ?? 0),
706
- sessionId: result.session_id != null ? String(result.session_id) : undefined,
1169
+ agentIdentity: resultIdentity,
1170
+ memberRef,
707
1171
  };
708
1172
  }
709
1173
  async wireMobMembers(mobId, member, peer) {
@@ -721,10 +1185,10 @@ export class MeerkatClient {
721
1185
  async mobLifecycle(mobId, action) {
722
1186
  await this.request("mob/lifecycle", { mob_id: mobId, action });
723
1187
  }
724
- async appendMobSystemContext(mobId, meerkatId, text, options) {
1188
+ async appendMobSystemContext(mobId, agentIdentity, text, options) {
725
1189
  return this.request("mob/append_system_context", {
726
1190
  mob_id: mobId,
727
- meerkat_id: meerkatId,
1191
+ agent_identity: agentIdentity,
728
1192
  text,
729
1193
  source: options?.source,
730
1194
  idempotency_key: options?.idempotencyKey,
@@ -744,6 +1208,9 @@ export class MeerkatClient {
744
1208
  : [];
745
1209
  return { events };
746
1210
  }
1211
+ async mobIngressInteraction(params) {
1212
+ return this.request("mob/ingress_interaction", params);
1213
+ }
747
1214
  async createMobProfile(name, profile) {
748
1215
  const raw = await this.request("mob/profile/create", {
749
1216
  name,
@@ -798,8 +1265,8 @@ export class MeerkatClient {
798
1265
  async subscribeMobEvents(mobId) {
799
1266
  return this.openEventSubscription("mob/stream_open", { mob_id: mobId }, "mob/stream_close", MeerkatClient.parseAttributedMobEvent);
800
1267
  }
801
- async subscribeMobMemberEvents(mobId, meerkatId) {
802
- 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);
803
1270
  }
804
1271
  async openEventSubscription(openMethod, params, closeMethod, parse) {
805
1272
  const result = this.process?.stdin
@@ -838,14 +1305,59 @@ export class MeerkatClient {
838
1305
  });
839
1306
  }
840
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;
841
1317
  return {
842
- eventId: String(raw.event_id ?? raw.eventId ?? ""),
843
- sourceId: String(raw.source_id ?? raw.sourceId ?? ""),
844
- seq: Number(raw.seq ?? 0),
845
- timestampMs: Number(raw.timestamp_ms ?? raw.timestampMs ?? 0),
846
- 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 } : {}),
847
1324
  };
848
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
+ }
849
1361
  static parseAttributedMobEvent(raw) {
850
1362
  return {
851
1363
  source: String(raw.source ?? ""),
@@ -861,9 +1373,6 @@ export class MeerkatClient {
861
1373
  if (wireRefs) {
862
1374
  params.skill_refs = wireRefs;
863
1375
  }
864
- if (options?.skillReferences) {
865
- params.skill_references = options.skillReferences;
866
- }
867
1376
  if (options?.flowToolOverlay) {
868
1377
  params.flow_tool_overlay = {
869
1378
  allowed_tools: options.flowToolOverlay.allowedTools,
@@ -908,9 +1417,6 @@ export class MeerkatClient {
908
1417
  if (wireRefs) {
909
1418
  params.skill_refs = wireRefs;
910
1419
  }
911
- if (options?.skillReferences) {
912
- params.skill_references = options.skillReferences;
913
- }
914
1420
  if (options?.flowToolOverlay) {
915
1421
  params.flow_tool_overlay = {
916
1422
  allowed_tools: options.flowToolOverlay.allowedTools,
@@ -955,6 +1461,51 @@ export class MeerkatClient {
955
1461
  async _archive(sessionId) {
956
1462
  await this.request("session/archive", { session_id: sessionId });
957
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
+ }
958
1509
  /** @internal */
959
1510
  async _send(sessionId, command) {
960
1511
  return this.send(sessionId, command);
@@ -963,6 +1514,11 @@ export class MeerkatClient {
963
1514
  async _peers(sessionId) {
964
1515
  return this.peers(sessionId);
965
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
+ */
966
1522
  async send(sessionId, command) {
967
1523
  const result = await this.request("comms/send", { session_id: sessionId, ...command });
968
1524
  return MeerkatClient.parseCommsSendReceipt(result);
@@ -970,51 +1526,126 @@ export class MeerkatClient {
970
1526
  async peers(sessionId) {
971
1527
  return this.request("comms/peers", { session_id: sessionId });
972
1528
  }
973
- async runtimeState(sessionId) {
974
- const result = await this.request("runtime/state", { session_id: sessionId });
975
- if (typeof result.state !== "string" || result.state.length === 0) {
976
- 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");
977
1535
  }
978
1536
  return result;
979
1537
  }
980
- async runtimeAccept(sessionId, input) {
981
- const result = await this.request("runtime/accept", { session_id: sessionId, input });
982
- if (typeof result.outcome_type !== "string" || result.outcome_type.length === 0) {
983
- throw new MeerkatError("INVALID_RESPONSE", "Invalid runtime/accept response: missing outcome_type");
984
- }
985
- 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
+ });
986
1544
  }
987
- async runtimeRetire(sessionId) {
988
- const result = await this.request("runtime/retire", { session_id: sessionId });
989
- if (typeof result.inputs_abandoned !== "number") {
990
- throw new MeerkatError("INVALID_RESPONSE", "Invalid runtime/retire response: missing inputs_abandoned");
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
+ });
1558
+ }
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");
991
1563
  }
992
1564
  return result;
993
1565
  }
994
- async runtimeReset(sessionId) {
995
- const result = await this.request("runtime/reset", { session_id: sessionId });
996
- if (typeof result.inputs_abandoned !== "number") {
997
- 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");
998
1570
  }
999
1571
  return result;
1000
1572
  }
1001
- async inputState(sessionId, inputId) {
1002
- const result = await this.request("input/state", { session_id: sessionId, input_id: inputId });
1003
- if (result === null) {
1004
- return null;
1005
- }
1006
- if (typeof result !== "object" || result === null) {
1007
- 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");
1008
1577
  }
1009
1578
  return result;
1010
1579
  }
1011
- async inputList(sessionId) {
1012
- const result = (await this.request("input/list", {
1013
- session_id: sessionId,
1014
- }));
1015
- return Array.isArray(result.input_ids)
1016
- ? result.input_ids.map((inputId) => String(inputId))
1017
- : [];
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);
1018
1649
  }
1019
1650
  // -- Transport ----------------------------------------------------------
1020
1651
  handleLine(line) {
@@ -1155,6 +1786,33 @@ export class MeerkatClient {
1155
1786
  }
1156
1787
  return String(raw);
1157
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
+ }
1158
1816
  static parseSkillDiagnostics(raw) {
1159
1817
  if (!raw || typeof raw !== "object")
1160
1818
  return undefined;
@@ -1200,15 +1858,16 @@ export class MeerkatClient {
1200
1858
  }
1201
1859
  }
1202
1860
  static parseRunResult(data) {
1203
- const usageRaw = data.usage;
1861
+ const context = "Invalid run result";
1862
+ const usageRaw = MeerkatClient.requireRecord(data.usage, "usage", context);
1204
1863
  const usage = {
1205
- inputTokens: Number(usageRaw?.input_tokens ?? 0),
1206
- 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"),
1207
1866
  cacheCreationTokens: usageRaw?.cache_creation_tokens != null
1208
- ? Number(usageRaw.cache_creation_tokens)
1867
+ ? MeerkatClient.requireNumberField(usageRaw, "cache_creation_tokens", context, "usage.cache_creation_tokens")
1209
1868
  : undefined,
1210
1869
  cacheReadTokens: usageRaw?.cache_read_tokens != null
1211
- ? Number(usageRaw.cache_read_tokens)
1870
+ ? MeerkatClient.requireNumberField(usageRaw, "cache_read_tokens", context, "usage.cache_read_tokens")
1212
1871
  : undefined,
1213
1872
  };
1214
1873
  const rawWarnings = data.schema_warnings;
@@ -1217,14 +1876,26 @@ export class MeerkatClient {
1217
1876
  path: String(w.path ?? ""),
1218
1877
  message: String(w.message ?? ""),
1219
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;
1220
1887
  return {
1221
1888
  sessionId: String(data.session_id ?? ""),
1222
1889
  sessionRef: data.session_ref != null ? String(data.session_ref) : undefined,
1223
1890
  text: String(data.text ?? ""),
1224
- turns: Number(data.turns ?? 0),
1225
- toolCalls: Number(data.tool_calls ?? 0),
1891
+ turns: MeerkatClient.requireNumberField(data, "turns", context),
1892
+ toolCalls: MeerkatClient.requireNumberField(data, "tool_calls", context),
1226
1893
  usage,
1894
+ terminalCauseKind: typeof data.terminal_cause_kind === "string"
1895
+ ? data.terminal_cause_kind
1896
+ : undefined,
1227
1897
  structuredOutput: data.structured_output,
1898
+ extractionError,
1228
1899
  schemaWarnings,
1229
1900
  skillDiagnostics: MeerkatClient.parseSkillDiagnostics(data.skill_diagnostics),
1230
1901
  };
@@ -1487,6 +2158,7 @@ export class MeerkatClient {
1487
2158
  : [];
1488
2159
  return {
1489
2160
  role,
2161
+ createdAt: String(data.created_at ?? ""),
1490
2162
  content: contentValue != null ? MeerkatClient.parseContentInput(contentValue) : undefined,
1491
2163
  toolCalls: rawToolCalls.map((toolCall) => ({
1492
2164
  id: String(toolCall.id ?? ""),
@@ -1545,12 +2217,22 @@ export class MeerkatClient {
1545
2217
  }
1546
2218
  static parseSessionAssistantBlock(data) {
1547
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;
1548
2224
  return {
1549
2225
  blockType: String(data.block_type ?? ""),
1550
2226
  text: blockData.text != null ? String(blockData.text) : undefined,
1551
2227
  id: blockData.id != null ? String(blockData.id) : undefined,
1552
2228
  name: blockData.name != null ? String(blockData.name) : undefined,
1553
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,
1554
2236
  meta: blockData.meta,
1555
2237
  };
1556
2238
  }
@@ -1652,13 +2334,12 @@ export class MeerkatClient {
1652
2334
  params.budget_limits = options.budgetLimits;
1653
2335
  if (options.providerParams != null)
1654
2336
  params.provider_params = options.providerParams;
1655
- if (options.preloadSkills != null)
1656
- params.preload_skills = options.preloadSkills;
2337
+ if (options.preloadSkills != null) {
2338
+ params.preload_skills = skillKeysToWire(options.preloadSkills);
2339
+ }
1657
2340
  const wireRefs = skillRefsToWire(options.skillRefs);
1658
2341
  if (wireRefs)
1659
2342
  params.skill_refs = wireRefs;
1660
- if (options.skillReferences != null)
1661
- params.skill_references = options.skillReferences;
1662
2343
  if (options.labels != null)
1663
2344
  params.labels = options.labels;
1664
2345
  if (options.additionalInstructions != null) {
@@ -1830,9 +2511,9 @@ export class MeerkatClient {
1830
2511
  static buildArgs(legacy, options) {
1831
2512
  if (legacy)
1832
2513
  return ["rpc"];
2514
+ const args = ["--realtime-ws", "127.0.0.1:0"];
1833
2515
  if (!options)
1834
- return [];
1835
- const args = [];
2516
+ return args;
1836
2517
  if (options.isolated)
1837
2518
  args.push("--isolated");
1838
2519
  if (options.realmId)