@moxt-ai/mobius 0.0.323 → 0.0.324

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 (88) hide show
  1. package/dist/cli.js +431 -280
  2. package/dist/cli.js.map +4 -4
  3. package/dist/runtime/command/dispatcher.d.ts +1 -0
  4. package/dist/runtime/command/dispatcher.d.ts.map +1 -1
  5. package/dist/runtime/command/dispatcher.js.map +1 -1
  6. package/dist/runtime/command/registry/content-write/index.d.ts +10 -0
  7. package/dist/runtime/command/registry/content-write/index.d.ts.map +1 -0
  8. package/dist/runtime/command/registry/content-write/index.js +125 -0
  9. package/dist/runtime/command/registry/content-write/index.js.map +1 -0
  10. package/dist/runtime/command/registry/file-read/index.d.ts.map +1 -1
  11. package/dist/runtime/command/registry/file-read/index.js +2 -2
  12. package/dist/runtime/command/registry/file-read/index.js.map +1 -1
  13. package/dist/runtime/command/registry/index.d.ts +2 -1
  14. package/dist/runtime/command/registry/index.d.ts.map +1 -1
  15. package/dist/runtime/command/registry/index.js +9 -1
  16. package/dist/runtime/command/registry/index.js.map +1 -1
  17. package/dist/runtime/command/registry/prompt.d.ts.map +1 -1
  18. package/dist/runtime/command/registry/prompt.js +7 -2
  19. package/dist/runtime/command/registry/prompt.js.map +1 -1
  20. package/dist/runtime/command/registry/session.d.ts.map +1 -1
  21. package/dist/runtime/command/registry/session.js +10 -5
  22. package/dist/runtime/command/registry/session.js.map +1 -1
  23. package/dist/runtime/command/transport.d.ts +1 -0
  24. package/dist/runtime/command/transport.d.ts.map +1 -1
  25. package/dist/runtime/command/transport.js +7 -1
  26. package/dist/runtime/command/transport.js.map +1 -1
  27. package/dist/runtime/relay/connect.d.ts.map +1 -1
  28. package/dist/runtime/relay/connect.js +1 -0
  29. package/dist/runtime/relay/connect.js.map +1 -1
  30. package/dist/runtime/relay/run-relay-connection.d.ts.map +1 -1
  31. package/dist/runtime/relay/run-relay-connection.js +6 -1
  32. package/dist/runtime/relay/run-relay-connection.js.map +1 -1
  33. package/dist/runtime.js +418 -266
  34. package/dist/runtime.js.map +4 -4
  35. package/dist/session/api/session-api.d.ts +11 -4
  36. package/dist/session/api/session-api.d.ts.map +1 -1
  37. package/dist/session/api/session-api.js +3 -1
  38. package/dist/session/api/session-api.js.map +1 -1
  39. package/dist/session/content/external-content-store.d.ts +2 -0
  40. package/dist/session/content/external-content-store.d.ts.map +1 -1
  41. package/dist/session/content/external-content-store.js +5 -0
  42. package/dist/session/content/external-content-store.js.map +1 -1
  43. package/dist/session/content/session-files.d.ts +1 -1
  44. package/dist/session/content/session-files.d.ts.map +1 -1
  45. package/dist/session/content/session-files.js +4 -11
  46. package/dist/session/content/session-files.js.map +1 -1
  47. package/dist/session/live/agent-session.d.ts +2 -2
  48. package/dist/session/live/agent-session.d.ts.map +1 -1
  49. package/dist/session/live/agent-session.js +45 -13
  50. package/dist/session/live/agent-session.js.map +1 -1
  51. package/dist/session/manager/session-manager.d.ts +3 -3
  52. package/dist/session/manager/session-manager.d.ts.map +1 -1
  53. package/dist/session/manager/session-manager.js +2 -2
  54. package/dist/session/manager/session-manager.js.map +1 -1
  55. package/dist/session/prompt/apply-session-configuration.d.ts.map +1 -1
  56. package/dist/session/prompt/apply-session-configuration.js +2 -2
  57. package/dist/session/prompt/apply-session-configuration.js.map +1 -1
  58. package/dist/session/prompt/execute-prompt.d.ts +3 -3
  59. package/dist/session/prompt/execute-prompt.d.ts.map +1 -1
  60. package/dist/session/prompt/execute-prompt.js +2 -2
  61. package/dist/session/prompt/execute-prompt.js.map +1 -1
  62. package/dist/session/prompt/prompt-artifacts.d.ts +7 -22
  63. package/dist/session/prompt/prompt-artifacts.d.ts.map +1 -1
  64. package/dist/session/prompt/prompt-artifacts.js +22 -76
  65. package/dist/session/prompt/prompt-artifacts.js.map +1 -1
  66. package/dist/session/prompt/prompt-lifecycle.d.ts.map +1 -1
  67. package/dist/session/prompt/prompt-lifecycle.js +15 -12
  68. package/dist/session/prompt/prompt-lifecycle.js.map +1 -1
  69. package/dist/session/turn/session-turns.d.ts +5 -9
  70. package/dist/session/turn/session-turns.d.ts.map +1 -1
  71. package/dist/session/turn/session-turns.js +1 -2
  72. package/dist/session/turn/session-turns.js.map +1 -1
  73. package/dist/storage/files/scratch-directory.d.ts +2 -0
  74. package/dist/storage/files/scratch-directory.d.ts.map +1 -1
  75. package/dist/storage/files/scratch-directory.js +7 -1
  76. package/dist/storage/files/scratch-directory.js.map +1 -1
  77. package/dist/util/filesystem/private-path.js +2 -3
  78. package/dist/util/filesystem/private-path.js.map +2 -2
  79. package/dist/util/platform/windows.d.ts.map +1 -1
  80. package/dist/util/platform/windows.js +3 -5
  81. package/dist/util/platform/windows.js.map +1 -1
  82. package/dist/util/process/executable.js +2 -3
  83. package/dist/util/process/executable.js.map +2 -2
  84. package/package.json +2 -2
  85. package/dist/runtime/relay/artifact-download.d.ts +0 -5
  86. package/dist/runtime/relay/artifact-download.d.ts.map +0 -1
  87. package/dist/runtime/relay/artifact-download.js +0 -105
  88. package/dist/runtime/relay/artifact-download.js.map +0 -1
