@joe-broadhead/messaging-bin-linux-arm64-gnu 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/messagingd +1 -1
- package/lib/messagingd.mjs +891 -117
- package/package.json +1 -1
package/lib/messagingd.mjs
CHANGED
|
@@ -6118,6 +6118,8 @@ var init_common = __esm(() => {
|
|
|
6118
6118
|
delegation: `messaging/1/delegation
|
|
6119
6119
|
`,
|
|
6120
6120
|
grant: `messaging/1/grant
|
|
6121
|
+
`,
|
|
6122
|
+
card: `messaging/1/card
|
|
6121
6123
|
`,
|
|
6122
6124
|
seal: "messaging/1/seal",
|
|
6123
6125
|
directAuth: `messaging/1/direct-auth
|
|
@@ -6210,6 +6212,7 @@ var init_common = __esm(() => {
|
|
|
6210
6212
|
deletionPollMs: 6 * 60 * 60 * 1000,
|
|
6211
6213
|
dormantDays: 180,
|
|
6212
6214
|
requestsPerDay: 20,
|
|
6215
|
+
requestChars: 500,
|
|
6213
6216
|
unsolicitedPerPairPerDay: 20,
|
|
6214
6217
|
unsolicitedTargetsPerDay: 50,
|
|
6215
6218
|
dirLookupPersons: 64,
|
|
@@ -6225,6 +6228,7 @@ var init_common = __esm(() => {
|
|
|
6225
6228
|
trustDefaultMs: 30 * 24 * 60 * 60 * 1000,
|
|
6226
6229
|
trustMaxMs: 90 * 24 * 60 * 60 * 1000,
|
|
6227
6230
|
grantBytes: 2048,
|
|
6231
|
+
cardBytes: 2048,
|
|
6228
6232
|
grantsPerPerson: 5000,
|
|
6229
6233
|
heldPerSender: 20,
|
|
6230
6234
|
heldTotal: 200,
|
|
@@ -6688,7 +6692,14 @@ function grantProblem(grant) {
|
|
|
6688
6692
|
return "resignedAt is before ts";
|
|
6689
6693
|
return;
|
|
6690
6694
|
}
|
|
6691
|
-
|
|
6695
|
+
function cardProblem(card) {
|
|
6696
|
+
if (card.from === card.to)
|
|
6697
|
+
return "a card is about another person";
|
|
6698
|
+
if (card.can.trustedUntil !== undefined && card.can.ask !== "open")
|
|
6699
|
+
return "trustedUntil needs ask open";
|
|
6700
|
+
return;
|
|
6701
|
+
}
|
|
6702
|
+
var ChatLevel, AgentMailLevel, AskLevel, GrantSubject, ConsentGrant, GRANTS_PER_SYNC = 100, GrantSync, CardCan, ContactCard, CardData, CardGetData;
|
|
6692
6703
|
var init_consent = __esm(() => {
|
|
6693
6704
|
init_zod();
|
|
6694
6705
|
init_common();
|
|
@@ -6718,6 +6729,30 @@ var init_consent = __esm(() => {
|
|
|
6718
6729
|
ctx.addIssue({ code: "custom", message: problem });
|
|
6719
6730
|
});
|
|
6720
6731
|
GrantSync = strictObject({ grants: array(record(string2(), unknown())).min(1).max(GRANTS_PER_SYNC) });
|
|
6732
|
+
CardCan = strictObject({
|
|
6733
|
+
chat: boolean2(),
|
|
6734
|
+
agentMail: boolean2(),
|
|
6735
|
+
ask: AskLevel,
|
|
6736
|
+
trustedUntil: Millis.optional()
|
|
6737
|
+
});
|
|
6738
|
+
ContactCard = object({
|
|
6739
|
+
v: literal(PROTOCOL),
|
|
6740
|
+
realm: RealmId,
|
|
6741
|
+
from: PersonId,
|
|
6742
|
+
to: PersonId,
|
|
6743
|
+
by: MachineId,
|
|
6744
|
+
seq: number2().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
6745
|
+
ts: Millis,
|
|
6746
|
+
grantId: Ulid.optional(),
|
|
6747
|
+
can: CardCan,
|
|
6748
|
+
sig: Base64Url
|
|
6749
|
+
}).superRefine((card, ctx) => {
|
|
6750
|
+
const problem = cardProblem(card);
|
|
6751
|
+
if (problem)
|
|
6752
|
+
ctx.addIssue({ code: "custom", message: problem });
|
|
6753
|
+
});
|
|
6754
|
+
CardData = strictObject({ card: record(string2(), unknown()) });
|
|
6755
|
+
CardGetData = strictObject({ get: literal(true) });
|
|
6721
6756
|
});
|
|
6722
6757
|
|
|
6723
6758
|
// packages/core/dist/schema/delegation.js
|
|
@@ -6754,7 +6789,14 @@ var init_delegation = __esm(() => {
|
|
|
6754
6789
|
});
|
|
6755
6790
|
|
|
6756
6791
|
// packages/core/dist/schema/envelope.js
|
|
6757
|
-
|
|
6792
|
+
function requestMessage(message) {
|
|
6793
|
+
const text = message.parts[0]?.text;
|
|
6794
|
+
return bare(message, "text") && typeof text === "string" && text.length >= 1 && text.length <= LIMITS.requestChars;
|
|
6795
|
+
}
|
|
6796
|
+
function cardMessage(message, what) {
|
|
6797
|
+
return bare(message, "data") && (what === "card" ? CardData : CardGetData).safeParse(message.parts[0].data).success;
|
|
6798
|
+
}
|
|
6799
|
+
var EnvelopeType, SEAL_ALG = "x25519-hkdf-sha256-a256gcm", EnvelopeClass, Party, Sender, Recipient, Envelope, ChannelPersons, ChannelConv, SelfConv, RequestConv, CardConv, Conv, MessagePayload, BARE_MESSAGE, bare = (message, part) => Object.keys(message).every((key) => BARE_MESSAGE.has(key)) && message.parts.length === 1 && Object.keys(message.parts[0]).join() === part, TaskStatusPayload, TaskArtifactPayload, TaskCancelPayload, ReceiptPayload, SessionInfo, DirectEndpoint, PresencePayload, PAYLOAD_BY_TYPE, Provenance;
|
|
6758
6800
|
var init_envelope = __esm(() => {
|
|
6759
6801
|
init_zod();
|
|
6760
6802
|
init_a2a();
|
|
@@ -6764,7 +6806,7 @@ var init_envelope = __esm(() => {
|
|
|
6764
6806
|
init_errors3();
|
|
6765
6807
|
init_realm();
|
|
6766
6808
|
EnvelopeType = _enum(["message", "task.status", "task.artifact", "task.cancel", "receipt", "presence"]);
|
|
6767
|
-
EnvelopeClass = _enum(["self", "contact"]);
|
|
6809
|
+
EnvelopeClass = _enum(["self", "contact", "request"]);
|
|
6768
6810
|
Party = object({
|
|
6769
6811
|
person: PersonId,
|
|
6770
6812
|
device: MachineId,
|
|
@@ -6802,7 +6844,9 @@ var init_envelope = __esm(() => {
|
|
|
6802
6844
|
object({ kind: literal("channel"), op: literal("post"), id: Ulid, name: ChannelName })
|
|
6803
6845
|
]);
|
|
6804
6846
|
SelfConv = object({ kind: literal("self"), what: _enum(["grant", "snapshot"]) });
|
|
6805
|
-
|
|
6847
|
+
RequestConv = object({ kind: literal("request") });
|
|
6848
|
+
CardConv = object({ kind: literal("card"), what: _enum(["card", "get"]) });
|
|
6849
|
+
Conv = discriminatedUnion("kind", [ChannelConv, SelfConv, RequestConv, CardConv]);
|
|
6806
6850
|
MessagePayload = object({
|
|
6807
6851
|
kind: _enum(["message", "task"]),
|
|
6808
6852
|
message: Message,
|
|
@@ -6812,9 +6856,14 @@ var init_envelope = __esm(() => {
|
|
|
6812
6856
|
conv: Conv.optional()
|
|
6813
6857
|
}).refine((p) => p.kind === "task" ? p.message.taskId !== undefined : true, { message: "a task message needs message.taskId" }).refine((p) => p.kind === "task" === (p.delegation !== undefined), { message: "a task request carries a delegation chain, and only a task request" }).refine((p) => p.conv?.kind !== "self" || p.message.parts.length === 1 && GrantSync.safeParse(p.message.parts[0].data).success, {
|
|
6814
6858
|
message: "a self sync message carries exactly one data part with its grants"
|
|
6859
|
+
}).refine((p) => p.conv?.kind !== "request" || requestMessage(p.message), {
|
|
6860
|
+
message: `a message request carries one text part of 1 to ${LIMITS.requestChars} characters, and nothing else`
|
|
6861
|
+
}).refine((p) => p.conv?.kind !== "card" || cardMessage(p.message, p.conv.what), {
|
|
6862
|
+
message: "a card message carries one data part, the card or the request for one, and nothing else"
|
|
6815
6863
|
}).refine((p) => p.message.taskId === undefined || Ulid.safeParse(p.message.taskId).success, {
|
|
6816
6864
|
message: "Messaging task ids are ULIDs"
|
|
6817
6865
|
});
|
|
6866
|
+
BARE_MESSAGE = new Set(["messageId", "role", "parts"]);
|
|
6818
6867
|
TaskStatusPayload = TaskStatusUpdateEvent;
|
|
6819
6868
|
TaskArtifactPayload = TaskArtifactUpdateEvent;
|
|
6820
6869
|
TaskCancelPayload = object({
|
|
@@ -7304,7 +7353,7 @@ var init_shapes = __esm(() => {
|
|
|
7304
7353
|
direction: _enum(["in", "out"]),
|
|
7305
7354
|
body: ChatText,
|
|
7306
7355
|
thread: Ulid.optional(),
|
|
7307
|
-
state: _enum(["sent", "delivered", "refused"]).optional(),
|
|
7356
|
+
state: _enum(["sent", "held", "delivered", "refused"]).optional(),
|
|
7308
7357
|
refused: array(PersonId).max(LIMITS.channelPersons).optional(),
|
|
7309
7358
|
notice: literal(true).optional()
|
|
7310
7359
|
});
|
|
@@ -7375,6 +7424,95 @@ var init_shapes = __esm(() => {
|
|
|
7375
7424
|
});
|
|
7376
7425
|
});
|
|
7377
7426
|
|
|
7427
|
+
// packages/core/dist/schema/ipc/consent.js
|
|
7428
|
+
var GrantTarget, Levels, GrantView, GrantReview, GrantSetParams, AskRequest, ContactRequestView, ContactSuggestion, HeldRequest, RequestAnswer, RequestText, PersonReach, CONSENT_METHODS;
|
|
7429
|
+
var init_consent2 = __esm(() => {
|
|
7430
|
+
init_zod();
|
|
7431
|
+
init_common();
|
|
7432
|
+
init_consent();
|
|
7433
|
+
init_directory_frames();
|
|
7434
|
+
GrantTarget = union([strictObject({ person: PersonId }), strictObject({ channel: ChannelName })]);
|
|
7435
|
+
Levels = {
|
|
7436
|
+
chat: ChatLevel.optional(),
|
|
7437
|
+
agentMail: AgentMailLevel.optional(),
|
|
7438
|
+
ask: AskLevel.optional(),
|
|
7439
|
+
trustedUntil: Millis.optional(),
|
|
7440
|
+
discoverable: boolean2().optional()
|
|
7441
|
+
};
|
|
7442
|
+
GrantView = object({
|
|
7443
|
+
subject: union([object({ person: PersonId }), object({ channel: Ulid, name: ChannelName.optional() })]),
|
|
7444
|
+
...Levels,
|
|
7445
|
+
id: Ulid,
|
|
7446
|
+
ts: Millis,
|
|
7447
|
+
by: MachineId,
|
|
7448
|
+
resignedAt: Millis.optional()
|
|
7449
|
+
});
|
|
7450
|
+
GrantReview = object({
|
|
7451
|
+
subject: GrantView.shape.subject,
|
|
7452
|
+
chat: literal("open").optional(),
|
|
7453
|
+
agentMail: literal("open").optional(),
|
|
7454
|
+
ask: _enum(["open", "request"]).optional(),
|
|
7455
|
+
trustedUntil: Millis.optional(),
|
|
7456
|
+
discoverable: literal(true).optional(),
|
|
7457
|
+
at: Millis
|
|
7458
|
+
});
|
|
7459
|
+
GrantSetParams = strictObject({ subject: GrantTarget, ...Levels, trust: literal(true).optional() });
|
|
7460
|
+
AskRequest = object({
|
|
7461
|
+
id: Ulid,
|
|
7462
|
+
kind: _enum(["message", "task"]),
|
|
7463
|
+
from: object({ person: PersonId, device: MachineId, label: MachineLabel, agent: SessionName, address: SessionAddress }),
|
|
7464
|
+
to: SessionName,
|
|
7465
|
+
preview: string2().max(LIMITS.previewChars),
|
|
7466
|
+
at: Millis,
|
|
7467
|
+
exp: Millis
|
|
7468
|
+
});
|
|
7469
|
+
ContactRequestView = object({
|
|
7470
|
+
id: Ulid,
|
|
7471
|
+
kind: literal("contact"),
|
|
7472
|
+
from: object({
|
|
7473
|
+
person: PersonId,
|
|
7474
|
+
device: MachineId,
|
|
7475
|
+
label: MachineLabel,
|
|
7476
|
+
name: string2().max(200),
|
|
7477
|
+
email: string2().max(320).optional(),
|
|
7478
|
+
org: _enum(["same", "other", "none"])
|
|
7479
|
+
}),
|
|
7480
|
+
text: string2().max(LIMITS.requestChars),
|
|
7481
|
+
at: Millis,
|
|
7482
|
+
exp: Millis
|
|
7483
|
+
});
|
|
7484
|
+
ContactSuggestion = object({
|
|
7485
|
+
id: Ulid,
|
|
7486
|
+
kind: literal("suggestion"),
|
|
7487
|
+
agent: SessionName,
|
|
7488
|
+
email: Email,
|
|
7489
|
+
at: Millis
|
|
7490
|
+
});
|
|
7491
|
+
HeldRequest = union([AskRequest, ContactRequestView, ContactSuggestion]);
|
|
7492
|
+
RequestAnswer = _enum(["once", "always", "accept", "decline", "block"]);
|
|
7493
|
+
RequestText = string2().min(1).refine((text) => text.length <= LIMITS.requestChars, { message: `at most ${LIMITS.requestChars} UTF-16 code units` });
|
|
7494
|
+
PersonReach = object({ person: PersonId, can: CardCan, source: _enum(["card", "default"]), at: Millis.optional() });
|
|
7495
|
+
CONSENT_METHODS = {
|
|
7496
|
+
"grant.list": {
|
|
7497
|
+
params: strictObject({ subject: GrantTarget.optional() }),
|
|
7498
|
+
result: object({ grants: array(GrantView), review: array(GrantReview) })
|
|
7499
|
+
},
|
|
7500
|
+
"grant.set": { params: GrantSetParams, result: object({ grant: GrantView }) },
|
|
7501
|
+
"grant.set.preview": { params: GrantSetParams, result: object({ sentence: string2().max(500) }) },
|
|
7502
|
+
"request.list": { params: strictObject({}), result: object({ requests: array(HeldRequest).max(LIMITS.heldTotal * 3) }) },
|
|
7503
|
+
"request.answer": {
|
|
7504
|
+
params: strictObject({ id: Ulid, answer: RequestAnswer, all: boolean2().optional() }),
|
|
7505
|
+
result: object({ answered: array(Ulid).max(LIMITS.heldTotal) })
|
|
7506
|
+
},
|
|
7507
|
+
"contact.request": {
|
|
7508
|
+
params: strictObject({ email: Email, text: RequestText, suggestion: Ulid.optional() }),
|
|
7509
|
+
result: object({ id: Ulid, conversation: RoomRef, person: PersonId })
|
|
7510
|
+
},
|
|
7511
|
+
"contact.request.preview": { params: strictObject({ email: Email }), result: object({ found: boolean2() }) },
|
|
7512
|
+
"card.get": { params: strictObject({ person: PersonId }), result: object({ sent: boolean2() }) }
|
|
7513
|
+
};
|
|
7514
|
+
});
|
|
7515
|
+
|
|
7378
7516
|
// packages/core/dist/schema/ipc/surfaces.js
|
|
7379
7517
|
var METADATA_BYTES = 16384, ConversationRef, ConvPlace, ConvSummary, PersonSummary, SURFACE_METHODS;
|
|
7380
7518
|
var init_surfaces = __esm(() => {
|
|
@@ -7382,7 +7520,9 @@ var init_surfaces = __esm(() => {
|
|
|
7382
7520
|
init_a2a();
|
|
7383
7521
|
init_common();
|
|
7384
7522
|
init_delegation();
|
|
7523
|
+
init_directory_frames();
|
|
7385
7524
|
init_errors3();
|
|
7525
|
+
init_consent2();
|
|
7386
7526
|
init_shapes();
|
|
7387
7527
|
ConversationRef = string2().regex(/^(?:dm:(?:[a-z][a-z0-9-]{0,31}|p_[a-z2-7]{26})|channel:[a-z][a-z0-9-]{0,31}|agent:(?:[a-z][a-z0-9-]{0,31}|p_[a-z2-7]{26})\/[a-z0-9][a-z0-9._-]{0,63})$/, "dm:<person>, channel:<name> or agent:<person>/<agent>");
|
|
7388
7528
|
ConvPlace = union([ChatSeq, ConvCursor]);
|
|
@@ -7405,7 +7545,7 @@ var init_surfaces = __esm(() => {
|
|
|
7405
7545
|
SURFACE_METHODS = {
|
|
7406
7546
|
"contacts.list": {
|
|
7407
7547
|
params: strictObject({ person: PersonId.optional(), includeOffline: boolean2().optional() }),
|
|
7408
|
-
result: object({ entries: array(DirectoryEntry), conversations: array(ConvSummary) })
|
|
7548
|
+
result: object({ entries: array(DirectoryEntry), conversations: array(ConvSummary), reach: array(PersonReach) })
|
|
7409
7549
|
},
|
|
7410
7550
|
"msg.send": {
|
|
7411
7551
|
params: strictObject({
|
|
@@ -7422,6 +7562,7 @@ var init_surfaces = __esm(() => {
|
|
|
7422
7562
|
extensions: array(string2().min(1).max(256)).max(16).optional(),
|
|
7423
7563
|
referenceTaskIds: array(Ulid).max(16).optional(),
|
|
7424
7564
|
wake: boolean2().optional().describe("hint that the recipient may wake an idle session; default true"),
|
|
7565
|
+
request: strictObject({ contact: Email }).optional(),
|
|
7425
7566
|
external: strictObject({ principal: string2().min(1).max(64) }).optional(),
|
|
7426
7567
|
onBehalfOf: strictObject({
|
|
7427
7568
|
taskId: Ulid.describe("a task this session executes"),
|
|
@@ -7491,63 +7632,6 @@ var init_surfaces = __esm(() => {
|
|
|
7491
7632
|
};
|
|
7492
7633
|
});
|
|
7493
7634
|
|
|
7494
|
-
// packages/core/dist/schema/ipc/consent.js
|
|
7495
|
-
var GrantTarget, Levels, GrantView, GrantReview, GrantSetParams, HeldRequest, RequestAnswer, CONSENT_METHODS;
|
|
7496
|
-
var init_consent2 = __esm(() => {
|
|
7497
|
-
init_zod();
|
|
7498
|
-
init_common();
|
|
7499
|
-
init_consent();
|
|
7500
|
-
GrantTarget = union([strictObject({ person: PersonId }), strictObject({ channel: ChannelName })]);
|
|
7501
|
-
Levels = {
|
|
7502
|
-
chat: ChatLevel.optional(),
|
|
7503
|
-
agentMail: AgentMailLevel.optional(),
|
|
7504
|
-
ask: AskLevel.optional(),
|
|
7505
|
-
trustedUntil: Millis.optional(),
|
|
7506
|
-
discoverable: boolean2().optional()
|
|
7507
|
-
};
|
|
7508
|
-
GrantView = object({
|
|
7509
|
-
subject: union([object({ person: PersonId }), object({ channel: Ulid, name: ChannelName.optional() })]),
|
|
7510
|
-
...Levels,
|
|
7511
|
-
id: Ulid,
|
|
7512
|
-
ts: Millis,
|
|
7513
|
-
by: MachineId,
|
|
7514
|
-
resignedAt: Millis.optional()
|
|
7515
|
-
});
|
|
7516
|
-
GrantReview = object({
|
|
7517
|
-
subject: GrantView.shape.subject,
|
|
7518
|
-
chat: literal("open").optional(),
|
|
7519
|
-
agentMail: literal("open").optional(),
|
|
7520
|
-
ask: _enum(["open", "request"]).optional(),
|
|
7521
|
-
trustedUntil: Millis.optional(),
|
|
7522
|
-
discoverable: literal(true).optional(),
|
|
7523
|
-
at: Millis
|
|
7524
|
-
});
|
|
7525
|
-
GrantSetParams = strictObject({ subject: GrantTarget, ...Levels, trust: literal(true).optional() });
|
|
7526
|
-
HeldRequest = object({
|
|
7527
|
-
id: Ulid,
|
|
7528
|
-
from: object({ person: PersonId, device: MachineId, label: MachineLabel, agent: SessionName, address: SessionAddress }),
|
|
7529
|
-
to: SessionName,
|
|
7530
|
-
kind: _enum(["message", "task"]),
|
|
7531
|
-
preview: string2().max(LIMITS.previewChars),
|
|
7532
|
-
at: Millis,
|
|
7533
|
-
exp: Millis
|
|
7534
|
-
});
|
|
7535
|
-
RequestAnswer = _enum(["once", "always", "decline", "block"]);
|
|
7536
|
-
CONSENT_METHODS = {
|
|
7537
|
-
"grant.list": {
|
|
7538
|
-
params: strictObject({ subject: GrantTarget.optional() }),
|
|
7539
|
-
result: object({ grants: array(GrantView), review: array(GrantReview) })
|
|
7540
|
-
},
|
|
7541
|
-
"grant.set": { params: GrantSetParams, result: object({ grant: GrantView }) },
|
|
7542
|
-
"grant.set.preview": { params: GrantSetParams, result: object({ sentence: string2().max(500) }) },
|
|
7543
|
-
"request.list": { params: strictObject({}), result: object({ requests: array(HeldRequest).max(LIMITS.heldTotal) }) },
|
|
7544
|
-
"request.answer": {
|
|
7545
|
-
params: strictObject({ id: Ulid, answer: RequestAnswer, all: boolean2().optional() }),
|
|
7546
|
-
result: object({ answered: array(Ulid).max(LIMITS.heldTotal) })
|
|
7547
|
-
}
|
|
7548
|
-
};
|
|
7549
|
-
});
|
|
7550
|
-
|
|
7551
7635
|
// packages/core/dist/schema/ipc/jelly.js
|
|
7552
7636
|
var NoticeId, NOTICE_LINE_CHARS = 120, Notice, NoticeCounts, DaemonHealth, AgentSummary, AgentConversation, JELLY_METHODS;
|
|
7553
7637
|
var init_jelly2 = __esm(() => {
|
|
@@ -7929,7 +8013,7 @@ var init_b64u = __esm(() => {
|
|
|
7929
8013
|
});
|
|
7930
8014
|
|
|
7931
8015
|
// packages/core/dist/version.js
|
|
7932
|
-
var VERSION = "0.
|
|
8016
|
+
var VERSION = "0.3.0";
|
|
7933
8017
|
|
|
7934
8018
|
// packages/core/dist/jcs.js
|
|
7935
8019
|
function hasLoneSurrogate(text) {
|
|
@@ -8494,16 +8578,17 @@ function seal(args, randomness) {
|
|
|
8494
8578
|
const plaintext = utf8(canonicalize(payload));
|
|
8495
8579
|
if (plaintext.length > LIMITS.plaintextBytes)
|
|
8496
8580
|
throw new MessagingError("too_large", `payload exceeds ${LIMITS.plaintextBytes} bytes`);
|
|
8497
|
-
|
|
8581
|
+
const klass = type === "message" && payload.conv?.kind === "request" ? "request" : classOf(args.from, args.to);
|
|
8582
|
+
assertMeaning(type, args.from, args.to, klass, payload);
|
|
8498
8583
|
if (type !== "presence")
|
|
8499
|
-
return sealBytes(args, type, plaintext, randomness);
|
|
8584
|
+
return sealBytes(args, type, klass, plaintext, randomness);
|
|
8500
8585
|
if (args.recipientEnc !== undefined)
|
|
8501
8586
|
throw new MessagingError("invalid_request", "presence is not sealed");
|
|
8502
|
-
const header = parseWith(Header, { ...base2(args, type), enc: "none" }, "envelope header");
|
|
8587
|
+
const header = parseWith(Header, { ...base2(args, type, undefined), enc: "none" }, "envelope header");
|
|
8503
8588
|
assertShape(header);
|
|
8504
8589
|
return signed(args.identity, { ...header, body: payload });
|
|
8505
8590
|
}
|
|
8506
|
-
function base2(args, type) {
|
|
8591
|
+
function base2(args, type, klass) {
|
|
8507
8592
|
if (args.from?.device !== args.identity.machine)
|
|
8508
8593
|
throw new MessagingError("invalid_request", "from.device must be the signing identity");
|
|
8509
8594
|
const ttl = args.ttlMs ?? LIMITS.ttlMs;
|
|
@@ -8520,15 +8605,15 @@ function base2(args, type) {
|
|
|
8520
8605
|
ts: now,
|
|
8521
8606
|
exp: now + ttl,
|
|
8522
8607
|
anchor: args.anchor?.kind === "epoch" ? { kind: "epoch", size: args.anchor.size, root: args.anchor.root } : { kind: args.anchor?.kind, seq: args.anchor?.seq, hash: args.anchor?.hash },
|
|
8523
|
-
...
|
|
8608
|
+
...klass === undefined ? {} : { class: klass },
|
|
8524
8609
|
...args.replyTo === undefined ? {} : { replyTo: args.replyTo }
|
|
8525
8610
|
};
|
|
8526
8611
|
}
|
|
8527
8612
|
function classOf(from, to) {
|
|
8528
8613
|
return to !== undefined && "person" in to && to.person === from?.person ? "self" : "contact";
|
|
8529
8614
|
}
|
|
8530
|
-
function sealBytes(args, type, plaintext, randomness) {
|
|
8531
|
-
const head = base2(args, type);
|
|
8615
|
+
function sealBytes(args, type, klass, plaintext, randomness) {
|
|
8616
|
+
const head = base2(args, type, klass);
|
|
8532
8617
|
const recipient = recipientKey(args.recipientEnc);
|
|
8533
8618
|
const { ephemeral, nonce } = randomness();
|
|
8534
8619
|
const epk = rawPublicKey(ephemeral);
|
|
@@ -8629,13 +8714,15 @@ function open2(env, identity) {
|
|
|
8629
8714
|
const value = parseWire(text, { maxBytes: LIMITS.plaintextBytes });
|
|
8630
8715
|
canonicalize(value);
|
|
8631
8716
|
const payload = parseWith(schema, value, `${type} payload`);
|
|
8632
|
-
assertMeaning(type, env.from, env.to, payload);
|
|
8717
|
+
assertMeaning(type, env.from, env.to, env.class, payload);
|
|
8633
8718
|
return payload;
|
|
8634
8719
|
}
|
|
8635
|
-
function assertMeaning(type, from, to, payload) {
|
|
8720
|
+
function assertMeaning(type, from, to, klass, payload) {
|
|
8636
8721
|
if (type !== "message" || to === undefined || !("device" in to))
|
|
8637
8722
|
return;
|
|
8638
8723
|
const p = payload;
|
|
8724
|
+
if (klass === "request" !== (p.conv?.kind === "request"))
|
|
8725
|
+
throw new MessagingError("invalid_request", "a message request is exactly an envelope of class request");
|
|
8639
8726
|
if (to.agent !== undefined) {
|
|
8640
8727
|
if (p.conv !== undefined)
|
|
8641
8728
|
throw new MessagingError("invalid_request", "mail to an agent has no conv");
|
|
@@ -8643,6 +8730,8 @@ function assertMeaning(type, from, to, payload) {
|
|
|
8643
8730
|
}
|
|
8644
8731
|
if (p.conv?.kind === "self" && from?.person !== to.person)
|
|
8645
8732
|
throw new MessagingError("invalid_request", "a self sync goes between devices of one person");
|
|
8733
|
+
if (p.conv?.kind === "card" && (from?.person === to.person || from?.agent !== undefined))
|
|
8734
|
+
throw new MessagingError("invalid_request", "a card goes from a person to another person");
|
|
8646
8735
|
const line = p.kind === "message" && p.external === undefined && p.message.taskId === undefined && Ulid.safeParse(p.message.messageId).success && (p.message.contextId === undefined || Ulid.safeParse(p.message.contextId).success);
|
|
8647
8736
|
if (!line)
|
|
8648
8737
|
throw new MessagingError("invalid_request", "a chat line is kind message, outside any task, with ULID messageId and contextId");
|
|
@@ -8701,6 +8790,17 @@ function shapeProblem(env) {
|
|
|
8701
8790
|
return "a sealed envelope needs epk and nonce";
|
|
8702
8791
|
if (env.class === undefined)
|
|
8703
8792
|
return "a sealed envelope needs class";
|
|
8793
|
+
if (env.class === "request") {
|
|
8794
|
+
if (env.type !== "message")
|
|
8795
|
+
return "only a message is of class request";
|
|
8796
|
+
if (classOf(env.from, env.to) !== "contact")
|
|
8797
|
+
return "a message request goes to another person";
|
|
8798
|
+
if (env.from.agent !== undefined || "agent" in env.to && env.to.agent !== undefined)
|
|
8799
|
+
return "a message request goes from a person to a person";
|
|
8800
|
+
if (env.replyTo !== undefined)
|
|
8801
|
+
return "a message request answers nothing";
|
|
8802
|
+
return;
|
|
8803
|
+
}
|
|
8704
8804
|
if (env.class !== classOf(env.from, env.to))
|
|
8705
8805
|
return `class ${env.class} does not match the ends`;
|
|
8706
8806
|
return;
|
|
@@ -10468,6 +10568,10 @@ function decide(args) {
|
|
|
10468
10568
|
if (policy.chat === "request_required")
|
|
10469
10569
|
return { action: "refuse", code: "request_required" };
|
|
10470
10570
|
return policy.chat === "muted" ? { action: "deliver", consent: "guarded", silent: true } : { action: "deliver", consent: "guarded" };
|
|
10571
|
+
case "request":
|
|
10572
|
+
if (stranger)
|
|
10573
|
+
return { action: "hold" };
|
|
10574
|
+
return policy.chat === "muted" ? { action: "deliver", consent: "guarded", silent: true } : { action: "deliver", consent: "guarded" };
|
|
10471
10575
|
case "ask":
|
|
10472
10576
|
if (policy.ask === "open")
|
|
10473
10577
|
return { action: "deliver", consent: trusted(inbound.chain) ? "trusted" : "guarded" };
|
|
@@ -10497,6 +10601,67 @@ var init_consent3 = __esm(() => {
|
|
|
10497
10601
|
ASK_RANK = { no: 0, request: 1, open: 2 };
|
|
10498
10602
|
});
|
|
10499
10603
|
|
|
10604
|
+
// packages/core/dist/contact-card.js
|
|
10605
|
+
function signContactCard(identity, args) {
|
|
10606
|
+
const unsigned = {
|
|
10607
|
+
v: PROTOCOL,
|
|
10608
|
+
realm: args.realm,
|
|
10609
|
+
from: args.from,
|
|
10610
|
+
to: args.to,
|
|
10611
|
+
by: identity.machine,
|
|
10612
|
+
seq: args.seq,
|
|
10613
|
+
ts: args.now,
|
|
10614
|
+
...args.grantId !== undefined ? { grantId: args.grantId } : {},
|
|
10615
|
+
can: { chat: args.can.chat, agentMail: args.can.agentMail, ask: args.can.ask, ...args.can.trustedUntil !== undefined ? { trustedUntil: args.can.trustedUntil } : {} }
|
|
10616
|
+
};
|
|
10617
|
+
const problem = cardProblem(unsigned);
|
|
10618
|
+
if (problem)
|
|
10619
|
+
throw new MessagingError("invalid_request", problem);
|
|
10620
|
+
const card = parseWith(ContactCard, { ...unsigned, sig: sign(identity, DOMAIN.card, unsigned) }, "card");
|
|
10621
|
+
if (utf8(canonicalize(card)).length > LIMITS.cardBytes)
|
|
10622
|
+
throw new MessagingError("too_large", `a card is at most ${LIMITS.cardBytes} bytes`);
|
|
10623
|
+
return card;
|
|
10624
|
+
}
|
|
10625
|
+
function verifyContactCard(raw, ctx) {
|
|
10626
|
+
try {
|
|
10627
|
+
const card = parseWith(ContactCard, raw, "card");
|
|
10628
|
+
if (utf8(canonicalize(raw)).length > LIMITS.cardBytes)
|
|
10629
|
+
throw new MessagingError("too_large", `a card is at most ${LIMITS.cardBytes} bytes`);
|
|
10630
|
+
const { sig, ...unsigned } = raw;
|
|
10631
|
+
const input = signingInput(DOMAIN.card, unsigned);
|
|
10632
|
+
if (card.realm !== ctx.realm)
|
|
10633
|
+
throw new MessagingError("invalid_request", "the card is for another realm");
|
|
10634
|
+
if (card.from !== ctx.from)
|
|
10635
|
+
throw new MessagingError("invalid_request", "the card is not its sender's");
|
|
10636
|
+
if (card.to !== ctx.to)
|
|
10637
|
+
throw new MessagingError("invalid_request", "the card is for another person");
|
|
10638
|
+
if (typeof sig !== "string" || !verifyInput(card.by, input, sig))
|
|
10639
|
+
throw new MessagingError("bad_signature", "the card's signature is invalid");
|
|
10640
|
+
return card;
|
|
10641
|
+
} catch (error) {
|
|
10642
|
+
throw asMessagingError(error, "card");
|
|
10643
|
+
}
|
|
10644
|
+
}
|
|
10645
|
+
function cardCan(relation, grant, now) {
|
|
10646
|
+
const policy = effectivePolicy(relation, grant, [], now);
|
|
10647
|
+
return {
|
|
10648
|
+
chat: policy.chat === "open" || policy.chat === "muted",
|
|
10649
|
+
agentMail: policy.agentMail === "open",
|
|
10650
|
+
ask: policy.ask,
|
|
10651
|
+
...policy.trustedUntil !== undefined ? { trustedUntil: policy.trustedUntil } : {}
|
|
10652
|
+
};
|
|
10653
|
+
}
|
|
10654
|
+
var init_contact_card = __esm(() => {
|
|
10655
|
+
init_b64u();
|
|
10656
|
+
init_check();
|
|
10657
|
+
init_consent3();
|
|
10658
|
+
init_jcs();
|
|
10659
|
+
init_common();
|
|
10660
|
+
init_consent();
|
|
10661
|
+
init_errors3();
|
|
10662
|
+
init_sign();
|
|
10663
|
+
});
|
|
10664
|
+
|
|
10500
10665
|
// packages/core/dist/delegation.js
|
|
10501
10666
|
function grantHash(grant) {
|
|
10502
10667
|
return sha256(canonicalize(grant));
|
|
@@ -11925,6 +12090,7 @@ var TaskStateName, ToolErrorBody, fenced = (what) => string2().describe(`${what}
|
|
|
11925
12090
|
var init_agent_tools = __esm(() => {
|
|
11926
12091
|
init_zod();
|
|
11927
12092
|
init_common();
|
|
12093
|
+
init_directory_frames();
|
|
11928
12094
|
init_errors3();
|
|
11929
12095
|
init_ipc();
|
|
11930
12096
|
TaskStateName = _enum(["submitted", "working", "completed", "failed", "canceled", "input_required", "rejected", "auth_required"]);
|
|
@@ -11960,14 +12126,15 @@ var init_agent_tools = __esm(() => {
|
|
|
11960
12126
|
ContactsInput = strictObject({
|
|
11961
12127
|
query: string2().min(1).max(100).optional().describe("Only contacts whose person, agent or channel name contains this text"),
|
|
11962
12128
|
kind: _enum(["person", "agent", "channel"]).optional().describe("Only persons (each with their agents), only agents, or only channels. Omit for persons and channels."),
|
|
11963
|
-
can: _enum(["chat", "ask"]).optional().describe("Only those you can chat with (a line to a person or channel) or ask (a task to an agent)"),
|
|
12129
|
+
can: _enum(["chat", "ask"]).optional().describe("Only those you can chat with (a line to a person or channel) or ask (a task to an agent, at once or after their person agrees)"),
|
|
11964
12130
|
limit: number2().int().min(1).max(50).optional().describe("At most this many (default 20)")
|
|
11965
12131
|
});
|
|
11966
12132
|
Can = object({
|
|
11967
12133
|
chat: boolean2().describe("A chat line reaches them: a person other than you, or a channel you are in"),
|
|
11968
12134
|
agentMail: boolean2().describe("Agent mail reaches them"),
|
|
11969
|
-
ask: _enum(["open", "request", "no"]).describe("open: a task request goes straight to the agent, which runs it as a guarded peer turn unless its person trusts you"),
|
|
11970
|
-
source: _enum(["card", "default"]).describe("
|
|
12135
|
+
ask: _enum(["open", "request", "no"]).describe("open: a task request goes straight to the agent, which runs it as a guarded peer turn unless its person trusts you; request: their person sees a request card first; no: refused"),
|
|
12136
|
+
source: _enum(["card", "default"]).describe("card: what their person's card says; default: the policy for your org, or for a stranger outside it"),
|
|
12137
|
+
card: iso.optional().describe("With source card: when their person signed it. It is advice: their person may have changed it since")
|
|
11971
12138
|
});
|
|
11972
12139
|
Agent = object({
|
|
11973
12140
|
address: string2().describe("person/agent@label"),
|
|
@@ -12007,7 +12174,8 @@ var init_agent_tools = __esm(() => {
|
|
|
12007
12174
|
ask: boolean2().optional().describe("true: a task request, which the agent runs and reports on; follow it with messaging_tasks and messaging_read {id}. Only to an agent."),
|
|
12008
12175
|
replyTo: Ulid.optional().describe("What this answers: a message or task id from messaging_inbox or messaging_read (to an agent), or a line id (to a person or channel, a reply in that thread). Answering a task you asked for, while it waits for input, goes to that task."),
|
|
12009
12176
|
onBehalfOf: Ulid.optional().describe("With ask: a task you are running that this new task is part of, so the recipient sees who asked whom. Without it, the task starts a new chain."),
|
|
12010
|
-
key: string2().min(1).max(64).optional().describe("Any id of yours for this send: repeating a call with the same key sends once")
|
|
12177
|
+
key: string2().min(1).max(64).optional().describe("Any id of yours for this send: repeating a call with the same key sends once"),
|
|
12178
|
+
request: strictObject({ contact: Email.describe("Their exact email, lower-case") }).optional().describe('Only with to: "me": ask your person to send a message request to someone outside your org. Only your person can send one; you are not told what they decide.')
|
|
12011
12179
|
});
|
|
12012
12180
|
SendOutput = object({
|
|
12013
12181
|
id: string2().describe("The message, task request or line id; messaging_read {id} shows how far mail got"),
|
|
@@ -12196,6 +12364,7 @@ var init_dist = __esm(() => {
|
|
|
12196
12364
|
init_address();
|
|
12197
12365
|
init_cards();
|
|
12198
12366
|
init_consent3();
|
|
12367
|
+
init_contact_card();
|
|
12199
12368
|
init_delegation2();
|
|
12200
12369
|
init_task();
|
|
12201
12370
|
init_direct2();
|
|
@@ -14430,6 +14599,17 @@ class Chats {
|
|
|
14430
14599
|
});
|
|
14431
14600
|
return { id, conversation: room };
|
|
14432
14601
|
}
|
|
14602
|
+
sendRequest(person, text) {
|
|
14603
|
+
const { realm, me } = this.#host.joined();
|
|
14604
|
+
if (person === me.person || !realm.persons().includes(person))
|
|
14605
|
+
throw new MessagingError("not_found", "no Messaging user with that email accepts requests");
|
|
14606
|
+
const now = this.#host.now();
|
|
14607
|
+
this.#paced([person], now);
|
|
14608
|
+
const id = ulid2(now);
|
|
14609
|
+
this.#fan([person], { kind: "request" }, line(id, text));
|
|
14610
|
+
this.#file({ id, room: `dm:${person}`, direction: "out", person: me.person, body: text, at: now });
|
|
14611
|
+
return { id };
|
|
14612
|
+
}
|
|
14433
14613
|
create(p) {
|
|
14434
14614
|
const { realm, me } = this.#host.joined();
|
|
14435
14615
|
if (this.#store.channelByLocal(p.name))
|
|
@@ -14665,6 +14845,12 @@ class Chats {
|
|
|
14665
14845
|
return false;
|
|
14666
14846
|
if (payload.stage === "delivered" || payload.stage === "rejected")
|
|
14667
14847
|
this.settle(row.envelope, payload.stage, payload.code);
|
|
14848
|
+
if (payload.stage === "held" && this.#store.holdChatSent(row.envelope, this.#host.now())) {
|
|
14849
|
+
const sent = this.#store.line(row.line);
|
|
14850
|
+
const rev = sent && this.#store.touchLine(sent.id);
|
|
14851
|
+
if (sent && rev !== undefined)
|
|
14852
|
+
this.#emit(sent.room, sent.id, this.#host.joined().me.person, rev);
|
|
14853
|
+
}
|
|
14668
14854
|
return true;
|
|
14669
14855
|
}
|
|
14670
14856
|
settle(envelope, stage, code) {
|
|
@@ -16515,7 +16701,8 @@ class Notices {
|
|
|
16515
16701
|
return { ok: true };
|
|
16516
16702
|
}
|
|
16517
16703
|
counts() {
|
|
16518
|
-
|
|
16704
|
+
const store = this.#host.store;
|
|
16705
|
+
return { unread: store.unreadLines(), requests: store.heldCount() + store.contactRequestCount() + store.contactSuggestionCount() };
|
|
16519
16706
|
}
|
|
16520
16707
|
disconnected(id) {
|
|
16521
16708
|
this.#watchers.delete(id);
|
|
@@ -17190,6 +17377,17 @@ function upgradeStore(db, migrations) {
|
|
|
17190
17377
|
throw error;
|
|
17191
17378
|
}
|
|
17192
17379
|
}
|
|
17380
|
+
function contactRequestRow(row) {
|
|
17381
|
+
return {
|
|
17382
|
+
id: String(row.id),
|
|
17383
|
+
person: String(row.person),
|
|
17384
|
+
machine: String(row.machine),
|
|
17385
|
+
envelope: JSON.parse(String(row.envelope)),
|
|
17386
|
+
payload: JSON.parse(String(row.payload)),
|
|
17387
|
+
at: Number(row.at),
|
|
17388
|
+
exp: Number(row.exp)
|
|
17389
|
+
};
|
|
17390
|
+
}
|
|
17193
17391
|
function heldRow(row) {
|
|
17194
17392
|
return {
|
|
17195
17393
|
id: String(row.id),
|
|
@@ -17619,6 +17817,58 @@ class Store {
|
|
|
17619
17817
|
deleteExpiredHeld(now) {
|
|
17620
17818
|
return this.#run("DELETE FROM held WHERE exp <= ?", now);
|
|
17621
17819
|
}
|
|
17820
|
+
contactCard(person) {
|
|
17821
|
+
const row = this.#get("SELECT card, seq, at FROM contact_cards WHERE person = ?", person);
|
|
17822
|
+
return row && { card: JSON.parse(String(row.card)), seq: Number(row.seq), at: Number(row.at) };
|
|
17823
|
+
}
|
|
17824
|
+
contactCards() {
|
|
17825
|
+
return this.#all("SELECT person, card FROM contact_cards ORDER BY person").map((row) => ({ person: String(row.person), card: JSON.parse(String(row.card)) }));
|
|
17826
|
+
}
|
|
17827
|
+
putContactCard(person, card, seq, at) {
|
|
17828
|
+
return this.#run("INSERT INTO contact_cards (person, card, seq, at) VALUES (?, ?, ?, ?) ON CONFLICT (person) DO UPDATE SET card = excluded.card, seq = excluded.seq, at = excluded.at WHERE excluded.seq > contact_cards.seq", person, JSON.stringify(card), seq, at) > 0;
|
|
17829
|
+
}
|
|
17830
|
+
deleteContactCard(person) {
|
|
17831
|
+
this.#run("DELETE FROM contact_cards WHERE person = ?", person);
|
|
17832
|
+
}
|
|
17833
|
+
putContactRequest(row) {
|
|
17834
|
+
this.#run("INSERT INTO contact_requests (id, person, machine, envelope, payload, at, exp) VALUES (?, ?, ?, ?, ?, ?, ?)", row.id, row.person, row.machine, JSON.stringify(row.envelope), JSON.stringify(row.payload), row.at, row.exp);
|
|
17835
|
+
}
|
|
17836
|
+
contactRequest(id) {
|
|
17837
|
+
const row = this.#get("SELECT * FROM contact_requests WHERE id = ?", id);
|
|
17838
|
+
return row && contactRequestRow(row);
|
|
17839
|
+
}
|
|
17840
|
+
contactRequests(person) {
|
|
17841
|
+
const rows = person === undefined ? this.#all("SELECT * FROM contact_requests ORDER BY at, id") : this.#all("SELECT * FROM contact_requests WHERE person = ? ORDER BY at, id", person);
|
|
17842
|
+
return rows.map(contactRequestRow);
|
|
17843
|
+
}
|
|
17844
|
+
contactRequestCount() {
|
|
17845
|
+
return Number(this.#get("SELECT COUNT(*) AS n FROM contact_requests")?.n ?? 0);
|
|
17846
|
+
}
|
|
17847
|
+
deleteContactRequest(id) {
|
|
17848
|
+
this.#run("DELETE FROM contact_requests WHERE id = ?", id);
|
|
17849
|
+
}
|
|
17850
|
+
deleteExpiredContactRequests(now) {
|
|
17851
|
+
return this.#run("DELETE FROM contact_requests WHERE exp <= ?", now);
|
|
17852
|
+
}
|
|
17853
|
+
putContactSuggestion(row) {
|
|
17854
|
+
this.#run("INSERT INTO contact_suggestions (id, session, email, at) VALUES (?, ?, ?, ?)", row.id, row.session, row.email, row.at);
|
|
17855
|
+
}
|
|
17856
|
+
contactSuggestion(id) {
|
|
17857
|
+
const row = this.#get("SELECT * FROM contact_suggestions WHERE id = ?", id);
|
|
17858
|
+
return row && { id: String(row.id), session: String(row.session), email: String(row.email), at: Number(row.at) };
|
|
17859
|
+
}
|
|
17860
|
+
contactSuggestions() {
|
|
17861
|
+
return this.#all("SELECT * FROM contact_suggestions ORDER BY at, id").map((row) => ({ id: String(row.id), session: String(row.session), email: String(row.email), at: Number(row.at) }));
|
|
17862
|
+
}
|
|
17863
|
+
contactSuggestionCount() {
|
|
17864
|
+
return Number(this.#get("SELECT COUNT(*) AS n FROM contact_suggestions")?.n ?? 0);
|
|
17865
|
+
}
|
|
17866
|
+
deleteContactSuggestion(id) {
|
|
17867
|
+
this.#run("DELETE FROM contact_suggestions WHERE id = ?", id);
|
|
17868
|
+
}
|
|
17869
|
+
deleteContactSuggestionsBefore(before) {
|
|
17870
|
+
return this.#run("DELETE FROM contact_suggestions WHERE at < ?", before);
|
|
17871
|
+
}
|
|
17622
17872
|
putGrant(subject, grant) {
|
|
17623
17873
|
this.#run("INSERT INTO grants (subject, grant) VALUES (?, ?) ON CONFLICT (subject) DO UPDATE SET grant = excluded.grant", subject, JSON.stringify(grant));
|
|
17624
17874
|
}
|
|
@@ -17711,6 +17961,9 @@ class Store {
|
|
|
17711
17961
|
settleChatSent(envelope, stage, code) {
|
|
17712
17962
|
return this.#run("UPDATE chat_sent SET stage = ?, code = ? WHERE envelope = ? AND stage = 'sent'", stage, opt(code), envelope) > 0;
|
|
17713
17963
|
}
|
|
17964
|
+
holdChatSent(envelope, at) {
|
|
17965
|
+
return this.#run("UPDATE chat_sent SET held_at = ? WHERE envelope = ? AND stage = 'sent' AND held_at IS NULL", at, envelope) > 0;
|
|
17966
|
+
}
|
|
17714
17967
|
chatSentDue(now) {
|
|
17715
17968
|
return this.#all("SELECT * FROM chat_sent WHERE stage = 'sent' AND exp <= ?", now).map(chatSentRow);
|
|
17716
17969
|
}
|
|
@@ -17725,19 +17978,23 @@ class Store {
|
|
|
17725
17978
|
const out = new Map;
|
|
17726
17979
|
if (lines.length === 0)
|
|
17727
17980
|
return out;
|
|
17728
|
-
const rows = this.#all(`SELECT line, person, MAX(stage = 'delivered') AS d, MIN(stage IN ('rejected', 'expired')) AS r
|
|
17981
|
+
const rows = this.#all(`SELECT line, person, MAX(stage = 'delivered') AS d, MIN(stage IN ('rejected', 'expired')) AS r, MAX(stage = 'sent' AND held_at IS NOT NULL) AS h
|
|
17729
17982
|
FROM chat_sent WHERE line IN (SELECT value FROM json_each(?)) AND person <> ? GROUP BY line, person ORDER BY person`, JSON.stringify(lines), me);
|
|
17730
17983
|
for (const row of rows) {
|
|
17731
17984
|
const line = String(row.line);
|
|
17732
|
-
const entry = out.get(line) ?? { state: "sent", refused: [], pending: false };
|
|
17985
|
+
const entry = out.get(line) ?? { state: "sent", refused: [], pending: false, waiting: false };
|
|
17733
17986
|
if (Number(row.r) === 1)
|
|
17734
17987
|
entry.refused.push(String(row.person));
|
|
17735
|
-
else if (Number(row.d) !== 1)
|
|
17736
|
-
|
|
17988
|
+
else if (Number(row.d) !== 1) {
|
|
17989
|
+
if (Number(row.h) === 1)
|
|
17990
|
+
entry.waiting = true;
|
|
17991
|
+
else
|
|
17992
|
+
entry.pending = true;
|
|
17993
|
+
}
|
|
17737
17994
|
out.set(line, entry);
|
|
17738
17995
|
}
|
|
17739
17996
|
for (const entry of out.values())
|
|
17740
|
-
entry.state = entry.refused.length > 0 ? "refused" : entry.pending ? "sent" : "delivered";
|
|
17997
|
+
entry.state = entry.refused.length > 0 ? "refused" : entry.pending ? "sent" : entry.waiting ? "held" : "delivered";
|
|
17741
17998
|
return out;
|
|
17742
17999
|
}
|
|
17743
18000
|
sentUndelivered(now) {
|
|
@@ -18053,7 +18310,7 @@ function taskRow2(r) {
|
|
|
18053
18310
|
}
|
|
18054
18311
|
return row;
|
|
18055
18312
|
}
|
|
18056
|
-
var CHAT_COUNTER = "chat", STORE_SCHEMA =
|
|
18313
|
+
var CHAT_COUNTER = "chat", STORE_SCHEMA = 9, STORE_MIN_READER = 3, MIGRATIONS, BASELINE = `
|
|
18057
18314
|
CREATE TABLE IF NOT EXISTS kv (k TEXT PRIMARY KEY, v TEXT NOT NULL) STRICT;
|
|
18058
18315
|
CREATE TABLE IF NOT EXISTS counters (name TEXT PRIMARY KEY, value INTEGER NOT NULL) STRICT;
|
|
18059
18316
|
CREATE TABLE IF NOT EXISTS roster (seq INTEGER PRIMARY KEY, hash TEXT NOT NULL, entry TEXT NOT NULL) STRICT;
|
|
@@ -18293,6 +18550,24 @@ CREATE TABLE IF NOT EXISTS notices (
|
|
|
18293
18550
|
) STRICT;
|
|
18294
18551
|
CREATE INDEX IF NOT EXISTS notices_at ON notices (at);
|
|
18295
18552
|
`
|
|
18553
|
+
},
|
|
18554
|
+
{
|
|
18555
|
+
schema: 9,
|
|
18556
|
+
ddl: `
|
|
18557
|
+
CREATE TABLE IF NOT EXISTS contact_cards (person TEXT PRIMARY KEY, card TEXT NOT NULL, seq INTEGER NOT NULL, at INTEGER NOT NULL) STRICT;
|
|
18558
|
+
CREATE TABLE IF NOT EXISTS contact_requests (
|
|
18559
|
+
id TEXT PRIMARY KEY,
|
|
18560
|
+
person TEXT NOT NULL,
|
|
18561
|
+
machine TEXT NOT NULL,
|
|
18562
|
+
envelope TEXT NOT NULL,
|
|
18563
|
+
payload TEXT NOT NULL,
|
|
18564
|
+
at INTEGER NOT NULL,
|
|
18565
|
+
exp INTEGER NOT NULL
|
|
18566
|
+
) STRICT;
|
|
18567
|
+
CREATE INDEX IF NOT EXISTS contact_requests_person ON contact_requests (person);
|
|
18568
|
+
CREATE TABLE IF NOT EXISTS contact_suggestions (id TEXT PRIMARY KEY, session TEXT NOT NULL, email TEXT NOT NULL, at INTEGER NOT NULL) STRICT;
|
|
18569
|
+
`,
|
|
18570
|
+
columns: [{ table: "chat_sent", column: "held_at", decl: "INTEGER" }]
|
|
18296
18571
|
}
|
|
18297
18572
|
];
|
|
18298
18573
|
UNSPACED = /([\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Thai}])/gu;
|
|
@@ -18693,7 +18968,7 @@ class Grants {
|
|
|
18693
18968
|
const review = this.#store.grantReviews().filter(({ subject }) => only === undefined || subject === only).map(({ subject, levels, at }) => ({ subject: this.#subjectView(subject), ...levels, at }));
|
|
18694
18969
|
return { grants, review };
|
|
18695
18970
|
}
|
|
18696
|
-
set(p) {
|
|
18971
|
+
set(p, opts = {}) {
|
|
18697
18972
|
const { identity, realm, me } = this.#host.joined();
|
|
18698
18973
|
const now = this.#host.now();
|
|
18699
18974
|
const key = this.#subjectKey(p.subject, true);
|
|
@@ -18708,13 +18983,15 @@ class Grants {
|
|
|
18708
18983
|
this.#sync("grant", [grant]);
|
|
18709
18984
|
});
|
|
18710
18985
|
this.#host.log("grant.set", { subject: "person" in subject ? "person" : "channel" });
|
|
18986
|
+
if ("person" in subject && opts.card !== false)
|
|
18987
|
+
this.#host.changed?.(subject.person);
|
|
18711
18988
|
return { grant: this.#view(grant) };
|
|
18712
18989
|
}
|
|
18713
|
-
setPerson(person, change) {
|
|
18990
|
+
setPerson(person, change, opts = {}) {
|
|
18714
18991
|
const levels = { ...this.personGrant(person), ...change };
|
|
18715
18992
|
if (levels.ask !== "open")
|
|
18716
18993
|
delete levels.trustedUntil;
|
|
18717
|
-
return this.set({ subject: { person }, ...levels });
|
|
18994
|
+
return this.set({ subject: { person }, ...levels }, opts);
|
|
18718
18995
|
}
|
|
18719
18996
|
check(p) {
|
|
18720
18997
|
const { identity, realm, me } = this.#host.joined();
|
|
@@ -18753,6 +19030,8 @@ class Grants {
|
|
|
18753
19030
|
if (this.#store.grantReview(key)?.id !== grant.id)
|
|
18754
19031
|
this.#store.deleteGrantReview(key);
|
|
18755
19032
|
merged++;
|
|
19033
|
+
if ("person" in grant.subject)
|
|
19034
|
+
this.#host.merged?.(grant.subject.person);
|
|
18756
19035
|
}
|
|
18757
19036
|
this.#host.log("grant.sync", { id: env.id, what: conv.what, merged });
|
|
18758
19037
|
this.sweep();
|
|
@@ -18774,6 +19053,8 @@ class Grants {
|
|
|
18774
19053
|
if (copy) {
|
|
18775
19054
|
this.#store.putGrant(subject, copy);
|
|
18776
19055
|
resigned.push(copy);
|
|
19056
|
+
if ("person" in copy.subject)
|
|
19057
|
+
this.#host.changed?.(copy.subject.person);
|
|
18777
19058
|
} else {
|
|
18778
19059
|
this.#store.deleteGrant(subject);
|
|
18779
19060
|
}
|
|
@@ -18931,18 +19212,23 @@ class Holds {
|
|
|
18931
19212
|
return { requests };
|
|
18932
19213
|
}
|
|
18933
19214
|
answer(p) {
|
|
19215
|
+
if (p.answer === "accept" && !this.#store.held(p.id))
|
|
19216
|
+
throw new MessagingError("not_found", "no request with that id is held here");
|
|
18934
19217
|
const now = this.#host.now();
|
|
18935
19218
|
const row = this.#store.held(p.id);
|
|
18936
19219
|
if (!row || row.exp <= now)
|
|
18937
19220
|
throw new MessagingError("not_found", "no request with that id is held here");
|
|
19221
|
+
if (p.answer === "accept")
|
|
19222
|
+
throw new MessagingError("invalid_request", "an ask takes once, always, decline or block; accept answers a message request");
|
|
18938
19223
|
if (p.answer === "always")
|
|
18939
19224
|
this.#host.grants.setPerson(row.person, { ask: "open" });
|
|
18940
19225
|
if (p.answer === "block")
|
|
18941
19226
|
this.#host.grants.setPerson(row.person, { chat: "blocked" });
|
|
18942
19227
|
const rows = p.all ? this.#store.helds(row.person).filter((held) => held.exp > now) : [row];
|
|
18943
19228
|
const answered = [];
|
|
19229
|
+
const answer = p.answer;
|
|
18944
19230
|
for (const held of rows) {
|
|
18945
|
-
this.#host.tx(() => this.#settle(held,
|
|
19231
|
+
this.#host.tx(() => this.#settle(held, answer));
|
|
18946
19232
|
answered.push(held.id);
|
|
18947
19233
|
}
|
|
18948
19234
|
this.#host.log("request.answered", { answer: p.answer, count: answered.length });
|
|
@@ -18968,6 +19254,351 @@ var init_holds = __esm(() => {
|
|
|
18968
19254
|
init_dist();
|
|
18969
19255
|
});
|
|
18970
19256
|
|
|
19257
|
+
// packages/daemon/dist/consent/cards.js
|
|
19258
|
+
class Cards {
|
|
19259
|
+
#host;
|
|
19260
|
+
#answered = new Map;
|
|
19261
|
+
#asked = new Map;
|
|
19262
|
+
constructor(host) {
|
|
19263
|
+
this.#host = host;
|
|
19264
|
+
}
|
|
19265
|
+
changed(person) {
|
|
19266
|
+
this.#issueSoon(person).catch((error) => this.#host.log("card.failed", { code: error.code ?? "internal" }));
|
|
19267
|
+
}
|
|
19268
|
+
async#issueSoon(person) {
|
|
19269
|
+
await this.#host.prefetch(person);
|
|
19270
|
+
await this.#host.fresh([person], () => this.#issue(person));
|
|
19271
|
+
}
|
|
19272
|
+
#issue(person) {
|
|
19273
|
+
const { identity, realm, me } = this.#host.joined();
|
|
19274
|
+
if (person === me.person)
|
|
19275
|
+
return;
|
|
19276
|
+
const relation = this.#host.relation(person);
|
|
19277
|
+
if (relation === "self" || relation === "stranger")
|
|
19278
|
+
return;
|
|
19279
|
+
const grant = this.#host.personGrant(person);
|
|
19280
|
+
if (grant?.chat === "blocked")
|
|
19281
|
+
return;
|
|
19282
|
+
const now = this.#host.now();
|
|
19283
|
+
const seq = this.#nextSeq(now);
|
|
19284
|
+
const held = this.#host.store.grant(`person:${person}`);
|
|
19285
|
+
const card = signContactCard(identity, {
|
|
19286
|
+
realm: realm.realm,
|
|
19287
|
+
from: me.person,
|
|
19288
|
+
to: person,
|
|
19289
|
+
seq,
|
|
19290
|
+
now,
|
|
19291
|
+
...grant !== undefined && typeof held?.id === "string" ? { grantId: held.id } : {},
|
|
19292
|
+
can: cardCan(relation, grant, now)
|
|
19293
|
+
});
|
|
19294
|
+
for (const device of realm.known(person).devices) {
|
|
19295
|
+
this.#host.route(this.#host.seal(device.machine, cardMessage2({ card }, "card")));
|
|
19296
|
+
}
|
|
19297
|
+
this.#host.log("card.sent", { seq });
|
|
19298
|
+
}
|
|
19299
|
+
#nextSeq(now) {
|
|
19300
|
+
const store = this.#host.store;
|
|
19301
|
+
const last = Number(store.getKv(SEQ_KEY) ?? 0);
|
|
19302
|
+
const seq = Math.max(now, last + 1);
|
|
19303
|
+
store.setKv(SEQ_KEY, String(seq));
|
|
19304
|
+
return seq;
|
|
19305
|
+
}
|
|
19306
|
+
receive(env, sender, payload) {
|
|
19307
|
+
const conv = payload.conv;
|
|
19308
|
+
const data = payload.message.parts[0].data;
|
|
19309
|
+
if (conv.what === "get")
|
|
19310
|
+
return this.#answer(sender.person);
|
|
19311
|
+
const { realm, me } = this.#host.joined();
|
|
19312
|
+
const drop = (code) => this.#host.log("card.dropped", { id: env.id, code });
|
|
19313
|
+
const relation = this.#host.relation(sender.person);
|
|
19314
|
+
if (relation === "stranger")
|
|
19315
|
+
return drop("request_required");
|
|
19316
|
+
if (this.#host.personGrant(sender.person)?.chat === "blocked")
|
|
19317
|
+
return drop("refused");
|
|
19318
|
+
let card;
|
|
19319
|
+
try {
|
|
19320
|
+
card = verifyContactCard(data.card, { realm: realm.realm, from: sender.person, to: me.person });
|
|
19321
|
+
} catch (error) {
|
|
19322
|
+
return drop(error.code ?? "invalid_request");
|
|
19323
|
+
}
|
|
19324
|
+
if (!realm.known(sender.person).devices.some((device) => device.machine === card.by))
|
|
19325
|
+
return drop("unknown_device");
|
|
19326
|
+
if (this.#host.store.putContactCard(sender.person, data.card, card.seq, this.#host.now()))
|
|
19327
|
+
this.#host.log("card.kept", { seq: card.seq });
|
|
19328
|
+
}
|
|
19329
|
+
#answer(person) {
|
|
19330
|
+
const now = this.#host.now();
|
|
19331
|
+
const last = this.#answered.get(person);
|
|
19332
|
+
if (last !== undefined && now - last < DAY_MS2)
|
|
19333
|
+
return;
|
|
19334
|
+
if (this.#host.personGrant(person) === undefined)
|
|
19335
|
+
return;
|
|
19336
|
+
this.#answered.set(person, now);
|
|
19337
|
+
this.changed(person);
|
|
19338
|
+
}
|
|
19339
|
+
async get(person) {
|
|
19340
|
+
const { me } = this.#host.joined();
|
|
19341
|
+
if (person === me.person)
|
|
19342
|
+
return false;
|
|
19343
|
+
const now = this.#host.now();
|
|
19344
|
+
const last = this.#asked.get(person);
|
|
19345
|
+
if (last !== undefined && now - last < DAY_MS2)
|
|
19346
|
+
return false;
|
|
19347
|
+
this.#asked.set(person, now);
|
|
19348
|
+
await this.#host.fresh([person], () => {
|
|
19349
|
+
const { realm } = this.#host.joined();
|
|
19350
|
+
for (const device of realm.known(person).devices)
|
|
19351
|
+
this.#host.route(this.#host.seal(device.machine, cardMessage2({ get: true }, "get")));
|
|
19352
|
+
});
|
|
19353
|
+
return true;
|
|
19354
|
+
}
|
|
19355
|
+
refused(person) {
|
|
19356
|
+
this.#host.store.deleteContactCard(person);
|
|
19357
|
+
this.get(person).catch(() => {
|
|
19358
|
+
return;
|
|
19359
|
+
});
|
|
19360
|
+
}
|
|
19361
|
+
reach(person, sameOrg) {
|
|
19362
|
+
const card = this.#card(person);
|
|
19363
|
+
if (card)
|
|
19364
|
+
return { person, can: card.can, source: "card", at: card.ts };
|
|
19365
|
+
return { person, can: cardCan(sameOrg ? "org" : "stranger", undefined, this.#host.now()), source: "default" };
|
|
19366
|
+
}
|
|
19367
|
+
cardPersons() {
|
|
19368
|
+
return this.#host.store.contactCards().map((row) => row.person);
|
|
19369
|
+
}
|
|
19370
|
+
#card(person) {
|
|
19371
|
+
const held = this.#host.store.contactCard(person);
|
|
19372
|
+
if (!held)
|
|
19373
|
+
return;
|
|
19374
|
+
const { realm, me } = this.#host.joined();
|
|
19375
|
+
try {
|
|
19376
|
+
const card = verifyContactCard(held.card, { realm: realm.realm, from: person, to: me.person });
|
|
19377
|
+
if (!realm.known(person).devices.some((device) => device.machine === card.by))
|
|
19378
|
+
return;
|
|
19379
|
+
const { trustedUntil, ...rest } = card.can;
|
|
19380
|
+
return { can: trustedUntil !== undefined && trustedUntil > this.#host.now() ? card.can : rest, ts: card.ts };
|
|
19381
|
+
} catch {
|
|
19382
|
+
this.#host.store.deleteContactCard(person);
|
|
19383
|
+
return;
|
|
19384
|
+
}
|
|
19385
|
+
}
|
|
19386
|
+
}
|
|
19387
|
+
function cardMessage2(data, what) {
|
|
19388
|
+
return { kind: "message", message: { messageId: ulid2(), role: "ROLE_USER", parts: [{ data }] }, conv: { kind: "card", what } };
|
|
19389
|
+
}
|
|
19390
|
+
var SEQ_KEY = "cards.seq", DAY_MS2 = 86400000;
|
|
19391
|
+
var init_cards2 = __esm(() => {
|
|
19392
|
+
init_dist();
|
|
19393
|
+
});
|
|
19394
|
+
|
|
19395
|
+
// packages/daemon/dist/consent/requests.js
|
|
19396
|
+
class Requests {
|
|
19397
|
+
#host;
|
|
19398
|
+
#store;
|
|
19399
|
+
#waiting = new Map;
|
|
19400
|
+
#day = -1;
|
|
19401
|
+
#today = new Set;
|
|
19402
|
+
constructor(host) {
|
|
19403
|
+
this.#host = host;
|
|
19404
|
+
this.#store = host.store;
|
|
19405
|
+
}
|
|
19406
|
+
async preview(p) {
|
|
19407
|
+
this.#hosted();
|
|
19408
|
+
return { found: await this.#resolve(p.email) !== undefined };
|
|
19409
|
+
}
|
|
19410
|
+
async request(p) {
|
|
19411
|
+
this.#hosted();
|
|
19412
|
+
const { me } = this.#host.joined();
|
|
19413
|
+
if (p.suggestion !== undefined && !this.#store.contactSuggestion(p.suggestion))
|
|
19414
|
+
throw new MessagingError("not_found", "no suggestion with that id is held here");
|
|
19415
|
+
const person = await this.#resolve(p.email);
|
|
19416
|
+
if (person === undefined)
|
|
19417
|
+
throw new MessagingError("not_found", "no Messaging user with that email accepts requests");
|
|
19418
|
+
if (person === me.person)
|
|
19419
|
+
throw new MessagingError("invalid_request", "that email is your own");
|
|
19420
|
+
if (this.#host.grants.personGrant(person)?.chat === "blocked")
|
|
19421
|
+
throw new MessagingError("invalid_state", "you blocked that person; unblock them with grant.set first");
|
|
19422
|
+
const now = this.#host.now();
|
|
19423
|
+
this.#paced(person, now);
|
|
19424
|
+
await this.#host.grants.prefetch(person);
|
|
19425
|
+
const line = await this.#host.fresh([person], () => this.#host.tx(() => {
|
|
19426
|
+
if (this.#host.grants.personGrant(person)?.chat === undefined)
|
|
19427
|
+
this.#host.grants.setPerson(person, { chat: "open" }, { card: false });
|
|
19428
|
+
const sent = this.#host.chats.sendRequest(person, p.text);
|
|
19429
|
+
if (p.suggestion !== undefined)
|
|
19430
|
+
this.#store.deleteContactSuggestion(p.suggestion);
|
|
19431
|
+
return sent;
|
|
19432
|
+
}));
|
|
19433
|
+
this.#waiting.set(person, now);
|
|
19434
|
+
this.#today.add(person);
|
|
19435
|
+
this.#host.log("contact.request.sent", {});
|
|
19436
|
+
return { id: line.id, conversation: `dm:${person}`, person };
|
|
19437
|
+
}
|
|
19438
|
+
async#resolve(email) {
|
|
19439
|
+
const { realm } = this.#host.joined();
|
|
19440
|
+
const found = await realm.resolve(email, "exact-email");
|
|
19441
|
+
if (found.length > 1)
|
|
19442
|
+
throw new MessagingError("ambiguous_address", "more than one person has that email");
|
|
19443
|
+
return found[0];
|
|
19444
|
+
}
|
|
19445
|
+
#hosted() {
|
|
19446
|
+
if (this.#host.realmKind() !== "directory") {
|
|
19447
|
+
throw new MessagingError("invalid_request", "message requests are for a hosted realm: on a team, every person is in your org and can be written to");
|
|
19448
|
+
}
|
|
19449
|
+
}
|
|
19450
|
+
#paced(person, now) {
|
|
19451
|
+
const day = Math.floor(now / DAY_MS3);
|
|
19452
|
+
if (day !== this.#day) {
|
|
19453
|
+
this.#day = day;
|
|
19454
|
+
this.#today.clear();
|
|
19455
|
+
}
|
|
19456
|
+
const waiting = this.#waiting.get(person);
|
|
19457
|
+
if (waiting !== undefined && now - waiting < LIMITS.ttlMs)
|
|
19458
|
+
throw new MessagingError("rate_limited", "a request to that person is waiting for their answer");
|
|
19459
|
+
if (!this.#today.has(person) && this.#today.size >= LIMITS.requestsPerDay) {
|
|
19460
|
+
throw new MessagingError("rate_limited", `you sent message requests to ${LIMITS.requestsPerDay} persons today; try again tomorrow`);
|
|
19461
|
+
}
|
|
19462
|
+
}
|
|
19463
|
+
answered(person) {
|
|
19464
|
+
this.#waiting.delete(person);
|
|
19465
|
+
}
|
|
19466
|
+
receive(env, sender, payload, now) {
|
|
19467
|
+
const reject = (code) => {
|
|
19468
|
+
this.#host.log("contact.request.rejected", { id: env.id, code });
|
|
19469
|
+
this.#host.receipt(sender.machine, env.id, "rejected", code);
|
|
19470
|
+
};
|
|
19471
|
+
if (this.#host.realmKind() !== "directory")
|
|
19472
|
+
return reject("invalid_request");
|
|
19473
|
+
const decision = this.#host.grants.decide(sender.person, { kind: "request" });
|
|
19474
|
+
if (decision.action === "refuse")
|
|
19475
|
+
return reject(decision.code);
|
|
19476
|
+
if (decision.action === "deliver")
|
|
19477
|
+
return this.#host.chats.receive(env, sender, asLine(payload), now);
|
|
19478
|
+
if (this.#store.contactRequest(env.id))
|
|
19479
|
+
return reject("invalid_request");
|
|
19480
|
+
if (this.#store.contactRequests(sender.person).length > 0 || this.#store.contactRequestCount() >= LIMITS.heldTotal)
|
|
19481
|
+
return reject("rate_limited");
|
|
19482
|
+
this.#store.putContactRequest({ id: env.id, person: sender.person, machine: sender.machine, envelope: env, payload, at: now, exp: env.exp });
|
|
19483
|
+
this.#host.held({ id: env.id, person: sender.person, agent: undefined, at: now });
|
|
19484
|
+
this.#host.log("contact.request.held", { id: env.id });
|
|
19485
|
+
this.#host.receipt(sender.machine, env.id, "held");
|
|
19486
|
+
}
|
|
19487
|
+
async list() {
|
|
19488
|
+
const { realm } = this.#host.joined();
|
|
19489
|
+
const now = this.#host.now();
|
|
19490
|
+
const out = [];
|
|
19491
|
+
for (const row of this.#store.contactRequests()) {
|
|
19492
|
+
if (row.exp <= now)
|
|
19493
|
+
continue;
|
|
19494
|
+
const device = realm.device(row.machine);
|
|
19495
|
+
const profile = await this.#host.profile(row.person).catch(() => {
|
|
19496
|
+
return;
|
|
19497
|
+
});
|
|
19498
|
+
out.push({
|
|
19499
|
+
id: row.id,
|
|
19500
|
+
kind: "contact",
|
|
19501
|
+
from: {
|
|
19502
|
+
person: row.person,
|
|
19503
|
+
device: row.machine,
|
|
19504
|
+
label: device?.label ?? "gone",
|
|
19505
|
+
name: (profile?.name ?? row.person).slice(0, 200),
|
|
19506
|
+
...profile?.email !== undefined ? { email: profile.email.slice(0, 320) } : {},
|
|
19507
|
+
org: profile?.org ?? "none"
|
|
19508
|
+
},
|
|
19509
|
+
text: requestText(row.payload),
|
|
19510
|
+
at: row.at,
|
|
19511
|
+
exp: row.exp
|
|
19512
|
+
});
|
|
19513
|
+
}
|
|
19514
|
+
for (const row of this.#store.contactSuggestions())
|
|
19515
|
+
out.push({ id: row.id, kind: "suggestion", agent: row.session, email: row.email, at: row.at });
|
|
19516
|
+
return out;
|
|
19517
|
+
}
|
|
19518
|
+
holds(id) {
|
|
19519
|
+
return this.#store.contactRequest(id) !== undefined || this.#store.contactSuggestion(id) !== undefined;
|
|
19520
|
+
}
|
|
19521
|
+
answer(p) {
|
|
19522
|
+
const suggestion = this.#store.contactSuggestion(p.id);
|
|
19523
|
+
if (suggestion) {
|
|
19524
|
+
if (p.answer !== "decline")
|
|
19525
|
+
throw new MessagingError("invalid_request", "a suggestion is dismissed with decline; send the request itself with contact.request");
|
|
19526
|
+
this.#host.tx(() => this.#store.deleteContactSuggestion(p.id));
|
|
19527
|
+
return { answered: [p.id] };
|
|
19528
|
+
}
|
|
19529
|
+
const now = this.#host.now();
|
|
19530
|
+
const row = this.#store.contactRequest(p.id);
|
|
19531
|
+
if (!row || row.exp <= now)
|
|
19532
|
+
throw new MessagingError("not_found", "no request with that id is held here");
|
|
19533
|
+
if (p.answer !== "accept" && p.answer !== "decline" && p.answer !== "block") {
|
|
19534
|
+
throw new MessagingError("invalid_request", "a message request takes accept, decline or block");
|
|
19535
|
+
}
|
|
19536
|
+
const answer = p.answer;
|
|
19537
|
+
const rows = p.all ? this.#store.contactRequests(row.person).filter((held) => held.exp > now) : [row];
|
|
19538
|
+
this.#host.tx(() => {
|
|
19539
|
+
if (answer === "accept")
|
|
19540
|
+
this.#host.grants.setPerson(row.person, { chat: "open" });
|
|
19541
|
+
if (answer === "block")
|
|
19542
|
+
this.#host.grants.setPerson(row.person, { chat: "blocked" });
|
|
19543
|
+
for (const held of rows)
|
|
19544
|
+
this.#settle(held, answer === "accept" ? "accept" : "decline");
|
|
19545
|
+
});
|
|
19546
|
+
this.#host.log("contact.request.answered", { answer, count: rows.length });
|
|
19547
|
+
return { answered: rows.map((held) => held.id) };
|
|
19548
|
+
}
|
|
19549
|
+
granted(person) {
|
|
19550
|
+
const chat = this.#host.grants.personGrant(person)?.chat;
|
|
19551
|
+
if (chat !== "open" && chat !== "blocked")
|
|
19552
|
+
return;
|
|
19553
|
+
for (const row of this.#store.contactRequests(person))
|
|
19554
|
+
this.#settle(row, chat === "open" ? "accept" : "decline");
|
|
19555
|
+
}
|
|
19556
|
+
#settle(row, answer) {
|
|
19557
|
+
this.#store.deleteContactRequest(row.id);
|
|
19558
|
+
const { realm } = this.#host.joined();
|
|
19559
|
+
const sender = realm.device(row.machine);
|
|
19560
|
+
if (!sender || sender.person !== row.person)
|
|
19561
|
+
return;
|
|
19562
|
+
if (answer === "decline")
|
|
19563
|
+
return this.#host.receipt(sender.machine, row.id, "rejected", "declined");
|
|
19564
|
+
this.#host.chats.receive(row.envelope, sender, asLine(row.payload), this.#host.now());
|
|
19565
|
+
}
|
|
19566
|
+
suggest(session, email) {
|
|
19567
|
+
const { me } = this.#host.joined();
|
|
19568
|
+
const now = this.#host.now();
|
|
19569
|
+
if (this.#store.contactSuggestionCount() >= LIMITS.heldTotal)
|
|
19570
|
+
throw new MessagingError("rate_limited", "your person has too many suggestions waiting");
|
|
19571
|
+
const id = ulid2(now);
|
|
19572
|
+
this.#host.tx(() => {
|
|
19573
|
+
this.#store.putContactSuggestion({ id, session, email, at: now });
|
|
19574
|
+
this.#host.held({ id, person: me.person, agent: session, at: now });
|
|
19575
|
+
});
|
|
19576
|
+
return id;
|
|
19577
|
+
}
|
|
19578
|
+
sweep(now) {
|
|
19579
|
+
const requests = this.#store.deleteExpiredContactRequests(now);
|
|
19580
|
+
const suggestions = this.#store.deleteContactSuggestionsBefore(now - SUGGESTION_MS);
|
|
19581
|
+
if (requests + suggestions > 0)
|
|
19582
|
+
this.#host.log("contact.request.expired", { requests, suggestions });
|
|
19583
|
+
for (const [person, at] of this.#waiting)
|
|
19584
|
+
if (now - at >= LIMITS.ttlMs)
|
|
19585
|
+
this.#waiting.delete(person);
|
|
19586
|
+
}
|
|
19587
|
+
}
|
|
19588
|
+
function asLine(payload) {
|
|
19589
|
+
const { conv: _conv, ...line } = payload;
|
|
19590
|
+
return line;
|
|
19591
|
+
}
|
|
19592
|
+
function requestText(payload) {
|
|
19593
|
+
const text = payload.message.parts[0]?.text;
|
|
19594
|
+
return typeof text === "string" ? text.slice(0, LIMITS.requestChars) : "";
|
|
19595
|
+
}
|
|
19596
|
+
var DAY_MS3 = 86400000, SUGGESTION_MS;
|
|
19597
|
+
var init_requests = __esm(() => {
|
|
19598
|
+
init_dist();
|
|
19599
|
+
SUGGESTION_MS = LIMITS.ttlMs;
|
|
19600
|
+
});
|
|
19601
|
+
|
|
18971
19602
|
// packages/daemon/dist/consent/sentence.js
|
|
18972
19603
|
function grantSentence(p, me, now) {
|
|
18973
19604
|
const levels = levelsFrom(p, now);
|
|
@@ -19063,6 +19694,8 @@ class Daemon {
|
|
|
19063
19694
|
#chats;
|
|
19064
19695
|
#grants;
|
|
19065
19696
|
#holds;
|
|
19697
|
+
#cards;
|
|
19698
|
+
#requests;
|
|
19066
19699
|
#notices;
|
|
19067
19700
|
#jellyHost;
|
|
19068
19701
|
#jellyAuth;
|
|
@@ -19128,6 +19761,36 @@ class Daemon {
|
|
|
19128
19761
|
seal: (machine, payload) => this.#seal("message", machine, undefined, payload),
|
|
19129
19762
|
route: (env) => this.#route(env),
|
|
19130
19763
|
tx: (fn) => this.#tx(fn),
|
|
19764
|
+
log: (event, fields) => this.#log("info", event, fields),
|
|
19765
|
+
changed: (person) => this.#cards.changed(person),
|
|
19766
|
+
merged: (person) => this.#requests.granted(person)
|
|
19767
|
+
});
|
|
19768
|
+
this.#cards = new Cards({
|
|
19769
|
+
store,
|
|
19770
|
+
now: this.#now,
|
|
19771
|
+
joined: () => this.#joined(),
|
|
19772
|
+
relation: (person) => this.#grants.relation(person),
|
|
19773
|
+
personGrant: (person) => this.#grants.personGrant(person),
|
|
19774
|
+
prefetch: (person) => this.#grants.prefetch(person),
|
|
19775
|
+
fresh: async (persons, fn) => {
|
|
19776
|
+
await this.#withFresh(persons, () => this.#tx(fn), { first: false });
|
|
19777
|
+
},
|
|
19778
|
+
seal: (machine, payload) => this.#seal("message", machine, undefined, payload),
|
|
19779
|
+
route: (env) => this.#route(env),
|
|
19780
|
+
log: (event, fields) => this.#log("info", event, fields)
|
|
19781
|
+
});
|
|
19782
|
+
this.#requests = new Requests({
|
|
19783
|
+
store,
|
|
19784
|
+
grants: this.#grants,
|
|
19785
|
+
chats: this.#chats,
|
|
19786
|
+
now: this.#now,
|
|
19787
|
+
joined: () => this.#joined(),
|
|
19788
|
+
realmKind: () => this.#realm?.kind,
|
|
19789
|
+
fresh: (persons, fn) => this.#withFresh(persons, fn),
|
|
19790
|
+
profile: async (person) => await this.#joined().realm.profile(person),
|
|
19791
|
+
receipt: (machine, ref, stage, code) => this.#receipt(machine, ref, undefined, stage, code),
|
|
19792
|
+
tx: (fn) => this.#tx(fn),
|
|
19793
|
+
held: (row) => this.#notices.held(row),
|
|
19131
19794
|
log: (event, fields) => this.#log("info", event, fields)
|
|
19132
19795
|
});
|
|
19133
19796
|
this.#holds = new Holds({
|
|
@@ -19414,10 +20077,12 @@ class Daemon {
|
|
|
19414
20077
|
"session.register": (p, ctx) => this.#register(p, ctx),
|
|
19415
20078
|
"session.update": (p, ctx) => this.#updateSession(this.#bound(p, ctx)),
|
|
19416
20079
|
"session.unregister": (p, ctx) => this.#unregister(this.#bound(p, ctx)),
|
|
19417
|
-
"contacts.list": (p) =>
|
|
19418
|
-
entries
|
|
19419
|
-
conversations
|
|
19420
|
-
|
|
20080
|
+
"contacts.list": async (p) => {
|
|
20081
|
+
const entries = directory2(this.#view(this.#joined()), p, this.#now(), { wake: (s) => this.#wakes(s) });
|
|
20082
|
+
const conversations = this.#chats.list({ limit: 200 }).conversations;
|
|
20083
|
+
const dms = conversations.flatMap((c) => c.conversation.startsWith("dm:") ? [c.conversation.slice("dm:".length)] : []);
|
|
20084
|
+
return { entries, conversations, reach: await this.#reach([...entries.map((entry) => entry.person), ...dms]) };
|
|
20085
|
+
},
|
|
19421
20086
|
"msg.send": (p, ctx) => isLineTarget(p.to) ? this.#sendLine(this.#bound(p, ctx)) : this.#send(this.#bound(p, ctx)),
|
|
19422
20087
|
"item.read": (p, ctx) => this.#itemRead(this.#bound(p, ctx)),
|
|
19423
20088
|
"inbox.list": (p, ctx) => ({ items: this.#store.inbox(this.#bound(p, ctx).key, p.unreadOnly ?? false, p.limit ?? 50) }),
|
|
@@ -19478,8 +20143,15 @@ class Daemon {
|
|
|
19478
20143
|
this.#grants.check(p);
|
|
19479
20144
|
return { sentence: grantSentence(p, this.#joined().me.person, this.#now()) };
|
|
19480
20145
|
},
|
|
19481
|
-
"request.list": () => this.#holds.list(),
|
|
19482
|
-
"request.answer": (p) => this.#holds.answer(p),
|
|
20146
|
+
"request.list": async () => ({ requests: [...this.#holds.list().requests, ...await this.#requests.list()] }),
|
|
20147
|
+
"request.answer": (p) => this.#requests.holds(p.id) ? this.#requests.answer(p) : this.#holds.answer(p),
|
|
20148
|
+
"contact.request": (p) => this.#requests.request(p),
|
|
20149
|
+
"contact.request.preview": (p) => this.#requests.preview(p),
|
|
20150
|
+
"card.get": async (p) => {
|
|
20151
|
+
const { realm } = this.#joined();
|
|
20152
|
+
await realm.profile(p.person);
|
|
20153
|
+
return { sent: await this.#cards.get(p.person) };
|
|
20154
|
+
},
|
|
19483
20155
|
"daemon.health": () => this.#health(),
|
|
19484
20156
|
"agents.list": () => this.#agentsList(),
|
|
19485
20157
|
"agents.read": (p) => this.#agentsRead(p),
|
|
@@ -19489,6 +20161,16 @@ class Daemon {
|
|
|
19489
20161
|
"device.remove.preview": (p) => this.#removePreview(p)
|
|
19490
20162
|
};
|
|
19491
20163
|
}
|
|
20164
|
+
async#reach(persons) {
|
|
20165
|
+
const { realm, me } = this.#joined();
|
|
20166
|
+
const all = [...new Set([...persons, ...this.#cards.cardPersons()])].filter((person) => person !== me.person).sort();
|
|
20167
|
+
const out = [];
|
|
20168
|
+
for (const person of all) {
|
|
20169
|
+
const org = realm.kind === "roster" ? "same" : await realm.profile(person).then((profile) => profile.org, () => "none");
|
|
20170
|
+
out.push(this.#cards.reach(person, org === "same"));
|
|
20171
|
+
}
|
|
20172
|
+
return out;
|
|
20173
|
+
}
|
|
19492
20174
|
async#grantSubject(p) {
|
|
19493
20175
|
const { realm } = this.#joined();
|
|
19494
20176
|
if ("person" in p.subject)
|
|
@@ -19997,7 +20679,12 @@ class Daemon {
|
|
|
19997
20679
|
throw new MessagingError("invalid_request", "to is person/agent[@label], a person, or #<channel>");
|
|
19998
20680
|
}
|
|
19999
20681
|
const conversation = p.to.startsWith("#") ? `channel:${name}` : `dm:${name}`;
|
|
20682
|
+
if (p.request !== undefined && conversation !== `dm:${this.#joined().me.person}`) {
|
|
20683
|
+
throw new MessagingError("invalid_request", "request.contact goes only with a line to your own person");
|
|
20684
|
+
}
|
|
20000
20685
|
const line = await this.#withFresh(this.#linePersons(conversation), () => this.#chats.send({ conversation, text, ...p.replyTo !== undefined ? { thread: p.replyTo } : {}, ...p.idempotencyKey !== undefined ? { clientId: p.idempotencyKey } : {} }, session, p.key));
|
|
20686
|
+
if (p.request !== undefined)
|
|
20687
|
+
this.#requests.suggest(session, p.request.contact);
|
|
20001
20688
|
return { results: [{ to: p.to, envelopeId: line.id, messageId: line.id, contextId: line.id, stage: "accepted" }] };
|
|
20002
20689
|
}
|
|
20003
20690
|
#linePersons(conversation) {
|
|
@@ -20065,7 +20752,7 @@ class Daemon {
|
|
|
20065
20752
|
this.#freshBound.set(env.id, this.#staleBound);
|
|
20066
20753
|
this.#routed = true;
|
|
20067
20754
|
}
|
|
20068
|
-
async#withFresh(persons, fn) {
|
|
20755
|
+
async#withFresh(persons, fn, opts = {}) {
|
|
20069
20756
|
const realm = this.#realm;
|
|
20070
20757
|
if (realm?.kind !== "directory")
|
|
20071
20758
|
return fn();
|
|
@@ -20086,8 +20773,9 @@ class Daemon {
|
|
|
20086
20773
|
this.#staleBound = stale;
|
|
20087
20774
|
try {
|
|
20088
20775
|
const result = fn();
|
|
20089
|
-
|
|
20090
|
-
|
|
20776
|
+
if (opts.first !== false)
|
|
20777
|
+
for (const person of others)
|
|
20778
|
+
this.#sealedTo.add(person);
|
|
20091
20779
|
return result;
|
|
20092
20780
|
} finally {
|
|
20093
20781
|
this.#staleBound = undefined;
|
|
@@ -20197,7 +20885,9 @@ ${row.receipt ? "receipt" : "envelope"}`;
|
|
|
20197
20885
|
this.#releaseWaiters();
|
|
20198
20886
|
return;
|
|
20199
20887
|
}
|
|
20200
|
-
if (code ===
|
|
20888
|
+
if (code === "rate_limited" && row.env.class === "request")
|
|
20889
|
+
this.#nacked(row, code);
|
|
20890
|
+
else if (code === undefined || code === "rate_limited" || code === "internal") {
|
|
20201
20891
|
this.#retryLater(row);
|
|
20202
20892
|
waiting.add(lane);
|
|
20203
20893
|
} else
|
|
@@ -20529,6 +21219,14 @@ ${row.receipt ? "receipt" : "envelope"}`;
|
|
|
20529
21219
|
const data = payload.message.parts[0].data;
|
|
20530
21220
|
return this.#grants.receive(env, payload.conv, data.grants);
|
|
20531
21221
|
}
|
|
21222
|
+
if (payload.conv?.kind === "request")
|
|
21223
|
+
return this.#requests.receive(env, sender, payload, now);
|
|
21224
|
+
if (payload.conv?.kind === "card") {
|
|
21225
|
+
this.#requests.answered(sender.person);
|
|
21226
|
+
return this.#cards.receive(env, sender, payload);
|
|
21227
|
+
}
|
|
21228
|
+
if (toSession === undefined && sender.person !== this.#joined().me.person)
|
|
21229
|
+
this.#requests.answered(sender.person);
|
|
20532
21230
|
if (toSession === undefined)
|
|
20533
21231
|
return this.#chats.receive(env, sender, payload, now);
|
|
20534
21232
|
if (fromSession === undefined)
|
|
@@ -20730,6 +21428,9 @@ ${row.receipt ? "receipt" : "envelope"}`;
|
|
|
20730
21428
|
this.#sendStatus(row);
|
|
20731
21429
|
}
|
|
20732
21430
|
#onReceipt(env, sender, payload) {
|
|
21431
|
+
if (payload.stage === "rejected" && sender.person !== this.#joined().me.person && (payload.code === "refused" || payload.code === "request_required" || payload.code === "declined")) {
|
|
21432
|
+
this.#cards.refused(sender.person);
|
|
21433
|
+
}
|
|
20733
21434
|
const sent = this.#store.sent(payload.ref);
|
|
20734
21435
|
if (sent && this.#counterpart(sent.toMachine, sender))
|
|
20735
21436
|
return void this.#advance(sent, payload.stage, payload.code);
|
|
@@ -20907,6 +21608,7 @@ ${row.receipt ? "receipt" : "envelope"}`;
|
|
|
20907
21608
|
this.#advance(sent, "expired");
|
|
20908
21609
|
this.#chats.sweep(now);
|
|
20909
21610
|
this.#holds.sweep(now);
|
|
21611
|
+
this.#requests.sweep(now);
|
|
20910
21612
|
sessions = this.#store.expireKept(now - KEPT_MS);
|
|
20911
21613
|
for (const row of this.#store.interruptedTasks(now - INPUT_TIMEOUT_MS))
|
|
20912
21614
|
this.#timeOut(row, now);
|
|
@@ -21627,6 +22329,8 @@ var init_daemon = __esm(() => {
|
|
|
21627
22329
|
init_wake_bridge();
|
|
21628
22330
|
init_grants();
|
|
21629
22331
|
init_holds();
|
|
22332
|
+
init_cards2();
|
|
22333
|
+
init_requests();
|
|
21630
22334
|
init_sentence();
|
|
21631
22335
|
DEFAULT_TUNING = {
|
|
21632
22336
|
presenceMinMs: 2000,
|
|
@@ -22322,18 +23026,18 @@ var init_directory3 = __esm(() => {
|
|
|
22322
23026
|
});
|
|
22323
23027
|
|
|
22324
23028
|
// packages/relay/dist/directory/feed.js
|
|
22325
|
-
var
|
|
23029
|
+
var DAY_MS4 = 86400000, FEED_RETENTION_MS;
|
|
22326
23030
|
var init_feed = __esm(() => {
|
|
22327
23031
|
init_dist();
|
|
22328
23032
|
init_jelly4();
|
|
22329
|
-
FEED_RETENTION_MS = 90 *
|
|
23033
|
+
FEED_RETENTION_MS = 90 * DAY_MS4;
|
|
22330
23034
|
});
|
|
22331
23035
|
|
|
22332
23036
|
// packages/relay/dist/directory/pacing.js
|
|
22333
|
-
var
|
|
23037
|
+
var DAY_MS5 = 86400000, PAIR_WINDOW_MS;
|
|
22334
23038
|
var init_pacing = __esm(() => {
|
|
22335
23039
|
init_dist();
|
|
22336
|
-
PAIR_WINDOW_MS = 30 *
|
|
23040
|
+
PAIR_WINDOW_MS = 30 * DAY_MS5;
|
|
22337
23041
|
});
|
|
22338
23042
|
|
|
22339
23043
|
// packages/relay/dist/directory/module.js
|
|
@@ -23321,7 +24025,7 @@ var HINTS = {
|
|
|
23321
24025
|
unknown_address: "find a person, agent or channel with `messaging who` or `messaging people`.",
|
|
23322
24026
|
refused: "do not retry; the recipient's policy refuses this from you.",
|
|
23323
24027
|
declined: "do not retry: the recipient's person declined this request.",
|
|
23324
|
-
request_required: "send them a message request first
|
|
24028
|
+
request_required: "send them a message request first, with messaging contact <email> <text>: they are outside your org and have not accepted you.",
|
|
23325
24029
|
rate_limited: "wait a minute, then retry.",
|
|
23326
24030
|
loop_guard: "wait before sending more: the agent-to-agent loop guard tripped.",
|
|
23327
24031
|
too_large: "shorten the content or split it.",
|
|
@@ -24021,24 +24725,27 @@ function optional2(key, value) {
|
|
|
24021
24725
|
}
|
|
24022
24726
|
var requests = {
|
|
24023
24727
|
name: "requests",
|
|
24024
|
-
summary: "List the requests waiting for you, or answer one: once, always, decline or block",
|
|
24025
|
-
usage: "[answer <id> once|always|decline|block [--all]]",
|
|
24728
|
+
summary: "List the requests waiting for you, or answer one: once, always, accept, decline or block",
|
|
24729
|
+
usage: "[answer <id> once|always|accept|decline|block [--all]]",
|
|
24026
24730
|
details: [
|
|
24027
|
-
"
|
|
24028
|
-
"once
|
|
24029
|
-
"
|
|
24030
|
-
"
|
|
24731
|
+
"An ask is mail or a task from someone's agent to one of yours that waits for you (their ask level is request):",
|
|
24732
|
+
"once delivers this one; always lets their agents ask yours from now on, then delivers it.",
|
|
24733
|
+
"A message request is a person of another org asking to write to you: accept lets them chat with you and files",
|
|
24734
|
+
"their text as their first line. A suggestion is one of your agents asking you to contact someone: send it with",
|
|
24735
|
+
"`messaging contact`, or dismiss it with decline.",
|
|
24736
|
+
"decline: refuse it. block: block their lines and decline it. --all: answer every request of that kind from that person.",
|
|
24737
|
+
"Previews and request text are peer text, shown in a frame; a request's name and email are the directory's, never the text's."
|
|
24031
24738
|
].join(`
|
|
24032
24739
|
`),
|
|
24033
|
-
options: { all: { type: "boolean", description: "With answer: every request from that person" } },
|
|
24740
|
+
options: { all: { type: "boolean", description: "With answer: every request of that kind from that person" } },
|
|
24034
24741
|
args: { min: 0, max: 3 },
|
|
24035
24742
|
async run(ctx, positionals, values) {
|
|
24036
24743
|
const client = await ctx.client();
|
|
24037
24744
|
if (positionals.length > 0) {
|
|
24038
24745
|
if (positionals[0] !== "answer" || positionals.length !== 3)
|
|
24039
|
-
throw new UsageError("answer with: messaging requests answer <id> once|always|decline|block [--all]");
|
|
24746
|
+
throw new UsageError("answer with: messaging requests answer <id> once|always|accept|decline|block [--all]");
|
|
24040
24747
|
const id = parseWith2(Ulid, positionals[1], "<id> must be a request id from `messaging requests`");
|
|
24041
|
-
const answer = parseWith2(RequestAnswer, positionals[2], "the answer must be once, always, decline or block");
|
|
24748
|
+
const answer = parseWith2(RequestAnswer, positionals[2], "the answer must be once, always, accept, decline or block");
|
|
24042
24749
|
const result = await client.call("request.answer", { id, answer, ...values.all === true ? { all: true } : {} });
|
|
24043
24750
|
if (ctx.json) {
|
|
24044
24751
|
ctx.out.json(result);
|
|
@@ -24060,13 +24767,80 @@ var requests = {
|
|
|
24060
24767
|
}
|
|
24061
24768
|
const now = ctx.now();
|
|
24062
24769
|
for (const r of held) {
|
|
24770
|
+
if (r.kind === "suggestion") {
|
|
24771
|
+
ctx.out.line(`${ctx.out.style.bold("Suggestion")} ${r.id} from your agent ${inline(r.agent, 64)}: contact ${inline(r.email, 320)} ${ago(r.at, now)}`);
|
|
24772
|
+
continue;
|
|
24773
|
+
}
|
|
24774
|
+
if (r.kind === "contact") {
|
|
24775
|
+
const who = `${inline(r.from.name, 64)}${r.from.email !== undefined ? ` <${inline(r.from.email, 320)}>` : ""}`;
|
|
24776
|
+
const org = r.from.org === "same" ? "your org" : r.from.org === "other" ? "another org" : "no org";
|
|
24777
|
+
ctx.out.line(`${ctx.out.style.bold("Message request")} ${r.id} from ${who} (${org}) ${ago(r.at, now)}, expires ${iso2(r.exp)}`);
|
|
24778
|
+
ctx.out.lines(peerBlock(r.from.name, [r.text]));
|
|
24779
|
+
continue;
|
|
24780
|
+
}
|
|
24063
24781
|
ctx.out.line(`${ctx.out.style.bold(r.kind === "task" ? "Task request" : "Message")} ${r.id} from ${inline(r.from.address)} to ${inline(r.to, 64)} ${ago(r.at, now)}, expires ${iso2(r.exp)}`);
|
|
24064
24782
|
ctx.out.lines(peerBlock(r.from.address, [r.preview]));
|
|
24065
24783
|
}
|
|
24066
|
-
ctx.out.note("Answer with: messaging requests answer <id> once|always|decline|block [--all]");
|
|
24784
|
+
ctx.out.note("Answer with: messaging requests answer <id> once|always|accept|decline|block [--all]");
|
|
24067
24785
|
}
|
|
24068
24786
|
};
|
|
24069
|
-
var
|
|
24787
|
+
var contact = {
|
|
24788
|
+
name: "contact",
|
|
24789
|
+
summary: "Send a message request to a person of another org, by their exact email",
|
|
24790
|
+
usage: "<email> <text\u2026> [--dry-run] [--suggestion <id>]",
|
|
24791
|
+
details: [
|
|
24792
|
+
"A hosted realm only. The email must be exact; nothing tells an unknown email from one that does not accept requests.",
|
|
24793
|
+
`The text is all they see before they answer: at most ${LIMITS.requestChars} characters. One request waits per person.`,
|
|
24794
|
+
"--dry-run only checks that the email finds someone. --suggestion answers your agent's suggestion of this contact."
|
|
24795
|
+
].join(`
|
|
24796
|
+
`),
|
|
24797
|
+
options: {
|
|
24798
|
+
"dry-run": { type: "boolean", description: "Check the email, send nothing" },
|
|
24799
|
+
suggestion: { type: "string", description: "The suggestion this answers, from `messaging requests`" }
|
|
24800
|
+
},
|
|
24801
|
+
args: { min: 1, max: 200 },
|
|
24802
|
+
async run(ctx, positionals, values) {
|
|
24803
|
+
const email = parseWith2(Email, positionals[0].toLowerCase(), "<email> must be an exact email address");
|
|
24804
|
+
const client = await ctx.client();
|
|
24805
|
+
if (values["dry-run"] === true) {
|
|
24806
|
+
const { found } = await client.call("contact.request.preview", { email });
|
|
24807
|
+
if (ctx.json)
|
|
24808
|
+
return ctx.out.json({ found });
|
|
24809
|
+
ctx.out.line(found ? "That email finds a Messaging user who accepts requests." : "No Messaging user with that email accepts requests.");
|
|
24810
|
+
return;
|
|
24811
|
+
}
|
|
24812
|
+
const text = positionals.slice(1).join(" ");
|
|
24813
|
+
if (text.length === 0)
|
|
24814
|
+
throw new UsageError("say who you are and why you write: messaging contact <email> <text\u2026>");
|
|
24815
|
+
if (text.length > LIMITS.requestChars)
|
|
24816
|
+
throw new UsageError(`a message request is at most ${LIMITS.requestChars} characters`);
|
|
24817
|
+
const suggestion = stringValue(values, "suggestion");
|
|
24818
|
+
const result = await client.call("contact.request", {
|
|
24819
|
+
email,
|
|
24820
|
+
text,
|
|
24821
|
+
...suggestion !== undefined ? { suggestion: parseWith2(Ulid, suggestion, "--suggestion must be a suggestion id from `messaging requests`") } : {}
|
|
24822
|
+
});
|
|
24823
|
+
if (ctx.json)
|
|
24824
|
+
return ctx.out.json(result);
|
|
24825
|
+
ctx.out.line(`Sent. They see your request first; it is ${result.conversation} once they accept.`);
|
|
24826
|
+
}
|
|
24827
|
+
};
|
|
24828
|
+
var card = {
|
|
24829
|
+
name: "card",
|
|
24830
|
+
summary: "Ask a person for their contact card: what they let you send them",
|
|
24831
|
+
usage: "<who>",
|
|
24832
|
+
details: "At most once a day per person. Their card arrives later; `messaging who` and the tools show it.",
|
|
24833
|
+
args: { min: 1, max: 1 },
|
|
24834
|
+
async run(ctx, positionals) {
|
|
24835
|
+
const person = parseWith2(PersonId, positionals[0], "<who> is a person (a lowercase name or a p_\u2026 id)");
|
|
24836
|
+
const client = await ctx.client();
|
|
24837
|
+
const { sent } = await client.call("card.get", { person });
|
|
24838
|
+
if (ctx.json)
|
|
24839
|
+
return ctx.out.json({ sent });
|
|
24840
|
+
ctx.out.line(sent ? "Asked. Their card comes when one of their devices answers." : "You asked them in the last day; their card comes when they answer.");
|
|
24841
|
+
}
|
|
24842
|
+
};
|
|
24843
|
+
var CONSENT_COMMANDS = [grants, grant2, requests, contact, card];
|
|
24070
24844
|
|
|
24071
24845
|
// packages/daemon/dist/cli/commands/daemon.js
|
|
24072
24846
|
init_dist();
|
|
@@ -25401,7 +26175,7 @@ var revokeInvite = {
|
|
|
25401
26175
|
ctx.out.line(`Revoked invite ${inviteId}.`);
|
|
25402
26176
|
}
|
|
25403
26177
|
};
|
|
25404
|
-
var
|
|
26178
|
+
var card2 = {
|
|
25405
26179
|
name: "team card",
|
|
25406
26180
|
summary: "Print this team's public card (relay, team id, name)",
|
|
25407
26181
|
usage: "",
|
|
@@ -25550,7 +26324,7 @@ var prepare = {
|
|
|
25550
26324
|
ctx.out.note("Give this machine id to the relay operator for --allow-team-create, then run `messaging team create`.");
|
|
25551
26325
|
}
|
|
25552
26326
|
};
|
|
25553
|
-
var TEAM_COMMANDS = [prepare, create, invite, join5, members2, remove, role, revokeInvite,
|
|
26327
|
+
var TEAM_COMMANDS = [prepare, create, invite, join5, members2, remove, role, revokeInvite, card2, add, pair, claim2, pairCancel];
|
|
25554
26328
|
|
|
25555
26329
|
// packages/daemon/dist/cli/version.js
|
|
25556
26330
|
init_dist();
|
|
@@ -25580,7 +26354,7 @@ var SECTIONS = [
|
|
|
25580
26354
|
["Daemon", ["daemon start", "daemon stop", "status"]],
|
|
25581
26355
|
["Team", ["team prepare", "team create", "team card", "team invite", "team join", "team pair", "team add", "team claim", "team pair-cancel", "team members", "team remove", "team role", "team revoke-invite"]],
|
|
25582
26356
|
["Identity", ["identity show", "identity migrate", "identity rotate-enc"]],
|
|
25583
|
-
["People", ["people", "devices", "grants", "grant", "requests", "notices"]],
|
|
26357
|
+
["People", ["people", "devices", "grants", "grant", "requests", "contact", "card", "notices"]],
|
|
25584
26358
|
["Messages", ["who", "send", "inbox", "read", "message-status"]],
|
|
25585
26359
|
["Chats", ["chat list", "chat read", "chat post", "chat search"]],
|
|
25586
26360
|
["Tasks", ["tasks", "task", "cancel"]],
|