@joe-broadhead/messaging 0.8.0 → 0.9.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +150 -0
  2. package/dist/bin/messaging-mcp.js +202 -31
  3. package/dist/bin/messaging-relay.js +291 -43
  4. package/dist/bin/messaging.js +290 -32
  5. package/dist/bin/messagingd.js +1158 -138
  6. package/dist/lib/modules/core/agent/present.js +5 -0
  7. package/dist/lib/modules/core/agent-tools.js +12 -0
  8. package/dist/lib/modules/core/consent.js +54 -14
  9. package/dist/lib/modules/core/contact-card.js +3 -3
  10. package/dist/lib/modules/core/index.js +1 -1
  11. package/dist/lib/modules/core/jelly-host.js +3 -0
  12. package/dist/lib/modules/core/schema/common.js +2 -0
  13. package/dist/lib/modules/core/schema/consent.js +20 -3
  14. package/dist/lib/modules/core/schema/conv-frames.js +10 -3
  15. package/dist/lib/modules/core/schema/directory-frames.js +8 -3
  16. package/dist/lib/modules/core/schema/errors.js +3 -2
  17. package/dist/lib/modules/core/schema/ipc/consent.js +85 -10
  18. package/dist/lib/modules/core/schema/ipc/conversation-views.js +8 -1
  19. package/dist/lib/modules/core/schema/ipc/conversations.js +5 -1
  20. package/dist/lib/modules/core/schema/ipc/device.js +7 -0
  21. package/dist/lib/modules/core/schema/ipc/jelly.js +14 -7
  22. package/dist/lib/modules/core/schema/ipc/shapes.js +60 -1
  23. package/dist/lib/modules/core/schema/ipc.js +51 -3
  24. package/dist/lib/modules/core/version.js +1 -1
  25. package/dist/spec/messaging-v1.md +321 -49
  26. package/dist/types/core/agent/present.d.ts +1 -1
  27. package/dist/types/core/agent-tools.d.ts +32 -0
  28. package/dist/types/core/consent.d.ts +30 -7
  29. package/dist/types/core/contact-card.d.ts +3 -3
  30. package/dist/types/core/conversation-agent-tools.d.ts +21 -0
  31. package/dist/types/core/index.d.ts +1 -1
  32. package/dist/types/core/schema/common.d.ts +2 -0
  33. package/dist/types/core/schema/consent.d.ts +23 -2
  34. package/dist/types/core/schema/conv-frames.d.ts +6 -2
  35. package/dist/types/core/schema/directory-frames.d.ts +8 -3
  36. package/dist/types/core/schema/envelope.d.ts +2 -0
  37. package/dist/types/core/schema/errors.d.ts +2 -0
  38. package/dist/types/core/schema/ipc/consent.d.ts +377 -15
  39. package/dist/types/core/schema/ipc/conversation-views.d.ts +176 -0
  40. package/dist/types/core/schema/ipc/conversations.d.ts +368 -0
  41. package/dist/types/core/schema/ipc/device.d.ts +6 -0
  42. package/dist/types/core/schema/ipc/jelly.d.ts +14 -6
  43. package/dist/types/core/schema/ipc/shapes.d.ts +198 -0
  44. package/dist/types/core/schema/ipc/surfaces.d.ts +1 -0
  45. package/dist/types/core/schema/ipc/tasks.d.ts +2 -0
  46. package/dist/types/core/schema/ipc.d.ts +826 -3
  47. package/dist/types/core/schema/relay.d.ts +4 -0
  48. package/dist/types/core/version.d.ts +1 -1
  49. package/dist/types/ipc/server.d.ts +6 -0
  50. package/package.json +1 -1
