@joe-broadhead/messaging-bin-win32-x64 0.6.0 → 0.7.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.
@@ -5129,12 +5129,6 @@ function _ensureDefaultMemoizer() {
5129
5129
  function string2(params) {
5130
5130
  return _string(ZodString, params);
5131
5131
  }
5132
- function ipv42(params) {
5133
- return _ipv4(ZodIPv4, params);
5134
- }
5135
- function ipv62(params) {
5136
- return _ipv6(ZodIPv6, params);
5137
- }
5138
5132
  function number2(params) {
5139
5133
  return _number(ZodNumber, params);
5140
5134
  }
@@ -6122,12 +6116,11 @@ var init_common = __esm(() => {
6122
6116
  card: `messaging/1/card
6123
6117
  `,
6124
6118
  seal: "messaging/1/seal",
6125
- directAuth: `messaging/1/direct-auth
6126
- `,
6127
- directLink: "messaging/1/direct-link",
6128
6119
  person: `messaging/1/person
6129
6120
  `,
6130
6121
  tombstone: "messaging/1/tombstone",
6122
+ dirRead: `messaging/1/dir-read
6123
+ `,
6131
6124
  deviceLog: `messaging/1/device-log
6132
6125
  `,
6133
6126
  deviceKeys: `messaging/1/device-keys
@@ -6163,7 +6156,7 @@ var init_common = __esm(() => {
6163
6156
  Millis = number2().int().nonnegative();
6164
6157
  Address = string2().min(1).max(200);
6165
6158
  SessionAddress = string2().regex(/^(?:[a-z][a-z0-9-]{0,31}|p_[a-z2-7]{26})\/[a-z0-9][a-z0-9._-]{0,63}@[a-z0-9][a-z0-9-]{0,31}$/, "person/session@label");
6166
- MemberRole = _enum(["admin", "member", "service"]);
6159
+ MemberRole = _enum(["admin", "member"]);
6167
6160
  SessionState = _enum(["idle", "busy"]);
6168
6161
  DeliveryTier = _enum(["push", "pull"]);
6169
6162
  DeliveryStage = _enum([
@@ -6404,7 +6397,7 @@ var init_jelly = __esm(() => {
6404
6397
  });
6405
6398
 
6406
6399
  // packages/core/dist/schema/directory.js
6407
- var MapProof, MapValue, DeviceId, DeviceKind, UnbindReason, Signature, LinkRequest, Authority, LogEntry, KeysStatement, EpochLeaf, EpochProof, STH, KeyRotation, EntryEpoch, PersonView;
6400
+ var MapProof, MapValue, DeviceId, DeviceKind, UnbindReason, Signature, LinkRequest, Authority, LogEntry, KeysStatement, EpochLeaf, EpochProof, STH, KeyRotation, EntryEpoch, PersonHead, PersonView;
6408
6401
  var init_directory = __esm(() => {
6409
6402
  init_zod();
6410
6403
  init_common();
@@ -6499,6 +6492,12 @@ var init_directory = __esm(() => {
6499
6492
  value: MapValue,
6500
6493
  mapProof: MapProof
6501
6494
  });
6495
+ PersonHead = strictObject({
6496
+ person: HostedPersonId,
6497
+ mapProof: MapProof,
6498
+ value: MapValue.optional(),
6499
+ tombstone: literal(true).optional()
6500
+ });
6502
6501
  PersonView = strictObject({
6503
6502
  person: HostedPersonId,
6504
6503
  log: array(LogEntry).max(1024),
@@ -6510,7 +6509,7 @@ var init_directory = __esm(() => {
6510
6509
  });
6511
6510
 
6512
6511
  // packages/core/dist/schema/a2a.js
6513
- var Struct, TaskState, TERMINAL_STATES, Role, Part, Message, TaskStatus, Artifact, Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent, AgentInterface, AgentExtension, AgentCapabilities, AgentSkill, AgentCard, SendMessageConfiguration, SendMessageRequest, SendMessageResponse, StreamResponse, GetTaskRequest, CancelTaskRequest, SubscribeToTaskRequest, ListTasksRequest, ListTasksResponse;
6512
+ var Struct, TaskState, TERMINAL_STATES, Role, Part, Message, TaskStatus, Artifact, Task, TaskStatusUpdateEvent, TaskArtifactUpdateEvent;
6514
6513
  var init_a2a = __esm(() => {
6515
6514
  init_zod();
6516
6515
  Struct = record(string2(), unknown());
@@ -6587,92 +6586,6 @@ var init_a2a = __esm(() => {
6587
6586
  lastChunk: boolean2().optional(),
6588
6587
  metadata: Struct.optional()
6589
6588
  });
6590
- AgentInterface = object({
6591
- url: string2().min(1),
6592
- protocolBinding: string2().min(1).describe("JSONRPC | GRPC | HTTP+JSON | a binding URI"),
6593
- tenant: string2().optional(),
6594
- protocolVersion: string2().min(1)
6595
- });
6596
- AgentExtension = object({
6597
- uri: string2(),
6598
- description: string2().optional(),
6599
- required: boolean2().optional(),
6600
- params: Struct.optional()
6601
- });
6602
- AgentCapabilities = object({
6603
- streaming: boolean2().optional(),
6604
- pushNotifications: boolean2().optional(),
6605
- extensions: array(AgentExtension).optional(),
6606
- extendedAgentCard: boolean2().optional()
6607
- });
6608
- AgentSkill = object({
6609
- id: string2().min(1),
6610
- name: string2().min(1),
6611
- description: string2(),
6612
- tags: array(string2()),
6613
- examples: array(string2()).optional(),
6614
- inputModes: array(string2()).optional(),
6615
- outputModes: array(string2()).optional(),
6616
- securityRequirements: array(unknown()).optional()
6617
- });
6618
- AgentCard = object({
6619
- name: string2().min(1),
6620
- description: string2(),
6621
- supportedInterfaces: array(AgentInterface).min(1),
6622
- provider: object({ url: string2(), organization: string2() }).optional(),
6623
- version: string2(),
6624
- documentationUrl: string2().optional(),
6625
- capabilities: AgentCapabilities,
6626
- securitySchemes: record(string2(), unknown()).optional(),
6627
- securityRequirements: array(unknown()).optional(),
6628
- defaultInputModes: array(string2()),
6629
- defaultOutputModes: array(string2()),
6630
- skills: array(AgentSkill),
6631
- signatures: array(object({ protected: string2(), signature: string2(), header: Struct.optional() })).optional(),
6632
- iconUrl: string2().optional()
6633
- });
6634
- SendMessageConfiguration = object({
6635
- acceptedOutputModes: array(string2()).optional(),
6636
- taskPushNotificationConfig: unknown().optional(),
6637
- historyLength: number2().int().nonnegative().optional(),
6638
- returnImmediately: boolean2().optional()
6639
- });
6640
- SendMessageRequest = object({
6641
- tenant: string2().optional(),
6642
- message: Message,
6643
- configuration: SendMessageConfiguration.optional(),
6644
- metadata: Struct.optional()
6645
- });
6646
- SendMessageResponse = union([object({ task: Task }), object({ message: Message })]);
6647
- StreamResponse = union([
6648
- object({ task: Task }),
6649
- object({ message: Message }),
6650
- object({ statusUpdate: TaskStatusUpdateEvent }),
6651
- object({ artifactUpdate: TaskArtifactUpdateEvent })
6652
- ]);
6653
- GetTaskRequest = object({
6654
- tenant: string2().optional(),
6655
- id: string2().min(1),
6656
- historyLength: number2().int().nonnegative().optional()
6657
- });
6658
- CancelTaskRequest = object({ tenant: string2().optional(), id: string2().min(1), metadata: Struct.optional() });
6659
- SubscribeToTaskRequest = object({ tenant: string2().optional(), id: string2().min(1) });
6660
- ListTasksRequest = object({
6661
- tenant: string2().optional(),
6662
- contextId: string2().optional(),
6663
- status: TaskState.or(literal("TASK_STATE_UNSPECIFIED")).optional(),
6664
- pageSize: number2().int().min(1).max(100).optional(),
6665
- pageToken: string2().optional(),
6666
- historyLength: number2().int().nonnegative().optional(),
6667
- statusTimestampAfter: string2().optional(),
6668
- includeArtifacts: boolean2().optional()
6669
- });
6670
- ListTasksResponse = object({
6671
- tasks: array(Task),
6672
- nextPageToken: string2(),
6673
- pageSize: number2().int(),
6674
- totalSize: number2().int()
6675
- });
6676
6589
  });
6677
6590
 
6678
6591
  // packages/core/dist/schema/consent.js
@@ -6760,13 +6673,12 @@ var init_consent = __esm(() => {
6760
6673
  });
6761
6674
 
6762
6675
  // packages/core/dist/schema/delegation.js
6763
- var DelegationScope, ExternalPrincipal, DelegationGrant, DelegationChain, DelegationSummary;
6676
+ var DelegationScope, DelegationGrant, DelegationChain, DelegationSummary;
6764
6677
  var init_delegation = __esm(() => {
6765
6678
  init_zod();
6766
6679
  init_common();
6767
6680
  init_realm();
6768
6681
  DelegationScope = string2().regex(/^[a-z0-9][a-z0-9._:/-]{0,63}$/, "scope label");
6769
- ExternalPrincipal = string2().min(1).max(64);
6770
6682
  DelegationGrant = looseObject({
6771
6683
  realm: RealmId,
6772
6684
  task: Ulid,
@@ -6778,7 +6690,6 @@ var init_delegation = __esm(() => {
6778
6690
  maxDepth: number2().int().min(1).max(LIMITS.delegationDepth),
6779
6691
  scope: array(DelegationScope).max(LIMITS.delegationScopes).optional(),
6780
6692
  parent: Hash.optional(),
6781
- external: looseObject({ principal: ExternalPrincipal }).optional(),
6782
6693
  sig: Base64Url
6783
6694
  });
6784
6695
  DelegationChain = array(DelegationGrant).min(1).max(LIMITS.delegationDepth);
@@ -6787,7 +6698,6 @@ var init_delegation = __esm(() => {
6787
6698
  depth: number2().int().min(1).max(LIMITS.delegationDepth),
6788
6699
  maxDepth: number2().int().min(1).max(LIMITS.delegationDepth),
6789
6700
  scope: array(DelegationScope).max(LIMITS.delegationScopes).optional(),
6790
- external: object({ principal: ExternalPrincipal, gateway: MachineId }).optional(),
6791
6701
  exp: Millis
6792
6702
  });
6793
6703
  });
@@ -6800,7 +6710,7 @@ function requestMessage(message) {
6800
6710
  function cardMessage(message, what) {
6801
6711
  return bare(message, "data") && (what === "card" ? CardData : CardGetData).safeParse(message.parts[0].data).success;
6802
6712
  }
6803
- var EnvelopeType, SEAL_ALG = "x25519-hkdf-sha256-a256gcm", EnvelopeClass, Party, Sender, Recipient, Envelope, ChannelPersons, ChannelConv, SelfConv, RequestConv, CardConv, Conv, MessagePayload, BARE_MESSAGE, bare = (message, part) => Object.keys(message).every((key) => BARE_MESSAGE.has(key)) && message.parts.length === 1 && Object.keys(message.parts[0]).join() === part, TaskStatusPayload, TaskArtifactPayload, TaskCancelPayload, ReceiptPayload, SessionInfo, DirectEndpoint, PresencePayload, PAYLOAD_BY_TYPE, Provenance;
6713
+ var EnvelopeType, SEAL_ALG = "x25519-hkdf-sha256-a256gcm", EnvelopeClass, Party, Sender, Recipient, Envelope, ChannelPersons, ChannelConv, SelfConv, RequestConv, CardConv, Conv, MessagePayload, BARE_MESSAGE, bare = (message, part) => Object.keys(message).every((key) => BARE_MESSAGE.has(key)) && message.parts.length === 1 && Object.keys(message.parts[0]).join() === part, TaskStatusPayload, TaskArtifactPayload, TaskCancelPayload, ReceiptPayload, SessionInfo, PresencePayload, PAYLOAD_BY_TYPE, Provenance;
6804
6714
  var init_envelope = __esm(() => {
6805
6715
  init_zod();
6806
6716
  init_a2a();
@@ -6855,7 +6765,6 @@ var init_envelope = __esm(() => {
6855
6765
  kind: _enum(["message", "task"]),
6856
6766
  message: Message,
6857
6767
  wake: boolean2().optional(),
6858
- external: object({ principal: string2().min(1).max(64) }).optional(),
6859
6768
  delegation: DelegationChain.optional(),
6860
6769
  conv: Conv.optional()
6861
6770
  }).refine((p) => p.kind === "task" ? p.message.taskId !== undefined : true, { message: "a task message needs message.taskId" }).refine((p) => p.kind === "task" === (p.delegation !== undefined), { message: "a task request carries a delegation chain, and only a task request" }).refine((p) => p.conv?.kind !== "self" || p.message.parts.length === 1 && GrantSync.safeParse(p.message.parts[0].data).success, {
@@ -6892,16 +6801,11 @@ var init_envelope = __esm(() => {
6892
6801
  kept: literal(true).optional(),
6893
6802
  wake: literal(true).optional()
6894
6803
  });
6895
- DirectEndpoint = object({
6896
- host: union([ipv42(), ipv62()]),
6897
- port: number2().int().min(1).max(65535)
6898
- });
6899
6804
  PresencePayload = object({
6900
6805
  seq: number2().int().nonnegative(),
6901
6806
  label: MachineLabel,
6902
6807
  sessions: array(SessionInfo).max(LIMITS.presenceSessions),
6903
6808
  features: array(string2().max(64)).max(32).optional(),
6904
- direct: array(DirectEndpoint).min(1).max(4).optional(),
6905
6809
  away: literal(true).optional()
6906
6810
  });
6907
6811
  PAYLOAD_BY_TYPE = {
@@ -6920,8 +6824,7 @@ var init_envelope = __esm(() => {
6920
6824
  device: MachineId,
6921
6825
  label: MachineLabel,
6922
6826
  verified: literal("signature"),
6923
- receivedAt: Millis,
6924
- external: object({ principal: string2().max(64), gateway: MachineId }).optional()
6827
+ receivedAt: Millis
6925
6828
  });
6926
6829
  });
6927
6830
 
@@ -6974,7 +6877,6 @@ var init_roster = __esm(() => {
6974
6877
  inviteId: Ulid,
6975
6878
  invitePub: Key32,
6976
6879
  person: PersonName,
6977
- role: _enum(["member", "service"]),
6978
6880
  expires: Millis
6979
6881
  })
6980
6882
  });
@@ -7011,7 +6913,6 @@ var init_roster = __esm(() => {
7011
6913
  inviteId: Ulid,
7012
6914
  secret: Key32,
7013
6915
  person: PersonName,
7014
- role: _enum(["member", "service"]),
7015
6916
  expires: Millis
7016
6917
  });
7017
6918
  });
@@ -7096,6 +6997,7 @@ var init_relay = __esm(() => {
7096
6997
  protocolError: 4002,
7097
6998
  removed: 4003,
7098
6999
  replaced: 4004,
7000
+ full: 1013,
7099
7001
  tooLarge: 1009
7100
7002
  };
7101
7003
  });
@@ -7104,7 +7006,10 @@ var init_relay = __esm(() => {
7104
7006
  function hostedRelayAuthPayload(args) {
7105
7007
  return { proto: RELAY_PROTOCOL, relay: args.relay, realm: args.realm, person: args.person, device: args.device, nonce: args.nonce, ts: args.ts };
7106
7008
  }
7107
- var Req2, Size, Handle, Email, OrgId, DirBindFrame, HostedHelloFrame, DirAppendFrame, DirKeysFrame, DirSthFrame, DirLookupFrame, DirDeviceFrame, DirResolveFrame, DirSearchFrame, DirWatchFrame, PresenceWatchFrame, DirEraseFrame, WAKE_AGENTS = 64, WAKE_AWAITING = 500, WAKE_TICKET_BYTES = 4096, WAKE_TICKET_MAX_S, WakeUrl, WakeArmFrame, WakeDisarmFrame, DirectoryClientFrame, HostedWelcomeFrame, DirOkFrame, DirSthIsFrame, DirPersonsFrame, ResolvedPerson, DirResolvedFrame, DirChangedFrame, DirUnboundFrame, DirectoryRelayFrame;
7009
+ function dirReadPayload(args) {
7010
+ return { proto: RELAY_PROTOCOL, relay: args.relay, realm: args.realm, device: args.device, nonce: args.nonce };
7011
+ }
7012
+ var Req2, Size, Handle, Email, OrgId, DirReadProof, DirBindFrame, HostedHelloFrame, DirAppendFrame, DirKeysFrame, DirSthFrame, DirLookupFrame, DirHeadsFrame, DirDeviceFrame, DirResolveFrame, DirSearchFrame, DirWatchFrame, PresenceWatchFrame, DirEraseFrame, WAKE_AGENTS = 64, WAKE_AWAITING = 500, WAKE_TICKET_BYTES = 4096, WAKE_TICKET_MAX_S, WakeUrl, WakeArmFrame, WakeDisarmFrame, DirectoryClientFrame, HostedWelcomeFrame, DirOkFrame, DirSthIsFrame, DirPersonsFrame, DirHeadsAreFrame, ResolvedPerson, DirResolvedFrame, DirChangedFrame, DirUnboundFrame, DirectoryRelayFrame;
7108
7013
  var init_directory_frames = __esm(() => {
7109
7014
  init_zod();
7110
7015
  init_common();
@@ -7117,6 +7022,7 @@ var init_directory_frames = __esm(() => {
7117
7022
  Handle = string2().regex(/^[a-z][a-z0-9-]{0,31}$/, "lowercase handle");
7118
7023
  Email = string2().max(254).regex(/^[^\s@A-Z]{1,64}@(?:[a-z0-9-]+\.)+[a-z0-9-]+$/, "lower-case email address");
7119
7024
  OrgId = string2().min(1).max(200);
7025
+ DirReadProof = strictObject({ device: DeviceId, sig: Base64Url });
7120
7026
  DirBindFrame = object({ t: literal("dir.bind"), req: Req2, entry: LogEntry });
7121
7027
  HostedHelloFrame = object({
7122
7028
  t: literal("hello"),
@@ -7139,6 +7045,13 @@ var init_directory_frames = __esm(() => {
7139
7045
  t: literal("dir.lookup"),
7140
7046
  req: Req2,
7141
7047
  persons: array(object({ person: HostedPersonId, knownSeq: number2().int().nonnegative().optional() })).min(1).max(LIMITS.dirLookupPersons),
7048
+ size: Size.optional(),
7049
+ proof: DirReadProof.optional()
7050
+ });
7051
+ DirHeadsFrame = object({
7052
+ t: literal("dir.heads"),
7053
+ req: Req2,
7054
+ persons: array(HostedPersonId).min(1).max(LIMITS.dirLookupPersons),
7142
7055
  size: Size.optional()
7143
7056
  });
7144
7057
  DirDeviceFrame = object({
@@ -7188,6 +7101,7 @@ var init_directory_frames = __esm(() => {
7188
7101
  DirSthFrame,
7189
7102
  DirLookupFrame,
7190
7103
  DirDeviceFrame,
7104
+ DirHeadsFrame,
7191
7105
  DirResolveFrame,
7192
7106
  DirSearchFrame,
7193
7107
  DirWatchFrame,
@@ -7220,6 +7134,13 @@ var init_directory_frames = __esm(() => {
7220
7134
  head: EpochProof,
7221
7135
  persons: array(PersonView).max(LIMITS.dirLookupPersons)
7222
7136
  });
7137
+ DirHeadsAreFrame = object({
7138
+ t: literal("dir.headsAre"),
7139
+ req: Req2,
7140
+ sth: STH,
7141
+ head: EpochProof,
7142
+ persons: array(PersonHead).max(LIMITS.dirLookupPersons)
7143
+ });
7223
7144
  ResolvedPerson = object({
7224
7145
  person: HostedPersonId,
7225
7146
  handle: Handle,
@@ -7243,38 +7164,13 @@ var init_directory_frames = __esm(() => {
7243
7164
  DirOkFrame,
7244
7165
  DirSthIsFrame,
7245
7166
  DirPersonsFrame,
7167
+ DirHeadsAreFrame,
7246
7168
  DirResolvedFrame,
7247
7169
  DirChangedFrame,
7248
7170
  DirUnboundFrame
7249
7171
  ]);
7250
7172
  });
7251
7173
 
7252
- // packages/core/dist/schema/direct.js
7253
- var DIRECT_PROTOCOL = "messaging-direct/1", DIRECT_HANDSHAKE_BYTES = 4096, DirectHalf, DirectHelloFrame, DirectAcceptFrame, DirectAuthFrame, DirectIdentity, DirectSendRecord, DirectAckRecord, DirectNackRecord, DirectRecord;
7254
- var init_direct = __esm(() => {
7255
- init_zod();
7256
- init_common();
7257
- init_errors3();
7258
- DirectHalf = object({ epk: Key32, nonce: Key32 });
7259
- DirectHelloFrame = object({ t: literal("hello"), proto: literal(DIRECT_PROTOCOL), epk: Key32, nonce: Key32 });
7260
- DirectAcceptFrame = object({ t: literal("accept"), epk: Key32, nonce: Key32, box: Base64Url.max(1024) });
7261
- DirectAuthFrame = object({ t: literal("auth"), box: Base64Url.max(1024) });
7262
- DirectIdentity = object({ team: TeamId, machine: MachineId, sig: Base64Url });
7263
- DirectSendRecord = object({
7264
- t: literal("send"),
7265
- env: looseObject({
7266
- v: string2(),
7267
- id: Ulid,
7268
- type: string2(),
7269
- from: looseObject({ device: MachineId }),
7270
- to: looseObject({ device: MachineId })
7271
- })
7272
- });
7273
- DirectAckRecord = object({ t: literal("ack"), id: Ulid });
7274
- DirectNackRecord = object({ t: literal("nack"), id: Ulid, code: ErrorCode });
7275
- DirectRecord = discriminatedUnion("t", [DirectSendRecord, DirectAckRecord, DirectNackRecord]);
7276
- });
7277
-
7278
7174
  // packages/core/dist/schema/ipc/device.js
7279
7175
  var BindingState, RealmAlarmSummary, DeviceStatus, DeviceChangeView, DeviceListEntry, LinkText, Sas, LinkPreview, KeyFingerprint, LoginUserCode, LoginPurpose, LoginKind, LoginShown, LoginState, DeviceRemoveParams, DeviceApproveParams, IdentityReplaceParams, SignInStep, DirectoryEraseParams, DEVICE_METHODS;
7280
7176
  var init_device = __esm(() => {
@@ -7327,7 +7223,6 @@ var init_device = __esm(() => {
7327
7223
  LoginPurpose = _enum(["bind", "remove", "replace", "erase"]);
7328
7224
  LoginKind = _enum(["laptop", "server", "other"]);
7329
7225
  LoginShown = object({
7330
- verificationUriComplete: string2().url().max(2048),
7331
7226
  verificationUri: string2().url().max(2048),
7332
7227
  userCode: LoginUserCode,
7333
7228
  fingerprint: KeyFingerprint,
@@ -7686,7 +7581,6 @@ var init_surfaces = __esm(() => {
7686
7581
  referenceTaskIds: array(Ulid).max(16).optional(),
7687
7582
  wake: boolean2().optional().describe("hint that the recipient may wake an idle session; default true"),
7688
7583
  request: strictObject({ contact: Email }).optional(),
7689
- external: strictObject({ principal: string2().min(1).max(64) }).optional(),
7690
7584
  onBehalfOf: strictObject({
7691
7585
  taskId: Ulid.describe("a task this session executes"),
7692
7586
  scope: array(DelegationScope).max(LIMITS.delegationScopes).optional().describe("narrow the scope; never wider than the task's"),
@@ -7921,7 +7815,6 @@ var init_ipc = __esm(() => {
7921
7815
  InviteSummary = object({
7922
7816
  inviteId: Ulid,
7923
7817
  person: PersonName,
7924
- role: _enum(["member", "service"]),
7925
7818
  expires: Millis,
7926
7819
  state: _enum(["open", "redeemed", "revoked", "expired"])
7927
7820
  });
@@ -7932,7 +7825,7 @@ var init_ipc = __esm(() => {
7932
7825
  "session.register": {
7933
7826
  params: strictObject({
7934
7827
  key: SessionKey,
7935
- agent: AgentKind.describe('client kind, e.g. "opencode", "claude-code", "a2a-gateway"'),
7828
+ agent: AgentKind.describe('client kind, e.g. "opencode", "claude-code"'),
7936
7829
  name: SessionName.optional().describe("requested name; the daemon may suffix it to keep it unique"),
7937
7830
  cwd: string2().max(1024).optional(),
7938
7831
  repo: string2().max(200).optional(),
@@ -8012,7 +7905,7 @@ var init_ipc = __esm(() => {
8012
7905
  result: object({ team: TeamId, name: TeamName, machine: MachineId })
8013
7906
  },
8014
7907
  "team.invite": {
8015
- params: strictObject({ person: PersonName, role: _enum(["member", "service"]).optional(), ttlMs: number2().int().min(60000).max(LIMITS.ttlMs).optional() }),
7908
+ params: strictObject({ person: PersonName, ttlMs: number2().int().min(60000).max(LIMITS.ttlMs).optional() }),
8016
7909
  result: object({ ticket: string2(), inviteId: Ulid, expires: Millis })
8017
7910
  },
8018
7911
  "team.join": {
@@ -8036,8 +7929,7 @@ var init_ipc = __esm(() => {
8036
7929
  machine: MachineId,
8037
7930
  enc: EncKey,
8038
7931
  person: PersonName,
8039
- label: MachineLabel,
8040
- role: _enum(["member", "service"]).optional()
7932
+ label: MachineLabel
8041
7933
  }),
8042
7934
  result: Ok
8043
7935
  },
@@ -8203,7 +8095,6 @@ var init_schema = __esm(() => {
8203
8095
  init_roster();
8204
8096
  init_relay();
8205
8097
  init_directory_frames();
8206
- init_direct();
8207
8098
  init_ipc();
8208
8099
  init_device();
8209
8100
  init_consent2();
@@ -8234,7 +8125,7 @@ var init_b64u = __esm(() => {
8234
8125
  });
8235
8126
 
8236
8127
  // packages/core/dist/version.js
8237
- var VERSION = "0.6.0";
8128
+ var VERSION = "0.7.0";
8238
8129
 
8239
8130
  // packages/core/dist/jcs.js
8240
8131
  function hasLoneSurrogate(text) {
@@ -8953,7 +8844,7 @@ function assertMeaning(type, from, to, klass, payload) {
8953
8844
  throw new MessagingError("invalid_request", "a self sync goes between devices of one person");
8954
8845
  if (p.conv?.kind === "card" && (from?.person === to.person || from?.agent !== undefined))
8955
8846
  throw new MessagingError("invalid_request", "a card goes from a person to another person");
8956
- const line = p.kind === "message" && p.external === undefined && p.message.taskId === undefined && Ulid.safeParse(p.message.messageId).success && (p.message.contextId === undefined || Ulid.safeParse(p.message.contextId).success);
8847
+ const line = p.kind === "message" && p.message.taskId === undefined && Ulid.safeParse(p.message.messageId).success && (p.message.contextId === undefined || Ulid.safeParse(p.message.contextId).success);
8957
8848
  if (!line)
8958
8849
  throw new MessagingError("invalid_request", "a chat line is kind message, outside any task, with ULID messageId and contextId");
8959
8850
  }
@@ -9265,7 +9156,7 @@ class Roster {
9265
9156
  if (!verify(invite.invitePub, DOMAIN.invite, redeemClaim(this.team, inviteId, member), proof)) {
9266
9157
  throw new MessagingError("invite_invalid", "invite proof does not verify for this member");
9267
9158
  }
9268
- if (member.person !== invite.person || member.role !== invite.role) {
9159
+ if (member.person !== invite.person || member.role !== "member") {
9269
9160
  throw new MessagingError("invite_invalid", "member does not match the invite");
9270
9161
  }
9271
9162
  this.#requireNew(member);
@@ -9401,14 +9292,12 @@ function createInvite(roster, identity, args) {
9401
9292
  inviteId,
9402
9293
  secret,
9403
9294
  person: args.person,
9404
- role: args.role,
9405
9295
  expires
9406
9296
  });
9407
9297
  const entry = createEntry(roster, identity, "invite.create", {
9408
9298
  inviteId,
9409
9299
  invitePub: rawPublicKey(seedKey(secret)),
9410
9300
  person: args.person,
9411
- role: args.role,
9412
9301
  expires
9413
9302
  }, now);
9414
9303
  return { entry, ticket, secret };
@@ -9430,7 +9319,7 @@ function decodeTicket(text) {
9430
9319
  function createRedeem(roster, ticket, identity, args) {
9431
9320
  if (ticket.team !== roster.team)
9432
9321
  throw new MessagingError("invite_invalid", "ticket is for another team");
9433
- const member = { machine: identity.machine, enc: identity.enc, person: ticket.person, role: ticket.role, label: args.label };
9322
+ const member = { machine: identity.machine, enc: identity.enc, person: ticket.person, role: "member", label: args.label };
9434
9323
  const proof = signWith(seedKey(ticket.secret), DOMAIN.invite, redeemClaim(roster.team, ticket.inviteId, member));
9435
9324
  return createEntry(roster, identity, "invite.redeem", { inviteId: ticket.inviteId, proof, member }, args.now);
9436
9325
  }
@@ -10256,6 +10145,8 @@ function page(url, issuer) {
10256
10145
  }
10257
10146
  if (at.origin !== new URL(issuer).origin)
10258
10147
  throw new MessagingError("jelly_unavailable", "Jelly ID's sign-in page is not on its own origin");
10148
+ if (at.search !== "" || at.hash !== "" || at.username !== "" || at.password !== "")
10149
+ throw new MessagingError("jelly_unavailable", "Jelly ID's sign-in page carries a query or credentials");
10259
10150
  return at.href;
10260
10151
  }
10261
10152
  function readDeviceCodeAnswer(status, text, issuer) {
@@ -10264,9 +10155,7 @@ function readDeviceCodeAnswer(status, text, issuer) {
10264
10155
  if (!parsed.success)
10265
10156
  throw new MessagingError("jelly_unavailable", "Jelly ID's sign-in code could not be read");
10266
10157
  const a = parsed.data;
10267
- const complete = page(a.verification_uri_complete, issuer);
10268
- const plain = page(a.verification_uri, issuer);
10269
- return { deviceCode: a.device_code, userCode: a.user_code, verificationUri: plain, verificationUriComplete: complete, expiresInS: a.expires_in, intervalS: a.interval ?? LOGIN_INTERVAL_DEFAULT_S };
10158
+ return { deviceCode: a.device_code, userCode: a.user_code, verificationUri: page(a.verification_uri, issuer), expiresInS: a.expires_in, intervalS: a.interval };
10270
10159
  }
10271
10160
  const answer = errorOf(text);
10272
10161
  switch (answer?.error) {
@@ -10306,8 +10195,6 @@ function readDevicePoll(status, text) {
10306
10195
  case "invalid_client":
10307
10196
  case "unsupported_grant_type":
10308
10197
  throw unsupported("it refused this device client");
10309
- case "invalid_request":
10310
- throw new MessagingError("invalid_request", "Jelly ID refused the poll as malformed");
10311
10198
  default:
10312
10199
  return { kind: "retry" };
10313
10200
  }
@@ -10325,7 +10212,7 @@ function deviceCodeForm(args) {
10325
10212
  function devicePollForm(args) {
10326
10213
  return new URLSearchParams({ grant_type: DEVICE_GRANT_TYPE, device_code: args.deviceCode, client_id: args.clientId });
10327
10214
  }
10328
- var DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code", LOGIN_SLOW_DOWN_S = 5, LOGIN_INTERVAL_DEFAULT_S = 5, LOGIN_INTERVAL_MAX_S = 60, JELLY_ANSWER_BYTES = 65536, ResourceName, Metadata, unsupported = (why) => new MessagingError("login_unsupported", `Jelly ID does not offer device sign-in yet: ${why}`), CodeAnswer, ErrorAnswer, TokenAnswer;
10215
+ var DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code", LOGIN_SLOW_DOWN_S = 5, LOGIN_INTERVAL_MAX_S = 60, JELLY_ANSWER_BYTES = 65536, ResourceName, Metadata, unsupported = (why) => new MessagingError("login_unsupported", `Jelly ID does not offer device sign-in yet: ${why}`), CodeAnswer, ErrorAnswer, TokenAnswer;
10329
10216
  var init_jelly_device = __esm(() => {
10330
10217
  init_b64u();
10331
10218
  init_keys();
@@ -10340,12 +10227,11 @@ var init_jelly_device = __esm(() => {
10340
10227
  jelly_resource_device_token_endpoint: string2().max(2048).optional()
10341
10228
  });
10342
10229
  CodeAnswer = looseObject({
10343
- device_code: string2().min(1).max(512),
10230
+ device_code: string2().min(22).max(256),
10344
10231
  user_code: string2().regex(/^[A-HJ-NP-Z2-9]{5}-[A-HJ-NP-Z2-9]{5}$/),
10345
10232
  verification_uri: string2().max(2048),
10346
- verification_uri_complete: string2().max(2048),
10347
10233
  expires_in: number2().int().positive().max(3600),
10348
- interval: number2().int().positive().max(LOGIN_INTERVAL_MAX_S).optional()
10234
+ interval: number2().int().positive().max(LOGIN_INTERVAL_MAX_S)
10349
10235
  });
10350
10236
  ErrorAnswer = looseObject({ error: string2().max(64), detail: string2().max(64).optional() });
10351
10237
  TokenAnswer = looseObject({ access_token: string2().min(1).max(8192), token_type: string2().max(32) });
@@ -10744,6 +10630,31 @@ function verifyPersonView(view, context) {
10744
10630
  throw asMessagingError(error, "person view");
10745
10631
  }
10746
10632
  }
10633
+ function checkPersonSuccession(earlier, later) {
10634
+ if (earlier.person !== later.person)
10635
+ throw new MessagingError("invalid_request", "the two states are of two persons");
10636
+ if (earlier.erased || later.erased)
10637
+ return;
10638
+ const ours = earlier.log.entries();
10639
+ const theirs = later.log.entries();
10640
+ if (theirs.length < ours.length)
10641
+ throw new DirectoryInconsistency("a later tree head serves a shorter log", { kind: "rollback", what: "log", ours: ours.length, theirs: theirs.length });
10642
+ for (let seq = 0;seq < ours.length; seq++) {
10643
+ if (logEntryHash(ours[seq]) !== logEntryHash(theirs[seq]))
10644
+ throw new DirectoryInconsistency("the log differs from the one held", { kind: "fork", seq, ours: ours[seq], theirs: theirs[seq] });
10645
+ }
10646
+ for (const [device, statement] of earlier.keys) {
10647
+ if (!later.log.device(device)?.current)
10648
+ continue;
10649
+ const next = later.keys.get(device);
10650
+ if (!next)
10651
+ throw new DirectoryInconsistency("a held keys statement is withheld", { kind: "rollback", what: "keys", ours: statement.seq, theirs: -1 });
10652
+ if (next.seq < statement.seq)
10653
+ throw new DirectoryInconsistency("a keys statement is older than the one held", { kind: "rollback", what: "keys", ours: statement.seq, theirs: next.seq });
10654
+ if (next.seq === statement.seq && keysStatementHash(next) !== keysStatementHash(statement))
10655
+ throw fail("two keys statements share a device and seq");
10656
+ }
10657
+ }
10747
10658
  function checkWitness(witness, seq, deadline, log, person, sth) {
10748
10659
  const epoch = verifyEpochProof(sth, witness.epoch);
10749
10660
  const committed = witness.value.log;
@@ -10754,6 +10665,33 @@ function checkWitness(witness, seq, deadline, log, person, sth) {
10754
10665
  if (epoch.leaf.ts > deadline)
10755
10666
  throw new MessagingError("binding_invalid", "a Jelly binding first appeared after its token's life");
10756
10667
  }
10668
+ function verifyPersonHead(view, context) {
10669
+ try {
10670
+ const parsed = parseWith(PersonHead, view, "person head");
10671
+ const { sth } = context;
10672
+ const head = verifyEpochProof(sth, context.head);
10673
+ if (head.index !== sth.size - 1)
10674
+ throw fail("the head epoch is not the tree head's last");
10675
+ const mapRoot = head.leaf.mapRoot;
10676
+ if (parsed.tombstone) {
10677
+ if (parsed.value)
10678
+ throw new MessagingError("invalid_request", "an erased person has no head");
10679
+ if (!verifyMapProof(mapRoot, parsed.person, TOMBSTONE, parsed.mapProof))
10680
+ throw new DirectoryInconsistency("the map does not hold the tombstone", { kind: "map", sth, head });
10681
+ return { person: parsed.person, state: "erased" };
10682
+ }
10683
+ if (!parsed.value) {
10684
+ if (!verifyMapProof(mapRoot, parsed.person, undefined, parsed.mapProof))
10685
+ throw new DirectoryInconsistency("the map holds a person served as absent", { kind: "map", sth, head });
10686
+ return { person: parsed.person, state: "absent" };
10687
+ }
10688
+ if (!verifyMapProof(mapRoot, parsed.person, mapValue(parsed.value), parsed.mapProof))
10689
+ throw new DirectoryInconsistency("the map does not commit to the head served", { kind: "map", sth, head });
10690
+ return { person: parsed.person, state: "live", log: parsed.value.log, keys: parsed.value.keys };
10691
+ } catch (error) {
10692
+ throw asMessagingError(error, "person head");
10693
+ }
10694
+ }
10757
10695
  var DirectoryInconsistency, fail = (what) => new MessagingError("directory_inconsistent", what);
10758
10696
  var init_person_view = __esm(() => {
10759
10697
  init_check();
@@ -11105,8 +11043,6 @@ function grant(identity, args) {
11105
11043
  const raw = args.parent;
11106
11044
  const parent = raw === undefined ? undefined : parseWith(DelegationGrant, raw, "parent grant");
11107
11045
  if (parent !== undefined) {
11108
- if (args.external !== undefined)
11109
- throw new MessagingError("invalid_request", "only a root grant names an external principal");
11110
11046
  if (parent.to !== args.from)
11111
11047
  throw new MessagingError("invalid_request", "a grant extends the grant to its delegating session");
11112
11048
  }
@@ -11131,8 +11067,7 @@ function grant(identity, args) {
11131
11067
  exp,
11132
11068
  maxDepth,
11133
11069
  ...scope !== undefined ? { scope: [...scope] } : {},
11134
- ...raw !== undefined ? { parent: grantHash(raw) } : {},
11135
- ...args.external !== undefined ? { external: { principal: args.external.principal } } : {}
11070
+ ...raw !== undefined ? { parent: grantHash(raw) } : {}
11136
11071
  };
11137
11072
  parseWith(DelegationGrant.omit({ sig: true }), unsigned, "delegation grant");
11138
11073
  return { ...unsigned, sig: sign(identity, DOMAIN.delegation, unsigned) };
@@ -11154,7 +11089,7 @@ function check2(raw, ctx) {
11154
11089
  throw new MessagingError("too_large", `a delegation chain is limited to ${LIMITS.delegationBytes} bytes`);
11155
11090
  let maxDepth = LIMITS.delegationDepth;
11156
11091
  let previous;
11157
- for (const [index, grant] of grants.entries()) {
11092
+ for (const grant of grants) {
11158
11093
  const { sig, ...unsigned } = grant;
11159
11094
  const input = signingInput(DOMAIN.delegation, unsigned);
11160
11095
  if (grant.realm !== realm.realm)
@@ -11176,9 +11111,6 @@ function check2(raw, ctx) {
11176
11111
  }
11177
11112
  if (!verifyInput(grant.by, input, sig))
11178
11113
  throw new MessagingError("bad_signature", "a grant signature is invalid");
11179
- if (grant.external !== undefined && (index > 0 || signer.role !== "service")) {
11180
- throw new MessagingError("forbidden", "only a service member's root grant names an external principal");
11181
- }
11182
11114
  if (previous === undefined) {
11183
11115
  if (grant.parent !== undefined)
11184
11116
  throw new MessagingError("invalid_request", "a root grant has no parent");
@@ -11207,9 +11139,6 @@ function check2(raw, ctx) {
11207
11139
  if (last.by !== hop.by || last.from !== hop.from || last.to !== hop.to || last.task !== hop.task) {
11208
11140
  throw new MessagingError("forbidden", "the chain does not authorize this request");
11209
11141
  }
11210
- const external = grants.length === 1 ? root.external?.principal : undefined;
11211
- if (external !== hop.external)
11212
- throw new MessagingError("forbidden", "the chain's external principal does not match the request");
11213
11142
  return {
11214
11143
  grants: structuredClone(grants),
11215
11144
  summary: {
@@ -11217,7 +11146,6 @@ function check2(raw, ctx) {
11217
11146
  depth: grants.length,
11218
11147
  maxDepth,
11219
11148
  ...last.scope !== undefined ? { scope: [...last.scope] } : {},
11220
- ...root.external !== undefined ? { external: { principal: root.external.principal, gateway: root.by } } : {},
11221
11149
  exp: Math.min(...grants.map((grant) => grant.exp))
11222
11150
  }
11223
11151
  };
@@ -11279,103 +11207,8 @@ var init_task = __esm(() => {
11279
11207
  };
11280
11208
  });
11281
11209
 
11282
- // packages/core/dist/direct.js
11283
- import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, createHash as createHash7, diffieHellman as diffieHellman2, generateKeyPairSync as generateKeyPairSync3, hkdfSync as hkdfSync2, randomBytes as randomBytes4 } from "crypto";
11284
- function directEphemeral() {
11285
- const key = generateKeyPairSync3("x25519").privateKey;
11286
- return { key, half: { epk: rawPublicKey(key), nonce: b64u.encode(randomBytes4(32)) } };
11287
- }
11288
- function directTranscript(initiator, responder) {
11289
- return { proto: DIRECT_PROTOCOL, initiator: { epk: initiator.epk, nonce: initiator.nonce }, responder: { epk: responder.epk, nonce: responder.nonce } };
11290
- }
11291
- function directAuthPayload(args) {
11292
- const { transcript, team, role, machine, peer } = args;
11293
- if (role === "initiator" !== (peer !== undefined))
11294
- throw new MessagingError("invalid_request", "only the initiator names its peer");
11295
- return { ...transcript, team, role, machine, ...peer === undefined ? {} : { peer } };
11296
- }
11297
- function directKeys(own, peerEpk, transcript) {
11298
- let shared;
11299
- try {
11300
- shared = diffieHellman2({ privateKey: own, publicKey: publicKeyFromRaw("x25519", peerEpk) });
11301
- } catch {
11302
- throw new MessagingError("bad_signature", "the peer's handshake key is not usable");
11303
- }
11304
- if (shared.every((byte) => byte === 0))
11305
- throw new MessagingError("bad_signature", "the peer's handshake key is not usable");
11306
- const salt = createHash7("sha256").update(canonicalize(transcript)).digest();
11307
- const okm = new Uint8Array(hkdfSync2("sha256", shared, salt, DOMAIN.directLink, 128));
11308
- return {
11309
- box: { responder: okm.slice(0, 32), initiator: okm.slice(32, 64) },
11310
- record: { initiator: okm.slice(64, 96), responder: okm.slice(96, 128) }
11311
- };
11312
- }
11313
- function sealDirectBox(key, transcript, identity) {
11314
- const cipher = createCipheriv2("aes-256-gcm", key, ZERO_NONCE, { authTagLength: TAG_BYTES2 });
11315
- cipher.setAAD(utf8(canonicalize(transcript)));
11316
- return b64u.encode(Buffer.concat([cipher.update(utf8(canonicalize(identity))), cipher.final(), cipher.getAuthTag()]));
11317
- }
11318
- function openDirectBox(key, transcript, box) {
11319
- let plaintext;
11320
- try {
11321
- const bytes = b64u.decode(box);
11322
- if (bytes.length <= TAG_BYTES2)
11323
- throw new Error("short");
11324
- const decipher = createDecipheriv2("aes-256-gcm", key, ZERO_NONCE, { authTagLength: TAG_BYTES2 });
11325
- decipher.setAAD(utf8(canonicalize(transcript)));
11326
- decipher.setAuthTag(bytes.subarray(bytes.length - TAG_BYTES2));
11327
- plaintext = Buffer.concat([decipher.update(bytes.subarray(0, bytes.length - TAG_BYTES2)), decipher.final()]);
11328
- } catch {
11329
- throw new MessagingError("bad_signature", "the handshake box does not open");
11330
- }
11331
- return parseWire(plaintext.toString("utf8"), { maxBytes: DIRECT_HANDSHAKE_BYTES });
11332
- }
11333
-
11334
- class DirectCipher {
11335
- #key;
11336
- #counter = 0;
11337
- constructor(key) {
11338
- if (key.length !== 32)
11339
- throw new MessagingError("invalid_request", "a record key is 32 bytes");
11340
- this.#key = key;
11341
- }
11342
- seal(plaintext) {
11343
- const cipher = createCipheriv2("aes-256-gcm", this.#key, this.#nonce(), { authTagLength: TAG_BYTES2 });
11344
- return Buffer.concat([cipher.update(plaintext), cipher.final(), cipher.getAuthTag()]);
11345
- }
11346
- open(record) {
11347
- try {
11348
- if (record.length < TAG_BYTES2)
11349
- throw new Error("short");
11350
- const decipher = createDecipheriv2("aes-256-gcm", this.#key, this.#nonce(), { authTagLength: TAG_BYTES2 });
11351
- decipher.setAuthTag(record.subarray(record.length - TAG_BYTES2));
11352
- return Buffer.concat([decipher.update(record.subarray(0, record.length - TAG_BYTES2)), decipher.final()]);
11353
- } catch {
11354
- throw new MessagingError("bad_signature", "a direct record does not open");
11355
- }
11356
- }
11357
- #nonce() {
11358
- if (this.#counter >= Number.MAX_SAFE_INTEGER)
11359
- throw new MessagingError("internal", "record counter exhausted");
11360
- const nonce = Buffer.alloc(NONCE_BYTES2);
11361
- nonce.writeBigUInt64BE(BigInt(this.#counter++), 4);
11362
- return nonce;
11363
- }
11364
- }
11365
- var NONCE_BYTES2 = 12, TAG_BYTES2 = 16, ZERO_NONCE;
11366
- var init_direct2 = __esm(() => {
11367
- init_b64u();
11368
- init_jcs();
11369
- init_keys();
11370
- init_common();
11371
- init_direct();
11372
- init_errors3();
11373
- init_wire();
11374
- ZERO_NONCE = new Uint8Array(NONCE_BYTES2);
11375
- });
11376
-
11377
11210
  // packages/core/dist/peer-text.js
11378
- import { randomBytes as randomBytes5 } from "crypto";
11211
+ import { randomBytes as randomBytes4 } from "crypto";
11379
11212
  function addReading(code, letters) {
11380
11213
  const c = String.fromCodePoint(code);
11381
11214
  READINGS.set(c, (READINGS.get(c) ?? "") + letters);
@@ -11612,7 +11445,7 @@ function createFence(options) {
11612
11445
  if (!TAG.test(tag))
11613
11446
  throw new Error("fence tag must be lowercase words joined by hyphens");
11614
11447
  const stem = tag.split("-")[0];
11615
- const random = options.random ?? randomBytes5;
11448
+ const random = options.random ?? randomBytes4;
11616
11449
  const opened = new RegExp(`<${tag}-[${NONCE_ALPHABET}]{${NONCE_LENGTH}}[ >]`);
11617
11450
  const neutralize = (text, clean = {}) => render(text, { singleLine: clean.singleLine === true, maxChars: clean.maxChars, terminal: false, attribute: false, stem });
11618
11451
  function fence(body, attributes = {}, clean = {}, block) {
@@ -12734,7 +12567,6 @@ var init_agent_tools = __esm(() => {
12734
12567
  at: iso,
12735
12568
  parts: array(Part2),
12736
12569
  verified: _enum(["signature", "none"]).optional().describe("signature: the daemon checked the sender's signature"),
12737
- external: fenced("An outside A2A principal it came from, through the gateway").optional(),
12738
12570
  replyTo: string2().optional(),
12739
12571
  chain: Chain.optional(),
12740
12572
  task: Task2.optional(),
@@ -12864,7 +12696,6 @@ var init_dist = __esm(() => {
12864
12696
  init_delegation2();
12865
12697
  init_task();
12866
12698
  init_a2a();
12867
- init_direct2();
12868
12699
  init_peer_text();
12869
12700
  init_agent_tools();
12870
12701
  init_operations();
@@ -12992,13 +12823,13 @@ var init_paths = __esm(() => {
12992
12823
  });
12993
12824
 
12994
12825
  // packages/ipc/dist/pipes.js
12995
- import { randomBytes as randomBytes6 } from "crypto";
12826
+ import { randomBytes as randomBytes5 } from "crypto";
12996
12827
  import { readFileSync, renameSync, rmSync, writeFileSync } from "fs";
12997
12828
  function newPipeName(tier) {
12998
- return `\\\\.\\pipe\\messaging-${randomBytes6(16).toString("hex")}-${tier}`;
12829
+ return `\\\\.\\pipe\\messaging-${randomBytes5(16).toString("hex")}-${tier}`;
12999
12830
  }
13000
12831
  function writePointer(path, pointer) {
13001
- const temp = `${path}.${process.pid}.${randomBytes6(4).toString("hex")}.tmp`;
12832
+ const temp = `${path}.${process.pid}.${randomBytes5(4).toString("hex")}.tmp`;
13002
12833
  try {
13003
12834
  writeFileSync(temp, `${JSON.stringify(pointer)}
13004
12835
  `, { flag: "wx" });
@@ -13054,7 +12885,7 @@ var init_rpc = __esm(() => {
13054
12885
 
13055
12886
  // packages/ipc/dist/windows-acl.js
13056
12887
  import { spawnSync } from "child_process";
13057
- import { randomBytes as randomBytes7 } from "crypto";
12888
+ import { randomBytes as randomBytes6 } from "crypto";
13058
12889
  import { existsSync, readFileSync as readFileSync2, rmSync as rmSync2 } from "fs";
13059
12890
  import { tmpdir } from "os";
13060
12891
  import { join as join2 } from "path";
@@ -13169,7 +13000,7 @@ function currentUserSid() {
13169
13000
  return sid;
13170
13001
  }
13171
13002
  function readDacl(path) {
13172
- const out = join2(tmpdir(), `messaging-acl-${process.pid}-${randomBytes7(8).toString("hex")}.txt`);
13003
+ const out = join2(tmpdir(), `messaging-acl-${process.pid}-${randomBytes6(8).toString("hex")}.txt`);
13173
13004
  try {
13174
13005
  run("icacls.exe", [path, "/save", out, "/q"]);
13175
13006
  return parseSddlDacl(sddlLine(readFileSync2(out)));
@@ -13948,85 +13779,6 @@ var init_dist2 = __esm(() => {
13948
13779
  init_windows_acl();
13949
13780
  });
13950
13781
 
13951
- // packages/daemon/dist/direct-config.js
13952
- import { BlockList, isIP } from "net";
13953
- function parseHostPort(text) {
13954
- const match = /^\[([0-9A-Fa-f:.]+)\](?::(\d{1,5}))?$/.exec(text) ?? /^([0-9.]+)(?::(\d{1,5}))?$/.exec(text);
13955
- if (!match)
13956
- return;
13957
- const host = match[1];
13958
- const family = isIP(host);
13959
- if (family === 0 || family === 6 !== text.startsWith("["))
13960
- return;
13961
- const port = match[2] === undefined ? undefined : Number(match[2]);
13962
- if (port !== undefined && port > 65535)
13963
- return;
13964
- return { host, port };
13965
- }
13966
- function family(host) {
13967
- const f = isIP(host);
13968
- return f === 4 ? "ipv4" : f === 6 ? "ipv6" : undefined;
13969
- }
13970
- function isLoopback(host) {
13971
- const f = family(host);
13972
- return f !== undefined && LOOPBACK.check(host, f);
13973
- }
13974
- function isWildcard(host) {
13975
- return host === "0.0.0.0" || host === "::";
13976
- }
13977
- function isDirectHost(host) {
13978
- const f = family(host);
13979
- return f !== undefined && (LOOPBACK.check(host, f) || LAN.check(host, f));
13980
- }
13981
- function mayDial(endpoint, ownListenerOnLoopback) {
13982
- if (!isDirectHost(endpoint.host))
13983
- return false;
13984
- return !isLoopback(endpoint.host) || ownListenerOnLoopback;
13985
- }
13986
- function advertisedEndpoints(config, bound) {
13987
- if (config.advertise === undefined)
13988
- return isWildcard(bound.host) ? [] : [{ host: bound.host, port: bound.port }];
13989
- return config.advertise.map((text) => {
13990
- const parsed = parseHostPort(text);
13991
- return { host: parsed.host, port: parsed.port ?? bound.port };
13992
- });
13993
- }
13994
- var DEFAULT_DIRECT_LISTEN = "127.0.0.1:0", LOOPBACK, LAN, ListenAddress, AdvertiseAddress, DirectConfig;
13995
- var init_direct_config = __esm(() => {
13996
- init_zod();
13997
- LOOPBACK = new BlockList;
13998
- LOOPBACK.addSubnet("127.0.0.0", 8, "ipv4");
13999
- LOOPBACK.addAddress("::1", "ipv6");
14000
- LAN = new BlockList;
14001
- for (const [net, prefix] of [
14002
- ["10.0.0.0", 8],
14003
- ["172.16.0.0", 12],
14004
- ["192.168.0.0", 16],
14005
- ["100.64.0.0", 10],
14006
- ["169.254.0.0", 16]
14007
- ]) {
14008
- LAN.addSubnet(net, prefix, "ipv4");
14009
- }
14010
- LAN.addSubnet("fe80::", 10, "ipv6");
14011
- LAN.addSubnet("fc00::", 7, "ipv6");
14012
- ListenAddress = string2().refine((text) => {
14013
- const parsed = parseHostPort(text);
14014
- return parsed?.port !== undefined && (isWildcard(parsed.host) || isDirectHost(parsed.host));
14015
- }, "listen must be ip:port (IPv6 in brackets) on loopback, a private or link-local address, or all interfaces");
14016
- AdvertiseAddress = string2().refine((text) => {
14017
- const parsed = parseHostPort(text);
14018
- return parsed !== undefined && parsed.port !== 0 && isDirectHost(parsed.host);
14019
- }, "advertise entries must be ip or ip:port (IPv6 in brackets) on loopback or a private or link-local address");
14020
- DirectConfig = object({
14021
- enabled: boolean2().optional(),
14022
- listen: ListenAddress.optional(),
14023
- advertise: array(AdvertiseAddress).min(1).max(4).optional()
14024
- }).strict().refine((c) => c.enabled !== true || c.advertise !== undefined || !isWildcard(parseHostPort(c.listen ?? DEFAULT_DIRECT_LISTEN).host), {
14025
- message: "advertise is required when listening on all interfaces",
14026
- path: ["advertise"]
14027
- });
14028
- });
14029
-
14030
13782
  // packages/daemon/dist/identity/record.js
14031
13783
  import { createPrivateKey as createPrivateKey2 } from "crypto";
14032
13784
  import { closeSync as closeSync2, constants as constants3, existsSync as existsSync3, fstatSync, fsyncSync, linkSync, readFileSync as readFileSync4, renameSync as renameSync2, rmSync as rmSync3, statSync, unlinkSync, writeSync } from "fs";
@@ -14303,7 +14055,6 @@ var init_home = __esm(() => {
14303
14055
  init_zod();
14304
14056
  init_dist();
14305
14057
  init_dist2();
14306
- init_direct_config();
14307
14058
  init_record();
14308
14059
  PRESENCE_FIELDS = ["repo", "branch", "doing"];
14309
14060
  PresenceField = _enum(PRESENCE_FIELDS);
@@ -14314,8 +14065,6 @@ var init_home = __esm(() => {
14314
14065
  label: MachineLabel.optional(),
14315
14066
  realm: DirectoryRealmConfig.strict().optional(),
14316
14067
  inbound: object({
14317
- refuse: array(PersonName).max(0, { error: 'inbound.refuse was replaced by block grants (spec \xA76.9): remove it, and block each person with grant.set {subject: {person}, chat: "blocked"}' }).optional(),
14318
- wake: literal("team", { error: "inbound.wake was removed: a push-tier session may be woken for what consent delivers to it; register sessions with tier pull to never wake them (spec \xA78.3)" }).optional(),
14319
14068
  quota: object({ items: number2().int().min(1), bytes: number2().int().min(1) }).partial().strict().optional()
14320
14069
  }).strict().optional(),
14321
14070
  presence: object({ share: array(PresenceField).max(PRESENCE_FIELDS.length) }).strict().optional(),
@@ -14329,7 +14078,6 @@ var init_home = __esm(() => {
14329
14078
  transcriptDays: number2().int().min(1).max(36500)
14330
14079
  }).partial().strict().optional(),
14331
14080
  ipc: object({ agentGroup: number2().int().min(0).max(4294967294) }).partial().strict().optional(),
14332
- transport: object({ direct: DirectConfig.optional() }).strict().optional(),
14333
14081
  identity: object({
14334
14082
  store: _enum(["auto", "file", "keychain"]),
14335
14083
  prompt: boolean2(),
@@ -14917,8 +14665,6 @@ function machineLoopGuard() {
14917
14665
  function inboundPolicy(facts, state, now) {
14918
14666
  if (!facts.recipient)
14919
14667
  return "unknown_session";
14920
- if (facts.recipient.agent === GATEWAY_AGENT)
14921
- return "refused";
14922
14668
  if (facts.task && facts.recipient.agent === CLI_AGENT)
14923
14669
  return "refused";
14924
14670
  const agents = facts.sender.agent !== CLI_AGENT && facts.recipient.agent !== CLI_AGENT;
@@ -14929,8 +14675,6 @@ ${facts.recipient.session}`, now))
14929
14675
  if (agents && !state.machineLoop.take(`${facts.sender.machine}
14930
14676
  ${facts.recipient.session}`, now))
14931
14677
  return "loop_guard";
14932
- if (facts.external && facts.sender.role !== "service")
14933
- return "forbidden";
14934
14678
  return;
14935
14679
  }
