@wirecat/cli-core 0.536.0 → 0.538.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/CHANGELOG.md +41 -0
- package/dist/calendar/cli/answers.d.ts +143 -0
- package/dist/calendar/cli/answers.js +28 -1
- package/dist/calendar/cli/index.js +35 -0
- package/dist/calendar/prepare.d.ts +134 -0
- package/dist/calendar/prepare.js +179 -0
- package/dist/calendar/testing/answers.d.ts +2 -1
- package/dist/calendar/testing/answers.js +3 -2
- package/dist/entities/contact-context.d.ts +35 -0
- package/dist/entities/contact-context.js +3 -3
- package/dist/errors/reasons.d.ts +1 -1
- package/dist/errors/reasons.js +6 -0
- package/dist/mail/server-search.js +20 -4
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/oauth/index.d.ts +1 -1
- package/dist/oauth/index.js +1 -1
- package/dist/oauth/keeper.d.ts +9 -2
- package/dist/oauth/keeper.js +29 -15
- package/dist/services/person-context.d.ts +6 -1
- package/dist/services/person-context.js +9 -3
- package/dist/store/sqlite/evidence-findings.js +4 -2
- package/dist/store/sqlite/task-board.js +16 -0
- package/dist/store/sqlite/work-tasks.d.ts +4 -1
- package/dist/store/sqlite/work-tasks.js +6 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,47 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.538.0 — 01.11.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`events prepare [event]`, in the shared calendar commands: what the store holds for one meeting.** The event as
|
|
15
|
+
`events show` gives it, and for each attendee other than the owner their linked identities, latest one-to-one
|
|
16
|
+
messages and mail, their messages in groups, past events and recorded meetings with them, read in every account that
|
|
17
|
+
saw them. Without an event, the next meeting with other attendees. `meta.accounts` names what was read, and
|
|
18
|
+
`meta.complete: false` names a chat the store does not hold whole (`chat.not-read`) or meetings not yet indexed
|
|
19
|
+
(`meetings.index-pending`); `untrusted` marks all third-party text. It summarises nothing. `recordContext` is
|
|
20
|
+
`personContext` for a person already found, read in the accounts the caller names.
|
|
21
|
+
- **A task error names the tasks it is about**: `task.has-subtasks` gives `details.subtasks` (their keys) and
|
|
22
|
+
`task.is-subtask` gives `details.parent`, in the JSON a CLI prints. The keys were raised but not kept.
|
|
23
|
+
- **`parent:` in the project task query**: `parent:<key>` lists a task's subtasks, also by a key it had before it
|
|
24
|
+
moved to another project; `parent:none` and `parent:any`.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **Mail a server search stores first gets the owner's rule tags.** `searchMailServer` saved new mail without the
|
|
29
|
+
`tag:` labels and `mailRuleId` its rules give, and sync never reads mail whose place it already knows, so that mail
|
|
30
|
+
stayed untagged until `rules apply`. It now tags it as `syncMail` does, and writes reused or refused labels back into
|
|
31
|
+
the rules the same way.
|
|
32
|
+
- **A findings batch stays approvable after a sibling batch of the same project is decided.** Accepting checks only
|
|
33
|
+
the evidence and assignee the accepted findings cite. Before, every task a
|
|
34
|
+
decision created moved the project's row, and every other waiting batch of that project was refused as
|
|
35
|
+
`evidence-findings.stale`. A renamed project no longer refuses a batch either; the project must still exist.
|
|
36
|
+
Meeting findings are unchanged.
|
|
37
|
+
|
|
38
|
+
### Changed — may break callers
|
|
39
|
+
|
|
40
|
+
- **Moving a subtask to another project is refused** with `task.is-subtask` (details `parent`, its parent's key),
|
|
41
|
+
unless the same edit detaches it with `parent: null`. Before, the move dropped its parent without saying so.
|
|
42
|
+
|
|
43
|
+
## 0.537.0 — 01.11.2026
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- **`tokenKeeper.tokens({ refresh: true })`** (and `accessToken({ refresh: true })`): after a server refuses an
|
|
48
|
+
access token whose `expiresAt` still called it fresh (a revoked session), refresh under the shared lock anyway. A
|
|
49
|
+
different fresh token another process saved meanwhile is answered instead of a second refresh.
|
|
50
|
+
|
|
10
51
|
## 0.536.0 — 01.11.2026
|
|
11
52
|
|
|
12
53
|
### Fixed
|
|
@@ -249,6 +249,149 @@ export declare const calendarAttendee: v.StrictObjectSchema<{
|
|
|
249
249
|
readonly events: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
|
|
250
250
|
readonly untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
251
251
|
}, undefined>;
|
|
252
|
+
export declare const calendarEventPreparation: v.StrictObjectSchema<{
|
|
253
|
+
readonly event: v.StrictObjectSchema<{
|
|
254
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
255
|
+
readonly event_series_id: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
256
|
+
readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
257
|
+
readonly description: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
258
|
+
readonly location: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
259
|
+
readonly starts_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
260
|
+
readonly ends_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
261
|
+
readonly timezone: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
262
|
+
readonly origin: v.PicklistSchema<["auto", "owner", "calendar"], undefined>;
|
|
263
|
+
readonly status: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
264
|
+
readonly all_day: v.BooleanSchema<undefined>;
|
|
265
|
+
readonly source_deleted_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
266
|
+
readonly deleted_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
267
|
+
readonly created_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
268
|
+
readonly updated_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
269
|
+
readonly change: v.OptionalSchema<v.PicklistSchema<["new", "moved", "cancelled"], undefined>, undefined>;
|
|
270
|
+
readonly changed_at: v.OptionalSchema<v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>, undefined>;
|
|
271
|
+
readonly was: v.OptionalSchema<v.StrictObjectSchema<{
|
|
272
|
+
readonly starts_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
273
|
+
readonly ends_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
274
|
+
}, undefined>, undefined>;
|
|
275
|
+
readonly overlapping: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
276
|
+
readonly overlaps: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
277
|
+
readonly ref: v.StringSchema<undefined>;
|
|
278
|
+
readonly entries: v.ArraySchema<v.StrictObjectSchema<{
|
|
279
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
280
|
+
readonly calendar_id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
281
|
+
readonly external_id: v.StringSchema<undefined>;
|
|
282
|
+
readonly kind: v.StringSchema<undefined>;
|
|
283
|
+
readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
284
|
+
readonly status: v.StringSchema<undefined>;
|
|
285
|
+
readonly event_type: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
286
|
+
readonly own_response: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
287
|
+
readonly starts_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
288
|
+
readonly ends_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
289
|
+
readonly join_url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
290
|
+
readonly url: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
291
|
+
readonly deleted_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
292
|
+
}, undefined>, undefined>;
|
|
293
|
+
readonly attendees: v.ArraySchema<v.StrictObjectSchema<{
|
|
294
|
+
email: v.StringSchema<undefined>;
|
|
295
|
+
name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
296
|
+
response: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
297
|
+
organizer: v.BooleanSchema<undefined>;
|
|
298
|
+
optional: v.BooleanSchema<undefined>;
|
|
299
|
+
resource: v.BooleanSchema<undefined>;
|
|
300
|
+
self: v.BooleanSchema<undefined>;
|
|
301
|
+
/** `person:<id>` when the shared store knows the address. */
|
|
302
|
+
person: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
303
|
+
}, undefined>, undefined>;
|
|
304
|
+
readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
|
|
305
|
+
readonly file_id: v.StringSchema<undefined>;
|
|
306
|
+
readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
307
|
+
readonly document: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
308
|
+
}, undefined>, undefined>;
|
|
309
|
+
readonly history: v.ArraySchema<v.StrictObjectSchema<{
|
|
310
|
+
readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
311
|
+
readonly starts_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
312
|
+
readonly ends_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
313
|
+
readonly location: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
314
|
+
readonly status: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
315
|
+
readonly changed_at: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
316
|
+
}, undefined>, undefined>;
|
|
317
|
+
readonly untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
318
|
+
}, undefined>;
|
|
319
|
+
readonly people: v.ArraySchema<v.StrictObjectSchema<{
|
|
320
|
+
readonly email: v.StringSchema<undefined>;
|
|
321
|
+
readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
322
|
+
readonly response: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
323
|
+
readonly organizer: v.BooleanSchema<undefined>;
|
|
324
|
+
readonly optional: v.BooleanSchema<undefined>;
|
|
325
|
+
readonly person: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
326
|
+
readonly read: v.BooleanSchema<undefined>;
|
|
327
|
+
readonly identities: v.ArraySchema<v.StringSchema<undefined>, undefined>;
|
|
328
|
+
readonly recent: v.StrictObjectSchema<{
|
|
329
|
+
readonly direct: v.ArraySchema<v.StrictObjectSchema<{
|
|
330
|
+
readonly locator: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
331
|
+
readonly provider: v.StringSchema<undefined>;
|
|
332
|
+
readonly account: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
333
|
+
readonly chat_id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
334
|
+
readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
335
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
336
|
+
readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
337
|
+
readonly sender_id: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
|
|
338
|
+
readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
339
|
+
readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
|
|
340
|
+
readonly text: v.StringSchema<undefined>;
|
|
341
|
+
}, undefined>, undefined>;
|
|
342
|
+
readonly groups: v.ArraySchema<v.StrictObjectSchema<{
|
|
343
|
+
readonly locator: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
344
|
+
readonly provider: v.StringSchema<undefined>;
|
|
345
|
+
readonly account: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
346
|
+
readonly chat_id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
347
|
+
readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
348
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
349
|
+
readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
|
|
350
|
+
readonly sender_id: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
|
|
351
|
+
readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
352
|
+
readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
|
|
353
|
+
readonly text: v.StringSchema<undefined>;
|
|
354
|
+
}, undefined>, undefined>;
|
|
355
|
+
}, undefined>;
|
|
356
|
+
readonly past_events: v.ArraySchema<v.StrictObjectSchema<{
|
|
357
|
+
readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
358
|
+
readonly title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
359
|
+
readonly starts_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
360
|
+
readonly ends_at: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>, undefined>;
|
|
361
|
+
readonly all_day: v.BooleanSchema<undefined>;
|
|
362
|
+
readonly location: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
363
|
+
readonly status: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
364
|
+
}, undefined>, undefined>;
|
|
365
|
+
readonly meetings: v.ArraySchema<v.StrictObjectSchema<{
|
|
366
|
+
readonly reference: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
367
|
+
readonly cues: v.ArraySchema<v.StrictObjectSchema<{
|
|
368
|
+
readonly reference: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
369
|
+
readonly fingerprint: v.StringSchema<undefined>;
|
|
370
|
+
readonly transcript_id: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
371
|
+
readonly content_hash: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
372
|
+
readonly position: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
373
|
+
readonly start_ms: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
374
|
+
readonly end_ms: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
375
|
+
readonly speaker_participant_id: v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
|
|
376
|
+
readonly speaker_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
377
|
+
readonly text: v.StringSchema<undefined>;
|
|
378
|
+
readonly revision: v.PicklistSchema<["current", "superseded"], undefined>;
|
|
379
|
+
}, undefined>, undefined>;
|
|
380
|
+
readonly cue_coverage: v.StrictObjectSchema<{
|
|
381
|
+
readonly included: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
382
|
+
readonly omitted: v.NullableSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
|
|
383
|
+
readonly has_more: v.BooleanSchema<undefined>;
|
|
384
|
+
}, undefined>;
|
|
385
|
+
}, undefined>, undefined>;
|
|
386
|
+
readonly has_more: v.BooleanSchema<undefined>;
|
|
387
|
+
}, undefined>, undefined>;
|
|
388
|
+
readonly limits: v.StrictObjectSchema<{
|
|
389
|
+
readonly people: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
390
|
+
readonly messages: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
391
|
+
readonly bytes: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
392
|
+
}, undefined>;
|
|
393
|
+
readonly untrusted: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
394
|
+
}, undefined>;
|
|
252
395
|
export declare const calendarLinkedPerson: v.StrictObjectSchema<{
|
|
253
396
|
readonly person: v.StringSchema<undefined>;
|
|
254
397
|
readonly name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -2,7 +2,8 @@ import { annotate } from "@wirecat/cli-base/commands";
|
|
|
2
2
|
import { queryFieldsOf } from "@wirecat/cli-base/query";
|
|
3
3
|
import * as v from "valibot";
|
|
4
4
|
import { answerEntity } from "../../cli/answer-entity.js";
|
|
5
|
-
import {
|
|
5
|
+
import { contextEvent, contextMessage, meetingCue } from "../../entities/contact-context.js";
|
|
6
|
+
import { count, id, reference, text, time, untrusted } from "../../entities/fields.js";
|
|
6
7
|
import { event } from "../../meetings/index.js";
|
|
7
8
|
const nullableText = v.nullable(text);
|
|
8
9
|
const nullableTime = v.nullable(time);
|
|
@@ -180,6 +181,31 @@ export const calendarAttendee = v.strictObject({
|
|
|
180
181
|
events: v.optional(count),
|
|
181
182
|
untrusted,
|
|
182
183
|
});
|
|
184
|
+
/** An attendee of the prepared event other than the owner, with what the store holds of them; `read` false: nothing. */
|
|
185
|
+
const preparedPerson = v.strictObject({
|
|
186
|
+
email: text,
|
|
187
|
+
name: nullableText,
|
|
188
|
+
response: nullableText,
|
|
189
|
+
organizer: v.boolean(),
|
|
190
|
+
optional: v.boolean(),
|
|
191
|
+
person: nullableText,
|
|
192
|
+
read: v.boolean(),
|
|
193
|
+
identities: v.array(text),
|
|
194
|
+
recent: v.strictObject({ direct: v.array(contextMessage), groups: v.array(contextMessage) }),
|
|
195
|
+
past_events: v.array(contextEvent),
|
|
196
|
+
meetings: v.array(v.strictObject({
|
|
197
|
+
reference,
|
|
198
|
+
cues: v.array(meetingCue),
|
|
199
|
+
cue_coverage: v.strictObject({ included: count, omitted: v.nullable(count), has_more: v.boolean() }),
|
|
200
|
+
})),
|
|
201
|
+
has_more: v.boolean(),
|
|
202
|
+
});
|
|
203
|
+
export const calendarEventPreparation = v.strictObject({
|
|
204
|
+
event: calendarEvent,
|
|
205
|
+
people: v.array(preparedPerson),
|
|
206
|
+
limits: v.strictObject({ people: count, messages: count, bytes: count }),
|
|
207
|
+
untrusted,
|
|
208
|
+
});
|
|
183
209
|
export const calendarLinkedPerson = v.strictObject({ person: text, name: nullableText, identities: v.array(text) });
|
|
184
210
|
export const calendarLinkReview = v.strictObject({
|
|
185
211
|
id,
|
|
@@ -198,6 +224,7 @@ const ANSWERS = {
|
|
|
198
224
|
"store status": { name: "calendar_sync_progress", schema: calendarSyncProgress },
|
|
199
225
|
"events list": { name: "event" },
|
|
200
226
|
"events show": { name: "calendar_event", schema: calendarEvent },
|
|
227
|
+
"events prepare": { name: "calendar_event_preparation", schema: calendarEventPreparation },
|
|
201
228
|
"events create": { name: "calendar_event_write", schema: calendarEventWrite },
|
|
202
229
|
"events update": { name: "calendar_event_write", schema: calendarEventWrite },
|
|
203
230
|
"events delete": { name: "calendar_event_write", schema: calendarEventWrite },
|
|
@@ -8,8 +8,10 @@ import { queryTerms, withQuery } from "../../cli/list-query.js";
|
|
|
8
8
|
import { terminalAsker } from "../../cli/messenger/ask.js";
|
|
9
9
|
import { lineOf } from "../../cli/steps.js";
|
|
10
10
|
import { searchEvents } from "../../services/events-search.js";
|
|
11
|
+
import { dateWindow } from "../../services/moment.js";
|
|
11
12
|
import { linkPeople } from "../../services/people.js";
|
|
12
13
|
import { BUSY_QUERY, BUSY_QUERY_NOTE, busyAnswer, busyQuery, workingHoursOf } from "../busy.js";
|
|
14
|
+
import { eventPreparation, nextMeeting, PREPARE_MESSAGES } from "../prepare.js";
|
|
13
15
|
import { accountsMeta, CALENDAR_UNTRUSTED_FIELDS, calendarIn, calendarItem, EVENTS_QUERY, EVENTS_QUERY_NOTE, eventAnswer, eventIdOf, eventOf, eventsAnswer, eventsFilter, peopleAnswer, resolveAnswer, reviewsAnswer, sourceAccounts, weekFilter, } from "../reads.js";
|
|
14
16
|
import { REPEAT_HELP } from "../recurrence.js";
|
|
15
17
|
import { bothSearchAnswer, serverSearchAnswer } from "../server-search.js";
|
|
@@ -40,6 +42,12 @@ const integer = (text) => {
|
|
|
40
42
|
throw new InvalidArgumentError("expected a positive integer");
|
|
41
43
|
return Number(text);
|
|
42
44
|
};
|
|
45
|
+
const upTo = (most) => (text) => {
|
|
46
|
+
const value = integer(text);
|
|
47
|
+
if (value > most)
|
|
48
|
+
throw new InvalidArgumentError(`expected 1 to ${most}`);
|
|
49
|
+
return value;
|
|
50
|
+
};
|
|
43
51
|
const minutes = (text) => {
|
|
44
52
|
const value = integer(text);
|
|
45
53
|
if (value < 5 || value > 1440)
|
|
@@ -232,6 +240,33 @@ const addCalendarCommandsDefinition = (program, deps) => {
|
|
|
232
240
|
const shown = await eventAnswer(await eventOf(deps.store, deps.accountId, eventIdOf(reference)), personOfEmail);
|
|
233
241
|
print({ ...shown, untrusted: CALENDAR_UNTRUSTED_FIELDS }, command);
|
|
234
242
|
});
|
|
243
|
+
example(annotate(events.command("prepare"), { effects: LOCAL_READ, partial: true })
|
|
244
|
+
.description("before a meeting: the event, its attendees with their answer, and each one's latest messages, mail and " +
|
|
245
|
+
"meetings the store holds; the next meeting when no event is named")
|
|
246
|
+
.summary("what the store holds for one meeting and the people in it")
|
|
247
|
+
.argument("[event]", "the event's id, or event:<id>; the next meeting with other attendees when absent"), "events prepare --json", "events prepare event:42 --limit 3", "events prepare event:42 --since-time 2026-10-01")
|
|
248
|
+
.option("--limit <n>", `messages per person in each list, 1–50; ${PREPARE_MESSAGES} if not given`, upTo(50))
|
|
249
|
+
.option("--since-time <time>", "no message or meeting older than this ISO 8601 time, or 30d ago")
|
|
250
|
+
.action(async (reference, opts, command) => {
|
|
251
|
+
const window = dateWindow({
|
|
252
|
+
...(opts.sinceTime === undefined ? {} : { sinceTime: opts.sinceTime }),
|
|
253
|
+
zone: zone(),
|
|
254
|
+
now: now(),
|
|
255
|
+
});
|
|
256
|
+
const details = reference === undefined
|
|
257
|
+
? await nextMeeting(deps.store, deps.accountId, now())
|
|
258
|
+
: await eventOf(deps.store, deps.accountId, eventIdOf(reference));
|
|
259
|
+
const { meta, ...prepared } = await eventPreparation(details, {
|
|
260
|
+
store: deps.store,
|
|
261
|
+
messages: messages("events prepare"),
|
|
262
|
+
account: { id: deps.accountId, key: deps.account },
|
|
263
|
+
personOf: personOfEmail ?? (async () => null),
|
|
264
|
+
now: now(),
|
|
265
|
+
...(opts.limit === undefined ? {} : { limit: opts.limit }),
|
|
266
|
+
...(window.since === undefined ? {} : { since: window.since }),
|
|
267
|
+
});
|
|
268
|
+
print({ ...prepared, meta: { ...meta, ...window.meta } }, command);
|
|
269
|
+
});
|
|
235
270
|
const searches = annotate(program.command("search").description("find stored things by text: events"), {
|
|
236
271
|
effects: GROUP,
|
|
237
272
|
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { EventDetails, EventStore } from "../meetings/index.js";
|
|
2
|
+
import { type ContextEvent, type ContextMessage } from "../services/person-context.js";
|
|
3
|
+
import { type PersonMeetingContextItem } from "../services/person-meeting-context.js";
|
|
4
|
+
import type { MessageStore } from "../store/store.js";
|
|
5
|
+
import { type PersonOfEmail, type ReadAccount } from "./reads.js";
|
|
6
|
+
export declare const PREPARE_MESSAGES = 5;
|
|
7
|
+
/** Attendees past this many are listed without their messages: a meeting of fifty would not fit any budget. */
|
|
8
|
+
export declare const PREPARE_PEOPLE = 10;
|
|
9
|
+
export declare const PREPARE_BYTES: number;
|
|
10
|
+
/** The account's next meeting that has not started yet, by the rule of `isMeeting`. */
|
|
11
|
+
export declare const nextMeeting: (store: EventStore, accountId: number, now: number) => Promise<EventDetails>;
|
|
12
|
+
export interface PreparationOptions {
|
|
13
|
+
store: EventStore;
|
|
14
|
+
messages: MessageStore;
|
|
15
|
+
account: ReadAccount;
|
|
16
|
+
personOf: PersonOfEmail;
|
|
17
|
+
now: number;
|
|
18
|
+
/** Messages per person, each list. */
|
|
19
|
+
limit?: number;
|
|
20
|
+
/** ms: no message or meeting older. */
|
|
21
|
+
since?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The stored evidence for one meeting: the event, and for each attendee other than the owner, their linked identities,
|
|
25
|
+
* their latest messages and mail, the events and recorded meetings they shared with the owner. Reads the store only,
|
|
26
|
+
* every account that saw each person (WC-ACCT-9), and writes nothing; the brief is the agent's.
|
|
27
|
+
*/
|
|
28
|
+
export declare const eventPreparation: (details: EventDetails, options: PreparationOptions) => Promise<{
|
|
29
|
+
event: {
|
|
30
|
+
id: string;
|
|
31
|
+
event_series_id: string | null;
|
|
32
|
+
title: string | null;
|
|
33
|
+
description: string | null;
|
|
34
|
+
location: string | null;
|
|
35
|
+
starts_at: string | null;
|
|
36
|
+
ends_at: string | null;
|
|
37
|
+
timezone: string | null;
|
|
38
|
+
origin: "auto" | "calendar" | "owner";
|
|
39
|
+
status: string | null;
|
|
40
|
+
all_day: boolean;
|
|
41
|
+
source_deleted_at: string | null;
|
|
42
|
+
deleted_at: string | null;
|
|
43
|
+
created_at: string;
|
|
44
|
+
updated_at: string;
|
|
45
|
+
untrusted?: string[] | undefined;
|
|
46
|
+
change?: "cancelled" | "moved" | "new" | undefined;
|
|
47
|
+
changed_at?: string | null | undefined;
|
|
48
|
+
was?: {
|
|
49
|
+
starts_at: string | null;
|
|
50
|
+
ends_at: string | null;
|
|
51
|
+
} | undefined;
|
|
52
|
+
overlapping?: boolean | undefined;
|
|
53
|
+
overlaps?: string[] | undefined;
|
|
54
|
+
ref: string;
|
|
55
|
+
entries: {
|
|
56
|
+
id: string;
|
|
57
|
+
calendar_id: string;
|
|
58
|
+
external_id: string;
|
|
59
|
+
kind: import("../meetings/event-store.js").EntryKind;
|
|
60
|
+
title: string | null;
|
|
61
|
+
status: import("../meetings/event-store.js").EntryStatus;
|
|
62
|
+
event_type: import("../meetings/event-store.js").EntryEventType | null;
|
|
63
|
+
own_response: import("../meetings/event-store.js").ParticipantResponse | null;
|
|
64
|
+
starts_at: string | null;
|
|
65
|
+
ends_at: string | null;
|
|
66
|
+
join_url: string | null;
|
|
67
|
+
url: string | null;
|
|
68
|
+
deleted_at: string | null;
|
|
69
|
+
}[];
|
|
70
|
+
attendees: {
|
|
71
|
+
email: string;
|
|
72
|
+
name: string | null;
|
|
73
|
+
response: import("../meetings/event-store.js").ParticipantResponse | null;
|
|
74
|
+
organizer: boolean;
|
|
75
|
+
optional: boolean;
|
|
76
|
+
resource: boolean;
|
|
77
|
+
self: boolean;
|
|
78
|
+
person: string | null;
|
|
79
|
+
}[];
|
|
80
|
+
attachments: {
|
|
81
|
+
file_id: string;
|
|
82
|
+
title: string | null;
|
|
83
|
+
document: string | null;
|
|
84
|
+
}[];
|
|
85
|
+
history: {
|
|
86
|
+
title: string | null;
|
|
87
|
+
starts_at: string | null;
|
|
88
|
+
ends_at: string | null;
|
|
89
|
+
location: string | null;
|
|
90
|
+
status: string | null;
|
|
91
|
+
changed_at: string;
|
|
92
|
+
}[];
|
|
93
|
+
};
|
|
94
|
+
people: {
|
|
95
|
+
email: string;
|
|
96
|
+
name: string | null;
|
|
97
|
+
response: string | null;
|
|
98
|
+
organizer: boolean;
|
|
99
|
+
optional: boolean;
|
|
100
|
+
person: string | null;
|
|
101
|
+
read: boolean;
|
|
102
|
+
identities: string[];
|
|
103
|
+
recent: {
|
|
104
|
+
direct: ContextMessage[];
|
|
105
|
+
groups: ContextMessage[];
|
|
106
|
+
};
|
|
107
|
+
pastEvents: ContextEvent[];
|
|
108
|
+
meetings: PersonMeetingContextItem[];
|
|
109
|
+
hasMore: boolean;
|
|
110
|
+
}[];
|
|
111
|
+
limits: {
|
|
112
|
+
people: number;
|
|
113
|
+
messages: number;
|
|
114
|
+
bytes: number;
|
|
115
|
+
};
|
|
116
|
+
untrusted: string[];
|
|
117
|
+
meta: Pick<{
|
|
118
|
+
run_id: string;
|
|
119
|
+
duration_ms: number;
|
|
120
|
+
accounts?: string[] | undefined;
|
|
121
|
+
complete: boolean;
|
|
122
|
+
issues?: {
|
|
123
|
+
code: "authentication_error" | "cancelled" | "configuration_error" | "confirmation_required" | "invalid_response" | "network_error" | "not_found" | "outcome_unknown" | "permission_error" | "provider_error" | "provider_unavailable" | "rate_limited" | "timeout" | "validation_error";
|
|
124
|
+
reason?: string | undefined;
|
|
125
|
+
source: string;
|
|
126
|
+
}[] | undefined;
|
|
127
|
+
truncated?: boolean | undefined;
|
|
128
|
+
timezone?: string | undefined;
|
|
129
|
+
window?: {
|
|
130
|
+
since?: string | undefined;
|
|
131
|
+
until?: string | undefined;
|
|
132
|
+
} | undefined;
|
|
133
|
+
}, "accounts" | "complete" | "issues" | "truncated">;
|
|
134
|
+
}>;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { parseMeetingReference } from "../meetings/index.js";
|
|
3
|
+
import { readsOf, recordContext } from "../services/person-context.js";
|
|
4
|
+
import { personMeetingContext } from "../services/person-meeting-context.js";
|
|
5
|
+
import { accountsMeta, CALENDAR_UNTRUSTED_FIELDS, eventAnswer, eventOf, } from "./reads.js";
|
|
6
|
+
export const PREPARE_MESSAGES = 5;
|
|
7
|
+
/** Attendees past this many are listed without their messages: a meeting of fifty would not fit any budget. */
|
|
8
|
+
export const PREPARE_PEOPLE = 10;
|
|
9
|
+
export const PREPARE_BYTES = 128 * 1024;
|
|
10
|
+
const PERSON_BYTES = 24 * 1024;
|
|
11
|
+
const MEETINGS = 2;
|
|
12
|
+
const CUES = 5;
|
|
13
|
+
/** How many upcoming events the next-meeting rule looks at before it gives up. */
|
|
14
|
+
const NEXT_SCAN = 200;
|
|
15
|
+
const NEXT_PAGE = 50;
|
|
16
|
+
const nameOf = ({ provider, account }) => `${provider}:${account}`;
|
|
17
|
+
const size = (value) => Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
18
|
+
/**
|
|
19
|
+
* What makes an event the next meeting: timed, not cancelled, not declined by the owner, an ordinary event (no focus
|
|
20
|
+
* time, out-of-office or working location), and someone other than the owner and the rooms invited.
|
|
21
|
+
*/
|
|
22
|
+
const isMeeting = (details) => {
|
|
23
|
+
if (details.event.allDay || details.event.status === "cancelled")
|
|
24
|
+
return false;
|
|
25
|
+
const live = details.entries.filter((entry) => entry.deletedAt === null && entry.status !== "cancelled");
|
|
26
|
+
if (!live.length || live.every((entry) => entry.ownResponse === "declined"))
|
|
27
|
+
return false;
|
|
28
|
+
if (live.every((entry) => entry.eventType !== null && entry.eventType !== "default"))
|
|
29
|
+
return false;
|
|
30
|
+
return details.participants.some((one) => !one.self && !one.resource);
|
|
31
|
+
};
|
|
32
|
+
/** The account's next meeting that has not started yet, by the rule of `isMeeting`. */
|
|
33
|
+
export const nextMeeting = async (store, accountId, now) => {
|
|
34
|
+
for (let offset = 0; offset < NEXT_SCAN; offset += NEXT_PAGE) {
|
|
35
|
+
const page = await store.events({ accountId, startsFrom: now, limit: NEXT_PAGE, offset });
|
|
36
|
+
for (const one of page) {
|
|
37
|
+
if (one.allDay || one.status === "cancelled")
|
|
38
|
+
continue;
|
|
39
|
+
const details = await eventOf(store, accountId, one.id).catch(() => undefined);
|
|
40
|
+
if (details && isMeeting(details))
|
|
41
|
+
return details;
|
|
42
|
+
}
|
|
43
|
+
if (page.length < NEXT_PAGE)
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
throw new CliError("not_found", "no upcoming meeting with other attendees in the store; run sync, or name an event", {
|
|
47
|
+
reason: "calendar.no-upcoming",
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
const UNTRUSTED = [
|
|
51
|
+
...CALENDAR_UNTRUSTED_FIELDS.filter((field) => !["candidates.title", "entry.title", "ended.title"].includes(field)).map((field) => `event.${field}`),
|
|
52
|
+
"people.name",
|
|
53
|
+
...["direct", "groups"].flatMap((list) => ["chat_title", "sender_name", "text"].map((field) => `people.recent.${list}.${field}`)),
|
|
54
|
+
"people.past_events.title",
|
|
55
|
+
"people.past_events.location",
|
|
56
|
+
"people.meetings.cues.speaker_name",
|
|
57
|
+
"people.meetings.cues.text",
|
|
58
|
+
];
|
|
59
|
+
/**
|
|
60
|
+
* The stored evidence for one meeting: the event, and for each attendee other than the owner, their linked identities,
|
|
61
|
+
* their latest messages and mail, the events and recorded meetings they shared with the owner. Reads the store only,
|
|
62
|
+
* every account that saw each person (WC-ACCT-9), and writes nothing; the brief is the agent's.
|
|
63
|
+
*/
|
|
64
|
+
export const eventPreparation = async (details, options) => {
|
|
65
|
+
const { messages: store, account, personOf, now, since } = options;
|
|
66
|
+
const limit = options.limit ?? PREPARE_MESSAGES;
|
|
67
|
+
const shown = await eventAnswer(details, personOf);
|
|
68
|
+
const others = shown.attendees
|
|
69
|
+
.filter((one) => !one.self && !one.resource)
|
|
70
|
+
.sort((a, b) => Number(b.organizer) - Number(a.organizer));
|
|
71
|
+
const read = new Map([[nameOf(account.key), account.key]]);
|
|
72
|
+
const issues = [];
|
|
73
|
+
const stored = await store.accounts.storedAccounts();
|
|
74
|
+
let left = PREPARE_BYTES - size(shown);
|
|
75
|
+
let truncated = others.length > PREPARE_PEOPLE;
|
|
76
|
+
let pending = 0;
|
|
77
|
+
const people = [];
|
|
78
|
+
for (const [index, attendee] of others.entries()) {
|
|
79
|
+
const base = {
|
|
80
|
+
email: attendee.email,
|
|
81
|
+
name: attendee.name,
|
|
82
|
+
response: attendee.response,
|
|
83
|
+
organizer: attendee.organizer,
|
|
84
|
+
optional: attendee.optional,
|
|
85
|
+
person: attendee.person,
|
|
86
|
+
};
|
|
87
|
+
const record = attendee.person ? await store.people.personOf({ provider: "email", id: attendee.email }) : undefined;
|
|
88
|
+
const nothing = {
|
|
89
|
+
...base,
|
|
90
|
+
read: false,
|
|
91
|
+
identities: record ? record.identities.map(({ provider, id }) => `${provider}:${id}`) : [],
|
|
92
|
+
recent: { direct: [], groups: [] },
|
|
93
|
+
pastEvents: [],
|
|
94
|
+
meetings: [],
|
|
95
|
+
hasMore: false,
|
|
96
|
+
};
|
|
97
|
+
if (!record || index >= PREPARE_PEOPLE || left < 2048) {
|
|
98
|
+
if (record && index < PREPARE_PEOPLE)
|
|
99
|
+
truncated = true;
|
|
100
|
+
people.push(nothing);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const reads = readsOf(record, account.key);
|
|
104
|
+
for (const { key } of reads)
|
|
105
|
+
read.set(nameOf(key), key);
|
|
106
|
+
const context = await recordContext(store, record, reads, {
|
|
107
|
+
messages: limit,
|
|
108
|
+
bytes: Math.min(left, PERSON_BYTES),
|
|
109
|
+
now,
|
|
110
|
+
...(since === undefined ? {} : { since }),
|
|
111
|
+
});
|
|
112
|
+
for (const chat of context.notRead)
|
|
113
|
+
issues.push({
|
|
114
|
+
code: "not_found",
|
|
115
|
+
reason: "chat.not-read",
|
|
116
|
+
source: `${chat.provider}:${chat.account}/${chat.chatId}`,
|
|
117
|
+
});
|
|
118
|
+
const meetings = await meetingsWith(store, record.uid, stored, since);
|
|
119
|
+
pending = Math.max(pending, meetings.pending);
|
|
120
|
+
for (const id of meetings.accounts) {
|
|
121
|
+
const one = stored.find((held) => held.id === id);
|
|
122
|
+
if (one)
|
|
123
|
+
read.set(nameOf(one), { provider: one.provider, account: one.account });
|
|
124
|
+
}
|
|
125
|
+
const entry = {
|
|
126
|
+
...nothing,
|
|
127
|
+
read: true,
|
|
128
|
+
recent: context.recent,
|
|
129
|
+
pastEvents: context.events.recent.filter((one) => one.ref !== shown.ref).slice(0, limit),
|
|
130
|
+
meetings: meetings.items,
|
|
131
|
+
hasMore: context.hasMore || meetings.hasMore,
|
|
132
|
+
};
|
|
133
|
+
left -= size(entry);
|
|
134
|
+
people.push(entry);
|
|
135
|
+
}
|
|
136
|
+
if (pending > 0)
|
|
137
|
+
issues.push({ code: "not_found", reason: "meetings.index-pending", source: "meetings" });
|
|
138
|
+
const calendar = await accountsMeta(options.store, [account], [], now);
|
|
139
|
+
const all = [...(calendar.issues ?? []), ...issues];
|
|
140
|
+
const meta = {
|
|
141
|
+
accounts: [...read.keys()],
|
|
142
|
+
complete: all.length === 0,
|
|
143
|
+
...(all.length ? { issues: all } : {}),
|
|
144
|
+
...(truncated || left < 0 ? { truncated: true } : {}),
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
event: shown,
|
|
148
|
+
people,
|
|
149
|
+
limits: { people: PREPARE_PEOPLE, messages: limit, bytes: PREPARE_BYTES },
|
|
150
|
+
untrusted: UNTRUSTED,
|
|
151
|
+
meta,
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
/** Their latest recorded meetings in every stored account that saw them; none is not an error. */
|
|
155
|
+
const meetingsWith = async (store, uid, stored, since) => {
|
|
156
|
+
const none = { items: [], hasMore: false, accounts: [], pending: 0 };
|
|
157
|
+
if (!stored.length)
|
|
158
|
+
return none;
|
|
159
|
+
try {
|
|
160
|
+
const found = await personMeetingContext(store, `person:${uid}`, {
|
|
161
|
+
accountIds: stored.slice(0, 1000).map(({ id }) => id),
|
|
162
|
+
meetings: MEETINGS,
|
|
163
|
+
cues: CUES,
|
|
164
|
+
bytes: PERSON_BYTES,
|
|
165
|
+
...(since === undefined ? {} : { since }),
|
|
166
|
+
});
|
|
167
|
+
return {
|
|
168
|
+
items: found.items,
|
|
169
|
+
hasMore: found.coverage.truncatedBy !== null || found.coverage.scanLimitReached,
|
|
170
|
+
accounts: [...new Set(found.items.map((item) => parseMeetingReference(item.reference).accountId))],
|
|
171
|
+
pending: found.coverage.pending,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
if (error instanceof CliError && error.code === "not_found")
|
|
176
|
+
return none;
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
/**
|
|
3
3
|
* Parses what a shared calendar command handed its host against the command's entity, as a host prints it: keys in
|
|
4
|
-
* snake_case, a list's items one by one, a search's hits each with the search's `untrusted`, and no `next`.
|
|
4
|
+
* snake_case, a list's items one by one, a search's hits each with the search's `untrusted`, and no `next`. A `meta`
|
|
5
|
+
* goes into the host's envelope, never into the item.
|
|
5
6
|
*/
|
|
6
7
|
export declare const checkCalendarAnswer: (root: Command, argv: readonly string[], value: unknown) => void;
|
|
@@ -13,14 +13,15 @@ const commandAt = (root, argv) => {
|
|
|
13
13
|
};
|
|
14
14
|
/**
|
|
15
15
|
* Parses what a shared calendar command handed its host against the command's entity, as a host prints it: keys in
|
|
16
|
-
* snake_case, a list's items one by one, a search's hits each with the search's `untrusted`, and no `next`.
|
|
16
|
+
* snake_case, a list's items one by one, a search's hits each with the search's `untrusted`, and no `next`. A `meta`
|
|
17
|
+
* goes into the host's envelope, never into the item.
|
|
17
18
|
*/
|
|
18
19
|
export const checkCalendarAnswer = (root, argv, value) => {
|
|
19
20
|
const command = commandAt(root, argv);
|
|
20
21
|
const schema = answerEntitySchema(command);
|
|
21
22
|
if (!schema)
|
|
22
23
|
throw new Error(`${argv.join(" ")} declares no answer entity`);
|
|
23
|
-
const { next: _next, ...body } = snakeKeys(value);
|
|
24
|
+
const { next: _next, meta: _meta, ...body } = snakeKeys(value);
|
|
24
25
|
const marker = body.untrusted
|
|
25
26
|
?.filter((field) => field.startsWith("hits."))
|
|
26
27
|
.map((field) => field.slice("hits.".length));
|