@moxt-ai/mobius 0.0.347 → 0.0.348

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.
Files changed (28) hide show
  1. package/dist/cli.js +153 -98
  2. package/dist/cli.js.map +4 -4
  3. package/dist/engine/runtime/command/registry/prompt.js +4 -4
  4. package/dist/engine/runtime/command/registry/prompt.js.map +1 -1
  5. package/dist/engine/runtime/command/registry/session.d.ts.map +1 -1
  6. package/dist/engine/runtime/command/registry/session.js +8 -1
  7. package/dist/engine/runtime/command/registry/session.js.map +1 -1
  8. package/dist/engine/runtime/relay/session-event-publisher.d.ts.map +1 -1
  9. package/dist/engine/runtime/relay/session-event-publisher.js +22 -1
  10. package/dist/engine/runtime/relay/session-event-publisher.js.map +1 -1
  11. package/dist/engine/session/content/external-content-store.d.ts +4 -1
  12. package/dist/engine/session/content/external-content-store.d.ts.map +1 -1
  13. package/dist/engine/session/content/external-content-store.js +11 -3
  14. package/dist/engine/session/content/external-content-store.js.map +1 -1
  15. package/dist/engine/session/prompt/prompt-artifacts.d.ts +6 -1
  16. package/dist/engine/session/prompt/prompt-artifacts.d.ts.map +1 -1
  17. package/dist/engine/session/prompt/prompt-artifacts.js +12 -17
  18. package/dist/engine/session/prompt/prompt-artifacts.js.map +1 -1
  19. package/dist/engine/session/prompt/prompt-lifecycle.d.ts.map +1 -1
  20. package/dist/engine/session/prompt/prompt-lifecycle.js +2 -1
  21. package/dist/engine/session/prompt/prompt-lifecycle.js.map +1 -1
  22. package/dist/engine/session/workspace/workspace-directory.d.ts +1 -0
  23. package/dist/engine/session/workspace/workspace-directory.d.ts.map +1 -1
  24. package/dist/engine/session/workspace/workspace-directory.js +2 -1
  25. package/dist/engine/session/workspace/workspace-directory.js.map +1 -1
  26. package/dist/engine.js +144 -90
  27. package/dist/engine.js.map +4 -4
  28. package/package.json +6 -6
package/dist/engine.js CHANGED
@@ -21673,7 +21673,8 @@ var workspaceListCommandSchema = external_exports.object({
21673
21673
  bindingId: identifierStringSchema,
21674
21674
  directoryId: DIRECTORY_IDENTIFIER,
21675
21675
  kind: external_exports.literal(WORKSPACE_LIST_KIND),
21676
- requestId: identifierStringSchema
21676
+ requestId: identifierStringSchema,
21677
+ view: external_exports.literal("metadata").optional()
21677
21678
  }).readonly();
