@wirecat/cli-core 0.543.0 → 0.545.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/cli/config-command.js +32 -2
  3. package/dist/cli/messenger/archive-commands.js +20 -2
  4. package/dist/cli/messenger/backfill-command.js +13 -2
  5. package/dist/cli/messenger/context.js +3 -0
  6. package/dist/cli/messenger/history-gaps.d.ts +12 -0
  7. package/dist/cli/messenger/history-gaps.js +41 -0
  8. package/dist/cli/messenger/messages-command.js +7 -3
  9. package/dist/cli/messenger/messages-search-command.js +5 -1
  10. package/dist/cli/messenger/search-answers.d.ts +3 -2
  11. package/dist/cli/messenger/search-answers.js +12 -5
  12. package/dist/cli/messenger/search-command.js +3 -1
  13. package/dist/cli/messenger/stored.d.ts +6 -1
  14. package/dist/cli/messenger/stored.js +38 -2
  15. package/dist/cli/program.js +22 -0
  16. package/dist/cli/settings.d.ts +9 -0
  17. package/dist/cli/settings.js +20 -0
  18. package/dist/cli/shared-command-exports.d.ts +24 -0
  19. package/dist/cli/skill-command.d.ts +2 -0
  20. package/dist/cli/skill-command.js +10 -0
  21. package/dist/cli/skill-refresh.d.ts +18 -0
  22. package/dist/cli/skill-refresh.js +82 -0
  23. package/dist/cli/steps.js +1 -0
  24. package/dist/entities/index.d.ts +1 -0
  25. package/dist/entities/message-transfer.d.ts +4 -0
  26. package/dist/entities/message-transfer.js +2 -0
  27. package/dist/services/history-limits.d.ts +45 -0
  28. package/dist/services/history-limits.js +50 -0
  29. package/dist/store/chats.d.ts +10 -0
  30. package/dist/store/chats.js +25 -0
  31. package/dist/store/sqlite/chats.js +2 -1
  32. package/dist/store/sqlite/history-skips.d.ts +24 -0
  33. package/dist/store/sqlite/history-skips.js +95 -0
  34. package/dist/store/store.d.ts +1 -0
  35. package/package.json +1 -1
  36. package/parity.json +4 -0
@@ -7,6 +7,7 @@ import { DEFAULT_RERANK_MODEL, RERANK_MODELS } from "../embeddings/models.js";
7
7
  import { envName, readOnlyLockOf } from "../ports/app.js";
8
8
  import { KEEP_SUCCESSFUL_RUNS_FOR_DAYS, RUNS_SIZE_LIMIT_MB } from "../runs/run.js";
9
9
  import { assertOverridesTighten, CEILING_KEY, COMMAND_PATH_KEY, LEVELS, layerPermissions, permissionOverrides, RESOURCE_KEY, RESOURCES, } from "../sends/permissions.js";
10
+ import { DEFAULT_HISTORY_LIMITS, HISTORY_MAX_DAYS } from "../services/history-limits.js";
10
11
  import { storePath } from "../store/path.js";
11
12
  import { sharedConfigPath } from "../store/shared-config.js";
12
13
  import { checkConfigText, issueMessage, readConfigFile, readConfigText, savedPermissionKeys, schemaIssues, settingPathOf, withoutPath, } from "./config-issues.js";
