synomem 0.2.0 → 0.4.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 +49 -0
- package/README.md +4 -4
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +8 -2
- package/dist/backend.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +379 -16
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +68 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +283 -12
- package/dist/client.js.map +1 -1
- package/dist/cloud.d.ts +16 -0
- package/dist/cloud.d.ts.map +1 -0
- package/dist/cloud.js +19 -0
- package/dist/cloud.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -1
- package/dist/config.js.map +1 -1
- package/dist/configure.d.ts +55 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +177 -0
- package/dist/configure.js.map +1 -0
- package/dist/credentials.d.ts +15 -2
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js.map +1 -1
- package/dist/import.d.ts +202 -38
- package/dist/import.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +72 -6
- package/dist/mcp/index.js.map +1 -1
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +8 -1
- package/dist/oauth.js.map +1 -1
- package/dist/ports/repository.d.ts +4 -1
- package/dist/ports/repository.d.ts.map +1 -1
- package/dist/projections.d.ts +9 -1
- package/dist/projections.d.ts.map +1 -1
- package/dist/projections.js +75 -4
- package/dist/projections.js.map +1 -1
- package/dist/prompt.d.ts +28 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +72 -0
- package/dist/prompt.js.map +1 -0
- package/dist/remote.d.ts +30 -1
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +14 -0
- package/dist/remote.js.map +1 -1
- package/dist/schemas.d.ts +270 -55
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +120 -6
- package/dist/schemas.js.map +1 -1
- package/dist/service.d.ts +30 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/storage.d.ts +25 -2
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +168 -15
- package/dist/storage.js.map +1 -1
- package/dist/types.d.ts +123 -4
- package/dist/types.d.ts.map +1 -1
- package/docs/cli.md +1 -1
- package/docs/examples.md +1 -1
- package/docs/mcp.md +1 -1
- package/docs/skill.md +1 -1
- package/docs/storage-format.md +1 -1
- package/package.json +8 -8
- package/src/backend.ts +8 -2
- package/src/cli.ts +543 -19
- package/src/client.ts +331 -11
- package/src/cloud.ts +19 -0
- package/src/config.ts +8 -1
- package/src/configure.ts +233 -0
- package/src/credentials.ts +17 -2
- package/src/index.ts +7 -1
- package/src/mcp/index.ts +95 -5
- package/src/oauth.ts +8 -1
- package/src/ports/repository.ts +5 -0
- package/src/projections.ts +74 -4
- package/src/prompt.ts +88 -0
- package/src/remote.ts +72 -0
- package/src/schemas.ts +125 -6
- package/src/service.ts +30 -0
- package/src/storage.ts +217 -14
- package/src/types.ts +129 -3
package/dist/client.js
CHANGED
|
@@ -4,10 +4,14 @@ import { ulid } from 'ulid';
|
|
|
4
4
|
import { resolveHome } from './config.js';
|
|
5
5
|
import { asSynomemError, SynomemError } from './errors.js';
|
|
6
6
|
import { assertNoSymlinkEscape } from './fs-utils.js';
|
|
7
|
-
import { escapeMarkdown, memoRecordsFromEvents, noteRecordsFromEvents, ProjectionManager, recordsFromEvents, taskRecordsFromEvents, todoRecordsFromEvents, } from './projections.js';
|
|
8
|
-
import { actorSchema, agentLookupSchema, bindRuntimeSchema, createAgentSchema, createNoteSchema, createTaskSchema, createTodoSchema, changesInputSchema, giveKudosSchema, itemListInputSchema, listInputSchema, reviseNoteSchema, sendMemoSchema, updateTaskSchema, updateTodoSchema, updateAgentSchema, } from './schemas.js';
|
|
7
|
+
import { escapeMarkdown, memoRecordsFromEvents, noteRecordsFromEvents, postRecordsFromEvents, ProjectionManager, recordsFromEvents, taskRecordsFromEvents, todoRecordsFromEvents, } from './projections.js';
|
|
8
|
+
import { actorSchema, agentLookupSchema, bindRuntimeSchema, createAgentSchema, createPostSchema, updatePostSchema, createNoteSchema, createTaskSchema, createTodoSchema, changesInputSchema, giveKudosSchema, itemListInputSchema, listInputSchema, reviseNoteSchema, sendMemoSchema, updateTaskSchema, updateTodoSchema, updateAgentSchema, } from './schemas.js';
|
|
9
9
|
import { SynomemStorage } from './storage.js';
|
|
10
10
|
export class SynomemCore {
|
|
11
|
+
/**
|
|
12
|
+
* Mutable because an agent actor is resolved to its canonical identity on
|
|
13
|
+
* init: callers name a handle, events record the opaque ID.
|
|
14
|
+
*/
|
|
11
15
|
actor;
|
|
12
16
|
repository;
|
|
13
17
|
projectionWriter;
|
|
@@ -22,6 +26,10 @@ export class SynomemCore {
|
|
|
22
26
|
get: (idOrAlias) => this.getAgent(idOrAlias),
|
|
23
27
|
list: () => this.listAgents(),
|
|
24
28
|
resolve: (query) => this.resolveAgent(query),
|
|
29
|
+
archive: (idOrAlias) => this.setAgentStatus(idOrAlias, 'archived'),
|
|
30
|
+
restore: (idOrAlias) => this.setAgentStatus(idOrAlias, 'active'),
|
|
31
|
+
addAliases: (idOrAlias, aliases) => this.addAgentAliases(idOrAlias, aliases),
|
|
32
|
+
removeAliases: (idOrAlias, aliases) => this.removeAgentAliases(idOrAlias, aliases),
|
|
25
33
|
directory: () => this.agentDirectory(),
|
|
26
34
|
bindings: (idOrAlias) => this.listRuntimeBindings(idOrAlias),
|
|
27
35
|
bindRuntime: (input) => this.bindRuntime(input),
|
|
@@ -42,6 +50,22 @@ export class SynomemCore {
|
|
|
42
50
|
read: (input) => this.readMemo(input),
|
|
43
51
|
archive: (input) => this.archiveMemo(input),
|
|
44
52
|
};
|
|
53
|
+
posts = {
|
|
54
|
+
create: (input) => this.createPost(input),
|
|
55
|
+
list: (input = {}) => this.listItems({ ...input, kinds: ['post'] }),
|
|
56
|
+
get: (id) => this.getPost(id),
|
|
57
|
+
update: (input) => this.updatePost(input),
|
|
58
|
+
archive: (input) => this.archivePost(input),
|
|
59
|
+
/*
|
|
60
|
+
* Acknowledging is an explicit call and always speaks for the caller alone.
|
|
61
|
+
* There is no bulk form and no acknowledge-on-behalf-of: an acknowledgement
|
|
62
|
+
* is one actor saying "I have seen this", and reading a post must never
|
|
63
|
+
* append one, or the roster stops meaning anything.
|
|
64
|
+
*/
|
|
65
|
+
acknowledge: (input) => this.acknowledgePost(input),
|
|
66
|
+
withdrawAcknowledgment: (input) => this.withdrawPostAcknowledgment(input),
|
|
67
|
+
roster: (postId) => this.postRoster(postId),
|
|
68
|
+
};
|
|
45
69
|
notes = {
|
|
46
70
|
create: (input) => this.createNote(input),
|
|
47
71
|
list: (input = {}) => this.listItems({ ...input, kinds: ['note'] }),
|
|
@@ -128,6 +152,29 @@ export class SynomemCore {
|
|
|
128
152
|
return;
|
|
129
153
|
await this.repository.init();
|
|
130
154
|
this.initialized = true;
|
|
155
|
+
/*
|
|
156
|
+
* An agent actor is resolved to its canonical identity here.
|
|
157
|
+
*
|
|
158
|
+
* Callers name a handle because that is what people and harnesses know,
|
|
159
|
+
* but every event must record the opaque ID — otherwise renaming a handle
|
|
160
|
+
* would orphan the history written under the old one. The display name
|
|
161
|
+
* comes from the profile for the same reason a harness cannot assert it on
|
|
162
|
+
* the command line: the stored record is the authority, not the argument.
|
|
163
|
+
*
|
|
164
|
+
* An unresolvable name is left as given rather than rejected, so a system
|
|
165
|
+
* actor can still create the agent that does not exist yet. Writing as an
|
|
166
|
+
* unknown agent is refused later by the checks that already exist.
|
|
167
|
+
*/
|
|
168
|
+
if (this.actor.kind === 'agent') {
|
|
169
|
+
const resolved = await this.repository.resolveAgent(this.actor.id);
|
|
170
|
+
if (resolved.match) {
|
|
171
|
+
this.actor = {
|
|
172
|
+
kind: 'agent',
|
|
173
|
+
id: resolved.match.id,
|
|
174
|
+
...(resolved.match.displayName ? { displayName: resolved.match.displayName } : {}),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
131
178
|
}
|
|
132
179
|
async close() {
|
|
133
180
|
await this.repository.close();
|
|
@@ -165,20 +212,29 @@ export class SynomemCore {
|
|
|
165
212
|
this.checkAbort();
|
|
166
213
|
await this.repository.assertEventCompatibility();
|
|
167
214
|
const parsed = this.validate(() => createAgentSchema.parse(input));
|
|
168
|
-
if (await this.repository.getAgent(parsed.
|
|
169
|
-
throw new SynomemError('AGENT_EXISTS', `Agent or alias already exists: ${parsed.
|
|
215
|
+
if (await this.repository.getAgent(parsed.handle)) {
|
|
216
|
+
throw new SynomemError('AGENT_EXISTS', `Agent or alias already exists: ${parsed.handle}`);
|
|
170
217
|
}
|
|
171
218
|
const aliases = [...new Set(parsed.aliases ?? [])].sort();
|
|
172
|
-
if (aliases.includes(parsed.
|
|
173
|
-
throw new SynomemError('ALIAS_CONFLICT', 'An agent cannot use its own
|
|
219
|
+
if (aliases.includes(parsed.handle)) {
|
|
220
|
+
throw new SynomemError('ALIAS_CONFLICT', 'An agent cannot use its own handle as an alias.');
|
|
174
221
|
}
|
|
175
222
|
for (const alias of aliases) {
|
|
176
223
|
if (await this.repository.getAgent(alias)) {
|
|
177
224
|
throw new SynomemError('ALIAS_CONFLICT', `Alias already belongs to an agent: ${alias}`);
|
|
178
225
|
}
|
|
179
226
|
}
|
|
227
|
+
/*
|
|
228
|
+
* The canonical ID is generated here and never supplied by the caller.
|
|
229
|
+
* Every event references it permanently, so it has to be free of meaning:
|
|
230
|
+
* a caller that could choose it could choose one that collides with an
|
|
231
|
+
* archived agent's history, and a meaningful ID becomes a handle nobody can
|
|
232
|
+
* rename.
|
|
233
|
+
*/
|
|
180
234
|
const profile = {
|
|
181
|
-
id:
|
|
235
|
+
id: this.nextId(),
|
|
236
|
+
handle: parsed.handle,
|
|
237
|
+
status: 'active',
|
|
182
238
|
displayName: parsed.displayName,
|
|
183
239
|
...(aliases.length ? { aliases } : {}),
|
|
184
240
|
...(parsed.description !== undefined ? { description: parsed.description } : {}),
|
|
@@ -206,8 +262,17 @@ export class SynomemCore {
|
|
|
206
262
|
if (!existing)
|
|
207
263
|
throw new SynomemError('AGENT_NOT_FOUND', `Unknown agent: ${idOrAlias}`);
|
|
208
264
|
const aliases = parsed.aliases ? [...new Set(parsed.aliases)].sort() : existing.aliases;
|
|
209
|
-
|
|
210
|
-
|
|
265
|
+
const handle = parsed.handle ?? existing.handle;
|
|
266
|
+
if (aliases?.includes(handle)) {
|
|
267
|
+
throw new SynomemError('ALIAS_CONFLICT', 'An agent cannot use its own handle as an alias.');
|
|
268
|
+
}
|
|
269
|
+
// Renaming the handle is allowed and is why the canonical ID exists, but a
|
|
270
|
+
// handle another agent already answers to is still refused.
|
|
271
|
+
if (parsed.handle && parsed.handle !== existing.handle) {
|
|
272
|
+
const owner = await this.repository.getAgent(parsed.handle);
|
|
273
|
+
if (owner && owner.id !== existing.id) {
|
|
274
|
+
throw new SynomemError('ALIAS_CONFLICT', `Handle already belongs to ${owner.id}: ${parsed.handle}`);
|
|
275
|
+
}
|
|
211
276
|
}
|
|
212
277
|
for (const alias of aliases ?? []) {
|
|
213
278
|
const owner = await this.repository.getAgent(alias);
|
|
@@ -234,6 +299,48 @@ export class SynomemCore {
|
|
|
234
299
|
await this.projectionWriter.syncAgent(updated.id);
|
|
235
300
|
return updated;
|
|
236
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* Archiving stops an agent acting without erasing it.
|
|
304
|
+
*
|
|
305
|
+
* Events reference the actor permanently, so deleting an agent would leave
|
|
306
|
+
* history pointing at nothing. Archived agents keep their records and their
|
|
307
|
+
* handle, and can be restored.
|
|
308
|
+
*/
|
|
309
|
+
async setAgentStatus(idOrAlias, status) {
|
|
310
|
+
this.checkAbort();
|
|
311
|
+
await this.repository.assertEventCompatibility();
|
|
312
|
+
this.validate(() => agentLookupSchema.parse(idOrAlias));
|
|
313
|
+
const existing = await this.repository.getAgent(idOrAlias);
|
|
314
|
+
if (!existing)
|
|
315
|
+
throw new SynomemError('AGENT_NOT_FOUND', `Unknown agent: ${idOrAlias}`);
|
|
316
|
+
if (existing.status === status)
|
|
317
|
+
return existing;
|
|
318
|
+
const updated = { ...existing, status };
|
|
319
|
+
await this.repository.transaction(async () => {
|
|
320
|
+
const event = {
|
|
321
|
+
...this.eventBase(existing.id, await this.repository.nextAggregateVersion(existing.id)),
|
|
322
|
+
type: 'agent.updated',
|
|
323
|
+
agentId: existing.id,
|
|
324
|
+
changes: { status },
|
|
325
|
+
};
|
|
326
|
+
await this.repository.updateAgent(updated, event.createdAt);
|
|
327
|
+
await this.repository.insertEvent(event);
|
|
328
|
+
});
|
|
329
|
+
await this.projectionWriter.syncAgent(updated.id);
|
|
330
|
+
return updated;
|
|
331
|
+
}
|
|
332
|
+
/** Adds aliases without disturbing the ones already there. */
|
|
333
|
+
async addAgentAliases(idOrAlias, add) {
|
|
334
|
+
const existing = await this.getAgent(idOrAlias);
|
|
335
|
+
const merged = [...new Set([...(existing.aliases ?? []), ...add])].sort();
|
|
336
|
+
return await this.updateAgent(existing.id, { aliases: merged });
|
|
337
|
+
}
|
|
338
|
+
async removeAgentAliases(idOrAlias, remove) {
|
|
339
|
+
const existing = await this.getAgent(idOrAlias);
|
|
340
|
+
const drop = new Set(remove.map((alias) => alias.trim().toLowerCase()));
|
|
341
|
+
const kept = (existing.aliases ?? []).filter((alias) => !drop.has(alias));
|
|
342
|
+
return await this.updateAgent(existing.id, { aliases: kept });
|
|
343
|
+
}
|
|
237
344
|
async getAgent(idOrAlias) {
|
|
238
345
|
this.checkAbort();
|
|
239
346
|
this.validate(() => agentLookupSchema.parse(idOrAlias));
|
|
@@ -569,6 +676,146 @@ export class SynomemCore {
|
|
|
569
676
|
await this.projectionWriter.syncAgent(record.event.recipientAgentId);
|
|
570
677
|
return await this.getMemoRecord(input.memoId);
|
|
571
678
|
}
|
|
679
|
+
async getPostRecord(id) {
|
|
680
|
+
await this.requireVisibleItem(id, 'post');
|
|
681
|
+
const record = postRecordsFromEvents(await this.repository.getReadableItemEvents(id))[0];
|
|
682
|
+
if (!record)
|
|
683
|
+
throw new SynomemError('ITEM_NOT_FOUND', `Unknown post: ${id}`);
|
|
684
|
+
return record;
|
|
685
|
+
}
|
|
686
|
+
async getPost(id) {
|
|
687
|
+
this.checkAbort();
|
|
688
|
+
return await this.getPostRecord(id);
|
|
689
|
+
}
|
|
690
|
+
async createPost(input) {
|
|
691
|
+
this.checkAbort();
|
|
692
|
+
await this.repository.assertEventCompatibility();
|
|
693
|
+
const parsed = this.validate(() => createPostSchema.parse(input));
|
|
694
|
+
// A reply inherits its parent's workspace by construction, and cannot name
|
|
695
|
+
// a different target — there is no target to name.
|
|
696
|
+
if (parsed.replyTo)
|
|
697
|
+
await this.requireVisibleItem(parsed.replyTo, 'post');
|
|
698
|
+
const outcome = await this.repository.transaction(async () => {
|
|
699
|
+
const prior = await this.priorMutation(parsed.idempotencyKey, 'post.created');
|
|
700
|
+
if (prior?.type === 'post.created')
|
|
701
|
+
return { id: prior.id, created: false };
|
|
702
|
+
const id = this.nextId();
|
|
703
|
+
const event = {
|
|
704
|
+
...this.eventBase(id, 1, id),
|
|
705
|
+
type: 'post.created',
|
|
706
|
+
title: parsed.title,
|
|
707
|
+
body: parsed.body,
|
|
708
|
+
tags: [...new Set(parsed.tags ?? [])].sort(),
|
|
709
|
+
...(parsed.replyTo ? { replyTo: parsed.replyTo } : {}),
|
|
710
|
+
...(parsed.idempotencyKey ? { idempotencyKey: parsed.idempotencyKey } : {}),
|
|
711
|
+
...(parsed.source ? { source: parsed.source } : {}),
|
|
712
|
+
...(parsed.metadata ? { metadata: parsed.metadata } : {}),
|
|
713
|
+
};
|
|
714
|
+
await this.repository.insertEvent(event);
|
|
715
|
+
return { id, created: true };
|
|
716
|
+
});
|
|
717
|
+
return {
|
|
718
|
+
record: await this.getPostRecord(outcome.id),
|
|
719
|
+
created: outcome.created,
|
|
720
|
+
deduplicated: !outcome.created,
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
/** Only the author edits a post. Everyone else responds to it. */
|
|
724
|
+
assertPostAuthor(record) {
|
|
725
|
+
if (this.administrative)
|
|
726
|
+
return;
|
|
727
|
+
if (record.event.actor.id !== this.actor.id || record.event.actor.kind !== this.actor.kind) {
|
|
728
|
+
throw new SynomemError('MUTATION_FORBIDDEN', 'Only the author can change a post.');
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
async updatePost(input) {
|
|
732
|
+
this.checkAbort();
|
|
733
|
+
const parsed = this.validate(() => updatePostSchema.parse(input));
|
|
734
|
+
const record = await this.getPostRecord(parsed.postId);
|
|
735
|
+
this.assertPostAuthor(record);
|
|
736
|
+
if (record.status === 'archived') {
|
|
737
|
+
throw new SynomemError('MUTATION_FORBIDDEN', 'An archived post cannot be edited.');
|
|
738
|
+
}
|
|
739
|
+
if (record.version !== parsed.expectedVersion) {
|
|
740
|
+
throw new SynomemError('REVISION_CONFLICT', `Post ${parsed.postId} is at version ${record.version}.`);
|
|
741
|
+
}
|
|
742
|
+
await this.repository.transaction(async () => {
|
|
743
|
+
const event = {
|
|
744
|
+
...this.eventBase(parsed.postId, await this.repository.nextAggregateVersion(parsed.postId)),
|
|
745
|
+
type: 'post.edited',
|
|
746
|
+
postId: parsed.postId,
|
|
747
|
+
title: parsed.title ?? record.title,
|
|
748
|
+
body: parsed.body ?? record.body,
|
|
749
|
+
tags: [...new Set(parsed.tags ?? record.tags ?? [])].sort(),
|
|
750
|
+
...(parsed.idempotencyKey ? { idempotencyKey: parsed.idempotencyKey } : {}),
|
|
751
|
+
};
|
|
752
|
+
await this.repository.insertEvent(event);
|
|
753
|
+
});
|
|
754
|
+
return await this.getPostRecord(parsed.postId);
|
|
755
|
+
}
|
|
756
|
+
async archivePost(input) {
|
|
757
|
+
this.checkAbort();
|
|
758
|
+
const record = await this.getPostRecord(input.postId);
|
|
759
|
+
this.assertPostAuthor(record);
|
|
760
|
+
if (record.status !== 'archived') {
|
|
761
|
+
await this.repository.transaction(async () => {
|
|
762
|
+
const event = {
|
|
763
|
+
...this.eventBase(input.postId, await this.repository.nextAggregateVersion(input.postId)),
|
|
764
|
+
type: 'post.archived',
|
|
765
|
+
postId: input.postId,
|
|
766
|
+
...(input.reason ? { reason: input.reason } : {}),
|
|
767
|
+
...(input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}),
|
|
768
|
+
};
|
|
769
|
+
await this.repository.insertEvent(event);
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
return await this.getPostRecord(input.postId);
|
|
773
|
+
}
|
|
774
|
+
async acknowledgePost(input) {
|
|
775
|
+
this.checkAbort();
|
|
776
|
+
const record = await this.getPostRecord(input.postId);
|
|
777
|
+
// Acknowledging twice is the same statement, so the second is a no-op
|
|
778
|
+
// rather than a second row or an error.
|
|
779
|
+
const already = record.acknowledgments.some((entry) => entry.actor.id === this.actor.id && entry.actor.kind === this.actor.kind);
|
|
780
|
+
if (!already) {
|
|
781
|
+
await this.repository.transaction(async () => {
|
|
782
|
+
const event = {
|
|
783
|
+
...this.eventBase(input.postId, await this.repository.nextAggregateVersion(input.postId)),
|
|
784
|
+
type: 'post.acknowledged',
|
|
785
|
+
postId: input.postId,
|
|
786
|
+
...(input.note ? { note: input.note } : {}),
|
|
787
|
+
...(input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}),
|
|
788
|
+
};
|
|
789
|
+
await this.repository.insertEvent(event);
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
return await this.getPostRecord(input.postId);
|
|
793
|
+
}
|
|
794
|
+
async withdrawPostAcknowledgment(input) {
|
|
795
|
+
this.checkAbort();
|
|
796
|
+
const record = await this.getPostRecord(input.postId);
|
|
797
|
+
const mine = record.acknowledgments.some((entry) => entry.actor.id === this.actor.id && entry.actor.kind === this.actor.kind);
|
|
798
|
+
if (mine) {
|
|
799
|
+
await this.repository.transaction(async () => {
|
|
800
|
+
const event = {
|
|
801
|
+
...this.eventBase(input.postId, await this.repository.nextAggregateVersion(input.postId)),
|
|
802
|
+
type: 'post.acknowledgment.withdrawn',
|
|
803
|
+
postId: input.postId,
|
|
804
|
+
...(input.reason ? { reason: input.reason } : {}),
|
|
805
|
+
};
|
|
806
|
+
await this.repository.insertEvent(event);
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
return await this.getPostRecord(input.postId);
|
|
810
|
+
}
|
|
811
|
+
async postRoster(postId) {
|
|
812
|
+
this.checkAbort();
|
|
813
|
+
await this.requireVisibleItem(postId, 'post');
|
|
814
|
+
const roster = await this.repository.postRoster(postId);
|
|
815
|
+
if (!roster)
|
|
816
|
+
throw new SynomemError('ITEM_NOT_FOUND', `Unknown post: ${postId}`);
|
|
817
|
+
return roster;
|
|
818
|
+
}
|
|
572
819
|
async getNoteRecord(id) {
|
|
573
820
|
await this.requireVisibleItem(id, 'note');
|
|
574
821
|
const record = noteRecordsFromEvents(await this.repository.getReadableItemEvents(id))[0];
|
|
@@ -986,10 +1233,34 @@ export class SynomemCore {
|
|
|
986
1233
|
});
|
|
987
1234
|
return await this.getTodoRecord(input.todoId);
|
|
988
1235
|
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Turns an agent name in a filter into the canonical ID the records hold.
|
|
1238
|
+
*
|
|
1239
|
+
* Callers filter by the name they know — a handle or an alias — while every
|
|
1240
|
+
* record stores the opaque ID. Without this the filter silently matches
|
|
1241
|
+
* nothing, which reads as "there is nothing here" rather than "that name
|
|
1242
|
+
* means something else now".
|
|
1243
|
+
*
|
|
1244
|
+
* An unresolvable name is passed through unchanged so it can match a legacy
|
|
1245
|
+
* name-shaped ID rather than being swallowed.
|
|
1246
|
+
*/
|
|
1247
|
+
async canonicalAgentId(name) {
|
|
1248
|
+
if (!name)
|
|
1249
|
+
return name;
|
|
1250
|
+
const resolved = await this.repository.resolveAgent(name);
|
|
1251
|
+
return resolved.match?.id ?? name;
|
|
1252
|
+
}
|
|
989
1253
|
async listItems(input) {
|
|
990
1254
|
this.checkAbort();
|
|
991
1255
|
const parsed = this.validate(() => itemListInputSchema.parse(input));
|
|
992
|
-
|
|
1256
|
+
const resolved = {
|
|
1257
|
+
...parsed,
|
|
1258
|
+
...(parsed.participantAgentId
|
|
1259
|
+
? { participantAgentId: await this.canonicalAgentId(parsed.participantAgentId) }
|
|
1260
|
+
: {}),
|
|
1261
|
+
...(parsed.actorId ? { actorId: await this.canonicalAgentId(parsed.actorId) } : {}),
|
|
1262
|
+
};
|
|
1263
|
+
return await this.repository.listItemSummaries(resolved, this.actor);
|
|
993
1264
|
}
|
|
994
1265
|
async listItemChanges(input) {
|
|
995
1266
|
this.checkAbort();
|
|
@@ -1072,8 +1343,8 @@ export class SynomemCore {
|
|
|
1072
1343
|
* check that silently lags the migration runner reports a healthy database as
|
|
1073
1344
|
* broken.
|
|
1074
1345
|
*/
|
|
1075
|
-
const CURRENT_SCHEMA_VERSION =
|
|
1076
|
-
const EXPECTED_APPLIED_MIGRATIONS = [1, 2, 3, 4, 5];
|
|
1346
|
+
const CURRENT_SCHEMA_VERSION = 7;
|
|
1347
|
+
const EXPECTED_APPLIED_MIGRATIONS = [1, 2, 3, 4, 5, 6, 7];
|
|
1077
1348
|
export class SynomemClient extends SynomemCore {
|
|
1078
1349
|
home;
|
|
1079
1350
|
storage;
|