@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.
- package/CHANGELOG.md +36 -0
- package/dist/cli/config-command.js +32 -2
- package/dist/cli/messenger/archive-commands.js +20 -2
- package/dist/cli/messenger/backfill-command.js +13 -2
- package/dist/cli/messenger/context.js +3 -0
- package/dist/cli/messenger/history-gaps.d.ts +12 -0
- package/dist/cli/messenger/history-gaps.js +41 -0
- package/dist/cli/messenger/messages-command.js +7 -3
- package/dist/cli/messenger/messages-search-command.js +5 -1
- package/dist/cli/messenger/search-answers.d.ts +3 -2
- package/dist/cli/messenger/search-answers.js +12 -5
- package/dist/cli/messenger/search-command.js +3 -1
- package/dist/cli/messenger/stored.d.ts +6 -1
- package/dist/cli/messenger/stored.js +38 -2
- package/dist/cli/program.js +22 -0
- package/dist/cli/settings.d.ts +9 -0
- package/dist/cli/settings.js +20 -0
- package/dist/cli/shared-command-exports.d.ts +24 -0
- package/dist/cli/skill-command.d.ts +2 -0
- package/dist/cli/skill-command.js +10 -0
- package/dist/cli/skill-refresh.d.ts +18 -0
- package/dist/cli/skill-refresh.js +82 -0
- package/dist/cli/steps.js +1 -0
- package/dist/entities/index.d.ts +1 -0
- package/dist/entities/message-transfer.d.ts +4 -0
- package/dist/entities/message-transfer.js +2 -0
- package/dist/services/history-limits.d.ts +45 -0
- package/dist/services/history-limits.js +50 -0
- package/dist/store/chats.d.ts +10 -0
- package/dist/store/chats.js +25 -0
- package/dist/store/sqlite/chats.js +2 -1
- package/dist/store/sqlite/history-skips.d.ts +24 -0
- package/dist/store/sqlite/history-skips.js +95 -0
- package/dist/store/store.d.ts +1 -0
- package/package.json +1 -1
- package/parity.json +4 -0
package/dist/cli/settings.js
CHANGED
|
@@ -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
|
},
|
package/dist/entities/index.d.ts
CHANGED
|
@@ -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
|
+
};
|
package/dist/store/chats.d.ts
CHANGED
|
@@ -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. */
|
package/dist/store/chats.js
CHANGED
|
@@ -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;
|