@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
@@ -6029,6 +6029,7 @@ var init_errors = __esmMin((() => {
6029
6029
  "jelly_unavailable",
6030
6030
  "log_conflict",
6031
6031
  "conv_conflict",
6032
+ "conflict",
6032
6033
  "stale_epoch",
6033
6034
  "device_limit",
6034
6035
  "admission_required",
@@ -7005,6 +7006,12 @@ var init_conversation = __esmMin((() => {
7005
7006
  function grantProblem(grant) {
7006
7007
  const own = "person" in grant.subject && grant.subject.person === grant.person;
7007
7008
  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";
7009
+ if ("relation" in grant.subject) {
7010
+ if (grant.agentMail !== void 0 || grant.trustedUntil !== void 0 || grant.discoverable !== void 0) return "a relation grant sets only chat or ask";
7011
+ if (grant.subject.relation === "outside" && grant.ask !== void 0) return "the outside relation grant sets only chat";
7012
+ if (grant.subject.relation === "outside" && grant.chat === "muted") return "the outside relation grant sets chat open or blocked";
7013
+ if (grant.subject.relation === "org" && grant.chat !== void 0) return "the org relation grant sets only ask";
7014
+ }
7008
7015
  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";
7009
7016
  if (!own && grant.discoverable !== void 0) return "discoverable is set only on a grant about its own person";
7010
7017
  if (grant.trustedUntil !== void 0) {
@@ -7017,7 +7024,7 @@ function cardProblem(card) {
7017
7024
  if (card.from === card.to) return "a card is about another person";
7018
7025
  if (card.can.trustedUntil !== void 0 && card.can.ask !== "open") return "trustedUntil needs ask open";
7019
7026
  }
7020
- var ChatLevel, AgentMailLevel, AskLevel, GrantSubject, GrantSync, CardCan, CardData, CardGetData;
7027
+ var ChatLevel, AgentMailLevel, AskLevel, GrantRelation, GrantSubject, GrantSync, CardCan, CardData, CardGetData;
7021
7028
  var init_consent$2 = __esmMin((() => {
7022
7029
  init_zod();
7023
7030
  init_common();
@@ -7034,7 +7041,12 @@ var init_consent$2 = __esmMin((() => {
7034
7041
  "request",
7035
7042
  "no"
7036
7043
  ]);
7037
- GrantSubject = union$1([strictObject({ person: PersonId }), strictObject({ conv: ConvId })]);
7044
+ GrantRelation = _enum(["outside", "org"]);
7045
+ GrantSubject = union$1([
7046
+ strictObject({ person: PersonId }),
7047
+ strictObject({ conv: ConvId }),
7048
+ strictObject({ relation: GrantRelation })
7049
+ ]);
7038
7050
  object({
7039
7051
  v: literal(PROTOCOL),
7040
7052
  realm: RealmId,
@@ -7483,6 +7495,7 @@ var init_directory_frames = __esmMin((() => {
7483
7495
  init_common();
7484
7496
  init_directory$1();
7485
7497
  init_realm$1();
7498
+ init_conversation();
7486
7499
  init_jelly$2();
7487
7500
  Req$1 = string$1().min(1).max(64);
7488
7501
  Size = number$1().int().positive();
@@ -7580,7 +7593,8 @@ var init_directory_frames = __esmMin((() => {
7580
7593
  url: WakeUrl,
7581
7594
  ticket: Jws.refine((ticket) => ticket.length <= WAKE_TICKET_BYTES, { message: `at most ${WAKE_TICKET_BYTES} bytes` }),
7582
7595
  agents: array(SessionName).max(64),
7583
- awaiting: array(Ulid).max(500)
7596
+ awaiting: array(Ulid).max(500),
7597
+ dms: array(ConvId).max(256).optional()
7584
7598
  });
7585
7599
  WakeDisarmFrame = object({
7586
7600
  t: literal("wake.disarm"),
@@ -7671,7 +7685,7 @@ var init_directory_frames = __esmMin((() => {
7671
7685
  ]);
7672
7686
  }));
7673
7687
  // packages/core/dist/schema/ipc/device.js
7674
- var BindingState, RealmAlarmSummary, DeviceStatus, DeviceChangeView, DeviceListEntry, LinkText, Sas, LinkPreview, KeyFingerprint, LoginUserCode, LoginPurpose, LoginKind, LoginShown, LoginState, DeviceRemoveParams, DeviceApproveParams, IdentityReplaceParams, SignInStep, DirectoryEraseParams, DEVICE_METHODS;
7688
+ var BindingState, RealmAlarmSummary, DeviceStatus, DeviceChangeView, PresenceFeature, DeviceListEntry, LinkText, Sas, LinkPreview, KeyFingerprint, LoginUserCode, LoginPurpose, LoginKind, LoginShown, LoginState, DeviceRemoveParams, DeviceApproveParams, IdentityReplaceParams, SignInStep, DirectoryEraseParams, DEVICE_METHODS;
7675
7689
  var init_device = __esmMin((() => {
7676
7690
  init_zod();
7677
7691
  init_common();
@@ -7722,6 +7736,7 @@ var init_device = __esmMin((() => {
7722
7736
  approver: MachineLabel.optional(),
7723
7737
  at: Millis
7724
7738
  });
7739
+ PresenceFeature = string$1().regex(/^[a-z][a-z0-9-]{0,31}$/, "a feature name");
7725
7740
  DeviceListEntry = object({
7726
7741
  device: MachineId,
7727
7742
  label: MachineLabel,
@@ -7740,7 +7755,8 @@ var init_device = __esmMin((() => {
7740
7755
  at: Millis,
7741
7756
  reason: UnbindReason,
7742
7757
  by: MachineId
7743
- }).optional()
7758
+ }).optional(),
7759
+ features: array(PresenceFeature).max(32).optional()
7744
7760
  });
7745
7761
  LinkText = string$1().min(1).max(2048);
7746
7762
  Sas = string$1().regex(/^[0-9]{6}$/, "six digits");
@@ -7947,7 +7963,10 @@ var init_device = __esmMin((() => {
7947
7963
  };
7948
7964
  }));
7949
7965
  // packages/core/dist/schema/ipc/shapes.js
7950
- var SessionKey, SendExpect, DirectoryEntry, PeerRef, InboxItem, TaskFollowUp, TaskRecord, SendTarget, SendResult;
7966
+ function taskViewRules(schema) {
7967
+ 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");
7968
+ }
7969
+ var SessionKey, ActionToken, SendExpect, DirectoryEntry, PeerRef, InboxItem, TaskFollowUp, TASK_SIGN_IN_TOOL_CHARS, TASK_REFUSAL_CHARS, HostLocalId, TaskQuestion, TaskRecordFields, TaskRecord, PersonTask, SendTarget, SendResult;
7951
7970
  var init_shapes = __esmMin((() => {
7952
7971
  init_zod();
7953
7972
  init_a2a();
@@ -7956,6 +7975,7 @@ var init_shapes = __esmMin((() => {
7956
7975
  init_delegation$1();
7957
7976
  init_errors();
7958
7977
  SessionKey = string$1().min(1).max(200);
7978
+ ActionToken = string$1().regex(/^[A-Za-z0-9_-]{43}$/, "action token");
7959
7979
  SendExpect = _enum([
7960
7980
  "next_turn",
7961
7981
  "wake",
@@ -8026,7 +8046,14 @@ var init_shapes = __esmMin((() => {
8026
8046
  stage: DeliveryStage,
8027
8047
  at: Millis
8028
8048
  });
8029
- TaskRecord = object({
8049
+ TASK_SIGN_IN_TOOL_CHARS = LIMITS.previewChars;
8050
+ TASK_REFUSAL_CHARS = LIMITS.requestChars;
8051
+ HostLocalId = string$1().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/, "a plain identifier of at most 200 characters");
8052
+ TaskQuestion = strictObject({
8053
+ sessionId: HostLocalId,
8054
+ approvalId: HostLocalId
8055
+ });
8056
+ TaskRecordFields = object({
8030
8057
  task: Task$1.extend({ id: Ulid }),
8031
8058
  role: _enum(["requester", "executor"]),
8032
8059
  key: SessionKey.describe("local session that owns this side of the task"),
@@ -8034,8 +8061,33 @@ var init_shapes = __esmMin((() => {
8034
8061
  createdAt: Millis,
8035
8062
  updatedAt: Millis,
8036
8063
  chain: DelegationSummary.optional(),
8037
- followUps: array(TaskFollowUp).min(1).max(LIMITS.taskFollowUps).optional()
8038
- });
8064
+ followUps: array(TaskFollowUp).min(1).max(LIMITS.taskFollowUps).optional(),
8065
+ agent: SessionName.optional(),
8066
+ person: object({
8067
+ id: PersonId,
8068
+ name: string$1().max(200)
8069
+ }).optional(),
8070
+ org: _enum([
8071
+ "same",
8072
+ "other",
8073
+ "none"
8074
+ ]).optional(),
8075
+ signIn: object({
8076
+ tool: string$1().min(1).max(TASK_SIGN_IN_TOOL_CHARS).optional(),
8077
+ by: _enum(["you", "them"])
8078
+ }).optional(),
8079
+ refusal: object({ reason: string$1().min(1).max(TASK_REFUSAL_CHARS) }).optional(),
8080
+ question: TaskQuestion.optional()
8081
+ });
8082
+ TaskRecord = taskViewRules(TaskRecordFields);
8083
+ PersonTask = taskViewRules(TaskRecordFields.omit({ key: true }).extend({
8084
+ key: never().optional(),
8085
+ session: SessionName.describe("this person's agent on this device that holds this side of the task"),
8086
+ cancel: strictObject({
8087
+ name: literal("cancel"),
8088
+ token: ActionToken
8089
+ }).optional()
8090
+ })).refine((r) => r.cancel === void 0 || r.role === "requester", "cancel is for the requester side");
8039
8091
  SendTarget = SessionAddress;
8040
8092
  SendResult = object({
8041
8093
  to: SendTarget,
@@ -8049,13 +8101,14 @@ var init_shapes = __esmMin((() => {
8049
8101
  });
8050
8102
  }));
8051
8103
  // packages/core/dist/schema/ipc/consent.js
8052
- var GrantTarget, Levels, GrantView, GrantReview, GrantSetParams, AskRequest, ContactRequestView, AgentInvitationRequest, ContactSuggestion, HeldRequest, RequestAnswer, RequestText, PersonReach, RequestAnswerParams, ContactRequestParams, CONSENT_METHODS;
8104
+ var GrantTarget, Levels, GrantView, GrantReview, GrantSetParams, RequestAnswer, RequestAction, Actions, AskRequest, ContactRequestView, AgentInvitationRequest, ContactSuggestion, HeldRequest, RequestText, PersonReach, RequestAnswerParams, ContactRequestParams, ReachGroupId, ReachValue, ReachOption, ReachGroup, ReachSnapshot, ReachSetParams, CONSENT_METHODS;
8053
8105
  var init_consent$1 = __esmMin((() => {
8054
8106
  init_zod();
8055
8107
  init_common();
8056
8108
  init_conversation();
8057
8109
  init_consent$2();
8058
8110
  init_directory_frames();
8111
+ init_shapes();
8059
8112
  GrantTarget = union$1([strictObject({ person: PersonId }), strictObject({ conv: ConvId })]);
8060
8113
  Levels = {
8061
8114
  chat: ChatLevel.optional(),
@@ -8086,6 +8139,18 @@ var init_consent$1 = __esmMin((() => {
8086
8139
  ...Levels,
8087
8140
  trust: literal(true).optional()
8088
8141
  });
8142
+ RequestAnswer = _enum([
8143
+ "once",
8144
+ "always",
8145
+ "accept",
8146
+ "decline",
8147
+ "block"
8148
+ ]);
8149
+ RequestAction = strictObject({
8150
+ name: RequestAnswer,
8151
+ token: ActionToken
8152
+ });
8153
+ Actions = { actions: array(RequestAction).max(4).optional() };
8089
8154
  AskRequest = object({
8090
8155
  id: Ulid,
8091
8156
  kind: _enum(["message", "task"]),
@@ -8099,7 +8164,8 @@ var init_consent$1 = __esmMin((() => {
8099
8164
  to: SessionName,
8100
8165
  preview: string$1().max(LIMITS.previewChars),
8101
8166
  at: Millis,
8102
- exp: Millis
8167
+ exp: Millis,
8168
+ ...Actions
8103
8169
  });
8104
8170
  ContactRequestView = object({
8105
8171
  id: Ulid,
@@ -8125,7 +8191,8 @@ var init_consent$1 = __esmMin((() => {
8125
8191
  }),
8126
8192
  text: string$1().max(LIMITS.requestChars),
8127
8193
  at: Millis,
8128
- exp: Millis
8194
+ exp: Millis,
8195
+ ...Actions
8129
8196
  });
8130
8197
  AgentInvitationRequest = strictObject({
8131
8198
  kind: literal("agent-invitation"),
@@ -8133,14 +8200,16 @@ var init_consent$1 = __esmMin((() => {
8133
8200
  cid: ConvId,
8134
8201
  from: ParticipantId,
8135
8202
  to: ParticipantId,
8136
- at: Millis
8203
+ at: Millis,
8204
+ ...Actions
8137
8205
  });
8138
8206
  ContactSuggestion = object({
8139
8207
  id: Ulid,
8140
8208
  kind: literal("suggestion"),
8141
8209
  agent: SessionName,
8142
8210
  email: Email,
8143
- at: Millis
8211
+ at: Millis,
8212
+ ...Actions
8144
8213
  });
8145
8214
  HeldRequest = union$1([
8146
8215
  AskRequest,
@@ -8148,13 +8217,6 @@ var init_consent$1 = __esmMin((() => {
8148
8217
  ContactSuggestion,
8149
8218
  AgentInvitationRequest
8150
8219
  ]);
8151
- RequestAnswer = _enum([
8152
- "once",
8153
- "always",
8154
- "accept",
8155
- "decline",
8156
- "block"
8157
- ]);
8158
8220
  RequestText = string$1().min(1).refine((text) => text.length <= LIMITS.requestChars, { message: `at most ${LIMITS.requestChars} UTF-16 code units` });
8159
8221
  PersonReach = object({
8160
8222
  person: PersonId,
@@ -8166,14 +8228,57 @@ var init_consent$1 = __esmMin((() => {
8166
8228
  RequestAnswerParams = strictObject({
8167
8229
  id: Ulid,
8168
8230
  answer: RequestAnswer,
8169
- all: boolean().optional()
8170
- });
8231
+ all: boolean().optional(),
8232
+ token: ActionToken.optional()
8233
+ }).refine((p) => p.token === void 0 || p.all !== true, "an action token answers one request: drop all");
8171
8234
  ContactRequestParams = strictObject({
8172
8235
  email: Email,
8173
8236
  text: RequestText,
8174
8237
  suggestion: Ulid.optional()
8175
8238
  });
8239
+ ReachGroupId = _enum(["message", "ask"]);
8240
+ ReachValue = _enum([
8241
+ "open",
8242
+ "ask",
8243
+ "never"
8244
+ ]);
8245
+ ReachOption = object({
8246
+ value: ReachValue,
8247
+ label: string$1().max(40),
8248
+ description: string$1().max(300)
8249
+ });
8250
+ ReachGroup = object({
8251
+ id: ReachGroupId,
8252
+ label: string$1().max(80),
8253
+ options: array(ReachOption).length(3),
8254
+ value: ReachValue.nullable(),
8255
+ lapsed: ReachValue.optional()
8256
+ });
8257
+ ReachSnapshot = object({
8258
+ revision: string$1().min(1).max(64),
8259
+ available: boolean(),
8260
+ groups: array(ReachGroup).length(2)
8261
+ });
8262
+ ReachSetParams = strictObject({
8263
+ revision: string$1().min(1).max(64),
8264
+ changes: strictObject({
8265
+ message: ReachValue.optional(),
8266
+ ask: ReachValue.optional()
8267
+ })
8268
+ });
8176
8269
  CONSENT_METHODS = {
8270
+ "reach.get": {
8271
+ params: strictObject({}),
8272
+ result: ReachSnapshot
8273
+ },
8274
+ "reach.set": {
8275
+ params: ReachSetParams,
8276
+ result: ReachSnapshot
8277
+ },
8278
+ "reach.set.preview": {
8279
+ params: ReachSetParams,
8280
+ result: object({ sentence: string$1().max(500) })
8281
+ },
8177
8282
  "grant.list": {
8178
8283
  params: strictObject({ subject: GrantTarget.optional() }),
8179
8284
  result: object({
@@ -8354,6 +8459,7 @@ var init_jelly$1 = __esmMin((() => {
8354
8459
  }).optional(),
8355
8460
  line: string$1().max(120).optional(),
8356
8461
  device: NoticeDevice.optional(),
8462
+ key: string$1().regex(/^task:[0-9A-HJKMNP-TV-Z]{26}$/, "task:<task id>").optional(),
8357
8463
  at: Millis
8358
8464
  });
8359
8465
  NoticeCounts = object({ requests: number$1().int().nonnegative() });
@@ -8424,7 +8530,8 @@ var init_jelly$1 = __esmMin((() => {
8424
8530
  }),
8425
8531
  result: object({
8426
8532
  relay: _enum(["armed", "pending"]),
8427
- awaiting: number$1().int().nonnegative()
8533
+ awaiting: number$1().int().nonnegative(),
8534
+ dms: number$1().int().nonnegative()
8428
8535
  })
8429
8536
  },
8430
8537
  "wake.disarm": {
@@ -8469,6 +8576,7 @@ var init_conversation_views = __esmMin((() => {
8469
8576
  init_common();
8470
8577
  init_conversation();
8471
8578
  init_a2a();
8579
+ init_shapes();
8472
8580
  Count$2 = number$1().int().nonnegative().safe();
8473
8581
  Seq$1 = number$1().int().nonnegative().safe();
8474
8582
  Name = string$1().max(200);
@@ -8544,7 +8652,11 @@ var init_conversation_views = __esmMin((() => {
8544
8652
  });
8545
8653
  TaskView = object({
8546
8654
  id: Ulid,
8547
- state: TaskState
8655
+ state: TaskState,
8656
+ cancel: strictObject({
8657
+ name: literal("cancel"),
8658
+ token: ActionToken
8659
+ }).optional()
8548
8660
  });
8549
8661
  EventView = object({
8550
8662
  cid: ConvId,
@@ -8703,6 +8815,11 @@ var init_conversation_views = __esmMin((() => {
8703
8815
  object({
8704
8816
  type: literal("gap"),
8705
8817
  pos: Seq$1
8818
+ }),
8819
+ object({
8820
+ type: literal("task"),
8821
+ pos: Seq$1,
8822
+ task: PersonTask
8706
8823
  })
8707
8824
  ]).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" });
8708
8825
  }));
@@ -8818,7 +8935,12 @@ var init_conversations = __esmMin((() => {
8818
8935
  at: Millis,
8819
8936
  cid: ConvId.optional(),
8820
8937
  agent: SessionName.optional(),
8821
- state: _enum(["held", "pending-delivery"])
8938
+ state: _enum([
8939
+ "held",
8940
+ "pending-delivery",
8941
+ "resend",
8942
+ "outdated"
8943
+ ])
8822
8944
  });
8823
8945
  ConvListParams = strictObject({
8824
8946
  key: Key,
@@ -9417,12 +9539,31 @@ var init_ipc = __esmMin((() => {
9417
9539
  message: strictObject({ parts: array(Part$1).min(1).max(LIMITS.partsPerMessage) }).optional(),
9418
9540
  artifact: Artifact.optional(),
9419
9541
  append: boolean().optional(),
9420
- lastChunk: boolean().optional()
9421
- }), strictObject({
9542
+ lastChunk: boolean().optional(),
9543
+ signIn: strictObject({ tool: string$1().min(1).max(TASK_SIGN_IN_TOOL_CHARS) }).optional()
9544
+ }).refine((p) => p.signIn === void 0 || p.state === "TASK_STATE_AUTH_REQUIRED", "signIn goes with TASK_STATE_AUTH_REQUIRED"), strictObject({
9422
9545
  key: SessionKey,
9423
9546
  taskId: Ulid,
9424
- reason: string$1().max(1e3).optional()
9425
- }), strictObject({ keys: array(SessionKey).min(1).max(256) }), strictObject({
9547
+ question: TaskQuestion.nullable()
9548
+ }), object({ ok: literal(true) }), strictObject({
9549
+ role: _enum(["requester", "executor"]).optional(),
9550
+ state: TaskState.optional(),
9551
+ session: SessionName.optional().describe("only this agent's tasks"),
9552
+ cursor: string$1().min(1).max(200).optional().describe("opaque; from a previous nextCursor"),
9553
+ limit: number$1().int().min(1).max(100).optional()
9554
+ }), object({
9555
+ tasks: array(PersonTask),
9556
+ nextCursor: string$1().max(200).optional().describe("absent when there are no more pages"),
9557
+ total: number$1().int().nonnegative()
9558
+ }), strictObject({
9559
+ taskId: Ulid,
9560
+ role: _enum(["requester", "executor"]).optional()
9561
+ }), strictObject({
9562
+ key: SessionKey.optional(),
9563
+ taskId: Ulid,
9564
+ reason: string$1().max(1e3).optional(),
9565
+ token: ActionToken.optional()
9566
+ }).refine((p) => p.key === void 0 !== (p.token === void 0), "give exactly one of key or token"), strictObject({ keys: array(SessionKey).min(1).max(256) }), strictObject({
9426
9567
  name: TeamName,
9427
9568
  person: PersonName,
9428
9569
  label: MachineLabel,
@@ -9505,6 +9646,9 @@ var init_ipc = __esmMin((() => {
9505
9646
  "grant.list",
9506
9647
  "grant.set",
9507
9648
  "grant.set.preview",
9649
+ "reach.get",
9650
+ "reach.set",
9651
+ "reach.set.preview",
9508
9652
  "request.list",
9509
9653
  "request.answer",
9510
9654
  "request.answer.preview",
@@ -9514,6 +9658,8 @@ var init_ipc = __esmMin((() => {
9514
9658
  "notice.subscribe",
9515
9659
  "notice.list",
9516
9660
  "notice.ack",
9661
+ "tasks.list",
9662
+ "tasks.get",
9517
9663
  "device.approve.preview",
9518
9664
  "device.remove.preview",
9519
9665
  "identity.replace.preview",
@@ -9668,7 +9814,8 @@ var init_conv_frames = __esmMin((() => {
9668
9814
  strictObject({
9669
9815
  t: literal("conv.capabilities"),
9670
9816
  req: Req,
9671
- outdated: array(PersonId).max(LIMITS.convOpParticipants)
9817
+ outdated: array(PersonId).max(LIMITS.convOpParticipants),
9818
+ unknown: array(PersonId).min(1).max(LIMITS.convOpParticipants).optional()
9672
9819
  });
9673
9820
  union$1([strictObject({
9674
9821
  t: literal("conv.push"),
@@ -9881,6 +10028,11 @@ var init_agent_tools = __esmMin((() => {
9881
10028
  titleAuthor: Author$1.optional().describe("With title: who wrote the request; ours when you asked (mine)"),
9882
10029
  status: authored("The latest status message").optional(),
9883
10030
  statusAuthor: Author$1.optional().describe("With status: who wrote it; ours when you run the task (asked)"),
10031
+ signIn: object({
10032
+ tool: authored("The tool the executor named").optional(),
10033
+ 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")
10034
+ }).optional().describe("auth_required only: the task waits on a sign-in; the tool was written by the executor, as statusAuthor says"),
10035
+ refusal: object({ reason: authored("The executor's reason") }).optional().describe("rejected only: why the executor declined, in its words, as statusAuthor says"),
9884
10036
  createdAt: iso,
9885
10037
  updatedAt: iso,
9886
10038
  consent: ToolConsentSchema.optional(),
@@ -10161,7 +10313,7 @@ var init_b64u = __esmMin((() => {
10161
10313
  // packages/core/dist/version.js
10162
10314
  var VERSION;
10163
10315
  var init_version = __esmMin((() => {
10164
- VERSION = "0.8.0";
10316
+ VERSION = "0.9.0";
10165
10317
  }));
10166
10318
  // packages/core/dist/jcs.js
10167
10319
  function hasLoneSurrogate(text) {
@@ -11457,8 +11609,11 @@ var init_jelly_host = __esmMin((() => {
11457
11609
  "people.search",
11458
11610
  "people.get",
11459
11611
  "grant.list",
11612
+ "reach.get",
11460
11613
  "request.list",
11461
11614
  "sessions.list",
11615
+ "tasks.list",
11616
+ "tasks.get",
11462
11617
  "notice.subscribe",
11463
11618
  "notice.list",
11464
11619
  "device.list"
@@ -13481,6 +13636,7 @@ var init_present = __esmMin((() => {
13481
13636
  init_errors();
13482
13637
  init_peer_text();
13483
13638
  init_common();
13639
+ init_ipc();
13484
13640
  init_text();
13485
13641
  LIMITS.toolTextChars;
13486
13642
  createFence({ tag: PEER_FENCE_TAG });
@@ -14831,7 +14987,8 @@ CREATE TABLE IF NOT EXISTS dir_wakes (
14831
14987
  );
14832
14988
  CREATE INDEX IF NOT EXISTS dir_wakes_exp ON dir_wakes (exp);
14833
14989
  `;
14834
- MIGRATIONS = [`
14990
+ MIGRATIONS = [
14991
+ `
14835
14992
  CREATE TABLE IF NOT EXISTS conv_logs (
14836
14993
  realm TEXT NOT NULL,
14837
14994
  cid TEXT NOT NULL,
@@ -14882,7 +15039,8 @@ CREATE TABLE IF NOT EXISTS conv_shares (
14882
15039
  CREATE INDEX IF NOT EXISTS conv_shares_to ON conv_shares (realm, cid, to_device, n);
14883
15040
  CREATE INDEX IF NOT EXISTS conv_shares_pair ON conv_shares (realm, cid, from_device, to_device);
14884
15041
  CREATE INDEX IF NOT EXISTS conv_shares_at ON conv_shares (at);
14885
- `, `
15042
+ `,
15043
+ `
14886
15044
  CREATE TABLE IF NOT EXISTS conv_request_decisions (
14887
15045
  realm TEXT NOT NULL,
14888
15046
  id TEXT NOT NULL,
@@ -14890,7 +15048,17 @@ CREATE TABLE IF NOT EXISTS conv_request_decisions (
14890
15048
  decision TEXT NOT NULL,
14891
15049
  PRIMARY KEY (realm, id)
14892
15050
  );
14893
- `];
15051
+ `,
15052
+ `
15053
+ CREATE TABLE IF NOT EXISTS dir_wake_dms (
15054
+ device TEXT NOT NULL,
15055
+ cid TEXT NOT NULL,
15056
+ exp INTEGER NOT NULL,
15057
+ PRIMARY KEY (device, cid)
15058
+ );
15059
+ CREATE INDEX IF NOT EXISTS dir_wake_dms_exp ON dir_wake_dms (exp);
15060
+ `
15061
+ ];
14894
15062
  RELAY_SCHEMA = 1 + MIGRATIONS.length;
14895
15063
  RELAY_BASELINE = SCHEMA;
14896
15064
  RelayStoreRefusedError = class extends Error {
@@ -19414,7 +19582,7 @@ var init_pacing = __esmMin((() => {
19414
19582
  const back = this.#store.pair(to, from)?.last_at ?? null;
19415
19583
  return back === null || now - back > 2592e6;
19416
19584
  }
19417
- audience(person, watcher, orgOf, now) {
19585
+ audience(person, watcher, orgOf, now, together) {
19418
19586
  if (person === watcher) return true;
19419
19587
  const org = orgOf(person);
19420
19588
  if (org !== void 0 && org === orgOf(watcher)) return true;
@@ -19422,7 +19590,8 @@ var init_pacing = __esmMin((() => {
19422
19590
  const at = this.#store.pair(from, to)?.line_at ?? null;
19423
19591
  return at !== null && now - at <= 2592e6;
19424
19592
  };
19425
- return wrote(person, watcher) && wrote(watcher, person);
19593
+ if (wrote(person, watcher) && wrote(watcher, person)) return true;
19594
+ return together?.(person, watcher) ?? false;
19426
19595
  }
19427
19596
  #roll(now) {
19428
19597
  const day = Math.floor(now / DAY_MS$1);
@@ -19513,18 +19682,21 @@ var init_wakes = __esmMin((() => {
19513
19682
  if (claims.aud !== void 0 && claims.aud !== frame.url) throw new MessagingError("invalid_request", "the wake ticket is for another URL");
19514
19683
  if (claims.sub !== void 0 && claims.sub !== bound) throw new MessagingError("invalid_request", "the wake ticket is for another host");
19515
19684
  this.#clearRetry(device);
19685
+ const dms = [...new Set(frame.dms ?? [])];
19516
19686
  this.#store.putWake({
19517
19687
  device,
19518
19688
  url: frame.url,
19519
19689
  ticket: frame.ticket,
19520
19690
  agents: [...frame.agents],
19521
19691
  awaiting: [...frame.awaiting],
19692
+ dms,
19522
19693
  exp: claims.exp * 1e3
19523
19694
  });
19524
19695
  this.#host.log("wake.armed", {
19525
19696
  machine: device,
19526
19697
  agents: frame.agents.length,
19527
- awaiting: frame.awaiting.length
19698
+ awaiting: frame.awaiting.length,
19699
+ dms: dms.length
19528
19700
  });
19529
19701
  }
19530
19702
  forget(device, why) {
@@ -19565,6 +19737,19 @@ var init_wakes = __esmMin((() => {
19565
19737
  this.#present(device, "own");
19566
19738
  }
19567
19739
  }
19740
+ dm(devices, cid) {
19741
+ for (const device of devices) {
19742
+ if (this.#host.online(device)) continue;
19743
+ const row = this.#store.wake(device);
19744
+ if (!row) continue;
19745
+ if (row.exp <= this.#host.now()) {
19746
+ this.forget(device, "removed");
19747
+ continue;
19748
+ }
19749
+ if (!this.#store.wakeDm(device, cid, row.exp)) continue;
19750
+ this.#present(device, "dm");
19751
+ }
19752
+ }
19568
19753
  purge(now) {
19569
19754
  const gone = this.#store.purgeWakes(now);
19570
19755
  if (gone > 0) this.#host.log("wake.expired", { count: gone });
@@ -20030,7 +20215,7 @@ var init_module$1 = __esmMin((() => {
20030
20215
  seesPresence(conn, person) {
20031
20216
  if (conn.device === void 0 || !this.#watches.get(conn)?.presence.has(person)) return false;
20032
20217
  const watcher = this.directory.currentPerson(conn.device);
20033
- return watcher !== void 0 && this.pacer.audience(person, watcher, (p) => this.directory.orgOf(p), this.#host.now());
20218
+ return watcher !== void 0 && this.pacer.audience(person, watcher, (p) => this.directory.orgOf(p), this.#host.now(), this.#host.together);
20034
20219
  }
20035
20220
  presenceWatched(conn) {
20036
20221
  return this.#watches.get(conn)?.presence ?? new Set();
@@ -20092,6 +20277,9 @@ var init_store$1 = __esmMin((() => {
20092
20277
  this.#q(`INSERT INTO dir_wakes (device, url, ticket, agents, awaiting, exp, not_before) VALUES (?, ?, ?, ?, ?, ?, 0)
20093
20278
  ON CONFLICT (device) DO UPDATE SET url = excluded.url, ticket = excluded.ticket, agents = excluded.agents,
20094
20279
  awaiting = excluded.awaiting, exp = excluded.exp, not_before = 0`).run(row.device, row.url, row.ticket, JSON.stringify(row.agents), JSON.stringify(row.awaiting), row.exp);
20280
+ this.#q("DELETE FROM dir_wake_dms WHERE device = ?").run(row.device);
20281
+ const put = this.#q("INSERT OR IGNORE INTO dir_wake_dms (device, cid, exp) VALUES (?, ?, ?)");
20282
+ for (const cid of row.dms) put.run(row.device, cid, row.exp);
20095
20283
  });
20096
20284
  }
20097
20285
  wake(device) {
@@ -20115,6 +20303,13 @@ var init_store$1 = __esmMin((() => {
20115
20303
  notBefore: Number(row.not_before)
20116
20304
  };
20117
20305
  }
20306
+ wakeDm(device, cid, exp) {
20307
+ return this.#q("SELECT 1 FROM dir_wake_dms WHERE device = ? AND cid = ? AND exp = ?").get(device, cid, exp) !== void 0;
20308
+ }
20309
+ wakeDmCount(device) {
20310
+ const row = this.#q("SELECT COUNT(*) AS n FROM dir_wake_dms WHERE device = ?").get(device);
20311
+ return Number(row.n);
20312
+ }
20118
20313
  setWakeNotBefore(device, at) {
20119
20314
  this.transaction(() => {
20120
20315
  this.#q("UPDATE dir_wakes SET not_before = ? WHERE device = ?").run(at, device);
@@ -20122,11 +20317,13 @@ var init_store$1 = __esmMin((() => {
20122
20317
  }
20123
20318
  deleteWake(device) {
20124
20319
  return this.transaction(() => {
20320
+ this.#q("DELETE FROM dir_wake_dms WHERE device = ?").run(device);
20125
20321
  return Number(this.#q("DELETE FROM dir_wakes WHERE device = ?").run(device).changes) > 0;
20126
20322
  });
20127
20323
  }
20128
20324
  purgeWakes(now) {
20129
20325
  return this.transaction(() => {
20326
+ this.#q("DELETE FROM dir_wake_dms WHERE exp <= ?").run(now);
20130
20327
  return Number(this.#q("DELETE FROM dir_wakes WHERE exp <= ?").run(now).changes);
20131
20328
  });
20132
20329
  }
@@ -20264,7 +20461,7 @@ var init_store$1 = __esmMin((() => {
20264
20461
  };
20265
20462
  }));
20266
20463
  // packages/relay/dist/conv/module.js
20267
- var CONV_FRAME_TYPES, persons, readers, digestOf, REPLY_BUDGET, ConvModule;
20464
+ var CONV_FRAME_TYPES, persons, readers, TOGETHER_SCAN, digestOf, REPLY_BUDGET, ConvModule;
20268
20465
  var init_module = __esmMin((() => {
20269
20466
  init_dist();
20270
20467
  init_module$1();
@@ -20281,6 +20478,7 @@ var init_module = __esmMin((() => {
20281
20478
  ]);
20282
20479
  persons = (state) => new Set(state?.participants.map((p) => p.person) ?? []);
20283
20480
  readers = (state) => new Set(state?.participants.filter((p) => p.state === "member").map((p) => p.person) ?? []);
20481
+ TOGETHER_SCAN = 50;
20284
20482
  digestOf = (raw) => b64u.encode(convEventDigest(raw));
20285
20483
  REPLY_BUDGET = Math.floor(LIMITS.frameBytes * 3 / 4);
20286
20484
  ConvModule = class {
@@ -20293,6 +20491,8 @@ var init_module = __esmMin((() => {
20293
20491
  #rekeys = new RateLimiter(LIMITS.convRekeysPerHour, LIMITS.convRekeysPerHour / 60);
20294
20492
  #creates = new RateLimiter(LIMITS.convCreatesPerHour, LIMITS.convCreatesPerHour / 60);
20295
20493
  #shares = new RateLimiter(LIMITS.convSharesPerMinute);
20494
+ #capsAsked = new Map();
20495
+ #capsDay = -1;
20296
20496
  constructor(store, host, options = {}) {
20297
20497
  this.#store = store;
20298
20498
  this.#host = host;
@@ -20423,6 +20623,10 @@ var init_module = __esmMin((() => {
20423
20623
  if (event.class === "op") this.#push(realm, new Set([...after].filter((p) => !before.has(p) && !readers(state).has(p))), machine, push);
20424
20624
  const author = this.#host.realm(realm)?.device(machine)?.person;
20425
20625
  if (event.class === "msg" && event.wake !== void 0 && author !== void 0) this.#host.woken?.(realm, author, event.wake);
20626
+ if (event.class === "msg" && state.kind === "dm" && author !== void 0) {
20627
+ const others = [...readers(state)].filter((p) => p !== author);
20628
+ if (others.length > 0) this.#host.dmEvent?.(realm, author, cid, others);
20629
+ }
20426
20630
  },
20427
20631
  failed: (error) => this.#host.nack(conn, req, error)
20428
20632
  }, () => this.#logs.delete(key));
@@ -20600,16 +20804,46 @@ var init_module = __esmMin((() => {
20600
20804
  const parsed = ConvCapsFrame.safeParse(raw);
20601
20805
  if (!parsed.success) throw new MessagingError("invalid_request", "malformed conv.caps");
20602
20806
  const { req, persons } = parsed.data;
20603
- const outdated = [...new Set(persons)].filter((person) => {
20807
+ const realm = conn.auth.team;
20808
+ const asker = this.#host.realm(realm)?.device(conn.auth.machine)?.person;
20809
+ if (asker === void 0) throw new MessagingError("forbidden", "conv.caps needs a current device");
20810
+ const day = Math.floor(this.#host.now() / 864e5);
20811
+ if (this.#capsDay !== day) {
20812
+ this.#capsDay = day;
20813
+ this.#capsAsked.clear();
20814
+ }
20815
+ const named = [...new Set(persons)];
20816
+ const asked = this.#capsAsked.get(`${realm} ${asker}`) ?? new Set();
20817
+ const unknown = [];
20818
+ for (const p of named) {
20819
+ if (p === asker || asked.has(p) || (this.#host.related?.(realm, asker, p) ?? true)) continue;
20820
+ if (asked.size < LIMITS.requestsPerDay) asked.add(p);
20821
+ else unknown.push(p);
20822
+ }
20823
+ this.#capsAsked.set(`${realm} ${asker}`, asked);
20824
+ const outdated = named.filter((p) => !unknown.includes(p)).filter((person) => {
20604
20825
  const devices = this.#host.features?.(conn.auth.team, person) ?? [];
20605
20826
  return devices.length > 0 && devices.every((features) => !features.includes("conversations"));
20606
20827
  });
20607
20828
  this.#host.send(conn, JSON.stringify({
20608
20829
  t: "conv.capabilities",
20609
20830
  req,
20610
- outdated
20831
+ outdated,
20832
+ ...unknown.length ? { unknown } : {}
20611
20833
  }), "conv.capabilities");
20612
20834
  }
20835
+ together(realm, a, b) {
20836
+ if (a === b) return true;
20837
+ for (const cid of this.#store.sharedCids(realm, a, b, TOGETHER_SCAN)) {
20838
+ const log = this.#load(realm, cid);
20839
+ const members = readers(log?.state());
20840
+ if (!log || !members.has(a) || !members.has(b)) continue;
20841
+ try {
20842
+ if (requestLogState(log, this.#binding(realm)).active) return true;
20843
+ } catch {}
20844
+ }
20845
+ return false;
20846
+ }
20613
20847
  #head(conn, raw) {
20614
20848
  const parsed = ConvHeadFrame.safeParse(raw);
20615
20849
  if (!parsed.success) throw new MessagingError("invalid_request", "invalid conv.head frame");
@@ -20731,6 +20965,7 @@ var init_store = __esmMin((() => {
20731
20965
  isMember: prepare("SELECT 1 AS x FROM conv_members WHERE realm = ? AND person = ? AND cid = ?"),
20732
20966
  convsOf: prepare("SELECT COUNT(*) AS n FROM conv_members WHERE realm = ? AND person = ?"),
20733
20967
  cidsOf: prepare("SELECT cid FROM conv_members WHERE realm = ? AND person = ? AND cid > ? ORDER BY cid LIMIT ?"),
20968
+ sharedCids: prepare("SELECT a.cid AS cid FROM conv_members a JOIN conv_members b ON b.realm = a.realm AND b.cid = a.cid WHERE a.realm = ? AND a.person = ? AND b.person = ? ORDER BY a.cid LIMIT ?"),
20734
20969
  shareById: prepare("SELECT digest FROM conv_shares WHERE realm = ? AND cid = ? AND id = ?"),
20735
20970
  pairCount: prepare("SELECT COUNT(*) AS n FROM conv_shares WHERE realm = ? AND cid = ? AND from_device = ? AND to_device = ?"),
20736
20971
  insertShare: prepare("INSERT INTO conv_shares (realm, cid, id, from_device, to_device, digest, at, event) VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING n"),
@@ -20769,6 +21004,9 @@ var init_store = __esmMin((() => {
20769
21004
  cidsOf(realm, person, after, limit) {
20770
21005
  return this.#sql.cidsOf.all(realm, person, after, limit).map((r) => r.cid);
20771
21006
  }
21007
+ sharedCids(realm, a, b, limit) {
21008
+ return this.#sql.sharedCids.all(realm, a, b, limit).map((r) => r.cid);
21009
+ }
20772
21010
  shareDigest(realm, cid, id) {
20773
21011
  return this.#sql.shareById.get(realm, cid, id)?.digest;
20774
21012
  }
@@ -21076,7 +21314,8 @@ var init_server = __esmMin((() => {
21076
21314
  connections: () => online()?.values() ?? [],
21077
21315
  purge: (device) => this.#store.dropMachine(realm, device),
21078
21316
  remove: (device, frame) => this.#removeDevice(realm, device, frame),
21079
- replayPresence: (conn, persons) => this.#replayPresence(conn, persons)
21317
+ replayPresence: (conn, persons) => this.#replayPresence(conn, persons),
21318
+ together: (a, b) => this.#conv.together(realm, a, b)
21080
21319
  });
21081
21320
  realm = module.directory.realm;
21082
21321
  this.#log.event("directory.serving", {
@@ -21101,6 +21340,11 @@ var init_server = __esmMin((() => {
21101
21340
  wasRemovedDevice: (realm, device) => this.#dir ? realm === this.#dir.directory.realm && this.#dir.directory.wasRemoved(device) : this.#rosters.get(realm)?.wasRemoved(device) ?? false,
21102
21341
  wasDevice: (realm, person, device) => this.#dir ? realm === this.#dir.directory.realm && this.#dir.directory.personOfDevice(device) === person : this.#rosters.get(realm)?.member(device)?.person === person,
21103
21342
  online: (realm) => this.#online.get(realm) ?? new Map(),
21343
+ related: (realm, asker, person) => {
21344
+ const dir = this.#dir;
21345
+ if (!dir || realm !== dir.directory.realm) return true;
21346
+ return dir.pacer.audience(person, asker, (p) => dir.directory.orgOf(p), this.#now(), (a, b) => this.#conv.together(realm, a, b));
21347
+ },
21104
21348
  features: (realm, person) => {
21105
21349
  const devices = this.#dir ? realm === this.#dir.directory.realm ? this.#dir.directory.devicesOf(person) : [] : this.#rosters.get(realm)?.members().filter((m) => m.person === person).map((m) => m.machine) ?? [];
21106
21350
  const found = [];
@@ -21115,6 +21359,10 @@ var init_server = __esmMin((() => {
21115
21359
  woken: (realm, person, sessions) => {
21116
21360
  const dir = this.#dir;
21117
21361
  if (dir && realm === dir.directory.realm) this.#guard("wake.hinted", () => dir.wakes.hinted(dir.directory.devicesOf(person), sessions));
21362
+ },
21363
+ dmEvent: (realm, _person, cid, others) => {
21364
+ const dir = this.#dir;
21365
+ if (dir && realm === dir.directory.realm) this.#guard("wake.dm", () => dir.wakes.dm(others.flatMap((p) => dir.directory.devicesOf(p)), cid));
21118
21366
  }
21119
21367
  });
21120
21368
  }