@@ -5868,7 +5868,8 @@ var init_common = __esmMin((() => {
5868
5868
  URI = {
5869
5869
  binding: "urn:messaging:binding:relay:v1",
5870
5870
  provenance: "urn:messaging:ext:provenance:v1",
5871
- presence: "urn:messaging:ext:presence:v1"
5871
+ presence: "urn:messaging:ext:presence:v1",
5872
+ signIn: "urn:messaging:ext:sign-in:v1"
5872
5873
  };
5873
5874
  Base64Url = string$1().regex(/^[A-Za-z0-9_-]*$/, "base64url without padding");
5874
5875
  Key32 = string$1().regex(/^[A-Za-z0-9_-]{43}$/, "32 bytes, base64url without padding");
@@ -6039,6 +6040,7 @@ var init_errors = __esmMin((() => {
6039
6040
  "jelly_unavailable",
6040
6041
  "log_conflict",
6041
6042
  "conv_conflict",
6043
+ "conflict",
6042
6044
  "stale_epoch",
6043
6045
  "device_limit",
6044
6046
  "admission_required",
@@ -6345,7 +6347,7 @@ var init_directory$2 = __esmMin((() => {
6345
6347
  });
6346
6348
  }));
6347
6349
  // packages/core/dist/schema/a2a.js
6348
- var Struct, TaskState, TERMINAL_STATES, Role, Part$1, Message, TaskStatus, Artifact, Task$1, TaskStatusUpdateEvent, TaskArtifactUpdateEvent;
6350
+ var Struct, TaskState, TERMINAL_STATES, INTERRUPTED_STATES, Role, Part$1, Message, TaskStatus, Artifact, Task$1, TaskStatusUpdateEvent, TaskArtifactUpdateEvent;
6349
6351
  var init_a2a = __esmMin((() => {
6350
6352
  init_zod();
6351
6353
  Struct = record(string$1(), unknown());
@@ -6365,6 +6367,7 @@ var init_a2a = __esmMin((() => {
6365
6367
  "TASK_STATE_CANCELED",
6366
6368
  "TASK_STATE_REJECTED"
6367
6369
  ];
6370
+ INTERRUPTED_STATES = ["TASK_STATE_INPUT_REQUIRED", "TASK_STATE_AUTH_REQUIRED"];
6368
6371
  Role = _enum(["ROLE_USER", "ROLE_AGENT"]);
6369
6372
  Part$1 = object$1({
6370
6373
  text: string$1().optional(),
@@ -7029,6 +7032,12 @@ var init_conversation = __esmMin((() => {
7029
7032
  function grantProblem(grant) {
7030
7033
  const own = "person" in grant.subject && grant.subject.person === grant.person;
7031
7034
  if ("conv" in grant.subject && (grant.chat !== void 0 || grant.trustedUntil !== void 0 || grant.discoverable !== void 0)) return "a conversation grant sets only ask and agentMail";
7035
+ if ("relation" in grant.subject) {
7036
+ if (grant.agentMail !== void 0 || grant.trustedUntil !== void 0 || grant.discoverable !== void 0) return "a relation grant sets only chat or ask";
7037
+ if (grant.subject.relation === "outside" && grant.ask !== void 0) return "the outside relation grant sets only chat";
7038
+ if (grant.subject.relation === "outside" && grant.chat === "muted") return "the outside relation grant sets chat open or blocked";
7039
+ if (grant.subject.relation === "org" && grant.chat !== void 0) return "the org relation grant sets only ask";
7040
+ }
7032
7041
  if (own && (grant.chat !== void 0 || grant.agentMail !== void 0 || grant.ask !== void 0 || grant.trustedUntil !== void 0)) return "a grant about its own person sets only discoverable";
7033
7042
  if (!own && grant.discoverable !== void 0) return "discoverable is set only on a grant about its own person";
7034
7043
  if (grant.trustedUntil !== void 0) {
@@ -7041,7 +7050,7 @@ function cardProblem(card) {
7041
7050
  if (card.from === card.to) return "a card is about another person";
7042
7051
  if (card.can.trustedUntil !== void 0 && card.can.ask !== "open") return "trustedUntil needs ask open";
7043
7052
  }
7044
- var ChatLevel, AgentMailLevel, AskLevel, GrantSubject, ConsentGrant, GrantSync, CardCan, ContactCard, CardData, CardGetData;
7053
+ var ChatLevel, AgentMailLevel, AskLevel, GrantRelation, GrantSubject, ConsentGrant, GrantSync, CardCan, ContactCard, CardData, CardGetData;
7045
7054
  var init_consent$2 = __esmMin((() => {
7046
7055
  init_zod();
7047
7056
  init_common();
@@ -7058,7 +7067,12 @@ var init_consent$2 = __esmMin((() => {
7058
7067
  "request",
7059
7068
  "no"
7060
7069
  ]);
7061
- GrantSubject = union$1([strictObject({ person: PersonId }), strictObject({ conv: ConvId })]);
7070
+ GrantRelation = _enum(["outside", "org"]);
7071
+ GrantSubject = union$1([
7072
+ strictObject({ person: PersonId }),
7073
+ strictObject({ conv: ConvId }),
7074
+ strictObject({ relation: GrantRelation })
7075
+ ]);
7062
7076
  ConsentGrant = object$1({
7063
7077
  v: literal(PROTOCOL),
7064
7078
  realm: RealmId,
@@ -7520,6 +7534,7 @@ var init_directory_frames = __esmMin((() => {
7520
7534
  init_common();
7521
7535
  init_directory$2();
7522
7536
  init_realm$1();
7537
+ init_conversation();
7523
7538
  init_jelly$3();
7524
7539
  Req$1 = string$1().min(1).max(64);
7525
7540
  Size = number$1().int().positive();
@@ -7617,7 +7632,8 @@ var init_directory_frames = __esmMin((() => {
7617
7632
  url: WakeUrl,
7618
7633
  ticket: Jws.refine((ticket) => ticket.length <= WAKE_TICKET_BYTES, { message: `at most ${WAKE_TICKET_BYTES} bytes` }),
7619
7634
  agents: array(SessionName).max(64),
7620
- awaiting: array(Ulid).max(500)
7635
+ awaiting: array(Ulid).max(500),
7636
+ dms: array(ConvId).max(256).optional()
7621
7637
  });
7622
7638
  WakeDisarmFrame = object$1({
7623
7639
  t: literal("wake.disarm"),
@@ -7708,7 +7724,7 @@ var init_directory_frames = __esmMin((() => {
7708
7724
  ]);
7709
7725
  }));
7710
7726
  // packages/core/dist/schema/ipc/device.js
7711
- var BindingState, RealmAlarmSummary, DeviceStatus, DeviceChangeView, DeviceListEntry, LinkText, Sas, LinkPreview, KeyFingerprint, LoginUserCode, LoginPurpose, LoginKind, LoginShown, LoginState, DeviceRemoveParams, DeviceApproveParams, IdentityReplaceParams, SignInStep, DirectoryEraseParams, DEVICE_METHODS;
7727
+ var BindingState, RealmAlarmSummary, DeviceStatus, DeviceChangeView, PresenceFeature, DeviceListEntry, LinkText, Sas, LinkPreview, KeyFingerprint, LoginUserCode, LoginPurpose, LoginKind, LoginShown, LoginState, DeviceRemoveParams, DeviceApproveParams, IdentityReplaceParams, SignInStep, DirectoryEraseParams, DEVICE_METHODS;
7712
7728
  var init_device = __esmMin((() => {
7713
7729
  init_zod();
7714
7730
  init_common();
@@ -7759,6 +7775,7 @@ var init_device = __esmMin((() => {
7759
7775
  approver: MachineLabel.optional(),
7760
7776
  at: Millis
7761
7777
  });
7778
+ PresenceFeature = string$1().regex(/^[a-z][a-z0-9-]{0,31}$/, "a feature name");
7762
7779
  DeviceListEntry = object$1({
7763
7780
  device: MachineId,
7764
7781
  label: MachineLabel,
@@ -7777,7 +7794,8 @@ var init_device = __esmMin((() => {
7777
7794
  at: Millis,
7778
7795
  reason: UnbindReason,
7779
7796
  by: MachineId
7780
- }).optional()
7797
+ }).optional(),
7798
+ features: array(PresenceFeature).max(32).optional()
7781
7799
  });
7782
7800
  LinkText = string$1().min(1).max(2048);
7783
7801
  Sas = string$1().regex(/^[0-9]{6}$/, "six digits");
@@ -7984,7 +8002,10 @@ var init_device = __esmMin((() => {
7984
8002
  };
7985
8003
  }));
7986
8004
  // packages/core/dist/schema/ipc/shapes.js
7987
- var SessionKey, SendExpect, DirectoryEntry, PeerRef, InboxItem, TaskFollowUp, TaskRecord, SendTarget, SendResult;
8005
+ function taskViewRules(schema) {
8006
+ return schema.refine((r) => r.signIn === void 0 || r.task.status.state === "TASK_STATE_AUTH_REQUIRED", "signIn is for an AUTH_REQUIRED task").refine((r) => r.refusal === void 0 || r.task.status.state === "TASK_STATE_REJECTED", "refusal is for a REJECTED task").refine((r) => r.question === void 0 || r.role === "executor" && (r.task.status.state === "TASK_STATE_AUTH_REQUIRED" || r.task.status.state === "TASK_STATE_INPUT_REQUIRED"), "question is for the executor's side of an AUTH_REQUIRED or INPUT_REQUIRED task");
8007
+ }
8008
+ var SessionKey, ActionToken, SendExpect, DirectoryEntry, PeerRef, InboxItem, TaskFollowUp, TASK_SIGN_IN_TOOL_CHARS, TASK_REFUSAL_CHARS, HostLocalId, TaskQuestion, TaskRecordFields, TaskRecord, PersonTask, SendTarget, SendResult;
7988
8009
  var init_shapes = __esmMin((() => {
7989
8010
  init_zod();
7990
8011
  init_a2a();
@@ -7993,6 +8014,7 @@ var init_shapes = __esmMin((() => {
7993
8014
  init_delegation$2();
7994
8015
  init_errors();
7995
8016
  SessionKey = string$1().min(1).max(200);
8017
+ ActionToken = string$1().regex(/^[A-Za-z0-9_-]{43}$/, "action token");
7996
8018
  SendExpect = _enum([
7997
8019
  "next_turn",
7998
8020
  "wake",
@@ -8063,7 +8085,14 @@ var init_shapes = __esmMin((() => {
8063
8085
  stage: DeliveryStage,
8064
8086
  at: Millis
8065
8087
  });
8066
- TaskRecord = object$1({
8088
+ TASK_SIGN_IN_TOOL_CHARS = LIMITS.previewChars;
8089
+ TASK_REFUSAL_CHARS = LIMITS.requestChars;
8090
+ HostLocalId = string$1().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/, "a plain identifier of at most 200 characters");
8091
+ TaskQuestion = strictObject({
8092
+ sessionId: HostLocalId,
8093
+ approvalId: HostLocalId
8094
+ });
8095
+ TaskRecordFields = object$1({
8067
8096
  task: Task$1.extend({ id: Ulid }),
8068
8097
  role: _enum(["requester", "executor"]),
8069
8098
  key: SessionKey.describe("local session that owns this side of the task"),
@@ -8071,8 +8100,33 @@ var init_shapes = __esmMin((() => {
8071
8100
  createdAt: Millis,
8072
8101
  updatedAt: Millis,
8073
8102
  chain: DelegationSummary.optional(),
8074
- followUps: array(TaskFollowUp).min(1).max(LIMITS.taskFollowUps).optional()
8075
- });
8103
+ followUps: array(TaskFollowUp).min(1).max(LIMITS.taskFollowUps).optional(),
8104
+ agent: SessionName.optional(),
8105
+ person: object$1({
8106
+ id: PersonId,
8107
+ name: string$1().max(200)
8108
+ }).optional(),
8109
+ org: _enum([
8110
+ "same",
8111
+ "other",
8112
+ "none"
8113
+ ]).optional(),
8114
+ signIn: object$1({
8115
+ tool: string$1().min(1).max(TASK_SIGN_IN_TOOL_CHARS).optional(),
8116
+ by: _enum(["you", "them"])
8117
+ }).optional(),
8118
+ refusal: object$1({ reason: string$1().min(1).max(TASK_REFUSAL_CHARS) }).optional(),
8119
+ question: TaskQuestion.optional()
8120
+ });
8121
+ TaskRecord = taskViewRules(TaskRecordFields);
8122
+ PersonTask = taskViewRules(TaskRecordFields.omit({ key: true }).extend({
8123
+ key: never().optional(),
8124
+ session: SessionName.describe("this person's agent on this device that holds this side of the task"),
8125
+ cancel: strictObject({
8126
+ name: literal("cancel"),
8127
+ token: ActionToken
8128
+ }).optional()
8129
+ })).refine((r) => r.cancel === void 0 || r.role === "requester", "cancel is for the requester side");
8076
8130
  SendTarget = SessionAddress;
8077
8131
  SendResult = object$1({
8078
8132
  to: SendTarget,
@@ -8086,13 +8140,14 @@ var init_shapes = __esmMin((() => {
8086
8140
  });
8087
8141
  }));
8088
8142
  // packages/core/dist/schema/ipc/consent.js
8089
- var GrantTarget, Levels, GrantView, GrantReview, GrantSetParams, AskRequest, ContactRequestView, AgentInvitationRequest, ContactSuggestion, HeldRequest, RequestAnswer, RequestText, PersonReach, RequestAnswerParams, ContactRequestParams, CONSENT_METHODS;
8143
+ var GrantTarget, Levels, GrantView, GrantReview, GrantSetParams, RequestAnswer, RequestAction, Actions, AskRequest, ContactRequestView, AgentInvitationRequest, ContactSuggestion, HeldRequest, RequestText, PersonReach, RequestAnswerParams, ContactRequestParams, ReachGroupId, ReachValue, ReachOption, ReachGroup, ReachSnapshot, ReachSetParams, REACH_QUESTIONS, CONSENT_METHODS;
8090
8144
  var init_consent$1 = __esmMin((() => {
8091
8145
  init_zod();
8092
8146
  init_common();
8093
8147
  init_conversation();
8094
8148
  init_consent$2();
8095
8149
  init_directory_frames();
8150
+ init_shapes();
8096
8151
  GrantTarget = union$1([strictObject({ person: PersonId }), strictObject({ conv: ConvId })]);
8097
8152
  Levels = {
8098
8153
  chat: ChatLevel.optional(),
@@ -8123,6 +8178,18 @@ var init_consent$1 = __esmMin((() => {
8123
8178
  ...Levels,
8124
8179
  trust: literal(true).optional()
8125
8180
  });
8181
+ RequestAnswer = _enum([
8182
+ "once",
8183
+ "always",
8184
+ "accept",
8185
+ "decline",
8186
+ "block"
8187
+ ]);
8188
+ RequestAction = strictObject({
8189
+ name: RequestAnswer,
8190
+ token: ActionToken
8191
+ });
8192
+ Actions = { actions: array(RequestAction).max(4).optional() };
8126
8193
  AskRequest = object$1({
8127
8194
  id: Ulid,
8128
8195
  kind: _enum(["message", "task"]),
@@ -8136,7 +8203,8 @@ var init_consent$1 = __esmMin((() => {
8136
8203
  to: SessionName,
8137
8204
  preview: string$1().max(LIMITS.previewChars),
8138
8205
  at: Millis,
8139
- exp: Millis
8206
+ exp: Millis,
8207
+ ...Actions
8140
8208
  });
8141
8209
  ContactRequestView = object$1({
8142
8210
  id: Ulid,
@@ -8162,7 +8230,8 @@ var init_consent$1 = __esmMin((() => {
8162
8230
  }),
8163
8231
  text: string$1().max(LIMITS.requestChars),
8164
8232
  at: Millis,
8165
- exp: Millis
8233
+ exp: Millis,
8234
+ ...Actions
8166
8235
  });
8167
8236
  AgentInvitationRequest = strictObject({
8168
8237
  kind: literal("agent-invitation"),
@@ -8170,14 +8239,16 @@ var init_consent$1 = __esmMin((() => {
8170
8239
  cid: ConvId,
8171
8240
  from: ParticipantId,
8172
8241
  to: ParticipantId,
8173
- at: Millis
8242
+ at: Millis,
8243
+ ...Actions
8174
8244
  });
8175
8245
  ContactSuggestion = object$1({
8176
8246
  id: Ulid,
8177
8247
  kind: literal("suggestion"),
8178
8248
  agent: SessionName,
8179
8249
  email: Email,
8180
- at: Millis
8250
+ at: Millis,
8251
+ ...Actions
8181
8252
  });
8182
8253
  HeldRequest = union$1([
8183
8254
  AskRequest,
@@ -8185,13 +8256,6 @@ var init_consent$1 = __esmMin((() => {
8185
8256
  ContactSuggestion,
8186
8257
  AgentInvitationRequest
8187
8258
  ]);
8188
- RequestAnswer = _enum([
8189
- "once",
8190
- "always",
8191
- "accept",
8192
- "decline",
8193
- "block"
8194
- ]);
8195
8259
  RequestText = string$1().min(1).refine((text) => text.length <= LIMITS.requestChars, { message: `at most ${LIMITS.requestChars} UTF-16 code units` });
8196
8260
  PersonReach = object$1({
8197
8261
  person: PersonId,
@@ -8203,14 +8267,98 @@ var init_consent$1 = __esmMin((() => {
8203
8267
  RequestAnswerParams = strictObject({
8204
8268
  id: Ulid,
8205
8269
  answer: RequestAnswer,
8206
- all: boolean().optional()
8207
- });
8270
+ all: boolean().optional(),
8271
+ token: ActionToken.optional()
8272
+ }).refine((p) => p.token === void 0 || p.all !== true, "an action token answers one request: drop all");
8208
8273
  ContactRequestParams = strictObject({
8209
8274
  email: Email,
8210
8275
  text: RequestText,
8211
8276
  suggestion: Ulid.optional()
8212
8277
  });
8278
+ ReachGroupId = _enum(["message", "ask"]);
8279
+ ReachValue = _enum([
8280
+ "open",
8281
+ "ask",
8282
+ "never"
8283
+ ]);
8284
+ ReachOption = object$1({
8285
+ value: ReachValue,
8286
+ label: string$1().max(40),
8287
+ description: string$1().max(300)
8288
+ });
8289
+ ReachGroup = object$1({
8290
+ id: ReachGroupId,
8291
+ label: string$1().max(80),
8292
+ options: array(ReachOption).length(3),
8293
+ value: ReachValue.nullable(),
8294
+ lapsed: ReachValue.optional()
8295
+ });
8296
+ ReachSnapshot = object$1({
8297
+ revision: string$1().min(1).max(64),
8298
+ available: boolean(),
8299
+ groups: array(ReachGroup).length(2)
8300
+ });
8301
+ ReachSetParams = strictObject({
8302
+ revision: string$1().min(1).max(64),
8303
+ changes: strictObject({
8304
+ message: ReachValue.optional(),
8305
+ ask: ReachValue.optional()
8306
+ })
8307
+ });
8308
+ REACH_QUESTIONS = [{
8309
+ id: "message",
8310
+ label: "Who can message you",
8311
+ options: [
8312
+ {
8313
+ value: "open",
8314
+ label: "Open",
8315
+ description: "Anyone outside your org may message you."
8316
+ },
8317
+ {
8318
+ value: "ask",
8319
+ label: "Ask me first",
8320
+ description: "People outside your org send a message request first, and you accept or decline it. People you have accepted may message you."
8321
+ },
8322
+ {
8323
+ value: "never",
8324
+ label: "Never",
8325
+ description: "No one outside your org may message you or send you a request. Your org, and people you have let message you, still can."
8326
+ }
8327
+ ]
8328
+ }, {
8329
+ id: "ask",
8330
+ label: "How agents may ask yours",
8331
+ options: [
8332
+ {
8333
+ value: "open",
8334
+ label: "Open",
8335
+ description: "Your teammates' agents may ask your agents without asking you. Their requests run guarded."
8336
+ },
8337
+ {
8338
+ value: "ask",
8339
+ label: "Ask me first",
8340
+ description: "Each request from a teammate's agent to yours waits for your answer."
8341
+ },
8342
+ {
8343
+ value: "never",
8344
+ label: "Never",
8345
+ description: "Your teammates' agents may not ask yours. Agents of people outside your org never may."
8346
+ }
8347
+ ]
8348
+ }];
8213
8349
  CONSENT_METHODS = {
8350
+ "reach.get": {
8351
+ params: strictObject({}),
8352
+ result: ReachSnapshot
8353
+ },
8354
+ "reach.set": {
8355
+ params: ReachSetParams,
8356
+ result: ReachSnapshot
8357
+ },
8358
+ "reach.set.preview": {
8359
+ params: ReachSetParams,
8360
+ result: object$1({ sentence: string$1().max(500) })
8361
+ },
8214
8362
  "grant.list": {
8215
8363
  params: strictObject({ subject: GrantTarget.optional() }),
8216
8364
  result: object$1({
@@ -8391,6 +8539,7 @@ var init_jelly$2 = __esmMin((() => {
8391
8539
  }).optional(),
8392
8540
  line: string$1().max(120).optional(),
8393
8541
  device: NoticeDevice.optional(),
8542
+ key: string$1().regex(/^task:[0-9A-HJKMNP-TV-Z]{26}$/, "task:<task id>").optional(),
8394
8543
  at: Millis
8395
8544
  });
8396
8545
  NoticeCounts = object$1({ requests: number$1().int().nonnegative() });
@@ -8461,7 +8610,8 @@ var init_jelly$2 = __esmMin((() => {
8461
8610
  }),
8462
8611
  result: object$1({
8463
8612
  relay: _enum(["armed", "pending"]),
8464
- awaiting: number$1().int().nonnegative()
8613
+ awaiting: number$1().int().nonnegative(),
8614
+ dms: number$1().int().nonnegative()
8465
8615
  })
8466
8616
  },
8467
8617
  "wake.disarm": {
@@ -8506,6 +8656,7 @@ var init_conversation_views = __esmMin((() => {
8506
8656
  init_common();
8507
8657
  init_conversation();
8508
8658
  init_a2a();
8659
+ init_shapes();
8509
8660
  Count$2 = number$1().int().nonnegative().safe();
8510
8661
  Seq$1 = number$1().int().nonnegative().safe();
8511
8662
  Name = string$1().max(200);
@@ -8581,7 +8732,11 @@ var init_conversation_views = __esmMin((() => {
8581
8732
  });
8582
8733
  TaskView = object$1({
8583
8734
  id: Ulid,
8584
- state: TaskState
8735
+ state: TaskState,
8736
+ cancel: strictObject({
8737
+ name: literal("cancel"),
8738
+ token: ActionToken
8739
+ }).optional()
8585
8740
  });
8586
8741
  EventView = object$1({
8587
8742
  cid: ConvId,
@@ -8740,6 +8895,11 @@ var init_conversation_views = __esmMin((() => {
8740
8895
  object$1({
8741
8896
  type: literal("gap"),
8742
8897
  pos: Seq$1
8898
+ }),
8899
+ object$1({
8900
+ type: literal("task"),
8901
+ pos: Seq$1,
8902
+ task: PersonTask
8743
8903
  })
8744
8904
  ]).refine((entry) => entry.type !== "event.update" || entry.cid === entry.event.cid && entry.id === entry.event.id, { message: "event.update names the same conversation and event as its projection" });
8745
8905
  }));
@@ -8855,7 +9015,12 @@ var init_conversations = __esmMin((() => {
8855
9015
  at: Millis,
8856
9016
  cid: ConvId.optional(),
8857
9017
  agent: SessionName.optional(),
8858
- state: _enum(["held", "pending-delivery"])
9018
+ state: _enum([
9019
+ "held",
9020
+ "pending-delivery",
9021
+ "resend",
9022
+ "outdated"
9023
+ ])
8859
9024
  });
8860
9025
  ConvListParams = strictObject({
8861
9026
  key: Key,
@@ -9497,16 +9662,47 @@ var init_ipc = __esmMin((() => {
9497
9662
  message: strictObject({ parts: array(Part$1).min(1).max(LIMITS.partsPerMessage) }).optional(),
9498
9663
  artifact: Artifact.optional(),
9499
9664
  append: boolean().optional(),
9500
- lastChunk: boolean().optional()
9501
- }),
9665
+ lastChunk: boolean().optional(),
9666
+ signIn: strictObject({ tool: string$1().min(1).max(TASK_SIGN_IN_TOOL_CHARS) }).optional()
9667
+ }).refine((p) => p.signIn === void 0 || p.state === "TASK_STATE_AUTH_REQUIRED", "signIn goes with TASK_STATE_AUTH_REQUIRED"),
9502
9668
  result: TaskRecord
9503
9669
  },
9504
- "task.cancel": {
9670
+ "task.question": {
9505
9671
  params: strictObject({
9506
9672
  key: SessionKey,
9507
9673
  taskId: Ulid,
9508
- reason: string$1().max(1e3).optional()
9674
+ question: TaskQuestion.nullable()
9675
+ }),
9676
+ result: object$1({ ok: literal(true) })
9677
+ },
9678
+ "tasks.list": {
9679
+ params: strictObject({
9680
+ role: _enum(["requester", "executor"]).optional(),
9681
+ state: TaskState.optional(),
9682
+ session: SessionName.optional().describe("only this agent's tasks"),
9683
+ cursor: string$1().min(1).max(200).optional().describe("opaque; from a previous nextCursor"),
9684
+ limit: number$1().int().min(1).max(100).optional()
9685
+ }),
9686
+ result: object$1({
9687
+ tasks: array(PersonTask),
9688
+ nextCursor: string$1().max(200).optional().describe("absent when there are no more pages"),
9689
+ total: number$1().int().nonnegative()
9690
+ })
9691
+ },
9692
+ "tasks.get": {
9693
+ params: strictObject({
9694
+ taskId: Ulid,
9695
+ role: _enum(["requester", "executor"]).optional()
9509
9696
  }),
9697
+ result: PersonTask
9698
+ },
9699
+ "task.cancel": {
9700
+ params: strictObject({
9701
+ key: SessionKey.optional(),
9702
+ taskId: Ulid,
9703
+ reason: string$1().max(1e3).optional(),
9704
+ token: ActionToken.optional()
9705
+ }).refine((p) => p.key === void 0 !== (p.token === void 0), "give exactly one of key or token"),
9510
9706
  result: TaskRecord
9511
9707
  },
9512
9708
  "events.subscribe": {
@@ -9659,7 +9855,8 @@ var init_ipc = __esmMin((() => {
9659
9855
  "task.list",
9660
9856
  "task.get",
9661
9857
  "task.update",
9662
- "task.cancel"
9858
+ "task.cancel",
9859
+ "task.question"
9663
9860
  ];
9664
9861
  HUMAN_METHODS = [
9665
9862
  ...conversationTier("human"),
@@ -9668,6 +9865,9 @@ var init_ipc = __esmMin((() => {
9668
9865
  "grant.list",
9669
9866
  "grant.set",
9670
9867
  "grant.set.preview",
9868
+ "reach.get",
9869
+ "reach.set",
9870
+ "reach.set.preview",
9671
9871
  "request.list",
9672
9872
  "request.answer",
9673
9873
  "request.answer.preview",
@@ -9677,6 +9877,8 @@ var init_ipc = __esmMin((() => {
9677
9877
  "notice.subscribe",
9678
9878
  "notice.list",
9679
9879
  "notice.ack",
9880
+ "tasks.list",
9881
+ "tasks.get",
9680
9882
  "device.approve.preview",
9681
9883
  "device.remove.preview",
9682
9884
  "identity.replace.preview",
@@ -9842,7 +10044,8 @@ var init_conv_frames = __esmMin((() => {
9842
10044
  ConvCapabilitiesFrame = strictObject({
9843
10045
  t: literal("conv.capabilities"),
9844
10046
  req: Req,
9845
- outdated: array(PersonId).max(LIMITS.convOpParticipants)
10047
+ outdated: array(PersonId).max(LIMITS.convOpParticipants),
10048
+ unknown: array(PersonId).min(1).max(LIMITS.convOpParticipants).optional()
9846
10049
  });
9847
10050
  ConvPushFrame = union$1([strictObject({
9848
10051
  t: literal("conv.push"),
@@ -10055,6 +10258,11 @@ var init_agent_tools = __esmMin((() => {
10055
10258
  titleAuthor: Author$1.optional().describe("With title: who wrote the request; ours when you asked (mine)"),
10056
10259
  status: authored("The latest status message").optional(),
10057
10260
  statusAuthor: Author$1.optional().describe("With status: who wrote it; ours when you run the task (asked)"),
10261
+ signIn: object$1({
10262
+ tool: authored("The tool the executor named").optional(),
10263
+ by: _enum(["you", "them"]).describe("Whose sign-in it is: you (your person, for an agent of theirs) or them (the other person); your Messaging daemon decides, never the peer")
10264
+ }).optional().describe("auth_required only: the task waits on a sign-in; the tool was written by the executor, as statusAuthor says"),
10265
+ refusal: object$1({ reason: authored("The executor's reason") }).optional().describe("rejected only: why the executor declined, in its words, as statusAuthor says"),
10058
10266
  createdAt: iso$1,
10059
10267
  updatedAt: iso$1,
10060
10268
  consent: ToolConsentSchema.optional(),
@@ -10335,7 +10543,7 @@ var init_b64u = __esmMin((() => {
10335
10543
  // packages/core/dist/version.js
10336
10544
  var VERSION;
10337
10545
  var init_version$1 = __esmMin((() => {
10338
- VERSION = "0.8.0";
10546
+ VERSION = "0.9.0";
10339
10547
  }));
10340
10548
  // packages/core/dist/jcs.js
10341
10549
  function hasLoneSurrogate(text) {
@@ -12192,8 +12400,11 @@ var init_jelly_host = __esmMin((() => {
12192
12400
  "people.search",
12193
12401
  "people.get",
12194
12402
  "grant.list",
12403
+ "reach.get",
12195
12404
  "request.list",
12196
12405
  "sessions.list",
12406
+ "tasks.list",
12407
+ "tasks.get",
12197
12408
  "notice.subscribe",
12198
12409
  "notice.list",
12199
12410
  "device.list"
@@ -13548,7 +13759,25 @@ function verifyConsentGrant(raw, ctx) {
13548
13759
  }
13549
13760
  }
13550
13761
  function grantSubjectKey(subject) {
13551
- return "person" in subject ? `person:${subject.person}` : `conv:${subject.conv}`;
13762
+ return "person" in subject ? `person:${subject.person}` : "conv" in subject ? `conv:${subject.conv}` : `relation:${subject.relation}`;
13763
+ }
13764
+ function reachChoices(outside, org) {
13765
+ const message = outside === void 0 ? void 0 : outside.chat === "open" ? "open" : outside.chat === "blocked" ? "never" : "ask";
13766
+ const ask = org?.ask === "open" ? "open" : org?.ask === "request" ? "ask" : org?.ask === "no" ? "never" : void 0;
13767
+ return {
13768
+ ...message ? { message } : {},
13769
+ ...ask ? { ask } : {}
13770
+ };
13771
+ }
13772
+ function reachGrant(group, value) {
13773
+ if (group === "message") return {
13774
+ subject: { relation: "outside" },
13775
+ levels: value === "open" ? { chat: "open" } : value === "never" ? { chat: "blocked" } : {}
13776
+ };
13777
+ return {
13778
+ subject: { relation: "org" },
13779
+ levels: { ask: value === "open" ? "open" : value === "never" ? "no" : "request" }
13780
+ };
13552
13781
  }
13553
13782
  function preferGrant(a, b, current) {
13554
13783
  if (a.ts !== b.ts) return a.ts > b.ts ? a : b;
@@ -13600,8 +13829,24 @@ function levelsOf(grant) {
13600
13829
  discoverable: grant.discoverable
13601
13830
  });
13602
13831
  }
13603
- function effectivePolicy(relation, person, channels, now) {
13832
+ function baseLevels(relation, reach) {
13604
13833
  const base = DEFAULTS[relation];
13834
+ if (relation === "org") return reach.ask === void 0 ? base : {
13835
+ ...base,
13836
+ ask: reach.ask === "open" ? "open" : reach.ask === "never" ? "no" : "request"
13837
+ };
13838
+ if (reach.message === "never") return {
13839
+ ...base,
13840
+ chat: "blocked"
13841
+ };
13842
+ if (relation === "stranger" && reach.message === "open") return {
13843
+ ...base,
13844
+ chat: "open"
13845
+ };
13846
+ return base;
13847
+ }
13848
+ function effectivePolicy(relation, person, channels, now, reach = {}) {
13849
+ const base = baseLevels(relation, reach);
13605
13850
  const channelAsk = channels.map((grant) => grant.ask).filter((level) => level !== void 0);
13606
13851
  const channelMail = channels.map((grant) => grant.agentMail).filter((level) => level !== void 0);
13607
13852
  const ask = person?.ask ?? (channelAsk.length > 0 ? channelAsk.reduce((a, b) => ASK_RANK[b] > ASK_RANK[a] ? b : a) : void 0);
@@ -13625,19 +13870,25 @@ function decide(args) {
13625
13870
  action: "deliver",
13626
13871
  consent: "self"
13627
13872
  };
13628
- const policy = effectivePolicy(args.relation, args.grant, args.channels, args.now);
13873
+ const policy = effectivePolicy(args.relation, args.grant, args.channels, args.now, args.reach);
13629
13874
  const trusted = (chain) => policy.trustedUntil !== void 0 && chain.every((person) => person === args.sender || person === args.self);
13630
13875
  if (inbound.kind === "answer") return {
13631
13876
  action: "deliver",
13632
13877
  consent: trusted([args.sender]) ? "trusted" : "guarded"
13633
13878
  };
13634
- if (policy.chat === "blocked") return {
13879
+ const blocked = policy.chat === "blocked";
13880
+ if (blocked && policy.granted.chat) return {
13635
13881
  action: "refuse",
13636
13882
  code: "refused"
13637
13883
  };
13638
13884
  const stranger = args.relation === "stranger";
13885
+ const defaultCode = (outside) => outside && !blocked ? "request_required" : "refused";
13639
13886
  switch (inbound.kind) {
13640
13887
  case "op":
13888
+ if (blocked) return {
13889
+ action: "refuse",
13890
+ code: "refused"
13891
+ };
13641
13892
  if (stranger) return {
13642
13893
  action: "refuse",
13643
13894
  code: "request_required"
@@ -13654,9 +13905,13 @@ function decide(args) {
13654
13905
  };
13655
13906
  return {
13656
13907
  action: "refuse",
13657
- code: stranger && !policy.granted.agentMail ? "request_required" : "refused"
13908
+ code: defaultCode(stranger && !policy.granted.agentMail)
13658
13909
  };
13659
13910
  }
13911
+ if (blocked) return {
13912
+ action: "refuse",
13913
+ code: "refused"
13914
+ };
13660
13915
  if (policy.chat === "request_required") return {
13661
13916
  action: "refuse",
13662
13917
  code: "request_required"
@@ -13670,7 +13925,11 @@ function decide(args) {
13670
13925
  consent: "guarded"
13671
13926
  };
13672
13927
  case "request":
13673
- if (stranger) return { action: "hold" };
13928
+ if (blocked) return {
13929
+ action: "refuse",
13930
+ code: "refused"
13931
+ };
13932
+ if (policy.chat === "request_required") return { action: "hold" };
13674
13933
  return policy.chat === "muted" ? {
13675
13934
  action: "deliver",
13676
13935
  consent: "guarded",
@@ -13687,7 +13946,7 @@ function decide(args) {
13687
13946
  if (policy.ask === "request") return { action: "hold" };
13688
13947
  return {
13689
13948
  action: "refuse",
13690
- code: !policy.granted.ask && (stranger || args.relation === "contact") ? "request_required" : "refused"
13949
+ code: defaultCode(!policy.granted.ask && (stranger || args.relation === "contact"))
13691
13950
  };
13692
13951
  }
13693
13952
  }
@@ -13769,8 +14028,8 @@ function verifyContactCard(raw, ctx) {
13769
14028
  throw asMessagingError(error, "card");
13770
14029
  }
13771
14030
  }
13772
- function cardCan(relation, grant, now) {
13773
- const policy = effectivePolicy(relation, grant, [], now);
14031
+ function cardCan(relation, grant, now, reach = {}) {
14032
+ const policy = effectivePolicy(relation, grant, [], now, reach);
13774
14033
  return {
13775
14034
  chat: policy.chat === "open" || policy.chat === "muted",
13776
14035
  agentMail: policy.agentMail === "open",
@@ -13916,6 +14175,9 @@ function canTransition(from, to) {
13916
14175
  function isTerminal(state) {
13917
14176
  return TERMINAL_STATES.includes(state);
13918
14177
  }
14178
+ function isInterrupted(state) {
14179
+ return INTERRUPTED_STATES.includes(state);
14180
+ }
13919
14181
  var NEXT;
13920
14182
  var init_task = __esmMin((() => {
13921
14183
  init_a2a();
@@ -14939,6 +15201,7 @@ var init_present = __esmMin((() => {
14939
15201
  init_errors();
14940
15202
  init_peer_text();
14941
15203
  init_common();
15204
+ init_ipc();
14942
15205
  init_text();
14943
15206
  LIMITS.toolTextChars;
14944
15207
  createFence({ tag: PEER_FENCE_TAG });
@@ -16786,7 +17049,7 @@ async function createServer(options, conversations) {
16786
17049
  method: "hello",
16787
17050
  params: {
16788
17051
  daemon: "messagingd",
16789
- version: options.version ?? "0.8.0",
17052
+ version: options.version ?? "0.9.0",
16790
17053
  protocol: 1,
16791
17054
  pid: process.pid,
16792
17055
  ...options.deployment ? { deployment: options.deployment } : {}
@@ -18859,7 +19122,7 @@ function taskRow(r) {
18859
19122
  }
18860
19123
  return row;
18861
19124
  }
18862
- var DatabaseSync$1, CONV_JUNK_ALARM_LIMIT, STORE_BASELINE, MIGRATIONS$1, STORE_SCHEMA, kvGet, kvSet, storeNumber, RESET_HINT, TASKS, text$1, num, json, opt, Store;
19125
+ var DatabaseSync$1, CONV_JUNK_ALARM_LIMIT, STORE_BASELINE, MIGRATIONS$1, RELATION$1, relationOf, STORE_SCHEMA, kvGet, kvSet, storeNumber, RESET_HINT, TASKS, text$1, num, json, opt, Store;
18863
19126
  var init_store$3 = __esmMin((() => {
18864
19127
  init_dist$3();
18865
19128
  ({DatabaseSync: DatabaseSync$1} = process.getBuiltinModule("node:sqlite"));
@@ -19168,8 +19431,14 @@ CREATE TABLE IF NOT EXISTS conv_evidence (
19168
19431
  at INTEGER NOT NULL,
19169
19432
  UNIQUE (cid, digest, code)
19170
19433
  ) STRICT;
19434
+ `,
19435
+ `
19436
+ CREATE TABLE IF NOT EXISTS reach_grants (relation TEXT PRIMARY KEY, grant TEXT NOT NULL) STRICT;
19437
+ CREATE TABLE IF NOT EXISTS reach_reviews (relation TEXT PRIMARY KEY, id TEXT NOT NULL, levels TEXT NOT NULL, at INTEGER NOT NULL) STRICT;
19171
19438
  `
19172
19439
  ];
19440
+ RELATION$1 = "relation:";
19441
+ relationOf = (subject) => subject.startsWith(RELATION$1) ? subject.slice(9) : void 0;
19173
19442
  STORE_SCHEMA = 1 + MIGRATIONS$1.length;
19174
19443
  kvGet = (db, k) => {
19175
19444
  const row = db.prepare("SELECT v FROM kv WHERE k = ?").get(k);
@@ -19740,6 +20009,16 @@ CREATE TABLE IF NOT EXISTS conv_evidence (
19740
20009
  contactRequests(person) {
19741
20010
  return (person === void 0 ? this.#all("SELECT * FROM contact_requests ORDER BY at, id") : this.#all("SELECT * FROM contact_requests WHERE person = ? ORDER BY at, id", person)).map(contactRequestRow);
19742
20011
  }
20012
+ legacyOutgoingRequests(now) {
20013
+ return this.#all(`SELECT t.id AS id, substr(t.room, 4) AS person, t.at AS at FROM transcript t
20014
+ WHERE t.direction = 'out' AND t.room LIKE 'dm:%'
20015
+ AND EXISTS (SELECT 1 FROM chat_sent c WHERE c.line = t.id AND c.stage = 'sent' AND c.held_at IS NOT NULL AND c.exp > ?)
20016
+ ORDER BY t.at, t.id`, now).map((r) => ({
20017
+ id: String(r.id),
20018
+ person: String(r.person),
20019
+ at: Number(r.at)
20020
+ }));
20021
+ }
19743
20022
  contactRequestCount() {
19744
20023
  return Number(this.#get("SELECT COUNT(*) AS n FROM contact_requests")?.n ?? 0);
19745
20024
  }
@@ -19779,41 +20058,57 @@ CREATE TABLE IF NOT EXISTS conv_evidence (
19779
20058
  return this.#run("DELETE FROM contact_suggestions WHERE at < ?", before);
19780
20059
  }
19781
20060
  putGrant(subject, grant) {
20061
+ const relation = relationOf(subject);
20062
+ if (relation !== void 0) {
20063
+ this.#run("INSERT INTO reach_grants (relation, grant) VALUES (?, ?) ON CONFLICT (relation) DO UPDATE SET grant = excluded.grant", relation, JSON.stringify(grant));
20064
+ return;
20065
+ }
19782
20066
  this.#run("INSERT INTO grants (subject, grant) VALUES (?, ?) ON CONFLICT (subject) DO UPDATE SET grant = excluded.grant", subject, JSON.stringify(grant));
19783
20067
  }
19784
20068
  grant(subject) {
19785
- const row = this.#get("SELECT grant FROM grants WHERE subject = ?", subject);
20069
+ const relation = relationOf(subject);
20070
+ const row = relation !== void 0 ? this.#get("SELECT grant FROM reach_grants WHERE relation = ?", relation) : this.#get("SELECT grant FROM grants WHERE subject = ?", subject);
19786
20071
  return row === void 0 ? void 0 : JSON.parse(String(row.grant));
19787
20072
  }
19788
20073
  grants() {
19789
- return this.#all("SELECT subject, grant FROM grants ORDER BY subject").map((row) => ({
20074
+ return [...this.#all("SELECT subject, grant FROM grants ORDER BY subject").map((row) => ({
19790
20075
  subject: String(row.subject),
19791
20076
  grant: JSON.parse(String(row.grant))
19792
- }));
20077
+ })), ...this.#all("SELECT relation, grant FROM reach_grants ORDER BY relation").map((row) => ({
20078
+ subject: `${RELATION$1}${String(row.relation)}`,
20079
+ grant: JSON.parse(String(row.grant))
20080
+ }))];
19793
20081
  }
19794
20082
  grantCount() {
19795
20083
  return Number(this.#get("SELECT COUNT(*) AS n FROM grants")?.n ?? 0);
19796
20084
  }
19797
20085
  deleteGrant(subject) {
19798
- this.#run("DELETE FROM grants WHERE subject = ?", subject);
20086
+ const relation = relationOf(subject);
20087
+ if (relation !== void 0) this.#run("DELETE FROM reach_grants WHERE relation = ?", relation);
20088
+ else this.#run("DELETE FROM grants WHERE subject = ?", subject);
19799
20089
  }
19800
20090
  putGrantReview(subject, id, levels, at) {
19801
- this.#run("INSERT INTO grant_reviews (subject, id, levels, at) VALUES (?, ?, ?, ?) ON CONFLICT (subject) DO UPDATE SET id = excluded.id, levels = excluded.levels, at = excluded.at", subject, id, JSON.stringify(levels), at);
20091
+ const relation = relationOf(subject);
20092
+ this.#run(relation !== void 0 ? "INSERT INTO reach_reviews (relation, id, levels, at) VALUES (?, ?, ?, ?) ON CONFLICT (relation) DO UPDATE SET id = excluded.id, levels = excluded.levels, at = excluded.at" : "INSERT INTO grant_reviews (subject, id, levels, at) VALUES (?, ?, ?, ?) ON CONFLICT (subject) DO UPDATE SET id = excluded.id, levels = excluded.levels, at = excluded.at", relation ?? subject, id, JSON.stringify(levels), at);
19802
20093
  }
19803
20094
  grantReviews() {
19804
- return this.#all("SELECT * FROM grant_reviews ORDER BY at DESC, subject").map((row) => ({
19805
- subject: String(row.subject),
19806
- id: String(row.id),
19807
- levels: JSON.parse(String(row.levels)),
19808
- at: Number(row.at)
19809
- }));
20095
+ const row = (subject, r) => ({
20096
+ subject,
20097
+ id: String(r.id),
20098
+ levels: JSON.parse(String(r.levels)),
20099
+ at: Number(r.at)
20100
+ });
20101
+ return [...this.#all("SELECT * FROM grant_reviews ORDER BY at DESC, subject").map((r) => row(String(r.subject), r)), ...this.#all("SELECT * FROM reach_reviews ORDER BY at DESC, relation").map((r) => row(`${RELATION$1}${String(r.relation)}`, r))];
19810
20102
  }
19811
20103
  grantReview(subject) {
19812
- const row = this.#get("SELECT id FROM grant_reviews WHERE subject = ?", subject);
20104
+ const relation = relationOf(subject);
20105
+ const row = relation !== void 0 ? this.#get("SELECT id FROM reach_reviews WHERE relation = ?", relation) : this.#get("SELECT id FROM grant_reviews WHERE subject = ?", subject);
19813
20106
  return row === void 0 ? void 0 : { id: String(row.id) };
19814
20107
  }
19815
20108
  deleteGrantReview(subject) {
19816
- this.#run("DELETE FROM grant_reviews WHERE subject = ?", subject);
20109
+ const relation = relationOf(subject);
20110
+ if (relation !== void 0) this.#run("DELETE FROM reach_reviews WHERE relation = ?", relation);
20111
+ else this.#run("DELETE FROM grant_reviews WHERE subject = ?", subject);
19817
20112
  }
19818
20113
  sentUndelivered(now) {
19819
20114
  return this.#all("SELECT * FROM sent WHERE exp <= ? AND stage IN ('accepted', 'relayed')", now).map(sentRow);
@@ -19878,12 +20173,13 @@ CREATE TABLE IF NOT EXISTS conv_evidence (
19878
20173
  if (r.chain !== void 0) 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));
19879
20174
  }
19880
20175
  listTasks(filter) {
19881
- const where = ["key = ?"];
19882
- const params = [filter.key];
20176
+ const where = ["1 = 1"];
20177
+ const params = [];
19883
20178
  const add = (clause, ...values) => {
19884
20179
  where.push(clause);
19885
20180
  params.push(...values);
19886
20181
  };
20182
+ if (filter.key !== void 0) add("key = ?", filter.key);
19887
20183
  if (filter.role !== void 0) add("role = ?", filter.role);
19888
20184
  if (filter.state !== void 0) add("state = ?", filter.state);
19889
20185
  if (filter.contextId !== void 0) add("context_id = ?", filter.contextId);
@@ -20613,7 +20909,7 @@ var init_conv_engine = __esmMin((() => {
20613
20909
  const pending = this.options.store.convPending(cid);
20614
20910
  if (rekey.cause === "device" && pending) {
20615
20911
  const first = admitConvEvent(parseWire(pending.event));
20616
- if (first.class === "op" && first.body.op === "rekey" && first.body.cause === "device" && first.body.device === rekey.device) return first.id;
20912
+ if (first.class === "op" && first.body.op === "rekey" && first.body.cause === "device" && first.body.device === rekey.device && (rekey.until === void 0 || first.body.until !== void 0 && first.body.until <= rekey.until)) return first.id;
20617
20913
  return await this.#queueRekey(cid, rekey, true);
20618
20914
  }
20619
20915
  return await this.#queueRekey(cid, rekey, false);
@@ -21394,12 +21690,13 @@ var init_conv_project = __esmMin((() => {
21394
21690
  ]);
21395
21691
  }));
21396
21692
  // packages/daemon/dist/conv-ipc.js
21397
- var DEFERRED_LINES, DEFERRED_MS, HOLD, REPLAY$1, RESPONSE_BYTES, ConversationIpc;
21693
+ var DEFERRED_LINES, DEFERRED_MS, CONTROL_DRAINS, HOLD, REPLAY$1, RESPONSE_BYTES, ConversationIpc;
21398
21694
  var init_conv_ipc = __esmMin((() => {
21399
21695
  init_dist$3();
21400
21696
  init_conv_project();
21401
21697
  DEFERRED_LINES = 50;
21402
21698
  DEFERRED_MS = 6048e5;
21699
+ CONTROL_DRAINS = 3;
21403
21700
  HOLD = {
21404
21701
  stage: "held",
21405
21702
  code: "context",
@@ -21440,6 +21737,18 @@ var init_conv_ipc = __esmMin((() => {
21440
21737
  own: this.options.own().person
21441
21738
  });
21442
21739
  }
21740
+ taskChanged(side) {
21741
+ this.#ring.push({
21742
+ pos: ++this.#pos,
21743
+ task: {
21744
+ id: side.id,
21745
+ role: side.role
21746
+ }
21747
+ });
21748
+ if (this.#ring.length > REPLAY$1) this.#floor = this.#ring.shift().pos;
21749
+ this.options.put("conv.ipc.pos", String(this.#pos));
21750
+ for (const wake of [...this.#waiters]) wake();
21751
+ }
21443
21752
  bind(engine) {
21444
21753
  if (this.#engine === engine) return;
21445
21754
  this.#unsubscribe?.();
@@ -21538,7 +21847,7 @@ var init_conv_ipc = __esmMin((() => {
21538
21847
  return Object.fromEntries(Object.keys(LIVE_CONVERSATION_METHODS).map((method) => [method, async (params, ctx, authority) => {
21539
21848
  if (authority?.scope === "host") throw new MessagingError("forbidden", "conversation content needs an action grant");
21540
21849
  const name = method;
21541
- const result = await this.call(name, params, ctx);
21850
+ const result = await this.call(name, params, ctx, authority);
21542
21851
  const parsed = LIVE_CONVERSATION_METHODS[name].result.parse(result);
21543
21852
  if (Buffer.byteLength(JSON.stringify(parsed)) > RESPONSE_BYTES) throw new MessagingError("too_large", "conversation response exceeds the byte budget");
21544
21853
  return parsed;
@@ -21664,7 +21973,13 @@ var init_conv_ipc = __esmMin((() => {
21664
21973
  const access = this.#access(engine, cid, scope);
21665
21974
  if (access.access === "forbidden") throw new MessagingError("forbidden", "the session cannot read this conversation");
21666
21975
  if (access.access === "ask") return HOLD;
21667
- let visible = (await projectEvents(engine, cid, scope.participant, this.#name, (p) => !this.options.blocked(participantPerson(p)))).filter((e) => e.seq >= access.since && e.at >= access.after);
21976
+ const orgs = new Map();
21977
+ const name = async (p) => {
21978
+ const profile = await this.#name(p);
21979
+ orgs.set(p, profile.org);
21980
+ return profile;
21981
+ };
21982
+ let visible = (await projectEvents(engine, cid, scope.participant, name, (p) => !this.options.blocked(p, orgs.get(p) ?? "none"))).filter((e) => e.seq >= access.since && e.at >= access.after);
21668
21983
  if (scope.agent && this.#state(engine, cid, scope).state.agentContext === "none") {
21669
21984
  const roots = new Set(visible.filter((e) => e.author.p === scope.participant || e.mentions?.some((m) => "p" in m && m.p === scope.participant)).map((e) => e.thread ?? e.id));
21670
21985
  visible = visible.filter((e) => roots.has(e.id) || e.thread !== void 0 && roots.has(e.thread));
@@ -21747,10 +22062,30 @@ var init_conv_ipc = __esmMin((() => {
21747
22062
  };
21748
22063
  const events = [];
21749
22064
  let pos = after;
21750
- for (const item of this.#ring.filter((i) => i.pos > after)) {
22065
+ const range = this.#ring.filter((i) => i.pos > after);
22066
+ for (const [index, item] of range.entries()) {
21751
22067
  pos = item.pos;
21752
- const c = item.change;
21753
22068
  let rendered;
22069
+ if ("task" in item) {
22070
+ const side = item.task;
22071
+ if (scope.key !== void 0 || scope.ctx.tier !== "admin" || !this.options.task) continue;
22072
+ if (range.slice(index + 1).some((later) => "task" in later && later.task.id === side.id && later.task.role === side.role)) continue;
22073
+ const task = await this.options.task(side.id, side.role, scope.ctx, scope.authority);
22074
+ if (!task) continue;
22075
+ rendered = {
22076
+ type: "task",
22077
+ pos,
22078
+ task
22079
+ };
22080
+ if (Buffer.byteLength(JSON.stringify([...events, rendered])) > 520192) {
22081
+ pos = item.pos - 1;
22082
+ break;
22083
+ }
22084
+ events.push(rendered);
22085
+ if (events.length >= (p.limit ?? 50)) break;
22086
+ continue;
22087
+ }
22088
+ const c = item.change;
21754
22089
  try {
21755
22090
  if (this.#access(engine, c.cid, scope).access !== "allow") continue;
21756
22091
  if (c.type === "members.changed") rendered = {
@@ -21834,7 +22169,7 @@ var init_conv_ipc = __esmMin((() => {
21834
22169
  result = await read();
21835
22170
  return result;
21836
22171
  }
21837
- async call(method, raw, ctx) {
22172
+ async call(method, raw, ctx, authority) {
21838
22173
  if (ctx.tier === "agent" && CONVERSATION_TIERS[method] !== "agent") throw new MessagingError("forbidden", "this method is human-only");
21839
22174
  const p = LIVE_CONVERSATION_METHODS[method].params.parse(raw);
21840
22175
  if (method === "people.get") {
@@ -21857,7 +22192,10 @@ var init_conv_ipc = __esmMin((() => {
21857
22192
  seats: engine?.list().filter((c) => c.state?.participants.some((p) => p.p === `${this.options.own().person}/${s.agent}` && p.state === "member")).map((c) => c.cid) ?? []
21858
22193
  })) };
21859
22194
  }
21860
- const scope = this.#scope("key" in p ? p.key : void 0, ctx);
22195
+ const scope = {
22196
+ ...this.#scope("key" in p ? p.key : void 0, ctx),
22197
+ authority
22198
+ };
21861
22199
  const engine = this.#current();
21862
22200
  const { cursor: _cursor, ...position } = p;
21863
22201
  const query = canonicalize({
@@ -22125,7 +22463,10 @@ var init_conv_ipc = __esmMin((() => {
22125
22463
  await this.options.current?.(p.participants.map((s) => participantPerson(s.p)));
22126
22464
  }
22127
22465
  const { cid, ...fields } = p;
22128
- if (method !== "conv.meta" && engine.pending(cid).length > 0) await engine.flush(cid);
22466
+ for (let drains = 0; method !== "conv.meta" && engine.pending(cid).length > 0; drains++) {
22467
+ if (drains === CONTROL_DRAINS) throw new MessagingError("conv_conflict", "the conversation is busy sending; try again");
22468
+ await engine.flush(cid);
22469
+ }
22129
22470
  if (method === "conv.meta") await engine.queueMeta(cid, p.meta);
22130
22471
  else engine.control(cid, {
22131
22472
  op: method.slice(5),
@@ -23118,7 +23459,11 @@ function fitPresence(sessions, baseBytes, limit) {
23118
23459
  var FEATURES;
23119
23460
  var init_directory$1 = __esmMin((() => {
23120
23461
  init_dist$3();
23121
- FEATURES = ["tasks", "conversations"];
23462
+ FEATURES = [
23463
+ "tasks",
23464
+ "conversations",
23465
+ "reach"
23466
+ ];
23122
23467
  }));
23123
23468
  // packages/daemon/dist/delegation.js
23124
23469
  function parentChain(store, key, p) {
@@ -23973,6 +24318,19 @@ var init_directory_identity = __esmMin((() => {
23973
24318
  const head = this.#head;
23974
24319
  return head !== void 0 && this.#options.now() - head.sth.ts > maxAgeMs + LIMITS.epochHeartbeatMs;
23975
24320
  }
24321
+ async freshen(persons, batch = LIMITS.dirLookupPersons) {
24322
+ const size1 = Math.max(1, Math.min(batch, LIMITS.dirLookupPersons));
24323
+ const now = this.#options.now();
24324
+ const size = this.#head?.sth.size ?? 0;
24325
+ const stale = [...new Set(persons)].filter((person) => {
24326
+ if (!isHosted(person)) return false;
24327
+ const held = this.#persons.get(person);
24328
+ return !held || now - held.fetchedAt >= LIMITS.dirFreshMs || held.size < size;
24329
+ });
24330
+ if (stale.length === 0 && !this.#headOlderThan(LIMITS.dirFreshMs)) return;
24331
+ await this.#latest();
24332
+ for (let i = 0; i < stale.length; i += size1) await this.#fetch(stale.slice(i, i + size1));
24333
+ }
23976
24334
  async profile(person) {
23977
24335
  if (!isHosted(person)) throw new MessagingError("not_found", "no such person in this realm");
23978
24336
  await this.devices(person);
@@ -25138,8 +25496,10 @@ var init_jelly$1 = __esmMin((() => {
25138
25496
  this.#deps = deps;
25139
25497
  }
25140
25498
  authorize(call) {
25141
- const sessionCall = call.params !== null && typeof call.params === "object" && "key" in call.params && typeof call.params.key === "string";
25142
- if (call.ctx.tier === "agent" || AGENT.has(call.method) && (!Object.hasOwn(CONVERSATION_METHODS, call.method) || sessionCall)) return void 0;
25499
+ const params = call.params !== null && typeof call.params === "object" ? call.params : void 0;
25500
+ const sessionCall = params !== void 0 && typeof params.key === "string";
25501
+ const tokenCall = params !== void 0 && Object.hasOwn(params, "token");
25502
+ if (call.ctx.tier === "agent" || AGENT.has(call.method) && !tokenCall && (!Object.hasOwn(CONVERSATION_METHODS, call.method) || sessionCall)) return void 0;
25143
25503
  const refuse = (error) => {
25144
25504
  this.#deps.log("warn", "jelly.grant.refused", {
25145
25505
  method: call.method,
@@ -25181,7 +25541,9 @@ var init_jelly$1 = __esmMin((() => {
25181
25541
  });
25182
25542
  return {
25183
25543
  scope: grant.scope,
25184
- expiresAt: (grant.exp + 5) * 1e3
25544
+ expiresAt: (grant.exp + 5) * 1e3,
25545
+ ...grant.dev !== void 0 ? { dev: grant.dev } : {},
25546
+ ...grant.mbr !== void 0 ? { mbr: grant.mbr } : {}
25185
25547
  };
25186
25548
  }
25187
25549
  ticket(p) {
@@ -25529,7 +25891,7 @@ function deviceLine(d) {
25529
25891
  const line = d.change === "added" ? `New device on your account: ${what}, ${vouchWords(d)}.` : d.change === "vouched" ? `${what} on your account is now vouched by Jelly ID: ${vouchWords(d)}.` : d.change === "replaced" ? `'${d.replaces?.label ?? "a device"}' was replaced by ${what}, ${vouchWords(d)}.` : `${what} was removed from your account.`;
25530
25892
  return line.length <= 120 ? line : `${line.slice(0, 119)}…`;
25531
25893
  }
25532
- var ALERT_TEXT, REPLAY, Notices;
25894
+ var ALERT_TEXT, TASK_SOURCE, REPLAY, Notices;
25533
25895
  var init_notices = __esmMin((() => {
25534
25896
  init_dist$3();
25535
25897
  ALERT_TEXT = {
@@ -25537,6 +25899,7 @@ var init_notices = __esmMin((() => {
25537
25899
  restored: "This device was restored from an older copy: it publishes no keys until it is replaced.",
25538
25900
  directory: "The key directory gave inconsistent answers about your devices; the evidence is kept."
25539
25901
  };
25902
+ TASK_SOURCE = "task:";
25540
25903
  REPLAY = 500;
25541
25904
  Notices = class {
25542
25905
  #host;
@@ -25554,6 +25917,15 @@ var init_notices = __esmMin((() => {
25554
25917
  at: row.at
25555
25918
  });
25556
25919
  }
25920
+ task(row) {
25921
+ this.#file({
25922
+ id: `m_task${row.taskId}`,
25923
+ category: "message",
25924
+ person: row.person,
25925
+ source: `${TASK_SOURCE}${row.taskId}`,
25926
+ at: row.at
25927
+ });
25928
+ }
25557
25929
  alert(kind, at) {
25558
25930
  this.#file({
25559
25931
  id: `a_${kind.replace(/_/g, "")}${at}`,
@@ -25586,6 +25958,7 @@ var init_notices = __esmMin((() => {
25586
25958
  notice: {
25587
25959
  id: row.id,
25588
25960
  category: row.category,
25961
+ ...row.source.startsWith(TASK_SOURCE) ? { key: `task:${row.source.slice(5)}` } : {},
25589
25962
  at: row.at
25590
25963
  }
25591
25964
  });
@@ -25659,6 +26032,7 @@ var init_notices = __esmMin((() => {
25659
26032
  org: own ? "same" : "none"
25660
26033
  };
25661
26034
  }
26035
+ if (row.category === "message" && row.source.startsWith(TASK_SOURCE)) notice.key = `task:${row.source.slice(5)}`;
25662
26036
  if (row.category === "alert" && row.source.startsWith("device:")) {
25663
26037
  const device = this.#host.device?.(Number(row.source.slice(7)));
25664
26038
  if (device) notice.device = device;
@@ -25671,6 +26045,67 @@ var init_notices = __esmMin((() => {
25671
26045
  }
25672
26046
  };
25673
26047
  }));
26048
+ // packages/daemon/dist/task-view.js
26049
+ function plainCapped(text, max, singleLine) {
26050
+ let budget = max;
26051
+ for (;;) {
26052
+ const out = cleanPeerText(text, {
26053
+ singleLine,
26054
+ maxChars: budget
26055
+ }).trim();
26056
+ if (out.length <= max) return out.length > 0 ? out : void 0;
26057
+ budget = Math.max(0, budget - (out.length - max));
26058
+ }
26059
+ }
26060
+ function executorWords(record) {
26061
+ const message = record.task.status.message;
26062
+ return message !== void 0 && (record.role === "executor" || message.metadata?.[URI.provenance] !== void 0);
26063
+ }
26064
+ function signInTool(record) {
26065
+ if (!executorWords(record)) return void 0;
26066
+ const value = record.task.status.message?.metadata?.[URI.signIn];
26067
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
26068
+ const tool = value.tool;
26069
+ return typeof tool === "string" ? plainCapped(tool, TASK_SIGN_IN_TOOL_CHARS, true) : void 0;
26070
+ }
26071
+ function refusalReason(record) {
26072
+ if (!executorWords(record)) return void 0;
26073
+ return plainCapped((record.task.status.message?.parts ?? []).flatMap((part) => typeof part.text === "string" ? [part.text] : []).join("\n"), TASK_REFUSAL_CHARS, false);
26074
+ }
26075
+ function withTaskView(record, me, named) {
26076
+ const view = { ...record };
26077
+ delete view.agent;
26078
+ delete view.person;
26079
+ delete view.org;
26080
+ delete view.signIn;
26081
+ delete view.refusal;
26082
+ delete view.question;
26083
+ if (record.peer.agent !== void 0) view.agent = record.peer.agent;
26084
+ if (named !== void 0) {
26085
+ view.person = {
26086
+ id: record.peer.person,
26087
+ name: named.name.slice(0, 200)
26088
+ };
26089
+ view.org = named.org;
26090
+ }
26091
+ const state = record.task.status.state;
26092
+ if (state === "TASK_STATE_AUTH_REQUIRED") {
26093
+ const mine = record.role === "executor" || me !== void 0 && record.peer.person === me;
26094
+ const tool = signInTool(record);
26095
+ view.signIn = {
26096
+ ...tool !== void 0 ? { tool } : {},
26097
+ by: mine ? "you" : "them"
26098
+ };
26099
+ }
26100
+ if (state === "TASK_STATE_REJECTED") {
26101
+ const reason = refusalReason(record);
26102
+ if (reason !== void 0) view.refusal = { reason };
26103
+ }
26104
+ return view;
26105
+ }
26106
+ var init_task_view = __esmMin((() => {
26107
+ init_dist$3();
26108
+ }));
25674
26109
  // packages/daemon/dist/realm/counterpart.js
25675
26110
  function successors(realm, recorded) {
25676
26111
  const chain = [recorded];
@@ -26667,7 +27102,117 @@ var init_wake_bridge = __esmMin((() => {
26667
27102
  };
26668
27103
  AnswerTooLarge = class extends Error {};
26669
27104
  }));
27105
+ // packages/daemon/dist/action-tokens.js
27106
+ function actionView(ctx, authority, sessionKey) {
27107
+ if (ctx.tier !== "admin" || sessionKey !== void 0 || authority?.scope === "host") return void 0;
27108
+ return {
27109
+ connection: ctx.id,
27110
+ ...authority?.dev !== void 0 ? { dev: authority.dev } : {},
27111
+ ...authority?.mbr !== void 0 ? { mbr: authority.mbr } : {}
27112
+ };
27113
+ }
27114
+ function taskCancelAction(tokens, task, view) {
27115
+ if (view === void 0 || task.role !== "requester" || task.ended) return void 0;
27116
+ return {
27117
+ name: "cancel",
27118
+ token: tokens.offer(view, "cancel", `task:${task.id}`)
27119
+ };
27120
+ }
27121
+ var ACTION_TOKEN_TTL_MS, ACTION_TOKENS_TOTAL, viewKey, offerKey, ActionTokens;
27122
+ var init_action_tokens = __esmMin((() => {
27123
+ init_dist$3();
27124
+ ACTION_TOKEN_TTL_MS = 6e5;
27125
+ ACTION_TOKENS_TOTAL = 4096;
27126
+ viewKey = (view) => JSON.stringify([
27127
+ view.connection,
27128
+ view.dev ?? null,
27129
+ view.mbr ?? null
27130
+ ]);
27131
+ offerKey = (view, name, target) => JSON.stringify([
27132
+ view,
27133
+ name,
27134
+ target
27135
+ ]);
27136
+ ActionTokens = class {
27137
+ now;
27138
+ limits;
27139
+ #held = new Map();
27140
+ #offers = new Map();
27141
+ #perView = new Map();
27142
+ constructor(now, limits = {
27143
+ perView: 256,
27144
+ total: ACTION_TOKENS_TOTAL,
27145
+ ttlMs: ACTION_TOKEN_TTL_MS
27146
+ }) {
27147
+ this.now = now;
27148
+ this.limits = limits;
27149
+ }
27150
+ get size() {
27151
+ return this.#held.size;
27152
+ }
27153
+ offer(view, name, target) {
27154
+ const now = this.now();
27155
+ const key = viewKey(view);
27156
+ const offered = this.#offers.get(offerKey(key, name, target));
27157
+ if (offered !== void 0) {
27158
+ const held = this.#held.get(offered);
27159
+ if (held !== void 0 && held.exp - now > this.limits.ttlMs / 2) return offered;
27160
+ }
27161
+ this.sweep();
27162
+ while ((this.#perView.get(key) ?? 0) >= this.limits.perView && this.#dropOldest((h) => h.view === key));
27163
+ while (this.#held.size >= this.limits.total && this.#dropOldest(() => true));
27164
+ const token = randomBytes(32).toString("base64url");
27165
+ this.#held.set(token, {
27166
+ name,
27167
+ target,
27168
+ view: key,
27169
+ connection: view.connection,
27170
+ exp: now + this.limits.ttlMs
27171
+ });
27172
+ this.#offers.set(offerKey(key, name, target), token);
27173
+ this.#perView.set(key, (this.#perView.get(key) ?? 0) + 1);
27174
+ return token;
27175
+ }
27176
+ redeem(token, want) {
27177
+ const held = this.#held.get(token);
27178
+ if (held !== void 0) this.#delete(token, held);
27179
+ this.#match(held, want);
27180
+ }
27181
+ check(token, want) {
27182
+ this.#match(this.#held.get(token), want);
27183
+ }
27184
+ disconnected(connection) {
27185
+ for (const [token, held] of [...this.#held]) if (held.connection === connection) this.#delete(token, held);
27186
+ }
27187
+ sweep() {
27188
+ const now = this.now();
27189
+ for (const [token, held] of [...this.#held]) if (held.exp <= now) this.#delete(token, held);
27190
+ }
27191
+ #match(held, want) {
27192
+ if (!(held !== void 0 && held.exp > this.now() && held.name === want.name && held.target === want.target && held.view === viewKey(want.view))) throw new MessagingError("grant_invalid", "the action token is not one this daemon offered for this action, item and view, or it was spent or expired; read the view again");
27193
+ }
27194
+ #dropOldest(which) {
27195
+ for (const [token, held] of this.#held) {
27196
+ if (!which(held)) continue;
27197
+ this.#delete(token, held);
27198
+ return true;
27199
+ }
27200
+ return false;
27201
+ }
27202
+ #delete(token, held) {
27203
+ this.#held.delete(token);
27204
+ const key = offerKey(held.view, held.name, held.target);
27205
+ if (this.#offers.get(key) === token) this.#offers.delete(key);
27206
+ const count = (this.#perView.get(held.view) ?? 1) - 1;
27207
+ if (count > 0) this.#perView.set(held.view, count);
27208
+ else this.#perView.delete(held.view);
27209
+ }
27210
+ };
27211
+ }));
26670
27212
  // packages/daemon/dist/consent/grants.js
27213
+ function isRelationKey(key) {
27214
+ return key.startsWith("relation:");
27215
+ }
26671
27216
  function levelsFrom(p, now) {
26672
27217
  const trustedUntil = p.trustedUntil ?? (p.trust ? now + LIMITS.trustDefaultMs : void 0);
26673
27218
  return levelsOf({
@@ -26718,14 +27263,42 @@ var init_grants = __esmMin((() => {
26718
27263
  const members = this.#host.participants(cid, event?.seq);
26719
27264
  return members.includes(person) && members.includes(me.person);
26720
27265
  })),
27266
+ reach: this.reach(),
26721
27267
  inbound,
26722
27268
  now: this.#host.now()
26723
27269
  });
26724
27270
  }
27271
+ hides(participant, org) {
27272
+ const slash = participant.indexOf("/");
27273
+ const person = slash === -1 ? participant : participant.slice(0, slash);
27274
+ const { me } = this.#host.joined();
27275
+ if (person === me.person) return false;
27276
+ const grant = this.personGrant(person);
27277
+ if (grant?.chat === "blocked") return true;
27278
+ if (slash !== -1 || org !== "other") return false;
27279
+ return effectivePolicy(this.hasGrant(person) ? "contact" : "stranger", grant, [], this.#host.now(), this.reach()).chat === "blocked";
27280
+ }
26725
27281
  personGrant(person) {
26726
27282
  const grant = this.#current(grantSubjectKey({ person }));
26727
27283
  return grant && levelsOf(grant);
26728
27284
  }
27285
+ relationGrant(relation) {
27286
+ const grant = this.#current(grantSubjectKey({ relation }));
27287
+ return grant && levelsOf(grant);
27288
+ }
27289
+ reach() {
27290
+ return reachChoices(this.relationGrant("outside"), this.relationGrant("org"));
27291
+ }
27292
+ relationRecord(relation) {
27293
+ const key = grantSubjectKey({ relation });
27294
+ return {
27295
+ grant: this.#store.grant(key) ?? null,
27296
+ review: this.#store.grantReviews().find(({ subject }) => subject === key) ?? null
27297
+ };
27298
+ }
27299
+ relationReview(relation) {
27300
+ return this.#store.grantReviews().find(({ subject }) => subject === grantSubjectKey({ relation }))?.levels;
27301
+ }
26729
27302
  conversationGrants(conversations) {
26730
27303
  return conversations.flatMap((conv) => {
26731
27304
  const grant = this.#current(grantSubjectKey({ conv }));
@@ -26751,8 +27324,8 @@ var init_grants = __esmMin((() => {
26751
27324
  list(p) {
26752
27325
  const only = p.subject === void 0 ? void 0 : this.#subjectKey(p.subject, false);
26753
27326
  return {
26754
- grants: this.#all().filter(({ subject }) => only === void 0 || subject === only).map(({ grant }) => this.#view(grant)).sort((a, b) => b.ts - a.ts || (a.id < b.id ? 1 : -1)),
26755
- review: this.#store.grantReviews().filter(({ subject }) => only === void 0 || subject === only).map(({ subject, levels, at }) => ({
27327
+ grants: this.#all().filter(({ subject }) => !isRelationKey(subject)).filter(({ subject }) => only === void 0 || subject === only).map(({ grant }) => this.#view(grant)).sort((a, b) => b.ts - a.ts || (a.id < b.id ? 1 : -1)),
27328
+ review: this.#store.grantReviews().filter(({ subject }) => !isRelationKey(subject)).filter(({ subject }) => only === void 0 || subject === only).map(({ subject, levels, at }) => ({
26756
27329
  subject: this.#subjectView(subject),
26757
27330
  ...levels,
26758
27331
  at
@@ -26792,6 +27365,30 @@ var init_grants = __esmMin((() => {
26792
27365
  ...levels
26793
27366
  }, opts);
26794
27367
  }
27368
+ setRelations(changes) {
27369
+ if (changes.length === 0) return;
27370
+ const { identity, realm, me } = this.#host.joined();
27371
+ const now = this.#host.now();
27372
+ const grants = changes.map(({ subject, levels }) => signConsentGrant(identity, {
27373
+ realm: realm.realm,
27374
+ person: me.person,
27375
+ subject,
27376
+ levels,
27377
+ now
27378
+ }));
27379
+ this.#host.tx(() => {
27380
+ for (const grant of grants) {
27381
+ const key = grantSubjectKey(grant.subject);
27382
+ this.#store.putGrant(key, grant);
27383
+ this.#store.deleteGrantReview(key);
27384
+ }
27385
+ this.#sync("grant", grants);
27386
+ });
27387
+ this.#host.log("grant.set", {
27388
+ subject: "relation",
27389
+ grants: grants.length
27390
+ });
27391
+ }
26795
27392
  check(p) {
26796
27393
  const { identity, realm, me } = this.#host.joined();
26797
27394
  const now = this.#host.now();
@@ -26835,7 +27432,7 @@ var init_grants = __esmMin((() => {
26835
27432
  }
26836
27433
  const key = grantSubjectKey(grant.subject);
26837
27434
  const held = this.#held(key);
26838
- if (!held && this.#store.grantCount() >= LIMITS.grantsPerPerson) {
27435
+ if (!held && !isRelationKey(key) && this.#store.grantCount() >= LIMITS.grantsPerPerson) {
26839
27436
  this.#host.log("grant.sync.skipped", {
26840
27437
  id: env.id,
26841
27438
  code: "rate_limited"
@@ -26976,10 +27573,14 @@ var init_grants = __esmMin((() => {
26976
27573
  return grantSubjectKey(target);
26977
27574
  }
26978
27575
  #subjectOf(key) {
26979
- return key.startsWith("person:") ? { person: key.slice(7) } : { conv: key.slice(5) };
27576
+ if (key.startsWith("person:")) return { person: key.slice(7) };
27577
+ if (isRelationKey(key)) return { relation: key.slice(9) };
27578
+ return { conv: key.slice(5) };
26980
27579
  }
26981
27580
  #subjectView(key) {
26982
- return this.#subjectOf(key);
27581
+ const subject = this.#subjectOf(key);
27582
+ if ("relation" in subject) throw new MessagingError("internal", "a relation grant has no grant view");
27583
+ return subject;
26983
27584
  }
26984
27585
  #view(grant) {
26985
27586
  return {
@@ -27140,7 +27741,7 @@ var init_cards = __esmMin((() => {
27140
27741
  seq,
27141
27742
  now,
27142
27743
  ...grant !== void 0 && typeof held?.id === "string" ? { grantId: held.id } : {},
27143
- can: cardCan(relation, grant, now)
27744
+ can: cardCan(relation, grant, now, this.#host.reach?.() ?? {})
27144
27745
  });
27145
27746
  for (const device of realm.known(person).devices) this.#host.route(this.#host.seal(device.machine, cardMessage({ card }, "card")));
27146
27747
  this.#host.log("card.sent", { seq });
@@ -27270,7 +27871,8 @@ var init_conversation_requests = __esmMin((() => {
27270
27871
  return this.#ids().map((id) => this.get(id)).sort((a, b) => a.at - b.at || a.id.localeCompare(b.id));
27271
27872
  }
27272
27873
  waitingOnThem() {
27273
- return this.list().filter((r) => r.direction === "outgoing" && r.state === "submitted" && r.request.descriptor.expiresAt > this.host.now()).map((r) => ({
27874
+ const now = this.host.now(), rows = this.list().filter((r) => r.direction === "outgoing");
27875
+ const submitted = rows.filter((r) => r.state === "submitted" && r.request.descriptor.expiresAt > now).map((r) => ({
27274
27876
  id: r.id,
27275
27877
  kind: "contact",
27276
27878
  to: r.person,
@@ -27278,6 +27880,16 @@ var init_conversation_requests = __esmMin((() => {
27278
27880
  at: r.at,
27279
27881
  cid: r.request.descriptor.cid
27280
27882
  }));
27883
+ const resend = rows.filter((r) => r.state === "resend" && r.at + LIMITS.ttlMs > now && !rows.some((n) => n.person === r.person && n.request && n.at >= r.at)).map((r) => ({
27884
+ id: r.id,
27885
+ kind: "contact",
27886
+ to: r.person,
27887
+ name: r.person,
27888
+ at: r.at,
27889
+ cid: dmConvId(this.host.person, r.person),
27890
+ resend: true
27891
+ }));
27892
+ return [...submitted, ...resend];
27281
27893
  }
27282
27894
  cutoverLegacy(outgoing = []) {
27283
27895
  this.host.store.tx(() => {
@@ -27359,6 +27971,7 @@ var init_conversation_requests = __esmMin((() => {
27359
27971
  if (d.realm !== this.host.realm || d.to !== this.host.person) throw new MessagingError("invalid_request", "request is for another recipient");
27360
27972
  if (d.issuedAt > this.host.now() + LIMITS.clockSkewMs || d.expiresAt <= this.host.now()) throw new MessagingError("expired", "request has expired");
27361
27973
  if (this.host.chat(d.from) === "blocked") throw new MessagingError("declined", "request declined");
27974
+ if (this.host.refuses?.(d.from)) throw new MessagingError("refused", "this person accepts no message requests from outside their org");
27362
27975
  return this.host.store.tx(() => {
27363
27976
  const old = this.get(d.id);
27364
27977
  if (old) {
@@ -27565,6 +28178,7 @@ var init_requests = __esmMin((() => {
27565
28178
  resolve: (email) => this.#resolve(email),
27566
28179
  current: (person) => h.current?.([person]) ?? Promise.resolve(),
27567
28180
  chat: (person) => h.grants.personGrant(person)?.chat,
28181
+ refuses: (person) => h.grants.decide(person, { kind: "request" }).action === "refuse",
27568
28182
  grantChat: (person, chat) => {
27569
28183
  h.grants.setPerson(person, { chat }, { card: false });
27570
28184
  },
@@ -27628,7 +28242,7 @@ var init_requests = __esmMin((() => {
27628
28242
  }, proof, body);
27629
28243
  }
27630
28244
  });
27631
- this.#lifecycle.cutoverLegacy();
28245
+ this.#lifecycle.cutoverLegacy(h.store.legacyOutgoingRequests(h.now()));
27632
28246
  return this.#lifecycle;
27633
28247
  }
27634
28248
  #mailbox() {
@@ -27714,12 +28328,16 @@ var init_requests = __esmMin((() => {
27714
28328
  const sent = parseWire(value);
27715
28329
  if (machine !== sent.device) return true;
27716
28330
  if (payload.stage === "held") this.host.store.setKv(`conv.request.held.${sent.request}`, "true");
28331
+ if (payload.stage === "delivered" || payload.stage === "read") {
28332
+ if (!this.host.store.getKv(`conv.request.outdated.${sent.request}`)) this.host.log("request.outdated", { id: sent.request });
28333
+ this.host.store.setKv(`conv.request.outdated.${sent.request}`, "true");
28334
+ }
27717
28335
  return true;
27718
28336
  }
27719
28337
  async waitingOnThem() {
27720
- return this.#current().waitingOnThem().map((row) => ({
28338
+ return this.#current().waitingOnThem().map(({ resend, ...row }) => ({
27721
28339
  ...row,
27722
- state: this.host.store.getKv(`conv.request.held.${row.id}`) === "true" ? "held" : "pending-delivery"
28340
+ state: resend ? "resend" : this.host.store.getKv(`conv.request.outdated.${row.id}`) === "true" ? "outdated" : this.host.store.getKv(`conv.request.held.${row.id}`) === "true" ? "held" : "pending-delivery"
27723
28341
  }));
27724
28342
  }
27725
28343
  get(id) {
@@ -27829,6 +28447,92 @@ var init_requests = __esmMin((() => {
27829
28447
  }
27830
28448
  };
27831
28449
  }));
28450
+ // packages/daemon/dist/consent/reach.js
28451
+ function revision(grants, choices) {
28452
+ return sha256(canonicalize({
28453
+ records: Object.fromEntries(GROUPS.map((group) => [group, grants.relationRecord(RELATION[group])])),
28454
+ choices
28455
+ })).slice(0, 32);
28456
+ }
28457
+ function reachSnapshot(grants) {
28458
+ const choices = grants.reach();
28459
+ const lapsed = reachChoices(grants.relationReview("outside"), grants.relationReview("org"));
28460
+ return {
28461
+ revision: revision(grants, choices),
28462
+ available: true,
28463
+ groups: REACH_QUESTIONS.map((question) => ({
28464
+ id: question.id,
28465
+ label: question.label,
28466
+ options: question.options.map((option) => ({ ...option })),
28467
+ value: choices[question.id] ?? null,
28468
+ ...lapsed[question.id] !== void 0 ? { lapsed: lapsed[question.id] } : {}
28469
+ }))
28470
+ };
28471
+ }
28472
+ function changesOf(grants, p) {
28473
+ if (p.revision !== revision(grants, grants.reach())) throw new MessagingError("conflict", "your reach choices changed since they were read; read them again and choose again");
28474
+ return GROUPS.flatMap((group) => p.changes[group] === void 0 ? [] : [[group, p.changes[group]]]);
28475
+ }
28476
+ function reachSet(grants, p) {
28477
+ const changes = changesOf(grants, p);
28478
+ grants.setRelations(changes.map(([group, value]) => {
28479
+ const { levels } = reachGrant(group, value);
28480
+ return {
28481
+ subject: { relation: RELATION[group] },
28482
+ levels
28483
+ };
28484
+ }));
28485
+ return reachSnapshot(grants);
28486
+ }
28487
+ function reachPreview(grants, p) {
28488
+ const changes = changesOf(grants, p);
28489
+ if (changes.length === 0) return "Nothing changes.";
28490
+ return changes.map(([group, value]) => SENTENCES[group][value]).join(" ");
28491
+ }
28492
+ var RELATION, GROUPS, SENTENCES;
28493
+ var init_reach = __esmMin((() => {
28494
+ init_dist$3();
28495
+ RELATION = {
28496
+ message: "outside",
28497
+ ask: "org"
28498
+ };
28499
+ GROUPS = ["message", "ask"];
28500
+ SENTENCES = {
28501
+ message: {
28502
+ open: "Anyone outside your org may message you.",
28503
+ ask: "People outside your org send you a message request first.",
28504
+ never: "No one outside your org may message you or send you a request, except people you have let message you."
28505
+ },
28506
+ ask: {
28507
+ open: "Your teammates' agents may ask yours without asking you; their requests run guarded.",
28508
+ ask: "You answer each request from a teammate's agent to yours first.",
28509
+ never: "Your teammates' agents may not ask yours."
28510
+ }
28511
+ };
28512
+ }));
28513
+ // packages/daemon/dist/conv-caps.js
28514
+ async function ensureCurrent(link, own, persons, handle) {
28515
+ const others = [...new Set(persons)].filter((p) => p !== own);
28516
+ if (!others.length) return;
28517
+ let outdated;
28518
+ try {
28519
+ outdated = (await link?.convCaps?.(others))?.outdated ?? [];
28520
+ } catch {
28521
+ return;
28522
+ }
28523
+ if (!outdated.length) return;
28524
+ const handles = await Promise.all(outdated.map(async (p) => {
28525
+ try {
28526
+ return await handle(p);
28527
+ } catch {
28528
+ return p;
28529
+ }
28530
+ }));
28531
+ throw new MessagingError("peer_outdated", `${handles.join(", ")} ${handles.length === 1 ? "needs" : "need"} to update Messaging`);
28532
+ }
28533
+ var init_conv_caps = __esmMin((() => {
28534
+ init_dist$3();
28535
+ }));
27832
28536
  // packages/daemon/dist/consent/sentence.js
27833
28537
  function grantSentence(p, me, now) {
27834
28538
  const levels = levelsFrom(p, now);
@@ -27857,6 +28561,38 @@ function grantSentence(p, me, now) {
27857
28561
  var init_sentence = __esmMin((() => {
27858
28562
  init_grants();
27859
28563
  }));
28564
+ // packages/daemon/dist/wake-dms.js
28565
+ function teammateDms(conversations, self) {
28566
+ const member = (state, person) => state.participants.some((p) => p.person === person && p.state === "member");
28567
+ const seated = (state, person) => state.participants.some((p) => p.p === person && p.state === "member");
28568
+ const channels = conversations.flatMap((c) => c.state?.kind === "channel" && seated(c.state, self) ? [c.state] : []);
28569
+ const shares = new Map();
28570
+ const shared = (person) => {
28571
+ let found = shares.get(person);
28572
+ if (found === void 0) shares.set(person, found = channels.some((state) => seated(state, person)));
28573
+ return found;
28574
+ };
28575
+ const found = [];
28576
+ for (const { cid, state } of conversations) {
28577
+ if (state?.kind !== "dm" || !member(state, self)) continue;
28578
+ const others = [...new Set(state.participants.filter((p) => p.person !== self).map((p) => p.person))];
28579
+ if (others.length !== 1) continue;
28580
+ const person = others[0];
28581
+ if (!member(state, person) || !shared(person)) continue;
28582
+ found.push({
28583
+ cid,
28584
+ person
28585
+ });
28586
+ }
28587
+ return found;
28588
+ }
28589
+ function wakeArmable(grants, person, org) {
28590
+ const chat = grants.personGrant(person)?.chat;
28591
+ if (chat === "blocked" || chat === "muted") return false;
28592
+ if (grants.hides(person, org)) return false;
28593
+ return true;
28594
+ }
28595
+ var init_wake_dms = __esmMin((() => {}));
27860
28596
  // packages/daemon/dist/daemon.js
27861
28597
  async function startDaemon$1(options) {
27862
28598
  const daemon = await Daemon.start(options);
@@ -27868,6 +28604,7 @@ async function startDaemon$1(options) {
27868
28604
  status: () => daemon.status(),
27869
28605
  conversations: () => daemon.conversations(),
27870
28606
  conversationsReady: () => daemon.conversationsReady(),
28607
+ actionTokens: () => daemon.actionTokens(),
27871
28608
  sweep: () => daemon.sweep(),
27872
28609
  close: () => daemon.close()
27873
28610
  };
@@ -27875,6 +28612,16 @@ async function startDaemon$1(options) {
27875
28612
  function jsonBytes(value) {
27876
28613
  return Buffer.byteLength(JSON.stringify(value) ?? "");
27877
28614
  }
28615
+ function signInMessage(message, tool) {
28616
+ return {
28617
+ ...message,
28618
+ metadata: {
28619
+ ...message.metadata,
28620
+ [URI.signIn]: { tool }
28621
+ },
28622
+ extensions: [...message.extensions ?? [], URI.signIn]
28623
+ };
28624
+ }
27878
28625
  function boundedRecord(record, limit) {
27879
28626
  if (jsonBytes(record) <= limit) return structuredClone(record);
27880
28627
  const { history: _history, artifacts: _artifacts, ...task } = record.task;
@@ -27982,7 +28729,7 @@ function signInToken(token) {
27982
28729
  return false;
27983
28730
  }
27984
28731
  }
27985
- var DAY, KEYCHAIN_LEFTOVERS_KEY, DEFAULT_TUNING, KEPT_MS, INPUT_TIMEOUT_MS, TASK_BYTES, PREDECESSOR_KEY, REPLACE_LABEL_KEY, REPLACED_KEY, ERASURES_PER_DAY, DEVICE_NOTICES_KEY, TRUST_NOTICE_MS, DEVICES_CHANGED_MS, MACHINE_KEY, DEFAULT_INVITE_TTL_MS, Daemon, LIST_BYTES;
28732
+ var DAY, KEYCHAIN_LEFTOVERS_KEY, DEFAULT_TUNING, KEPT_MS, INPUT_TIMEOUT_MS, TASK_BYTES, PREDECESSOR_KEY, REPLACE_LABEL_KEY, REPLACED_KEY, ERASURES_PER_DAY, DEVICE_NOTICES_KEY, TRUST_NOTICE_MS, DEVICES_CHANGED_MS, MACHINE_KEY, DEFAULT_INVITE_TTL_MS, Daemon, LIST_BYTES, NAMED_PERSONS;
27986
28733
  var init_daemon = __esmMin((() => {
27987
28734
  init_dist$3();
27988
28735
  init_dist$2();
@@ -28012,6 +28759,7 @@ var init_daemon = __esmMin((() => {
28012
28759
  init_answer_preview();
28013
28760
  init_snapshot();
28014
28761
  init_notices();
28762
+ init_task_view();
28015
28763
  init_counterpart();
28016
28764
  init_resolve();
28017
28765
  init_secrets();
@@ -28021,11 +28769,15 @@ var init_daemon = __esmMin((() => {
28021
28769
  init_tasks();
28022
28770
  init_version();
28023
28771
  init_wake_bridge();
28772
+ init_action_tokens();
28024
28773
  init_grants();
28025
28774
  init_holds();
28026
28775
  init_cards();
28027
28776
  init_requests();
28777
+ init_reach();
28778
+ init_conv_caps();
28028
28779
  init_sentence();
28780
+ init_wake_dms();
28029
28781
  DAY = 864e5;
28030
28782
  KEYCHAIN_LEFTOVERS_KEY = "keychain.stale";
28031
28783
  DEFAULT_TUNING = {
@@ -28108,11 +28860,14 @@ var init_daemon = __esmMin((() => {
28108
28860
  #cards;
28109
28861
  #requests;
28110
28862
  #notices;
28863
+ #named = new Map();
28864
+ #questions = new Map();
28111
28865
  #jellyHost;
28112
28866
  #attached = false;
28113
28867
  #wakeArmed = false;
28114
28868
  #snapshots;
28115
28869
  #jellyAuth;
28870
+ #actions;
28116
28871
  #state = "starting";
28117
28872
  #sync;
28118
28873
  #pending = [];
@@ -28151,6 +28906,7 @@ var init_daemon = __esmMin((() => {
28151
28906
  this.#jellyHttp = options.jellyHttp;
28152
28907
  this.#loginWait = options.loginWait;
28153
28908
  this.#jellyHost = options.jellyHost;
28909
+ this.#actions = new ActionTokens(() => this.#now());
28154
28910
  this.#config = readConfig(paths.config);
28155
28911
  this.#configStamp = configStamp(paths.config);
28156
28912
  this.closed = new Promise((resolve) => this.#settleClosed = resolve);
@@ -28177,9 +28933,10 @@ var init_daemon = __esmMin((() => {
28177
28933
  put: (key, value) => store.setKv(key, value),
28178
28934
  fresh: (people) => this.#withFresh(people, () => {}),
28179
28935
  refuseSecrets: (value) => this.#refuseSecrets(value, "conv.send", "conversation line"),
28180
- blocked: (person) => this.#grants.personGrant(person)?.chat === "blocked",
28936
+ blocked: (participant, org) => this.#grants.hides(participant, org),
28181
28937
  current: (persons) => this.#ensureCurrent(persons),
28182
- outgoingRequests: () => this.#requests.waitingOnThem()
28938
+ outgoingRequests: () => this.#requests.waitingOnThem(),
28939
+ task: (id, role, ctx, authority) => this.#personTaskEntry(id, role, ctx, authority)
28183
28940
  });
28184
28941
  this.#conversationInbox = new ConversationInbox({
28185
28942
  engine: () => this.#conversationEngine(),
@@ -28283,6 +29040,7 @@ var init_daemon = __esmMin((() => {
28283
29040
  joined: () => this.#joined(),
28284
29041
  relation: (person) => this.#grants.relation(person),
28285
29042
  personGrant: (person) => this.#grants.personGrant(person),
29043
+ reach: () => this.#grants.reach(),
28286
29044
  prefetch: (person) => this.#grants.prefetch(person),
28287
29045
  fresh: async (persons, fn) => {
28288
29046
  await this.#withFresh(persons, () => this.#tx(fn), { first: false });
@@ -28656,10 +29414,10 @@ var init_daemon = __esmMin((() => {
28656
29414
  } : p);
28657
29415
  },
28658
29416
  "device.list": () => {
28659
- return { devices: (this.#directory?.list() ?? this.#rosterDevices()).devices.map((d) => d.self && this.#away() ? {
29417
+ return { devices: (this.#directory?.list() ?? this.#rosterDevices()).devices.map((d) => this.#withFeatures(d.self && this.#away() ? {
28660
29418
  ...d,
28661
29419
  away: true
28662
- } : d) };
29420
+ } : d)) };
28663
29421
  },
28664
29422
  "device.remove": (p) => this.#homeDirectory("device.remove").remove(p),
28665
29423
  "device.link": (p) => this.#homeDirectory("device.link").link(p),
@@ -28719,10 +29477,13 @@ var init_daemon = __esmMin((() => {
28719
29477
  ...this.#bound(p, ctx),
28720
29478
  kind: "message"
28721
29479
  }),
28722
- "task.list": (p, ctx) => this.#listTasks(this.#bound(p, ctx)),
28723
- "task.get": (p, ctx) => this.#getTask(this.#bound(p, ctx)),
28724
- "task.update": (p, ctx) => this.#updateTask(this.#bound(p, ctx)),
28725
- "task.cancel": (p, ctx) => this.#cancelTask(this.#bound(p, ctx)),
29480
+ "task.list": (p, ctx) => this.#listTasks(this.#bound(p, ctx), ctx.tier === "admin"),
29481
+ "task.get": (p, ctx) => this.#getTask(this.#bound(p, ctx), ctx.tier === "admin"),
29482
+ "task.update": (p, ctx) => this.#updateTask(this.#bound(p, ctx), ctx.tier === "admin"),
29483
+ "task.cancel": (p, ctx, authority) => p.token === void 0 ? this.#cancelTask(this.#bound({
29484
+ ...p,
29485
+ key: p.key
29486
+ }, ctx), ctx.tier === "admin") : this.#cancelByToken(p, p.token, this.#tokenView("task.cancel", ctx, authority)),
28726
29487
  "events.subscribe": (p, ctx) => {
28727
29488
  for (const key of p.keys) this.#bound({ key }, ctx);
28728
29489
  return { ok: true };
@@ -28751,13 +29512,28 @@ var init_daemon = __esmMin((() => {
28751
29512
  this.#grants.check(p);
28752
29513
  return { sentence: grantSentence(p, this.#joined().me.person, this.#now()) };
28753
29514
  },
28754
- "request.list": async () => ({ requests: [
28755
- ...this.#holds.list().requests,
28756
- ...await this.#requests.list(),
28757
- ...this.#conversationInvitations.list()
28758
- ] }),
28759
- "request.answer": (p) => this.#conversationInvitations.holds(p.id) ? this.#conversationInvitations.answer(p.id, p.answer, p.all) : this.#requests.holds(p.id) ? this.#requests.answer(p) : this.#holds.answer(p),
28760
- "request.answer.preview": (p) => this.#answerPreview(p),
29515
+ "reach.get": () => reachSnapshot(this.#grants),
29516
+ "reach.set": (p) => reachSet(this.#grants, p),
29517
+ "reach.set.preview": (p) => ({ sentence: reachPreview(this.#grants, p) }),
29518
+ "request.list": async (_p, ctx, authority) => {
29519
+ const requests = [
29520
+ ...this.#holds.list().requests,
29521
+ ...await this.#requests.list(),
29522
+ ...this.#conversationInvitations.list()
29523
+ ];
29524
+ const view = actionView(ctx, authority, void 0);
29525
+ return { requests: view === void 0 ? requests : requests.map((request) => this.#withActions(request, view)) };
29526
+ },
29527
+ "request.answer": (p, ctx, authority) => {
29528
+ this.#requestToken("request.answer", p, ctx, authority, "redeem");
29529
+ const { token: _token, ...answer } = p;
29530
+ return this.#conversationInvitations.holds(answer.id) ? this.#conversationInvitations.answer(answer.id, answer.answer, answer.all) : this.#requests.holds(answer.id) ? this.#requests.answer(answer) : this.#holds.answer(answer);
29531
+ },
29532
+ "request.answer.preview": (p, ctx, authority) => {
29533
+ this.#requestToken("request.answer.preview", p, ctx, authority, "check");
29534
+ const { token: _token, ...answer } = p;
29535
+ return this.#answerPreview(answer);
29536
+ },
28761
29537
  "contact.request": (p) => this.#requests.request(p),
28762
29538
  "contact.request.preview": (p) => this.#requests.preview(p),
28763
29539
  "card.get": async (p) => {
@@ -28774,6 +29550,9 @@ var init_daemon = __esmMin((() => {
28774
29550
  "notice.subscribe": (p, ctx, authority) => this.#notices.subscribe(ctx, p, authority?.scope === "host" ? authority.expiresAt : void 0),
28775
29551
  "notice.list": (p) => this.#notices.list(p),
28776
29552
  "notice.ack": (p) => this.#notices.ack(p),
29553
+ "task.question": (p, ctx) => this.#setQuestion(this.#bound(p, ctx)),
29554
+ "tasks.list": (p, ctx, authority) => this.#personTasks(p, actionView(ctx, authority, void 0)),
29555
+ "tasks.get": (p, ctx, authority) => this.#personTaskGet(p, actionView(ctx, authority, void 0)),
28777
29556
  "device.remove.preview": (p) => this.#removePreview(p),
28778
29557
  ...this.#conversationIpc.handlers()
28779
29558
  };
@@ -28840,7 +29619,7 @@ var init_daemon = __esmMin((() => {
28840
29619
  else this.#publish(event);
28841
29620
  }
28842
29621
  #emitTask(row, origin, update) {
28843
- const record = this.#withFollowUps(boundedRecord(row.record, TASK_BYTES));
29622
+ const record = this.#taskView(this.#withFollowUps(boundedRecord(row.record, TASK_BYTES)));
28844
29623
  this.#emit({
28845
29624
  type: "task",
28846
29625
  key: row.record.key,
@@ -28848,6 +29627,10 @@ var init_daemon = __esmMin((() => {
28848
29627
  origin,
28849
29628
  ...update ? { update: structuredClone(update) } : {}
28850
29629
  });
29630
+ this.#conversationIpc.taskChanged({
29631
+ id: row.record.task.id,
29632
+ role: row.record.role
29633
+ });
28851
29634
  }
28852
29635
  #overBudget(before, after) {
28853
29636
  const size = jsonBytes(after);
@@ -29049,6 +29832,7 @@ var init_daemon = __esmMin((() => {
29049
29832
  }
29050
29833
  #disconnected(ctx) {
29051
29834
  if (this.#closed) return;
29835
+ this.#actions.disconnected(ctx.id);
29052
29836
  this.#notices.disconnected(ctx.id);
29053
29837
  let changed = false;
29054
29838
  for (const [key, lapsed] of [...this.#lapsed]) if (lapsed.delete(ctx.id) && lapsed.size === 0) this.#lapsed.delete(key);
@@ -30042,6 +30826,11 @@ var init_daemon = __esmMin((() => {
30042
30826
  };
30043
30827
  this.#store.putTask(row);
30044
30828
  this.#emitTask(row, "peer");
30829
+ if (sender.person !== this.#joined().me.person) this.#notices.task({
30830
+ taskId,
30831
+ person: sender.person,
30832
+ at: now
30833
+ });
30045
30834
  } else if (executor) {
30046
30835
  executor.record.task.history = [...executor.record.task.history ?? [], message];
30047
30836
  executor.record.updatedAt = now;
@@ -30129,6 +30918,7 @@ var init_daemon = __esmMin((() => {
30129
30918
  state: "TASK_STATE_CANCELED",
30130
30919
  timestamp: iso(now)
30131
30920
  };
30921
+ this.#questions.delete(row.record.task.id);
30132
30922
  row.record.task.status = status;
30133
30923
  row.record.updatedAt = now;
30134
30924
  this.#store.putTask(row);
@@ -30198,10 +30988,10 @@ var init_daemon = __esmMin((() => {
30198
30988
  if (other && other.record.key === key) throw new MessagingError("invalid_state", role === "executor" ? "only the executor session may update this task" : "only the requester session may cancel this task");
30199
30989
  throw new MessagingError("not_found", "no such task for this session");
30200
30990
  }
30201
- #getTask(p) {
30991
+ async #getTask(p, owner) {
30202
30992
  for (const role of ["requester", "executor"]) {
30203
30993
  const row = this.#store.task(p.taskId, role);
30204
- if (row && row.record.key === p.key) return this.#withFollowUps(row.record);
30994
+ if (row && row.record.key === p.key) return this.#withQuestion(await this.#viewNamed(this.#withFollowUps(row.record)), owner);
30205
30995
  }
30206
30996
  throw new MessagingError("not_found", "no such task for this session");
30207
30997
  }
@@ -30221,7 +31011,7 @@ var init_daemon = __esmMin((() => {
30221
31011
  followUps
30222
31012
  };
30223
31013
  }
30224
- #listTasks(p) {
31014
+ async #listTasks(p, owner) {
30225
31015
  const limit = p.limit ?? 50;
30226
31016
  const { rows, total, more } = this.#store.listTasks({
30227
31017
  key: p.key,
@@ -30244,8 +31034,9 @@ var init_daemon = __esmMin((() => {
30244
31034
  }
30245
31035
  const last = tasks.at(-1);
30246
31036
  const truncated = tasks.length < rows.length;
31037
+ await Promise.all([...new Set(tasks.map((t) => t.peer.person))].map((person) => this.#nameOf(person)));
30247
31038
  return {
30248
- tasks,
31039
+ tasks: tasks.map((t) => this.#withQuestion(this.#taskView(t), owner)),
30249
31040
  total,
30250
31041
  ...(more || truncated) && last ? { nextCursor: encodeCursor({
30251
31042
  updatedAt: last.updatedAt,
@@ -30254,7 +31045,7 @@ var init_daemon = __esmMin((() => {
30254
31045
  }) } : {}
30255
31046
  };
30256
31047
  }
30257
- async #updateTask(p) {
31048
+ async #updateTask(p, owner) {
30258
31049
  this.#joined();
30259
31050
  const row = this.#ownTask(p.key, p.taskId, "executor");
30260
31051
  this.#refuseSecrets(p, "task.update", "task update");
@@ -30266,7 +31057,7 @@ var init_daemon = __esmMin((() => {
30266
31057
  const now = this.#now();
30267
31058
  const status = statusChange ? {
30268
31059
  state: p.state,
30269
- ...p.message !== void 0 ? { message: agentMessage(p.message.parts, task.id, task.contextId, now) } : {},
31060
+ ...p.signIn !== void 0 ? { message: signInMessage(agentMessage(p.message?.parts ?? [{ text: "Sign-in required." }], task.id, task.contextId, now), p.signIn.tool) } : p.message !== void 0 ? { message: agentMessage(p.message.parts, task.id, task.contextId, now) } : {},
30270
31061
  timestamp: iso(now)
30271
31062
  } : void 0;
30272
31063
  const after = structuredClone(row.record);
@@ -30305,6 +31096,7 @@ var init_daemon = __esmMin((() => {
30305
31096
  contextId,
30306
31097
  status
30307
31098
  }, { replyTo: request });
31099
+ this.#questions.delete(task.id);
30308
31100
  task.status = status;
30309
31101
  row.record.updatedAt = now;
30310
31102
  this.#emitTask(row, "local", { status });
@@ -30314,12 +31106,94 @@ var init_daemon = __esmMin((() => {
30314
31106
  this.#store.putTask(row);
30315
31107
  });
30316
31108
  await this.#settle(ids);
30317
- return boundedRecord(this.#store.task(p.taskId, "executor")?.record ?? row.record, TASK_BYTES);
31109
+ return this.#withQuestion(await this.#viewNamed(boundedRecord(this.#store.task(p.taskId, "executor")?.record ?? row.record, TASK_BYTES)), owner);
31110
+ }
31111
+ async #cancelTask(p, owner) {
31112
+ this.#joined();
31113
+ return this.#cancelRow(this.#ownTask(p.key, p.taskId, "requester"), p, owner);
31114
+ }
31115
+ #tokenView(method, ctx, authority) {
31116
+ const view = actionView(ctx, authority, void 0);
31117
+ if (view === void 0) {
31118
+ this.#log("warn", "action.token.refused", {
31119
+ method,
31120
+ reason: "not the human tier"
31121
+ });
31122
+ throw new MessagingError("forbidden", `${method} takes an action token only on the human tier`);
31123
+ }
31124
+ return view;
31125
+ }
31126
+ #requestToken(method, p, ctx, authority, use) {
31127
+ if (p.token === void 0) {
31128
+ if (this.#jellyHost === void 0) return;
31129
+ this.#log("warn", "action.token.refused", {
31130
+ method,
31131
+ reason: "missing"
31132
+ });
31133
+ throw new MessagingError("grant_required", `${method} under a Jelly host needs the request's action token from request.list`);
31134
+ }
31135
+ this.#useToken(method, p.token, {
31136
+ name: p.answer,
31137
+ target: `request:${p.id}`,
31138
+ view: this.#tokenView(method, ctx, authority)
31139
+ }, use);
31140
+ }
31141
+ #useToken(method, token, want, use) {
31142
+ try {
31143
+ if (use === "redeem") this.#actions.redeem(token, want);
31144
+ else this.#actions.check(token, want);
31145
+ } catch (error) {
31146
+ this.#log("warn", "action.token.refused", {
31147
+ method,
31148
+ reason: "no matching live token"
31149
+ });
31150
+ throw error;
31151
+ }
31152
+ }
31153
+ actionTokens() {
31154
+ return this.#actions.size;
31155
+ }
31156
+ #withActions(request, view) {
31157
+ if (request.kind === "contact" && request.state !== "pending") return request;
31158
+ const names = request.kind === "suggestion" ? ["decline"] : request.kind === "contact" ? [
31159
+ "accept",
31160
+ "decline",
31161
+ "block"
31162
+ ] : request.kind === "agent-invitation" ? ["accept", "decline"] : [
31163
+ "once",
31164
+ "always",
31165
+ "decline",
31166
+ "block"
31167
+ ];
31168
+ return {
31169
+ ...request,
31170
+ actions: names.map((name) => ({
31171
+ name,
31172
+ token: this.#actions.offer(view, name, `request:${request.id}`)
31173
+ }))
31174
+ };
30318
31175
  }
30319
- async #cancelTask(p) {
31176
+ taskCancel(row, role, view) {
31177
+ return taskCancelAction(this.#actions, {
31178
+ id: row.record.task.id,
31179
+ role,
31180
+ ended: isTerminal(row.record.task.status.state)
31181
+ }, view);
31182
+ }
31183
+ async #cancelByToken(p, token, view) {
30320
31184
  this.#joined();
30321
- const row = this.#ownTask(p.key, p.taskId, "requester");
30322
- this.#refuseSecrets(p, "task.cancel", "task cancel");
31185
+ this.#useToken("task.cancel", token, {
31186
+ name: "cancel",
31187
+ target: `task:${p.taskId}`,
31188
+ view
31189
+ }, "redeem");
31190
+ const row = this.#store.task(p.taskId, "requester");
31191
+ if (row === void 0) throw new MessagingError("not_found", "no task this person's agent asked has that id");
31192
+ return this.#cancelRow(row, p, true);
31193
+ }
31194
+ async #cancelRow(row, p, owner) {
31195
+ const { token: _token, ...scanned } = p;
31196
+ this.#refuseSecrets(scanned, "task.cancel", "task cancel");
30323
31197
  if (isTerminal(row.record.task.status.state)) throw new MessagingError("invalid_state", "the task is in a terminal state");
30324
31198
  const env = this.#tx(() => {
30325
31199
  const env = this.#seal("task.cancel", row.record.peer.device, this.#sessionName(row), {
@@ -30330,10 +31204,123 @@ var init_daemon = __esmMin((() => {
30330
31204
  return env;
30331
31205
  });
30332
31206
  await this.#settle([env.id]);
30333
- return boundedRecord(this.#store.task(p.taskId, "requester")?.record ?? row.record, TASK_BYTES);
31207
+ return this.#withQuestion(await this.#viewNamed(boundedRecord(this.#store.task(p.taskId, "requester")?.record ?? row.record, TASK_BYTES)), owner);
31208
+ }
31209
+ async #nameOf(person) {
31210
+ const realm = this.#realm ?? this.#directory?.realm;
31211
+ if (!realm) return this.#named.get(person);
31212
+ let named;
31213
+ try {
31214
+ const profile = await realm.profile(person);
31215
+ named = {
31216
+ name: profile.name,
31217
+ org: profile.org
31218
+ };
31219
+ } catch (error) {
31220
+ if (!(error instanceof MessagingError) || error.code !== "not_found") return this.#named.get(person);
31221
+ named = {
31222
+ name: person,
31223
+ org: "none"
31224
+ };
31225
+ }
31226
+ if (this.#named.size >= NAMED_PERSONS && !this.#named.has(person)) this.#named.clear();
31227
+ this.#named.set(person, named);
31228
+ return named;
31229
+ }
31230
+ #taskView(record) {
31231
+ const named = this.#named.get(record.peer.person);
31232
+ if (named === void 0) this.#nameOf(record.peer.person).catch(() => void 0);
31233
+ return withTaskView(record, this.#me(), named);
31234
+ }
31235
+ #setQuestion(p) {
31236
+ const row = this.#ownTask(p.key, p.taskId, "executor");
31237
+ if (p.question === null) {
31238
+ if (this.#questions.delete(p.taskId)) this.#conversationIpc.taskChanged({
31239
+ id: p.taskId,
31240
+ role: "executor"
31241
+ });
31242
+ return { ok: true };
31243
+ }
31244
+ if (!isInterrupted(row.record.task.status.state)) throw new MessagingError("invalid_state", "only a task in auth-required or input-required waits on a question");
31245
+ const host = this.#store.sessionHost(p.key);
31246
+ if (host !== void 0 && host.sessionID !== p.question.sessionId) throw new MessagingError("forbidden", "a question names the session's own host session");
31247
+ this.#questions.set(p.taskId, {
31248
+ sessionId: p.question.sessionId,
31249
+ approvalId: p.question.approvalId
31250
+ });
31251
+ this.#conversationIpc.taskChanged({
31252
+ id: p.taskId,
31253
+ role: "executor"
31254
+ });
31255
+ return { ok: true };
31256
+ }
31257
+ async #personTask(row, view) {
31258
+ const { key: _key, ...rest } = this.#withQuestion(await this.#viewNamed(this.#withFollowUps(boundedRecord(row.record, TASK_BYTES))), true);
31259
+ const cancel = this.taskCancel(row, row.record.role, view);
31260
+ return {
31261
+ ...rest,
31262
+ session: this.#sessionName(row),
31263
+ ...cancel ? { cancel } : {}
31264
+ };
31265
+ }
31266
+ async #personTasks(p, view) {
31267
+ const limit = p.limit ?? 50;
31268
+ const key = p.session === void 0 ? void 0 : this.#store.sessionByName(p.session)?.key;
31269
+ if (p.session !== void 0 && key === void 0) return {
31270
+ tasks: [],
31271
+ total: 0
31272
+ };
31273
+ const { rows, total, more } = this.#store.listTasks({
31274
+ ...key !== void 0 ? { key } : {},
31275
+ role: p.role,
31276
+ state: p.state,
31277
+ after: p.cursor === void 0 ? void 0 : decodeCursor(p.cursor),
31278
+ limit
31279
+ });
31280
+ const tasks = [];
31281
+ let bytes = 0;
31282
+ for (const row of rows) {
31283
+ const task = await this.#personTask(row, view);
31284
+ const size = jsonBytes(task);
31285
+ if (tasks.length > 0 && bytes + size > LIST_BYTES) break;
31286
+ tasks.push(task);
31287
+ bytes += size;
31288
+ }
31289
+ const last = tasks.at(-1);
31290
+ return {
31291
+ tasks,
31292
+ total,
31293
+ ...(more || tasks.length < rows.length) && last ? { nextCursor: encodeCursor({
31294
+ updatedAt: last.updatedAt,
31295
+ taskId: last.task.id,
31296
+ role: last.role
31297
+ }) } : {}
31298
+ };
31299
+ }
31300
+ async #personTaskGet(p, view) {
31301
+ const rows = (p.role !== void 0 ? [p.role] : ["requester", "executor"]).flatMap((role) => this.#store.task(p.taskId, role) ?? []);
31302
+ if (rows.length === 0) throw new MessagingError("not_found", "no task of this person's agents has that id");
31303
+ if (rows.length > 1) throw new MessagingError("invalid_request", "this device holds both sides of that task; supply role");
31304
+ return await this.#personTask(rows[0], view);
31305
+ }
31306
+ async #personTaskEntry(id, role, ctx, authority) {
31307
+ const row = this.#store.task(id, role);
31308
+ return row ? await this.#personTask(row, actionView(ctx, authority, void 0)) : void 0;
31309
+ }
31310
+ #withQuestion(view, owner) {
31311
+ const question = this.#questions.get(view.task.id);
31312
+ if (!owner || question === void 0 || view.role !== "executor" || !isInterrupted(view.task.status.state)) return view;
31313
+ return {
31314
+ ...view,
31315
+ question: { ...question }
31316
+ };
31317
+ }
31318
+ async #viewNamed(record) {
31319
+ return withTaskView(record, this.#me(), await this.#nameOf(record.peer.person).catch(() => void 0));
30334
31320
  }
30335
31321
  sweep() {
30336
31322
  if (this.#closed) return;
31323
+ this.#actions.sweep();
30337
31324
  this.#directory?.sweep();
30338
31325
  const now = this.#now();
30339
31326
  this.#expireTickets(now);
@@ -30383,6 +31370,7 @@ var init_daemon = __esmMin((() => {
30383
31370
  message: agentMessage([{ text: INPUT_TIMEOUT_TEXT }], row.record.task.id, row.record.task.contextId, now),
30384
31371
  timestamp: iso(now)
30385
31372
  };
31373
+ this.#questions.delete(row.record.task.id);
30386
31374
  row.record.task.status = status;
30387
31375
  row.record.updatedAt = now;
30388
31376
  this.#store.putTask(row);
@@ -30390,24 +31378,7 @@ var init_daemon = __esmMin((() => {
30390
31378
  this.#sendStatus(row);
30391
31379
  }
30392
31380
  async #ensureCurrent(persons) {
30393
- const own = this.#joined().me.person;
30394
- const others = [...new Set(persons)].filter((p) => p !== own);
30395
- if (!others.length) return;
30396
- let outdated;
30397
- try {
30398
- outdated = (await this.#transport?.conversations?.convCaps?.(others))?.outdated ?? [];
30399
- } catch {
30400
- return;
30401
- }
30402
- if (!outdated.length) return;
30403
- const handles = await Promise.all(outdated.map(async (p) => {
30404
- try {
30405
- return (await this.#joined().realm.profile(p)).handle;
30406
- } catch {
30407
- return p;
30408
- }
30409
- }));
30410
- throw new MessagingError("peer_outdated", `${handles.join(", ")} ${handles.length === 1 ? "needs" : "need"} to update Messaging`);
31381
+ await ensureCurrent(this.#transport?.conversations, this.#joined().me.person, persons, async (p) => (await this.#joined().realm.profile(p)).handle);
30411
31382
  }
30412
31383
  conversations() {
30413
31384
  return this.#conversationEngine();
@@ -30767,6 +31738,15 @@ var init_daemon = __esmMin((() => {
30767
31738
  self: d.machine === identity.machine
30768
31739
  })) };
30769
31740
  }
31741
+ #withFeatures(d) {
31742
+ const raw = d.self ? [...FEATURES] : this.#store.directoryRow(d.device)?.presence.features;
31743
+ if (raw === void 0) return d;
31744
+ const features = [...new Set(raw.filter((f) => PresenceFeature.safeParse(f).success))].slice(0, 32);
31745
+ return {
31746
+ ...d,
31747
+ features
31748
+ };
31749
+ }
30770
31750
  #away() {
30771
31751
  return this.#jellyHost !== void 0 && !this.#attached;
30772
31752
  }
@@ -30798,23 +31778,49 @@ var init_daemon = __esmMin((() => {
30798
31778
  if (issuer === void 0 || new URL(p.url).origin !== new URL(issuer).origin) throw new MessagingError("invalid_request", "the wake URL is not on the realm's Jelly ID");
30799
31779
  const agents = [...new Set(p.agents)];
30800
31780
  const awaiting = this.#store.awaitedRequests(agents, TERMINAL_STATES, 500);
31781
+ const dms = await this.#wakeDms();
30801
31782
  const sent = await home.wake({
30802
31783
  url: p.url,
30803
31784
  ticket: p.ticket,
30804
31785
  agents,
30805
- awaiting
31786
+ awaiting,
31787
+ dms
30806
31788
  });
30807
31789
  this.#wakeArmed = true;
30808
31790
  this.#log("info", "wake.arm", {
30809
31791
  relay: sent === "sent" ? "armed" : "pending",
30810
31792
  agents: agents.length,
30811
- awaiting: awaiting.length
31793
+ awaiting: awaiting.length,
31794
+ dms: dms.length
30812
31795
  });
30813
31796
  return {
30814
31797
  relay: sent === "sent" ? "armed" : "pending",
30815
- awaiting: awaiting.length
31798
+ awaiting: awaiting.length,
31799
+ dms: dms.length
30816
31800
  };
30817
31801
  }
31802
+ async #wakeDms() {
31803
+ const engine = this.#conversationEngine();
31804
+ if (!engine) return [];
31805
+ const { me, realm } = this.#joined();
31806
+ const orgs = new Map();
31807
+ const orgOf = (person) => {
31808
+ let org = orgs.get(person);
31809
+ if (!org) orgs.set(person, org = realm.profile(person).then((profile) => profile.org, () => "none"));
31810
+ return org;
31811
+ };
31812
+ const candidates = teammateDms(engine.list(), me.person);
31813
+ await realm.freshen?.(candidates.map((c) => c.person)).catch(() => {});
31814
+ const dms = [];
31815
+ for (const { cid, person } of candidates) {
31816
+ if (dms.length >= 256) break;
31817
+ if (this.#shouldArm(person, await orgOf(person))) dms.push(cid);
31818
+ }
31819
+ return dms;
31820
+ }
31821
+ #shouldArm(person, org) {
31822
+ return wakeArmable(this.#grants, person, org);
31823
+ }
30818
31824
  async #wakeDisarm() {
30819
31825
  const home = this.#directory;
30820
31826
  if (!home) throw new MessagingError("invalid_state", "wake tickets are for a directory realm");
@@ -31625,6 +32631,7 @@ var init_daemon = __esmMin((() => {
31625
32631
  }
31626
32632
  };
31627
32633
  LIST_BYTES = 1572864;
32634
+ NAMED_PERSONS = 1e3;
31628
32635
  }));
31629
32636
  // node_modules/ws/lib/constants.js
31630
32637
  var require_constants = __commonJSMin(((exports, module) => {
@@ -34164,7 +35171,8 @@ var init_pages = __esmMin((() => {
34164
35171
  var init_store$2 = __esmMin((() => {
34165
35172
  init_dist$3();
34166
35173
  ({DatabaseSync} = process.getBuiltinModule("node:sqlite"));
34167
- MIGRATIONS = [`
35174
+ MIGRATIONS = [
35175
+ `
34168
35176
  CREATE TABLE IF NOT EXISTS conv_logs (
34169
35177
  realm TEXT NOT NULL,
34170
35178
  cid TEXT NOT NULL,
@@ -34215,7 +35223,8 @@ CREATE TABLE IF NOT EXISTS conv_shares (
34215
35223
  CREATE INDEX IF NOT EXISTS conv_shares_to ON conv_shares (realm, cid, to_device, n);
34216
35224
  CREATE INDEX IF NOT EXISTS conv_shares_pair ON conv_shares (realm, cid, from_device, to_device);
34217
35225
  CREATE INDEX IF NOT EXISTS conv_shares_at ON conv_shares (at);
34218
- `, `
35226
+ `,
35227
+ `
34219
35228
  CREATE TABLE IF NOT EXISTS conv_request_decisions (
34220
35229
  realm TEXT NOT NULL,
34221
35230
  id TEXT NOT NULL,
@@ -34223,7 +35232,17 @@ CREATE TABLE IF NOT EXISTS conv_request_decisions (
34223
35232
  decision TEXT NOT NULL,
34224
35233
  PRIMARY KEY (realm, id)
34225
35234
  );
34226
- `];
35235
+ `,
35236
+ `
35237
+ CREATE TABLE IF NOT EXISTS dir_wake_dms (
35238
+ device TEXT NOT NULL,
35239
+ cid TEXT NOT NULL,
35240
+ exp INTEGER NOT NULL,
35241
+ PRIMARY KEY (device, cid)
35242
+ );
35243
+ CREATE INDEX IF NOT EXISTS dir_wake_dms_exp ON dir_wake_dms (exp);
35244
+ `
35245
+ ];
34227
35246
  1 + MIGRATIONS.length;
34228
35247
  }));
34229
35248
  // packages/relay/dist/backoff.js
@@ -34879,7 +35898,8 @@ var init_client = __esmMin((() => {
34879
35898
  url: arm.url,
34880
35899
  ticket: arm.ticket,
34881
35900
  agents: arm.agents,
34882
- awaiting: arm.awaiting
35901
+ awaiting: arm.awaiting,
35902
+ ...arm.dms ? { dms: arm.dms } : {}
34883
35903
  });
34884
35904
  }
34885
35905
  wakeDisarm() {