synomem 0.1.0 → 0.2.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/ARCHITECTURE.md +2 -2
- package/CHANGELOG.md +37 -1
- package/README.md +28 -15
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +311 -70
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +96 -17
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +333 -76
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +8 -8
- package/dist/import.d.ts +207 -13
- package/dist/import.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +156 -31
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp-server.js +54 -8
- package/dist/mcp-server.js.map +1 -1
- package/dist/ports/repository.d.ts +18 -1
- package/dist/ports/repository.d.ts.map +1 -1
- package/dist/projections.d.ts +18 -1
- package/dist/projections.d.ts.map +1 -1
- package/dist/projections.js +137 -44
- package/dist/projections.js.map +1 -1
- package/dist/remote.d.ts +57 -9
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +43 -2
- package/dist/remote.js.map +1 -1
- package/dist/schemas.d.ts +302 -21
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +142 -26
- package/dist/schemas.js.map +1 -1
- package/dist/service.d.ts +57 -10
- package/dist/service.d.ts.map +1 -1
- package/dist/skill-install.d.ts +8 -2
- package/dist/skill-install.d.ts.map +1 -1
- package/dist/skill-install.js +6 -11
- package/dist/skill-install.js.map +1 -1
- package/dist/storage.d.ts +41 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +254 -36
- package/dist/storage.js.map +1 -1
- package/dist/types.d.ts +213 -38
- package/dist/types.d.ts.map +1 -1
- package/docs/cli.md +53 -20
- package/docs/examples.md +4 -4
- package/docs/mcp.md +12 -4
- package/docs/skill.md +10 -7
- package/docs/storage-format.md +4 -4
- package/openapi/synomem-v1.yaml +24 -24
- package/package.json +1 -1
- package/skills/synomem/SKILL.md +24 -8
- package/skills/synomem/agents/openai.yaml +1 -1
- package/skills/synomem/references/examples.md +1 -1
- package/src/cli.ts +572 -173
- package/src/client.ts +391 -79
- package/src/config.ts +8 -8
- package/src/index.ts +11 -1
- package/src/mcp/index.ts +189 -30
- package/src/mcp-server.ts +58 -8
- package/src/ports/repository.ts +16 -0
- package/src/projections.ts +139 -44
- package/src/remote.ts +118 -8
- package/src/schemas.ts +147 -26
- package/src/service.ts +59 -7
- package/src/skill-install.ts +14 -17
- package/src/storage.ts +326 -34
- package/src/types.ts +228 -39
package/src/projections.ts
CHANGED
|
@@ -16,9 +16,24 @@ import type {
|
|
|
16
16
|
KudosRecord,
|
|
17
17
|
MemoRecord,
|
|
18
18
|
NoteRecord,
|
|
19
|
+
TaskDue,
|
|
20
|
+
TaskRecord,
|
|
19
21
|
TodoRecord,
|
|
20
22
|
} from './types.js';
|
|
21
23
|
|
|
24
|
+
/**
|
|
25
|
+
* The instant a deadline has passed.
|
|
26
|
+
*
|
|
27
|
+
* A date-only deadline does not invent a time of day; it lapses at the end of
|
|
28
|
+
* that UTC day, which is the latest reading that cannot call work late while
|
|
29
|
+
* the stated day is still running. Every projection must use this one rule, or
|
|
30
|
+
* two stores would disagree about what is overdue.
|
|
31
|
+
*/
|
|
32
|
+
export function dueInstant(due: TaskDue | undefined): string | undefined {
|
|
33
|
+
if (!due) return undefined;
|
|
34
|
+
return due.kind === 'date' ? `${due.date}T23:59:59.999Z` : due.datetime;
|
|
35
|
+
}
|
|
36
|
+
|
|
22
37
|
const generatedMarker = '<!-- Generated by Synomem. Rebuild with `synomem rebuild`. -->';
|
|
23
38
|
|
|
24
39
|
function manifestPath(home: string, path: string): string {
|
|
@@ -111,7 +126,7 @@ function memoInboxMarkdown(record: MemoRecord): string {
|
|
|
111
126
|
].join('\n')}\n`;
|
|
112
127
|
}
|
|
113
128
|
|
|
114
|
-
function
|
|
129
|
+
function taskInboxMarkdown(record: TaskRecord): string {
|
|
115
130
|
const due =
|
|
116
131
|
record.current.due?.kind === 'date'
|
|
117
132
|
? record.current.due.date
|
|
@@ -126,7 +141,7 @@ function todoInboxMarkdown(record: TodoRecord): string {
|
|
|
126
141
|
`**Status:** ${record.status} `,
|
|
127
142
|
`**Priority:** ${record.current.priority} `,
|
|
128
143
|
`**Due:** ${escapeMarkdown(due)} `,
|
|
129
|
-
`**
|
|
144
|
+
`**Task ID:** \`${record.event.id}\``,
|
|
130
145
|
'',
|
|
131
146
|
...(record.current.description ? [escapeMarkdown(record.current.description), ''] : []),
|
|
132
147
|
].join('\n')}\n`;
|
|
@@ -157,16 +172,16 @@ function memoryMarkdown(profile: AgentProfile, records: NoteRecord[], rebuiltAt:
|
|
|
157
172
|
return `${lines.join('\n')}\n`;
|
|
158
173
|
}
|
|
159
174
|
|
|
160
|
-
function
|
|
175
|
+
function tasksMarkdown(profile: AgentProfile, records: TaskRecord[], rebuiltAt: string): string {
|
|
161
176
|
const lines = [
|
|
162
177
|
generatedMarker,
|
|
163
178
|
'',
|
|
164
|
-
`#
|
|
179
|
+
`# Tasks — ${escapeMarkdown(profile.displayName)}`,
|
|
165
180
|
'',
|
|
166
181
|
`_Last rebuilt: ${rebuiltAt}_`,
|
|
167
182
|
'',
|
|
168
183
|
];
|
|
169
|
-
if (!records.length) lines.push('_No
|
|
184
|
+
if (!records.length) lines.push('_No tasks yet._', '');
|
|
170
185
|
for (const record of records) {
|
|
171
186
|
const due =
|
|
172
187
|
record.current.due?.kind === 'date'
|
|
@@ -180,7 +195,7 @@ function todosMarkdown(profile: AgentProfile, records: TodoRecord[], rebuiltAt:
|
|
|
180
195
|
`**Status:** ${record.status} `,
|
|
181
196
|
`**Priority:** ${record.current.priority} `,
|
|
182
197
|
`**Due:** ${escapeMarkdown(due)} `,
|
|
183
|
-
`**
|
|
198
|
+
`**Task ID:** \`${record.event.id}\``,
|
|
184
199
|
'',
|
|
185
200
|
);
|
|
186
201
|
}
|
|
@@ -278,6 +293,13 @@ export function noteRecordsFromEvents(events: SynomemEvent[]): NoteRecord[] {
|
|
|
278
293
|
return [...records.values()].sort((a, b) => b.event.createdAt.localeCompare(a.event.createdAt));
|
|
279
294
|
}
|
|
280
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Builds private Todo records.
|
|
298
|
+
*
|
|
299
|
+
* Deliberately simpler than the Task reducer: there is no acceptance state and
|
|
300
|
+
* no response history, because there is nobody to negotiate with. A Todo goes
|
|
301
|
+
* open → completed / canceled / archived, and back to open on reopen.
|
|
302
|
+
*/
|
|
281
303
|
export function todoRecordsFromEvents(events: SynomemEvent[]): TodoRecord[] {
|
|
282
304
|
const records = new Map<string, TodoRecord>();
|
|
283
305
|
for (const event of events) {
|
|
@@ -286,14 +308,13 @@ export function todoRecordsFromEvents(events: SynomemEvent[]): TodoRecord[] {
|
|
|
286
308
|
event,
|
|
287
309
|
current: {
|
|
288
310
|
title: event.title,
|
|
289
|
-
...(event.
|
|
311
|
+
...(event.details !== undefined ? { details: event.details } : {}),
|
|
290
312
|
priority: event.priority,
|
|
291
313
|
...(event.due ? { due: event.due } : {}),
|
|
292
314
|
tags: event.tags ?? [],
|
|
293
|
-
visibility: event.visibility,
|
|
294
315
|
version: event.aggregateVersion,
|
|
295
316
|
},
|
|
296
|
-
status:
|
|
317
|
+
status: 'open',
|
|
297
318
|
});
|
|
298
319
|
} else if (event.type === 'todo.updated') {
|
|
299
320
|
const record = records.get(event.todoId);
|
|
@@ -301,24 +322,25 @@ export function todoRecordsFromEvents(events: SynomemEvent[]): TodoRecord[] {
|
|
|
301
322
|
record.update = event;
|
|
302
323
|
record.current = {
|
|
303
324
|
title: event.title,
|
|
304
|
-
...(event.
|
|
325
|
+
...(event.details !== undefined ? { details: event.details } : {}),
|
|
305
326
|
priority: event.priority,
|
|
306
327
|
...(event.due ? { due: event.due } : {}),
|
|
307
328
|
tags: event.tags ?? [],
|
|
308
|
-
visibility: event.visibility,
|
|
309
329
|
version: event.aggregateVersion,
|
|
310
330
|
};
|
|
311
331
|
}
|
|
312
|
-
} else if (event.type === 'todo.
|
|
332
|
+
} else if (event.type === 'todo.reopened') {
|
|
313
333
|
const record = records.get(event.todoId);
|
|
314
334
|
if (record) {
|
|
335
|
+
record.reopened = event;
|
|
315
336
|
record.current.version = event.aggregateVersion;
|
|
316
337
|
record.status = 'open';
|
|
338
|
+
delete record.terminal;
|
|
317
339
|
}
|
|
318
340
|
} else if (
|
|
319
341
|
event.type === 'todo.completed' ||
|
|
320
|
-
event.type === 'todo.
|
|
321
|
-
event.type === 'todo.
|
|
342
|
+
event.type === 'todo.canceled' ||
|
|
343
|
+
event.type === 'todo.archived'
|
|
322
344
|
) {
|
|
323
345
|
const record = records.get(event.todoId);
|
|
324
346
|
if (record) {
|
|
@@ -327,12 +349,85 @@ export function todoRecordsFromEvents(events: SynomemEvent[]): TodoRecord[] {
|
|
|
327
349
|
record.status =
|
|
328
350
|
event.type === 'todo.completed'
|
|
329
351
|
? 'completed'
|
|
330
|
-
: event.type === 'todo.
|
|
352
|
+
: event.type === 'todo.canceled'
|
|
353
|
+
? 'canceled'
|
|
354
|
+
: 'archived';
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return [...records.values()];
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export function taskRecordsFromEvents(events: SynomemEvent[]): TaskRecord[] {
|
|
362
|
+
const records = new Map<string, TaskRecord>();
|
|
363
|
+
for (const event of events) {
|
|
364
|
+
if (event.type === 'task.created') {
|
|
365
|
+
records.set(event.id, {
|
|
366
|
+
event,
|
|
367
|
+
current: {
|
|
368
|
+
title: event.title,
|
|
369
|
+
...(event.description !== undefined ? { description: event.description } : {}),
|
|
370
|
+
priority: event.priority,
|
|
371
|
+
...(event.due ? { due: event.due } : {}),
|
|
372
|
+
tags: event.tags ?? [],
|
|
373
|
+
visibility: event.visibility,
|
|
374
|
+
version: event.aggregateVersion,
|
|
375
|
+
},
|
|
376
|
+
status: event.requiresAcceptance ? 'assigned' : 'open',
|
|
377
|
+
responses: [],
|
|
378
|
+
});
|
|
379
|
+
} else if (event.type === 'task.updated') {
|
|
380
|
+
const record = records.get(event.taskId);
|
|
381
|
+
if (record) {
|
|
382
|
+
record.update = event;
|
|
383
|
+
record.current = {
|
|
384
|
+
title: event.title,
|
|
385
|
+
...(event.description !== undefined ? { description: event.description } : {}),
|
|
386
|
+
priority: event.priority,
|
|
387
|
+
...(event.due ? { due: event.due } : {}),
|
|
388
|
+
tags: event.tags ?? [],
|
|
389
|
+
visibility: event.visibility,
|
|
390
|
+
version: event.aggregateVersion,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
} else if (event.type === 'task.accepted') {
|
|
394
|
+
const record = records.get(event.taskId);
|
|
395
|
+
if (record) {
|
|
396
|
+
record.current.version = event.aggregateVersion;
|
|
397
|
+
record.status = 'open';
|
|
398
|
+
record.responses.push({
|
|
399
|
+
kind: 'accepted',
|
|
400
|
+
...(event.response !== undefined ? { response: event.response } : {}),
|
|
401
|
+
actor: event.actor,
|
|
402
|
+
at: event.createdAt,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
} else if (
|
|
406
|
+
event.type === 'task.completed' ||
|
|
407
|
+
event.type === 'task.rejected' ||
|
|
408
|
+
event.type === 'task.canceled'
|
|
409
|
+
) {
|
|
410
|
+
const record = records.get(event.taskId);
|
|
411
|
+
if (record) {
|
|
412
|
+
record.terminal = event;
|
|
413
|
+
record.current.version = event.aggregateVersion;
|
|
414
|
+
if (event.type === 'task.rejected') {
|
|
415
|
+
record.responses.push({
|
|
416
|
+
kind: 'rejected',
|
|
417
|
+
response: event.response,
|
|
418
|
+
actor: event.actor,
|
|
419
|
+
at: event.createdAt,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
record.status =
|
|
423
|
+
event.type === 'task.completed'
|
|
424
|
+
? 'completed'
|
|
425
|
+
: event.type === 'task.rejected'
|
|
331
426
|
? 'rejected'
|
|
332
427
|
: 'canceled';
|
|
333
428
|
}
|
|
334
|
-
} else if (event.type === '
|
|
335
|
-
const record = records.get(event.
|
|
429
|
+
} else if (event.type === 'task.reopened') {
|
|
430
|
+
const record = records.get(event.taskId);
|
|
336
431
|
if (record) {
|
|
337
432
|
record.reopened = event;
|
|
338
433
|
record.current.version = event.aggregateVersion;
|
|
@@ -358,9 +453,9 @@ export function renderMarkdownExport(events: SynomemEvent[]): string {
|
|
|
358
453
|
(record) =>
|
|
359
454
|
`## Note: ${escapeMarkdown(record.current.title)}\n\n${escapeMarkdown(record.current.body)}\n\nStatus: ${record.status}; version ${record.current.version}\n\nID: \`${record.event.id}\``,
|
|
360
455
|
),
|
|
361
|
-
...
|
|
456
|
+
...taskRecordsFromEvents(events).map(
|
|
362
457
|
(record) =>
|
|
363
|
-
`##
|
|
458
|
+
`## Task: ${escapeMarkdown(record.current.title)}\n\n${record.current.description ? `${escapeMarkdown(record.current.description)}\n\n` : ''}Status: ${record.status}; priority ${record.current.priority}\n\nID: \`${record.event.id}\``,
|
|
364
459
|
),
|
|
365
460
|
];
|
|
366
461
|
return `${sections.join('\n\n')}\n`;
|
|
@@ -380,7 +475,7 @@ export class ProjectionManager implements ProjectionWriter {
|
|
|
380
475
|
const records = recordsFromEvents(events);
|
|
381
476
|
const memos = memoRecordsFromEvents(events);
|
|
382
477
|
const notes = noteRecordsFromEvents(events);
|
|
383
|
-
const
|
|
478
|
+
const tasks = taskRecordsFromEvents(events);
|
|
384
479
|
const rebuiltAt = events.at(-1)?.createdAt ?? new Date(0).toISOString();
|
|
385
480
|
const generated: string[] = [];
|
|
386
481
|
|
|
@@ -394,12 +489,12 @@ export class ProjectionManager implements ProjectionWriter {
|
|
|
394
489
|
const inboxDirectory = join(agentDirectory, 'inbox');
|
|
395
490
|
const kudosInboxDirectory = join(inboxDirectory, 'kudos');
|
|
396
491
|
const memoInboxDirectory = join(inboxDirectory, 'memos');
|
|
397
|
-
const
|
|
492
|
+
const taskInboxDirectory = join(inboxDirectory, 'tasks');
|
|
398
493
|
assertNoSymlinkEscape(this.storage.home, inboxDirectory);
|
|
399
494
|
ensureDirectory(inboxDirectory);
|
|
400
495
|
ensureDirectory(kudosInboxDirectory);
|
|
401
496
|
ensureDirectory(memoInboxDirectory);
|
|
402
|
-
ensureDirectory(
|
|
497
|
+
ensureDirectory(taskInboxDirectory);
|
|
403
498
|
|
|
404
499
|
atomicWriteDerivedFile(
|
|
405
500
|
profilePath,
|
|
@@ -424,11 +519,11 @@ export class ProjectionManager implements ProjectionWriter {
|
|
|
424
519
|
atomicWriteDerivedFile(memoryPath, memoryMarkdown(profile, agentNotes, rebuiltAt));
|
|
425
520
|
generated.push(manifestPath(this.storage.home, memoryPath));
|
|
426
521
|
}
|
|
427
|
-
const
|
|
428
|
-
if (this.storage.config.projection.
|
|
429
|
-
const
|
|
430
|
-
atomicWriteDerivedFile(
|
|
431
|
-
generated.push(manifestPath(this.storage.home,
|
|
522
|
+
const agentTasks = tasks.filter((record) => record.event.assigneeAgentId === profile.id);
|
|
523
|
+
if (this.storage.config.projection.writeTasksMarkdown) {
|
|
524
|
+
const tasksPath = join(agentDirectory, 'TASKS.md');
|
|
525
|
+
atomicWriteDerivedFile(tasksPath, tasksMarkdown(profile, agentTasks, rebuiltAt));
|
|
526
|
+
generated.push(manifestPath(this.storage.home, tasksPath));
|
|
432
527
|
}
|
|
433
528
|
if (this.storage.config.projection.writeInboxEntries) {
|
|
434
529
|
for (const record of agentRecords.filter(
|
|
@@ -445,11 +540,11 @@ export class ProjectionManager implements ProjectionWriter {
|
|
|
445
540
|
atomicWriteDerivedFile(inboxPath, memoInboxMarkdown(record));
|
|
446
541
|
generated.push(manifestPath(this.storage.home, inboxPath));
|
|
447
542
|
}
|
|
448
|
-
for (const record of
|
|
543
|
+
for (const record of agentTasks.filter(
|
|
449
544
|
(item) => item.status === 'open' || item.status === 'assigned',
|
|
450
545
|
)) {
|
|
451
|
-
const inboxPath = join(
|
|
452
|
-
atomicWriteDerivedFile(inboxPath,
|
|
546
|
+
const inboxPath = join(taskInboxDirectory, `${record.event.id}.md`);
|
|
547
|
+
atomicWriteDerivedFile(inboxPath, taskInboxMarkdown(record));
|
|
453
548
|
generated.push(manifestPath(this.storage.home, inboxPath));
|
|
454
549
|
}
|
|
455
550
|
}
|
|
@@ -487,7 +582,7 @@ export class ProjectionManager implements ProjectionWriter {
|
|
|
487
582
|
const notes = noteRecordsFromEvents(events).filter(
|
|
488
583
|
(record) => record.event.ownerAgentId === profile.id,
|
|
489
584
|
);
|
|
490
|
-
const
|
|
585
|
+
const tasks = taskRecordsFromEvents(events).filter(
|
|
491
586
|
(record) => record.event.assigneeAgentId === profile.id,
|
|
492
587
|
);
|
|
493
588
|
const rebuiltAt = events.at(-1)?.createdAt ?? new Date(0).toISOString();
|
|
@@ -495,14 +590,14 @@ export class ProjectionManager implements ProjectionWriter {
|
|
|
495
590
|
const inboxDirectory = join(agentDirectory, 'inbox');
|
|
496
591
|
const kudosInboxDirectory = join(inboxDirectory, 'kudos');
|
|
497
592
|
const memoInboxDirectory = join(inboxDirectory, 'memos');
|
|
498
|
-
const
|
|
593
|
+
const taskInboxDirectory = join(inboxDirectory, 'tasks');
|
|
499
594
|
assertNoSymlinkEscape(this.storage.home, agentDirectory);
|
|
500
595
|
assertNoSymlinkEscape(this.storage.home, inboxDirectory);
|
|
501
596
|
ensureDirectory(agentDirectory);
|
|
502
597
|
ensureDirectory(inboxDirectory);
|
|
503
598
|
ensureDirectory(kudosInboxDirectory);
|
|
504
599
|
ensureDirectory(memoInboxDirectory);
|
|
505
|
-
ensureDirectory(
|
|
600
|
+
ensureDirectory(taskInboxDirectory);
|
|
506
601
|
|
|
507
602
|
const generated: string[] = [];
|
|
508
603
|
const profilePath = join(agentDirectory, 'profile.json');
|
|
@@ -532,10 +627,10 @@ export class ProjectionManager implements ProjectionWriter {
|
|
|
532
627
|
generated.push(manifestPath(this.storage.home, memoryPath));
|
|
533
628
|
}
|
|
534
629
|
|
|
535
|
-
if (this.storage.config.projection.
|
|
536
|
-
const
|
|
537
|
-
atomicWriteDerivedFile(
|
|
538
|
-
generated.push(manifestPath(this.storage.home,
|
|
630
|
+
if (this.storage.config.projection.writeTasksMarkdown) {
|
|
631
|
+
const tasksPath = join(agentDirectory, 'TASKS.md');
|
|
632
|
+
atomicWriteDerivedFile(tasksPath, tasksMarkdown(profile, tasks, rebuiltAt));
|
|
633
|
+
generated.push(manifestPath(this.storage.home, tasksPath));
|
|
539
634
|
}
|
|
540
635
|
|
|
541
636
|
if (this.storage.config.projection.writeInboxEntries) {
|
|
@@ -553,12 +648,12 @@ export class ProjectionManager implements ProjectionWriter {
|
|
|
553
648
|
if (!existsSync(inboxPath)) atomicWriteDerivedFile(inboxPath, memoInboxMarkdown(record));
|
|
554
649
|
generated.push(relativePath);
|
|
555
650
|
}
|
|
556
|
-
for (const record of
|
|
651
|
+
for (const record of tasks.filter(
|
|
557
652
|
(item) => item.status === 'open' || item.status === 'assigned',
|
|
558
653
|
)) {
|
|
559
|
-
const inboxPath = join(
|
|
654
|
+
const inboxPath = join(taskInboxDirectory, `${record.event.id}.md`);
|
|
560
655
|
const relativePath = manifestPath(this.storage.home, inboxPath);
|
|
561
|
-
if (!existsSync(inboxPath)) atomicWriteDerivedFile(inboxPath,
|
|
656
|
+
if (!existsSync(inboxPath)) atomicWriteDerivedFile(inboxPath, taskInboxMarkdown(record));
|
|
562
657
|
generated.push(relativePath);
|
|
563
658
|
}
|
|
564
659
|
}
|
|
@@ -589,13 +684,13 @@ export class ProjectionManager implements ProjectionWriter {
|
|
|
589
684
|
const events = this.storage.getReadableEvents();
|
|
590
685
|
const records = recordsFromEvents(events);
|
|
591
686
|
const memos = memoRecordsFromEvents(events);
|
|
592
|
-
const
|
|
687
|
+
const tasks = taskRecordsFromEvents(events);
|
|
593
688
|
const paths: string[] = [];
|
|
594
689
|
for (const profile of profiles) {
|
|
595
690
|
paths.push(`${profile.id}/profile.json`);
|
|
596
691
|
if (this.storage.config.projection.writeWinsMarkdown) paths.push(`${profile.id}/WINS.md`);
|
|
597
692
|
if (this.storage.config.projection.writeMemoryMarkdown) paths.push(`${profile.id}/MEMORY.md`);
|
|
598
|
-
if (this.storage.config.projection.
|
|
693
|
+
if (this.storage.config.projection.writeTasksMarkdown) paths.push(`${profile.id}/TASKS.md`);
|
|
599
694
|
if (this.storage.config.projection.writeInboxEntries) {
|
|
600
695
|
for (const record of records.filter(
|
|
601
696
|
(item) =>
|
|
@@ -610,12 +705,12 @@ export class ProjectionManager implements ProjectionWriter {
|
|
|
610
705
|
)) {
|
|
611
706
|
paths.push(`${profile.id}/inbox/memos/${record.event.id}.md`);
|
|
612
707
|
}
|
|
613
|
-
for (const record of
|
|
708
|
+
for (const record of tasks.filter(
|
|
614
709
|
(item) =>
|
|
615
710
|
item.event.assigneeAgentId === profile.id &&
|
|
616
711
|
(item.status === 'open' || item.status === 'assigned'),
|
|
617
712
|
)) {
|
|
618
|
-
paths.push(`${profile.id}/inbox/
|
|
713
|
+
paths.push(`${profile.id}/inbox/tasks/${record.event.id}.md`);
|
|
619
714
|
}
|
|
620
715
|
}
|
|
621
716
|
}
|
package/src/remote.ts
CHANGED
|
@@ -4,16 +4,19 @@ import type { SynomemService, SynomemServiceCapabilities, SynomemServiceInfo } f
|
|
|
4
4
|
import type {
|
|
5
5
|
ActorIdentity,
|
|
6
6
|
ChangesInput,
|
|
7
|
+
BindRuntimeInput,
|
|
7
8
|
CreateAgentInput,
|
|
8
9
|
CreateNoteInput,
|
|
9
|
-
|
|
10
|
+
CreateTaskInput,
|
|
10
11
|
GiveKudosInput,
|
|
11
12
|
ItemListInput,
|
|
12
13
|
KudosListInput,
|
|
13
14
|
ReviseNoteInput,
|
|
14
15
|
SendMemoInput,
|
|
15
16
|
UpdateAgentInput,
|
|
17
|
+
UpdateTaskInput,
|
|
16
18
|
UpdateTodoInput,
|
|
19
|
+
CreateTodoInput,
|
|
17
20
|
} from './types.js';
|
|
18
21
|
|
|
19
22
|
const defaultMaximumResponseBytes = 1024 * 1024;
|
|
@@ -169,6 +172,32 @@ export class RemoteSynomemService implements SynomemService {
|
|
|
169
172
|
),
|
|
170
173
|
list: () =>
|
|
171
174
|
this.request<Awaited<ReturnType<SynomemService['agents']['list']>>>('GET', 'agents'),
|
|
175
|
+
resolve: (query: string) =>
|
|
176
|
+
this.request<Awaited<ReturnType<SynomemService['agents']['resolve']>>>(
|
|
177
|
+
'GET',
|
|
178
|
+
`agents/resolve?query=${encodeURIComponent(query)}`,
|
|
179
|
+
),
|
|
180
|
+
directory: () =>
|
|
181
|
+
this.request<Awaited<ReturnType<SynomemService['agents']['directory']>>>(
|
|
182
|
+
'GET',
|
|
183
|
+
'agents/directory',
|
|
184
|
+
),
|
|
185
|
+
bindings: (idOrAlias: string) =>
|
|
186
|
+
this.request<Awaited<ReturnType<SynomemService['agents']['bindings']>>>(
|
|
187
|
+
'GET',
|
|
188
|
+
`agents/${encodeURIComponent(idOrAlias)}/runtimes`,
|
|
189
|
+
),
|
|
190
|
+
bindRuntime: (input: BindRuntimeInput) =>
|
|
191
|
+
this.mutation<Awaited<ReturnType<SynomemService['agents']['bindRuntime']>>>(
|
|
192
|
+
'POST',
|
|
193
|
+
`agents/${encodeURIComponent(input.agentId)}/runtimes`,
|
|
194
|
+
input,
|
|
195
|
+
),
|
|
196
|
+
unbindRuntime: (bindingId: string) =>
|
|
197
|
+
this.request<Awaited<ReturnType<SynomemService['agents']['unbindRuntime']>>>(
|
|
198
|
+
'DELETE',
|
|
199
|
+
`agents/runtimes/${encodeURIComponent(bindingId)}`,
|
|
200
|
+
),
|
|
172
201
|
};
|
|
173
202
|
|
|
174
203
|
readonly kudos = {
|
|
@@ -261,6 +290,42 @@ export class RemoteSynomemService implements SynomemService {
|
|
|
261
290
|
),
|
|
262
291
|
};
|
|
263
292
|
|
|
293
|
+
readonly tasks = {
|
|
294
|
+
create: (input: CreateTaskInput) =>
|
|
295
|
+
this.mutation<Awaited<ReturnType<SynomemService['tasks']['create']>>>('POST', 'tasks', input),
|
|
296
|
+
list: (input: Omit<ItemListInput, 'kinds'> = {}) =>
|
|
297
|
+
this.request<Awaited<ReturnType<SynomemService['tasks']['list']>>>(
|
|
298
|
+
'GET',
|
|
299
|
+
`tasks${queryString(input)}`,
|
|
300
|
+
),
|
|
301
|
+
get: (id: string) =>
|
|
302
|
+
this.request<Awaited<ReturnType<SynomemService['tasks']['get']>>>(
|
|
303
|
+
'GET',
|
|
304
|
+
`tasks/${encodeURIComponent(id)}`,
|
|
305
|
+
),
|
|
306
|
+
update: (input: UpdateTaskInput) =>
|
|
307
|
+
this.mutation<Awaited<ReturnType<SynomemService['tasks']['update']>>>(
|
|
308
|
+
'POST',
|
|
309
|
+
`tasks/${encodeURIComponent(input.taskId)}/revisions`,
|
|
310
|
+
input,
|
|
311
|
+
['taskId'],
|
|
312
|
+
),
|
|
313
|
+
accept: (input: { taskId: string; response?: string; idempotencyKey?: string }) =>
|
|
314
|
+
this.taskTransition('accept', input),
|
|
315
|
+
reject: (input: { taskId: string; response: string; idempotencyKey?: string }) =>
|
|
316
|
+
this.taskTransition('reject', input),
|
|
317
|
+
complete: (input: { taskId: string; note?: string; idempotencyKey?: string }) =>
|
|
318
|
+
this.taskTransition('complete', input),
|
|
319
|
+
reopen: (input: { taskId: string; idempotencyKey?: string }) =>
|
|
320
|
+
this.taskTransition('reopen', input),
|
|
321
|
+
cancel: (input: { taskId: string; reason?: string; idempotencyKey?: string }) =>
|
|
322
|
+
this.taskTransition('cancel', input),
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Private self-reminders. The remote surface mirrors the local one exactly so
|
|
327
|
+
* a caller does not have to know which backend it is talking to.
|
|
328
|
+
*/
|
|
264
329
|
readonly todos = {
|
|
265
330
|
create: (input: CreateTodoInput) =>
|
|
266
331
|
this.mutation<Awaited<ReturnType<SynomemService['todos']['create']>>>('POST', 'todos', input),
|
|
@@ -281,16 +346,43 @@ export class RemoteSynomemService implements SynomemService {
|
|
|
281
346
|
input,
|
|
282
347
|
['todoId'],
|
|
283
348
|
),
|
|
284
|
-
accept: (input: { todoId: string; idempotencyKey?: string }) =>
|
|
285
|
-
this.todoTransition('accept', input),
|
|
286
|
-
reject: (input: { todoId: string; reason?: string; idempotencyKey?: string }) =>
|
|
287
|
-
this.todoTransition('reject', input),
|
|
288
349
|
complete: (input: { todoId: string; note?: string; idempotencyKey?: string }) =>
|
|
289
350
|
this.todoTransition('complete', input),
|
|
290
351
|
reopen: (input: { todoId: string; idempotencyKey?: string }) =>
|
|
291
352
|
this.todoTransition('reopen', input),
|
|
292
353
|
cancel: (input: { todoId: string; reason?: string; idempotencyKey?: string }) =>
|
|
293
354
|
this.todoTransition('cancel', input),
|
|
355
|
+
archive: (input: { todoId: string; idempotencyKey?: string }) =>
|
|
356
|
+
this.todoTransition('archive', input),
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Discovery mirrors the local semantics: the age/deadline is resolved here so
|
|
361
|
+
* both backends answer the same question, rather than each server deciding
|
|
362
|
+
* what "older than 24 hours" means.
|
|
363
|
+
*/
|
|
364
|
+
readonly discovery = {
|
|
365
|
+
unanswered: (
|
|
366
|
+
input: Omit<ItemListInput, 'awaitingResponse' | 'pending'> & { olderThanHours?: number } = {},
|
|
367
|
+
) => {
|
|
368
|
+
const { olderThanHours, awaitingSince, ...rest } = input;
|
|
369
|
+
const since =
|
|
370
|
+
awaitingSince ??
|
|
371
|
+
(olderThanHours !== undefined
|
|
372
|
+
? new Date(Date.now() - olderThanHours * 3_600_000).toISOString()
|
|
373
|
+
: undefined);
|
|
374
|
+
return this.request<Awaited<ReturnType<SynomemService['items']['list']>>>(
|
|
375
|
+
'GET',
|
|
376
|
+
`items${queryString({ ...rest, awaitingResponse: true, ...(since ? { awaitingSince: since } : {}) })}`,
|
|
377
|
+
);
|
|
378
|
+
},
|
|
379
|
+
overdue: (input: Omit<ItemListInput, 'overdueAsOf'> & { asOf?: string } = {}) => {
|
|
380
|
+
const { asOf, ...rest } = input;
|
|
381
|
+
return this.request<Awaited<ReturnType<SynomemService['items']['list']>>>(
|
|
382
|
+
'GET',
|
|
383
|
+
`items${queryString({ ...rest, overdueAsOf: asOf ?? new Date().toISOString() })}`,
|
|
384
|
+
);
|
|
385
|
+
},
|
|
294
386
|
};
|
|
295
387
|
|
|
296
388
|
readonly items = {
|
|
@@ -410,11 +502,29 @@ export class RemoteSynomemService implements SynomemService {
|
|
|
410
502
|
);
|
|
411
503
|
}
|
|
412
504
|
|
|
413
|
-
private
|
|
505
|
+
private taskTransition(
|
|
414
506
|
transition: 'accept' | 'reject' | 'complete' | 'reopen' | 'cancel',
|
|
507
|
+
input: {
|
|
508
|
+
taskId: string;
|
|
509
|
+
idempotencyKey?: string;
|
|
510
|
+
reason?: string;
|
|
511
|
+
note?: string;
|
|
512
|
+
response?: string;
|
|
513
|
+
},
|
|
514
|
+
) {
|
|
515
|
+
return this.mutation<Awaited<ReturnType<SynomemService['tasks']['accept']>>>(
|
|
516
|
+
'POST',
|
|
517
|
+
`tasks/${encodeURIComponent(input.taskId)}/${transition}`,
|
|
518
|
+
input,
|
|
519
|
+
['taskId'],
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
private todoTransition(
|
|
524
|
+
transition: 'complete' | 'reopen' | 'cancel' | 'archive',
|
|
415
525
|
input: { todoId: string; idempotencyKey?: string; reason?: string; note?: string },
|
|
416
526
|
) {
|
|
417
|
-
return this.mutation<Awaited<ReturnType<SynomemService['todos']['
|
|
527
|
+
return this.mutation<Awaited<ReturnType<SynomemService['todos']['complete']>>>(
|
|
418
528
|
'POST',
|
|
419
529
|
`todos/${encodeURIComponent(input.todoId)}/${transition}`,
|
|
420
530
|
input,
|
|
@@ -437,7 +547,7 @@ export class RemoteSynomemService implements SynomemService {
|
|
|
437
547
|
}
|
|
438
548
|
|
|
439
549
|
private async request<T>(
|
|
440
|
-
method: 'GET' | 'POST' | 'PATCH',
|
|
550
|
+
method: 'GET' | 'POST' | 'PATCH' | 'DELETE',
|
|
441
551
|
path: string,
|
|
442
552
|
body?: object,
|
|
443
553
|
idempotencyKey?: string,
|