@@ -34,6 +35,8 @@ export const count = v.pipe(v.number(wholeNumber), v.integer(wholeNumber), v.min
34
35
  const zeroOrMore = plain("has to be a whole number, 0 or more");
35
36
  const countOrZero = v.pipe(v.number(zeroOrMore), v.integer(zeroOrMore), v.minValue(0, zeroOrMore));
36
37
  export const flag = v.boolean(plain("has to be true or false"));
38
+ const historyDaysMessage = plain(`has to be a whole number of days, 1 to ${HISTORY_MAX_DAYS}`);
39
+ const historyDays = v.pipe(v.number(historyDaysMessage), v.integer(historyDaysMessage), v.minValue(1, historyDaysMessage), v.maxValue(HISTORY_MAX_DAYS, historyDaysMessage));
37
40
  /** `record_input`: true, false, or "failed" — the words typed kept only in a run that failed. */
38
41
  export const recordInputSetting = v.union([v.boolean(), v.literal("failed")], plain('has to be true, false or "failed"'));
39
42
  const wholeOrZero = (text, name) => {
@@ -71,6 +74,9 @@ const SHARED_PROFILE_ENTRIES = {
71
74
  transcribe_with: v.optional(v.picklist(["auto", "messenger", "local"], plain("has to be auto, messenger or local"))),
72
75
  speech_model: v.optional(v.string(plain("has to be a model id from `models list`, in quotes"))),
73
76
  search_catch_up: v.optional(flag),
77
+ history_dialog_days: v.optional(historyDays),
78
+ history_group_days: v.optional(historyDays),
79
+ history_group_members_max: v.optional(count),
74
80
  picture_search: v.optional(flag),
75
81
  search_reranker: v.optional(v.picklist([...RERANK_MODELS.map((model) => model.id), "off"], plain(`has to be off or a reranker id: ${RERANK_MODELS.map((model) => model.id).join(", ")}`))),
76
82
  ...AI_ENTRIES,
@@ -91,6 +97,9 @@ export const MESSENGER_ONLY_SETTINGS = [
91
97
  "sends_per_hour",
92
98
  "transcribe_with",
93
99
  "search_catch_up",
100
+ "history_dialog_days",
101
+ "history_group_days",
102
+ "history_group_members_max",
94
103
  "read_other_bots",
95
104
  ];
96
105
  /** One program, one version: whether to look for a newer one is not a per-profile matter. */
@@ -255,6 +264,9 @@ export const settingsFor = (app, extension = {}) => {
255
264
  const color = fromLayers("color", undefined);
256
265
  const senderColors = fromLayers("sender_colors", false);
257
266
  const searchCatchUp = fromLayers("search_catch_up", false);
267
+ const historyDialogDays = fromLayers("history_dialog_days", DEFAULT_HISTORY_LIMITS.dialogDays);
268
+ const historyGroupDays = fromLayers("history_group_days", DEFAULT_HISTORY_LIMITS.groupDays);
269
+ const historyGroupMembersMax = fromLayers("history_group_members_max", DEFAULT_HISTORY_LIMITS.groupMembersMax);
258
270
  const pictureSearch = fromLayers("picture_search", false);
259
271
  const searchReranker = fromLayers("search_reranker", DEFAULT_RERANK_MODEL);
260
272
  const recordInput = fromLayers("record_input", true);
@@ -306,6 +318,11 @@ export const settingsFor = (app, extension = {}) => {
306
318
  color: color.value,
307
319
  senderColors: senderColors.value,
308
320
  searchCatchUp: searchCatchUp.value,
321
+ historyLimits: {
322
+ dialogDays: historyDialogDays.value,
323
+ groupDays: historyGroupDays.value,
324
+ groupMembersMax: historyGroupMembersMax.value,
325
+ },
309
326
  pictureSearch: pictureSearch.value,
310
327
  searchReranker: searchReranker.value,
311
328
  limit: limit.value,
@@ -343,6 +360,9 @@ export const settingsFor = (app, extension = {}) => {
343
360
  color: color.from,
344
361
  senderColors: senderColors.from,
345
362
  searchCatchUp: searchCatchUp.from,
363
+ historyDialogDays: historyDialogDays.from,
364
+ historyGroupDays: historyGroupDays.from,
365
+ historyGroupMembersMax: historyGroupMembersMax.from,
346
366
  pictureSearch: pictureSearch.from,
347
367
  searchReranker: searchReranker.from,
348
368
  record: record.from,
@@ -82,6 +82,9 @@ export declare const appDoctorCommand: (app: import("./entry.js").AppIdentity, c
82
82
  transcribe_with: import("valibot").OptionalSchema<import("valibot").PicklistSchema<["auto", "messenger", "local"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
83
83
  speech_model: import("valibot").OptionalSchema<import("valibot").StringSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
84
84
  search_catch_up: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
85
+ history_dialog_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MaxValueAction<number, 1095, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
86
+ history_group_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MaxValueAction<number, 1095, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
87
+ history_group_members_max: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
85
88
  picture_search: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
86
89
  search_reranker: import("valibot").OptionalSchema<import("valibot").PicklistSchema<[...string[], "off"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
87
90
  update_check: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
@@ -158,6 +161,9 @@ export declare const appDoctorCommand: (app: import("./entry.js").AppIdentity, c
158
161
  transcribe_with: import("valibot").OptionalSchema<import("valibot").PicklistSchema<["auto", "messenger", "local"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
159
162
  speech_model: import("valibot").OptionalSchema<import("valibot").StringSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
160
163
  search_catch_up: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
164
+ history_dialog_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MaxValueAction<number, 1095, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
165
+ history_group_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MaxValueAction<number, 1095, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
166
+ history_group_members_max: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
161
167
  picture_search: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
162
168
  search_reranker: import("valibot").OptionalSchema<import("valibot").PicklistSchema<[...string[], "off"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
163
169
  }, (issue: import("valibot").StrictObjectIssue) => string>, "has to be an object of profiles, by name">, {}>;
@@ -256,6 +262,9 @@ export declare const appMcpCommand: (app: import("./entry.js").AppIdentity, conf
256
262
  transcribe_with: import("valibot").OptionalSchema<import("valibot").PicklistSchema<["auto", "messenger", "local"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
257
263
  speech_model: import("valibot").OptionalSchema<import("valibot").StringSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
258
264
  search_catch_up: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
265
+ history_dialog_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MaxValueAction<number, 1095, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
266
+ history_group_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MaxValueAction<number, 1095, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
267
+ history_group_members_max: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
259
268
  picture_search: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
260
269
  search_reranker: import("valibot").OptionalSchema<import("valibot").PicklistSchema<[...string[], "off"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
261
270
  update_check: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
@@ -332,6 +341,9 @@ export declare const appMcpCommand: (app: import("./entry.js").AppIdentity, conf
332
341
  transcribe_with: import("valibot").OptionalSchema<import("valibot").PicklistSchema<["auto", "messenger", "local"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
333
342
  speech_model: import("valibot").OptionalSchema<import("valibot").StringSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
334
343
  search_catch_up: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
344
+ history_dialog_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MaxValueAction<number, 1095, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
345
+ history_group_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MaxValueAction<number, 1095, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
346
+ history_group_members_max: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
335
347
  picture_search: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
336
348
  search_reranker: import("valibot").OptionalSchema<import("valibot").PicklistSchema<[...string[], "off"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
337
349
  }, (issue: import("valibot").StrictObjectIssue) => string>, "has to be an object of profiles, by name">, {}>;
@@ -437,6 +449,9 @@ export declare const configCommand: (app: import("./entry.js").AppIdentity, conf
437
449
  transcribe_with: import("valibot").OptionalSchema<import("valibot").PicklistSchema<["auto", "messenger", "local"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
438
450
  speech_model: import("valibot").OptionalSchema<import("valibot").StringSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
439
451
  search_catch_up: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
452
+ history_dialog_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MaxValueAction<number, 1095, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
453
+ history_group_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MaxValueAction<number, 1095, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
454
+ history_group_members_max: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
440
455
  picture_search: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
441
456
  search_reranker: import("valibot").OptionalSchema<import("valibot").PicklistSchema<[...string[], "off"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
442
457
  update_check: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
@@ -513,6 +528,9 @@ export declare const configCommand: (app: import("./entry.js").AppIdentity, conf
513
528
  transcribe_with: import("valibot").OptionalSchema<import("valibot").PicklistSchema<["auto", "messenger", "local"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
514
529
  speech_model: import("valibot").OptionalSchema<import("valibot").StringSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
515
530
  search_catch_up: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
531
+ history_dialog_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MaxValueAction<number, 1095, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
532
+ history_group_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MaxValueAction<number, 1095, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
533
+ history_group_members_max: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
516
534
  picture_search: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
517
535
  search_reranker: import("valibot").OptionalSchema<import("valibot").PicklistSchema<[...string[], "off"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
518
536
  }, (issue: import("valibot").StrictObjectIssue) => string>, "has to be an object of profiles, by name">, {}>;
@@ -646,6 +664,9 @@ export declare const completeCommand: (messenger: import("./index.js").Messenger
646
664
  transcribe_with: import("valibot").OptionalSchema<import("valibot").PicklistSchema<["auto", "messenger", "local"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
647
665
  speech_model: import("valibot").OptionalSchema<import("valibot").StringSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
648
666
  search_catch_up: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
667
+ history_dialog_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MaxValueAction<number, 1095, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
668
+ history_group_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MaxValueAction<number, 1095, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
669
+ history_group_members_max: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
649
670
  picture_search: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
650
671
  search_reranker: import("valibot").OptionalSchema<import("valibot").PicklistSchema<[...string[], "off"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
651
672
  update_check: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
@@ -722,6 +743,9 @@ export declare const completeCommand: (messenger: import("./index.js").Messenger
722
743
  transcribe_with: import("valibot").OptionalSchema<import("valibot").PicklistSchema<["auto", "messenger", "local"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
723
744
  speech_model: import("valibot").OptionalSchema<import("valibot").StringSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
724
745
  search_catch_up: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
746
+ history_dialog_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MaxValueAction<number, 1095, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
747
+ history_group_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MaxValueAction<number, 1095, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
748
+ history_group_members_max: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
725
749
  picture_search: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
726
750
  search_reranker: import("valibot").OptionalSchema<import("valibot").PicklistSchema<[...string[], "off"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
727
751
  }, (issue: import("valibot").StrictObjectIssue) => string>, "has to be an object of profiles, by name">, {}>;
@@ -1,5 +1,7 @@
1
1
  import type { Command } from "commander";
2
2
  import type { AppIdentity } from "../ports/app.js";
3
+ /** The SKILL.md a program's `skill` command installs, so a run can refresh old copies of it. */
4
+ export declare const skillSourceOf: (program: Command, appName: string) => URL | undefined;
3
5
  /**
4
6
  * `skill show` and `skill install`. `skill` is the CLI's own SKILL.md, shipped beside `dist/` in the
5
7
  * package and in a checkout alike, so the skill printed is always this version's.
@@ -3,6 +3,14 @@ import { skillCommand as sharedSkillCommand } from "@wirecat/cli-base/skill";
3
3
  import { environmentOf, outputFor } from "./context.js";
4
4
  import { grantsAuthority } from "./effects.js";
5
5
  import { printItem } from "./envelope.js";
6
+ const skillSources = new WeakMap();
7
+ // zm and memo mount their own `skill` command in place of this one (`customizeSharedCommand`), from the same file.
8
+ const skillSourcesByApp = new Map();
9
+ /** The SKILL.md a program's `skill` command installs, so a run can refresh old copies of it. */
10
+ export const skillSourceOf = (program, appName) => {
11
+ const command = program.commands.find((sub) => sub.name() === "skill");
12
+ return command ? (skillSources.get(command) ?? skillSourcesByApp.get(appName)) : undefined;
13
+ };
6
14
  /**
7
15
  * `skill show` and `skill install`. `skill` is the CLI's own SKILL.md, shipped beside `dist/` in the
8
16
  * package and in a checkout alike, so the skill printed is always this version's.
@@ -15,6 +23,8 @@ export const skillCommand = (app, skill) => {
15
23
  const renderer = { ...output.renderer, result: (value) => printItem(output, value) };
16
24
  return { ...output, renderer, ...(env ? { env } : {}) };
17
25
  }, app.messenger === false ? {} : SHARED_SKILLS);
26
+ skillSources.set(command, skill);
27
+ skillSourcesByApp.set(app.appName, skill);
18
28
  // An installed skill is instructions every later agent follows.
19
29
  const install = command.commands.find((sub) => sub.name() === "install");
20
30
  if (install) {
@@ -0,0 +1,18 @@
1
+ import { type SkillApp } from "@wirecat/cli-base/skill";
2
+ import { type AppIdentity } from "../ports/app.js";
3
+ type Env = Readonly<Record<string, string | undefined>>;
4
+ /** Not where the owner's own agents look: a test, a moved HOME or state folder, or the owner said no. */
5
+ export declare const skipsSkillRefresh: (app: AppIdentity, env: NodeJS.ProcessEnv) => boolean;
6
+ export interface SkillRefreshRequest {
7
+ app: SkillApp;
8
+ skill: URL;
9
+ env: Env;
10
+ /** `<state>/skill-refresh.json`: the version this was last tried for, so each version tries once. */
11
+ markerPath: string;
12
+ }
13
+ /**
14
+ * On the first run of a new version, rewrites the installed SKILL.md copies that an older version wrote, and answers
15
+ * the line for stderr — or `undefined` when there was nothing to do. A failure is a one-line note, never an error.
16
+ */
17
+ export declare const refreshSkills: ({ app, skill, env, markerPath }: SkillRefreshRequest) => string | undefined;
18
+ export {};
@@ -0,0 +1,82 @@
1
+ import { lstatSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { isMovedRun, loadConfigFile, saveConfigFile } from "@wirecat/cli-base";
3
+ import { hermesCopyPath, installSkill, skillStatus, skillVersion, withVersion, } from "@wirecat/cli-base/skill";
4
+ import { isNewer } from "@wirecat/cli-base/update";
5
+ import * as v from "valibot";
6
+ import { envName } from "../ports/app.js";
7
+ const marker = v.object({ version: v.optional(v.string()) });
8
+ const AGENT_NAMES = {
9
+ claude: "Claude Code",
10
+ agents: "other agents",
11
+ hermes: "Hermes",
12
+ };
13
+ /** Not where the owner's own agents look: a test, a moved HOME or state folder, or the owner said no. */
14
+ export const skipsSkillRefresh = (app, env) => env.VITEST !== undefined ||
15
+ Boolean(env[envName(app, "NO_SKILL_REFRESH")]) ||
16
+ isMovedRun({ appName: app.appName, prefix: app.envPrefix, env });
17
+ // A symlink points into a checkout or a dotfiles repo; writing through it changes a file the tool never wrote.
18
+ const isPlainFile = (path) => {
19
+ try {
20
+ return lstatSync(path).isFile();
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ };
26
+ /**
27
+ * Only `stale` copies: one with a version stamp that `skill install` wrote for an older release. `unversioned` means
28
+ * no released `skill install` wrote it (each stamps `metadata.version`), so it is somebody's own file and stays.
29
+ */
30
+ const staleTargets = (app, env) => skillStatus(app, env)
31
+ .filter(({ state, path }) => state === "stale" && isPlainFile(path))
32
+ .map(({ target }) => target);
33
+ /** Hermes's own copy, read from disk: asking Hermes would start a program and could take seconds. */
34
+ const refreshHermesCopy = (app, skill, env) => {
35
+ const path = hermesCopyPath(app.appName, env);
36
+ if (!isPlainFile(path))
37
+ return false;
38
+ const installed = skillVersion(readFileSync(path, "utf8"));
39
+ if (installed === undefined || !isNewer(app.version, installed))
40
+ return false;
41
+ writeFileSync(path, withVersion(readFileSync(skill, "utf8"), app.version));
42
+ return true;
43
+ };
44
+ /**
45
+ * On the first run of a new version, rewrites the installed SKILL.md copies that an older version wrote, and answers
46
+ * the line for stderr — or `undefined` when there was nothing to do. A failure is a one-line note, never an error.
47
+ */
48
+ export const refreshSkills = ({ app, skill, env, markerPath }) => {
49
+ try {
50
+ const seen = loadConfigFile(markerPath, marker, () => ({})).version;
51
+ if (seen === app.version)
52
+ return undefined;
53
+ }
54
+ catch {
55
+ // An unreadable marker counts as a new version; the write below replaces it.
56
+ }
57
+ const save = () => {
58
+ try {
59
+ saveConfigFile(markerPath, { version: app.version });
60
+ }
61
+ catch {
62
+ // Unsaved, the next run tries again; that costs two file reads.
63
+ }
64
+ };
65
+ try {
66
+ const targets = staleTargets(app, env);
67
+ if (targets.length > 0)
68
+ installSkill(app, skill, { targets, env });
69
+ const hermes = refreshHermesCopy(app, skill, env);
70
+ save();
71
+ const names = [...targets, ...(hermes ? ["hermes"] : [])].map((target) => AGENT_NAMES[target]);
72
+ if (names.length === 0)
73
+ return undefined;
74
+ return `Updated the ${app.command} instructions for ${listed(names)} to ${app.version}`;
75
+ }
76
+ catch (error) {
77
+ save();
78
+ const reason = error instanceof Error ? error.message.split("\n")[0] : String(error);
79
+ return `Could not update the ${app.command} instructions for agents (${reason}); \`${app.command} skill install\` does it`;
80
+ }
81
+ };
82
+ const listed = (names) => names.length < 2 ? (names[0] ?? "") : `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
package/dist/cli/steps.js CHANGED
@@ -16,6 +16,7 @@ export const STEPS = defineSteps([
16
16
  { name: "all", required: false },
17
17
  { name: "since_time", required: false },
18
18
  { name: "background", required: false },
19
+ { name: "full_history", required: false },
19
20
  ],
20
21
  why: "the local copy is missing messages; syncing brings them in",
21
22
  },
@@ -1106,6 +1106,7 @@ export declare const ENTITIES: {
1106
1106
  readonly held: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
1107
1107
  readonly held_chats: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
1108
1108
  }, undefined>, undefined>;
1109
+ readonly history_included: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
1109
1110
  }, undefined>, v.StrictObjectSchema<{
1110
1111
  readonly batch: v.OptionalSchema<v.StrictObjectSchema<{
1111
1112
  readonly attempted: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
@@ -209,6 +209,8 @@ export declare const messageSyncChat: v.StrictObjectSchema<{
209
209
  readonly held: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
210
210
  readonly held_chats: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
211
211
  }, undefined>, undefined>;
212
+ /** `--full-history`: the chat's pushed history is now kept in full; `false` when the store does not know the chat. */
213
+ readonly history_included: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
212
214
  }, undefined>;
213
215
  export declare const messageSyncAll: v.StrictObjectSchema<{
214
216
  readonly batch: v.OptionalSchema<v.StrictObjectSchema<{
@@ -333,6 +335,8 @@ export declare const messageSync: v.UnionSchema<[v.StrictObjectSchema<{
333
335
  readonly held: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
334
336
  readonly held_chats: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>, undefined>;
335
337
  }, undefined>, undefined>;
338
+ /** `--full-history`: the chat's pushed history is now kept in full; `false` when the store does not know the chat. */
339
+ readonly history_included: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
336
340
  }, undefined>, v.StrictObjectSchema<{
337
341
  readonly batch: v.OptionalSchema<v.StrictObjectSchema<{
338
342
  readonly attempted: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
@@ -60,6 +60,8 @@ export const messageSyncChat = v.strictObject({
60
60
  reached_last: v.optional(v.literal(true)),
61
61
  stopped: v.optional(v.literal(true)),
62
62
  tasks,
63
+ /** `--full-history`: the chat's pushed history is now kept in full; `false` when the store does not know the chat. */
64
+ history_included: v.optional(v.boolean()),
63
65
  });
64
66
  export const messageSyncAll = v.strictObject({
65
67
  batch: v.optional(batchResult),
@@ -0,0 +1,45 @@
1
+ import type { Id } from "../domain/models.js";
2
+ import type { HistoryBatch } from "../ports/messenger.js";
3
+ /** How much pushed history the store keeps of each kind of chat. */
4
+ export interface HistoryLimits {
5
+ /** One-to-one chats: days back from now. */
6
+ dialogDays: number;
7
+ /** Groups of more than two people: days back from now. */
8
+ groupDays: number;
9
+ /** Above this many people a group's pushed history is not kept at all. */
10
+ groupMembersMax: number;
11
+ }
12
+ export declare const HISTORY_MAX_DAYS = 1095;
13
+ export declare const DEFAULT_HISTORY_LIMITS: HistoryLimits;
14
+ export type HistorySkipReason = "group_too_large" | "older_than_window";
15
+ /** What the limits left out of one chat, summed over every push. */
16
+ export interface HistorySkip {
17
+ reason: HistorySkipReason;
18
+ messages: number;
19
+ oldestAt: string;
20
+ newestAt: string;
21
+ /** For `group_too_large`: how many people the group had. */
22
+ members?: number;
23
+ /** For `older_than_window`: the window's start when the push was saved. */
24
+ keptFrom?: string;
25
+ }
26
+ /** The kind and size of a chat, as the batch names it or the store holds it. */
27
+ export interface ChatShape {
28
+ kind: string;
29
+ participantsCount: number | null;
30
+ }
31
+ /** Whether this chat's pushed history is kept, and from when. A chat the owner lifted keeps everything. */
32
+ export declare const historyWindow: (shape: ChatShape | undefined, limits: HistoryLimits, now: number) => {
33
+ keepFrom: number;
34
+ } | {
35
+ skip: "group_too_large";
36
+ members: number;
37
+ };
38
+ /**
39
+ * The batch without the messages its chats' limits leave out, and what was left out per chat. Chat rows, people and
40
+ * re-keys always stay: a skipped chat is still listed, so its history can be fetched by hand.
41
+ */
42
+ export declare const limitBatch: (batch: HistoryBatch, shapes: (chatId: Id) => ChatShape | undefined, included: (chatId: Id) => boolean, limits: HistoryLimits, now?: number) => {
43
+ batch: HistoryBatch;
44
+ skipped: Map<Id, HistorySkip>;
45
+ };
@@ -0,0 +1,50 @@
1
+ export const HISTORY_MAX_DAYS = 1095;
2
+ export const DEFAULT_HISTORY_LIMITS = { dialogDays: 1095, groupDays: 365, groupMembersMax: 50 };
3
+ const DAY_MS = 24 * 60 * 60 * 1000;
4
+ /** Whether this chat's pushed history is kept, and from when. A chat the owner lifted keeps everything. */
5
+ export const historyWindow = (shape, limits, now) => {
6
+ const group = shape !== undefined && shape.kind !== "dialog" && shape.kind !== "saved";
7
+ if (group && (shape.participantsCount ?? 0) > limits.groupMembersMax)
8
+ return { skip: "group_too_large", members: shape.participantsCount ?? 0 };
9
+ const days = group ? limits.groupDays : limits.dialogDays;
10
+ return { keepFrom: now - Math.min(days, HISTORY_MAX_DAYS) * DAY_MS };
11
+ };
12
+ /**
13
+ * The batch without the messages its chats' limits leave out, and what was left out per chat. Chat rows, people and
14
+ * re-keys always stay: a skipped chat is still listed, so its history can be fetched by hand.
15
+ */
16
+ export const limitBatch = (batch, shapes, included, limits, now = Date.now()) => {
17
+ const skipped = new Map();
18
+ if (!batch.messages?.length)
19
+ return { batch, skipped };
20
+ const named = new Map((batch.chats ?? []).map((chat) => [chat.id, chat]));
21
+ const windows = new Map();
22
+ const windowOf = (chatId) => {
23
+ if (!windows.has(chatId)) {
24
+ const chat = named.get(chatId);
25
+ const shape = chat ? { kind: chat.kind, participantsCount: chat.participantsCount ?? null } : shapes(chatId);
26
+ windows.set(chatId, included(chatId) ? undefined : historyWindow(shape, limits, now));
27
+ }
28
+ return windows.get(chatId);
29
+ };
30
+ const messages = batch.messages.filter((message) => {
31
+ const window = windowOf(message.chatId);
32
+ if (window === undefined)
33
+ return true;
34
+ const at = Date.parse(message.timestamp);
35
+ if ("keepFrom" in window && !(at < window.keepFrom))
36
+ return true;
37
+ const reason = "skip" in window ? window.skip : "older_than_window";
38
+ const held = skipped.get(message.chatId);
39
+ const iso = new Date(at).toISOString();
40
+ skipped.set(message.chatId, {
41
+ reason,
42
+ messages: (held?.messages ?? 0) + 1,
43
+ oldestAt: held && held.oldestAt < iso ? held.oldestAt : iso,
44
+ newestAt: held && held.newestAt > iso ? held.newestAt : iso,
45
+ ...("skip" in window ? { members: window.members } : { keptFrom: new Date(window.keepFrom).toISOString() }),
46
+ });
47
+ return false;
48
+ });
49
+ return { batch: { ...batch, messages }, skipped };
50
+ };
@@ -1,8 +1,10 @@
1
1
  import type { ChannelTagMatch } from "../domain/channel-tags.js";
2
2
  import type { Chat, ChatKind, ChatUpdate, Id, Page } from "../domain/models.js";
3
+ import type { HistorySkip } from "../services/history-limits.js";
3
4
  import { type ChatRekey, type ChatRekeys } from "./sqlite/chat-rekey.js";
4
5
  import type { ChatMetadata, LeftChat } from "./sqlite/chats.js";
5
6
  import type { ChatCompleteness } from "./sqlite/completeness.js";
7
+ import type { ChatHistoryShape, StoredHistorySkip } from "./sqlite/history-skips.js";
6
8
  import type { StoreContext } from "./sqlite/open.js";
7
9
  import type { Where } from "./sqlite/query-filter.js";
8
10
  import type { AccountKey, ChatActivity, ChatStats, StoredChatFilter } from "./types.js";
@@ -64,6 +66,14 @@ export interface ChatsStore {
64
66
  * `chatId` itself. Only for finding the chat: the old id and its history are never shown or searched.
65
67
  */
66
68
  renamedChatId(key: AccountKey, chatId: Id): Promise<Id | undefined>;
69
+ /** Per stored chat: its kind, how many people, and whether the owner lifted its history limits. */
70
+ historyShapes(key: AccountKey, chatIds: Id[]): Promise<Map<Id, ChatHistoryShape>>;
71
+ /** Adds what the history limits left out of pushed history, per chat. */
72
+ recordHistorySkips(key: AccountKey, skips: Map<Id, HistorySkip>): Promise<void>;
73
+ /** Lifts the history limits for one chat, or puts them back; `false` when the store does not know the chat. */
74
+ setHistoryIncluded(key: AccountKey, chatId: Id, included: boolean): Promise<boolean>;
75
+ /** Every chat whose pushed history the limits left out, most messages first. */
76
+ historySkips(key: AccountKey): Promise<StoredHistorySkip[]>;
67
77
  /** A stored chat's kind, with or without messages; `undefined` when the store does not know the chat. */
68
78
  chatKind(key: AccountKey, chatId: Id): Promise<ChatKind | undefined>;
69
79
  /** A stored chat's title, with or without messages; `null` when it has none or the store does not know the chat. */
@@ -3,6 +3,7 @@ import { keysOver } from "./keys.js";
3
3
  import { rekeyChat, rekeyChats } from "./sqlite/chat-rekey.js";
4
4
  import * as chatQueries from "./sqlite/chats.js";
5
5
  import * as completeness from "./sqlite/completeness.js";
6
+ import * as history from "./sqlite/history-skips.js";
6
7
  import * as reads from "./sqlite/reads.js";
7
8
  import { write } from "./sqlite/transaction.js";
8
9
  export const chatsStoreOver = (context) => {
@@ -85,6 +86,30 @@ export const chatsStoreOver = (context) => {
85
86
  const accountKey = findAccountPk(key);
86
87
  return accountKey === undefined ? undefined : chatQueries.renamedChatId(context, accountKey, chatId);
87
88
  },
89
+ historyShapes: async (key, chatIds) => {
90
+ const accountKey = findAccountPk(key);
91
+ return accountKey === undefined ? new Map() : history.historyShapes(context, accountKey, chatIds);
92
+ },
93
+ recordHistorySkips: async (key, skips) => {
94
+ const accountKey = findAccountPk(key);
95
+ if (accountKey === undefined || skips.size === 0)
96
+ return;
97
+ write(database, () => history.recordHistorySkips(context, accountKey, skips));
98
+ },
99
+ setHistoryIncluded: async (key, chatId, included) => {
100
+ const accountKey = findAccountPk(key);
101
+ if (accountKey === undefined)
102
+ return false;
103
+ let done = false;
104
+ write(database, () => {
105
+ done = history.setHistoryIncluded(context, accountKey, chatId, included);
106
+ });
107
+ return done;
108
+ },
109
+ historySkips: async (key) => {
110
+ const accountKey = findAccountPk(key);
111
+ return accountKey === undefined ? [] : history.historySkips(context, accountKey);
112
+ },
88
113
  chatKind: async (key, chatId) => {
89
114
  const accountKey = findAccountPk(key);
90
115
  if (accountKey === undefined)
@@ -1,6 +1,7 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
2
  import { and, eq, inArray, or, sql } from "./drizzle/core.js";
3
3
  import { folded } from "./folded.js";
4
+ import { withoutHistory } from "./history-skips.js";
4
5
  import { identityPk } from "./identities.js";
5
6
  import { bound } from "./query-filter.js";
6
7
  import { chatEventPeople, chatEvents, chatMembers, chats, chunks, fetchLeases, identities, involvements, memberCounts, memberObservationMembers, memberObservations, memberStays, messageReactions, messageRevisions, messages, messageTranscripts, pollVotes, syncRanges, } from "./schema.js";
@@ -395,7 +396,7 @@ const toChat = (row, at, parentId) => ({
395
396
  scope: row.scope,
396
397
  parentChatId: parentId ?? null,
397
398
  membershipState: row.membershipState,
398
- providerMetadata: parsed(row.metadata),
399
+ providerMetadata: withoutHistory(parsed(row.metadata)),
399
400
  }),
400
401
  ...muteOf(row, at),
401
402
  ...present({ archived: flag(row.archived), markedUnread: flag(row.markedUnread) }),
@@ -0,0 +1,24 @@
1
+ import type { Id } from "../../domain/models.js";
2
+ import type { ChatShape, HistorySkip } from "../../services/history-limits.js";
3
+ import type { StoreContext } from "./open.js";
4
+ /**
5
+ * The history limits' record of a chat, kept in the chat row's `metadata` under this key so no store version is
6
+ * needed; `Chat.providerMetadata` leaves it out.
7
+ */
8
+ export declare const HISTORY_KEY = "$history";
9
+ export interface ChatHistoryShape extends ChatShape {
10
+ included: boolean;
11
+ }
12
+ export interface StoredHistorySkip extends HistorySkip {
13
+ chatId: Id;
14
+ title: string | null;
15
+ }
16
+ export declare const historyShapes: (context: StoreContext, accountKey: number, chatIds: Id[]) => Map<Id, ChatHistoryShape>;
17
+ /** Adds what one push left out to what the chat's record already holds. A chat the store lacks is left as it is. */
18
+ export declare const recordHistorySkips: (context: StoreContext, accountKey: number, skips: Map<Id, HistorySkip>) => void;
19
+ /** Lifts the limits for one chat from now on, or puts them back. Answers `false` for a chat the store lacks. */
20
+ export declare const setHistoryIncluded: (context: StoreContext, accountKey: number, chatId: Id, included: boolean) => boolean;
21
+ /** Every chat of the account whose pushed history the limits left out, most messages first. */
22
+ export declare const historySkips: ({ database }: StoreContext, accountKey: number) => StoredHistorySkip[];
23
+ /** `metadata` as the messenger gave it: the history record is the store's own. */
24
+ export declare const withoutHistory: <T extends Record<string, unknown> | undefined>(metadata: T) => T;