@batalabs/virlow-mcp-core 3.123.0 → 3.125.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.
@@ -8,6 +8,24 @@ import { WorkspaceKeyring } from './keyring.js';
8
8
  * `truncated` tells the caller when the account holds more than this. Also
9
9
  * bounds listVisibleRows(), which backs mention-matching and backlinks(). */
10
10
  export declare const SEARCH_SCAN_CAP = 500;
11
+ /**
12
+ * An Exposure plus a fingerprint of the folder list it was computed from.
13
+ *
14
+ * The fingerprint is what tells the paged row cache whether the rows it holds
15
+ * were filtered under the same folder settings the current call is asking
16
+ * about. It rides on the exposure object so it can never be paired with the
17
+ * wrong one: an instance field would be overwritten by any concurrent call
18
+ * that reached listFolders() second.
19
+ */
20
+ /** What the to-do tools need to know about folders. See NotesService.todoScope. */
21
+ export interface TodoScope {
22
+ /** Whether the connector may see what is in this folder. */
23
+ allows(folderId: string): boolean;
24
+ /** The shared workspace a folder belongs to; null for a personal one. */
25
+ workspaceOf(folderId: string | null): string | null;
26
+ /** The folder's name as the person sees it. */
27
+ folderName(folderId: string): Promise<string>;
28
+ }
11
29
  /** One property a setProperties() call actually changed, said the way the
12
30
  * caller asked for it: the definition's name, and option names rather than
13
31
  * the option ids stored in the note. A caller that had to read ids back to
@@ -290,6 +308,13 @@ export declare class NotesService {
290
308
  }>;
291
309
  withheld: number;
292
310
  }>;
311
+ /**
312
+ * Where shared to-dos may come from, for the to-do tools: a shared list
313
+ * lives in its workspace's root folder, so it is open exactly when that
314
+ * folder is (the same rule as the workspace's notes), and is read and
315
+ * written under the workspace key. Computed fresh for each call.
316
+ */
317
+ todoScope(): Promise<TodoScope>;
293
318
  /** Folder exposure for this request. Not cached across calls: the user can
294
319
  * flip the switch in the app at any moment and the next tool call must
295
320
  * respect it. */
package/dist/cjs/notes.js CHANGED
@@ -849,6 +849,25 @@ class NotesService {
849
849
  }
850
850
  return { folders, notes, withheld };
851
851
  }
852
+ /**
853
+ * Where shared to-dos may come from, for the to-do tools: a shared list
854
+ * lives in its workspace's root folder, so it is open exactly when that
855
+ * folder is (the same rule as the workspace's notes), and is read and
856
+ * written under the workspace key. Computed fresh for each call.
857
+ */
858
+ async todoScope() {
859
+ const { folders } = await this.api.listFolders();
860
+ const exposure = await this.exposureOf(folders);
861
+ const byId = new Map(folders.map((f) => [f.id, f]));
862
+ return {
863
+ allows: (folderId) => exposure.allows(folderId),
864
+ workspaceOf: (folderId) => exposure.workspaceOf(folderId),
865
+ folderName: async (folderId) => {
866
+ const folder = byId.get(folderId);
867
+ return folder ? this.folderName(this.vault.session.key, folder) : 'Shared workspace';
868
+ },
869
+ };
870
+ }
852
871
  /** Folder exposure for this request. Not cached across calls: the user can
853
872
  * flip the switch in the app at any moment and the next tool call must
854
873
  * respect it. */
@@ -1,6 +1,8 @@
1
1
  import type { VirlowApi } from './api.js';
2
+ import type { WorkspaceKeyring } from './keyring.js';
3
+ import type { TodoScope } from './notes.js';
2
4
  import { type Repeat, type Task, type TodoList } from './todo-format.js';