package/dist/runtime.js CHANGED
@@ -500,11 +500,10 @@ function terminateProcess2(child, signal) {
500
500
  var windowsPermissions = `
501
501
  $ErrorActionPreference = 'Stop'
502
502
  $ProgressPreference = 'SilentlyContinue'
503
- Import-Module (Join-Path $PSHOME 'Modules/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1')
504
503
  $item = Get-Item -LiteralPath $env:MOBIUS_PERMISSION_PATH -Force
505
504
  if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'Unsafe path' }
506
505
  $sid = [Security.Principal.WindowsIdentity]::GetCurrent().User
507
- $acl = Get-Acl -LiteralPath $item.FullName
506
+ $acl = $item.GetAccessControl()
508
507
  $owner = $acl.GetOwner([Security.Principal.SecurityIdentifier])
509
508
  if ($owner -ne $sid -and -not ($owner.Value -eq 'S-1-5-32-544' -and ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))) { throw 'Path is not user-owned' }
510
509
  if ($env:MOBIUS_PERMISSION_PRIVATE -eq '1') {
@@ -518,7 +517,7 @@ if ($env:MOBIUS_PERMISSION_PRIVATE -eq '1') {
518
517
  $private.SetOwner($sid)
519
518
  $private.SetAccessRuleProtection($true, $false)
520
519
  $private.AddAccessRule($rule)
521
- Set-Acl -LiteralPath $item.FullName -AclObject $private
520
+ $item.SetAccessControl($private)
522
521
  }
523
522
  `;
524
523
  var windowsPermissionsTimeoutMs = 3e4;
@@ -21387,6 +21386,8 @@ var sessionConfigureCommandSchema = external_exports.object({
21387
21386
  ...sessionIdentityFields,
21388
21387
  booleanValue: external_exports.boolean(),
21389
21388
  configId: external_exports.string().min(1).max(128),
21389
+ // Omitted by legacy clients: explicit edits retain last-write-wins behavior.
21390
+ expectedConfigurationRevision: identifierStringSchema.optional(),
21390
21391
  kind: external_exports.literal(SESSION_CONFIGURE_KIND),
21391
21392
  requestId: identifierStringSchema,
21392
21393
  value: external_exports.string().max(500),
@@ -21394,6 +21395,8 @@ var sessionConfigureCommandSchema = external_exports.object({
21394
21395
  }).readonly();
21395
21396
  var sessionConfigUpdatedEventSchema = external_exports.object({
21396
21397
  configOptions: sessionConfigOptionsSchema,
21398
+ // An opaque Runtime generation token; absent on older Runtimes.
21399
+ configurationRevision: identifierStringSchema.optional(),
21397
21400
  kind: external_exports.literal(SESSION_CONFIG_UPDATED_KIND),
21398
21401
  requestId: identifierStringSchema,
21399
21402
  sessionId: identifierStringSchema
@@ -21412,6 +21415,8 @@ var sessionConfigReadCommandSchema = external_exports.object({
21412
21415
  }).readonly();
21413
21416
  var sessionConfigReadResultSchema = external_exports.object({
21414
21417
  configOptions: sessionConfigOptionsSchema,
21418
+ // An opaque Runtime generation token; absent on older Runtimes.
21419
+ configurationRevision: identifierStringSchema.optional(),
21415
21420
  kind: external_exports.literal(SESSION_CONFIG_READ_RESULT_KIND),
21416
21421
  requestId: identifierStringSchema,
21417
21422
  sessionId: identifierStringSchema
@@ -21445,9 +21450,17 @@ var encodeSessionRenamedEvent = (message) => encodeZodMessage(message, sessionRe
21445
21450
  // ../mobius-sdk/src/protocol/session/files/index.ts
21446
21451
  var FILE_READ_KIND = "file.read";
21447
21452
  var FILE_BEGIN_KIND = "file.begin";
21448
- var FILE_CHUNK_KIND = "file.chunk";
21449
21453
  var FILE_END_KIND = "file.end";
21450
21454
  var FILE_FAILED_KIND = "file.failed";
21455
+ var CONTENT_WRITE_KIND = "content.write";
21456
+ var CONTENT_COMMIT_KIND = "content.commit";
21457
+ var CONTENT_READY_KIND = "content.ready";
21458
+ var CONTENT_AVAILABLE_KIND = "content.available";
21459
+ var CONTENT_CHUNK_MAX_BYTES = 192 * 1024;
21460
+ var CONTENT_WRITE_MAX_BYTES = 32 * 1024 * 1024;
21461
+ var PROMPT_IMAGE_MAX_BYTES = 5e6;
21462
+ var CONTENT_CHUNK_MAGIC = new Uint8Array([77, 66, 67, 49]);
21463
+ var CONTENT_CHUNK_HEADER_BYTES = CONTENT_CHUNK_MAGIC.byteLength + 1 + 4;
21451
21464
  var fileReadCommandSchema = external_exports.object({
21452
21465
  kind: external_exports.literal(FILE_READ_KIND),
21453
21466
  maxBytes: external_exports.number().int().positive().max(128 * 1024 * 1024),
@@ -21464,13 +21477,86 @@ var fileBeginEventSchema = external_exports.object({
21464
21477
  requestId: identifierStringSchema,
21465
21478
  sessionId: identifierStringSchema
21466
21479
  }).readonly();
21467
- var fileChunkEventSchema = external_exports.object({
21468
- data: external_exports.string().min(1).max(4e5),
21469
- kind: external_exports.literal(FILE_CHUNK_KIND),
21480
+ var contentWriteCommandSchema = external_exports.object({
21481
+ byteLength: external_exports.number().int().positive().max(CONTENT_WRITE_MAX_BYTES),
21482
+ contentKey: external_exports.string().regex(/^[a-f0-9]{64}$/u),
21483
+ contentKind: external_exports.enum(["image", "resource"]),
21484
+ kind: external_exports.literal(CONTENT_WRITE_KIND),
21485
+ mediaType: external_exports.string().min(1).max(255),
21486
+ name: external_exports.string().min(1).max(500),
21487
+ requestId: identifierStringSchema,
21488
+ sessionId: identifierStringSchema
21489
+ }).readonly();
21490
+ var contentCommitCommandSchema = external_exports.object({
21491
+ kind: external_exports.literal(CONTENT_COMMIT_KIND),
21492
+ requestId: identifierStringSchema,
21493
+ sessionId: identifierStringSchema
21494
+ }).readonly();
21495
+ var contentReadyEventSchema = external_exports.object({
21496
+ kind: external_exports.literal(CONTENT_READY_KIND),
21497
+ requestId: identifierStringSchema,
21498
+ sessionId: identifierStringSchema
21499
+ }).readonly();
21500
+ var contentAvailableEventSchema = external_exports.object({
21501
+ byteLength: external_exports.number().int().positive().max(CONTENT_WRITE_MAX_BYTES),
21502
+ contentKey: external_exports.string().regex(/^[a-f0-9]{64}$/u),
21503
+ kind: external_exports.literal(CONTENT_AVAILABLE_KIND),
21470
21504
  requestId: identifierStringSchema,
21471
- sequence: external_exports.number().int().nonnegative().max(1024),
21472
21505
  sessionId: identifierStringSchema
21473
21506
  }).readonly();
21507
+ function binaryBytes(value) {
21508
+ if (value instanceof ArrayBuffer) {
21509
+ return new Uint8Array(value);
21510
+ }
21511
+ if (ArrayBuffer.isView(value)) {
21512
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
21513
+ }
21514
+ throw new Error("Content chunk must be a binary WebSocket frame");
21515
+ }
21516
+ function encodeContentChunkFrame(frame) {
21517
+ if (!isIdentifier(frame.requestId)) {
21518
+ throw new Error("Content chunk request identifier is invalid");
21519
+ }
21520
+ if (!Number.isSafeInteger(frame.sequence) || frame.sequence < 0 || frame.sequence > 4294967295) {
21521
+ throw new Error("Content chunk sequence is invalid");
21522
+ }
21523
+ if (frame.bytes.byteLength < 1 || frame.bytes.byteLength > CONTENT_CHUNK_MAX_BYTES) {
21524
+ throw new Error("Content chunk size is invalid");
21525
+ }
21526
+ const requestId = new TextEncoder().encode(frame.requestId);
21527
+ if (requestId.byteLength > 255) {
21528
+ throw new Error("Content chunk request identifier is too long");
21529
+ }
21530
+ const encoded = new Uint8Array(CONTENT_CHUNK_HEADER_BYTES + requestId.byteLength + frame.bytes.byteLength);
21531
+ encoded.set(CONTENT_CHUNK_MAGIC, 0);
21532
+ encoded[CONTENT_CHUNK_MAGIC.byteLength] = requestId.byteLength;
21533
+ new DataView(encoded.buffer).setUint32(CONTENT_CHUNK_MAGIC.byteLength + 1, frame.sequence);
21534
+ encoded.set(requestId, CONTENT_CHUNK_HEADER_BYTES);
21535
+ encoded.set(frame.bytes, CONTENT_CHUNK_HEADER_BYTES + requestId.byteLength);
21536
+ return encoded;
21537
+ }
21538
+ function decodeContentChunkFrame(value) {
21539
+ const encoded = binaryBytes(value);
21540
+ if (encoded.byteLength <= CONTENT_CHUNK_HEADER_BYTES || !CONTENT_CHUNK_MAGIC.every((byte, index2) => encoded[index2] === byte)) {
21541
+ throw new Error("Content chunk frame header is invalid");
21542
+ }
21543
+ const requestIdLength = encoded[CONTENT_CHUNK_MAGIC.byteLength] ?? 0;
21544
+ const payloadOffset = CONTENT_CHUNK_HEADER_BYTES + requestIdLength;
21545
+ if (requestIdLength < 1 || payloadOffset >= encoded.byteLength) {
21546
+ throw new Error("Content chunk frame length is invalid");
21547
+ }
21548
+ const requestId = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(
21549
+ encoded.subarray(CONTENT_CHUNK_HEADER_BYTES, payloadOffset)
21550
+ );
21551
+ const bytes = encoded.slice(payloadOffset);
21552
+ const sequence = new DataView(encoded.buffer, encoded.byteOffset, encoded.byteLength).getUint32(
21553
+ CONTENT_CHUNK_MAGIC.byteLength + 1
21554
+ );
21555
+ if (!isIdentifier(requestId) || bytes.byteLength > CONTENT_CHUNK_MAX_BYTES) {
21556
+ throw new Error("Content chunk frame is invalid");
21557
+ }
21558
+ return { bytes, requestId, sequence };
21559
+ }
21474
21560
  var fileEndEventSchema = external_exports.object({
21475
21561
  kind: external_exports.literal(FILE_END_KIND),
21476
21562
  requestId: identifierStringSchema,
@@ -21487,12 +21573,18 @@ var decodeFileReadCommand = (encoded) => decodeZodMessage(encoded, fileReadComma
21487
21573
  var encodeFileReadCommand = (message) => encodeZodMessage(message, fileReadCommandSchema);
21488
21574
  var decodeFileBeginEvent = (encoded) => decodeZodMessage(encoded, fileBeginEventSchema);
21489
21575
  var encodeFileBeginEvent = (message) => encodeZodMessage(message, fileBeginEventSchema);
21490
- var decodeFileChunkEvent = (encoded) => decodeZodMessage(encoded, fileChunkEventSchema);
21491
- var encodeFileChunkEvent = (message) => encodeZodMessage(message, fileChunkEventSchema);
21492
21576
  var decodeFileEndEvent = (encoded) => decodeZodMessage(encoded, fileEndEventSchema);
21493
21577
  var encodeFileEndEvent = (message) => encodeZodMessage(message, fileEndEventSchema);
21494
21578
  var decodeFileFailedEvent = (encoded) => decodeZodMessage(encoded, fileFailedEventSchema);
21495
21579
  var encodeFileFailedEvent = (message) => encodeZodMessage(message, fileFailedEventSchema);
21580
+ var decodeContentWriteCommand = (encoded) => decodeZodMessage(encoded, contentWriteCommandSchema);
21581
+ var encodeContentWriteCommand = (message) => encodeZodMessage(message, contentWriteCommandSchema);
21582
+ var decodeContentCommitCommand = (encoded) => decodeZodMessage(encoded, contentCommitCommandSchema);
21583
+ var encodeContentCommitCommand = (message) => encodeZodMessage(message, contentCommitCommandSchema);
21584
+ var decodeContentReadyEvent = (encoded) => decodeZodMessage(encoded, contentReadyEventSchema);
21585
+ var encodeContentReadyEvent = (message) => encodeZodMessage(message, contentReadyEventSchema);
21586
+ var decodeContentAvailableEvent = (encoded) => decodeZodMessage(encoded, contentAvailableEventSchema);
21587
+ var encodeContentAvailableEvent = (message) => encodeZodMessage(message, contentAvailableEventSchema);
21496
21588
 
21497
21589
  // ../mobius-sdk/src/protocol/shared/tool-input.ts
21498
21590
  var MAX_CONTENT_LENGTH = 1e6;
@@ -21749,6 +21841,7 @@ var sessionReadyEventSchema = external_exports.object({
21749
21841
  capabilities: sessionCapabilitiesSchema,
21750
21842
  commands: external_exports.array(sessionCommandSchema).max(500).readonly(),
21751
21843
  configOptions: sessionConfigOptionsSchema,
21844
+ configurationRevision: identifierStringSchema.optional(),
21752
21845
  directoryId: DIRECTORY_IDENTIFIER,
21753
21846
  directoryLabel: external_exports.string().min(1).max(MAX_WORKSPACE_PATH_LENGTH),
21754
21847
  kind: external_exports.literal(SESSION_READY_KIND),
@@ -21836,21 +21929,16 @@ var SessionHistoryPolicy = Object.freeze({
21836
21929
  ExecutionOnly: "execution-only"
21837
21930
  });
21838
21931
  var MAX_PROMPT_LENGTH = 1e5;
21839
- var MAX_PROMPT_ATTACHMENT_LENGTH = 6e6;
21840
- var promptImageSchema = external_exports.object({
21841
- data: external_exports.string().min(1).max(4e6),
21932
+ var MAX_PROMPT_ATTACHMENT_BYTES = 32 * 1024 * 1024;
21933
+ var promptAttachmentSchema = external_exports.object({
21934
+ byteLength: external_exports.number().int().positive().max(MAX_PROMPT_ATTACHMENT_BYTES),
21935
+ contentKey: external_exports.string().regex(/^[a-f0-9]{64}$/u),
21936
+ kind: external_exports.enum(["image", "resource"]),
21842
21937
  mimeType: external_exports.string().min(1).max(255),
21843
- name: external_exports.string().min(1).max(500)
21844
- }).readonly();
21845
- var promptResourceSchema = external_exports.object({
21846
- mimeType: external_exports.string().max(255),
21847
21938
  name: external_exports.string().min(1).max(500),
21848
- text: external_exports.string().max(1e6),
21849
- type: external_exports.enum(["embedded", "link"]),
21850
21939
  uri: external_exports.string().min(1).max(4096)
21851
21940
  }).readonly();
21852
- var decodePromptImagesValue = (value) => decodeZodValue(value, external_exports.array(promptImageSchema).max(8).readonly());
21853
- var decodePromptResourcesValue = (value) => decodeZodValue(value, external_exports.array(promptResourceSchema).max(16).readonly());
21941
+ var decodePromptAttachmentsValue = (value) => decodeZodValue(value, external_exports.array(promptAttachmentSchema).max(16).readonly());
21854
21942
  function readPromptDisposition(value) {
21855
21943
  const field = value["disposition"];
21856
21944
  if (field === void 0 || field === SessionPromptDisposition.Reject) {
@@ -21869,13 +21957,12 @@ function decodeSessionPromptCommand(encoded) {
21869
21957
  const hasSessionConfigurations = Object.hasOwn(value, "sessionConfigurations");
21870
21958
  assertKind(value, SESSION_PROMPT_KIND);
21871
21959
  const prompt = readText(value, "prompt", MAX_PROMPT_LENGTH);
21872
- const images = decodePromptImagesValue(value["images"]);
21873
- const resources = decodePromptResourcesValue(value["resources"]);
21874
- const attachmentLength = images.reduce((total, image) => total + image.data.length, 0) + resources.reduce((total, resource) => total + resource.text.length, 0);
21875
- if (attachmentLength > MAX_PROMPT_ATTACHMENT_LENGTH) {
21960
+ const attachments = !Object.hasOwn(value, "attachments") && Array.isArray(value["images"]) && Array.isArray(value["resources"]) ? [] : decodePromptAttachmentsValue(value["attachments"]);
21961
+ const attachmentBytes = attachments.reduce((total, attachment) => total + attachment.byteLength, 0);
21962
+ if (attachmentBytes > MAX_PROMPT_ATTACHMENT_BYTES) {
21876
21963
  throw new ProtocolMessageError("Prompt attachments are too large");
21877
21964
  }
21878
- if (prompt.trim().length === 0 && images.length === 0 && resources.length === 0) {
21965
+ if (prompt.trim().length === 0 && attachments.length === 0) {
21879
21966
  throw new ProtocolMessageError("Prompt must contain content");
21880
21967
  }
21881
21968
  const disposition = readPromptDisposition(value);
@@ -21895,13 +21982,12 @@ function decodeSessionPromptCommand(encoded) {
21895
21982
  commandId: readIdentifier(value, "commandId"),
21896
21983
  directoryId: readDirectoryIdentifier(value, "directoryId"),
21897
21984
  disposition,
21898
- images,
21985
+ attachments,
21899
21986
  ...value["historyPolicy"] === void 0 ? {} : {
21900
21987
  historyPolicy: readHistoryPolicy(value)
21901
21988
  },
21902
21989
  kind: SESSION_PROMPT_KIND,
21903
21990
  prompt,
21904
- resources,
21905
21991
  ...hasSessionConfigurations ? { sessionConfigurations } : {},
21906
21992
  sessionId: readIdentifier(value, "sessionId"),
21907
21993
  targetCommandId
@@ -21937,7 +22023,15 @@ function decodeSessionPromptDispositionCommand(encoded) {
21937
22023
  };
21938
22024
  }
21939
22025
  function encodeSessionPromptCommand(message) {
21940
- return JSON.stringify(decodeSessionPromptCommand(JSON.stringify(message)));
22026
+ const command = decodeSessionPromptCommand(JSON.stringify(message));
22027
+ return JSON.stringify({
22028
+ ...command,
22029
+ // Keep text prompts compatible with the previous Runtime release while
22030
+ // attachments move to the content-key protocol. Older Runtimes ignore
22031
+ // `attachments` and require both legacy arrays to be present.
22032
+ images: [],
22033
+ resources: []
22034
+ });
21941
22035
  }
21942
22036
  function encodeSessionCancelCommand(message) {
21943
22037
  return JSON.stringify(decodeSessionCancelCommand(JSON.stringify(message)));
@@ -22022,9 +22116,7 @@ var nativeSessionResultSchema = external_exports.discriminatedUnion("action", [
22022
22116
  external_exports.object({
22023
22117
  commandId: identifierStringSchema,
22024
22118
  prompt: external_exports.string().max(1e5),
22025
- images: external_exports.array(promptImageSchema).max(8).readonly(),
22026
- // Absent from daemons that predate it; a client then cannot restore the prompt's files.
22027
- resources: external_exports.array(promptResourceSchema).max(16).readonly().optional()
22119
+ attachments: external_exports.array(promptAttachmentSchema).max(16).readonly()
22028
22120
  }).readonly()
22029
22121
  ).readonly(),
22030
22122
  turn: activeTurnSnapshotSchema.nullable()
@@ -22626,6 +22718,16 @@ var serviceToDaemonSignatures = {
22626
22718
  decodeAgentCatalogDiscoverCommand
22627
22719
  ),
22628
22720
  [FILE_READ_KIND]: defineMessageSignature(FILE_READ_KIND, encodeFileReadCommand, decodeFileReadCommand),
22721
+ [CONTENT_WRITE_KIND]: defineMessageSignature(
22722
+ CONTENT_WRITE_KIND,
22723
+ encodeContentWriteCommand,
22724
+ decodeContentWriteCommand
22725
+ ),
22726
+ [CONTENT_COMMIT_KIND]: defineMessageSignature(
22727
+ CONTENT_COMMIT_KIND,
22728
+ encodeContentCommitCommand,
22729
+ decodeContentCommitCommand
22730
+ ),
22629
22731
  [SESSION_LIST_KIND]: defineMessageSignature(SESSION_LIST_KIND, encodeSessionListCommand, decodeSessionListCommand),
22630
22732
  [SESSION_QUERY_KIND]: defineMessageSignature(
22631
22733
  SESSION_QUERY_KIND,
@@ -22785,9 +22887,14 @@ var daemonToServiceSignatures = {
22785
22887
  decodeAgentCatalogDiscoveryFailedEvent
22786
22888
  ),
22787
22889
  [FILE_BEGIN_KIND]: defineMessageSignature(FILE_BEGIN_KIND, encodeFileBeginEvent, decodeFileBeginEvent),
22788
- [FILE_CHUNK_KIND]: defineMessageSignature(FILE_CHUNK_KIND, encodeFileChunkEvent, decodeFileChunkEvent),
22789
22890
  [FILE_END_KIND]: defineMessageSignature(FILE_END_KIND, encodeFileEndEvent, decodeFileEndEvent),
22790
22891
  [FILE_FAILED_KIND]: defineMessageSignature(FILE_FAILED_KIND, encodeFileFailedEvent, decodeFileFailedEvent),
22892
+ [CONTENT_READY_KIND]: defineMessageSignature(CONTENT_READY_KIND, encodeContentReadyEvent, decodeContentReadyEvent),
22893
+ [CONTENT_AVAILABLE_KIND]: defineMessageSignature(
22894
+ CONTENT_AVAILABLE_KIND,
22895
+ encodeContentAvailableEvent,
22896
+ decodeContentAvailableEvent
22897
+ ),
22791
22898
  [SESSION_LISTED_KIND]: defineMessageSignature(
22792
22899
  SESSION_LISTED_KIND,
22793
22900
  encodeSessionsListedEvent,
@@ -32275,7 +32382,7 @@ import { fetch as fetch3 } from "undici";
32275
32382
  // package.json
32276
32383
  var package_default = {
32277
32384
  name: "@moxt-ai/mobius",
32278
- version: "0.0.323",
32385
+ version: "0.0.324",
32279
32386
  description: "Mobius CLI for pairing and running local AI agent services.",
32280
32387
  type: "module",
32281
32388
  bin: {
@@ -83559,7 +83666,8 @@ var PreparedSession = class {
83559
83666
  configOptions;
83560
83667
  prompted;
83561
83668
  restored;
83562
- constructor(agentName, agentVersion, capabilities, commands, configOptions, prompted, restored) {
83669
+ configurationRevision;
83670
+ constructor(agentName, agentVersion, capabilities, commands, configOptions, prompted, restored, configurationRevision) {
83563
83671
  this.agentName = agentName;
83564
83672
  this.agentVersion = agentVersion;
83565
83673
  this.capabilities = capabilities;
@@ -83567,6 +83675,7 @@ var PreparedSession = class {
83567
83675
  this.configOptions = configOptions;
83568
83676
  this.prompted = prompted;
83569
83677
  this.restored = restored;
83678
+ this.configurationRevision = configurationRevision;
83570
83679
  }
83571
83680
  };
83572
83681
  var AgentSettingsSnapshot = class {
@@ -84606,7 +84715,8 @@ var AgentSession = class _AgentSession {
84606
84715
  #agentVersion = "";
84607
84716
  #capabilities = unsupportedSessionCapabilities();
84608
84717
  #commands = [];
84609
- #configRevision = 0;
84718
+ #configRevision = crypto.randomUUID();
84719
+ #configurationChanges = new SerialTaskQueue();
84610
84720
  #configOptions = [];
84611
84721
  #sessionConfigurations = new SessionConfigurations();
84612
84722
  #nativeHistoryOwnership = { kind: "unbound" };
@@ -84775,7 +84885,8 @@ var AgentSession = class _AgentSession {
84775
84885
  this.#commands,
84776
84886
  this.#configOptions,
84777
84887
  this.#prompted,
84778
- this.#restored
84888
+ this.#restored,
84889
+ this.#configRevision
84779
84890
  );
84780
84891
  ensureHistoryLoaded = async (signal) => {
84781
84892
  if (!this.#capabilities.loadSession) {
@@ -84905,12 +85016,23 @@ var AgentSession = class _AgentSession {
84905
85016
  const configured = await this.#adapter.configureSession(this.#target(), selection, signal);
84906
85017
  if (this.#configRevision === configRevision) {
84907
85018
  this.#configOptions = configured;
85019
+ this.#configRevision = crypto.randomUUID();
85020
+ await this.#publishConfiguration();
84908
85021
  }
84909
85022
  };
84910
- configure = async (options) => {
85023
+ // The revision check and Agent mutation share one queue, including legacy callers.
85024
+ // A lost response may have committed: reread after a conflict, never replay an edit blindly.
85025
+ configure = async (options) => this.#configurationChanges.run(async (signal) => {
85026
+ if (options.expectedConfigurationRevision !== void 0 && options.expectedConfigurationRevision !== this.#configRevision) {
85027
+ throw new AcpExecutionError(
85028
+ "Session settings changed. Review the current settings before applying your selection again.",
85029
+ {},
85030
+ "session_configuration_conflict"
85031
+ );
85032
+ }
84911
85033
  await this.#applyConfiguration(
84912
85034
  options.valueType === "boolean" ? { type: "boolean", configId: options.configId, booleanValue: options.booleanValue } : { type: "select", configId: options.configId, value: options.value },
84913
- options.signal
85035
+ signal
84914
85036
  );
84915
85037
  const applied = this.#configOptions.find((option) => option.id === options.configId);
84916
85038
  if (applied === void 0) {
@@ -84918,7 +85040,24 @@ var AgentSession = class _AgentSession {
84918
85040
  }
84919
85041
  this.#sessionConfigurations.select(applied);
84920
85042
  this.writeStoredSession((/* @__PURE__ */ new Date()).toISOString());
84921
- return this.#configOptions;
85043
+ return { configOptions: this.#configOptions, configurationRevision: this.#configRevision };
85044
+ }, options.signal);
85045
+ #publishConfiguration = async () => {
85046
+ const event = this.#store.events.append(this.#mobiusSessionId, {
85047
+ type: "acp",
85048
+ payload: {
85049
+ sessionUpdate: "config_option_update",
85050
+ configOptions: this.#configOptions.map((option) => ({
85051
+ category: option.category,
85052
+ description: option.description,
85053
+ id: option.id,
85054
+ name: option.name,
85055
+ ...option.type === "boolean" ? { type: "boolean", currentValue: option.booleanValue } : { type: "select", currentValue: option.currentValue, options: option.values }
85056
+ }))
85057
+ }
85058
+ });
85059
+ await this.#getObserver().onSessionEvent?.(event);
85060
+ await this.emitConfigUpdate();
84922
85061
  };
84923
85062
  #target = () => ({
84924
85063
  nativeSessionId: this.#updates.sessionId(),
@@ -85096,6 +85235,13 @@ var AgentSession = class _AgentSession {
85096
85235
  this.#nativeHistoryOwnership = { kind: "agent-owned" };
85097
85236
  };
85098
85237
  #processEvent = async (event) => {
85238
+ if (event.kind === "configuration" || event.kind === "mode") {
85239
+ this.#configOptions = event.kind === "configuration" ? event.configOptions : this.#configOptions.map(
85240
+ (option) => option.category === "mode" ? { ...option, currentValue: event.modeId } : option
85241
+ );
85242
+ this.#configRevision = crypto.randomUUID();
85243
+ this.#captureSelectedConfigurationValues();
85244
+ }
85099
85245
  if (this.#sessionLoad.kind === "capturing") {
85100
85246
  await this.#sessionLoad.history.record(event.payload);
85101
85247
  } else if (this.#sessionLoad.kind === "loaded" || this.#sessionLoad.kind === "execution-only") {
@@ -85116,17 +85262,7 @@ var AgentSession = class _AgentSession {
85116
85262
  });
85117
85263
  return;
85118
85264
  case "configuration":
85119
- this.#configOptions = event.configOptions;
85120
- this.#configRevision += 1;
85121
- this.#captureSelectedConfigurationValues();
85122
- await this.emitConfigUpdate();
85123
- return;
85124
85265
  case "mode":
85125
- this.#configOptions = this.#configOptions.map(
85126
- (option) => option.category === "mode" ? { ...option, currentValue: event.modeId } : option
85127
- );
85128
- this.#configRevision += 1;
85129
- this.#captureSelectedConfigurationValues();
85130
85266
  await this.emitConfigUpdate();
85131
85267
  return;
85132
85268
  case "info":
@@ -85189,6 +85325,7 @@ var AgentSession = class _AgentSession {
85189
85325
  }
85190
85326
  setConfigOptions(options) {
85191
85327
  this.#configOptions = options;
85328
+ this.#configRevision = crypto.randomUUID();
85192
85329
  }
85193
85330
  setNativeHistoryOwnership(ownership) {
85194
85331
  this.#nativeHistoryOwnership = ownership;
@@ -85210,6 +85347,7 @@ var AgentSession = class _AgentSession {
85210
85347
  emitConfigUpdate = async () => {
85211
85348
  await this.#getObserver().onConfig({
85212
85349
  configOptions: this.#configOptions,
85350
+ configurationRevision: this.#configRevision,
85213
85351
  kind: SESSION_CONFIG_UPDATED_KIND,
85214
85352
  requestId: `agent-update-${crypto.randomUUID()}`,
85215
85353
  sessionId: this.#mobiusSessionId
@@ -85608,7 +85746,7 @@ var SessionManager2 = class {
85608
85746
  (option) => option.id === "model" || option.category === "model"
85609
85747
  );
85610
85748
  const modelIsFixed = modelOption2?.type === "select" && modelOption2.values.length < 2 && modelOption2.currentValue === model;
85611
- const configOptions = model === void 0 || modelIsFixed ? prepared.configOptions : await runtime.configure({
85749
+ const configOptions = model === void 0 || modelIsFixed ? prepared.configOptions : (await runtime.configure({
85612
85750
  ...options2,
85613
85751
  booleanValue: false,
85614
85752
  configId: modelOption2?.id ?? "model",
@@ -85616,7 +85754,7 @@ var SessionManager2 = class {
85616
85754
  sessionId: `agent-settings-${crypto.randomUUID()}`,
85617
85755
  value: model,
85618
85756
  valueType: "select"
85619
- });
85757
+ })).configOptions;
85620
85758
  return new AgentSettingsSnapshot(
85621
85759
  prepared.agentName,
85622
85760
  prepared.agentVersion,
@@ -86452,19 +86590,22 @@ function promptContent(command) {
86452
86590
  if (command.prompt.length > 0) {
86453
86591
  content.push({ text: command.prompt, type: "text" });
86454
86592
  }
86455
- for (const image of command.images) {
86456
- content.push({
86457
- data: image.data,
86458
- mimeType: image.mimeType,
86459
- type: "image"
86460
- });
86461
- }
86462
- for (const resource of command.resources) {
86593
+ for (const attachment of command.attachments) {
86463
86594
  content.push(
86464
- resource.type === "embedded" ? {
86465
- resource: { mimeType: resource.mimeType, text: resource.text, uri: resource.uri },
86466
- type: "resource"
86467
- } : { mimeType: resource.mimeType, name: resource.name, type: "resource_link", uri: resource.uri }
86595
+ attachment.kind === "image" ? {
86596
+ byteLength: attachment.byteLength,
86597
+ imageKey: attachment.contentKey,
86598
+ mimeType: attachment.mimeType,
86599
+ type: "external_image",
86600
+ uri: attachment.uri
86601
+ } : {
86602
+ byteLength: attachment.byteLength,
86603
+ encoding: "text",
86604
+ mimeType: attachment.mimeType,
86605
+ resourceKey: attachment.contentKey,
86606
+ type: "external_resource",
86607
+ uri: attachment.uri
86608
+ }
86468
86609
  );
86469
86610
  }
86470
86611
  return content;
@@ -87143,8 +87284,7 @@ var SessionTurns = class {
87143
87284
  queued.push({
87144
87285
  commandId: candidate.command.commandId,
87145
87286
  prompt: candidate.command.prompt,
87146
- images: candidate.command.images,
87147
- resources: candidate.command.resources
87287
+ attachments: candidate.command.attachments
87148
87288
  });
87149
87289
  }
87150
87290
  return { turn: turnSnapshot ?? null, queued };
@@ -87441,7 +87581,7 @@ import { join as join27 } from "node:path";
87441
87581
 
87442
87582
  // src/storage/files/scratch-directory.ts
87443
87583
  import { randomUUID as randomUUID15 } from "node:crypto";
87444
- import { lstat as lstat4, mkdir as mkdir5, readdir as readdir6, realpath as realpath7, rename as rename4, rm as rm3, stat as stat6, utimes, writeFile as writeFile2 } from "node:fs/promises";
87584
+ import { lstat as lstat4, mkdir as mkdir5, readdir as readdir6, readFile as readFile6, realpath as realpath7, rename as rename4, rm as rm3, stat as stat6, utimes, writeFile as writeFile2 } from "node:fs/promises";
87445
87585
  import { dirname as dirname11, join as join26 } from "node:path";
87446
87586
  async function atomicWrite(path7, bytes, signal) {
87447
87587
  const temporary = `${path7}.${randomUUID15()}.tmp`;
@@ -87505,6 +87645,12 @@ var ScratchDirectory = class {
87505
87645
  return null;
87506
87646
  }
87507
87647
  };
87648
+ /** Reads a regular file owned by this scratch directory after validating its canonical path. */
87649
+ read = async (path7, signal) => {
87650
+ const resolved = await this.resolveFile(path7);
87651
+ signal.throwIfAborted();
87652
+ return resolved === null ? null : new Uint8Array(await readFile6(resolved, { signal }));
87653
+ };
87508
87654
  removeAll = async () => {
87509
87655
  await rm3(this.#root, { force: true, recursive: true });
87510
87656
  };
@@ -87606,6 +87752,11 @@ var LocalExternalContentStore = class {
87606
87752
  signal.throwIfAborted();
87607
87753
  return resolvedPath === null ? null : { mediaType: entry.mediaType, name: entry.name, path: resolvedPath };
87608
87754
  };
87755
+ read = (sessionId, request, signal) => this.#track(async () => {
87756
+ const active = AbortSignal.any([signal, this.#lifetime.signal]);
87757
+ const resolved = await this.#resolve(sessionId, request, active);
87758
+ return resolved === null ? null : this.#scratch.read(resolved.path, active);
87759
+ });
87609
87760
  [Symbol.asyncDispose] = async () => {
87610
87761
  clearInterval(this.#expirationTimer);
87611
87762
  this.#lifetime.abort();
@@ -88271,6 +88422,12 @@ function send(socket, message) {
88271
88422
  }
88272
88423
  socket.send(encodeDaemonToServiceMessage(message));
88273
88424
  }
88425
+ function sendContentChunk(socket, requestId, sequence, bytes) {
88426
+ if (socket.readyState !== WebSocket2.OPEN) {
88427
+ throw new Error("Relay connection is not open");
88428
+ }
88429
+ socket.send(encodeContentChunkFrame({ bytes, requestId, sequence }));
88430
+ }
88274
88431
  function reportOperationFailure(socket, requestId, sessionId, error62, runtimeTimings = []) {
88275
88432
  if (socket.readyState !== WebSocket2.OPEN) {
88276
88433
  return;
@@ -88890,13 +89047,133 @@ function createAgentSettingsCommands(context) {
88890
89047
  ];
88891
89048
  }
88892
89049
 
89050
+ // src/runtime/command/registry/content-write/index.ts
89051
+ import { createHash as createHash9 } from "node:crypto";
89052
+ function createContentWriteCommands(context) {
89053
+ const pending = /* @__PURE__ */ new Map();
89054
+ const operations = /* @__PURE__ */ new Set();
89055
+ const fail = (command, error62) => {
89056
+ pending.delete(command.requestId);
89057
+ send(context.socket, {
89058
+ code: "content_write_failed",
89059
+ kind: FILE_FAILED_KIND,
89060
+ message: error62 instanceof Error ? error62.message : "The content could not be stored",
89061
+ requestId: command.requestId,
89062
+ sessionId: command.sessionId
89063
+ });
89064
+ };
89065
+ const write = {
89066
+ kind: CONTENT_WRITE_KIND,
89067
+ handle: (_commandContext, message) => {
89068
+ if (message.kind !== CONTENT_WRITE_KIND) {
89069
+ throw new Error("Command kind mismatch");
89070
+ }
89071
+ const existing = pending.get(message.requestId);
89072
+ if (existing !== void 0) {
89073
+ if (JSON.stringify(existing.command) !== JSON.stringify(message)) {
89074
+ fail(message, new Error("The content request identifier is already in use"));
89075
+ return;
89076
+ }
89077
+ } else {
89078
+ pending.set(message.requestId, {
89079
+ bytes: new Uint8Array(message.byteLength),
89080
+ command: message,
89081
+ hash: createHash9("sha256"),
89082
+ receivedBytes: 0,
89083
+ sequence: 0
89084
+ });
89085
+ }
89086
+ send(context.socket, {
89087
+ kind: CONTENT_READY_KIND,
89088
+ requestId: message.requestId,
89089
+ sessionId: message.sessionId
89090
+ });
89091
+ }
89092
+ };
89093
+ const commit = {
89094
+ kind: CONTENT_COMMIT_KIND,
89095
+ handle: (_commandContext, message) => {
89096
+ if (message.kind !== CONTENT_COMMIT_KIND) {
89097
+ throw new Error("Command kind mismatch");
89098
+ }
89099
+ const state = pending.get(message.requestId);
89100
+ if (state === void 0 || state.command.sessionId !== message.sessionId) {
89101
+ fail(message, new Error("The content upload is unavailable"));
89102
+ return;
89103
+ }
89104
+ pending.delete(message.requestId);
89105
+ const operation = (async () => {
89106
+ try {
89107
+ if (state.receivedBytes !== state.command.byteLength) {
89108
+ throw new Error("The content upload ended at an unexpected length");
89109
+ }
89110
+ if (state.hash.digest("hex") !== state.command.contentKey) {
89111
+ throw new Error("The content upload failed integrity validation");
89112
+ }
89113
+ const stored = await context.externalContent.put({
89114
+ bytes: state.bytes,
89115
+ kind: state.command.contentKind,
89116
+ mediaType: state.command.mediaType,
89117
+ name: state.command.name,
89118
+ sessionId: state.command.sessionId,
89119
+ signal: context.signal
89120
+ });
89121
+ if (stored.key !== state.command.contentKey || stored.byteLength !== state.command.byteLength) {
89122
+ throw new Error("Stored content did not match the upload");
89123
+ }
89124
+ send(context.socket, {
89125
+ byteLength: stored.byteLength,
89126
+ contentKey: stored.key,
89127
+ kind: CONTENT_AVAILABLE_KIND,
89128
+ requestId: message.requestId,
89129
+ sessionId: message.sessionId
89130
+ });
89131
+ } catch (error62) {
89132
+ if (!context.signal.aborted) {
89133
+ fail(message, error62);
89134
+ }
89135
+ }
89136
+ })();
89137
+ operations.add(operation);
89138
+ void operation.finally(() => operations.delete(operation));
89139
+ }
89140
+ };
89141
+ return {
89142
+ commands: [write, commit],
89143
+ receiveChunk: (frame) => {
89144
+ const state = pending.get(frame.requestId);
89145
+ if (state === void 0) {
89146
+ context.diagnostics.daemonDiagnostic("content_chunk_discarded", frame.requestId, {
89147
+ reason: "upload_unavailable",
89148
+ sequence: frame.sequence.toString()
89149
+ });
89150
+ return;
89151
+ }
89152
+ if (frame.sequence !== state.sequence || state.receivedBytes + frame.bytes.byteLength > state.command.byteLength) {
89153
+ fail(state.command, new Error("The content upload contains invalid chunks"));
89154
+ return;
89155
+ }
89156
+ state.bytes.set(frame.bytes, state.receivedBytes);
89157
+ state.hash.update(frame.bytes);
89158
+ pending.set(frame.requestId, {
89159
+ ...state,
89160
+ receivedBytes: state.receivedBytes + frame.bytes.byteLength,
89161
+ sequence: state.sequence + 1
89162
+ });
89163
+ },
89164
+ settle: async () => {
89165
+ pending.clear();
89166
+ await Promise.allSettled([...operations]);
89167
+ }
89168
+ };
89169
+ }
89170
+
88893
89171
  // src/runtime/command/registry/file-read/index.ts
88894
89172
  import { WebSocket as WebSocket5 } from "undici";
88895
89173
 
88896
89174
  // src/session/content/session-files.ts
88897
89175
  import { basename as basename8, extname as extname2, isAbsolute as isAbsolute16, join as join31, normalize as normalize3, resolve as resolve17 } from "node:path";
88898
89176
  import { fileURLToPath as fileURLToPath2 } from "node:url";
88899
- var CHUNK_BYTES = 192 * 1024;
88900
89177
  function requestedPath(value) {
88901
89178
  if (value.includes("\0")) {
88902
89179
  return null;
@@ -88936,7 +89213,7 @@ async function resolveFile(value, workingDirectory, signal) {
88936
89213
  }
88937
89214
  return null;
88938
89215
  }
88939
- async function streamLocalFile(command, send2, signal, workingDirectory, externalContent) {
89216
+ async function streamLocalFile(command, send2, sendChunk, signal, workingDirectory, externalContent) {
88940
89217
  const external = await externalContent.resolve(command.sessionId, command.path, signal);
88941
89218
  const target = external?.path ?? await resolveFile(command.path, workingDirectory, signal);
88942
89219
  if (target === null) {
@@ -88958,14 +89235,8 @@ async function streamLocalFile(command, send2, signal, workingDirectory, externa
88958
89235
  sessionId: command.sessionId
88959
89236
  });
88960
89237
  let sequence = 0;
88961
- for await (const bytes of readFileChunks(target, CHUNK_BYTES, signal)) {
88962
- send2({
88963
- data: Buffer.from(bytes).toString("base64"),
88964
- kind: FILE_CHUNK_KIND,
88965
- requestId: command.requestId,
88966
- sequence,
88967
- sessionId: command.sessionId
88968
- });
89238
+ for await (const bytes of readFileChunks(target, CONTENT_CHUNK_MAX_BYTES, signal)) {
89239
+ sendChunk(command.requestId, sequence, bytes);
88969
89240
  sequence += 1;
88970
89241
  }
88971
89242
  const after = await readPathMetadata(target);
@@ -88990,6 +89261,7 @@ var fileReadCommand = {
88990
89261
  void streamLocalFile(
88991
89262
  message,
88992
89263
  (response) => send(socket, response),
89264
+ (requestId, sequence, bytes) => sendContentChunk(socket, requestId, sequence, bytes),
88993
89265
  signal,
88994
89266
  sessions.findSessions([message.sessionId])[0]?.cwd ?? null,
88995
89267
  externalContent
@@ -89587,7 +89859,7 @@ async function applySessionConfigurationSelection(sessions, options, selection,
89587
89859
  if (sessionConfigurationMatches(option, selection)) {
89588
89860
  return options;
89589
89861
  }
89590
- return await sessions.configureSession({
89862
+ return (await sessions.configureSession({
89591
89863
  adapter,
89592
89864
  additionalDirectories,
89593
89865
  agent,
@@ -89599,7 +89871,7 @@ async function applySessionConfigurationSelection(sessions, options, selection,
89599
89871
  signal,
89600
89872
  value: selection.value,
89601
89873
  valueType: selection.valueType
89602
- });
89874
+ })).configOptions;
89603
89875
  }
89604
89876
  async function applyCompleteSessionConfiguration(sessions, initialOptions, selections, agent, adapter, cwd, additionalDirectories, commandId, sessionId, signal) {
89605
89877
  let options = initialOptions;
@@ -89636,8 +89908,7 @@ function warnIgnoredSetting(event, commandId, sessionId, agentId, selection, cat
89636
89908
  }
89637
89909
 
89638
89910
  // src/session/prompt/prompt-artifacts.ts
89639
- var ARTIFACT_SCHEME = "mobius-artifact:";
89640
- var MAX_AGENT_IMAGE_BASE64_LENGTH = 4e6;
89911
+ import { createHash as createHash10 } from "node:crypto";
89641
89912
  var MAX_AGENT_RESOURCE_BYTE_LENGTH = 1e6;
89642
89913
  var PromptArtifactError = class extends OperationError {
89643
89914
  constructor(code, message, options = {}) {
@@ -89653,99 +89924,60 @@ var ResolvedPromptArtifacts = class {
89653
89924
  this.resources = resources;
89654
89925
  }
89655
89926
  };
89656
- var PromptArtifactReference = class {
89657
- artifactId;
89658
- byteLength;
89659
- resource;
89660
- sha256;
89661
- constructor(artifactId, byteLength, sha256, resource) {
89662
- this.artifactId = artifactId;
89663
- this.byteLength = byteLength;
89664
- this.resource = resource;
89665
- this.sha256 = sha256;
89666
- }
89667
- };
89668
- function parseReference(resource) {
89669
- let url2;
89670
- try {
89671
- url2 = new URL(resource.uri);
89672
- } catch (cause) {
89673
- throw new PromptArtifactError("artifact_reference_invalid", "The prompt artifact reference is invalid", {
89674
- cause
89675
- });
89676
- }
89677
- const keys = [...url2.searchParams.keys()].sort();
89678
- const artifactId = url2.pathname;
89679
- const encodedByteLength = url2.searchParams.get("byteLength") ?? "";
89680
- const sha256 = url2.searchParams.get("sha256") ?? "";
89681
- const byteLength = Number(encodedByteLength);
89682
- if (url2.protocol !== ARTIFACT_SCHEME || url2.hash.length > 0 || resource.type !== "link" || resource.text.length > 0 || !isIdentifier(artifactId) || keys.length !== 2 || keys[0] !== "byteLength" || keys[1] !== "sha256" || !/^[1-9][0-9]*$/.test(encodedByteLength) || !Number.isSafeInteger(byteLength) || !/^[a-f0-9]{64}$/.test(sha256)) {
89683
- throw new PromptArtifactError("artifact_reference_invalid", "The prompt artifact reference is invalid");
89684
- }
89685
- return new PromptArtifactReference(artifactId, byteLength, sha256, resource);
89686
- }
89687
89927
  function isTextMediaType(mediaType) {
89688
89928
  return mediaType.startsWith("text/") || mediaType === "application/json" || mediaType.endsWith("+json") || mediaType === "application/xml" || mediaType.endsWith("+xml");
89689
89929
  }
89690
- async function resolvePromptArtifacts(download, command, signal) {
89691
- const images = [...command.images];
89930
+ async function resolvePromptArtifacts(read, attachments, signal) {
89931
+ const images = [];
89692
89932
  const resources = [];
89693
- for (const resource of command.resources) {
89933
+ for (const attachment of attachments) {
89694
89934
  signal.throwIfAborted();
89695
- if (!resource.uri.startsWith(ARTIFACT_SCHEME)) {
89696
- resources.push(resource);
89697
- continue;
89935
+ const bytes = await read(attachment, signal);
89936
+ if (bytes.byteLength !== attachment.byteLength || createHash10("sha256").update(bytes).digest("hex") !== attachment.contentKey) {
89937
+ throw new PromptArtifactError(
89938
+ "artifact_integrity_failed",
89939
+ "The prompt attachment failed integrity validation"
89940
+ );
89698
89941
  }
89699
- const reference = parseReference(resource);
89700
- const bytes = await download(
89701
- {
89702
- artifactId: reference.artifactId,
89703
- byteLength: reference.byteLength,
89704
- commandId: command.commandId,
89705
- mimeType: reference.resource.mimeType,
89706
- sessionId: command.sessionId,
89707
- sha256: reference.sha256
89708
- },
89709
- signal
89710
- );
89711
- if (resource.mimeType.startsWith("image/")) {
89712
- const data = Buffer.from(bytes).toString("base64");
89713
- if (data.length > MAX_AGENT_IMAGE_BASE64_LENGTH || images.length >= 8) {
89942
+ if (attachment.kind === "image") {
89943
+ if (bytes.byteLength > PROMPT_IMAGE_MAX_BYTES || images.length >= 8) {
89714
89944
  throw new PromptArtifactError(
89715
89945
  "artifact_too_large_for_agent",
89716
- "The prompt artifact exceeds the Agent image limit"
89946
+ "The prompt attachment exceeds the Agent image limit"
89717
89947
  );
89718
89948
  }
89719
- images.push({ data, mimeType: resource.mimeType, name: resource.name });
89949
+ images.push({
89950
+ data: Buffer.from(bytes).toString("base64"),
89951
+ mimeType: attachment.mimeType,
89952
+ name: attachment.name
89953
+ });
89720
89954
  continue;
89721
89955
  }
89722
- if (!isTextMediaType(resource.mimeType)) {
89956
+ if (!isTextMediaType(attachment.mimeType)) {
89723
89957
  throw new PromptArtifactError(
89724
89958
  "artifact_media_type_unsupported",
89725
- "The Agent cannot consume this prompt artifact media type"
89959
+ "The Agent cannot consume this prompt attachment media type"
89726
89960
  );
89727
89961
  }
89728
89962
  if (bytes.byteLength > MAX_AGENT_RESOURCE_BYTE_LENGTH || resources.length >= 16) {
89729
89963
  throw new PromptArtifactError(
89730
89964
  "artifact_too_large_for_agent",
89731
- "The prompt artifact exceeds the Agent resource limit"
89965
+ "The prompt attachment exceeds the Agent resource limit"
89732
89966
  );
89733
89967
  }
89734
89968
  let text2;
89735
89969
  try {
89736
89970
  text2 = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
89737
89971
  } catch (cause) {
89738
- throw new PromptArtifactError("artifact_encoding_invalid", "The prompt artifact is not valid UTF-8 text", {
89739
- cause
89740
- });
89972
+ throw new PromptArtifactError(
89973
+ "artifact_encoding_invalid",
89974
+ "The prompt attachment is not valid UTF-8 text",
89975
+ {
89976
+ cause
89977
+ }
89978
+ );
89741
89979
  }
89742
- resources.push({
89743
- mimeType: resource.mimeType,
89744
- name: resource.name,
89745
- text: text2,
89746
- type: "embedded",
89747
- uri: resource.uri
89748
- });
89980
+ resources.push({ mimeType: attachment.mimeType, text: text2, type: "embedded", uri: attachment.uri });
89749
89981
  }
89750
89982
  return new ResolvedPromptArtifacts(images, resources);
89751
89983
  }
@@ -89756,7 +89988,7 @@ async function executePrompt({
89756
89988
  eventProjection,
89757
89989
  command,
89758
89990
  resolveAgent,
89759
- downloadArtifact,
89991
+ readArtifact,
89760
89992
  reportFailure,
89761
89993
  directories,
89762
89994
  sessions,
@@ -89838,7 +90070,7 @@ async function executePrompt({
89838
90070
  stream.epoch,
89839
90071
  availability.disposition
89840
90072
  );
89841
- const attachments = await resolvePromptArtifacts(downloadArtifact, command, signal);
90073
+ const attachments = await resolvePromptArtifacts(readArtifact, command.attachments, signal);
89842
90074
  let stopReason;
89843
90075
  if (availability.disposition === SessionPromptDisposition.Steer) {
89844
90076
  await sessions.steerPrompt({
@@ -89898,115 +90130,6 @@ async function executePrompt({
89898
90130
  }
89899
90131
  }
89900
90132
 
89901
- // src/runtime/relay/artifact-download.ts
89902
- import { createHash as createHash9 } from "node:crypto";
89903
- import { fetch as fetch4 } from "undici";
89904
- var ARTIFACT_DOWNLOAD_TIMEOUT_MILLISECONDS = 2e4;
89905
- function downloadUrl(config2, artifact) {
89906
- const url2 = relayHttpEndpoint(config2.relayUrl);
89907
- let channelPath = "";
89908
- if (url2.pathname.endsWith("/daemon")) {
89909
- channelPath = url2.pathname.slice(0, -"/daemon".length);
89910
- } else {
89911
- const runtimePath = "/v1/runtimes/connect";
89912
- if (!url2.pathname.endsWith(runtimePath)) {
89913
- throw new PromptArtifactError("artifact_endpoint_invalid", "The Relay artifact endpoint is invalid");
89914
- }
89915
- channelPath = `${url2.pathname.slice(0, -runtimePath.length)}/channels/${encodeURIComponent(config2.channelId)}`;
89916
- }
89917
- url2.pathname = `${channelPath}/sessions/${encodeURIComponent(artifact.sessionId)}/commands/${encodeURIComponent(artifact.commandId)}/artifacts/${encodeURIComponent(artifact.artifactId)}/content`;
89918
- return url2;
89919
- }
89920
- async function responseBytes(response, expectedByteLength, signal) {
89921
- const declaredLength = response.headers.get("content-length") ?? "";
89922
- if (!/^(0|[1-9][0-9]*)$/.test(declaredLength) || Number(declaredLength) !== expectedByteLength || response.body === null) {
89923
- throw new PromptArtifactError("artifact_integrity_failed", "The prompt artifact failed integrity validation");
89924
- }
89925
- const reader = response.body.getReader();
89926
- const chunks = [];
89927
- let byteLength = 0;
89928
- try {
89929
- while (true) {
89930
- signal.throwIfAborted();
89931
- const chunk = await reader.read();
89932
- signal.throwIfAborted();
89933
- if (chunk.done) {
89934
- break;
89935
- }
89936
- byteLength += chunk.value.byteLength;
89937
- if (byteLength > expectedByteLength) {
89938
- await reader.cancel("Artifact response exceeded its authorization");
89939
- throw new PromptArtifactError(
89940
- "artifact_integrity_failed",
89941
- "The prompt artifact failed integrity validation"
89942
- );
89943
- }
89944
- chunks.push(chunk.value);
89945
- }
89946
- } finally {
89947
- reader.releaseLock();
89948
- }
89949
- if (byteLength !== expectedByteLength) {
89950
- throw new PromptArtifactError("artifact_integrity_failed", "The prompt artifact failed integrity validation");
89951
- }
89952
- const bytes = new Uint8Array(byteLength);
89953
- let offset = 0;
89954
- for (const chunk of chunks) {
89955
- bytes.set(chunk, offset);
89956
- offset += chunk.byteLength;
89957
- }
89958
- return bytes;
89959
- }
89960
- async function downloadPromptArtifact(config2, artifact, signal) {
89961
- signal.throwIfAborted();
89962
- const timeoutSignal = AbortSignal.timeout(ARTIFACT_DOWNLOAD_TIMEOUT_MILLISECONDS);
89963
- const requestSignal = AbortSignal.any([signal, timeoutSignal]);
89964
- let response;
89965
- try {
89966
- response = await fetch4(downloadUrl(config2, artifact), {
89967
- dispatcher: environmentProxyDispatcher,
89968
- headers: {
89969
- accept: artifact.mimeType,
89970
- "accept-encoding": "identity",
89971
- ...relayAuthorization(config2.relayUrl)
89972
- },
89973
- signal: requestSignal
89974
- });
89975
- if (!response.ok) {
89976
- await response.body?.cancel();
89977
- throw new PromptArtifactError(
89978
- "artifact_download_rejected",
89979
- "The Relay rejected the prompt artifact download"
89980
- );
89981
- }
89982
- if (response.headers.get("content-type") !== artifact.mimeType || response.headers.get("x-mobius-artifact-id") !== artifact.artifactId || response.headers.get("x-mobius-sha256") !== artifact.sha256) {
89983
- await response.body?.cancel();
89984
- throw new PromptArtifactError(
89985
- "artifact_integrity_failed",
89986
- "The prompt artifact failed integrity validation"
89987
- );
89988
- }
89989
- const bytes = await responseBytes(response, artifact.byteLength, requestSignal);
89990
- if (createHash9("sha256").update(bytes).digest("hex") !== artifact.sha256) {
89991
- throw new PromptArtifactError(
89992
- "artifact_integrity_failed",
89993
- "The prompt artifact failed integrity validation"
89994
- );
89995
- }
89996
- return bytes;
89997
- } catch (error62) {
89998
- signal.throwIfAborted();
89999
- if (error62 instanceof PromptArtifactError) {
90000
- throw error62;
90001
- }
90002
- throw new PromptArtifactError(
90003
- timeoutSignal.aborted ? "artifact_download_timeout" : "artifact_download_failed",
90004
- timeoutSignal.aborted ? "The prompt artifact download timed out" : "The prompt artifact could not be downloaded",
90005
- { cause: error62 }
90006
- );
90007
- }
90008
- }
90009
-
90010
90133
  // src/runtime/command/registry/prompt.ts
90011
90134
  var sessionPromptCommand = {
90012
90135
  kind: SESSION_PROMPT_KIND,
@@ -90060,7 +90183,17 @@ var sessionPromptCommand = {
90060
90183
  eventProjection: context.eventProjection,
90061
90184
  command: message,
90062
90185
  resolveAgent: context.resolveAgent,
90063
- downloadArtifact: (artifact, signal) => downloadPromptArtifact(context.config, artifact, signal),
90186
+ readArtifact: async (artifact, signal) => {
90187
+ const bytes = await context.externalContent.read(
90188
+ message.sessionId,
90189
+ `mobius-content:${artifact.kind}:${artifact.contentKey}`,
90190
+ signal
90191
+ );
90192
+ if (bytes === null) {
90193
+ throw new Error("The prompt attachment is unavailable");
90194
+ }
90195
+ return bytes;
90196
+ },
90064
90197
  reportFailure: context.diagnostics.daemonDiagnostic,
90065
90198
  directories: context.directories,
90066
90199
  sessions: context.sessions,
@@ -90142,7 +90275,7 @@ async function prepareSession(command, recovery, closed, config2, resolveAgent,
90142
90275
  };
90143
90276
  const prepared = command.transient === true ? await sessions.inspectSession(preparation, metrics) : recovery.kind === "history-required" ? await sessions.restoreSession(preparation, metrics, closed) : await sessions.prepareSession(preparation, metrics, closed);
90144
90277
  const configuration = command.configuration;
90145
- const configOptions = configuration === void 0 ? prepared.configOptions : await metrics.measure(
90278
+ const snapshot = configuration === void 0 ? prepared : await metrics.measure(
90146
90279
  "configuration",
90147
90280
  async () => await sessions.configureSession({
90148
90281
  ...preparation,
@@ -90160,7 +90293,8 @@ async function prepareSession(command, recovery, closed, config2, resolveAgent,
90160
90293
  agentVersion: prepared.agentVersion,
90161
90294
  capabilities: prepared.capabilities,
90162
90295
  commands: prepared.commands,
90163
- configOptions,
90296
+ configOptions: snapshot.configOptions,
90297
+ configurationRevision: snapshot.configurationRevision,
90164
90298
  directoryId: command.directoryId,
90165
90299
  directoryLabel: resolved.directoryLabel,
90166
90300
  kind: "session.ready",
@@ -90293,11 +90427,12 @@ async function executeSessionConfigure(socket, command, config2, resolveAgent, d
90293
90427
  command.additionalDirectoryIds,
90294
90428
  signal
90295
90429
  );
90296
- const configOptions = await sessions.configureSession({
90430
+ const snapshot = await sessions.configureSession({
90297
90431
  additionalDirectories: resolved.additionalDirectories,
90298
90432
  ...await resolveAgent(command.agentId, signal, command.sessionId),
90299
90433
  booleanValue: command.booleanValue,
90300
90434
  configId: command.configId,
90435
+ ...command.expectedConfigurationRevision === void 0 ? {} : { expectedConfigurationRevision: command.expectedConfigurationRevision },
90301
90436
  cwd: resolved.cwd,
90302
90437
  requestId: command.requestId,
90303
90438
  sessionId: command.sessionId,
@@ -90306,7 +90441,7 @@ async function executeSessionConfigure(socket, command, config2, resolveAgent, d
90306
90441
  valueType: command.valueType
90307
90442
  });
90308
90443
  send(socket, {
90309
- configOptions,
90444
+ ...snapshot,
90310
90445
  kind: "session.config.updated",
90311
90446
  requestId: command.requestId,
90312
90447
  sessionId: command.sessionId
@@ -90426,6 +90561,7 @@ async function executeSessionConfigRead(socket, command, config2, resolveAgent,
90426
90561
  );
90427
90562
  send(socket, {
90428
90563
  configOptions: ready.configOptions,
90564
+ configurationRevision: ready.configurationRevision,
90429
90565
  kind: SESSION_CONFIG_READ_RESULT_KIND,
90430
90566
  requestId: command.requestId,
90431
90567
  sessionId: command.sessionId
@@ -90465,6 +90601,7 @@ var sessionConfigReadCommand = {
90465
90601
 
90466
90602
  // src/runtime/command/registry/index.ts
90467
90603
  function createCommandRegistry(context) {
90604
+ const contentWrites = createContentWriteCommands(context);
90468
90605
  const commands = [
90469
90606
  runtimeDiagnosticsCommand,
90470
90607
  runtimePingCommand,
@@ -90473,6 +90610,7 @@ function createCommandRegistry(context) {
90473
90610
  ...createNativeSessionCommands(context),
90474
90611
  ...createAgentCatalogCommands(context),
90475
90612
  ...createAgentSettingsCommands(context),
90613
+ ...contentWrites.commands,
90476
90614
  fileReadCommand,
90477
90615
  sessionCancelCommand,
90478
90616
  sessionCloseCommand,
@@ -90495,8 +90633,12 @@ function createCommandRegistry(context) {
90495
90633
  }
90496
90634
  command.handle(context, message);
90497
90635
  },
90636
+ dispatchChunk: (message) => contentWrites.receiveChunk(decodeContentChunkFrame(message)),
90498
90637
  settle: async () => {
90499
- await Promise.all(commands.flatMap((command) => command.settle === void 0 ? [] : [command.settle()]));
90638
+ await Promise.all([
90639
+ contentWrites.settle(),
90640
+ ...commands.flatMap((command) => command.settle === void 0 ? [] : [command.settle()])
90641
+ ]);
90500
90642
  }
90501
90643
  };
90502
90644
  }
@@ -90558,6 +90700,7 @@ function connectRelay(url2, signal) {
90558
90700
  dispatcher,
90559
90701
  headers
90560
90702
  });
90703
+ socket.binaryType = "arraybuffer";
90561
90704
  let released = false;
90562
90705
  const release = (reason) => {
90563
90706
  if (released) {
@@ -90921,7 +91064,16 @@ async function runRelayConnection({
90921
91064
  const handleMessage = (event) => {
90922
91065
  const encoded = event.data;
90923
91066
  if (typeof encoded !== "string") {
90924
- failAndClose(new Error("Relay sent a non-text message"), 4003, "Text message required", "protocol");
91067
+ try {
91068
+ commands.dispatchChunk(encoded);
91069
+ } catch (error62) {
91070
+ failAndClose(
91071
+ new Error("Relay sent an invalid content chunk", { cause: error62 }),
91072
+ 4008,
91073
+ "Invalid content chunk",
91074
+ "protocol"
91075
+ );
91076
+ }
90925
91077
  return;
90926
91078
  }
90927
91079
  try {