14936
14680
  function isFinalStage(stage) {
@@ -14946,7 +14690,7 @@ function canAdvance(from, to) {
14946
14690
  function inboxAdvances(from, to) {
14947
14691
  return INBOX_RANK[to] > INBOX_RANK[from];
14948
14692
  }
14949
- var RESPONSE_RATE_FACTOR = 4, CHAT_SEND_PER_MACHINE_PER_MINUTE, DEFER_MARGIN_MS = 60000, RESPONSE_TYPES, LOOP_MACHINE_FACTOR = 2, GATEWAY_AGENT = "a2a-gateway", STAGE_RANK, INBOX_RANK;
14693
+ var RESPONSE_RATE_FACTOR = 4, CHAT_SEND_PER_MACHINE_PER_MINUTE, DEFER_MARGIN_MS = 60000, RESPONSE_TYPES, LOOP_MACHINE_FACTOR = 2, STAGE_RANK, INBOX_RANK;
14950
14694
  var init_policy = __esm(() => {
14951
14695
  init_dist();
14952
14696
  CHAT_SEND_PER_MACHINE_PER_MINUTE = LIMITS.inboundPerMachinePerMinute - 10;
@@ -14966,7 +14710,7 @@ var init_policy = __esm(() => {
14966
14710
  });
14967
14711
 
14968
14712
  // packages/daemon/dist/chat.js
14969
- import { createHash as createHash8 } from "crypto";
14713
+ import { createHash as createHash7 } from "crypto";
14970
14714
 
14971
14715
  class Chats {
14972
14716
  #host;
@@ -15041,7 +14785,7 @@ class Chats {
15041
14785
  }
15042
14786
  send(p, session, scope = "") {
15043
14787
  const room = p.conversation;
15044
- const digest = p.clientId === undefined ? undefined : createHash8("sha256").update(JSON.stringify([room, p.thread ?? "", p.text])).digest("base64url");
14788
+ const digest = p.clientId === undefined ? undefined : createHash7("sha256").update(JSON.stringify([room, p.thread ?? "", p.text])).digest("base64url");
15045
14789
  if (p.clientId !== undefined) {
15046
14790
  const earlier = this.#store.chatSend(scope, p.clientId);
15047
14791
  if (earlier) {
@@ -15438,7 +15182,7 @@ var init_chat = __esm(() => {
15438
15182
  });
15439
15183
 
15440
15184
  // packages/daemon/dist/log.js
15441
- import { closeSync as closeSync5, constants as constants5, lstatSync as lstatSync3, renameSync as renameSync6, writeSync as writeSync2 } from "fs";
15185
+ import { closeSync as closeSync5, constants as constants5, lstatSync as lstatSync3, renameSync as renameSync6, writeSync as writeSync3 } from "fs";
15442
15186
  function formatLine(level, event, fields = {}) {
15443
15187
  const record = { t: new Date().toISOString(), level, event };
15444
15188
  for (const [name, value] of Object.entries(fields))
@@ -15467,7 +15211,7 @@ function createLogger(options) {
15467
15211
  }
15468
15212
  const fd = openNoFollow(file, constants5.O_WRONLY | constants5.O_APPEND | constants5.O_CREAT, 384);
15469
15213
  try {
15470
- writeSync2(fd, line);
15214
+ writeSync3(fd, line);
15471
15215
  } finally {
15472
15216
  closeSync5(fd);
15473
15217
  }
@@ -15534,9 +15278,9 @@ var init_log = __esm(() => {
15534
15278
  });
15535
15279
 
15536
15280
  // packages/daemon/dist/keyring.js
15537
- import { createHash as createHash9, generateKeyPairSync as generateKeyPairSync4 } from "crypto";
15281
+ import { createHash as createHash8, generateKeyPairSync as generateKeyPairSync3 } from "crypto";
15538
15282
  function rotationOffset(machine) {
15539
- return createHash9("sha256").update(machine).digest().readUInt32BE(0) % ROTATION_SPREAD_MS;
15283
+ return createHash8("sha256").update(machine).digest().readUInt32BE(0) % ROTATION_SPREAD_MS;
15540
15284
  }
15541
15285
  function rotationDue(now, rotatedAt, days, machine) {
15542
15286
  return days > 0 && now >= rotatedAt + days * DAY_MS + rotationOffset(machine);
@@ -15755,7 +15499,7 @@ class Keyring {
15755
15499
  if (kept.length >= MAX_RETAINED) {
15756
15500
  throw new MessagingError("invalid_state", "too many retained encryption keys; wait until the oldest expires, then rotate again");
15757
15501
  }
15758
- const next = withEncKey(identity, generateKeyPairSync4("x25519").privateKey);
15502
+ const next = withEncKey(identity, generateKeyPairSync3("x25519").privateKey);
15759
15503
  const pending = { identity: next, retained: [{ key: identity.encKey }, ...kept] };
15760
15504
  store.save(pending);
15761
15505
  this.#keys = pending;
@@ -15790,7 +15534,7 @@ var init_keyring = __esm(() => {
15790
15534
  });
15791
15535
 
15792
15536
  // packages/daemon/dist/directory.js
15793
- function presencePayload(rows, label, seq, extra, policy) {
15537
+ function presencePayload(rows, label, seq, policy) {
15794
15538
  const { share } = policy;
15795
15539
  const sessions = rows.slice(0, LIMITS.presenceSessions).map((s) => {
15796
15540
  const live = s.unregisteredAt === undefined;
@@ -15812,8 +15556,7 @@ function presencePayload(rows, label, seq, extra, policy) {
15812
15556
  seq,
15813
15557
  label,
15814
15558
  sessions: [],
15815
- features: [...FEATURES, ...extra?.features ?? []],
15816
- ...extra?.direct ? { direct: extra.direct } : {},
15559
+ features: [...FEATURES],
15817
15560
  ...policy.away === true ? { away: true } : {}
15818
15561
  };
15819
15562
  return { ...payload, sessions: fitPresence(sessions, jcsBytes(payload), LIMITS.presenceBytes) };
@@ -15987,10 +15730,9 @@ function signHop(args) {
15987
15730
  now,
15988
15731
  ...last !== undefined ? { parent: last } : {},
15989
15732
  ...scope !== undefined ? { scope } : {},
15990
- ...maxDepth !== undefined ? { maxDepth } : {},
15991
- ...request.external !== undefined ? { external: { principal: request.external.principal } } : {}
15733
+ ...maxDepth !== undefined ? { maxDepth } : {}
15992
15734
  });
15993
- const hop = { by: identity.machine, from, to: args.to, task: args.task, ...request.external !== undefined ? { external: request.external.principal } : {} };
15735
+ const hop = { by: identity.machine, from, to: args.to, task: args.task };
15994
15736
  return verifyChain([...args.parent ?? [], grant], { realm, now, hop });
15995
15737
  }
15996
15738
  function inboundChain(raw, ctx) {
@@ -15998,8 +15740,7 @@ function inboundChain(raw, ctx) {
15998
15740
  by: ctx.sender.machine,
15999
15741
  from: formatAddress(ctx.sender.person, ctx.fromSession, ctx.sender.label),
16000
15742
  to: formatAddress(ctx.me.person, ctx.toSession, ctx.me.label),
16001
- task: ctx.task,
16002
- ...ctx.external !== undefined ? { external: ctx.external.principal } : {}
15743
+ task: ctx.task
16003
15744
  };
16004
15745
  return verifyChain(raw, { realm: ctx.realm, now: ctx.now, hop });
16005
15746
  }
@@ -16264,7 +16005,6 @@ class DeviceLogin {
16264
16005
  const pending = {
16265
16006
  state: "pending",
16266
16007
  purpose: args.purpose,
16267
- verificationUriComplete: code.verificationUriComplete,
16268
16008
  verificationUri: code.verificationUri,
16269
16009
  userCode: code.userCode,
16270
16010
  fingerprint: jellyKeyFingerprint(device),
@@ -16369,7 +16109,7 @@ var init_device_login = __esm(() => {
16369
16109
  });
16370
16110
 
16371
16111
  // packages/daemon/dist/realm/directory-identity.js
16372
- import { randomBytes as randomBytes8 } from "crypto";
16112
+ import { randomBytes as randomBytes7 } from "crypto";
16373
16113
 
16374
16114
  class DirectoryIdentity {
16375
16115
  kind = "directory";
@@ -16636,12 +16376,21 @@ class DirectoryIdentity {
16636
16376
  throw new MessagingError("binding_invalid", "this device was removed from its account; it cannot be bound again");
16637
16377
  const done = await this.#extendLog(async () => {
16638
16378
  await this.#latest();
16639
- await this.#fetch([person]);
16640
- const log = this.#persons.get(person)?.state.log;
16641
- const current = log?.device(identity.machine);
16642
- if (current && !current.current)
16643
- throw new MessagingError("binding_invalid", "this device was removed from its account; it cannot be bound again");
16644
- const head = log?.head();
16379
+ let current;
16380
+ let head;
16381
+ if (this.#own.person === person && this.binding().state !== "none") {
16382
+ await this.#fetch([person]);
16383
+ const log = this.#persons.get(person)?.state.log;
16384
+ current = log?.device(identity.machine);
16385
+ if (current && !current.current)
16386
+ throw new MessagingError("binding_invalid", "this device was removed from its account; it cannot be bound again");
16387
+ head = log?.head();
16388
+ } else {
16389
+ const verified = await this.#headOf(person);
16390
+ if (verified.state === "erased")
16391
+ throw new MessagingError("binding_invalid", "the account was erased");
16392
+ head = verified.state === "live" ? verified.log : undefined;
16393
+ }
16645
16394
  const entry = signLogEntry(identity, {
16646
16395
  v: PROTOCOL,
16647
16396
  realm: this.realm,
@@ -16771,7 +16520,7 @@ class DirectoryIdentity {
16771
16520
  if (this.#own.person !== undefined) {
16772
16521
  throw new MessagingError("invalid_state", this.removed() ? "this device was removed from its account; it cannot be bound again" : "this device is already bound");
16773
16522
  }
16774
- const link = createLinkRequest(this.#options.identity(), { realm: this.realm, label: args.label, kind: args.kind, nonce: randomBytes8(16), ts: this.#options.now() });
16523
+ const link = createLinkRequest(this.#options.identity(), { realm: this.realm, label: args.label, kind: args.kind, nonce: randomBytes7(16), ts: this.#options.now() });
16775
16524
  this.#pendingLink = link;
16776
16525
  return { link: encodeLink(link), sas: linkSas(link), expires: link.ts + LIMITS.linkTtlMs };
16777
16526
  }
@@ -16965,8 +16714,9 @@ class DirectoryIdentity {
16965
16714
  async#fetch(persons) {
16966
16715
  if (!this.#head)
16967
16716
  await this.#latest();
16717
+ const basis = new Map(persons.map((person) => [person, this.#persons.get(person)]));
16968
16718
  const ask = persons.map((person) => {
16969
- const held = this.#persons.get(person);
16719
+ const held = basis.get(person);
16970
16720
  const length = held?.state.erased ? 0 : held?.state.log.length ?? 0;
16971
16721
  return length > 0 ? { person, knownSeq: length - 1 } : { person };
16972
16722
  });
@@ -16990,8 +16740,14 @@ class DirectoryIdentity {
16990
16740
  const view = views.get(person);
16991
16741
  if (!view)
16992
16742
  continue;
16993
- const before = this.#persons.get(person)?.state;
16994
- const state = await this.#verify(view, head);
16743
+ const state = await this.#verify(view, head, basis.get(person)?.state);
16744
+ const current = this.#persons.get(person);
16745
+ if (current && current !== basis.get(person)) {
16746
+ this.#succession(current.size > head.sth.size ? state : current.state, current.size > head.sth.size ? current.state : state);
16747
+ if (current.size > head.sth.size)
16748
+ continue;
16749
+ }
16750
+ const before = current?.state;
16995
16751
  this.#persons.set(person, { state, size: head.sth.size, fetchedAt: now });
16996
16752
  this.#cache.putPerson({ person, entries: [...state.log.entries()], keys: [...state.keys.values()], erased: state.erased, size: head.sth.size, fetchedAt: now });
16997
16753
  if (state.log.length !== (before?.log.length ?? 0) || state.erased !== (before?.erased ?? false))
@@ -17005,22 +16761,58 @@ class DirectoryIdentity {
17005
16761
  if (unwatched.length > 0)
17006
16762
  this.watch(unwatched);
17007
16763
  }
17008
- async#verify(view, head) {
17009
- const known = this.#persons.get(view.person)?.state;
16764
+ async#headOf(person) {
16765
+ if (!this.#head)
16766
+ await this.#latest();
16767
+ let head = this.#head;
16768
+ let answer;
16769
+ try {
16770
+ answer = await this.#call((link) => link.heads([person], head.sth.size));
16771
+ } catch (error) {
16772
+ if (!(error instanceof MessagingError) || error.code !== "invalid_request")
16773
+ throw error;
16774
+ await this.#latest();
16775
+ head = this.#head;
16776
+ answer = await this.#call((link) => link.heads([person], head.sth.size));
16777
+ }
16778
+ if (answer.sth.size !== head.sth.size || answer.sth.root !== head.sth.root)
16779
+ throw new MessagingError("directory_inconsistent", "the directory answered at another tree head");
16780
+ const view = answer.persons.find((p) => p.person === person);
16781
+ if (!view)
16782
+ throw new MessagingError("directory_inconsistent", "the directory left out the person asked");
16783
+ try {
16784
+ return verifyPersonHead(view, { sth: head.sth, head: head.head });
16785
+ } catch (error) {
16786
+ this.#inconsistent(error);
16787
+ throw error;
16788
+ }
16789
+ }
16790
+ async#verify(view, head, known) {
17010
16791
  const tokens = view.log.flatMap((entry) => entry.authority.kind === "jelly" ? [entry.authority.token] : []);
17011
16792
  const keys = await this.#keyring.prepare(tokens, this.#options.now());
17012
16793
  this.#saveArchive();
17013
16794
  try {
17014
16795
  return verifyPersonView(view, { ...this.#context(view.person, keys), sth: head.sth, head: head.head, ...known && !known.erased ? { known } : {} });
17015
16796
  } catch (error) {
17016
- if (error instanceof DirectoryInconsistency) {
17017
- this.#raise({ kind: "directory", reason: error.message, at: this.#options.now(), evidence: error.evidence });
17018
- } else if (error instanceof MessagingError && error.code === "directory_inconsistent") {
17019
- this.#raise({ kind: "directory", reason: error.message, at: this.#options.now() });
17020
- }
16797
+ this.#inconsistent(error);
16798
+ throw error;
16799
+ }
16800
+ }
16801
+ #succession(earlier, later) {
16802
+ try {
16803
+ checkPersonSuccession(earlier, later);
16804
+ } catch (error) {
16805
+ this.#inconsistent(error);
17021
16806
  throw error;
17022
16807
  }
17023
16808
  }
16809
+ #inconsistent(error) {
16810
+ if (error instanceof DirectoryInconsistency) {
16811
+ this.#raise({ kind: "directory", reason: error.message, at: this.#options.now(), evidence: error.evidence });
16812
+ } else if (error instanceof MessagingError && error.code === "directory_inconsistent") {
16813
+ this.#raise({ kind: "directory", reason: error.message, at: this.#options.now() });
16814
+ }
16815
+ }
17024
16816
  #saveArchive() {
17025
16817
  this.#cache.setArchive(this.#keyring.archive());
17026
16818
  }
@@ -17393,7 +17185,6 @@ class DirectoryHome {
17393
17185
  const started = await this.#login.start({ purpose, label, kind });
17394
17186
  return {
17395
17187
  purpose,
17396
- verificationUriComplete: started.verificationUriComplete,
17397
17188
  verificationUri: started.verificationUri,
17398
17189
  userCode: started.userCode,
17399
17190
  fingerprint: started.fingerprint,
@@ -17417,7 +17208,6 @@ class DirectoryHome {
17417
17208
  const started = await this.#login.start({ purpose, label: args.label, kind: args.kind });
17418
17209
  return {
17419
17210
  purpose,
17420
- verificationUriComplete: started.verificationUriComplete,
17421
17211
  verificationUri: started.verificationUri,
17422
17212
  userCode: started.userCode,
17423
17213
  fingerprint: started.fingerprint,
@@ -17849,12 +17639,12 @@ var init_answer_preview = __esm(() => {
17849
17639
  });
17850
17640
 
17851
17641
  // packages/daemon/dist/snapshot.js
17852
- import { createHash as createHash10 } from "crypto";
17642
+ import { createHash as createHash9 } from "crypto";
17853
17643
  import { chmodSync as chmodSync4, chownSync as chownSync2, existsSync as existsSync6, lstatSync as lstatSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync8, rmSync as rmSync4, writeFileSync as writeFileSync5 } from "fs";
17854
17644
  import { join as join7 } from "path";
17855
17645
  function sha256File(path) {
17856
17646
  const data = readFileSync8(path);
17857
- return { bytes: data.length, sha256: createHash10("sha256").update(data).digest("hex") };
17647
+ return { bytes: data.length, sha256: createHash9("sha256").update(data).digest("hex") };
17858
17648
  }
17859
17649
 
17860
17650
  class Snapshots {
@@ -18639,46 +18429,44 @@ var init_storage_errors = __esm(() => {
18639
18429
  });
18640
18430
 
18641
18431
  // packages/daemon/dist/store.js
18642
- import { chmodSync as chmodSync5, closeSync as closeSync7, openSync as openSync3 } from "fs";
18432
+ import { chmodSync as chmodSync5, closeSync as closeSync7, existsSync as existsSync7, openSync as openSync3 } from "fs";
18643
18433
  import { DatabaseSync } from "node:sqlite";
18644
- function additiveColumn(decl) {
18645
- if (/\b(PRIMARY|UNIQUE|CHECK|REFERENCES|COLLATE|GENERATED|AS)\b/i.test(decl))
18646
- return false;
18647
- return !/\bNOT\s+NULL\b/i.test(decl) || /\bDEFAULT\b/i.test(decl);
18434
+ function checkStore(db, path) {
18435
+ if (db.prepare("SELECT 1 FROM sqlite_master LIMIT 1").get() === undefined)
18436
+ return true;
18437
+ const hasKv = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'kv'").get() !== undefined;
18438
+ const raw = hasKv ? kvGet(db, "generation") : undefined;
18439
+ if (raw === undefined) {
18440
+ throw new Error(`${path} was written before the store reset (it records no store generation), and this messagingd does not read it. ${RESET_HINT}`);
18441
+ }
18442
+ if (storeNumber(raw) !== STORE_GENERATION) {
18443
+ throw new Error(`${path} is of store generation ${raw.slice(0, 16)}; this messagingd reads generation ${STORE_GENERATION}. ${RESET_HINT}`);
18444
+ }
18445
+ const minReader = kvGet(db, "min_reader") ?? "";
18446
+ if ((storeNumber(minReader) ?? Number.POSITIVE_INFINITY) > STORE_SCHEMA) {
18447
+ throw new Error(`${path} needs a messagingd that reads store schema ${minReader.slice(0, 16) || "(unrecorded)"} or later; this one reads up to ${STORE_SCHEMA}. Run a newer messagingd`);
18448
+ }
18449
+ return false;
18450
+ }
18451
+ function checkStoreFile(path) {
18452
+ if (!existsSync7(path))
18453
+ return;
18454
+ const db = new DatabaseSync(path, { readOnly: true, timeout: 5000 });
18455
+ try {
18456
+ checkStore(db, path);
18457
+ } finally {
18458
+ db.close();
18459
+ }
18648
18460
  }
18649
- function upgradeStore(db, migrations) {
18461
+ function openStore(db, path) {
18650
18462
  db.exec("BEGIN IMMEDIATE");
18651
18463
  try {
18652
- db.exec("CREATE TABLE IF NOT EXISTS kv (k TEXT PRIMARY KEY, v TEXT NOT NULL) STRICT;");
18653
- const raw = kvGet(db, "schema");
18654
- const stored = schemaNumber(raw);
18655
- const minReader = schemaNumber(kvGet(db, "min_reader")) ?? STORE_MIN_READER;
18656
- const top = migrations.reduce((max, m) => Math.max(max, m.schema), STORE_SCHEMA);
18657
- if (raw !== undefined && (stored === undefined || stored < STORE_MIN_READER)) {
18658
- throw new Error(`messaging.db has schema ${raw.slice(0, 16)}, from before the first released store (schema ${STORE_MIN_READER}); move the Messaging home aside and set it up again`);
18659
- }
18660
- if (minReader !== undefined && minReader > top) {
18661
- throw new Error(`messaging.db needs a messagingd that reads store schema ${minReader} or later; this one reads up to ${top}. Run a newer messagingd`);
18662
- }
18663
- db.exec(BASELINE);
18664
- for (const migration of migrations) {
18665
- if (migration.ddl) {
18666
- if (/\b(DROP|RENAME|ALTER)\b|\bCREATE\s+UNIQUE\b/i.test(migration.ddl))
18667
- throw new Error(`store migration ${migration.schema} is not additive`);
18668
- db.exec(migration.ddl);
18669
- }
18670
- for (const { table, column, decl } of migration.columns ?? []) {
18671
- if (!/^[a-z_][a-z0-9_]*$/.test(table) || !/^[a-z_][a-z0-9_]*$/.test(column) || !additiveColumn(decl)) {
18672
- throw new Error(`store migration ${migration.schema} is not additive (${table}.${column})`);
18673
- }
18674
- const has = db.prepare(`SELECT name FROM pragma_table_info('${table}')`).all().some((c) => c.name === column);
18675
- if (!has)
18676
- db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${decl}`);
18677
- }
18678
- }
18679
- kvSet(db, "schema", String(Math.max(stored ?? 0, top)));
18680
- if (kvGet(db, "min_reader") === undefined)
18464
+ if (checkStore(db, path)) {
18465
+ db.exec(BASELINE);
18466
+ kvSet(db, "generation", String(STORE_GENERATION));
18467
+ kvSet(db, "schema", String(STORE_SCHEMA));
18681
18468
  kvSet(db, "min_reader", String(STORE_MIN_READER));
18469
+ }
18682
18470
  db.exec("COMMIT");
18683
18471
  } catch (error) {
18684
18472
  db.exec("ROLLBACK");
@@ -18733,7 +18521,7 @@ class Store {
18733
18521
  this.#db = new DatabaseSync(path, { timeout: 5000 });
18734
18522
  this.#db.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL;");
18735
18523
  try {
18736
- upgradeStore(this.#db, MIGRATIONS);
18524
+ openStore(this.#db, path);
18737
18525
  } catch (error) {
18738
18526
  this.#db.close();
18739
18527
  throw error;
@@ -18921,7 +18709,7 @@ class Store {
18921
18709
  }
18922
18710
  insertInbox(row) {
18923
18711
  const { key, stage, ...item } = row.item;
18924
- this.#run("INSERT INTO inbox (id, key, session, sender, item, message, stage, received_at, bytes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", item.id, key, row.session, row.sender, JSON.stringify({ ...item, from: storedPeer(item.from) }), JSON.stringify(storedMessage(row.message)), stage, item.receivedAt, item.bytes);
18712
+ this.#run("INSERT INTO inbox (id, key, session, sender, item, message, stage, received_at, bytes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", item.id, key, row.session, row.sender, JSON.stringify(item), JSON.stringify(row.message), stage, item.receivedAt, item.bytes);
18925
18713
  }
18926
18714
  inbox(key, unreadOnly, limit) {
18927
18715
  const sql = `SELECT * FROM inbox WHERE key = ?${unreadOnly ? " AND stage <> 'read'" : ""} ORDER BY received_at DESC, rowid DESC LIMIT ?`;
@@ -19322,7 +19110,7 @@ class Store {
19322
19110
  const prefix = `${peer}@`;
19323
19111
  const take = limit + 1;
19324
19112
  const [inboxBound, inboxArgs] = bound("received_at", "id");
19325
- const mail = this.#all(`SELECT * FROM inbox WHERE key = ? AND json_extract(item, '$.kind') = 'message' AND json_extract(item, '$.from.person') = ? AND json_extract(item, '$.from.session') = ?${inboxBound} ORDER BY received_at ${order}, id ${order} LIMIT ?`, key, person, session, ...inboxArgs, take).map((r) => ({ source: "inbox", at: Number(r.received_at), id: String(r.id), inbox: inboxRow2(r) }));
19113
+ const mail = this.#all(`SELECT * FROM inbox WHERE key = ? AND json_extract(item, '$.kind') = 'message' AND json_extract(item, '$.from.person') = ? AND json_extract(item, '$.from.agent') = ?${inboxBound} ORDER BY received_at ${order}, id ${order} LIMIT ?`, key, person, session, ...inboxArgs, take).map((r) => ({ source: "inbox", at: Number(r.received_at), id: String(r.id), inbox: inboxRow2(r) }));
19326
19114
  const [sentBound, sentArgs] = bound("created_at", "id");
19327
19115
  const sent = this.#all(`SELECT * FROM sent WHERE key = ? AND kind = 'message' AND substr(to_addr, 1, ?) = ?${sentBound} ORDER BY created_at ${order}, id ${order} LIMIT ?`, key, prefix.length, prefix, ...sentArgs, take).map((r) => ({ source: "sent", at: Number(r.created_at), id: String(r.id), sent: sentRow(r) }));
19328
19116
  const [taskBound, taskArgs] = bound("created_at", "task_id");
@@ -19377,7 +19165,7 @@ class Store {
19377
19165
  }
19378
19166
  agentPeers(key, limit) {
19379
19167
  return this.#all(`SELECT peer, SUM(unread) AS unread, MAX(at) AS at FROM (
19380
- SELECT json_extract(item, '$.from.person') || '/' || json_extract(item, '$.from.session') AS peer, stage <> 'read' AS unread, received_at AS at
19168
+ SELECT json_extract(item, '$.from.person') || '/' || json_extract(item, '$.from.agent') AS peer, stage <> 'read' AS unread, received_at AS at
19381
19169
  FROM inbox WHERE key = ? AND json_extract(item, '$.kind') = 'message'
19382
19170
  UNION ALL
19383
19171
  SELECT substr(to_addr, 1, instr(to_addr || '@', '@') - 1), 0, created_at FROM sent WHERE key = ? AND kind = 'message'
@@ -19404,7 +19192,7 @@ class Store {
19404
19192
  ON CONFLICT (task_id, role) DO UPDATE SET key = excluded.key, session = excluded.session, peer = excluded.peer,
19405
19193
  peer_machine = excluded.peer_machine, peer_address = excluded.peer_address, state = excluded.state,
19406
19194
  context_id = excluded.context_id, task = excluded.task, updated_at = excluded.updated_at,
19407
- request = COALESCE(excluded.request, tasks.request)`, r.task.id, r.role, r.key, row.session, JSON.stringify(storedPeer(r.peer)), r.peer.device, r.peer.address, r.task.status.state, opt(r.task.contextId), JSON.stringify(taskMessages(r.task, storedMessage)), r.createdAt, r.updatedAt, opt(row.request));
19195
+ request = COALESCE(excluded.request, tasks.request)`, r.task.id, r.role, r.key, row.session, JSON.stringify(r.peer), r.peer.device, r.peer.address, r.task.status.state, opt(r.task.contextId), JSON.stringify(r.task), r.createdAt, r.updatedAt, opt(row.request));
19408
19196
  if (r.chain !== undefined) {
19409
19197
  this.#run("INSERT OR IGNORE INTO delegations (task_id, role, grants, chain) VALUES (?, ?, ?, ?)", r.task.id, r.role, JSON.stringify(row.grants), JSON.stringify(r.chain));
19410
19198
  }
@@ -19507,35 +19295,12 @@ function directoryRow(r) {
19507
19295
  function inboxRow2(r) {
19508
19296
  const item = json(r.item);
19509
19297
  return {
19510
- item: { ...item, from: peerOf(item.from), key: String(r.key), stage: String(r.stage) },
19298
+ item: { ...item, key: String(r.key), stage: String(r.stage) },
19511
19299
  session: String(r.session),
19512
19300
  sender: String(r.sender),
19513
- message: messageOf2(json(r.message))
19301
+ message: json(r.message)
19514
19302
  };
19515
19303
  }
19516
- function storedPeer(peer) {
19517
- const { agent, device, ...rest } = peer;
19518
- return { ...rest, ...agent !== undefined ? { session: agent } : {}, machine: device };
19519
- }
19520
- function peerOf(stored) {
19521
- const { session, machine, ...rest } = stored;
19522
- return { ...rest, ...session !== undefined ? { agent: session } : {}, device: machine };
19523
- }
19524
- function storedMessage(message) {
19525
- const provenance = message.metadata?.[URI.provenance];
19526
- return provenance === undefined ? message : { ...message, metadata: { ...message.metadata, [URI.provenance]: storedPeer(provenance) } };
19527
- }
19528
- function taskMessages(task, convert) {
19529
- return {
19530
- ...task,
19531
- status: task.status.message !== undefined ? { ...task.status, message: convert(task.status.message) } : task.status,
19532
- ...task.history !== undefined ? { history: task.history.map(convert) } : {}
19533
- };
19534
- }
19535
- function messageOf2(message) {
19536
- const provenance = message.metadata?.[URI.provenance];
19537
- return provenance === undefined ? message : { ...message, metadata: { ...message.metadata, [URI.provenance]: peerOf(provenance) } };
19538
- }
19539
19304
  function outboxRow(r) {
19540
19305
  return {
19541
19306
  id: String(r.id),
@@ -19612,10 +19377,10 @@ function matchQuery(query) {
19612
19377
  function taskRow2(r) {
19613
19378
  const row = {
19614
19379
  record: {
19615
- task: taskMessages(json(r.task), messageOf2),
19380
+ task: json(r.task),
19616
19381
  role: String(r.role),
19617
19382
  key: String(r.key),
19618
- peer: peerOf(json(r.peer)),
19383
+ peer: json(r.peer),
19619
19384
  createdAt: Number(r.created_at),
19620
19385
  updatedAt: Number(r.updated_at)
19621
19386
  },
@@ -19631,7 +19396,7 @@ function taskRow2(r) {
19631
19396
  }
19632
19397
  return row;
19633
19398
  }
19634
- var CHAT_COUNTER = "chat", STORE_SCHEMA = 9, STORE_MIN_READER = 3, MIGRATIONS, BASELINE = `
19399
+ var CHAT_COUNTER = "chat", STORE_GENERATION = 2, STORE_SCHEMA = 1, STORE_MIN_READER = 1, BASELINE = `
19635
19400
  CREATE TABLE IF NOT EXISTS kv (k TEXT PRIMARY KEY, v TEXT NOT NULL) STRICT;
19636
19401
  CREATE TABLE IF NOT EXISTS counters (name TEXT PRIMARY KEY, value INTEGER NOT NULL) STRICT;
19637
19402
  CREATE TABLE IF NOT EXISTS roster (seq INTEGER PRIMARY KEY, hash TEXT NOT NULL, entry TEXT NOT NULL) STRICT;
@@ -19737,7 +19502,8 @@ CREATE TABLE IF NOT EXISTS chat_sent (
19737
19502
  person TEXT NOT NULL,
19738
19503
  stage TEXT NOT NULL,
19739
19504
  code TEXT,
19740
- exp INTEGER NOT NULL
19505
+ exp INTEGER NOT NULL,
19506
+ held_at INTEGER
19741
19507
  ) STRICT;
19742
19508
  CREATE INDEX IF NOT EXISTS chat_sent_by_line ON chat_sent (line, person);
19743
19509
  CREATE INDEX IF NOT EXISTS chat_sent_pending ON chat_sent (exp) WHERE stage = 'sent';
@@ -19780,6 +19546,7 @@ CREATE TABLE IF NOT EXISTS tasks (
19780
19546
  task TEXT NOT NULL,
19781
19547
  created_at INTEGER NOT NULL,
19782
19548
  updated_at INTEGER NOT NULL,
19549
+ request TEXT,
19783
19550
  PRIMARY KEY (task_id, role)
19784
19551
  ) STRICT;
19785
19552
  CREATE INDEX IF NOT EXISTS tasks_by_key ON tasks (key, updated_at DESC, task_id DESC, role DESC);
@@ -19808,37 +19575,20 @@ CREATE TABLE IF NOT EXISTS idempotency (
19808
19575
  created_at INTEGER NOT NULL,
19809
19576
  PRIMARY KEY (key, ikey)
19810
19577
  ) STRICT;
19811
- `, kvGet = (db, k) => {
19812
- const row = db.prepare("SELECT v FROM kv WHERE k = ?").get(k);
19813
- return row?.v === undefined || row.v === null ? undefined : String(row.v);
19814
- }, kvSet = (db, k, v) => {
19815
- db.prepare("INSERT INTO kv (k, v) VALUES (?, ?) ON CONFLICT (k) DO UPDATE SET v = excluded.v").run(k, v);
19816
- }, schemaNumber = (value) => value !== undefined && /^[1-9][0-9]{0,8}$/.test(value) ? Number(value) : undefined, TASKS = "tasks LEFT JOIN delegations USING (task_id, role)", text2 = (v) => v === null || v === undefined ? undefined : String(v), num = (v) => v === null || v === undefined ? undefined : Number(v), json = (v) => JSON.parse(String(v)), opt = (v) => v === undefined ? null : v, UNSPACED;
19817
- var init_store2 = __esm(() => {
19818
- init_dist();
19819
- MIGRATIONS = [
19820
- { schema: 4, columns: [{ table: "tasks", column: "request", decl: "TEXT" }] },
19821
- {
19822
- schema: 5,
19823
- ddl: `CREATE TABLE IF NOT EXISTS dir_persons (
19578
+ -- A directory realm's cache of verified persons (spec \xA715.9); its tree head, Jelly key archive, own record and alarm
19579
+ -- are kv rows (dir.*).
19580
+ CREATE TABLE IF NOT EXISTS dir_persons (
19824
19581
  person TEXT PRIMARY KEY,
19825
19582
  entries TEXT NOT NULL,
19826
19583
  keys TEXT NOT NULL,
19827
19584
  erased INTEGER NOT NULL DEFAULT 0,
19828
19585
  size INTEGER NOT NULL,
19829
19586
  fetched_at INTEGER NOT NULL
19830
- ) STRICT;`
19831
- },
19832
- {
19833
- schema: 6,
19834
- ddl: `
19587
+ ) STRICT;
19588
+ -- This person's grants, one per subject, as signed; and the widening levels that lapsed with their signer (spec \xA76.9).
19835
19589
  CREATE TABLE IF NOT EXISTS grants (subject TEXT PRIMARY KEY, grant TEXT NOT NULL) STRICT;
19836
19590
  CREATE TABLE IF NOT EXISTS grant_reviews (subject TEXT PRIMARY KEY, id TEXT NOT NULL, levels TEXT NOT NULL, at INTEGER NOT NULL) STRICT;
19837
- `
19838
- },
19839
- {
19840
- schema: 7,
19841
- ddl: `
19591
+ -- Requests held for this person to answer (spec \xA76.9, "Holds"): the envelope as received and its opened payload.
19842
19592
  CREATE TABLE IF NOT EXISTS held (
19843
19593
  id TEXT PRIMARY KEY,
19844
19594
  person TEXT NOT NULL,
@@ -19851,11 +19601,8 @@ CREATE TABLE IF NOT EXISTS held (
19851
19601
  ) STRICT;
19852
19602
  CREATE INDEX IF NOT EXISTS held_person ON held (person);
19853
19603
  CREATE INDEX IF NOT EXISTS held_exp ON held (exp);
19854
- `
19855
- },
19856
- {
19857
- schema: 8,
19858
- ddl: `
19604
+ -- Deployment "jelly" (spec \xA78.11): spent action-grant ids until their expiry, and the Jelly agent each session key
19605
+ -- was registered for. Notices (spec \xA78.2, "Notices") name their source (a line, a held request, an alarm).
19859
19606
  CREATE TABLE IF NOT EXISTS act_jti (jti TEXT PRIMARY KEY, exp INTEGER NOT NULL) STRICT;
19860
19607
  CREATE INDEX IF NOT EXISTS act_jti_exp ON act_jti (exp);
19861
19608
  CREATE TABLE IF NOT EXISTS session_agents (key TEXT PRIMARY KEY, agt TEXT NOT NULL) STRICT;
@@ -19870,11 +19617,8 @@ CREATE TABLE IF NOT EXISTS notices (
19870
19617
  acked_at INTEGER
19871
19618
  ) STRICT;
19872
19619
  CREATE INDEX IF NOT EXISTS notices_at ON notices (at);
19873
- `
19874
- },
19875
- {
19876
- schema: 9,
19877
- ddl: `
19620
+ -- Contact cards and message requests (spec \xA76.9): the latest card each other person sent this one; message requests
19621
+ -- held for the person to accept, as received and opened; and the contacts this person's agents suggested.
19878
19622
  CREATE TABLE IF NOT EXISTS contact_cards (person TEXT PRIMARY KEY, card TEXT NOT NULL, seq INTEGER NOT NULL, at INTEGER NOT NULL) STRICT;
19879
19623
  CREATE TABLE IF NOT EXISTS contact_requests (
19880
19624
  id TEXT PRIMARY KEY,
@@ -19887,10 +19631,14 @@ CREATE TABLE IF NOT EXISTS contact_requests (
19887
19631
  ) STRICT;
19888
19632
  CREATE INDEX IF NOT EXISTS contact_requests_person ON contact_requests (person);
19889
19633
  CREATE TABLE IF NOT EXISTS contact_suggestions (id TEXT PRIMARY KEY, session TEXT NOT NULL, email TEXT NOT NULL, at INTEGER NOT NULL) STRICT;
19890
- `,
19891
- columns: [{ table: "chat_sent", column: "held_at", decl: "INTEGER" }]
19892
- }
19893
- ];
19634
+ `, kvGet = (db, k) => {
19635
+ const row = db.prepare("SELECT v FROM kv WHERE k = ?").get(k);
19636
+ return row?.v === undefined || row.v === null ? undefined : String(row.v);
19637
+ }, kvSet = (db, k, v) => {
19638
+ db.prepare("INSERT INTO kv (k, v) VALUES (?, ?) ON CONFLICT (k) DO UPDATE SET v = excluded.v").run(k, v);
19639
+ }, storeNumber = (value) => value !== undefined && /^[1-9][0-9]{0,8}$/.test(value) ? Number(value) : undefined, RESET_HINT = "Nothing was changed. To start over, stop messagingd, move the whole Messaging home aside (it keeps the old messages, and the messagingd that wrote it still opens it), then set the home up again: messaging login, or messaging team create or team join for a self-hosted team", TASKS = "tasks LEFT JOIN delegations USING (task_id, role)", text2 = (v) => v === null || v === undefined ? undefined : String(v), num = (v) => v === null || v === undefined ? undefined : Number(v), json = (v) => JSON.parse(String(v)), opt = (v) => v === undefined ? null : v, UNSPACED;
19640
+ var init_store2 = __esm(() => {
19641
+ init_dist();
19894
19642
  UNSPACED = /([\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Thai}])/gu;
19895
19643
  });
19896
19644
 
@@ -21213,6 +20961,7 @@ class Daemon {
21213
20961
  static async start(options) {
21214
20962
  const paths = homePaths(options.home, options.jellyHost?.run);
21215
20963
  ensureHome(paths);
20964
+ checkStoreFile(paths.db);
21216
20965
  const jelly = options.jellyHost;
21217
20966
  if (jelly?.realm !== undefined && readConfig(paths.config).realm === undefined) {
21218
20967
  options = { ...options, jellyHost: { ...jelly, realm: await pinRealm(jelly.realm, options.wellKnown) } };
@@ -21895,7 +21644,7 @@ class Daemon {
21895
21644
  type: "presence",
21896
21645
  from: { person: me.person, device: identity.machine },
21897
21646
  to: { team: true },
21898
- payload: presencePayload(final ? this.#store.sessions().map((s) => ({ ...s, unregisteredAt: s.unregisteredAt ?? this.#now() })) : this.#store.sessions(), me.label, this.#store.next("presence"), this.#transport?.advertise?.(), {
21647
+ payload: presencePayload(final ? this.#store.sessions().map((s) => ({ ...s, unregisteredAt: s.unregisteredAt ?? this.#now() })) : this.#store.sessions(), me.label, this.#store.next("presence"), {
21899
21648
  wake: (s) => this.#wakes(s),
21900
21649
  share: new Set(this.#currentConfig().presence?.share ?? []),
21901
21650
  away: this.#away()
@@ -22045,7 +21794,6 @@ class Daemon {
22045
21794
  kind: p.kind,
22046
21795
  message,
22047
21796
  wake: p.wake ?? true,
22048
- ...p.external !== undefined ? { external: p.external } : {},
22049
21797
  ...chain !== undefined ? { delegation: chain.grants } : {}
22050
21798
  };
22051
21799
  const replyTo = p.replyTo ?? follow?.request;
@@ -22121,7 +21869,7 @@ class Daemon {
22121
21869
  }
22122
21870
  async#sendLine(p) {
22123
21871
  const session = this.#sessionOf(p.key);
22124
- const extra = ["contextId", "taskId", "messageId", "metadata", "extensions", "referenceTaskIds", "external", "onBehalfOf", "wake"].filter((name) => p[name] !== undefined);
21872
+ const extra = ["contextId", "taskId", "messageId", "metadata", "extensions", "referenceTaskIds", "onBehalfOf", "wake"].filter((name) => p[name] !== undefined);
22125
21873
  if (p.kind !== "message")
22126
21874
  throw new MessagingError("invalid_request", "a task goes to an agent, person/agent");
22127
21875
  if (extra.length > 0)
@@ -22712,7 +22460,7 @@ ${row.receipt ? "receipt" : "envelope"}`;
22712
22460
  if (payload.kind === "task") {
22713
22461
  const { realm, me } = this.#joined();
22714
22462
  try {
22715
- inboundChain(payload.delegation, { realm, now, sender, fromSession, me, toSession, task: payload.message.taskId, external: payload.external });
22463
+ inboundChain(payload.delegation, { realm, now, sender, fromSession, me, toSession, task: payload.message.taskId });
22716
22464
  } catch (error) {
22717
22465
  return reject(codeOf(error));
22718
22466
  }
@@ -22742,10 +22490,9 @@ ${row.receipt ? "receipt" : "envelope"}`;
22742
22490
  const recipient = this.#store.sessionByName(toSession);
22743
22491
  const config = this.#currentConfig();
22744
22492
  const code = inboundPolicy({
22745
- sender: { machine: sender.machine, person: sender.person, role: sender.role, session: fromSession, agent: this.#agentOf(sender.machine, fromSession) },
22493
+ sender: { machine: sender.machine, session: fromSession, agent: this.#agentOf(sender.machine, fromSession) },
22746
22494
  ...recipient ? { recipient: { session: recipient.name, agent: recipient.agent } } : {},
22747
- task: payload.kind === "task",
22748
- external: payload.external !== undefined
22495
+ task: payload.kind === "task"
22749
22496
  }, this.#policy, now);
22750
22497
  if (code !== undefined || !recipient)
22751
22498
  return reject(code ?? "unknown_session");
@@ -22763,7 +22510,7 @@ ${row.receipt ? "receipt" : "envelope"}`;
22763
22510
  return reject("invalid_request");
22764
22511
  const { realm, me } = this.#joined();
22765
22512
  try {
22766
- chain = inboundChain(payload.delegation, { realm, now, sender, fromSession, me, toSession: recipient.name, task: taskId, external: payload.external });
22513
+ chain = inboundChain(payload.delegation, { realm, now, sender, fromSession, me, toSession: recipient.name, task: taskId });
22767
22514
  } catch (error) {
22768
22515
  return reject(codeOf(error));
22769
22516
  }
@@ -22780,7 +22527,7 @@ ${row.receipt ? "receipt" : "envelope"}`;
22780
22527
  return reject("too_large");
22781
22528
  }
22782
22529
  const from = peerRef(sender, fromSession);
22783
- const message = withProvenance(payload.message, provenanceOf3(env, sender, now, payload.external));
22530
+ const message = withProvenance(payload.message, provenanceOf3(env, sender, now));
22784
22531
  const item = {
22785
22532
  id: env.id,
22786
22533
  key: recipient.key,
@@ -22845,7 +22592,7 @@ ${row.receipt ? "receipt" : "envelope"}`;
22845
22592
  this.#dropped("not_found", env.id);
22846
22593
  return;
22847
22594
  }
22848
- if (row.request !== undefined && env.replyTo !== row.request) {
22595
+ if (env.replyTo !== row.request) {
22849
22596
  this.#dropped("invalid_request", env.id);
22850
22597
  return;
22851
22598
  }
@@ -23152,9 +22899,6 @@ ${row.receipt ? "receipt" : "envelope"}`;
23152
22899
  onPeer: (machine, online) => this.#onPeer(machine, online),
23153
22900
  onState: (state) => this.#onState(state),
23154
22901
  ...this.#tuning.reconnect ? { reconnect: this.#tuning.reconnect } : {},
23155
- ...this.#config.transport?.direct ? { direct: this.#config.transport.direct } : {},
23156
- member: (machine) => this.#removed ? undefined : this.#realm?.device(machine),
23157
- presence: (machine) => this.#store.directoryRow(machine)?.presence,
23158
22902
  log: this.#log
23159
22903
  });
23160
22904
  this.#transport = transport;
@@ -23287,7 +23031,6 @@ ${row.receipt ? "receipt" : "envelope"}`;
23287
23031
  this.#stopRemoved();
23288
23032
  return;
23289
23033
  }
23290
- this.#transport?.rosterChanged?.();
23291
23034
  this.#sealedTo.clear();
23292
23035
  const gone = hosted ? "unknown_device" : "not_member";
23293
23036
  this.#tx(() => {
@@ -23739,6 +23482,8 @@ ${row.receipt ? "receipt" : "envelope"}`;
23739
23482
  let last = this.#store.getKv(DEVICE_NOTICES_KEY);
23740
23483
  if (last === undefined) {
23741
23484
  const admitted = changes.find((change) => change.device === me && change.change === "added");
23485
+ if (!start && changes.length === 0)
23486
+ return;
23742
23487
  if (start || admitted === undefined) {
23743
23488
  this.#store.setKv(DEVICE_NOTICES_KEY, String(head));
23744
23489
  return;
@@ -23938,7 +23683,7 @@ ${row.receipt ? "receipt" : "envelope"}`;
23938
23683
  throw new MessagingError("relay_unavailable", "config.json has no relay url");
23939
23684
  let result;
23940
23685
  await this.#append((roster) => {
23941
- const made = createInvite(roster, identity, { person: p.person, role: p.role ?? "member", ttlMs: p.ttlMs ?? DEFAULT_INVITE_TTL_MS, relay, now: this.#now() });
23686
+ const made = createInvite(roster, identity, { person: p.person, ttlMs: p.ttlMs ?? DEFAULT_INVITE_TTL_MS, relay, now: this.#now() });
23942
23687
  result = { ticket: made.ticket, inviteId: made.entry.body.inviteId, expires: made.entry.body.expires };
23943
23688
  return made.entry;
23944
23689
  });
@@ -23958,8 +23703,8 @@ ${row.receipt ? "receipt" : "envelope"}`;
23958
23703
  const invites = roster.entries().flatMap((entry) => {
23959
23704
  if (entry.kind !== "invite.create")
23960
23705
  return [];
23961
- const { inviteId, person, role, expires } = entry.body;
23962
- return [{ inviteId, person, role, expires, state: roster.invite(inviteId, now)?.state ?? "expired" }];
23706
+ const { inviteId, person, expires } = entry.body;
23707
+ return [{ inviteId, person, expires, state: roster.invite(inviteId, now)?.state ?? "expired" }];
23963
23708
  });
23964
23709
  return { team: { id: roster.team, name: roster.name }, members, invites };
23965
23710
  }
@@ -23982,7 +23727,7 @@ ${row.receipt ? "receipt" : "envelope"}`;
23982
23727
  }
23983
23728
  async#teamAdd(p) {
23984
23729
  const { identity } = this.#admin();
23985
- await this.#append((roster) => createEntry(roster, identity, "member.add", { machine: p.machine, enc: p.enc, person: p.person, role: p.role ?? "member", label: p.label }, this.#now()));
23730
+ await this.#append((roster) => createEntry(roster, identity, "member.add", { machine: p.machine, enc: p.enc, person: p.person, role: "member", label: p.label }, this.#now()));
23986
23731
  this.#log("info", "team.added", { machine: p.machine, person: p.person });
23987
23732
  return { ok: true };
23988
23733
  }
@@ -24099,7 +23844,7 @@ function chainPersons(sender, payload) {
24099
23844
  function peerRef(member, session) {
24100
23845
  return { address: formatAddress(member.person, session, member.label), person: member.person, agent: session, device: member.machine, label: member.label };
24101
23846
  }
24102
- function provenanceOf3(env, sender, now, external) {
23847
+ function provenanceOf3(env, sender, now) {
24103
23848
  const session = env.from.agent;
24104
23849
  return {
24105
23850
  envelopeId: env.id,
@@ -24109,8 +23854,7 @@ function provenanceOf3(env, sender, now, external) {
24109
23854
  device: sender.machine,
24110
23855
  label: sender.label,
24111
23856
  verified: "signature",
24112
- receivedAt: now,
24113
- ...external !== undefined ? { external: { principal: external.principal, gateway: sender.machine } } : {}
23857
+ receivedAt: now
24114
23858
  };
24115
23859
  }
24116
23860
  function withProvenance(message, provenance) {
@@ -24126,7 +23870,7 @@ function plaintextBytes(env) {
24126
23870
  }
24127
23871
  function requestOf(row) {
24128
23872
  if (row.request === undefined)
24129
- throw new MessagingError("invalid_state", "this task began before this version of messagingd; its updates cannot be sent");
23873
+ throw new MessagingError("invalid_state", "this task has no request to answer");
24130
23874
  return row.request;
24131
23875
  }
24132
23876
  function recipientSession(env) {
@@ -24258,641 +24002,6 @@ var init_daemon = __esm(() => {
24258
24002
  DEFAULT_INVITE_TTL_MS = DAY3;
24259
24003
  });
24260
24004
 
24261
- // packages/daemon/dist/direct-link.js
24262
- import { createServer } from "http";
24263
- import { WebSocket, WebSocketServer } from "ws";
24264
- function unavailable2(message) {
24265
- return new MessagingError("relay_unavailable", message);
24266
- }
24267
-
24268
- class Bucket {
24269
- capacity;
24270
- #tokens;
24271
- #at = Date.now();
24272
- constructor(capacity) {
24273
- this.capacity = capacity;
24274
- this.#tokens = capacity;
24275
- }
24276
- take(amount = 1) {
24277
- const now = Date.now();
24278
- this.#tokens = Math.min(this.capacity, this.#tokens + (now - this.#at) * this.capacity / 60000);
24279
- this.#at = now;
24280
- if (this.#tokens < amount)
24281
- return false;
24282
- this.#tokens -= amount;
24283
- return true;
24284
- }
24285
- }
24286
-
24287
- class Link {
24288
- peer;
24289
- role;
24290
- #ws;
24291
- #tx;
24292
- #rx;
24293
- #owner;
24294
- #pending = new Map;
24295
- #deliveries = Promise.resolve();
24296
- #closed = false;
24297
- constructor(peer, role, ws, keys, owner) {
24298
- this.peer = peer;
24299
- this.role = role;
24300
- this.#ws = ws;
24301
- this.#owner = owner;
24302
- const other = role === "initiator" ? "responder" : "initiator";
24303
- this.#tx = new DirectCipher(keys.record[role]);
24304
- this.#rx = new DirectCipher(keys.record[other]);
24305
- ws.removeAllListeners("message");
24306
- ws.on("message", (data, isBinary) => {
24307
- try {
24308
- this.#onMessage(data, isBinary);
24309
- } catch {
24310
- this.close(DIRECT_CLOSE.protocolError);
24311
- }
24312
- });
24313
- ws.on("close", () => this.#onClose());
24314
- }
24315
- get open() {
24316
- return !this.#closed && this.#ws.readyState === WebSocket.OPEN;
24317
- }
24318
- send(env, ackMs) {
24319
- const existing = this.#pending.get(env.id);
24320
- if (existing)
24321
- return existing.promise;
24322
- if (!this.open)
24323
- return Promise.reject(unavailable2("direct link closed"));
24324
- const text = JSON.stringify({ t: "send", env });
24325
- if (Buffer.byteLength(text) > LIMITS.frameBytes)
24326
- return Promise.reject(new MessagingError("too_large", "envelope exceeds the frame limit"));
24327
- let entry;
24328
- const promise = new Promise((resolve, reject) => {
24329
- const timer = setTimeout(() => this.#settle(env.id, unavailable2("direct peer did not answer in time")), ackMs);
24330
- timer.unref();
24331
- entry = { resolve, reject, timer, promise: undefined };
24332
- });
24333
- entry.promise = promise;
24334
- this.#pending.set(env.id, entry);
24335
- this.#write({ t: "send", env }, text);
24336
- return promise;
24337
- }
24338
- close(code) {
24339
- if (this.#closed)
24340
- return;
24341
- this.#closed = true;
24342
- this.#failAll();
24343
- this.#ws.close(code);
24344
- setTimeout(() => this.#ws.terminate(), 2000).unref();
24345
- }
24346
- #onClose() {
24347
- this.#closed = true;
24348
- this.#failAll();
24349
- this.#owner.forget(this);
24350
- }
24351
- #failAll() {
24352
- for (const id of [...this.#pending.keys()])
24353
- this.#settle(id, unavailable2("direct link closed"));
24354
- }
24355
- #settle(id, error) {
24356
- const entry = this.#pending.get(id);
24357
- if (!entry)
24358
- return;
24359
- this.#pending.delete(id);
24360
- clearTimeout(entry.timer);
24361
- if (error)
24362
- entry.reject(error);
24363
- else
24364
- entry.resolve();
24365
- }
24366
- #write(record, text = JSON.stringify(record)) {
24367
- if (this.open)
24368
- this.#ws.send(this.#tx.seal(Buffer.from(text, "utf8")));
24369
- }
24370
- #onMessage(data, isBinary) {
24371
- if (this.#closed)
24372
- return;
24373
- if (!isBinary)
24374
- return this.close(DIRECT_CLOSE.protocolError);
24375
- if (!this.#owner.isMember(this.peer))
24376
- return this.close(DIRECT_CLOSE.notMember);
24377
- const bytes = bufferOf(data);
24378
- let raw;
24379
- try {
24380
- raw = parseWire(this.#rx.open(bytes).toString("utf8"), { maxBytes: LIMITS.frameBytes });
24381
- } catch {
24382
- return this.close(DIRECT_CLOSE.protocolError);
24383
- }
24384
- const within = this.#owner.withinRate(this.peer, bytes.length);
24385
- const parsed = DirectRecord.safeParse(raw);
24386
- if (!parsed.success) {
24387
- const id = raw?.env?.id;
24388
- if (raw?.t === "send" && Ulid.safeParse(id).success)
24389
- this.#write({ t: "nack", id, code: within ? "invalid_request" : "rate_limited" });
24390
- return;
24391
- }
24392
- const record = parsed.data;
24393
- if (record.t === "send")
24394
- return this.#onSend(raw.env, record.env, within);
24395
- if (!within)
24396
- return;
24397
- if (record.t === "ack")
24398
- this.#settle(record.id);
24399
- else
24400
- this.#settle(record.id, new MessagingError(record.code, "refused by the direct peer"));
24401
- }
24402
- #onSend(raw, env, within) {
24403
- const nack = (code) => this.#write({ t: "nack", id: env.id, code });
24404
- if (!within)
24405
- return nack("rate_limited");
24406
- if (env.from.device !== this.peer || env.to.device !== this.#owner.self || env.type === "presence")
24407
- return nack("invalid_request");
24408
- this.#deliveries = this.#deliveries.then(async () => {
24409
- try {
24410
- await this.#owner.deliver(raw);
24411
- } catch (error) {
24412
- return nack(error instanceof MessagingError ? error.code : "internal");
24413
- }
24414
- this.#write({ t: "ack", id: env.id });
24415
- });
24416
- }
24417
- }
24418
-
24419
- class DirectLinks {
24420
- #options;
24421
- #limits;
24422
- #in = new Map;
24423
- #out = new Map;
24424
- #dials = new Map;
24425
- #rates = new Map;
24426
- #pendingByAddress = new Map;
24427
- #handshakes;
24428
- #sockets = new Set;
24429
- #dialing = new Set;
24430
- #pending = 0;
24431
- #server;
24432
- #wss;
24433
- #closed = false;
24434
- stats = { inbound: 0, outbound: 0, refused: 0, dialFailed: 0, delivered: 0 };
24435
- constructor(options) {
24436
- this.#options = options;
24437
- this.#limits = { ...DEFAULT_DIRECT_LIMITS, ...options.limits };
24438
- this.#handshakes = new Bucket(this.#limits.handshakesPerMinute);
24439
- }
24440
- get self() {
24441
- return this.#options.identity.machine;
24442
- }
24443
- isMember(machine) {
24444
- return machine !== this.self && this.#options.member(machine) !== undefined;
24445
- }
24446
- listen(host, port) {
24447
- if (this.#server)
24448
- return Promise.reject(new MessagingError("invalid_state", "already listening"));
24449
- const server = createServer((_req, res) => {
24450
- res.writeHead(426, { connection: "close" });
24451
- res.end();
24452
- });
24453
- server.headersTimeout = this.#limits.handshakeMs;
24454
- server.requestTimeout = this.#limits.handshakeMs;
24455
- server.maxConnections = this.#limits.pending + 256;
24456
- const wss = new WebSocketServer({ noServer: true, clientTracking: false, ...SOCKET_OPTIONS });
24457
- server.on("connection", (socket) => {
24458
- this.#sockets.add(socket);
24459
- socket.once("close", () => this.#sockets.delete(socket));
24460
- });
24461
- server.on("upgrade", (req, socket, head) => this.#upgrade(wss, req, socket, head));
24462
- this.#server = server;
24463
- this.#wss = wss;
24464
- return new Promise((resolve, reject) => {
24465
- server.once("error", reject);
24466
- server.listen({ host, port, exclusive: true }, () => {
24467
- server.off("error", reject);
24468
- server.on("error", () => {});
24469
- const bound = server.address();
24470
- if (bound === null || typeof bound === "string")
24471
- return reject(new MessagingError("internal", "listener has no address"));
24472
- resolve({ host: bound.address, port: bound.port });
24473
- });
24474
- });
24475
- }
24476
- async close() {
24477
- this.#closed = true;
24478
- for (const link of [...this.#in.values(), ...this.#out.values()])
24479
- link.close(1001);
24480
- for (const ws of this.#dialing)
24481
- ws.terminate();
24482
- this.#in.clear();
24483
- this.#out.clear();
24484
- const server = this.#server;
24485
- this.#server = undefined;
24486
- this.#wss?.close();
24487
- if (!server)
24488
- return;
24489
- await new Promise((resolve) => {
24490
- server.close(() => resolve());
24491
- for (const socket of this.#sockets)
24492
- socket.destroy();
24493
- });
24494
- }
24495
- hasLink(peer) {
24496
- return this.#linkTo(peer) !== undefined;
24497
- }
24498
- async send(peer, endpoints, env) {
24499
- if (this.#closed)
24500
- throw unavailable2("direct links are closed");
24501
- if (!this.isMember(peer))
24502
- throw new MessagingError("not_member", "not a current member");
24503
- let link = this.#linkTo(peer);
24504
- if (!link) {
24505
- if (endpoints.length === 0)
24506
- throw unavailable2("no direct link to this machine");
24507
- link = await this.#dialOnce(peer, endpoints);
24508
- }
24509
- await link.send(env, this.#limits.ackMs);
24510
- }
24511
- prune() {
24512
- for (const link of [...this.#in.values(), ...this.#out.values()])
24513
- if (!this.isMember(link.peer))
24514
- link.close(DIRECT_CLOSE.notMember);
24515
- }
24516
- forget(link) {
24517
- if (this.#in.get(link.peer) === link)
24518
- this.#in.delete(link.peer);
24519
- if (this.#out.get(link.peer) === link)
24520
- this.#out.delete(link.peer);
24521
- }
24522
- withinRate(peer, bytes) {
24523
- let rate = this.#rates.get(peer);
24524
- if (!rate) {
24525
- rate = { frames: new Bucket(this.#limits.framesPerMinute), bytes: new Bucket(this.#limits.bytesPerMinute) };
24526
- this.#rates.set(peer, rate);
24527
- }
24528
- return rate.frames.take() && rate.bytes.take(bytes);
24529
- }
24530
- async deliver(raw) {
24531
- await this.#options.onDeliver(raw);
24532
- this.stats.delivered++;
24533
- }
24534
- #linkTo(peer) {
24535
- for (const link of [this.#out.get(peer), this.#in.get(peer)])
24536
- if (link?.open)
24537
- return link;
24538
- return;
24539
- }
24540
- #upgrade(wss, req, socket, head) {
24541
- const address = req.socket.remoteAddress ?? "?";
24542
- const fromAddress = this.#pendingByAddress.get(address) ?? 0;
24543
- const path = new URL(req.url ?? "/", "http://direct").pathname;
24544
- if (this.#closed || path !== DIRECT_PATH || this.#pending >= this.#limits.pending || fromAddress >= this.#limits.pendingPerAddress || !this.#handshakes.take()) {
24545
- this.stats.refused++;
24546
- socket.destroy();
24547
- return;
24548
- }
24549
- this.#pending++;
24550
- this.#pendingByAddress.set(address, fromAddress + 1);
24551
- let released = false;
24552
- const release = () => {
24553
- if (released)
24554
- return;
24555
- released = true;
24556
- this.#pending--;
24557
- const left = (this.#pendingByAddress.get(address) ?? 1) - 1;
24558
- if (left > 0)
24559
- this.#pendingByAddress.set(address, left);
24560
- else
24561
- this.#pendingByAddress.delete(address);
24562
- };
24563
- const timer = setTimeout(() => {
24564
- release();
24565
- socket.destroy();
24566
- }, this.#limits.handshakeMs);
24567
- timer.unref();
24568
- socket.once("close", () => {
24569
- clearTimeout(timer);
24570
- release();
24571
- });
24572
- wss.handleUpgrade(req, socket, head, (ws) => this.#respond(ws, () => {
24573
- clearTimeout(timer);
24574
- release();
24575
- }));
24576
- }
24577
- #respond(ws, done) {
24578
- const { identity, team } = this.#options;
24579
- let state = { step: "hello" };
24580
- const refuse = () => {
24581
- state = { step: "done" };
24582
- this.stats.refused++;
24583
- done();
24584
- ws.close(DIRECT_CLOSE.authFailed);
24585
- setTimeout(() => ws.terminate(), 2000).unref();
24586
- };
24587
- ws.on("error", () => {});
24588
- ws.on("message", (data, isBinary) => {
24589
- try {
24590
- step(data, isBinary);
24591
- } catch {
24592
- if (state.step !== "done")
24593
- refuse();
24594
- }
24595
- });
24596
- ws.on("close", () => done());
24597
- const step = (data, isBinary) => {
24598
- if (state.step === "done")
24599
- return;
24600
- if (state.step === "hello") {
24601
- const hello = handshakeFrame(DirectHelloFrame, data, isBinary);
24602
- if (!hello)
24603
- return refuse();
24604
- const own = directEphemeral();
24605
- const transcript = directTranscript(hello, own.half);
24606
- let keys;
24607
- try {
24608
- keys = directKeys(own.key, hello.epk, transcript);
24609
- } catch {
24610
- return refuse();
24611
- }
24612
- const sig = sign(identity, DOMAIN.directAuth, directAuthPayload({ transcript, team, role: "responder", machine: identity.machine }));
24613
- state = { step: "auth", transcript, keys };
24614
- ws.send(JSON.stringify({ t: "accept", ...own.half, box: sealDirectBox(keys.box.responder, transcript, { team, machine: identity.machine, sig }) }));
24615
- return;
24616
- }
24617
- const { transcript, keys } = state;
24618
- const auth = handshakeFrame(DirectAuthFrame, data, isBinary);
24619
- const peer = auth && openIdentity(keys.box.initiator, transcript, auth.box);
24620
- if (!peer || peer.team !== team || !this.isMember(peer.machine))
24621
- return refuse();
24622
- const signed = directAuthPayload({ transcript, team, role: "initiator", machine: peer.machine, peer: identity.machine });
24623
- if (!verify(peer.machine, DOMAIN.directAuth, signed, peer.sig))
24624
- return refuse();
24625
- state = { step: "done" };
24626
- done();
24627
- if (this.#closed)
24628
- return void ws.close(1001);
24629
- const link = new Link(peer.machine, "responder", ws, keys, this);
24630
- this.#in.get(peer.machine)?.close(DIRECT_CLOSE.replaced);
24631
- this.#in.set(peer.machine, link);
24632
- this.stats.inbound++;
24633
- };
24634
- }
24635
- #dialOnce(peer, endpoints) {
24636
- let dial = this.#dials.get(peer);
24637
- if (!dial) {
24638
- dial = this.#dialAny(peer, endpoints).finally(() => this.#dials.delete(peer));
24639
- this.#dials.set(peer, dial);
24640
- }
24641
- return dial;
24642
- }
24643
- async#dialAny(peer, endpoints) {
24644
- let last;
24645
- for (const endpoint of endpoints) {
24646
- try {
24647
- const link = await this.#dial(peer, endpoint);
24648
- if (this.#closed) {
24649
- link.close(1001);
24650
- break;
24651
- }
24652
- this.#out.get(peer)?.close(1000);
24653
- this.#out.set(peer, link);
24654
- this.stats.outbound++;
24655
- return link;
24656
- } catch (error) {
24657
- last = error;
24658
- }
24659
- }
24660
- this.stats.dialFailed++;
24661
- throw last instanceof MessagingError ? last : unavailable2("could not reach the direct peer");
24662
- }
24663
- #dial(peer, endpoint) {
24664
- const { identity, team } = this.#options;
24665
- const host = endpoint.host.includes(":") ? `[${endpoint.host}]` : endpoint.host;
24666
- return new Promise((resolve, reject) => {
24667
- const ws = new WebSocket(`ws://${host}:${endpoint.port}${DIRECT_PATH}`, { ...SOCKET_OPTIONS, handshakeTimeout: this.#limits.dialMs, followRedirects: false });
24668
- this.#dialing.add(ws);
24669
- let settled = false;
24670
- const fail = (message) => {
24671
- if (settled)
24672
- return;
24673
- settled = true;
24674
- clearTimeout(timer);
24675
- this.#dialing.delete(ws);
24676
- ws.removeAllListeners("message");
24677
- if (ws.readyState === WebSocket.OPEN)
24678
- ws.close(DIRECT_CLOSE.authFailed);
24679
- else
24680
- ws.terminate();
24681
- reject(unavailable2(message));
24682
- };
24683
- const timer = setTimeout(() => fail("direct handshake timed out"), this.#limits.dialMs);
24684
- timer.unref();
24685
- const own = directEphemeral();
24686
- ws.on("error", () => fail("cannot reach the direct peer"));
24687
- ws.on("close", () => fail("the direct peer closed the link"));
24688
- ws.on("unexpected-response", () => fail("the direct peer refused the upgrade"));
24689
- ws.on("open", () => ws.send(JSON.stringify({ t: "hello", proto: DIRECT_PROTOCOL, ...own.half })));
24690
- ws.on("message", (data, isBinary) => {
24691
- try {
24692
- onAccept(data, isBinary);
24693
- } catch {
24694
- fail("bad handshake");
24695
- }
24696
- });
24697
- const onAccept = (data, isBinary) => {
24698
- if (settled)
24699
- return;
24700
- const accept = handshakeFrame(DirectAcceptFrame, data, isBinary);
24701
- if (!accept)
24702
- return fail("bad handshake frame");
24703
- const transcript = directTranscript(own.half, accept);
24704
- let keys;
24705
- try {
24706
- keys = directKeys(own.key, accept.epk, transcript);
24707
- } catch {
24708
- return fail("unusable handshake key");
24709
- }
24710
- const them = openIdentity(keys.box.responder, transcript, accept.box);
24711
- if (!them || them.team !== team || them.machine !== peer || !this.isMember(peer))
24712
- return fail("the direct peer is not the expected member");
24713
- if (!verify(peer, DOMAIN.directAuth, directAuthPayload({ transcript, team, role: "responder", machine: peer }), them.sig))
24714
- return fail("bad handshake signature");
24715
- const sig = sign(identity, DOMAIN.directAuth, directAuthPayload({ transcript, team, role: "initiator", machine: identity.machine, peer }));
24716
- ws.send(JSON.stringify({ t: "auth", box: sealDirectBox(keys.box.initiator, transcript, { team, machine: identity.machine, sig }) }));
24717
- settled = true;
24718
- clearTimeout(timer);
24719
- this.#dialing.delete(ws);
24720
- ws.removeAllListeners("close");
24721
- resolve(new Link(peer, "initiator", ws, keys, this));
24722
- };
24723
- });
24724
- }
24725
- }
24726
- function handshakeFrame(schema, data, isBinary) {
24727
- if (isBinary)
24728
- return;
24729
- const bytes = bufferOf(data);
24730
- if (bytes.length > DIRECT_HANDSHAKE_BYTES)
24731
- return;
24732
- try {
24733
- const parsed = schema.safeParse(parseWire(bytes.toString("utf8"), { maxBytes: DIRECT_HANDSHAKE_BYTES }));
24734
- return parsed.success ? parsed.data : undefined;
24735
- } catch {
24736
- return;
24737
- }
24738
- }
24739
- function openIdentity(key, transcript, box) {
24740
- try {
24741
- const parsed = DirectIdentity.safeParse(openDirectBox(key, transcript, box));
24742
- return parsed.success ? parsed.data : undefined;
24743
- } catch {
24744
- return;
24745
- }
24746
- }
24747
- function bufferOf(data) {
24748
- if (Buffer.isBuffer(data))
24749
- return data;
24750
- if (Array.isArray(data))
24751
- return Buffer.concat(data);
24752
- return Buffer.from(data);
24753
- }
24754
- var DIRECT_PATH = "/v1/direct", DIRECT_CLOSE, DEFAULT_DIRECT_LIMITS, RECORD_BYTES, SOCKET_OPTIONS;
24755
- var init_direct_link = __esm(() => {
24756
- init_dist();
24757
- DIRECT_CLOSE = { authFailed: 4001, protocolError: 4002, notMember: 4003, replaced: 4004 };
24758
- DEFAULT_DIRECT_LIMITS = {
24759
- pending: 16,
24760
- pendingPerAddress: 4,
24761
- handshakeMs: 5000,
24762
- handshakesPerMinute: 120,
24763
- dialMs: 2000,
24764
- ackMs: 5000,
24765
- framesPerMinute: LIMITS.relayFramesPerMinute,
24766
- bytesPerMinute: LIMITS.relayBytesPerMinute
24767
- };
24768
- RECORD_BYTES = LIMITS.frameBytes + 16;
24769
- SOCKET_OPTIONS = { maxPayload: RECORD_BYTES, perMessageDeflate: false };
24770
- });
24771
-
24772
- // packages/daemon/dist/direct-transport.js
24773
- function withDirect(relay, tuning = {}) {
24774
- return (options) => options.direct?.enabled === true && options.team !== undefined ? new DirectTransport(relay, options, options.direct, tuning) : relay(options);
24775
- }
24776
-
24777
- class DirectTransport {
24778
- #relay;
24779
- #links;
24780
- #options;
24781
- #config;
24782
- #listen;
24783
- #backoffMs;
24784
- #maxBackoffMs;
24785
- #backoff = new Map;
24786
- #advertised;
24787
- #running = false;
24788
- #counts = { direct: 0, relay: 0, fallback: 0 };
24789
- constructor(relay, options, config, tuning = {}) {
24790
- this.#options = options;
24791
- this.#config = config;
24792
- const listen = parseHostPort(config.listen ?? DEFAULT_DIRECT_LISTEN);
24793
- this.#listen = { host: listen.host, port: listen.port ?? 0 };
24794
- this.#backoffMs = tuning.backoffMs ?? 30000;
24795
- this.#maxBackoffMs = tuning.maxBackoffMs ?? 600000;
24796
- this.#links = new DirectLinks({
24797
- identity: options.identity,
24798
- team: options.team,
24799
- member: (machine) => options.member?.(machine),
24800
- onDeliver: (raw) => options.onDeliver(raw),
24801
- ...tuning.limits ? { limits: tuning.limits } : {}
24802
- });
24803
- this.#relay = relay(options);
24804
- }
24805
- start() {
24806
- if (this.#running)
24807
- return;
24808
- this.#running = true;
24809
- this.#links.listen(this.#listen.host, this.#listen.port).then((bound) => {
24810
- if (!this.#running)
24811
- return;
24812
- this.#advertised = advertisedEndpoints(this.#config, bound);
24813
- this.#options.log?.("info", "direct.listening", { host: bound.host, port: bound.port, advertised: this.#advertised.length });
24814
- }).catch((error) => this.#options.log?.("warn", "direct.listen.failed", { code: codeOf(error) })).finally(() => {
24815
- if (this.#running)
24816
- this.#relay.start();
24817
- });
24818
- }
24819
- async stop() {
24820
- this.#running = false;
24821
- this.#advertised = undefined;
24822
- await this.#links.close();
24823
- await this.#relay.stop();
24824
- }
24825
- get connected() {
24826
- return this.#relay.connected;
24827
- }
24828
- get stats() {
24829
- return { ...this.#links.stats, ...this.#counts };
24830
- }
24831
- async send(env) {
24832
- const to = this.#running && env.type !== "presence" && "device" in env.to ? env.to.device : undefined;
24833
- if (to !== undefined) {
24834
- const endpoints = this.#dialable(to);
24835
- if (endpoints.length > 0 || this.#links.hasLink(to)) {
24836
- try {
24837
- await this.#links.send(to, endpoints, env);
24838
- this.#backoff.delete(to);
24839
- this.#counts.direct++;
24840
- return;
24841
- } catch (error) {
24842
- this.#counts.fallback++;
24843
- this.#fail(to);
24844
- this.#options.log?.("info", "direct.fallback", { id: env.id, code: codeOf(error) });
24845
- }
24846
- }
24847
- }
24848
- await this.#relay.send(env);
24849
- this.#counts.relay++;
24850
- }
24851
- presence(env) {
24852
- this.#relay.presence(env);
24853
- }
24854
- retryNow() {
24855
- this.#relay.retryNow?.();
24856
- }
24857
- appendRoster(entry) {
24858
- return this.#relay.appendRoster(entry);
24859
- }
24860
- syncRoster(after) {
24861
- return this.#relay.syncRoster(after);
24862
- }
24863
- rosterChanged() {
24864
- this.#links.prune();
24865
- }
24866
- hasLink(machine) {
24867
- return this.#links.hasLink(machine);
24868
- }
24869
- advertise() {
24870
- const direct = this.#advertised;
24871
- return direct && direct.length > 0 ? { features: ["direct"], direct } : undefined;
24872
- }
24873
- #dialable(machine) {
24874
- const backoff = this.#backoff.get(machine);
24875
- if (backoff && Date.now() < backoff.until)
24876
- return [];
24877
- if (!this.#options.member?.(machine))
24878
- return [];
24879
- const presence = this.#options.presence?.(machine);
24880
- if (!presence?.features?.includes("direct") || !presence.direct)
24881
- return [];
24882
- return presence.direct.filter((endpoint) => mayDial(endpoint, isLoopback(this.#listen.host)));
24883
- }
24884
- #fail(machine) {
24885
- const prior = this.#backoff.get(machine);
24886
- const ms = prior ? Math.min(prior.ms * 2, this.#maxBackoffMs) : this.#backoffMs;
24887
- this.#backoff.set(machine, { until: Date.now() + ms, ms });
24888
- }
24889
- }
24890
- var init_direct_transport = __esm(() => {
24891
- init_direct_config();
24892
- init_direct_link();
24893
- init_log();
24894
- });
24895
-
24896
24005
  // packages/relay/dist/frames.js
24897
24006
  function text3(data) {
24898
24007
  if (Buffer.isBuffer(data))
@@ -24922,21 +24031,37 @@ var init_pages = __esm(() => {
24922
24031
  ROSTER_PAGE_BYTES = LIMITS.frameBytes - 4096;
24923
24032
  PAGE_CACHE_BYTES = 8 * MIB;
24924
24033
  });
24034
+ // packages/relay/dist/metrics.js
24035
+ var init_metrics = () => {};
24036
+
24925
24037
  // packages/relay/dist/store.js
24926
24038
  var init_store3 = __esm(() => {
24927
24039
  init_dist();
24040
+ init_metrics();
24928
24041
  });
24929
24042
 
24043
+ // packages/relay/dist/backoff.js
24044
+ function backoffDelay(attempt, minMs, maxMs, random = Math.random) {
24045
+ const ceiling = Math.min(maxMs, minMs * 2 ** Math.min(attempt, 30));
24046
+ return Math.round(ceiling / 2 + random() * (ceiling / 2));
24047
+ }
24048
+
24930
24049
  // packages/relay/dist/directory/jelly.js
24931
24050
  var init_jelly4 = __esm(() => {
24932
24051
  init_dist();
24933
24052
  });
24934
24053
 
24054
+ // packages/relay/dist/directory/witness.js
24055
+ var init_witness = __esm(() => {
24056
+ init_dist();
24057
+ });
24058
+
24935
24059
  // packages/relay/dist/directory/directory.js
24936
24060
  var WARM_MAX_MS;
24937
24061
  var init_directory3 = __esm(() => {
24938
24062
  init_dist();
24939
24063
  init_jelly4();
24064
+ init_witness();
24940
24065
  WARM_MAX_MS = 5 * 60000;
24941
24066
  });
24942
24067
 
@@ -24945,6 +24070,7 @@ var DAY_MS4 = 86400000, FEED_RETENTION_MS;
24945
24070
  var init_feed = __esm(() => {
24946
24071
  init_dist();
24947
24072
  init_jelly4();
24073
+ init_store3();
24948
24074
  FEED_RETENTION_MS = 90 * DAY_MS4;
24949
24075
  });
24950
24076
 
@@ -24977,6 +24103,7 @@ var init_module = __esm(() => {
24977
24103
  "dir.sth",
24978
24104
  "dir.lookup",
24979
24105
  "dir.device",
24106
+ "dir.heads",
24980
24107
  "dir.resolve",
24981
24108
  "dir.search",
24982
24109
  "dir.watch",
@@ -24993,7 +24120,7 @@ var init_store4 = __esm(() => {
24993
24120
  });
24994
24121
 
24995
24122
  // packages/relay/dist/server.js
24996
- var HIGH_WATER, MAX_BUFFERED;
24123
+ var FRAME_TYPES, HIGH_WATER, MAX_BUFFERED;
24997
24124
  var init_server2 = __esm(() => {
24998
24125
  init_dist();
24999
24126
  init_frames();
@@ -25001,20 +24128,43 @@ var init_server2 = __esm(() => {
25001
24128
  init_limits();
25002
24129
  init_pages();
25003
24130
  init_store3();
24131
+ init_metrics();
25004
24132
  init_module();
25005
24133
  init_store4();
24134
+ FRAME_TYPES = new Set([
24135
+ "challenge",
24136
+ "hello",
24137
+ "welcome",
24138
+ "ping",
24139
+ "pong",
24140
+ "join",
24141
+ "roster.append",
24142
+ "roster.sync",
24143
+ "roster",
24144
+ "roster.ok",
24145
+ "peer",
24146
+ "send",
24147
+ "presence",
24148
+ "recv.ack",
24149
+ "deliver",
24150
+ "ack",
24151
+ "nack",
24152
+ "error",
24153
+ "dir.ok",
24154
+ "dir.persons",
24155
+ "dir.headsAre",
24156
+ "dir.sthIs",
24157
+ "dir.resolved",
24158
+ "dir.changed",
24159
+ "dir.unbound",
24160
+ ...DIRECTORY_FRAMES
24161
+ ]);
25006
24162
  HIGH_WATER = 1024 * 1024;
25007
24163
  MAX_BUFFERED = 16 * 1024 * 1024;
25008
24164
  });
25009
24165
 
25010
- // packages/relay/dist/backoff.js
25011
- function backoffDelay(attempt, minMs, maxMs, random = Math.random) {
25012
- const ceiling = Math.min(maxMs, minMs * 2 ** Math.min(attempt, 30));
25013
- return Math.round(ceiling / 2 + random() * (ceiling / 2));
25014
- }
25015
-
25016
24166
  // packages/relay/dist/client.js
25017
- import { WebSocket as WebSocket2 } from "ws";
24167
+ import { WebSocket } from "ws";
25018
24168
 
25019
24169
  class RelayClient {
25020
24170
  #options;
@@ -25071,7 +24221,7 @@ class RelayClient {
25071
24221
  if (!ws)
25072
24222
  return;
25073
24223
  await new Promise((resolve) => {
25074
- if (ws.readyState === WebSocket2.CLOSED)
24224
+ if (ws.readyState === WebSocket.CLOSED)
25075
24225
  return resolve();
25076
24226
  const force = setTimeout(() => ws.terminate(), 2000);
25077
24227
  ws.once("close", () => {
@@ -25093,7 +24243,7 @@ class RelayClient {
25093
24243
  return existing.promise;
25094
24244
  const ws = this.#socket();
25095
24245
  if (!ws)
25096
- return Promise.reject(unavailable3("not connected to the relay"));
24246
+ return Promise.reject(unavailable2("not connected to the relay"));
25097
24247
  const text = frameText({ t: "send", env });
25098
24248
  if (text instanceof MessagingError)
25099
24249
  return Promise.reject(text);
@@ -25124,6 +24274,9 @@ class RelayClient {
25124
24274
  dirSth(args = {}) {
25125
24275
  return this.#dir("dir.sthIs", { t: "dir.sth", ...args });
25126
24276
  }
24277
+ dirHeads(persons, size) {
24278
+ return this.#dir("dir.headsAre", { t: "dir.heads", persons, ...size !== undefined ? { size } : {} });
24279
+ }
25127
24280
  dirLookup(persons, size) {
25128
24281
  return this.#dir("dir.persons", { t: "dir.lookup", persons, ...size !== undefined ? { size } : {} });
25129
24282
  }
@@ -25153,7 +24306,7 @@ class RelayClient {
25153
24306
  return Promise.reject(new MessagingError("invalid_request", "this relay link is not to a directory relay"));
25154
24307
  const ws = this.#socket();
25155
24308
  if (!ws)
25156
- return Promise.reject(unavailable3("not connected to the relay"));
24309
+ return Promise.reject(unavailable2("not connected to the relay"));
25157
24310
  const req = `r${++this.#req}`;
25158
24311
  const text = frameText({ ...frame, req });
25159
24312
  if (text instanceof MessagingError)
@@ -25161,7 +24314,7 @@ class RelayClient {
25161
24314
  const timer = setTimeout(() => {
25162
24315
  const request = this.#dirRequests.get(req);
25163
24316
  this.#dirRequests.delete(req);
25164
- request?.pending.reject(unavailable3("relay did not answer in time"));
24317
+ request?.pending.reject(unavailable2("relay did not answer in time"));
25165
24318
  }, REQUEST_MS);
25166
24319
  timer.unref();
25167
24320
  const pending = deferred();
@@ -25193,7 +24346,7 @@ class RelayClient {
25193
24346
  this.#helloSent = false;
25194
24347
  this.#error = undefined;
25195
24348
  this.#lastSeen = Date.now();
25196
- const ws = new WebSocket2(this.#options.url, SOCKET_OPTIONS2);
24349
+ const ws = new WebSocket(this.#options.url, SOCKET_OPTIONS);
25197
24350
  this.#ws = ws;
25198
24351
  this.#welcomeTimer = setTimeout(() => {
25199
24352
  if (this.#ws !== ws || this.#connected)
@@ -25215,7 +24368,7 @@ class RelayClient {
25215
24368
  clearInterval(this.#pingTimer);
25216
24369
  clearTimeout(this.#welcomeTimer);
25217
24370
  this.#connected = false;
25218
- const lost = unavailable3("relay connection closed");
24371
+ const lost = unavailable2("relay connection closed");
25219
24372
  for (const pending of this.#sends.values())
25220
24373
  pending.reject(lost);
25221
24374
  for (const request of [...this.#requests.values(), ...this.#dirRequests.values()]) {
@@ -25404,12 +24557,12 @@ ${id}`;
25404
24557
  #request(expect, frame) {
25405
24558
  const ws = this.#socket();
25406
24559
  if (!ws)
25407
- return Promise.reject(unavailable3("not connected to the relay"));
24560
+ return Promise.reject(unavailable2("not connected to the relay"));
25408
24561
  const req = `r${++this.#req}`;
25409
24562
  const text = frameText({ ...frame, req });
25410
24563
  if (text instanceof MessagingError)
25411
24564
  return Promise.reject(text);
25412
- const timer = setTimeout(() => this.#take(req)?.pending.reject(unavailable3("relay did not answer in time")), REQUEST_MS);
24565
+ const timer = setTimeout(() => this.#take(req)?.pending.reject(unavailable2("relay did not answer in time")), REQUEST_MS);
25413
24566
  timer.unref();
25414
24567
  const request = expect === "roster" ? { expect, pending: deferred(), timer } : { expect, pending: deferred(), timer };
25415
24568
  this.#requests.set(req, request);
@@ -25417,7 +24570,7 @@ ${id}`;
25417
24570
  return request.pending.promise;
25418
24571
  }
25419
24572
  #socket() {
25420
- return this.#connected && this.#ws?.readyState === WebSocket2.OPEN ? this.#ws : undefined;
24573
+ return this.#connected && this.#ws?.readyState === WebSocket.OPEN ? this.#ws : undefined;
25421
24574
  }
25422
24575
  }
25423
24576
  function createTeamOnRelay(url, genesis) {
@@ -25444,7 +24597,7 @@ function directoryOneShot(url, frame, expect) {
25444
24597
  }
25445
24598
  let settled = false;
25446
24599
  let sent = false;
25447
- const ws = new WebSocket2(url, SOCKET_OPTIONS2);
24600
+ const ws = new WebSocket(url, SOCKET_OPTIONS);
25448
24601
  const finish = (error, value) => {
25449
24602
  if (settled)
25450
24603
  return;
@@ -25456,7 +24609,7 @@ function directoryOneShot(url, frame, expect) {
25456
24609
  else
25457
24610
  resolve(value);
25458
24611
  };
25459
- const timer = setTimeout(() => finish(unavailable3("relay did not answer")), ONE_SHOT_MS);
24612
+ const timer = setTimeout(() => finish(unavailable2("relay did not answer")), ONE_SHOT_MS);
25460
24613
  ws.on("message", (data, isBinary) => {
25461
24614
  if (isBinary)
25462
24615
  return;
@@ -25468,7 +24621,10 @@ function directoryOneShot(url, frame, expect) {
25468
24621
  }
25469
24622
  if (raw?.t === "challenge" && !sent) {
25470
24623
  sent = true;
25471
- const outgoing = frameText({ ...frame, req: "r1" }, LIMITS.preAuthFrameBytes);
24624
+ if (typeof frame === "function" && raw.relay !== relayOrigin(url))
24625
+ return finish(unavailable2("relay id does not match the relay URL origin"));
24626
+ const body = typeof frame === "function" ? frame({ relay: String(raw.relay), nonce: String(raw.nonce) }) : frame;
24627
+ const outgoing = frameText({ ...body, req: "r1" }, LIMITS.preAuthFrameBytes);
25472
24628
  if (outgoing instanceof MessagingError)
25473
24629
  return finish(outgoing);
25474
24630
  ws.send(outgoing);
@@ -25481,18 +24637,26 @@ function directoryOneShot(url, frame, expect) {
25481
24637
  }
25482
24638
  }
25483
24639
  });
25484
- ws.on("error", (error) => finish(unavailable3(`cannot reach the relay: ${error.message}`)));
25485
- ws.on("close", () => finish(unavailable3("relay closed the connection")));
24640
+ ws.on("error", (error) => finish(unavailable2(`cannot reach the relay: ${error.message}`)));
24641
+ ws.on("close", () => finish(unavailable2("relay closed the connection")));
25486
24642
  });
25487
24643
  }
25488
24644
  function sthOnRelay(url, args = {}) {
25489
24645
  return directoryOneShot(url, { t: "dir.sth", ...args }, "dir.sthIs");
25490
24646
  }
25491
- function lookupOnRelay(url, persons, size) {
25492
- return directoryOneShot(url, { t: "dir.lookup", persons, ...size !== undefined ? { size } : {} }, "dir.persons");
24647
+ function lookupOnRelay(url, persons, size, identity, realm) {
24648
+ return directoryOneShot(url, (challenge) => ({
24649
+ t: "dir.lookup",
24650
+ persons,
24651
+ ...size !== undefined ? { size } : {},
24652
+ proof: { device: identity.machine, sig: sign(identity, DOMAIN.dirRead, dirReadPayload({ relay: challenge.relay, realm, device: identity.machine, nonce: challenge.nonce })) }
24653
+ }), "dir.persons");
24654
+ }
24655
+ function headsOnRelay(url, persons, size) {
24656
+ return directoryOneShot(url, { t: "dir.heads", persons, ...size !== undefined ? { size } : {} }, "dir.headsAre");
25493
24657
  }
25494
24658
  function deviceOnRelay(url, device) {
25495
- return directoryOneShot(url, { t: "dir.device", device }, "dir.persons");
24659
+ return directoryOneShot(url, { t: "dir.device", device }, "dir.headsAre");
25496
24660
  }
25497
24661
  function redeemOnRelay(url, entry) {
25498
24662
  return oneShot(url, { t: "roster.append", entry }, "roster.ok");
@@ -25510,7 +24674,7 @@ function fetchRosterAsMember(url, team, identity) {
25510
24674
  let welcomed = false;
25511
24675
  let req = 0;
25512
24676
  let page;
25513
- const ws = new WebSocket2(url, SOCKET_OPTIONS2);
24677
+ const ws = new WebSocket(url, SOCKET_OPTIONS);
25514
24678
  const finish = (error, value) => {
25515
24679
  if (settled)
25516
24680
  return;
@@ -25526,7 +24690,7 @@ function fetchRosterAsMember(url, team, identity) {
25526
24690
  page?.pending.reject(error);
25527
24691
  finish(error);
25528
24692
  };
25529
- const timer = setTimeout(() => fail(unavailable3("relay did not answer")), ONE_SHOT_MS);
24693
+ const timer = setTimeout(() => fail(unavailable2("relay did not answer")), ONE_SHOT_MS);
25530
24694
  const fetchPage = (after) => {
25531
24695
  const pending = deferred();
25532
24696
  const outgoing = frameText({ t: "roster.sync", req: `r${++req}`, after });
@@ -25575,8 +24739,8 @@ function fetchRosterAsMember(url, team, identity) {
25575
24739
  fail(new MessagingError(reply.code, reply.message));
25576
24740
  }
25577
24741
  });
25578
- ws.on("error", (error) => fail(unavailable3(`cannot reach the relay: ${error.message}`)));
25579
- ws.on("close", () => fail(unavailable3("relay closed the connection")));
24742
+ ws.on("error", (error) => fail(unavailable2(`cannot reach the relay: ${error.message}`)));
24743
+ ws.on("close", () => fail(unavailable2("relay closed the connection")));
25580
24744
  });
25581
24745
  }
25582
24746
  function oneShot(url, frame, expect) {
@@ -25590,7 +24754,7 @@ function oneShot(url, frame, expect) {
25590
24754
  const req = "r1";
25591
24755
  let settled = false;
25592
24756
  let sent = false;
25593
- const ws = new WebSocket2(url, SOCKET_OPTIONS2);
24757
+ const ws = new WebSocket(url, SOCKET_OPTIONS);
25594
24758
  const finish = (error, value) => {
25595
24759
  if (settled)
25596
24760
  return;
@@ -25602,7 +24766,7 @@ function oneShot(url, frame, expect) {
25602
24766
  else
25603
24767
  resolve(value);
25604
24768
  };
25605
- const timer = setTimeout(() => finish(unavailable3("relay did not answer")), ONE_SHOT_MS);
24769
+ const timer = setTimeout(() => finish(unavailable2("relay did not answer")), ONE_SHOT_MS);
25606
24770
  ws.on("message", (data, isBinary) => {
25607
24771
  if (isBinary)
25608
24772
  return;
@@ -25639,8 +24803,8 @@ function oneShot(url, frame, expect) {
25639
24803
  finish(new MessagingError(reply.code, reply.message));
25640
24804
  }
25641
24805
  });
25642
- ws.on("error", (error) => finish(unavailable3(`cannot reach the relay: ${error.message}`)));
25643
- ws.on("close", () => finish(unavailable3("relay closed the connection")));
24806
+ ws.on("error", (error) => finish(unavailable2(`cannot reach the relay: ${error.message}`)));
24807
+ ws.on("close", () => finish(unavailable2("relay closed the connection")));
25644
24808
  });
25645
24809
  }
25646
24810
  async function collectPages(after, pages, fetch2) {
@@ -25676,7 +24840,7 @@ function frameText(frame, limit = LIMITS.frameBytes) {
25676
24840
  return new MessagingError("too_large", `frame exceeds the relay's ${limit}-byte limit`);
25677
24841
  return json;
25678
24842
  }
25679
- function unavailable3(message) {
24843
+ function unavailable2(message) {
25680
24844
  return new MessagingError("relay_unavailable", message);
25681
24845
  }
25682
24846
  function deferred() {
@@ -25694,12 +24858,12 @@ function callback(fn) {
25694
24858
  fn();
25695
24859
  } catch {}
25696
24860
  }
25697
- var PING_MS = 20000, DEAD_MS, HANDSHAKE_MS = 1e4, WELCOME_MS = 15000, ONE_SHOT_MS = 15000, REQUEST_MS = 15000, MAX_PAGES = 256, SOCKET_OPTIONS2, RelayRefusal;
24861
+ var PING_MS = 20000, DEAD_MS, HANDSHAKE_MS = 1e4, WELCOME_MS = 15000, ONE_SHOT_MS = 15000, REQUEST_MS = 15000, MAX_PAGES = 256, SOCKET_OPTIONS, RelayRefusal;
25698
24862
  var init_client2 = __esm(() => {
25699
24863
  init_dist();
25700
24864
  init_frames();
25701
24865
  DEAD_MS = 2 * PING_MS + 5000;
25702
- SOCKET_OPTIONS2 = { maxPayload: LIMITS.frameBytes, perMessageDeflate: false, handshakeTimeout: HANDSHAKE_MS };
24866
+ SOCKET_OPTIONS = { maxPayload: LIMITS.frameBytes, perMessageDeflate: false, handshakeTimeout: HANDSHAKE_MS };
25703
24867
  RelayRefusal = class RelayRefusal extends MessagingError {
25704
24868
  retryAfterMs;
25705
24869
  constructor(code, message, retryAfterMs) {
@@ -25839,11 +25003,21 @@ class RelayDirectoryLink {
25839
25003
  }
25840
25004
  async lookup(persons, size) {
25841
25005
  const asks = persons.map((p) => ({ person: p.person, ...p.knownSeq !== undefined ? { knownSeq: p.knownSeq } : {} }));
25842
- const reply = parse5(DirPersonsFrame, await this.#read((client) => client.dirLookup(asks, size), () => lookupOnRelay(this.#options.url, asks, size)));
25006
+ const { url, identity, realm } = this.#options;
25007
+ const own = this.#options.person();
25008
+ const reply = parse5(DirPersonsFrame, await this.#read((client) => client.dirLookup(asks, size), () => lookupOnRelay(url, asks, size, identity, realm), asks.some((ask) => ask.person !== own)));
25843
25009
  return { sth: reply.sth, persons: reply.persons };
25844
25010
  }
25845
- async#read(authed, preAuth) {
25846
- const client = this.#connect();
25011
+ async heads(persons, size) {
25012
+ const reply = parse5(DirHeadsAreFrame, await this.#read((client) => client.dirHeads([...persons], size), () => headsOnRelay(this.#options.url, [...persons], size)));
25013
+ return { sth: reply.sth, head: reply.head, persons: reply.persons };
25014
+ }
25015
+ async#read(authed, preAuth, wait = false) {
25016
+ let client = this.#connect();
25017
+ if (wait && client && !client.connected)
25018
+ client = await this.#authed().catch(() => {
25019
+ return;
25020
+ });
25847
25021
  if (client?.connected) {
25848
25022
  try {
25849
25023
  return await authed(client);
@@ -25855,7 +25029,7 @@ class RelayDirectoryLink {
25855
25029
  return link(preAuth());
25856
25030
  }
25857
25031
  async device(device) {
25858
- const reply = parse5(DirPersonsFrame, await link(deviceOnRelay(this.#options.url, device)));
25032
+ const reply = parse5(DirHeadsAreFrame, await link(deviceOnRelay(this.#options.url, device)));
25859
25033
  return { sth: reply.sth, persons: reply.persons };
25860
25034
  }
25861
25035
  async resolve(query) {
@@ -25945,18 +25119,15 @@ var init_relay_transport = __esm(() => {
25945
25119
  function startDaemon2(options) {
25946
25120
  return startDaemon({
25947
25121
  ...options,
25948
- transportFactory: options.transportFactory ?? withDirect(relayTransport),
25122
+ transportFactory: options.transportFactory ?? relayTransport,
25949
25123
  relayOps: options.relayOps ?? relayOps,
25950
25124
  directoryLinkFactory: options.directoryLinkFactory ?? relayDirectoryLink
25951
25125
  });
25952
25126
  }
25953
25127
  var init_dist4 = __esm(() => {
25954
25128
  init_daemon();
25955
- init_direct_transport();
25956
25129
  init_relay_transport();
25957
25130
  init_relay_transport();
25958
- init_direct_transport();
25959
- init_direct_config();
25960
25131
  init_home();
25961
25132
  init_lock();
25962
25133
  init_log();
@@ -27273,7 +26444,7 @@ var devices = {
27273
26444
  "directory, removed ones last.",
27274
26445
  "link: on a new device Jelly does not enrol, shows its link request and a six-digit code (design \xA72.5).",
27275
26446
  "approve: on a device of yours that Jelly bound, shows what the link asks for, then approves it once you type",
27276
- "the code the new device shows. A device approved by link approves none. Removing devices is not in this CLI."
26447
+ "the code the new device shows. A device approved by link approves none. To remove a device: messaging login --remove <device>."
27277
26448
  ].join(`
27278
26449
  `),
27279
26450
  options: {
@@ -27647,16 +26818,15 @@ async function showSignIn(ctx, started, open) {
27647
26818
  const { bold } = ctx.out.style;
27648
26819
  const lead = started.purpose === "remove" ? "To remove that device, sign in at Jelly ID:" : started.purpose === "replace" ? "To replace your old device, sign in at Jelly ID with this one:" : started.purpose === "erase" ? "To erase your account, sign in at Jelly ID:" : "To sign this device in, open:";
27649
26820
  ctx.out.line(lead);
27650
- ctx.out.line(` ${started.verificationUriComplete}`);
27651
- ctx.out.line(`and check that the page shows the code ${bold(started.userCode)} and the key ${bold(started.fingerprint)}.`);
27652
- ctx.out.line(`(Or open ${started.verificationUri} and type the code.)`);
26821
+ ctx.out.line(` ${started.verificationUri}`);
26822
+ ctx.out.line(`and type the code ${bold(started.userCode)} there. Approve only if the page shows this device's key ${bold(started.fingerprint)}.`);
27653
26823
  }
27654
26824
  if (open) {
27655
- const opened = await (ctx.io.openUrl ?? openInBrowser)(started.verificationUriComplete);
26825
+ const opened = await (ctx.io.openUrl ?? openInBrowser)(started.verificationUri);
27656
26826
  if (!opened)
27657
- ctx.out.note("Could not open a browser here; open the link on any device.");
26827
+ ctx.out.note("Could not open a browser here; open the page on any device and type the code.");
27658
26828
  } else if (!ctx.json && (ctx.io.env.SSH_CONNECTION !== undefined || ctx.io.env.SSH_TTY !== undefined)) {
27659
- ctx.out.note("Over SSH: open the link on your laptop or phone; the code ties it to this machine.");
26829
+ ctx.out.note("Over SSH: open the page on your laptop or phone and type the code; the code ties it to this machine.");
27660
26830
  }
27661
26831
  }
27662
26832
  async function waitSignIn(ctx, client, started) {
@@ -27696,9 +26866,9 @@ var login = {
27696
26866
  summary: "Sign this device in to your account through the browser (hosted realm)",
27697
26867
  usage: "[--label <label>] [--kind laptop|server|other] [--open] [--no-wait] [--remove <device> [--reason removed|lost]]",
27698
26868
  details: [
27699
- "Prints a link to Jelly ID and a code. Open the link on any device (with --open, this machine's browser opens it),",
27700
- "check that the page shows the same code and key, and approve. This device then binds to your account: the first",
27701
- "device of your account, or one more. Over SSH, open the link on your laptop or phone.",
26869
+ "Prints Jelly ID's page and a code. Open the page on any device (with --open, this machine's browser opens it),",
26870
+ "type the code, check that the page shows the same key, and approve. This device then binds to your account: the",
26871
+ "first device of your account, or one more. Over SSH, open the page on your laptop or phone.",
27702
26872
  "With --remove, the sign-in removes another device of yours instead (a lost one, with --reason lost).",
27703
26873
  "Needs Jelly ID's device sign-in; until Jelly ID offers it, this says so and does nothing."
27704
26874
  ].join(`
@@ -27706,8 +26876,8 @@ var login = {
27706
26876
  options: {
27707
26877
  label: { type: "string", value: "<label>", description: "This device's label (default: its current one, else device)" },
27708
26878
  kind: { type: "string", value: "<kind>", description: "laptop (default), server or other" },
27709
- open: { type: "boolean", description: "Open the link in this machine's browser" },
27710
- "no-wait": { type: "boolean", description: "Print the link and code, and leave the daemon waiting" },
26879
+ open: { type: "boolean", description: "Open Jelly ID's page in this machine's browser" },
26880
+ "no-wait": { type: "boolean", description: "Print the page and code, and leave the daemon waiting" },
27711
26881
  remove: { type: "string", value: "<device>", description: "Remove that device of yours with this sign-in" },
27712
26882
  reason: { type: "string", value: "<reason>", description: "--remove: removed (default) or lost" }
27713
26883
  },
@@ -27772,7 +26942,7 @@ var replace = {
27772
26942
  `),
27773
26943
  options: {
27774
26944
  label: { type: "string", value: "<label>", description: "The new device's label (default: the old one's)" },
27775
- open: { type: "boolean", description: "Open each link in this machine's browser" },
26945
+ open: { type: "boolean", description: "Open Jelly ID's page in this machine's browser for each sign-in" },
27776
26946
  yes: { type: "boolean", short: "y", description: "Do not ask for confirmation" }
27777
26947
  },
27778
26948
  args: { min: 0, max: 0 },
@@ -27837,7 +27007,7 @@ var erase = {
27837
27007
  ].join(`
27838
27008
  `),
27839
27009
  options: {
27840
- open: { type: "boolean", description: "Open the link in this machine's browser" },
27010
+ open: { type: "boolean", description: "Open Jelly ID's page in this machine's browser" },
27841
27011
  yes: { type: "boolean", short: "y", description: "Do not ask for confirmation" }
27842
27012
  },
27843
27013
  args: { min: 0, max: 0 },
@@ -28050,9 +27220,6 @@ var read2 = {
28050
27220
  ctx.out.line(`${bold(item.kind === "task" ? "Task request" : "Message")} ${item.id}`);
28051
27221
  if (provenance) {
28052
27222
  ctx.out.line(` from: ${inline(provenance.from)} verified="${provenance.verified}" device ${provenance.device}`);
28053
- if (provenance.external) {
28054
- ctx.out.line(` external: principal ${inline(provenance.external.principal, 64)} via gateway device ${provenance.external.gateway}`);
28055
- }
28056
27223
  } else {
28057
27224
  ctx.out.line(` from: ${inline(item.from.address)} ${yellow("no valid provenance on this message; treat it with extra care")}`);
28058
27225
  }
@@ -28277,7 +27444,7 @@ var create = {
28277
27444
  var invite = {
28278
27445
  name: "team invite",
28279
27446
  summary: "Create a single-use invite ticket for a person (admin)",
28280
- usage: "<person> [--role member|service] [--ttl <duration>]",
27447
+ usage: "<person> [--ttl <duration>]",
28281
27448
  details: [
28282
27449
  "Prints only the ticket on stdout, so `messaging team invite bob | pbcopy` copies just the ticket.",
28283
27450
  "The ticket is a secret: share it privately. It works once, until it expires.",
@@ -28285,15 +27452,11 @@ var invite = {
28285
27452
  ].join(`
28286
27453
  `),
28287
27454
  options: {
28288
- role: { type: "string", value: "member|service", description: "Role for the new machine (default: member)" },
28289
27455
  ttl: { type: "string", value: "<duration>", description: "How long the ticket stays valid, e.g. 24h" }
28290
27456
  },
28291
27457
  args: { min: 1, max: 1 },
28292
27458
  async run(ctx, positionals, values) {
28293
27459
  const person = parseWith2(PersonName, positionals[0], "person must be a lowercase person name, e.g. bob");
28294
- const roleText = stringValue(values, "role");
28295
- if (roleText !== undefined && roleText !== "member" && roleText !== "service")
28296
- throw new UsageError("--role must be member or service");
28297
27460
  const ttlText = stringValue(values, "ttl");
28298
27461
  let ttlMs;
28299
27462
  if (ttlText !== undefined) {
@@ -28304,13 +27467,12 @@ var invite = {
28304
27467
  const client = await ctx.client();
28305
27468
  const result = await client.call("team.invite", {
28306
27469
  person,
28307
- ...roleText !== undefined ? { role: roleText } : {},
28308
27470
  ...ttlMs !== undefined ? { ttlMs } : {}
28309
27471
  });
28310
27472
  if (ctx.json) {
28311
27473
  ctx.out.json(result);
28312
27474
  } else {
28313
- ctx.out.note(`Invite ${result.inviteId} for ${person} (${roleText ?? "member"}), expires ${iso2(result.expires)} (${until(result.expires, ctx.now())}).`);
27475
+ ctx.out.note(`Invite ${result.inviteId} for ${person}, expires ${iso2(result.expires)} (${until(result.expires, ctx.now())}).`);
28314
27476
  ctx.out.line(result.ticket);
28315
27477
  }
28316
27478
  ctx.out.warn(`this ticket is a secret. Share it privately with ${person}, never in a public channel or a shared log.`);
@@ -28369,7 +27531,7 @@ var members2 = {
28369
27531
  const now = ctx.now();
28370
27532
  ctx.out.line();
28371
27533
  ctx.out.line(`Invites (${result.invites.length})`);
28372
- ctx.out.lines(table(["INVITE", "PERSON", "ROLE", "STATE", "EXPIRES"], result.invites.map((i) => [i.inviteId, i.person, i.role, i.state, i.state === "open" ? until(i.expires, now) : iso2(i.expires)]), style));
27534
+ ctx.out.lines(table(["INVITE", "PERSON", "STATE", "EXPIRES"], result.invites.map((i) => [i.inviteId, i.person, i.state, i.state === "open" ? until(i.expires, now) : iso2(i.expires)]), style));
28373
27535
  }
28374
27536
  }
28375
27537
  };
@@ -28431,10 +27593,10 @@ var remove = {
28431
27593
  var role = {
28432
27594
  name: "team role",
28433
27595
  summary: "Change a member's role (admin)",
28434
- usage: "<machine|person/label> <admin|member|service>",
27596
+ usage: "<machine|person/label> <admin|member>",
28435
27597
  args: { min: 2, max: 2 },
28436
27598
  async run(ctx, positionals) {
28437
- const newRole = parseWith2(MemberRole, positionals[1], "role must be admin, member or service");
27599
+ const newRole = parseWith2(MemberRole, positionals[1], "role must be admin or member");
28438
27600
  const { member } = await resolveMember(ctx, positionals[0]);
28439
27601
  const client = await ctx.client();
28440
27602
  const result = await client.call("team.role", { machine: member.machine, role: newRole });
@@ -28482,17 +27644,13 @@ var card2 = {
28482
27644
  var add = {
28483
27645
  name: "team add",
28484
27646
  summary: "Admit a machine by its public admit card (admin)",
28485
- usage: "<card|-> [--role member|service] [--yes]",
27647
+ usage: "<card|-> [--yes]",
28486
27648
  details: "Pass `-` to read the card from stdin (then pass --yes). The card has public keys only. You are admitting that exact machine. Asks for confirmation unless --yes.",
28487
27649
  options: {
28488
- role: { type: "string", value: "member|service", description: "Role for the new machine (default: member)" },
28489
27650
  yes: { type: "boolean", short: "y", description: "Do not ask for confirmation" }
28490
27651
  },
28491
27652
  args: { min: 1, max: 1 },
28492
27653
  async run(ctx, positionals, values) {
28493
- const roleText = stringValue(values, "role");
28494
- if (roleText !== undefined && roleText !== "member" && roleText !== "service")
28495
- throw new UsageError("--role must be member or service");
28496
27654
  const fromStdin = positionals[0] === "-";
28497
27655
  const text = (fromStdin ? await readAll(ctx.io.stdin, 8192) : positionals[0]).trim();
28498
27656
  const card = decodeAdmitCard(text);
@@ -28509,8 +27667,7 @@ var add = {
28509
27667
  machine: card.machine,
28510
27668
  enc: card.enc,
28511
27669
  person: card.person,
28512
- label: card.label,
28513
- ...roleText !== undefined ? { role: roleText } : {}
27670
+ label: card.label
28514
27671
  });
28515
27672
  if (ctx.json)
28516
27673
  ctx.out.json({ ok: true, person: card.person, label: card.label, machine: card.machine });
@@ -28872,6 +28029,37 @@ function cliMain(argv, start) {
28872
28029
  // packages/daemon/dist/bin/daemon-main.js
28873
28030
  init_dist2();
28874
28031
  import { resolve as resolve4 } from "path";
28032
+
28033
+ // packages/daemon/dist/bin/fatal.js
28034
+ import { writeSync as writeSync2 } from "fs";
28035
+ var sink;
28036
+ function setFatalLog(log) {
28037
+ sink = log;
28038
+ }
28039
+ function installFatalPolicy(name) {
28040
+ const fatal = (origin) => (error) => {
28041
+ const fields = { origin, error: error instanceof Error ? error.name : typeof error, ...frameOf(error) };
28042
+ try {
28043
+ sink?.("error", "daemon.fatal", fields);
28044
+ } catch {}
28045
+ const frames = error instanceof Error ? error.stack?.split(`
28046
+ `).slice(1).join(`
28047
+ `) ?? "" : "";
28048
+ writeSync2(2, `${name}: fatal ${fields.origin}: ${fields.error}
28049
+ ${frames}
28050
+ `);
28051
+ process.exit(70);
28052
+ };
28053
+ process.on("uncaughtException", fatal("uncaughtException"));
28054
+ process.on("unhandledRejection", fatal("unhandledRejection"));
28055
+ }
28056
+ function frameOf(error) {
28057
+ const frame = error instanceof Error ? error.stack?.split(`
28058
+ `).find((line) => line.trimStart().startsWith("at ")) : undefined;
28059
+ return frame ? { at: frame.trim().slice(3, 240) } : {};
28060
+ }
28061
+
28062
+ // packages/daemon/dist/bin/daemon-main.js
28875
28063
  var USAGE = `usage: messagingd [--home <dir>]
28876
28064
  messagingd --deployment jelly --home <dir> --act-key <path> --label <label>
28877
28065
  [--kind jelly-host] [--run <dir>] [--socket-group <gid>]
@@ -29002,6 +28190,7 @@ ${USAGE}`);
29002
28190
  stderr: process.stderr.isTTY === true,
29003
28191
  level: level === "debug" || level === "warn" || level === "error" ? level : "info"
29004
28192
  });
28193
+ setFatalLog(log);
29005
28194
  let daemon;
29006
28195
  try {
29007
28196
  const jellyHost = fromEnv ? {
@@ -29072,6 +28261,8 @@ function filterSqliteWarning() {
29072
28261
  // packages/daemon/dist/bin/standalone.js
29073
28262
  filterSqliteWarning();
29074
28263
  var { mode, args } = dispatch(process.argv0, process.argv.slice(2));
28264
+ if (mode !== "cli")
28265
+ installFatalPolicy("messagingd");
29075
28266
  var done = mode === "cli" ? cliMain(args, { daemonCommand: [process.execPath] }) : daemonMain(args);
29076
28267
  done.then((code) => {
29077
28268
  process.exitCode = code;