21678
21679
  var workspaceEntriesEventSchema = external_exports.object({
21679
21680
  bindingId: identifierStringSchema,
@@ -63398,6 +63399,7 @@ var ClaudeAcpAgent = class {
63398
63399
  session.titles.onPrompt(params.prompt);
63399
63400
  const turn = {
63400
63401
  promptUuid,
63402
+ inputQueuedAt: performance.now(),
63401
63403
  isLocalOnlyCommand,
63402
63404
  settled: false,
63403
63405
  resolve: () => {
@@ -63883,6 +63885,15 @@ var ClaudeAcpAgent = class {
63883
63885
  case "started": {
63884
63886
  const queued = findUnsettledTurn(frame.command_uuid);
63885
63887
  if (queued) {
63888
+ if (!queued.commandStarted) {
63889
+ this.logger.log(JSON.stringify({
63890
+ event: "claude-sdk-command-start-timing",
63891
+ sessionId: params.sessionId,
63892
+ promptUuid: queued.promptUuid,
63893
+ observedAt: Date.now(),
63894
+ queuedToCommandStartMs: performance.now() - queued.inputQueuedAt
63895
+ }));
63896
+ }
63886
63897
  queued.commandStarted = true;
63887
63898
  }
63888
63899
  const state = session.orphanCommands?.get(frame.command_uuid);
@@ -64569,6 +64580,16 @@ ${message.api_refusal_explanation}` : "";
64569
64580
  }
64570
64581
  const queued = findUnsettledTurn(message.uuid);
64571
64582
  if (queued) {
64583
+ if (!queued.inputEchoObserved) {
64584
+ queued.inputEchoObserved = true;
64585
+ this.logger.log(JSON.stringify({
64586
+ event: "claude-sdk-input-echo-timing",
64587
+ sessionId: params.sessionId,
64588
+ promptUuid: queued.promptUuid,
64589
+ observedAt: Date.now(),
64590
+ queuedToEchoMs: performance.now() - queued.inputQueuedAt
64591
+ }));
64592
+ }
64572
64593
  if (session.activeTurn !== queued) {
64573
64594
  if (session.activeTurn) {
64574
64595
  if (session.cancelled) {
@@ -86975,6 +86996,63 @@ var NativeSessionService = class {
86975
86996
  };
86976
86997
  };
86977
86998
 
86999
+ // src/engine/session/prompt/prompt-artifacts.ts
87000
+ import { createHash as createHash6 } from "node:crypto";
87001
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
87002
+ var PromptArtifactError = class extends OperationError {
87003
+ constructor(code, message, options = {}) {
87004
+ super(code, message, options);
87005
+ this.name = "PromptArtifactError";
87006
+ }
87007
+ };
87008
+ var ResolvedPromptArtifacts = class {
87009
+ images;
87010
+ resources;
87011
+ constructor(images, resources) {
87012
+ this.images = images;
87013
+ this.resources = resources;
87014
+ }
87015
+ };
87016
+ function isTextPromptResourceMediaType(mediaType) {
87017
+ return mediaType.startsWith("text/") || mediaType === "application/json" || mediaType.endsWith("+json") || mediaType === "application/xml" || mediaType.endsWith("+xml");
87018
+ }
87019
+ async function resolvePromptArtifacts(read, attachments, signal) {
87020
+ const images = [];
87021
+ const resources = [];
87022
+ for (const attachment of attachments) {
87023
+ signal.throwIfAborted();
87024
+ const artifact = await read(attachment, signal);
87025
+ const { bytes } = artifact;
87026
+ if (bytes.byteLength !== attachment.byteLength || createHash6("sha256").update(bytes).digest("hex") !== attachment.contentKey) {
87027
+ throw new PromptArtifactError(
87028
+ "artifact_integrity_failed",
87029
+ "The prompt attachment failed integrity validation"
87030
+ );
87031
+ }
87032
+ if (attachment.kind === "image") {
87033
+ if (bytes.byteLength > PROMPT_IMAGE_MAX_BYTES || images.length >= 8) {
87034
+ continue;
87035
+ }
87036
+ images.push({
87037
+ data: Buffer.from(bytes).toString("base64"),
87038
+ mimeType: attachment.mimeType,
87039
+ name: attachment.name
87040
+ });
87041
+ continue;
87042
+ }
87043
+ if (bytes.byteLength > MAX_PROMPT_ATTACHMENT_BYTES || resources.length >= 16) {
87044
+ continue;
87045
+ }
87046
+ resources.push({
87047
+ mimeType: attachment.mimeType,
87048
+ name: attachment.name,
87049
+ type: "link",
87050
+ uri: pathToFileURL2(artifact.path).href
87051
+ });
87052
+ }
87053
+ return new ResolvedPromptArtifacts(images, resources);
87054
+ }
87055
+
86978
87056
  // src/engine/session/prompt/prompt-lifecycle.ts
86979
87057
  function promptContent(command) {
86980
87058
  const content = [];
@@ -86991,7 +87069,7 @@ function promptContent(command) {
86991
87069
  uri: attachment.uri
86992
87070
  } : {
86993
87071
  byteLength: attachment.byteLength,
86994
- encoding: "text",
87072
+ encoding: isTextPromptResourceMediaType(attachment.mimeType) ? "text" : "binary",
86995
87073
  mimeType: attachment.mimeType,
86996
87074
  resourceKey: attachment.contentKey,
86997
87075
  type: "external_resource",
@@ -87156,7 +87234,7 @@ async function settleAbandonedTurn(projection, operations, cancellation) {
87156
87234
  }
87157
87235
 
87158
87236
  // src/engine/session/turn/execution-receipts.ts
87159
- import { createHash as createHash6, randomUUID as randomUUID15 } from "node:crypto";
87237
+ import { createHash as createHash7, randomUUID as randomUUID15 } from "node:crypto";
87160
87238
  var MAX_TERMINAL_RECEIPTS = 1024;
87161
87239
  var ExecutionReceipts = class {
87162
87240
  #executions = /* @__PURE__ */ new Map();
@@ -87268,7 +87346,7 @@ var ExecutionReceipts = class {
87268
87346
  type: "lifecycle-metadata",
87269
87347
  payload: { kind: "prompt.completed", commandId, stopReason: "cancelled" }
87270
87348
  };
87271
- const commandHash = createHash6("sha256").update(
87349
+ const commandHash = createHash7("sha256").update(
87272
87350
  encodeSessionCancelBeforeStartCommand({
87273
87351
  commandId,
87274
87352
  kind: "session.cancel-before-start",
@@ -87332,7 +87410,7 @@ var ExecutionReceipts = class {
87332
87410
  return recovered;
87333
87411
  };
87334
87412
  claimExecution = (commandId, sessionId, commandJson) => {
87335
- const commandHash = createHash6("sha256").update(commandJson).digest("hex");
87413
+ const commandHash = createHash7("sha256").update(commandJson).digest("hex");
87336
87414
  let existing = this.#executions.get(commandId);
87337
87415
  const previous = this.outcome(commandId);
87338
87416
  if (existing?.sessionId === sessionId && !existing.started && previous?.type === "lifecycle-metadata" && previous.payload.kind === "prompt.failed-before-start" && previous.payload.code === "runtime_restarted") {
@@ -87881,7 +87959,8 @@ var WorkspaceDirectoryService = class {
87881
87959
  };
87882
87960
  describe = async (bindingId, id, signal) => {
87883
87961
  const path7 = await this.resolve(bindingId, id, signal);
87884
- return { path: path7, label: this.label(path7) };
87962
+ const parent = dirname10(path7);
87963
+ return { path: path7, label: this.label(path7), parentDirectoryId: parent === path7 ? "" : encodePath(parent) };
87885
87964
  };
87886
87965
  list = async (bindingId, id, signal) => {
87887
87966
  const target = await this.resolve(bindingId, id, signal);
@@ -88052,8 +88131,8 @@ function buildSessionLayer(options) {
88052
88131
  }
88053
88132
 
88054
88133
  // src/engine/session/content/external-content-store.ts
88055
- import { createHash as createHash7 } from "node:crypto";
88056
- import { join as join26 } from "node:path";
88134
+ import { createHash as createHash8 } from "node:crypto";
88135
+ import { extname as extname3, join as join26 } from "node:path";
88057
88136
 
88058
88137
  // src/engine/storage/files/scratch-directory.ts
88059
88138
  import { randomUUID as randomUUID17 } from "node:crypto";
@@ -88140,6 +88219,10 @@ var metadataSchema = external_exports.strictObject({
88140
88219
  mediaType: external_exports.string().min(1).max(2048).regex(/^[\u0020-\u007e]+$/u),
88141
88220
  name: external_exports.string().min(1).max(500)
88142
88221
  });
88222
+ function contentFileName(key, name2) {
88223
+ const extension = extname3(name2).toLowerCase();
88224
+ return /^\.[a-z0-9]{1,16}$/u.test(extension) ? `${key}${extension}` : key;
88225
+ }
88143
88226
  var LocalExternalContentStore = class {
88144
88227
  #scratch;
88145
88228
  #entries = /* @__PURE__ */ new Map();
@@ -88182,7 +88265,7 @@ var LocalExternalContentStore = class {
88182
88265
  const active = AbortSignal.any([signal, this.#lifetime.signal]);
88183
88266
  active.throwIfAborted();
88184
88267
  const metadata = metadataSchema.parse({ kind, mediaType, name: name2 });
88185
- const key = createHash7("sha256").update(bytes).digest("hex");
88268
+ const key = createHash8("sha256").update(bytes).digest("hex");
88186
88269
  const request = `mobius-content:${kind}:${key}`;
88187
88270
  const existing = this.#entries.get(request);
88188
88271
  if (existing !== void 0 && existing.expiresAt > Date.now()) {
@@ -88195,7 +88278,7 @@ var LocalExternalContentStore = class {
88195
88278
  }
88196
88279
  await this.#expire();
88197
88280
  active.throwIfAborted();
88198
- const path7 = await this.#scratch.write(kind, key, bytes, active);
88281
+ const path7 = await this.#scratch.write(kind, contentFileName(key, metadata.name), bytes, active);
88199
88282
  active.throwIfAborted();
88200
88283
  const retained = this.#entries.get(request);
88201
88284
  this.#entries.set(request, {
@@ -88231,7 +88314,11 @@ var LocalExternalContentStore = class {
88231
88314
  read = (sessionId, request, signal) => this.#track(async () => {
88232
88315
  const active = AbortSignal.any([signal, this.#lifetime.signal]);
88233
88316
  const resolved = await this.#resolve(sessionId, request, active);
88234
- return resolved === null ? null : this.#scratch.read(resolved.path, active);
88317
+ if (resolved === null) {
88318
+ return null;
88319
+ }
88320
+ const bytes = await this.#scratch.read(resolved.path, active);
88321
+ return bytes === null ? null : { ...resolved, bytes };
88235
88322
  });
88236
88323
  [Symbol.asyncDispose] = async () => {
88237
88324
  clearInterval(this.#expirationTimer);
@@ -89050,7 +89137,7 @@ var SessionError = class extends OperationError {
89050
89137
  };
89051
89138
 
89052
89139
  // src/engine/session/settings/discover-agent-settings.ts
89053
- import { createHash as createHash8 } from "node:crypto";
89140
+ import { createHash as createHash9 } from "node:crypto";
89054
89141
 
89055
89142
  // src/engine/agent/settings/agent-settings-base.ts
89056
89143
  var AgentSettingsLaunchSelectionResult = class {
@@ -89368,7 +89455,7 @@ function settingsRevision(agent, reportedAgentVersion, settings, modelEffort) {
89368
89455
  unsupportedModelIds: modelEffort.unsupportedModelIds,
89369
89456
  valuesByModel: {}
89370
89457
  };
89371
- return createHash8("sha256").update(
89458
+ return createHash9("sha256").update(
89372
89459
  JSON.stringify({
89373
89460
  adapterVersion: agent.adapterVersion,
89374
89461
  agentId: agent.id,
@@ -89524,7 +89611,7 @@ function createAgentSettingsCommands(context) {
89524
89611
  }
89525
89612
 
89526
89613
  // src/engine/runtime/command/registry/content-write/index.ts
89527
- import { createHash as createHash9 } from "node:crypto";
89614
+ import { createHash as createHash10 } from "node:crypto";
89528
89615
  function createContentWriteCommands(context) {
89529
89616
  const pending = /* @__PURE__ */ new Map();
89530
89617
  const operations = /* @__PURE__ */ new Set();
@@ -89554,7 +89641,7 @@ function createContentWriteCommands(context) {
89554
89641
  pending.set(message.requestId, {
89555
89642
  bytes: new Uint8Array(message.byteLength),
89556
89643
  command: message,
89557
- hash: createHash9("sha256"),
89644
+ hash: createHash10("sha256"),
89558
89645
  receivedBytes: 0,
89559
89646
  sequence: 0
89560
89647
  });
@@ -89648,7 +89735,7 @@ function createContentWriteCommands(context) {
89648
89735
  import { WebSocket as WebSocket5 } from "undici";
89649
89736
 
89650
89737
  // src/engine/session/content/session-files.ts
89651
- import { basename as basename8, extname as extname3, isAbsolute as isAbsolute16, join as join30, normalize as normalize3, resolve as resolve16 } from "node:path";
89738
+ import { basename as basename8, extname as extname4, isAbsolute as isAbsolute16, join as join30, normalize as normalize3, resolve as resolve16 } from "node:path";
89652
89739
  import { fileURLToPath as fileURLToPath2 } from "node:url";
89653
89740
  function requestedPath(value) {
89654
89741
  if (value.includes("\0")) {
@@ -89704,7 +89791,7 @@ async function streamLocalFile(command, send2, sendChunk, signal, workingDirecto
89704
89791
  send2({
89705
89792
  byteLength: before.size,
89706
89793
  kind: FILE_BEGIN_KIND,
89707
- mediaType: external?.mediaType ?? raster?.mimeType ?? (extname3(target).toLowerCase() === ".svg" ? "image/svg+xml" : "application/octet-stream"),
89794
+ mediaType: external?.mediaType ?? raster?.mimeType ?? (extname4(target).toLowerCase() === ".svg" ? "image/svg+xml" : "application/octet-stream"),
89708
89795
  modifiedAt: before.modifiedAt.toISOString(),
89709
89796
  name: external?.name ?? (basename8(target) || "file"),
89710
89797
  requestId: command.requestId,
@@ -90418,67 +90505,6 @@ function warnIgnoredSetting(event, commandId, sessionId, agentId, selection, cat
90418
90505
  `);
90419
90506
  }
90420
90507
 
90421
- // src/engine/session/prompt/prompt-artifacts.ts
90422
- import { createHash as createHash10 } from "node:crypto";
90423
- var MAX_AGENT_RESOURCE_BYTE_LENGTH = 1e6;
90424
- var PromptArtifactError = class extends OperationError {
90425
- constructor(code, message, options = {}) {
90426
- super(code, message, options);
90427
- this.name = "PromptArtifactError";
90428
- }
90429
- };
90430
- var ResolvedPromptArtifacts = class {
90431
- images;
90432
- resources;
90433
- constructor(images, resources) {
90434
- this.images = images;
90435
- this.resources = resources;
90436
- }
90437
- };
90438
- function isTextMediaType(mediaType) {
90439
- return mediaType.startsWith("text/") || mediaType === "application/json" || mediaType.endsWith("+json") || mediaType === "application/xml" || mediaType.endsWith("+xml");
90440
- }
90441
- async function resolvePromptArtifacts(read, attachments, signal) {
90442
- const images = [];
90443
- const resources = [];
90444
- for (const attachment of attachments) {
90445
- signal.throwIfAborted();
90446
- const bytes = await read(attachment, signal);
90447
- if (bytes.byteLength !== attachment.byteLength || createHash10("sha256").update(bytes).digest("hex") !== attachment.contentKey) {
90448
- throw new PromptArtifactError(
90449
- "artifact_integrity_failed",
90450
- "The prompt attachment failed integrity validation"
90451
- );
90452
- }
90453
- if (attachment.kind === "image") {
90454
- if (bytes.byteLength > PROMPT_IMAGE_MAX_BYTES || images.length >= 8) {
90455
- continue;
90456
- }
90457
- images.push({
90458
- data: Buffer.from(bytes).toString("base64"),
90459
- mimeType: attachment.mimeType,
90460
- name: attachment.name
90461
- });
90462
- continue;
90463
- }
90464
- if (!isTextMediaType(attachment.mimeType)) {
90465
- continue;
90466
- }
90467
- if (bytes.byteLength > MAX_AGENT_RESOURCE_BYTE_LENGTH || resources.length >= 16) {
90468
- continue;
90469
- }
90470
- let text2;
90471
- try {
90472
- text2 = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
90473
- } catch {
90474
- signal.throwIfAborted();
90475
- continue;
90476
- }
90477
- resources.push({ mimeType: attachment.mimeType, text: text2, type: "embedded", uri: attachment.uri });
90478
- }
90479
- return new ResolvedPromptArtifacts(images, resources);
90480
- }
90481
-
90482
90508
  // src/engine/session/prompt/execute-prompt.ts
90483
90509
  async function executePrompt({
90484
90510
  operations,
@@ -90690,16 +90716,16 @@ var sessionPromptCommand = {
90690
90716
  eventProjection: context.eventProjection,
90691
90717
  command: message,
90692
90718
  resolveAgent: context.resolveAgent,
90693
- readArtifact: async (artifact, signal) => {
90694
- const bytes = await context.externalContent.read(
90719
+ readArtifact: async (attachment, signal) => {
90720
+ const artifact = await context.externalContent.read(
90695
90721
  message.sessionId,
90696
- `mobius-content:${artifact.kind}:${artifact.contentKey}`,
90722
+ `mobius-content:${attachment.kind}:${attachment.contentKey}`,
90697
90723
  signal
90698
90724
  );
90699
- if (bytes === null) {
90725
+ if (artifact === null) {
90700
90726
  throw new Error("The prompt attachment is unavailable");
90701
90727
  }
90702
- return bytes;
90728
+ return { bytes: artifact.bytes, path: artifact.path };
90703
90729
  },
90704
90730
  reportFailure: context.diagnostics.daemonDiagnostic,
90705
90731
  directories: context.directories,
@@ -90727,7 +90753,7 @@ var sessionPromptCommand = {
90727
90753
  // package.json
90728
90754
  var package_default = {
90729
90755
  name: "@moxt-ai/mobius",
90730
- version: "0.0.347",
90756
+ version: "0.0.348",
90731
90757
  description: "Mobius CLI for pairing and running local AI agent services.",
90732
90758
  type: "module",
90733
90759
  bin: {
@@ -90769,13 +90795,13 @@ var package_default = {
90769
90795
  prepack: "pnpm build",
90770
90796
  start: "node dist/cli.js",
90771
90797
  typecheck: "tsc --project tsconfig.json && tsc --project tests/tsconfig.json",
90772
- "pretest:controlled": "tsc --project src/engine/acp/harness/claude/upstream/tsconfig.json && tsc --project src/engine/acp/harness/pi/upstream/tsconfig.json",
90773
- "test:controlled": "vitest run tests/sessions tests/runtime --maxWorkers=4",
90798
+ "pretest:controlled": "pnpm build",
90799
+ "test:controlled": "vitest run tests/sessions tests/runtime",
90774
90800
  "pretest:manual": "pnpm pretest:controlled",
90775
90801
  "test:manual": "vitest run tests/manual",
90776
- "pretest:agents": "tsc --project src/engine/acp/harness/claude/upstream/tsconfig.json && tsc --project src/engine/acp/harness/pi/upstream/tsconfig.json",
90802
+ "pretest:agents": "pnpm build",
90777
90803
  "test:agents": "vitest run tests/agents --maxWorkers=2",
90778
- "pretest:npm-compatibility": "tsc --project src/engine/acp/harness/claude/upstream/tsconfig.json && tsc --project src/engine/acp/harness/pi/upstream/tsconfig.json",
90804
+ "pretest:npm-compatibility": "pnpm build",
90779
90805
  "test:npm-compatibility": "vitest run tests/runtime/daemon-upgrade.integration.test.ts",
90780
90806
  "pretest:pressure": "tsc --project src/engine/acp/harness/claude/upstream/tsconfig.json && tsc --project src/engine/acp/harness/pi/upstream/tsconfig.json",
90781
90807
  "test:pressure": "node tests/pressure/concurrent-sessions.mjs"
@@ -90899,7 +90925,12 @@ async function prepareSession(command, recovery, closed, config2, resolveAgent,
90899
90925
  }
90900
90926
  async function executeWorkspaceList(socket, command, directories, signal) {
90901
90927
  try {
90902
- const listing = await directories.list(command.bindingId, command.directoryId, signal);
90928
+ const listing = command.view === "metadata" ? await directories.describe(command.bindingId, command.directoryId, signal).then((directory) => ({
90929
+ directoryId: command.directoryId,
90930
+ directoryLabel: directory.label,
90931
+ entries: [],
90932
+ parentDirectoryId: directory.parentDirectoryId
90933
+ })) : await directories.list(command.bindingId, command.directoryId, signal);
90903
90934
  send(socket, {
90904
90935
  bindingId: command.bindingId,
90905
90936
  directoryId: listing.directoryId,
@@ -91832,13 +91863,23 @@ var RuntimeSessionEventPublisher = class {
91832
91863
  return;
91833
91864
  }
91834
91865
  if (batch === void 0 || newBatch) {
91835
- batch = { events: [], bytes: EMPTY_ENVELOPE_BYTES };
91866
+ batch = { events: [], firstAnswers: [], bytes: EMPTY_ENVELOPE_BYTES };
91836
91867
  this.#pending.push(batch);
91837
91868
  }
91838
91869
  batch.events.push(event);
91839
91870
  batch.bytes += bytes;
91840
91871
  this.#pendingBytes += additionalBytes;
91872
+ const awaiting = this.#awaitingFirstAnswer.get(event.sessionId);
91841
91873
  const firstAnswer = this.#consumeFirstAnswer(event);
91874
+ if (firstAnswer && awaiting !== void 0) {
91875
+ batch.firstAnswers.push({
91876
+ sessionId: event.sessionId,
91877
+ commandId: awaiting.commandId,
91878
+ eventId: event.eventId,
91879
+ observedAt: Date.now(),
91880
+ publishedAt: performance.now()
91881
+ });
91882
+ }
91842
91883
  const connection = this.#connection;
91843
91884
  if (connection.kind !== "connected" || connection.socket.readyState !== WebSocket9.OPEN) {
91844
91885
  return;
@@ -91893,6 +91934,19 @@ var RuntimeSessionEventPublisher = class {
91893
91934
  this.#pendingBytes -= batch.bytes;
91894
91935
  try {
91895
91936
  socket.send(encoded);
91937
+ for (const answer of batch.firstAnswers) {
91938
+ console.info(
91939
+ JSON.stringify({
91940
+ event: "runtime-first-answer-handoff-timing",
91941
+ sessionId: answer.sessionId,
91942
+ commandId: answer.commandId,
91943
+ eventId: answer.eventId,
91944
+ observedAt: answer.observedAt,
91945
+ handedOffAt: Date.now(),
91946
+ publishToHandoffMs: performance.now() - answer.publishedAt
91947
+ })
91948
+ );
91949
+ }
91896
91950
  } catch {
91897
91951
  console.warn("Live Session output could not be sent", { code: "live_event_send_failed" });
91898
91952
  break;