@wirecat/cli-core 0.367.0 → 0.368.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 CHANGED
@@ -7,6 +7,18 @@ shape of this file.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.368.0 — 20.10.2026
11
+
12
+ ### Fixed
13
+
14
+ - **Reads of a left chat's kept history find it by any id; live operations refuse it by any id.** `store export`
15
+ (also `--output-dir` with named chats), `store status`, `messages thread`, a search's `--chat` and `chat:` in one
16
+ account, `metadata get`, members, stats, events, tags, tasks, conversations, embeddings, pictures and attachments
17
+ now find a chat the account left by its name or id, the current one or one it was re-keyed from, as `chats show`
18
+ and `messages list` already did. Before, a left chat named by a non-numeric id was refused. `chats track` and
19
+ `store gaps plan` refuse a left chat with "you left chat <id>", also by a numeric id, which used to pass as typed.
20
+ A numeric id the store does not hold still passes as typed.
21
+
10
22
  ## 0.367.0 — 20.10.2026
11
23
 
12
24
  ### Added
@@ -370,7 +370,7 @@ export const tasksCommand = (messenger) => {
370
370
  const passes = await context.withStore(async (store, account) => {
371
371
  const ids = [];
372
372
  for (const chat of chats)
373
- ids.push(await storedChatId(messenger, chat, store, account));
373
+ ids.push(await storedChatId(messenger, chat, store, account, { includeLeft: true }));
374
374
  return applyTaskRulesToChats(store, account, ids, { ...(last === undefined ? {} : { last }), yes });
375
375
  }, { name: "tasks scan" });
376
376
  renderAnswer(this, context, listOf("tasks scan", { items: passes.map(snakeKeys), limit: passes.length, has_more: false }), () => `${passes.map((pass) => applyText(pass, yes, tool)).join("\n")}\n`);
@@ -29,13 +29,13 @@ export const archiveService = (deps) => {
29
29
  const found = async (chat) => {
30
30
  const store = await deps.store();
31
31
  const account = await deps.account();
32
- return { store, account, chatId: await storedChatId(deps.messenger, chat, store, account) };
32
+ return { store, account, chatId: await storedChatId(deps.messenger, chat, store, account, { includeLeft: true }) };
33
33
  };
34
34
  return {
35
35
  status: async (chat) => {
36
36
  const store = await deps.store();
37
37
  const account = await deps.account();
38
- const only = chat === undefined ? undefined : await storedChatId(deps.messenger, chat, store, account);
38
+ const only = chat === undefined ? undefined : await storedChatId(deps.messenger, chat, store, account, { includeLeft: true });
39
39
  const stats = await store.chats.chatStats(account, only);
40
40
  return Promise.all(stats.map(async (one) => ({ ...one, held: await store.sync.ranges(account, one.chatId) })));
41
41
  },
@@ -54,7 +54,7 @@ export const archiveService = (deps) => {
54
54
  const store = await deps.store();
55
55
  const account = await deps.account();
56
56
  if (chats !== undefined && chats.length > 0) {
57
- const named = await Promise.all(chats.map((chat) => storedChatId(deps.messenger, chat, store, account)));
57
+ const named = await Promise.all(chats.map((chat) => storedChatId(deps.messenger, chat, store, account, { includeLeft: true })));
58
58
  const titles = new Map((await store.chats.chatStats(account)).map((one) => [one.chatId, one.title]));
59
59
  return { account, chats: [...new Set(named)].map((id) => ({ id, title: titles.get(id) ?? null })) };
60
60
  }
@@ -147,7 +147,7 @@ const messageOf = async (deps, store, chat, message) => {
147
147
  }
148
148
  if (message === undefined)
149
149
  throw new CliError("validation_error", "which message? give its id after the chat");
150
- return { chatId: await chatIdIn(deps.messenger, chat, store), messageId: message.trim() };
150
+ return { chatId: await chatIdIn(deps.messenger, chat, store, { includeLeft: true }), messageId: message.trim() };
151
151
  };
152
152
  export const attachmentsService = (deps) => ({
153
153
  extract: async ({ chat, message: onlyMessage, paths, fromDir, cursor, scanLimit: requestedScanLimit, limit: requestedLimit, download, onItem, signal, load = importEngine, ocr, concurrency = 1, } = {}) => {
@@ -167,7 +167,7 @@ export const attachmentsService = (deps) => ({
167
167
  deps.guard.check({ chatId: null, key: "attachments.extract" }, { reserve: false });
168
168
  const store = await accountStore(deps);
169
169
  const { account } = store;
170
- const chatId = chat === undefined ? undefined : await chatIdIn(deps.messenger, chat, store);
170
+ const chatId = chat === undefined ? undefined : await chatIdIn(deps.messenger, chat, store, { includeLeft: true });
171
171
  const directory = fromDir === undefined
172
172
  ? undefined
173
173
  : await directoryPaths(store, chatId, fromDir, deps.messenger.app, deps.env ?? process.env);
@@ -346,7 +346,7 @@ export const attachmentsService = (deps) => ({
346
346
  if (query)
347
347
  for (const node of walkQuery(query.root))
348
348
  if (node.field === "chat" && node.operator !== "wildcard" && !chats.has(node.value))
349
- chats.set(node.value, await chatIdIn(deps.messenger, node.value, store));
349
+ chats.set(node.value, await chatIdIn(deps.messenger, node.value, store, { includeLeft: true }));
350
350
  const where = query ? queryFilter(query, attachmentColumns(chats), { command: "attachments list" }) : undefined;
351
351
  const views = await store.attachments.attachments({ ...(where ? { where } : {}), offset, limit: limit + 1 });
352
352
  return { items: views.slice(0, limit).map((view) => itemOf(account, view)), has_more: views.length > limit };
@@ -5,7 +5,7 @@ import { chatIdIn } from "./message-resolution.js";
5
5
  export const chatDocumentsService = (deps) => ({
6
6
  list: async (chat, { limit, offset }) => {
7
7
  const store = await accountStore(deps);
8
- const chatId = await chatIdIn(deps.messenger, chat, store);
8
+ const chatId = await chatIdIn(deps.messenger, chat, store, { includeLeft: true });
9
9
  const items = (await store.messages.documentLinkMessages(chatId)).flatMap((message) => googleDocumentLinks(message.text).map(({ url, documentId }) => {
10
10
  const stored = documentId === null ? undefined : message.documents.find((one) => one.fileId === documentId);
11
11
  return {
@@ -24,7 +24,7 @@ export const CHAT_KINDS = ["dialog", "group", "channel", "saved"];
24
24
  export const STORED_EVENTS = 1000;
25
25
  /** For a messenger whose history is pushed: the events `serve` kept, never asking the messenger. */
26
26
  const storedEvents = async (store, deps, chat, since) => {
27
- const chatId = await chatIdIn(deps.messenger, chat, store);
27
+ const chatId = await chatIdIn(deps.messenger, chat, store, { includeLeft: true });
28
28
  const found = await store.roster.chatEvents(chatId, {
29
29
  since: new Date(since).toISOString(),
30
30
  limit: STORED_EVENTS + 1,
@@ -94,7 +94,7 @@ export const chatsService = (deps) => ({
94
94
  return capability(connection, "members", "list a group's members")(chat, window);
95
95
  }
96
96
  const store = await accountStore(deps);
97
- const chatId = await chatIdIn(deps.messenger, chat, store);
97
+ const chatId = await chatIdIn(deps.messenger, chat, store, { includeLeft: true });
98
98
  const everyone = await store.roster.members(chatId);
99
99
  const end = window.limit === undefined ? everyone.length : window.offset + window.limit;
100
100
  return { chatId, items: everyone.slice(window.offset, end), hasMore: everyone.length > end };
@@ -116,7 +116,7 @@ export const chatsService = (deps) => ({
116
116
  },
117
117
  stats: async (chat, { since, by, timezone }) => {
118
118
  const store = await accountStore(deps);
119
- const chatId = await chatIdIn(deps.messenger, chat, store);
119
+ const chatId = await chatIdIn(deps.messenger, chat, store, { includeLeft: true });
120
120
  const [completeness] = await store.chats.chatCompleteness([chatId]);
121
121
  if (!completeness)
122
122
  throw new CliError("not_found", `no stored chat ${chatId}`);
@@ -213,7 +213,7 @@ export const chatsService = (deps) => ({
213
213
  tracked: async () => (await accountStore(deps)).roster.trackedChats(),
214
214
  memberHistory: async (chat, { since }) => {
215
215
  const store = await accountStore(deps);
216
- const chatId = await chatIdIn(deps.messenger, chat, store);
216
+ const chatId = await chatIdIn(deps.messenger, chat, store, { includeLeft: true });
217
217
  const [stays, revisions] = await Promise.all([
218
218
  store.roster.memberStays(chatId),
219
219
  store.roster.profileRevisions(chatId),
@@ -222,7 +222,7 @@ export const chatsService = (deps) => ({
222
222
  },
223
223
  trackedChat: async (chat) => {
224
224
  const store = await accountStore(deps);
225
- const chatId = await chatIdIn(deps.messenger, chat, store);
225
+ const chatId = await chatIdIn(deps.messenger, chat, store, { includeLeft: true });
226
226
  const entry = (await store.roster.trackedChats()).find((one) => one.chatId === chatId);
227
227
  const since = new Date(Date.now() - TRACKED_DAYS * 86_400_000).toISOString().slice(0, 10);
228
228
  return {
@@ -37,7 +37,7 @@ export const BATCH_SIZE = { default: 50, min: 10, max: 200 };
37
37
  export const conversationsService = (deps) => {
38
38
  const found = async (chat) => {
39
39
  const store = await accountStore(deps);
40
- return { store, chatId: await chatIdIn(deps.messenger, chat, store) };
40
+ return { store, chatId: await chatIdIn(deps.messenger, chat, store, { includeLeft: true }) };
41
41
  };
42
42
  const notBuilt = (chatId) => new CliError("not_found", `chat ${chatId} has no conversations yet — \`${typedCommand(deps.messenger.app.command, `conversations build --chat ${chatId}`)}\``);
43
43
  return {
@@ -80,7 +80,7 @@ export const embeddingsMaintenance = (deps) => {
80
80
  const found = async (chat) => {
81
81
  const store = await deps.store();
82
82
  const account = await deps.account();
83
- const chatId = await storedChatId(deps.messenger, chat, store, account);
83
+ const chatId = await storedChatId(deps.messenger, chat, store, account, { includeLeft: true });
84
84
  if (!(await store.conversations.conversationState(account, chatId))?.builtAt)
85
85
  throw notBuiltError(deps, chatId);
86
86
  return { store, account, chatId };
@@ -198,7 +198,7 @@ export const embeddingsMaintenance = (deps) => {
198
198
  const store = await deps.store();
199
199
  const account = await deps.account();
200
200
  const target = embeddingTarget(deps, choice);
201
- const chatId = chat === undefined ? undefined : await storedChatId(deps.messenger, chat, store, account);
201
+ const chatId = chat === undefined ? undefined : await storedChatId(deps.messenger, chat, store, account, { includeLeft: true });
202
202
  const chats = await readinessOf(deps, chatId, target.key);
203
203
  if (chatId !== undefined)
204
204
  return { model: target.id, chats };
@@ -211,7 +211,7 @@ export const embeddingsMaintenance = (deps) => {
211
211
  const target = embeddingTarget(deps, choice);
212
212
  if (embed && !build && !target.installed)
213
213
  throw notDownloaded(deps, target.id);
214
- const chatId = chat === undefined ? undefined : await storedChatId(deps.messenger, chat, store, account);
214
+ const chatId = chat === undefined ? undefined : await storedChatId(deps.messenger, chat, store, account, { includeLeft: true });
215
215
  const scan = async () => ({
216
216
  chats: (await readinessOf(deps, chatId, target.key)).sort(byOldestBuild),
217
217
  unbuilt: chatId === undefined ? await store.vectors.unbuiltGroups(account) : [],
@@ -278,7 +278,7 @@ export const embeddingsMaintenance = (deps) => {
278
278
  clear: async (chat, choice) => {
279
279
  const store = await deps.store();
280
280
  const account = await deps.account();
281
- const chatId = await storedChatId(deps.messenger, chat, store, account);
281
+ const chatId = await storedChatId(deps.messenger, chat, store, account, { includeLeft: true });
282
282
  const cleared = await store.vectors.clearVectors(account, chatId, choice === undefined ? undefined : embeddingTarget(deps, choice).key);
283
283
  return { chat: chatId, cleared };
284
284
  },
@@ -235,7 +235,7 @@ export const embeddingsSearch = (deps) => {
235
235
  const store = await deps.store();
236
236
  const account = await deps.account();
237
237
  const target = embeddingTarget(deps, choice);
238
- const chatId = await storedChatId(deps.messenger, chat, store, account);
238
+ const chatId = await storedChatId(deps.messenger, chat, store, account, { includeLeft: true });
239
239
  const [source] = await store.conversations.conversationsOfMessages(account, [{ chatId, messageId: message }]);
240
240
  if (!source) {
241
241
  if (!(await store.conversations.conversationState(account, chatId))?.builtAt)
@@ -7,7 +7,7 @@ export const readEvidencePacket = async (store, account, { chat, limit, before }
7
7
  throw new CliError("validation_error", "evidence message limit must be an integer from 1 to 100");
8
8
  if (before !== undefined && !before.trim())
9
9
  throw new CliError("validation_error", "evidence before id must not be empty");
10
- const chatId = await storedChatId(messenger, chat, store, account);
10
+ const chatId = await storedChatId(messenger, chat, store, account, { includeLeft: true });
11
11
  const page = await store.messages.messages(account, chatId, { limit, ...(before === undefined ? {} : { before }) });
12
12
  const items = page.items.toReversed();
13
13
  const packet = prepareEvidencePacket({
@@ -10,8 +10,13 @@ export type Saved = Partial<Pick<MessengerTraits, "savedChatId" | "app">>;
10
10
  * in store mode the store is the only source, so an empty answer would pass for "nothing was said".
11
11
  */
12
12
  export declare const readChatId: (deps: ServiceDeps, reference: string, store: MessageStore, account: AccountKey) => Promise<string>;
13
- /** A chat as typed, found among the stored chats the way an adapter finds it among its own. */
14
- export declare const storedChatId: (messenger: Saved, reference: string, store: MessageStore, account: AccountKey) => Promise<string>;
13
+ /**
14
+ * A chat as typed, found among the stored chats the way an adapter finds it among its own; `includeLeft` tries the
15
+ * left ones last, for a read of their kept history.
16
+ */
17
+ export declare const storedChatId: (messenger: Saved, reference: string, store: MessageStore, account: AccountKey, { includeLeft }?: {
18
+ includeLeft?: boolean;
19
+ }) => Promise<string>;
15
20
  /** `storedChatId`, among the chats of the account the store is bound to; `includeLeft` tries the left ones last. */
16
21
  export declare const chatIdIn: (messenger: Saved, reference: string, store: AccountStore, { includeLeft }?: {
17
22
  includeLeft?: boolean;
@@ -22,24 +22,37 @@ export const readChatId = async (deps, reference, store, account) => {
22
22
  return chatId;
23
23
  };
24
24
  const LEFT_EMPTY = "you left this chat, and the store holds none of its messages";
25
- /** A chat as typed, found among the stored chats the way an adapter finds it among its own. */
26
- export const storedChatId = async (messenger, reference, store, account) => chatIdAmong(messenger, reference, account, {
27
- held: () => store.chats.chats(account, {}),
28
- renamed: (id) => store.chats.renamedChatId(account, id),
29
- });
30
- /** `storedChatId`, then among the chats the account left: their history is kept, so a read of it still finds them. */
31
- const keptChatId = (messenger, reference, store, account) => storedChatId(messenger, reference, store, account).catch((error) => {
32
- if (codeOf(error) !== "not_found")
33
- throw error;
34
- return chatIdAmong(messenger, reference, account, {
35
- held: () => store.chats.chats(account, { includeLeft: true }),
36
- renamed: (id) => store.chats.renamedChatId(account, id),
25
+ /**
26
+ * A chat as typed, found among the stored chats the way an adapter finds it among its own; `includeLeft` tries the
27
+ * left ones last, for a read of their kept history.
28
+ */
29
+ export const storedChatId = (messenger, reference, store, account, { includeLeft = false } = {}) => {
30
+ const renamed = (id) => store.chats.renamedChatId(account, id);
31
+ const among = chatIdAmong(messenger, reference, account, {
32
+ held: () => store.chats.chats(account, {}),
33
+ renamed,
34
+ left: async (id) => leftIn(await store.chats.chats(account, byId(id))),
37
35
  });
38
- });
36
+ if (!includeLeft)
37
+ return among;
38
+ return among.catch((error) => {
39
+ if (codeOf(error) !== "not_found")
40
+ throw error;
41
+ return chatIdAmong(messenger, reference, account, {
42
+ held: () => store.chats.chats(account, { includeLeft: true }),
43
+ renamed,
44
+ });
45
+ });
46
+ };
47
+ const keptChatId = (messenger, reference, store, account) => storedChatId(messenger, reference, store, account, { includeLeft: true });
39
48
  /** `storedChatId`, among the chats of the account the store is bound to; `includeLeft` tries the left ones last. */
40
49
  export const chatIdIn = (messenger, reference, store, { includeLeft = false } = {}) => {
41
50
  const renamed = (id) => store.chats.renamedChatId(id);
42
- const among = chatIdAmong(messenger, reference, store.account, { held: () => store.chats.chats({}), renamed });
51
+ const among = chatIdAmong(messenger, reference, store.account, {
52
+ held: () => store.chats.chats({}),
53
+ renamed,
54
+ left: async (id) => leftIn(await store.chats.chats(byId(id))),
55
+ });
43
56
  if (!includeLeft)
44
57
  return among;
45
58
  return among.catch((error) => {
@@ -51,13 +64,24 @@ export const chatIdIn = (messenger, reference, store, { includeLeft = false } =
51
64
  });
52
65
  });
53
66
  };
54
- const chatIdAmong = async (messenger, reference, account, { held, renamed }) => {
67
+ const byId = (id) => ({
68
+ includeLeft: true,
69
+ limit: 1,
70
+ where: { sql: "chats.external_id = ?", params: [id] },
71
+ });
72
+ const leftIn = ({ items }) => items.some((one) => one.membershipState === "left");
73
+ /** `left` is given where a left chat is refused; it lets a numeric id the store does not hold through as typed. */
74
+ const chatIdAmong = async (messenger, reference, account, { held, renamed, left, }) => {
55
75
  const trimmed = reference.trim();
56
76
  if (messenger.savedChatId && ["me", "self", "saved"].includes(trimmed.toLowerCase())) {
57
77
  return messenger.savedChatId(account);
58
78
  }
59
- if (/^-?\d+$/.test(trimmed))
60
- return (await renamed(trimmed)) ?? trimmed;
79
+ if (/^-?\d+$/.test(trimmed)) {
80
+ const id = (await renamed(trimmed)) ?? trimmed;
81
+ if (await left?.(id))
82
+ throw new CliError("not_found", `you left chat ${id}`);
83
+ return id;
84
+ }
61
85
  const chats = (await held()).items;
62
86
  const exact = chats.find((one) => one.id === trimmed);
63
87
  if (exact)
@@ -90,7 +114,7 @@ const missing = (error) => {
90
114
  export const chatAmong = async (messenger, store, accounts, reference) => {
91
115
  const [only] = accounts;
92
116
  if (accounts.length === 1 && only)
93
- return { account: only, chatId: await storedChatId(messenger, reference, store, only) };
117
+ return { account: only, chatId: await storedChatId(messenger, reference, store, only, { includeLeft: true }) };
94
118
  const found = (await Promise.all(accounts.map(async (account) => {
95
119
  const held = (await store.chats.chats(account, {})).items;
96
120
  const chatId = await storedChatId(messenger, reference, store, account).catch(missing);
@@ -158,7 +158,7 @@ export const messagesService = (deps) => {
158
158
  if (account.provider !== deps.messenger.provider ||
159
159
  (target.account !== undefined && target.account !== account.account))
160
160
  throw new CliError("validation_error", "that locator belongs to another account; select its profile first");
161
- return readThreadContext(store, account, await storedChatId(deps.messenger, target.chat, store, account), target.message, options);
161
+ return readThreadContext(store, account, await storedChatId(deps.messenger, target.chat, store, account, { includeLeft: true }), target.message, options);
162
162
  }),
163
163
  comments: async (chat, typedPost, window) => {
164
164
  const post = typedPost.trim();
@@ -3,10 +3,10 @@ import { CHANNEL_TAG_RULES_VERSION, classifyChannel } from "../domain/channel-ta
3
3
  import { capability } from "../ports/messenger.js";
4
4
  import { storedChatId } from "./message-resolution.js";
5
5
  export const metadataService = (deps) => {
6
- const held = async (reference) => {
6
+ const held = async (reference, { includeLeft = false } = {}) => {
7
7
  const store = await deps.store();
8
8
  const account = await deps.account();
9
- const chatId = await storedChatId(deps.messenger, reference, store, account);
9
+ const chatId = await storedChatId(deps.messenger, reference, store, account, { includeLeft });
10
10
  return { store, account, chatId };
11
11
  };
12
12
  const refresh = async (reference) => {
@@ -44,7 +44,7 @@ export const metadataService = (deps) => {
44
44
  return missing;
45
45
  },
46
46
  get: async (reference) => {
47
- const { store, account, chatId } = await held(reference);
47
+ const { store, account, chatId } = await held(reference, { includeLeft: true });
48
48
  return { chatId, metadata: (await store.chats.chatMetadata(account, chatId)) ?? null };
49
49
  },
50
50
  refresh,
@@ -80,7 +80,7 @@ export const peopleService = (deps) => {
80
80
  const store = await deps.store();
81
81
  const account = await deps.account();
82
82
  if (!fetch) {
83
- const ids = await Promise.all(chats.map((chat) => storedChatId(deps.messenger, chat, store, account)));
83
+ const ids = await Promise.all(chats.map((chat) => storedChatId(deps.messenger, chat, store, account, { includeLeft: true })));
84
84
  return personMessages(store, account, person, {
85
85
  chats: ids,
86
86
  ...(limit ? { limit } : {}),
@@ -80,7 +80,7 @@ export const picturesService = (deps) => {
80
80
  throw new CliError("validation_error", PICTURES_OFF);
81
81
  const store = await deps.store();
82
82
  const account = await deps.account();
83
- const chatId = chat === undefined ? undefined : await storedChatId(deps.messenger, chat, store, account);
83
+ const chatId = chat === undefined ? undefined : await storedChatId(deps.messenger, chat, store, account, { includeLeft: true });
84
84
  const use = await embedder();
85
85
  const result = { model: model.key, embedded: 0, skipped: 0, failed: 0, more: false };
86
86
  const pending = [];
@@ -177,7 +177,7 @@ export const picturesService = (deps) => {
177
177
  return {};
178
178
  const store = await deps.store();
179
179
  const account = await deps.account();
180
- const chatId = chat === undefined ? undefined : await storedChatId(deps.messenger, chat, store, account);
180
+ const chatId = chat === undefined ? undefined : await storedChatId(deps.messenger, chat, store, account, { includeLeft: true });
181
181
  let vector;
182
182
  try {
183
183
  const use = await embedder();
@@ -26,7 +26,7 @@ export const retentionService = (deps) => ({
26
26
  limit: query.limit ?? 20,
27
27
  };
28
28
  const store = await deps.store(), account = await deps.account();
29
- const chatId = await storedChatId(deps.messenger, chat, store, account);
29
+ const chatId = await storedChatId(deps.messenger, chat, store, account, { includeLeft: true });
30
30
  const result = await store.roster.retention(account, chatId, options);
31
31
  return {
32
32
  ...result,
@@ -71,7 +71,7 @@ const targetOf = async (deps, store, account, { chat, contact, message, task })
71
71
  }
72
72
  if (chat === undefined)
73
73
  throw new CliError("validation_error", ONE_TARGET);
74
- const chatId = await storedChatId(deps.messenger, chat, store, account);
74
+ const chatId = await storedChatId(deps.messenger, chat, store, account, { includeLeft: true });
75
75
  return message === undefined ? { type: "chat", chatId } : { type: "message", chatId, messageId: message.trim() };
76
76
  };
77
77
  const viewOf = (account, target) => target.type === "message"
@@ -149,7 +149,7 @@ export const tasksService = (deps, categories) => {
149
149
  const categoryOf = async (store, name) => taskCategoryOf(await listOf(store))(name);
150
150
  const inStore = async (work) => work(await deps.store(), await deps.account());
151
151
  const service = (store) => createTaskService({ store: store.tasks });
152
- const groupOf = async (store, account, chat) => chat === undefined ? undefined : storedChatId(deps.messenger, chat, store, account);
152
+ const groupOf = async (store, account, chat) => chat === undefined ? undefined : storedChatId(deps.messenger, chat, store, account, { includeLeft: true });
153
153
  const refuseBare = (target) => {
154
154
  throw new CliError("validation_error", `no task ${singleLine(target)} — write task:${singleLine(target)} for a task`);
155
155
  };
@@ -212,7 +212,7 @@ export const tasksService = (deps, categories) => {
212
212
  const groups = new Map();
213
213
  for (const node of query ? walkQuery(query.root) : [])
214
214
  if (node.field === "chat" && node.operator !== "wildcard" && !groups.has(node.value))
215
- groups.set(node.value, await storedChatId(deps.messenger, node.value, store, account));
215
+ groups.set(node.value, await storedChatId(deps.messenger, node.value, store, account, { includeLeft: true }));
216
216
  const where = query ? queryFilter(query, taskColumns(groups), { command: "tasks list" }) : undefined;
217
217
  const rows = await store.tasks.query(taskAccount(account), where, { limit: limit + 1, offset });
218
218
  const shown = rows.slice(0, limit);
@@ -443,7 +443,7 @@ export const tasksService = (deps, categories) => {
443
443
  type: "chat",
444
444
  provider: account.provider,
445
445
  account: account.account,
446
- chat: await storedChatId(deps.messenger, chat, store, account),
446
+ chat: await storedChatId(deps.messenger, chat, store, account, { includeLeft: true }),
447
447
  })));
448
448
  const origin = by === "owner" ? "owner" : "suggested";
449
449
  for (const target of [...written, ...chatRefs]) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-core",
3
- "version": "0.367.0",
3
+ "version": "0.368.0",
4
4
  "description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Viacheslav Ptsarev",