@joe-broadhead/messaging-bin-linux-x64-gnu 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/messagingd CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/bin/messagingd
3
- size: 91797472 bytes
4
- sha256: 8bf49cd428a461ceb01c87c0e7d49427331cac1773724e59bb164810267d9adb
3
+ size: 91826144 bytes
4
+ sha256: 17c9fef1eef307ffca13a1151dd708eaf6f63dda4e448339ac991162abed1317
@@ -6391,7 +6391,8 @@ var init_jelly = __esm(() => {
6391
6391
  jti: string2().regex(/^[\x21-\x7e]{1,128}$/, "printable jti"),
6392
6392
  iss: string2().max(2048).optional(),
6393
6393
  dev: string2().max(200).optional(),
6394
- cls: string2().max(32).optional()
6394
+ cls: string2().max(32).optional(),
6395
+ mbr: string2().max(200).optional()
6395
6396
  });
6396
6397
  JellySessionClaims = looseObject({
6397
6398
  aud: string2().min(1).max(2048),
@@ -6900,7 +6901,8 @@ var init_envelope = __esm(() => {
6900
6901
  label: MachineLabel,
6901
6902
  sessions: array(SessionInfo).max(LIMITS.presenceSessions),
6902
6903
  features: array(string2().max(64)).max(32).optional(),
6903
- direct: array(DirectEndpoint).min(1).max(4).optional()
6904
+ direct: array(DirectEndpoint).min(1).max(4).optional(),
6905
+ away: literal(true).optional()
6904
6906
  });
6905
6907
  PAYLOAD_BY_TYPE = {
6906
6908
  message: MessagePayload,
@@ -7102,7 +7104,7 @@ var init_relay = __esm(() => {
7102
7104
  function hostedRelayAuthPayload(args) {
7103
7105
  return { proto: RELAY_PROTOCOL, relay: args.relay, realm: args.realm, person: args.person, device: args.device, nonce: args.nonce, ts: args.ts };
7104
7106
  }
7105
- var Req2, Size, Handle, Email, OrgId, DirBindFrame, HostedHelloFrame, DirAppendFrame, DirKeysFrame, DirSthFrame, DirLookupFrame, DirDeviceFrame, DirResolveFrame, DirSearchFrame, DirWatchFrame, PresenceWatchFrame, DirEraseFrame, DirectoryClientFrame, HostedWelcomeFrame, DirOkFrame, DirSthIsFrame, DirPersonsFrame, ResolvedPerson, DirResolvedFrame, DirChangedFrame, DirUnboundFrame, DirectoryRelayFrame;
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;
7106
7108
  var init_directory_frames = __esm(() => {
7107
7109
  init_zod();
7108
7110
  init_common();
@@ -7167,6 +7169,17 @@ var init_directory_frames = __esm(() => {
7167
7169
  entries: array(LogEntry).min(1).max(LIMITS.devicesPerPerson),
7168
7170
  token: Jws
7169
7171
  });
7172
+ WAKE_TICKET_MAX_S = 7 * 24 * 60 * 60;
7173
+ WakeUrl = string2().max(2048).regex(/^https?:\/\/[^/?#@\s]+\/hosts\/hst_[A-Za-z0-9._:-]{1,123}\/wake$/, "a Jelly ID wake URL");
7174
+ WakeArmFrame = object({
7175
+ t: literal("wake.arm"),
7176
+ req: Req2,
7177
+ url: WakeUrl,
7178
+ ticket: Jws.refine((ticket) => ticket.length <= WAKE_TICKET_BYTES, { message: `at most ${WAKE_TICKET_BYTES} bytes` }),
7179
+ agents: array(SessionName).max(WAKE_AGENTS),
7180
+ awaiting: array(Ulid).max(WAKE_AWAITING)
7181
+ });
7182
+ WakeDisarmFrame = object({ t: literal("wake.disarm"), req: Req2 });
7170
7183
  DirectoryClientFrame = discriminatedUnion("t", [
7171
7184
  DirBindFrame,
7172
7185
  HostedHelloFrame,
@@ -7179,7 +7192,9 @@ var init_directory_frames = __esm(() => {
7179
7192
  DirSearchFrame,
7180
7193
  DirWatchFrame,
7181
7194
  PresenceWatchFrame,
7182
- DirEraseFrame
7195
+ DirEraseFrame,
7196
+ WakeArmFrame,
7197
+ WakeDisarmFrame
7183
7198
  ]);
7184
7199
  HostedWelcomeFrame = object({ t: literal("welcome"), sth: STH });
7185
7200
  DirOkFrame = object({
@@ -7261,7 +7276,7 @@ var init_direct = __esm(() => {
7261
7276
  });
7262
7277
 
7263
7278
  // packages/core/dist/schema/ipc/device.js
7264
- var BindingState, RealmAlarmSummary, DeviceStatus, DeviceListEntry, LinkText, Sas, LinkPreview, KeyFingerprint, LoginUserCode, LoginPurpose, LoginKind, LoginShown, LoginState, DEVICE_METHODS;
7279
+ var BindingState, RealmAlarmSummary, DeviceStatus, DeviceListEntry, LinkText, Sas, LinkPreview, KeyFingerprint, LoginUserCode, LoginPurpose, LoginKind, LoginShown, LoginState, DeviceRemoveParams, DeviceApproveParams, DEVICE_METHODS;
7265
7280
  var init_device = __esm(() => {
7266
7281
  init_zod();
7267
7282
  init_common();
@@ -7315,6 +7330,8 @@ var init_device = __esm(() => {
7315
7330
  object({ state: literal("failed"), purpose: LoginPurpose, code: ErrorCode, message: string2().max(500) }),
7316
7331
  object({ state: literal("cancelled"), purpose: LoginPurpose })
7317
7332
  ]);
7333
+ DeviceRemoveParams = strictObject({ device: MachineId, reason: _enum(["self", "removed", "lost"]).optional(), token: Jws.optional() });
7334
+ DeviceApproveParams = strictObject({ link: LinkText, sas: Sas });
7318
7335
  DEVICE_METHODS = {
7319
7336
  "device.status": { params: strictObject({}), result: DeviceStatus },
7320
7337
  "device.bind": {
@@ -7323,7 +7340,7 @@ var init_device = __esm(() => {
7323
7340
  },
7324
7341
  "device.list": { params: strictObject({}), result: object({ devices: array(DeviceListEntry) }) },
7325
7342
  "device.remove": {
7326
- params: strictObject({ device: MachineId, reason: _enum(["self", "removed", "lost"]).optional(), token: Jws.optional() }),
7343
+ params: DeviceRemoveParams,
7327
7344
  result: object({ seq: number2().int().nonnegative() })
7328
7345
  },
7329
7346
  "device.link": {
@@ -7340,9 +7357,9 @@ var init_device = __esm(() => {
7340
7357
  },
7341
7358
  "device.login.status": { params: strictObject({ waitMs: number2().int().min(0).max(120000).optional() }), result: LoginState },
7342
7359
  "device.login.cancel": { params: strictObject({}), result: object({ ok: literal(true) }) },
7343
- "device.approve.preview": { params: strictObject({ link: LinkText }), result: LinkPreview },
7360
+ "device.approve.preview": { params: DeviceApproveParams.partial({ sas: true }), result: LinkPreview },
7344
7361
  "device.approve": {
7345
- params: strictObject({ link: LinkText, sas: Sas }),
7362
+ params: DeviceApproveParams,
7346
7363
  result: object({ device: MachineId, seq: number2().int().nonnegative() })
7347
7364
  }
7348
7365
  };
@@ -7458,7 +7475,7 @@ var init_shapes = __esm(() => {
7458
7475
  });
7459
7476
 
7460
7477
  // packages/core/dist/schema/ipc/consent.js
7461
- var GrantTarget, Levels, GrantView, GrantReview, GrantSetParams, AskRequest, ContactRequestView, ContactSuggestion, HeldRequest, RequestAnswer, RequestText, PersonReach, CONSENT_METHODS;
7478
+ var GrantTarget, Levels, GrantView, GrantReview, GrantSetParams, AskRequest, ContactRequestView, ContactSuggestion, HeldRequest, RequestAnswer, RequestText, PersonReach, RequestAnswerParams, ContactRequestParams, CONSENT_METHODS;
7462
7479
  var init_consent2 = __esm(() => {
7463
7480
  init_zod();
7464
7481
  init_common();
@@ -7525,6 +7542,8 @@ var init_consent2 = __esm(() => {
7525
7542
  RequestAnswer = _enum(["once", "always", "accept", "decline", "block"]);
7526
7543
  RequestText = string2().min(1).refine((text) => text.length <= LIMITS.requestChars, { message: `at most ${LIMITS.requestChars} UTF-16 code units` });
7527
7544
  PersonReach = object({ person: PersonId, can: CardCan, source: _enum(["card", "default"]), at: Millis.optional() });
7545
+ RequestAnswerParams = strictObject({ id: Ulid, answer: RequestAnswer, all: boolean2().optional() });
7546
+ ContactRequestParams = strictObject({ email: Email, text: RequestText, suggestion: Ulid.optional() });
7528
7547
  CONSENT_METHODS = {
7529
7548
  "grant.list": {
7530
7549
  params: strictObject({ subject: GrantTarget.optional() }),
@@ -7534,14 +7553,25 @@ var init_consent2 = __esm(() => {
7534
7553
  "grant.set.preview": { params: GrantSetParams, result: object({ sentence: string2().max(500) }) },
7535
7554
  "request.list": { params: strictObject({}), result: object({ requests: array(HeldRequest).max(LIMITS.heldTotal * 3) }) },
7536
7555
  "request.answer": {
7537
- params: strictObject({ id: Ulid, answer: RequestAnswer, all: boolean2().optional() }),
7556
+ params: RequestAnswerParams,
7538
7557
  result: object({ answered: array(Ulid).max(LIMITS.heldTotal) })
7539
7558
  },
7559
+ "request.answer.preview": {
7560
+ params: RequestAnswerParams,
7561
+ result: object({
7562
+ kind: _enum(["message", "task", "contact", "suggestion"]),
7563
+ from: object({ name: string2().max(200), email: string2().max(320).optional(), org: _enum(["same", "other", "none"]) }),
7564
+ agent: SessionName.optional(),
7565
+ to: SessionName.optional(),
7566
+ count: number2().int().positive(),
7567
+ sentence: string2().max(500)
7568
+ })
7569
+ },
7540
7570
  "contact.request": {
7541
- params: strictObject({ email: Email, text: RequestText, suggestion: Ulid.optional() }),
7571
+ params: ContactRequestParams,
7542
7572
  result: object({ id: Ulid, conversation: RoomRef, person: PersonId })
7543
7573
  },
7544
- "contact.request.preview": { params: strictObject({ email: Email }), result: object({ found: boolean2() }) },
7574
+ "contact.request.preview": { params: ContactRequestParams.partial({ text: true }), result: object({ found: boolean2() }) },
7545
7575
  "card.get": { params: strictObject({ person: PersonId }), result: object({ sent: boolean2() }) }
7546
7576
  };
7547
7577
  });
@@ -7660,7 +7690,7 @@ var init_surfaces = __esm(() => {
7660
7690
  },
7661
7691
  "people.get": {
7662
7692
  params: strictObject({ person: PersonId }),
7663
- result: PersonSummary.extend({ devices: array(object({ device: MachineId, label: MachineLabel, self: boolean2() })) })
7693
+ result: PersonSummary.extend({ devices: array(object({ device: MachineId, label: MachineLabel, self: boolean2(), away: literal(true).optional() })) })
7664
7694
  }
7665
7695
  };
7666
7696
  });
@@ -7670,7 +7700,10 @@ var NoticeId, NOTICE_LINE_CHARS = 120, Notice, NoticeCounts, DaemonHealth, Agent
7670
7700
  var init_jelly2 = __esm(() => {
7671
7701
  init_zod();
7672
7702
  init_common();
7703
+ init_directory_frames();
7704
+ init_jelly();
7673
7705
  init_directory();
7706
+ init_device();
7674
7707
  init_shapes();
7675
7708
  init_surfaces();
7676
7709
  NoticeId = string2().regex(/^[mra]_[0-9A-Za-z]{1,64}$/, "notice id");
@@ -7724,8 +7757,32 @@ var init_jelly2 = __esm(() => {
7724
7757
  result: object({ notices: array(Notice), counts: NoticeCounts })
7725
7758
  },
7726
7759
  "notice.ack": { params: strictObject({ ids: array(NoticeId).min(1).max(200) }), result: object({ ok: literal(true) }) },
7760
+ "daemon.woke": { params: strictObject({}), result: object({ ok: literal(true) }) },
7761
+ "person.attached": { params: strictObject({ attached: boolean2() }), result: object({ ok: literal(true) }) },
7762
+ "wake.arm": {
7763
+ params: strictObject({
7764
+ url: WakeUrl,
7765
+ ticket: Jws.refine((ticket) => ticket.length <= WAKE_TICKET_BYTES, { message: `at most ${WAKE_TICKET_BYTES} bytes` }),
7766
+ agents: array(SessionName).max(WAKE_AGENTS)
7767
+ }),
7768
+ result: object({ relay: _enum(["armed", "pending"]), awaiting: number2().int().nonnegative() })
7769
+ },
7770
+ "wake.disarm": { params: strictObject({}), result: object({ relay: _enum(["disarmed", "pending"]) }) },
7771
+ "daemon.snapshot": {
7772
+ params: strictObject({ done: literal(true).optional() }),
7773
+ result: union([
7774
+ object({
7775
+ folder: string2().max(4096),
7776
+ files: array(object({ name: string2().max(64), bytes: number2().int().nonnegative(), sha256: string2().regex(/^[0-9a-f]{64}$/) })).max(8),
7777
+ storeSchema: number2().int().positive(),
7778
+ at: Millis,
7779
+ until: Millis
7780
+ }),
7781
+ object({ released: boolean2() })
7782
+ ])
7783
+ },
7727
7784
  "device.remove.preview": {
7728
- params: strictObject({ device: MachineId }),
7785
+ params: DeviceRemoveParams,
7729
7786
  result: object({
7730
7787
  label: MachineLabel,
7731
7788
  kind: DeviceKind.optional(),
@@ -7739,7 +7796,7 @@ var init_jelly2 = __esm(() => {
7739
7796
  });
7740
7797
 
7741
7798
  // packages/core/dist/schema/ipc.js
7742
- var MESSAGING_ERROR_RPC_CODE = -32000, SessionHost, IPC_PROTOCOL = 1, IpcHello, Ok, Empty, DaemonStatus, InviteSummary, IPC_METHODS, AGENT_METHODS, HOST_SCOPE_METHODS, IpcEvent;
7799
+ var MESSAGING_ERROR_RPC_CODE = -32000, SessionHost, IPC_PROTOCOL = 1, IpcHello, Ok, Empty, DaemonStatus, InviteSummary, IPC_METHODS, AGENT_METHODS, HUMAN_METHODS, ADMIN_METHODS, HOST_SCOPE_METHODS, IpcEvent;
7743
7800
  var init_ipc = __esm(() => {
7744
7801
  init_zod();
7745
7802
  init_device();
@@ -7980,7 +8037,64 @@ var init_ipc = __esm(() => {
7980
8037
  "task.update",
7981
8038
  "task.cancel"
7982
8039
  ];
7983
- HOST_SCOPE_METHODS = ["notice.subscribe", "notice.list", "conv.list", "daemon.stop"];
8040
+ HUMAN_METHODS = [
8041
+ "conv.list",
8042
+ "conv.read",
8043
+ "conv.markRead",
8044
+ "conv.subscribe",
8045
+ "person.send",
8046
+ "people.search",
8047
+ "people.get",
8048
+ "grant.list",
8049
+ "grant.set",
8050
+ "grant.set.preview",
8051
+ "request.list",
8052
+ "request.answer",
8053
+ "request.answer.preview",
8054
+ "contact.request",
8055
+ "contact.request.preview",
8056
+ "card.get",
8057
+ "agents.list",
8058
+ "agents.read",
8059
+ "notice.subscribe",
8060
+ "notice.list",
8061
+ "notice.ack",
8062
+ "device.approve.preview",
8063
+ "device.remove.preview",
8064
+ "channel.create",
8065
+ "channel.invite",
8066
+ "channel.leave",
8067
+ "channel.remove"
8068
+ ];
8069
+ ADMIN_METHODS = [
8070
+ "daemon.stop",
8071
+ "daemon.woke",
8072
+ "person.attached",
8073
+ "daemon.snapshot",
8074
+ "wake.arm",
8075
+ "wake.disarm",
8076
+ "device.list",
8077
+ "device.remove",
8078
+ "device.link",
8079
+ "device.login",
8080
+ "device.login.status",
8081
+ "device.login.cancel",
8082
+ "device.approve",
8083
+ "identity.rotateEnc",
8084
+ "team.create",
8085
+ "team.invite",
8086
+ "team.join",
8087
+ "team.members",
8088
+ "team.remove",
8089
+ "team.role",
8090
+ "team.revokeInvite",
8091
+ "team.add",
8092
+ "team.pair",
8093
+ "team.claim",
8094
+ "team.pairCancel",
8095
+ "team.prepare"
8096
+ ];
8097
+ HOST_SCOPE_METHODS = ["notice.subscribe", "notice.list", "conv.list", "daemon.stop", "daemon.woke", "person.attached", "daemon.snapshot", "wake.arm", "wake.disarm"];
7984
8098
  IpcEvent = discriminatedUnion("type", [
7985
8099
  object({ type: literal("inbox"), key: SessionKey, item: InboxItem }),
7986
8100
  object({ type: literal("receipt"), key: SessionKey, envelopeId: Ulid, to: SessionAddress, stage: DeliveryStage, code: ErrorCode.optional() }),
@@ -8047,7 +8161,7 @@ var init_b64u = __esm(() => {
8047
8161
  });
8048
8162
 
8049
8163
  // packages/core/dist/version.js
8050
- var VERSION = "0.4.0";
8164
+ var VERSION = "0.5.0";
8051
8165
 
8052
8166
  // packages/core/dist/jcs.js
8053
8167
  function hasLoneSurrogate(text) {
@@ -9273,6 +9387,9 @@ var init_roster2 = __esm(() => {
9273
9387
 
9274
9388
  // packages/core/dist/jelly-host.js
9275
9389
  import { verify as ed25519Verify2 } from "crypto";
9390
+ function actionClass(method) {
9391
+ return Object.hasOwn(ACTION_CLASSES, method) ? ACTION_CLASSES[method] : "local";
9392
+ }
9276
9393
  function grantInput(params) {
9277
9394
  return sha256(canonicalize(params ?? {}));
9278
9395
  }
@@ -9342,9 +9459,16 @@ function verifyActGrant(token, check) {
9342
9459
  throw fail("is for other params");
9343
9460
  checkTimes(claims.iat, claims.exp, ACT_GRANT_MAX_S, check.now, fail);
9344
9461
  }
9345
- if (claims.cls !== undefined && claims.act !== "host" && claims.cls !== (CHAT_CLASS.has(check.method) ? "chat" : "local"))
9346
- throw fail("names another action class");
9347
- return { scope: claims.act === "host" ? "host" : "action", jti: claims.jti, exp: claims.exp, dev: claims.dev };
9462
+ if (claims.act === "host") {
9463
+ if (claims.cls !== undefined)
9464
+ throw fail("is host scope, which names no action class");
9465
+ } else {
9466
+ if (claims.cls === undefined)
9467
+ throw fail("names no action class");
9468
+ if (claims.cls !== actionClass(check.method))
9469
+ throw fail(`names action class ${claims.cls}, not ${actionClass(check.method)}`);
9470
+ }
9471
+ return { scope: claims.act === "host" ? "host" : "action", jti: claims.jti, exp: claims.exp, dev: claims.dev, mbr: claims.mbr };
9348
9472
  }
9349
9473
  function verifySessionTicket(token, key, now) {
9350
9474
  const fail = (why) => new MessagingError("ticket_invalid", `the session ticket ${why}`);
@@ -9379,7 +9503,7 @@ function parseActKey(text) {
9379
9503
  }
9380
9504
  return parsed.data;
9381
9505
  }
9382
- var ACT_GRANT_MAX_S = 60, HOST_GRANT_MAX_S = 600, SESSION_TICKET_MAX_S, JELLY_HOST_SKEW_S = 5, CHAT_CLASS;
9506
+ var ACT_GRANT_MAX_S = 60, HOST_GRANT_MAX_S = 600, SESSION_TICKET_MAX_S, JELLY_HOST_SKEW_S = 5, CHAT_METHODS, VIEW_METHODS, classOf2 = (method) => CHAT_METHODS.includes(method) ? "chat" : VIEW_METHODS.includes(method) ? "view" : "local", ACTION_CLASSES;
9383
9507
  var init_jelly_host = __esm(() => {
9384
9508
  init_b64u();
9385
9509
  init_hash();
@@ -9388,8 +9512,24 @@ var init_jelly_host = __esm(() => {
9388
9512
  init_wire();
9389
9513
  init_errors3();
9390
9514
  init_jelly();
9515
+ init_ipc();
9391
9516
  SESSION_TICKET_MAX_S = 7 * 24 * 60 * 60;
9392
- CHAT_CLASS = new Set(["person.send", "conv.markRead"]);
9517
+ CHAT_METHODS = ["person.send", "conv.markRead", "notice.ack"];
9518
+ VIEW_METHODS = [
9519
+ "conv.list",
9520
+ "conv.read",
9521
+ "conv.subscribe",
9522
+ "people.search",
9523
+ "people.get",
9524
+ "grant.list",
9525
+ "request.list",
9526
+ "agents.list",
9527
+ "agents.read",
9528
+ "notice.subscribe",
9529
+ "notice.list",
9530
+ "device.list"
9531
+ ];
9532
+ ACTION_CLASSES = Object.freeze(Object.fromEntries([...HUMAN_METHODS, ...ADMIN_METHODS].map((method) => [method, classOf2(method)])));
9393
9533
  });
9394
9534
 
9395
9535
  // packages/core/dist/directory/person.js
@@ -12634,6 +12774,7 @@ var init_dist = __esm(() => {
12634
12774
  init_contact_card();
12635
12775
  init_delegation2();
12636
12776
  init_task();
12777
+ init_a2a();
12637
12778
  init_direct2();
12638
12779
  init_peer_text();
12639
12780
  init_agent_tools();
@@ -15583,7 +15724,8 @@ function presencePayload(rows, label, seq, extra, policy) {
15583
15724
  label,
15584
15725
  sessions: [],
15585
15726
  features: [...FEATURES, ...extra?.features ?? []],
15586
- ...extra?.direct ? { direct: extra.direct } : {}
15727
+ ...extra?.direct ? { direct: extra.direct } : {},
15728
+ ...policy.away === true ? { away: true } : {}
15587
15729
  };
15588
15730
  return { ...payload, sessions: fitPresence(sessions, jcsBytes(payload), LIMITS.presenceBytes) };
15589
15731
  }
@@ -17019,6 +17161,12 @@ class DirectoryHome {
17019
17161
  this.#options.log("info", "directory.approved", { seq: done.seq });
17020
17162
  return done;
17021
17163
  }
17164
+ async wake(state) {
17165
+ const link = this.#link;
17166
+ if (!link?.wake)
17167
+ return "pending";
17168
+ return link.wake(state);
17169
+ }
17022
17170
  presenceWatch(persons) {
17023
17171
  this.#link?.presenceWatch(persons).catch((error) => this.#failed("directory.presence.watch", error));
17024
17172
  }
@@ -17033,6 +17181,7 @@ class DirectoryHome {
17033
17181
  },
17034
17182
  send: (env) => home.#link ? home.#link.send(env) : Promise.reject(noLink()),
17035
17183
  presence: (env) => home.#link?.presence(env),
17184
+ retryNow: () => home.#link?.retryNow?.(),
17036
17185
  appendRoster: () => Promise.reject(new MessagingError("invalid_state", "a directory realm has no roster")),
17037
17186
  syncRoster: () => Promise.reject(new MessagingError("invalid_state", "a directory realm has no roster"))
17038
17187
  };
@@ -17147,7 +17296,12 @@ class JellyAuth {
17147
17296
  return refuse(new MessagingError("grant_invalid", "the host-scope grant is bound to another connection"));
17148
17297
  }
17149
17298
  }
17150
- this.#deps.log("info", "jelly.grant", { method: call.method, scope: grant.scope, ...grant.dev !== undefined ? { dev: grant.dev.slice(0, 200) } : {} });
17299
+ this.#deps.log("info", "jelly.grant", {
17300
+ method: call.method,
17301
+ scope: grant.scope,
17302
+ ...grant.dev !== undefined ? { dev: grant.dev.slice(0, 200) } : {},
17303
+ ...grant.mbr !== undefined ? { mbr: grant.mbr.slice(0, 200) } : {}
17304
+ });
17151
17305
  return { scope: grant.scope, expiresAt: (grant.exp + JELLY_HOST_SKEW_S) * 1000 };
17152
17306
  }
17153
17307
  ticket(p) {
@@ -17185,6 +17339,292 @@ var init_jelly3 = __esm(() => {
17185
17339
  HOST_SCOPE = new Set(HOST_SCOPE_METHODS);
17186
17340
  });
17187
17341
 
17342
+ // packages/daemon/dist/jelly-env.js
17343
+ import { delimiter, isAbsolute } from "path";
17344
+ function jellyEnvNamed(env) {
17345
+ return JELLY_ENV.filter((name) => (env[name] ?? "") !== "");
17346
+ }
17347
+ function fail2(message) {
17348
+ throw new MessagingError("invalid_request", message);
17349
+ }
17350
+ function required3(env, name) {
17351
+ const value = env[name] ?? "";
17352
+ if (value === "")
17353
+ fail2(`${name} is not set: a Jelly host sets it with JELLY_ACTION_KEY`);
17354
+ return value;
17355
+ }
17356
+ function folder(name, value, separator = delimiter) {
17357
+ if (!isAbsolute(value))
17358
+ fail2(`${name} is not an absolute path`);
17359
+ if (value.includes(separator))
17360
+ fail2(`${name} names more than one folder (joined with "${separator}"); the daemon takes one`);
17361
+ return value;
17362
+ }
17363
+ function relayUrlOf(url) {
17364
+ let parsed;
17365
+ try {
17366
+ parsed = new URL(url);
17367
+ } catch {
17368
+ return fail2(`JELLY_RESOURCES' ${JELLY_RESOURCE_NAME} url is not a URL`);
17369
+ }
17370
+ const loopback = /^(?:localhost|127\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[::1\])$/.test(parsed.hostname);
17371
+ const scheme = parsed.protocol === "https:" ? "wss:" : parsed.protocol === "http:" && loopback ? "ws:" : undefined;
17372
+ if (scheme === undefined)
17373
+ fail2(`JELLY_RESOURCES' ${JELLY_RESOURCE_NAME} url is not https:// (or http:// to a loopback host)`);
17374
+ if (parsed.username !== "" || parsed.password !== "" || parsed.search !== "" || parsed.hash !== "" || parsed.pathname !== "/" && parsed.pathname !== "") {
17375
+ fail2(`JELLY_RESOURCES' ${JELLY_RESOURCE_NAME} url is not an origin: the relay is derived from its origin alone`);
17376
+ }
17377
+ return `${scheme}//${parsed.host}/v1/connect`;
17378
+ }
17379
+ function jellyEnvRealm(env) {
17380
+ let raw;
17381
+ try {
17382
+ raw = parseWire(required3(env, "JELLY_RESOURCES"), { maxBytes: 64 * 1024 });
17383
+ } catch (error) {
17384
+ if (error instanceof MessagingError && /JELLY_RESOURCES is not set/.test(error.message))
17385
+ throw error;
17386
+ return fail2("JELLY_RESOURCES is not JSON");
17387
+ }
17388
+ const list = Resources.safeParse(raw);
17389
+ if (!list.success)
17390
+ fail2("JELLY_RESOURCES is not a list of discovery entries");
17391
+ const entries = list.data.map((item) => ResourceEntry.safeParse(item)).filter((parsed) => parsed.success && parsed.data.name === JELLY_RESOURCE_NAME);
17392
+ if (entries.length === 0)
17393
+ fail2(`JELLY_RESOURCES has no ${JELLY_RESOURCE_NAME} entry`);
17394
+ if (entries.length > 1)
17395
+ fail2(`JELLY_RESOURCES has more than one ${JELLY_RESOURCE_NAME} entry`);
17396
+ const entry = entries[0].data;
17397
+ const realm = DirectoryRealmConfig.strict().safeParse({
17398
+ kind: "directory",
17399
+ url: relayUrlOf(entry.url),
17400
+ issuer: required3(env, "JELLY_ID_ISSUER"),
17401
+ resource: entry.resource,
17402
+ key: required3(env, "JELLY_SERVICE_ANCHOR")
17403
+ });
17404
+ if (!realm.success) {
17405
+ const at = realm.error.issues[0]?.path[0];
17406
+ fail2(at === "issuer" ? "JELLY_ID_ISSUER is not an https:// issuer" : at === "key" ? "JELLY_SERVICE_ANCHOR is not an Ed25519 key in base64url" : `JELLY_RESOURCES' ${JELLY_RESOURCE_NAME} entry is invalid at ${String(at)}`);
17407
+ }
17408
+ try {
17409
+ directoryRealmId(realm.data);
17410
+ } catch {
17411
+ fail2("JELLY_SERVICE_ANCHOR is not a usable Ed25519 key");
17412
+ }
17413
+ return realm.data;
17414
+ }
17415
+ function readJellyEnv(env) {
17416
+ const actKeyPath = required3(env, "JELLY_ACTION_KEY");
17417
+ if (!isAbsolute(actKeyPath))
17418
+ fail2("JELLY_ACTION_KEY is not an absolute path");
17419
+ const home = folder("STATE_DIRECTORY", required3(env, "STATE_DIRECTORY"));
17420
+ const runText = env.RUNTIME_DIRECTORY ?? "";
17421
+ const groupText = env.JELLY_SOCKET_GROUP ?? "";
17422
+ if (groupText !== "" && process.platform === "win32")
17423
+ fail2("JELLY_SOCKET_GROUP is not supported on Windows: a named pipe is not shared with a group");
17424
+ if (groupText !== "" && (!/^\d{1,10}$/.test(groupText) || Number(groupText) > 4294967294))
17425
+ fail2("JELLY_SOCKET_GROUP is not a numeric gid");
17426
+ let label;
17427
+ try {
17428
+ label = jellyLabel(required3(env, "JELLY_DEVICE_LABEL"));
17429
+ } catch (error) {
17430
+ if (error instanceof MessagingError && /is not set/.test(error.message))
17431
+ throw error;
17432
+ return fail2("JELLY_DEVICE_LABEL has no letter or digit to make a device label from");
17433
+ }
17434
+ return {
17435
+ home,
17436
+ ...runText !== "" ? { run: folder("RUNTIME_DIRECTORY", runText) } : {},
17437
+ actKeyPath,
17438
+ label,
17439
+ ...groupText !== "" ? { socketGroup: Number(groupText) } : {},
17440
+ realm: jellyEnvRealm(env)
17441
+ };
17442
+ }
17443
+ function realmMismatch(pinned, named) {
17444
+ return ["url", "issuer", "resource", "key"].filter((member) => pinned[member] !== named[member]);
17445
+ }
17446
+ async function pinRealm(realm, get = wellKnownGet) {
17447
+ const origin = new URL(realm.url);
17448
+ const url = `${origin.protocol === "wss:" ? "https:" : "http:"}//${origin.host}/.well-known/messaging.json`;
17449
+ let text;
17450
+ try {
17451
+ text = await get(url);
17452
+ } catch (error) {
17453
+ throw new MessagingError("relay_unavailable", `cannot read the relay's realm document ${url} for the first start (${error instanceof Error ? error.message.slice(0, 200) : "failed"}); the next start tries again`);
17454
+ }
17455
+ let raw;
17456
+ try {
17457
+ raw = parseWire(text, { maxBytes: 16 * 1024 });
17458
+ } catch {
17459
+ return fail2(`the relay's realm document ${url} is not JSON`);
17460
+ }
17461
+ const { realm: _id, ...published } = typeof raw === "object" && raw !== null ? raw : {};
17462
+ const parsed = DirectoryRealmConfig.safeParse(published);
17463
+ if (!parsed.success)
17464
+ fail2(`the relay's realm document ${url} is not a directory realm`);
17465
+ const differs = realmMismatch(parsed.data, realm);
17466
+ if (differs.length > 0)
17467
+ fail2(`the relay's realm document ${url} names another ${differs.join(", ")} than the environment: the home is not pinned`);
17468
+ return parsed.data.orgDomains !== undefined ? { ...realm, orgDomains: parsed.data.orgDomains } : realm;
17469
+ }
17470
+ var JELLY_ENV, JELLY_RESOURCE_NAME = "messaging", ResourceEntry, Resources, wellKnownGet = async (url) => {
17471
+ const response = await fetch(url, { redirect: "error", signal: AbortSignal.timeout(5000) });
17472
+ if (!response.ok)
17473
+ throw new MessagingError("relay_unavailable", `the relay answered ${response.status}`);
17474
+ const text = await response.text();
17475
+ if (Buffer.byteLength(text) > 16 * 1024)
17476
+ throw new MessagingError("too_large", "the relay's realm document is larger than 16 KiB");
17477
+ return text;
17478
+ };
17479
+ var init_jelly_env = __esm(() => {
17480
+ init_dist();
17481
+ init_zod();
17482
+ init_jelly3();
17483
+ JELLY_ENV = ["JELLY_ACTION_KEY", "JELLY_ID_ISSUER", "JELLY_RESOURCES", "JELLY_SERVICE_ANCHOR", "JELLY_DEVICE_LABEL", "JELLY_SOCKET_GROUP"];
17484
+ ResourceEntry = looseObject({ name: string2().max(200), resource: string2().min(1).max(2048), url: string2().min(1).max(2048) });
17485
+ Resources = array(unknown()).max(64);
17486
+ });
17487
+
17488
+ // packages/daemon/dist/consent/answer-preview.js
17489
+ function who3(from) {
17490
+ const name = cleanPeerText(from.name, { singleLine: true, maxChars: 80 }) || "someone";
17491
+ const email = from.email === undefined ? undefined : cleanPeerText(from.email, { singleLine: true, maxChars: 100 });
17492
+ return `${name} (${email !== undefined && email !== "" ? `${email}, ` : ""}${ORG[from.org]})`;
17493
+ }
17494
+ function answersFor(kind) {
17495
+ return kind === "suggestion" ? ["decline"] : kind === "contact" ? ["accept", "decline", "block"] : ["once", "always", "decline", "block"];
17496
+ }
17497
+ function answerSentence(input) {
17498
+ const { kind, answer } = input;
17499
+ if (answer !== undefined && !answersFor(kind).includes(answer)) {
17500
+ throw new MessagingError("invalid_request", `a ${kind === "message" || kind === "task" ? "request to your agent" : kind === "contact" ? "message request" : "suggestion"} takes ${answersFor(kind).join(", ")}`);
17501
+ }
17502
+ const sender = who3(input.from);
17503
+ const single = input.count <= 1;
17504
+ if (kind === "suggestion") {
17505
+ const email = cleanPeerText(input.email ?? "", { singleLine: true, maxChars: 100 });
17506
+ if (answer === undefined)
17507
+ return `Your agent ${input.agent} suggests you send a message request to ${email}.`;
17508
+ return `Dismiss your agent ${input.agent}'s suggestion to contact ${email}. Nothing is sent.`;
17509
+ }
17510
+ if (kind === "contact") {
17511
+ const thing = single ? "their message request" : `their ${input.count} message requests`;
17512
+ if (answer === undefined)
17513
+ return `${sender} asks to message you.`;
17514
+ if (answer === "accept")
17515
+ return `${sender} may message you from now on: ${single ? "their request becomes their first message" : "their requests become their first messages"}, and they get your card.`;
17516
+ if (answer === "decline")
17517
+ return `Decline ${thing} from ${sender}. They are told it was declined.`;
17518
+ return `Block ${sender}: nothing from them or their agents reaches you, and ${thing} ${single ? "is" : "are"} declined.`;
17519
+ }
17520
+ const thing = single ? `this ${kind}` : `these ${input.count} requests`;
17521
+ const src = single ? `${sender}'s agent ${input.agent}` : `${sender}'s agents`;
17522
+ const dst = single ? `your agent ${input.to}` : "your agents";
17523
+ if (answer === undefined)
17524
+ return `${src} asks to send ${kind === "task" ? "a task" : "a message"} to ${dst}.`;
17525
+ if (answer === "once")
17526
+ return `Deliver ${thing} from ${src} to ${dst} this once, guarded: ${single ? "your agent" : "your agents"} may answer only them.`;
17527
+ if (answer === "always")
17528
+ return `Deliver ${thing} from ${src} to ${dst}, and let their agents ask yours from now on without asking you.`;
17529
+ if (answer === "decline")
17530
+ return `Decline ${thing} from ${src}. They are told it was declined.`;
17531
+ return `Block ${sender}: nothing from them or their agents reaches you, and ${thing} ${single ? "is" : "are"} declined.`;
17532
+ }
17533
+ var ORG;
17534
+ var init_answer_preview = __esm(() => {
17535
+ init_dist();
17536
+ ORG = { same: "your org", other: "another org", none: "no org" };
17537
+ });
17538
+
17539
+ // packages/daemon/dist/snapshot.js
17540
+ import { createHash as createHash10 } from "crypto";
17541
+ 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";
17542
+ import { join as join7 } from "path";
17543
+ function sha256File(path) {
17544
+ const data = readFileSync8(path);
17545
+ return { bytes: data.length, sha256: createHash10("sha256").update(data).digest("hex") };
17546
+ }
17547
+
17548
+ class Snapshots {
17549
+ #deps;
17550
+ #timer;
17551
+ constructor(deps) {
17552
+ this.#deps = deps;
17553
+ }
17554
+ get folder() {
17555
+ return join7(this.#deps.run, "snapshot");
17556
+ }
17557
+ take() {
17558
+ const folder = this.folder;
17559
+ if (existsSync6(folder))
17560
+ throw new MessagingError("invalid_state", "a snapshot is held: release it with daemon.snapshot {done: true} first");
17561
+ const group = this.#deps.group;
17562
+ mkdirSync5(folder, { mode: group === undefined ? 448 : 488 });
17563
+ try {
17564
+ if (!IS_WINDOWS && group !== undefined) {
17565
+ chownSync2(folder, process.geteuid?.() ?? -1, group);
17566
+ chmodSync4(folder, 488);
17567
+ }
17568
+ const fileMode = group === undefined ? 384 : 416;
17569
+ const files = [];
17570
+ const db = join7(folder, "messaging.db");
17571
+ this.#deps.vacuumInto(db);
17572
+ if (existsSync6(this.#deps.config))
17573
+ writeFileSync5(join7(folder, "config.json"), readFileSync8(this.#deps.config), { mode: fileMode });
17574
+ for (const name of ["messaging.db", "config.json"]) {
17575
+ const path = join7(folder, name);
17576
+ if (!existsSync6(path))
17577
+ continue;
17578
+ if (!IS_WINDOWS) {
17579
+ if (group !== undefined)
17580
+ chownSync2(path, process.geteuid?.() ?? -1, group);
17581
+ chmodSync4(path, fileMode);
17582
+ }
17583
+ files.push({ name, ...sha256File(path) });
17584
+ }
17585
+ const at = this.#deps.now();
17586
+ const until = at + SNAPSHOT_HOLD_MS;
17587
+ const manifest = { storeSchema: this.#deps.storeSchema, at, files };
17588
+ writeFileSync5(join7(folder, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
17589
+ `, { mode: fileMode });
17590
+ if (!IS_WINDOWS && group !== undefined)
17591
+ chownSync2(join7(folder, "manifest.json"), process.geteuid?.() ?? -1, group);
17592
+ clearTimeout(this.#timer);
17593
+ this.#timer = setTimeout(() => this.release(), SNAPSHOT_HOLD_MS);
17594
+ this.#timer.unref();
17595
+ return { folder, files, storeSchema: this.#deps.storeSchema, at, until };
17596
+ } catch (error) {
17597
+ this.release();
17598
+ if (error instanceof MessagingError)
17599
+ throw error;
17600
+ throw new MessagingError("internal", `the snapshot failed: ${error.code ?? "error"}`);
17601
+ }
17602
+ }
17603
+ release() {
17604
+ clearTimeout(this.#timer);
17605
+ this.#timer = undefined;
17606
+ let present;
17607
+ try {
17608
+ lstatSync4(this.folder);
17609
+ present = true;
17610
+ } catch {
17611
+ present = false;
17612
+ }
17613
+ if (present)
17614
+ rmSync4(this.folder, { recursive: true, force: true });
17615
+ return { released: present };
17616
+ }
17617
+ close() {
17618
+ this.release();
17619
+ }
17620
+ }
17621
+ var SNAPSHOT_HOLD_MS;
17622
+ var init_snapshot = __esm(() => {
17623
+ init_dist();
17624
+ init_dist2();
17625
+ SNAPSHOT_HOLD_MS = 60 * 60000;
17626
+ });
17627
+
17188
17628
  // packages/daemon/dist/notices.js
17189
17629
  class Notices {
17190
17630
  #host;
@@ -17861,7 +18301,7 @@ var init_storage_errors = __esm(() => {
17861
18301
  });
17862
18302
 
17863
18303
  // packages/daemon/dist/store.js
17864
- import { chmodSync as chmodSync4, closeSync as closeSync7, openSync as openSync3 } from "fs";
18304
+ import { chmodSync as chmodSync5, closeSync as closeSync7, openSync as openSync3 } from "fs";
17865
18305
  import { DatabaseSync } from "node:sqlite";
17866
18306
  function additiveColumn(decl) {
17867
18307
  if (/\b(PRIMARY|UNIQUE|CHECK|REFERENCES|COLLATE|GENERATED|AS)\b/i.test(decl))
@@ -17950,7 +18390,7 @@ class Store {
17950
18390
  constructor(path) {
17951
18391
  if (path !== ":memory:") {
17952
18392
  closeSync7(openSync3(path, "a", 384));
17953
- chmodSync4(path, 384);
18393
+ chmodSync5(path, 384);
17954
18394
  }
17955
18395
  this.#db = new DatabaseSync(path, { timeout: 5000 });
17956
18396
  this.#db.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL;");
@@ -17965,6 +18405,11 @@ class Store {
17965
18405
  this.#statements.clear();
17966
18406
  this.#db.close();
17967
18407
  }
18408
+ vacuumInto(path) {
18409
+ if (this.#depth > 0)
18410
+ throw new MessagingError("invalid_state", "a snapshot is not taken inside a transaction");
18411
+ this.#db.exec(`VACUUM INTO '${path.replaceAll("'", "''")}'`);
18412
+ }
17968
18413
  tx(fn) {
17969
18414
  if (this.#depth > 0) {
17970
18415
  const name = `sp${this.#depth}`;
@@ -18602,6 +19047,14 @@ class Store {
18602
19047
  SELECT substr(peer_address, 1, instr(peer_address || '@', '@') - 1), 0, created_at FROM tasks WHERE key = ?
18603
19048
  ) GROUP BY peer ORDER BY MAX(at) DESC, peer LIMIT ?`, key, key, key, limit).map((row) => ({ peer: String(row.peer), unread: Number(row.unread), lastAt: Number(row.at) }));
18604
19049
  }
19050
+ awaitedRequests(sessions, terminal, limit) {
19051
+ if (sessions.length === 0)
19052
+ return [];
19053
+ const rows = this.#all(`SELECT request FROM tasks WHERE role = 'requester' AND request IS NOT NULL
19054
+ AND session IN (${sessions.map(() => "?").join(", ")}) AND state NOT IN (${terminal.map(() => "?").join(", ")})
19055
+ ORDER BY updated_at DESC, task_id DESC LIMIT ?`, ...sessions, ...terminal, limit);
19056
+ return rows.map((row) => String(row.request));
19057
+ }
18605
19058
  task(taskId, role) {
18606
19059
  const row = this.#get(`SELECT * FROM ${TASKS} WHERE task_id = ? AND role = ?`, taskId, role);
18607
19060
  return row && taskRow2(row);
@@ -19173,14 +19626,14 @@ var init_version = __esm(() => {
19173
19626
  });
19174
19627
 
19175
19628
  // packages/daemon/dist/wake-bridge.js
19176
- import { closeSync as closeSync8, constants as constants7, fstatSync as fstatSync4, lstatSync as lstatSync4, openSync as openSync4, readSync as readSync2 } from "fs";
19629
+ import { closeSync as closeSync8, constants as constants7, fstatSync as fstatSync4, lstatSync as lstatSync5, openSync as openSync4, readSync as readSync2 } from "fs";
19177
19630
  import { request } from "http";
19178
19631
  import { homedir as homedir2 } from "os";
19179
- import { isAbsolute, join as join7 } from "path";
19632
+ import { isAbsolute as isAbsolute2, join as join8 } from "path";
19180
19633
  function serviceFile(env = process.env, windows = IS_WINDOWS) {
19181
19634
  const home = (windows ? env.USERPROFILE : env.HOME) || homedir2();
19182
- const state = env.XDG_STATE_HOME && isAbsolute(env.XDG_STATE_HOME) ? env.XDG_STATE_HOME : join7(home, ".local", "state");
19183
- return join7(state, "opencode", "service.json");
19635
+ const state = env.XDG_STATE_HOME && isAbsolute2(env.XDG_STATE_HOME) ? env.XDG_STATE_HOME : join8(home, ".local", "state");
19636
+ return join8(state, "opencode", "service.json");
19184
19637
  }
19185
19638
  function loopbackOrigin(url) {
19186
19639
  let parsed;
@@ -19199,7 +19652,7 @@ function loopbackOrigin(url) {
19199
19652
  function readService(file) {
19200
19653
  if (IS_WINDOWS) {
19201
19654
  try {
19202
- const stats = lstatSync4(file);
19655
+ const stats = lstatSync5(file);
19203
19656
  if (stats.isSymbolicLink() || !stats.isFile() || aclProblem(file, { pinnedOwner: false }) !== undefined)
19204
19657
  return "service_unsafe";
19205
19658
  } catch (error) {
@@ -20211,6 +20664,8 @@ class Daemon {
20211
20664
  #closed = false;
20212
20665
  #online = new Set;
20213
20666
  #holders = new Map;
20667
+ #ticketEnds = new Map;
20668
+ #lapsed = new Map;
20214
20669
  #policy = {
20215
20670
  rate: inboundRateLimiter(),
20216
20671
  presence: responseRateLimiter(),
@@ -20230,6 +20685,9 @@ class Daemon {
20230
20685
  #requests;
20231
20686
  #notices;
20232
20687
  #jellyHost;
20688
+ #attached = false;
20689
+ #wakeArmed = false;
20690
+ #snapshots;
20233
20691
  #jellyAuth;
20234
20692
  #state = "starting";
20235
20693
  #sync;
@@ -20340,6 +20798,14 @@ class Daemon {
20340
20798
  address: (sender, session) => peerRef(sender, session).address,
20341
20799
  held: (row) => this.#notices.held(row)
20342
20800
  });
20801
+ this.#snapshots = new Snapshots({
20802
+ run: paths.run,
20803
+ config: paths.config,
20804
+ vacuumInto: (path) => this.#store.vacuumInto(path),
20805
+ storeSchema: STORE_SCHEMA,
20806
+ group: options.jellyHost?.socketGroup,
20807
+ now: this.#now
20808
+ });
20343
20809
  this.#notices = new Notices({
20344
20810
  store,
20345
20811
  now: this.#now,
@@ -20379,6 +20845,10 @@ class Daemon {
20379
20845
  static async start(options) {
20380
20846
  const paths = homePaths(options.home, options.jellyHost?.run);
20381
20847
  ensureHome(paths);
20848
+ const jelly = options.jellyHost;
20849
+ if (jelly?.realm !== undefined && readConfig(paths.config).realm === undefined) {
20850
+ options = { ...options, jellyHost: { ...jelly, realm: await pinRealm(jelly.realm, options.wellKnown) } };
20851
+ }
20382
20852
  jellyConfig(paths.config, options.jellyHost);
20383
20853
  const lock = acquireLock(paths.lock);
20384
20854
  let store;
@@ -20417,6 +20887,7 @@ class Daemon {
20417
20887
  }
20418
20888
  this.#sync.load();
20419
20889
  this.#store.keepAllLive(this.#now(), CLI_AGENT);
20890
+ this.#snapshots.release();
20420
20891
  const handlers = this.#handlers();
20421
20892
  const jelly = this.#jellyHost;
20422
20893
  if (jelly)
@@ -20541,6 +21012,7 @@ class Daemon {
20541
21012
  await server.close().catch(() => {});
20542
21013
  await Promise.race([this.#flushing, new Promise((resolve) => setTimeout(resolve, 2000).unref())]);
20543
21014
  this.#store.close();
21015
+ this.#snapshots.close();
20544
21016
  } finally {
20545
21017
  this.#lock.release();
20546
21018
  this.#log("info", "daemon.stopped", { pid: process.pid });
@@ -20689,6 +21161,7 @@ class Daemon {
20689
21161
  },
20690
21162
  "request.list": async () => ({ requests: [...this.#holds.list().requests, ...await this.#requests.list()] }),
20691
21163
  "request.answer": (p) => this.#requests.holds(p.id) ? this.#requests.answer(p) : this.#holds.answer(p),
21164
+ "request.answer.preview": (p) => this.#answerPreview(p),
20692
21165
  "contact.request": (p) => this.#requests.request(p),
20693
21166
  "contact.request.preview": (p) => this.#requests.preview(p),
20694
21167
  "card.get": async (p) => {
@@ -20697,6 +21170,11 @@ class Daemon {
20697
21170
  return { sent: await this.#cards.get(p.person) };
20698
21171
  },
20699
21172
  "daemon.health": () => this.#health(),
21173
+ "daemon.woke": () => this.#woke(),
21174
+ "wake.arm": (p) => this.#wakeArm(p),
21175
+ "wake.disarm": () => this.#wakeDisarm(),
21176
+ "daemon.snapshot": (p) => p.done ? this.#snapshots.release() : this.#snapshots.take(),
21177
+ "person.attached": (p) => this.#personAttached(p.attached),
20700
21178
  "agents.list": () => this.#agentsList(),
20701
21179
  "agents.read": (p) => this.#agentsRead(p),
20702
21180
  "notice.subscribe": (p, ctx, authority) => this.#notices.subscribe(ctx, p, authority?.scope === "host" ? authority.expiresAt : undefined),
@@ -20893,14 +21371,39 @@ class Daemon {
20893
21371
  if (!holders)
20894
21372
  this.#holders.set(p.key, holders = new Set);
20895
21373
  holders.add(ctx.id);
21374
+ if (ticket) {
21375
+ this.#ticketEnds.set(p.key, Math.max(this.#ticketEnds.get(p.key) ?? 0, ticket.exp * 1000));
21376
+ for (const id of this.#lapsed.get(p.key) ?? [])
21377
+ holders.add(id);
21378
+ this.#lapsed.delete(p.key);
21379
+ }
20896
21380
  this.#sessionsChanged();
20897
21381
  return { session: name, address: formatAddress(me.person, name, me.label) };
20898
21382
  }
20899
21383
  #bound(p, ctx) {
21384
+ const ends = this.#ticketEnds.get(p.key);
21385
+ const held = this.#holders.get(p.key)?.has(ctx.id) === true || this.#lapsed.get(p.key)?.has(ctx.id) === true;
21386
+ if (ends !== undefined && ends <= this.#now() && held) {
21387
+ throw new MessagingError("ticket_invalid", "the session ticket this session registered with has expired: register again with a fresh ticket from the host");
21388
+ }
20900
21389
  if (!this.#holders.get(p.key)?.has(ctx.id))
20901
21390
  throw new MessagingError("not_found", "this session key is not registered on this connection");
20902
21391
  return p;
20903
21392
  }
21393
+ #expireTickets(now) {
21394
+ let changed = false;
21395
+ for (const [key, ends] of this.#ticketEnds) {
21396
+ if (ends > now || !this.#holders.has(key))
21397
+ continue;
21398
+ this.#lapsed.set(key, this.#holders.get(key));
21399
+ this.#holders.delete(key);
21400
+ this.#log("info", "session.ticket.expired", {});
21401
+ if (this.#store.keepSession(key, now))
21402
+ changed = true;
21403
+ }
21404
+ if (changed)
21405
+ this.#sessionsChanged();
21406
+ }
20904
21407
  #sessionOf(key) {
20905
21408
  const session = this.#store.sessionByKey(key);
20906
21409
  if (!session)
@@ -20923,6 +21426,8 @@ class Daemon {
20923
21426
  }
20924
21427
  #unregister(p) {
20925
21428
  this.#holders.delete(p.key);
21429
+ this.#ticketEnds.delete(p.key);
21430
+ this.#lapsed.delete(p.key);
20926
21431
  if (this.#store.keepSession(p.key, this.#now()))
20927
21432
  this.#sessionsChanged();
20928
21433
  return { ok: true };
@@ -20933,10 +21438,14 @@ class Daemon {
20933
21438
  this.#chats.disconnected(ctx);
20934
21439
  this.#notices.disconnected(ctx.id);
20935
21440
  let changed = false;
21441
+ for (const [key, lapsed] of [...this.#lapsed])
21442
+ if (lapsed.delete(ctx.id) && lapsed.size === 0)
21443
+ this.#lapsed.delete(key);
20936
21444
  for (const [key, holders] of [...this.#holders]) {
20937
21445
  if (!holders.delete(ctx.id) || holders.size > 0)
20938
21446
  continue;
20939
21447
  this.#holders.delete(key);
21448
+ this.#ticketEnds.delete(key);
20940
21449
  if (this.#store.sessionByKey(key)?.agent === CLI_AGENT)
20941
21450
  continue;
20942
21451
  if (this.#store.keepSession(key, this.#now()))
@@ -20984,7 +21493,8 @@ class Daemon {
20984
21493
  to: { team: true },
20985
21494
  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?.(), {
20986
21495
  wake: (s) => this.#wakes(s),
20987
- share: new Set(this.#currentConfig().presence?.share ?? [])
21496
+ share: new Set(this.#currentConfig().presence?.share ?? []),
21497
+ away: this.#away()
20988
21498
  }),
20989
21499
  now: this.#now()
20990
21500
  });
@@ -21267,7 +21777,19 @@ class Daemon {
21267
21777
  async#peopleGet(p) {
21268
21778
  const { realm, identity } = this.#joined();
21269
21779
  const profile = await realm.profile(p.person);
21270
- const devices = realm.known(p.person).devices.map((device) => ({ device: device.machine, label: device.label, self: device.machine === identity.machine }));
21780
+ const fresh = this.#now() - (realm.kind === "directory" ? this.#tuning.presenceOnlineMs : this.#tuning.presenceMaxMs * 2);
21781
+ const away = (machine) => {
21782
+ if (machine === identity.machine)
21783
+ return this.#away();
21784
+ const row = this.#store.directoryRow(machine);
21785
+ return row !== undefined && row.receivedAt >= fresh && row.presence.away === true;
21786
+ };
21787
+ const devices = realm.known(p.person).devices.map((device) => ({
21788
+ device: device.machine,
21789
+ label: device.label,
21790
+ self: device.machine === identity.machine,
21791
+ ...away(device.machine) ? { away: true } : {}
21792
+ }));
21271
21793
  return { person: p.person, ...profile, devices };
21272
21794
  }
21273
21795
  #seal(type, toMachine, session, payload, extra = {}) {
@@ -22143,6 +22665,7 @@ ${row.receipt ? "receipt" : "envelope"}`;
22143
22665
  return;
22144
22666
  this.#directory?.sweep();
22145
22667
  const now = this.#now();
22668
+ this.#expireTickets(now);
22146
22669
  let sessions = 0;
22147
22670
  try {
22148
22671
  this.#tx(() => {
@@ -22416,6 +22939,86 @@ ${row.receipt ? "receipt" : "envelope"}`;
22416
22939
  const devices = realm.known(me.person).devices.map((d) => ({ device: d.machine, label: d.label, vouched: "admin", current: true, self: d.machine === identity.machine }));
22417
22940
  return { devices };
22418
22941
  }
22942
+ #away() {
22943
+ return this.#jellyHost !== undefined && !this.#attached;
22944
+ }
22945
+ #personAttached(attached) {
22946
+ if (attached !== this.#attached) {
22947
+ this.#attached = attached;
22948
+ this.#log("info", "person.attached", { attached });
22949
+ this.#presenceSoon();
22950
+ }
22951
+ return { ok: true };
22952
+ }
22953
+ #woke() {
22954
+ this.#log("info", "daemon.woke", {});
22955
+ this.#transport?.retryNow?.();
22956
+ this.sweep();
22957
+ this.#presenceSoon();
22958
+ this.#wokeHook();
22959
+ return { ok: true };
22960
+ }
22961
+ #wokeHook() {
22962
+ if (!this.#wakeArmed)
22963
+ return;
22964
+ this.#wakeDisarm().catch((error) => this.#log("warn", "wake.disarm.failed", { code: codeOf(error) }));
22965
+ }
22966
+ async#wakeArm(p) {
22967
+ const home = this.#directory;
22968
+ if (!home)
22969
+ throw new MessagingError("invalid_state", "wake tickets are for a directory realm");
22970
+ if (home.person() === undefined)
22971
+ throw new MessagingError("invalid_state", "this device is not bound yet");
22972
+ const issuer = this.#config.realm?.issuer;
22973
+ if (issuer === undefined || new URL(p.url).origin !== new URL(issuer).origin)
22974
+ throw new MessagingError("invalid_request", "the wake URL is not on the realm's Jelly ID");
22975
+ const agents = [...new Set(p.agents)];
22976
+ const awaiting = this.#store.awaitedRequests(agents, TERMINAL_STATES, WAKE_AWAITING);
22977
+ const sent = await home.wake({ url: p.url, ticket: p.ticket, agents, awaiting });
22978
+ this.#wakeArmed = true;
22979
+ this.#log("info", "wake.arm", { relay: sent === "sent" ? "armed" : "pending", agents: agents.length, awaiting: awaiting.length });
22980
+ return { relay: sent === "sent" ? "armed" : "pending", awaiting: awaiting.length };
22981
+ }
22982
+ async#wakeDisarm() {
22983
+ const home = this.#directory;
22984
+ if (!home)
22985
+ throw new MessagingError("invalid_state", "wake tickets are for a directory realm");
22986
+ const sent = await home.wake("disarm");
22987
+ this.#wakeArmed = false;
22988
+ this.#log("info", "wake.disarm", { relay: sent === "sent" ? "disarmed" : "pending" });
22989
+ return { relay: sent === "sent" ? "disarmed" : "pending" };
22990
+ }
22991
+ async#answerPreview(p) {
22992
+ const { realm, me } = this.#joined();
22993
+ const now = this.#now();
22994
+ const profile = async (person) => {
22995
+ const found = await realm.profile(person).catch(() => {
22996
+ return;
22997
+ });
22998
+ return found ? { name: found.name.slice(0, 200), ...found.email !== undefined ? { email: found.email.slice(0, 320) } : {}, org: found.org } : { name: String(person).slice(0, 200), org: "none" };
22999
+ };
23000
+ const answer = p.answer;
23001
+ const ask = this.#store.held(p.id);
23002
+ if (ask && ask.exp > now) {
23003
+ const kind = ask.payload.kind;
23004
+ const count = p.all ? this.#store.helds(ask.person).filter((row) => row.exp > now).length : 1;
23005
+ const from = await profile(ask.person);
23006
+ const agent = ask.envelope.from.agent ?? "";
23007
+ return { kind, from, ...agent !== "" ? { agent } : {}, to: ask.session, count, sentence: answerSentence({ kind, from, agent, to: ask.session, answer, count }) };
23008
+ }
23009
+ const request = this.#store.contactRequest(p.id);
23010
+ if (request && request.exp > now) {
23011
+ const count = p.all ? this.#store.contactRequests(request.person).filter((row) => row.exp > now).length : 1;
23012
+ const from = await profile(request.person);
23013
+ return { kind: "contact", from, count, sentence: answerSentence({ kind: "contact", from, answer, count }) };
23014
+ }
23015
+ const suggestion = this.#store.contactSuggestion(p.id);
23016
+ if (suggestion) {
23017
+ const from = await profile(me.person);
23018
+ return { kind: "suggestion", from, agent: suggestion.session, count: 1, sentence: answerSentence({ kind: "suggestion", from, agent: suggestion.session, email: suggestion.email, answer, count: 1 }) };
23019
+ }
23020
+ throw new MessagingError("not_found", "no request with that id is held here");
23021
+ }
22419
23022
  #me() {
22420
23023
  return (this.#realm ?? this.#directory?.realm)?.self()?.person;
22421
23024
  }
@@ -22834,6 +23437,22 @@ function jellyConfig(path, jelly) {
22834
23437
  }
22835
23438
  return;
22836
23439
  }
23440
+ if (jelly.realm !== undefined) {
23441
+ if (config.realm === undefined) {
23442
+ if (config.relay !== undefined || config.person !== undefined) {
23443
+ throw new MessagingError("invalid_request", `${path} is a roster home (relay, person): a Jelly host's home is pinned to a directory realm; give the service a new STATE_DIRECTORY`);
23444
+ }
23445
+ if (config.ipc?.agentGroup !== undefined)
23446
+ throw new MessagingError("invalid_request", "deployment jelly shares its sockets with --socket-group, not ipc.agentGroup");
23447
+ writeConfig(path, { ...config, realm: jelly.realm, deployment: "jelly" });
23448
+ return;
23449
+ }
23450
+ const differs = realmMismatch(config.realm, jelly.realm);
23451
+ if (differs.length > 0) {
23452
+ const which = differs.map((member) => `${member} ${JSON.stringify(config.realm[member])} (the environment: ${JSON.stringify(jelly.realm[member])})`).join(", ");
23453
+ throw new MessagingError("invalid_request", `${path} pins another realm: ${which}. A home never changes realm, and the daemon does not rewrite it; restore the variables the home was made with, or give the service a new STATE_DIRECTORY`);
23454
+ }
23455
+ }
22837
23456
  if (config.realm === undefined)
22838
23457
  throw new MessagingError("invalid_request", "deployment jelly needs a directory realm: config.json has no realm (a Jelly host supervises no roster realm)");
22839
23458
  if (config.ipc?.agentGroup !== undefined)
@@ -22862,6 +23481,9 @@ var init_daemon = __esm(() => {
22862
23481
  init_roster_identity();
22863
23482
  init_directory_home();
22864
23483
  init_jelly3();
23484
+ init_jelly_env();
23485
+ init_answer_preview();
23486
+ init_snapshot();
22865
23487
  init_notices();
22866
23488
  init_resolve();
22867
23489
  init_secrets();
@@ -23483,6 +24105,9 @@ class DirectTransport {
23483
24105
  presence(env) {
23484
24106
  this.#relay.presence(env);
23485
24107
  }
24108
+ retryNow() {
24109
+ this.#relay.retryNow?.();
24110
+ }
23486
24111
  appendRoster(entry) {
23487
24112
  return this.#relay.appendRoster(entry);
23488
24113
  }
@@ -23584,6 +24209,11 @@ var init_pacing = __esm(() => {
23584
24209
  PAIR_WINDOW_MS = 30 * DAY_MS5;
23585
24210
  });
23586
24211
 
24212
+ // packages/relay/dist/directory/wakes.js
24213
+ var init_wakes = __esm(() => {
24214
+ init_dist();
24215
+ });
24216
+
23587
24217
  // packages/relay/dist/directory/module.js
23588
24218
  var DAY4, DIRECTORY_FRAMES;
23589
24219
  var init_module = __esm(() => {
@@ -23592,9 +24222,30 @@ var init_module = __esm(() => {
23592
24222
  init_feed();
23593
24223
  init_jelly4();
23594
24224
  init_pacing();
24225
+ init_wakes();
23595
24226
  DAY4 = 24 * 60;
23596
- DIRECTORY_FRAMES = new Set(["dir.bind", "dir.append", "dir.keys", "dir.sth", "dir.lookup", "dir.device", "dir.resolve", "dir.search", "dir.watch", "presence.watch", "dir.erase"]);
24227
+ DIRECTORY_FRAMES = new Set([
24228
+ "dir.bind",
24229
+ "dir.append",
24230
+ "dir.keys",
24231
+ "dir.sth",
24232
+ "dir.lookup",
24233
+ "dir.device",
24234
+ "dir.resolve",
24235
+ "dir.search",
24236
+ "dir.watch",
24237
+ "presence.watch",
24238
+ "dir.erase",
24239
+ "wake.arm",
24240
+ "wake.disarm"
24241
+ ]);
24242
+ });
24243
+
24244
+ // packages/relay/dist/directory/store.js
24245
+ var init_store4 = __esm(() => {
24246
+ init_dist();
23597
24247
  });
24248
+
23598
24249
  // packages/relay/dist/server.js
23599
24250
  var HIGH_WATER, MAX_BUFFERED;
23600
24251
  var init_server2 = __esm(() => {
@@ -23605,6 +24256,7 @@ var init_server2 = __esm(() => {
23605
24256
  init_pages();
23606
24257
  init_store3();
23607
24258
  init_module();
24259
+ init_store4();
23608
24260
  HIGH_WATER = 1024 * 1024;
23609
24261
  MAX_BUFFERED = 16 * 1024 * 1024;
23610
24262
  });
@@ -23657,6 +24309,14 @@ class RelayClient {
23657
24309
  if (!this.#ws && !this.#retryTimer)
23658
24310
  this.#open();
23659
24311
  }
24312
+ retryNow() {
24313
+ if (!this.#running || this.#ws || !this.#retryTimer)
24314
+ return;
24315
+ clearTimeout(this.#retryTimer);
24316
+ this.#retryTimer = undefined;
24317
+ this.#attempt = 0;
24318
+ this.#open();
24319
+ }
23660
24320
  async stop() {
23661
24321
  this.#running = false;
23662
24322
  clearTimeout(this.#retryTimer);
@@ -23733,6 +24393,12 @@ class RelayClient {
23733
24393
  presenceWatch(persons) {
23734
24394
  return this.#dir("dir.ok", { t: "presence.watch", persons });
23735
24395
  }
24396
+ wakeArm(arm) {
24397
+ return this.#dir("dir.ok", { t: "wake.arm", url: arm.url, ticket: arm.ticket, agents: arm.agents, awaiting: arm.awaiting });
24398
+ }
24399
+ wakeDisarm() {
24400
+ return this.#dir("dir.ok", { t: "wake.disarm" });
24401
+ }
23736
24402
  dirErase(entries, token) {
23737
24403
  return this.#dir("dir.ok", { t: "dir.erase", entries, token });
23738
24404
  }
@@ -24310,6 +24976,7 @@ class RelayDirectoryLink {
24310
24976
  #running = false;
24311
24977
  #watched = [];
24312
24978
  #presence = [];
24979
+ #wake;
24313
24980
  #opening = new Set;
24314
24981
  constructor(options) {
24315
24982
  this.#options = options;
@@ -24351,6 +25018,8 @@ class RelayDirectoryLink {
24351
25018
  client.dirWatch(this.#watched).catch(() => {});
24352
25019
  if (this.#presence.length > 0)
24353
25020
  client.presenceWatch(this.#presence).catch(() => {});
25021
+ if (this.#wake !== undefined)
25022
+ this.#sendWake(client, this.#wake).catch(() => {});
24354
25023
  const sth = "sth" in state ? STH.safeParse(state.sth) : undefined;
24355
25024
  pushes.state(true, sth?.success ? sth.data : undefined);
24356
25025
  },
@@ -24458,6 +25127,34 @@ class RelayDirectoryLink {
24458
25127
  presence(env) {
24459
25128
  this.#client?.presence(env);
24460
25129
  }
25130
+ retryNow() {
25131
+ this.#client?.retryNow();
25132
+ }
25133
+ async wake(state) {
25134
+ this.#wake = state;
25135
+ const client = this.#connect();
25136
+ if (!client?.connected)
25137
+ return "pending";
25138
+ try {
25139
+ await this.#sendWake(client, state);
25140
+ return "sent";
25141
+ } catch (error) {
25142
+ if (isMessagingError(error) && error.code === "relay_unavailable")
25143
+ return "pending";
25144
+ throw error;
25145
+ }
25146
+ }
25147
+ async#sendWake(client, state) {
25148
+ try {
25149
+ parse5(DirOkFrame, await (state === "disarm" ? client.wakeDisarm() : client.wakeArm(state)));
25150
+ } catch (error) {
25151
+ if (!(isMessagingError(error) && error.code === "relay_unavailable") && this.#wake === state)
25152
+ this.#wake = undefined;
25153
+ throw error;
25154
+ }
25155
+ if (this.#wake === state)
25156
+ this.#wake = undefined;
25157
+ }
24461
25158
  async presenceWatch(persons) {
24462
25159
  this.#presence = [...persons];
24463
25160
  const client = this.#connect();
@@ -24516,6 +25213,7 @@ var init_dist4 = __esm(() => {
24516
25213
  init_log();
24517
25214
  init_version();
24518
25215
  init_jelly3();
25216
+ init_jelly_env();
24519
25217
  init_store2();
24520
25218
  init_dist();
24521
25219
  });
@@ -27276,7 +27974,13 @@ Runs the Messaging daemon in the foreground for one MESSAGING_HOME (default ~/.m
27276
27974
  --version print the version, the store schema and the protocol (--json: as JSON); needs no home
27277
27975
  --help print this help
27278
27976
 
27279
- Under a Jelly host (spec \xA78.11):
27977
+ Under a Jelly host (spec \xA78.11), with no arguments: the environment start contract. JELLY_ACTION_KEY (the path of
27978
+ the host's public action key) starts it; STATE_DIRECTORY is the home, RUNTIME_DIRECTORY the socket folder,
27979
+ JELLY_DEVICE_LABEL the label, JELLY_SOCKET_GROUP the group, and JELLY_ID_ISSUER, JELLY_RESOURCES (the messaging entry)
27980
+ and JELLY_SERVICE_ANCHOR the realm, which the first start writes to config.json. Flags win: a start with
27981
+ --deployment jelly reads none of these; any other start with JELLY_* variables set is refused.
27982
+
27983
+ Or by flags:
27280
27984
  --deployment jelly every human and admin call needs the host's action grant
27281
27985
  --act-key <path> the host's public action key (an Ed25519 JWK); required
27282
27986
  --label <label> this device's label, from the host; required
@@ -27336,7 +28040,20 @@ function versionText(json, info) {
27336
28040
  return `messagingd ${info.version} (store schema ${info.storeSchema}, protocol ${info.protocol})
27337
28041
  `;
27338
28042
  }
27339
- async function daemonMain(argv) {
28043
+ function startSource(values, env, named) {
28044
+ const flags = Object.keys(values).length > 0;
28045
+ if (flags) {
28046
+ if (values["--deployment"] === "jelly" || named.length === 0)
28047
+ return "flags";
28048
+ return `${named[0]} is set, so the environment starts a Jelly host, but the flags start a self-host daemon; use one or the other`;
28049
+ }
28050
+ if ((env.JELLY_ACTION_KEY ?? "") !== "")
28051
+ return "environment";
28052
+ if (named.length > 0)
28053
+ return `${named[0]} is set without JELLY_ACTION_KEY: a Jelly start needs the host's action key`;
28054
+ return "self";
28055
+ }
28056
+ async function daemonMain(argv, env = process.env) {
27340
28057
  const args = parseArgs2(argv);
27341
28058
  if (typeof args === "string") {
27342
28059
  process.stderr.write(`messagingd: ${args}
@@ -27354,7 +28071,23 @@ ${USAGE}`);
27354
28071
  return 0;
27355
28072
  }
27356
28073
  const { values } = args;
27357
- const home = values["--home"] ? resolve4(values["--home"]) : messagingHome();
28074
+ const source = startSource(values, env, jellyEnvNamed(env));
28075
+ if (source !== "flags" && source !== "environment" && source !== "self") {
28076
+ process.stderr.write(`messagingd: ${source}
28077
+ `);
28078
+ return 2;
28079
+ }
28080
+ let fromEnv;
28081
+ if (source === "environment") {
28082
+ try {
28083
+ fromEnv = readJellyEnv(env);
28084
+ } catch (error) {
28085
+ process.stderr.write(`messagingd: ${error instanceof Error ? error.message : "the Jelly environment is invalid"}
28086
+ `);
28087
+ return 1;
28088
+ }
28089
+ }
28090
+ const home = fromEnv ? resolve4(fromEnv.home) : values["--home"] ? resolve4(values["--home"]) : messagingHome();
27358
28091
  const level = process.env.MESSAGING_LOG_LEVEL;
27359
28092
  const log = createLogger({
27360
28093
  file: homePaths(home).log,
@@ -27363,7 +28096,14 @@ ${USAGE}`);
27363
28096
  });
27364
28097
  let daemon;
27365
28098
  try {
27366
- const jellyHost = values["--deployment"] === "jelly" ? {
28099
+ const jellyHost = fromEnv ? {
28100
+ actKey: readActKey(fromEnv.actKeyPath),
28101
+ label: fromEnv.label,
28102
+ kind: "jelly-host",
28103
+ realm: fromEnv.realm,
28104
+ ...fromEnv.socketGroup !== undefined ? { socketGroup: fromEnv.socketGroup } : {},
28105
+ ...fromEnv.run !== undefined ? { run: resolve4(fromEnv.run) } : {}
28106
+ } : values["--deployment"] === "jelly" ? {
27367
28107
  actKey: readActKey(resolve4(values["--act-key"])),
27368
28108
  label: jellyLabel(values["--label"]),
27369
28109
  kind: "jelly-host",
@@ -27371,6 +28111,8 @@ ${USAGE}`);
27371
28111
  ...values["--run"] !== undefined ? { run: resolve4(values["--run"]) } : {}
27372
28112
  } : undefined;
27373
28113
  daemon = await startDaemon2({ home, log, ...jellyHost ? { jellyHost } : {} });
28114
+ if (jellyHost)
28115
+ log("info", "daemon.start", { deployment: "jelly", source });
27374
28116
  } catch (error) {
27375
28117
  process.stderr.write(`messagingd: ${error instanceof Error ? error.message : "failed to start"}
27376
28118
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joe-broadhead/messaging-bin-linux-x64-gnu",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "The self-contained messagingd executable for one platform: the Messaging daemon and its CLI (`messagingd cli …`, or invoked as `messaging`), with no Node.js, npm install or install script on the machine",
5
5
  "license": "MIT",
6
6
  "author": "Joe Broadhead",