3
- import type { Vault } from './vault.js';
5
+ import { type Vault } from './vault.js';
4
6
  export interface ListTodosArgs {
5
7
  list?: string;
6
8
  include_completed?: boolean;
@@ -39,20 +41,36 @@ export interface CreateTodoListArgs {
39
41
  interface Loaded {
40
42
  tasks: Task[];
41
43
  lists: TodoList[];
44
+ /** Each shared list's workspace name, by list id. */
45
+ sharedIn: Map<string, string>;
46
+ /** The key version each shared row is under, by id. */
47
+ versions: Map<string, number>;
48
+ }
49
+ /** What reading and writing shared to-dos needs; without it only the person's own are seen. */
50
+ export interface SharedTodos {
51
+ scope: () => Promise<TodoScope>;
52
+ keyring: WorkspaceKeyring;
42
53
  }
43
54
  export declare class TodoService {
44
55
  private readonly api;
45
56
  private readonly vault;
46
57
  /** The clock, for "today" and reminder checks. Tests pass a fixed one. */
47
58
  private readonly now;
59
+ private readonly shared;
48
60
  constructor(api: VirlowApi, vault: Vault,
49
61
  /** The clock, for "today" and reminder checks. Tests pass a fixed one. */
50
- now?: () => Date);
62
+ now?: () => Date, shared?: SharedTodos | null);
51
63
  private today;
52
64
  /** The key, or the vault's own "ask the user to unlock" error. */
53
65
  private key;
54
66
  private fetchAll;
55
67
  private decrypt;
68
+ /**
69
+ * The key a row is under, or null to leave it out: a shared row whose
70
+ * workspace folder is not open to AI, or whose key does not open. A lock
71
+ * is not a row to skip; it propagates.
72
+ */
73
+ private keyForRow;
56
74
  load(): Promise<Loaded>;
57
75
  /** A list by id or name (case-insensitive). Never guesses between two
58
76
  * lists with one name, and never makes one up. */
@@ -66,7 +84,15 @@ export declare class TodoService {
66
84
  private validDay;
67
85
  private validRemind;
68
86
  private encrypted;
87
+ /**
88
+ * Seals a task's text and details for where it lives: the account key for
89
+ * the person's own, the workspace's current key (never older than the row
90
+ * already is) for a shared one, with that version sent along.
91
+ */
92
+ private seal;
69
93
  private createTask;
94
+ /** My Day, Important and reminders are each person's own, so a shared task never carries them. */
95
+ private refusePersonal;
70
96
  private channels;
71
97
  /** Cancels the task's pending reminders. */
72
98
  private clearReminders;
package/dist/cjs/todos.js CHANGED
@@ -2,7 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TodoService = void 0;
4
4
  const virlow_crypto_1 = require("@batalabs/virlow-crypto");
5
+ const exposure_js_1 = require("./exposure.js");
5
6
  const todo_format_js_1 = require("./todo-format.js");
7
+ const vault_js_1 = require("./vault.js");
6
8
  /**
7
9
  * The to-do list over the API, for the MCP tools.
8
10
  *
@@ -14,6 +16,13 @@ const todo_format_js_1 = require("./todo-format.js");
14
16
  *
15
17
  * Nothing is cached: a to-do list is small, and the apps change it all the
16
18
  * time, so every call reads the current state.
19
+ *
20
+ * A list shared with a workspace lives in the workspace's root folder, under
21
+ * the workspace key, with its tasks beside it. It is seen exactly when that
22
+ * folder is open to AI (the rule for the workspace's notes), and read and
23
+ * written with the workspace key. My Day, Important and reminders are each
24
+ * person's own, so they are never set on a shared task; moving a list or
25
+ * task in or out of a workspace is left to the apps.
17
26
  */
18
27
  /** Smart list names as a tool takes them. */
19
28
  const SMART_ALIASES = {
@@ -31,12 +40,14 @@ class TodoService {
31
40
  api;
32
41
  vault;
33
42
  now;
43
+ shared;
34
44
  constructor(api, vault,
35
45
  /** The clock, for "today" and reminder checks. Tests pass a fixed one. */
36
- now = () => new Date()) {
46
+ now = () => new Date(), shared = null) {
37
47
  this.api = api;
38
48
  this.vault = vault;
39
49
  this.now = now;
50
+ this.shared = shared;
40
51
  }
41
52
  today() {
42
53
  return (0, todo_format_js_1.dayKey)(this.now());
@@ -69,32 +80,85 @@ class TodoService {
69
80
  return null;
70
81
  }
71
82
  }
83
+ /**
84
+ * The key a row is under, or null to leave it out: a shared row whose
85
+ * workspace folder is not open to AI, or whose key does not open. A lock
86
+ * is not a row to skip; it propagates.
87
+ */
88
+ async keyForRow(accountKey, row, scope) {
89
+ if (!(0, exposure_js_1.isSharedNote)(row))
90
+ return { key: accountKey, workspaceId: null };
91
+ if (!this.shared || !scope || !row.folderId || !scope.allows(row.folderId))
92
+ return null;
93
+ const workspaceId = scope.workspaceOf(row.folderId);
94
+ // The row's own folder field and the folder list must agree on its
95
+ // workspace, as for notes; one that does not is not opened.
96
+ const sent = row.folder?.workspaceId;
97
+ if (!workspaceId || (sent !== undefined && sent !== workspaceId))
98
+ return null;
99
+ try {
100
+ return { key: await this.shared.keyring.forRead(accountKey, row.keyVersion, workspaceId), workspaceId };
101
+ }
102
+ catch (err) {
103
+ if (err instanceof vault_js_1.LockedError)
104
+ throw err;
105
+ return null;
106
+ }
107
+ }
72
108
  async load() {
73
- const key = this.key();
74
- const [taskRows, listRows] = await Promise.all([this.fetchAll(todo_format_js_1.TODO_NOTE_TYPE), this.fetchAll(todo_format_js_1.TODO_LIST_NOTE_TYPE)]);
109
+ const accountKey = this.key();
110
+ const [taskRows, listRows, scope] = await Promise.all([
111
+ this.fetchAll(todo_format_js_1.TODO_NOTE_TYPE),
112
+ this.fetchAll(todo_format_js_1.TODO_LIST_NOTE_TYPE),
113
+ this.shared ? this.shared.scope() : Promise.resolve(null),
114
+ ]);
115
+ const versions = new Map();
116
+ const open = async (row) => {
117
+ const found = await this.keyForRow(accountKey, row, scope);
118
+ if (!found)
119
+ return null;
120
+ const opened = await this.decrypt(found.key, row);
121
+ if (!opened)
122
+ return null;
123
+ if (found.workspaceId)
124
+ versions.set(row.id, row.keyVersion);
125
+ return { opened, workspaceId: found.workspaceId, folderId: found.workspaceId ? row.folderId : null };
126
+ };
75
127
  const tasks = [];
76
128
  for (const row of taskRows) {
77
- const opened = await this.decrypt(key, row);
78
- if (!opened)
129
+ const got = await open(row);
130
+ if (!got)
79
131
  continue;
80
132
  tasks.push({
81
133
  id: row.id,
82
- title: opened.title,
83
- details: (0, todo_format_js_1.parseDetails)(opened.content),
134
+ title: got.opened.title,
135
+ details: (0, todo_format_js_1.parseDetails)(got.opened.content),
84
136
  done: row.archived === true,
85
137
  createdAt: row.createdAt,
86
138
  updatedAt: row.updatedAt,
139
+ folderId: got.folderId,
140
+ workspaceId: got.workspaceId,
87
141
  });
88
142
  }
89
143
  const lists = [];
144
+ const sharedIn = new Map();
90
145
  for (const row of listRows) {
91
- const opened = await this.decrypt(key, row);
92
- if (!opened)
146
+ const got = await open(row);
147
+ if (!got)
93
148
  continue;
94
- lists.push({ id: row.id, name: opened.title, meta: (0, todo_format_js_1.parseListMeta)(opened.content), createdAt: row.createdAt });
149
+ lists.push({
150
+ id: row.id,
151
+ name: got.opened.title,
152
+ meta: (0, todo_format_js_1.parseListMeta)(got.opened.content),
153
+ createdAt: row.createdAt,
154
+ folderId: got.folderId,
155
+ workspaceId: got.workspaceId,
156
+ });
157
+ if (got.folderId && scope)
158
+ sharedIn.set(row.id, await scope.folderName(got.folderId));
95
159
  }
96
160
  lists.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
97
- return { tasks, lists };
161
+ return { tasks, lists, sharedIn, versions };
98
162
  }
99
163
  /** A list by id or name (case-insensitive). Never guesses between two
100
164
  * lists with one name, and never makes one up. */
@@ -121,15 +185,19 @@ class TodoService {
121
185
  return smart;
122
186
  return (0, todo_format_js_1.listKey)(this.resolveList(lists, ref).id);
123
187
  }
124
- listName(lists, id) {
188
+ listName(lists, id, sharedIn) {
125
189
  if (id === null)
126
190
  return 'Tasks';
127
- return lists.find((l) => l.id === id)?.name ?? 'Tasks';
191
+ const list = lists.find((l) => l.id === id);
192
+ if (!list)
193
+ return 'Tasks';
194
+ const where = sharedIn?.get(list.id);
195
+ return where ? `${list.name} (shared in ${where})` : list.name;
128
196
  }
129
- describe(task, lists) {
197
+ describe(task, lists, sharedIn) {
130
198
  const d = task.details;
131
199
  const today = this.today();
132
- const parts = [`list: ${this.listName(lists, d.list)}`];
200
+ const parts = [`list: ${this.listName(lists, d.list, sharedIn)}`];
133
201
  if (d.due)
134
202
  parts.push(`due ${d.due}${!task.done && d.due < today ? ' (overdue)' : ''}`);
135
203
  if (d.myDay === today)
@@ -155,7 +223,7 @@ class TodoService {
155
223
  return lines.join('\n');
156
224
  }
157
225
  async listTodos(args) {
158
- const { tasks, lists } = await this.load();
226
+ const { tasks, lists, sharedIn } = await this.load();
159
227
  const today = this.today();
160
228
  const scope = this.scopeOf(lists, args.list);
161
229
  const query = args.query?.trim().toLowerCase();
@@ -171,27 +239,33 @@ class TodoService {
171
239
  .sort((a, b) => (b.details.completedAt ?? b.updatedAt).localeCompare(a.details.completedAt ?? a.updatedAt))
172
240
  : [];
173
241
  const shown = [...open.filter(matches), ...done.filter(matches)];
174
- const where = scope ? this.scopeName(lists, scope) : 'every list';
242
+ const where = scope ? this.scopeName(lists, scope, sharedIn) : 'every list';
175
243
  if (shown.length === 0) {
176
244
  return query
177
245
  ? `No to-dos in ${where} match "${args.query}".`
178
246
  : `No ${args.include_completed ? '' : 'open '}to-dos in ${where}.`;
179
247
  }
180
248
  const head = `${open.filter(matches).length} open${args.include_completed ? `, ${done.filter(matches).length} completed` : ''} in ${where} (today is ${today}):`;
181
- return [head, ...shown.map((t) => this.describe(t, lists))].join('\n');
249
+ return [head, ...shown.map((t) => this.describe(t, lists, sharedIn))].join('\n');
182
250
  }
183
- scopeName(lists, scope) {
251
+ scopeName(lists, scope, sharedIn) {
184
252
  const smart = todo_format_js_1.SMART_LISTS.find((s) => s.id === scope);
185
253
  if (smart)
186
254
  return smart.label;
187
- return this.listName(lists, scope.slice(5));
255
+ return this.listName(lists, scope.slice(5), sharedIn);
188
256
  }
189
257
  async listLists() {
190
- const { tasks, lists } = await this.load();
258
+ const { tasks, lists, sharedIn } = await this.load();
191
259
  const today = this.today();
192
260
  const lines = todo_format_js_1.SMART_LISTS.map((s) => `- ${s.label} (${s.id === 'myday' ? 'my_day' : s.id}): ${(0, todo_format_js_1.openTasks)(tasks, s.id, today).length} open`);
193
- lines.push(...lists.map((l) => `- ${l.meta.emoji ? `${l.meta.emoji} ` : ''}${l.name} (id: ${l.id}): ${(0, todo_format_js_1.openTasks)(tasks, (0, todo_format_js_1.listKey)(l.id), today).length} open`));
194
- return [`Smart lists, then the person's own lists (today is ${today}):`, ...lines].join('\n');
261
+ lines.push(...lists.map((l) => {
262
+ const where = sharedIn.get(l.id);
263
+ return `- ${l.meta.emoji ? `${l.meta.emoji} ` : ''}${l.name} (id: ${l.id}${where ? `, shared in ${where}` : ''}): ${(0, todo_format_js_1.openTasks)(tasks, (0, todo_format_js_1.listKey)(l.id), today).length} open`;
264
+ }));
265
+ const note = sharedIn.size
266
+ ? ' A shared list is seen by everyone in its workspace; My Day, Important and reminders stay personal and are not set on its tasks.'
267
+ : '';
268
+ return [`Smart lists, then the person's lists (today is ${today}):${note}`, ...lines].join('\n');
195
269
  }
196
270
  // MARK: Writing
197
271
  validDay(value, field) {
@@ -208,14 +282,40 @@ class TodoService {
208
282
  throw new Error(`remind_at must be in the future, got ${value}.`);
209
283
  return at;
210
284
  }
211
- async encrypted(title, content) {
212
- const key = this.key();
285
+ async encrypted(title, content, key = this.key()) {
213
286
  const salt = (0, virlow_crypto_1.bytesToBase64)(crypto.getRandomValues(new Uint8Array(32)));
214
287
  const { encryptedTitle, encryptedContent, iv } = await (0, virlow_crypto_1.encryptNoteFields)(key, title, content);
215
288
  return { title: '[ENCRYPTED]', content: '[ENCRYPTED]', encryptedTitle, encryptedContent, iv, salt };
216
289
  }
217
- async createTask(text, details) {
218
- return this.api.createNote({ ...(await this.encrypted(text, (0, todo_format_js_1.serializeDetails)(details))), type: todo_format_js_1.TODO_NOTE_TYPE, folderId: null });
290
+ /**
291
+ * Seals a task's text and details for where it lives: the account key for
292
+ * the person's own, the workspace's current key (never older than the row
293
+ * already is) for a shared one, with that version sent along.
294
+ */
295
+ async seal(text, details, workspaceId, atLeast = null) {
296
+ if (!workspaceId)
297
+ return this.encrypted(text, (0, todo_format_js_1.serializeDetails)(details));
298
+ if (!this.shared)
299
+ throw new Error('This to-do is in a shared workspace, which this connection cannot write to.');
300
+ const choice = await this.shared.keyring.forWrite(this.key(), workspaceId, { atLeast });
301
+ if (choice.keyVersion === null)
302
+ throw new Error('A shared to-do needs its workspace key.');
303
+ return { ...(await this.encrypted(text, (0, todo_format_js_1.serializeDetails)(details), choice.key)), keyVersion: choice.keyVersion };
304
+ }
305
+ async createTask(text, details, where = { folderId: null, workspaceId: null }) {
306
+ return this.api.createNote({
307
+ ...(await this.seal(text, details, where.workspaceId)),
308
+ type: todo_format_js_1.TODO_NOTE_TYPE,
309
+ folderId: where.workspaceId ? where.folderId : null,
310
+ });
311
+ }
312
+ /** My Day, Important and reminders are each person's own, so a shared task never carries them. */
313
+ refusePersonal(list, sharedIn, asked) {
314
+ if (asked.length === 0)
315
+ return;
316
+ const where = sharedIn.get(list.id) ?? 'a shared workspace';
317
+ throw new Error(`"${list.name}" is shared in ${where}, and ${asked.join(', ')} ${asked.length > 1 ? 'are' : 'is'} personal, ` +
318
+ 'so it is not set on a shared task. Leave it out, or use one of the person\'s own lists.');
219
319
  }
220
320
  async channels() {
221
321
  try {
@@ -246,11 +346,19 @@ class TodoService {
246
346
  throw new Error(`repeat must be one of ${todo_format_js_1.REPEATS.join(', ')}.`);
247
347
  }
248
348
  const remindAt = args.remind_at !== undefined ? this.validRemind(args.remind_at) : null;
249
- const { lists } = await this.load();
349
+ const { lists, sharedIn } = await this.load();
250
350
  const today = this.today();
251
351
  const details = (0, todo_format_js_1.parseDetails)('');
252
- if (args.list !== undefined && args.list.trim() !== '')
253
- details.list = this.resolveList(lists, args.list).id;
352
+ const list = args.list !== undefined && args.list.trim() !== '' ? this.resolveList(lists, args.list) : null;
353
+ if (list)
354
+ details.list = list.id;
355
+ if (list?.workspaceId) {
356
+ this.refusePersonal(list, sharedIn, [
357
+ ...(args.my_day ? ['My Day'] : []),
358
+ ...(args.important ? ['Important'] : []),
359
+ ...(remindAt ? ['a reminder'] : []),
360
+ ]);
361
+ }
254
362
  if (args.due !== undefined)
255
363
  details.due = this.validDay(args.due, 'due');
256
364
  if (args.my_day)
@@ -268,17 +376,32 @@ class TodoService {
268
376
  }
269
377
  if (remindAt)
270
378
  details.remindAt = remindAt.toISOString();
271
- const row = await this.createTask(text, details);
379
+ const where = { folderId: list?.folderId ?? null, workspaceId: list?.workspaceId ?? null };
380
+ const row = await this.createTask(text, details, where);
272
381
  if (remindAt)
273
382
  await this.createReminder(row.id, remindAt);
274
- const task = { id: row.id, title: text, details, done: false, createdAt: row.createdAt, updatedAt: row.updatedAt };
275
- return `Added to-do (id: ${row.id}).\n${this.describe(task, lists)}`;
383
+ const task = { id: row.id, title: text, details, done: false, createdAt: row.createdAt, updatedAt: row.updatedAt, ...where };
384
+ return `Added to-do (id: ${row.id}).\n${this.describe(task, lists, sharedIn)}`;
276
385
  }
277
386
  async updateTodo(args) {
278
- const { tasks, lists } = await this.load();
387
+ const { tasks, lists, sharedIn, versions } = await this.load();
279
388
  const task = tasks.find((t) => t.id === args.id);
280
389
  if (!task)
281
390
  throw new Error(`No to-do with id ${args.id}. Call list_todos to find it.`);
391
+ const workspaceId = task.workspaceId ?? null;
392
+ if (workspaceId) {
393
+ const list = lists.find((l) => l.id === task.details.list) ?? {
394
+ id: '',
395
+ name: 'This to-do',
396
+ meta: (0, todo_format_js_1.parseListMeta)(''),
397
+ createdAt: task.createdAt,
398
+ };
399
+ this.refusePersonal(list, sharedIn, [
400
+ ...(args.my_day ? ['My Day'] : []),
401
+ ...(args.important ? ['Important'] : []),
402
+ ...(args.remind_at ? ['a reminder'] : []),
403
+ ]);
404
+ }
282
405
  const today = this.today();
283
406
  const details = { ...task.details, steps: task.details.steps.map((s) => ({ ...s })) };
284
407
  let text = task.title;
@@ -295,8 +418,17 @@ class TodoService {
295
418
  details.important = args.important;
296
419
  if (args.note !== undefined)
297
420
  details.note = args.note;
298
- if (args.list !== undefined)
299
- details.list = args.list === null || args.list.trim() === '' ? null : this.resolveList(lists, args.list).id;
421
+ if (args.list !== undefined) {
422
+ const target = args.list === null || args.list.trim() === '' ? null : this.resolveList(lists, args.list);
423
+ // A task stays on its side of a workspace: crossing changes its key
424
+ // and who can see it, which the apps do with the whole list.
425
+ if ((target?.workspaceId ?? null) !== workspaceId) {
426
+ throw new Error(workspaceId
427
+ ? 'This to-do is in a shared list. It can move to another list in the same workspace, not to the person\'s own lists.'
428
+ : 'A to-do moves into a shared list only with its whole list, from the Virlow app (Share on the list).');
429
+ }
430
+ details.list = target?.id ?? null;
431
+ }
300
432
  if (args.repeat !== undefined) {
301
433
  if (args.repeat !== null && !todo_format_js_1.REPEATS.includes(args.repeat))
302
434
  throw new Error(`repeat must be one of ${todo_format_js_1.REPEATS.join(', ')}.`);
@@ -330,11 +462,12 @@ class TodoService {
330
462
  if (reopening)
331
463
  details.completedAt = null;
332
464
  const done = completing ? true : reopening ? false : task.done;
333
- const body = await this.encrypted(text, (0, todo_format_js_1.serializeDetails)(details));
465
+ const body = await this.seal(text, details, workspaceId, versions.get(task.id) ?? null);
334
466
  if (completing || reopening)
335
467
  body.archived = done;
336
468
  await this.api.updateNote(task.id, body);
337
- if (remindAt !== undefined) {
469
+ // A shared task has no reminders to change.
470
+ if (remindAt !== undefined && !workspaceId) {
338
471
  await this.clearReminders(task.id);
339
472
  if (remindAt)
340
473
  await this.createReminder(task.id, remindAt);
@@ -342,20 +475,21 @@ class TodoService {
342
475
  const lines = [];
343
476
  if (completing) {
344
477
  // A done task needs no reminder.
345
- await this.clearReminders(task.id);
478
+ if (!workspaceId)
479
+ await this.clearReminders(task.id);
346
480
  const next = (0, todo_format_js_1.nextRepeat)({ ...task, title: text, details }, today);
347
481
  if (next) {
348
482
  const at = next.remindAt ? new Date(next.remindAt) : null;
349
483
  const future = at !== null && at.getTime() > this.now().getTime();
350
484
  const nextDetails = { ...next, remindAt: future ? next.remindAt : null };
351
- const row = await this.createTask(text, nextDetails);
485
+ const row = await this.createTask(text, nextDetails, { folderId: task.folderId ?? null, workspaceId });
352
486
  if (future)
353
487
  await this.createReminder(row.id, at);
354
488
  lines.push(`It repeats ${next.repeat}: the next one (id: ${row.id}) is due ${next.due}.`);
355
489
  }
356
490
  }
357
491
  const updated = { ...task, title: text, details, done };
358
- return [`Updated to-do.`, this.describe(updated, lists), ...lines].join('\n');
492
+ return [`Updated to-do.`, this.describe(updated, lists, sharedIn), ...lines].join('\n');
359
493
  }
360
494
  async createList(args) {
361
495
  const name = args.name?.trim();
package/dist/notes.d.ts CHANGED
@@ -8,6 +8,24 @@ import { WorkspaceKeyring } from './keyring.js';
8
8
  * `truncated` tells the caller when the account holds more than this. Also
9
9
  * bounds listVisibleRows(), which backs mention-matching and backlinks(). */
10
10
  export declare const SEARCH_SCAN_CAP = 500;
11
+ /**
12
+ * An Exposure plus a fingerprint of the folder list it was computed from.
13
+ *
14
+ * The fingerprint is what tells the paged row cache whether the rows it holds
15
+ * were filtered under the same folder settings the current call is asking
16
+ * about. It rides on the exposure object so it can never be paired with the
17
+ * wrong one: an instance field would be overwritten by any concurrent call
18
+ * that reached listFolders() second.
19
+ */
20
+ /** What the to-do tools need to know about folders. See NotesService.todoScope. */
21
+ export interface TodoScope {
22
+ /** Whether the connector may see what is in this folder. */
23
+ allows(folderId: string): boolean;
24
+ /** The shared workspace a folder belongs to; null for a personal one. */
25
+ workspaceOf(folderId: string | null): string | null;
26
+ /** The folder's name as the person sees it. */
27
+ folderName(folderId: string): Promise<string>;
28
+ }
11
29
  /** One property a setProperties() call actually changed, said the way the
12
30
  * caller asked for it: the definition's name, and option names rather than
13
31
  * the option ids stored in the note. A caller that had to read ids back to
@@ -290,6 +308,13 @@ export declare class NotesService {
290
308
  }>;
291
309
  withheld: number;
292
310
  }>;
311
+ /**
312
+ * Where shared to-dos may come from, for the to-do tools: a shared list
313
+ * lives in its workspace's root folder, so it is open exactly when that
314
+ * folder is (the same rule as the workspace's notes), and is read and
315
+ * written under the workspace key. Computed fresh for each call.
316
+ */
317
+ todoScope(): Promise<TodoScope>;
293
318
  /** Folder exposure for this request. Not cached across calls: the user can
294
319
  * flip the switch in the app at any moment and the next tool call must
295
320
  * respect it. */
@@ -1 +1 @@
1
- {"version":3,"file":"notes.d.ts","sourceRoot":"","sources":["../src/notes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"notes.d.ts","sourceRoot":"","sources":["../src/notes.ts"],"names":[],"mappings":"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"}
package/dist/notes.js CHANGED
@@ -846,6 +846,25 @@ export class NotesService {
846
846
  }
847
847
  return { folders, notes, withheld };
848
848
  }
849
+ /**
850
+ * Where shared to-dos may come from, for the to-do tools: a shared list
851
+ * lives in its workspace's root folder, so it is open exactly when that
852
+ * folder is (the same rule as the workspace's notes), and is read and
853
+ * written under the workspace key. Computed fresh for each call.
854
+ */
855
+ async todoScope() {
856
+ const { folders } = await this.api.listFolders();
857
+ const exposure = await this.exposureOf(folders);
858
+ const byId = new Map(folders.map((f) => [f.id, f]));
859
+ return {
860
+ allows: (folderId) => exposure.allows(folderId),
861
+ workspaceOf: (folderId) => exposure.workspaceOf(folderId),
862
+ folderName: async (folderId) => {
863
+ const folder = byId.get(folderId);
864
+ return folder ? this.folderName(this.vault.session.key, folder) : 'Shared workspace';
865
+ },
866
+ };
867
+ }
849
868
  /** Folder exposure for this request. Not cached across calls: the user can
850
869
  * flip the switch in the app at any moment and the next tool call must
851
870
  * respect it. */