synomem 0.6.10 → 0.7.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.
Files changed (53) hide show
  1. package/dist/backend.d.ts.map +1 -1
  2. package/dist/backend.js +1 -0
  3. package/dist/backend.js.map +1 -1
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +139 -43
  6. package/dist/cli.js.map +1 -1
  7. package/dist/client.d.ts +22 -1
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +164 -3
  10. package/dist/client.js.map +1 -1
  11. package/dist/errors.d.ts +1 -1
  12. package/dist/errors.d.ts.map +1 -1
  13. package/dist/errors.js +2 -0
  14. package/dist/errors.js.map +1 -1
  15. package/dist/import.d.ts +83 -0
  16. package/dist/import.d.ts.map +1 -1
  17. package/dist/mcp/index.d.ts.map +1 -1
  18. package/dist/mcp/index.js +110 -2
  19. package/dist/mcp/index.js.map +1 -1
  20. package/dist/ports/repository.d.ts +10 -1
  21. package/dist/ports/repository.d.ts.map +1 -1
  22. package/dist/projections.d.ts.map +1 -1
  23. package/dist/projections.js +6 -0
  24. package/dist/projections.js.map +1 -1
  25. package/dist/remote.d.ts +22 -1
  26. package/dist/remote.d.ts.map +1 -1
  27. package/dist/remote.js +18 -4
  28. package/dist/remote.js.map +1 -1
  29. package/dist/schemas.d.ts +130 -0
  30. package/dist/schemas.d.ts.map +1 -1
  31. package/dist/schemas.js +77 -0
  32. package/dist/schemas.js.map +1 -1
  33. package/dist/service.d.ts +14 -1
  34. package/dist/service.d.ts.map +1 -1
  35. package/dist/storage.d.ts +12 -1
  36. package/dist/storage.d.ts.map +1 -1
  37. package/dist/storage.js +136 -19
  38. package/dist/storage.js.map +1 -1
  39. package/dist/types.d.ts +80 -1
  40. package/dist/types.d.ts.map +1 -1
  41. package/package.json +1 -1
  42. package/src/backend.ts +1 -0
  43. package/src/cli.ts +305 -91
  44. package/src/client.ts +184 -2
  45. package/src/errors.ts +2 -0
  46. package/src/mcp/index.ts +140 -1
  47. package/src/ports/repository.ts +8 -0
  48. package/src/projections.ts +6 -0
  49. package/src/remote.ts +65 -4
  50. package/src/schemas.ts +85 -0
  51. package/src/service.ts +18 -0
  52. package/src/storage.ts +167 -13
  53. package/src/types.ts +90 -2
package/src/remote.ts CHANGED
@@ -19,6 +19,9 @@ import type {
19
19
  UpdateTaskInput,
20
20
  UpdateTodoInput,
21
21
  CreateTodoInput,
22
+ CreateTopicInput,
23
+ UpdateTopicInput,
24
+ TopicListInput,
22
25
  } from './types.js';
23
26
 
24
27
  const defaultMaximumResponseBytes = 1024 * 1024;
@@ -37,6 +40,17 @@ export interface RemoteSynomemOptions {
37
40
  signal?: AbortSignal;
38
41
  timeoutMs?: number;
39
42
  maximumResponseBytes?: number;
43
+ /**
44
+ * False when `expectedActor` is only a historical CLI fallback guess (a
45
+ * caller named no real identity via `--actor`, `SYNOMEM_ACTOR_ID`, or a
46
+ * project binding), not an identity the caller actually asserted. Defaults
47
+ * to true, so every existing caller keeps its current strict behavior.
48
+ * `kind: 'system'` already bypasses the match unconditionally (its own
49
+ * historical bootstrap placeholder); this is the same bypass for a
50
+ * fallback of any other kind, without pretending that fallback IS a real
51
+ * identity.
52
+ */
53
+ assertActor?: boolean;
40
54
  }
41
55
 
42
56
  interface ApiErrorEnvelope {
@@ -153,6 +167,7 @@ export class RemoteSynomemService implements SynomemService {
153
167
  private readonly signal?: AbortSignal;
154
168
  private readonly timeoutMs: number;
155
169
  private readonly maximumResponseBytes: number;
170
+ private readonly assertActor: boolean;
156
171
  private initialized = false;
157
172
  private cachedCapabilities?: SynomemServiceCapabilities;
158
173
 
@@ -228,6 +243,48 @@ export class RemoteSynomemService implements SynomemService {
228
243
  ),
229
244
  };
230
245
 
246
+ readonly topics = {
247
+ create: (input: CreateTopicInput) =>
248
+ this.mutation<Awaited<ReturnType<SynomemService['topics']['create']>>>(
249
+ 'POST',
250
+ 'topics',
251
+ input,
252
+ ),
253
+ update: (idOrAlias: string, changes: UpdateTopicInput) =>
254
+ this.mutation<Awaited<ReturnType<SynomemService['topics']['update']>>>(
255
+ 'PATCH',
256
+ `topics/${encodeURIComponent(idOrAlias)}`,
257
+ changes,
258
+ ),
259
+ get: (idOrAlias: string) =>
260
+ this.request<Awaited<ReturnType<SynomemService['topics']['get']>>>(
261
+ 'GET',
262
+ `topics/${encodeURIComponent(idOrAlias)}`,
263
+ ),
264
+ list: (input: TopicListInput = {}) =>
265
+ this.request<Awaited<ReturnType<SynomemService['topics']['list']>>>(
266
+ 'GET',
267
+ `topics${queryString(input)}`,
268
+ ),
269
+ resolve: (query: string) =>
270
+ this.request<Awaited<ReturnType<SynomemService['topics']['resolve']>>>(
271
+ 'GET',
272
+ `topics/resolve?query=${encodeURIComponent(query)}`,
273
+ ),
274
+ archive: (idOrAlias: string) =>
275
+ this.mutation<Awaited<ReturnType<SynomemService['topics']['archive']>>>(
276
+ 'POST',
277
+ `topics/${encodeURIComponent(idOrAlias)}/archive`,
278
+ {},
279
+ ),
280
+ restore: (idOrAlias: string) =>
281
+ this.mutation<Awaited<ReturnType<SynomemService['topics']['restore']>>>(
282
+ 'POST',
283
+ `topics/${encodeURIComponent(idOrAlias)}/restore`,
284
+ {},
285
+ ),
286
+ };
287
+
231
288
  readonly posts = {
232
289
  create: (input: CreatePostInput) =>
233
290
  this.mutation<Awaited<ReturnType<SynomemService['posts']['create']>>>('POST', 'posts', input),
@@ -489,6 +546,7 @@ export class RemoteSynomemService implements SynomemService {
489
546
  throw asSynomemError(error);
490
547
  }
491
548
  this.credentialProvider = options.credentialProvider;
549
+ this.assertActor = options.assertActor ?? true;
492
550
  this.fetchImplementation = options.fetch ?? fetch;
493
551
  this.signal = options.signal;
494
552
  this.timeoutMs = options.timeoutMs ?? defaultTimeoutMs;
@@ -520,10 +578,13 @@ export class RemoteSynomemService implements SynomemService {
520
578
  // credential the server authenticates ever reports that kind back, so
521
579
  // asserting it here would make every one of those commands fail against
522
580
  // a remote backend no matter who is actually calling — exactly the
523
- // bootstrap deadlock the server-side half of this already fixed. Only a
524
- // caller that named a real identity (an agent ID, or an actor supplied
525
- // via --actor/SYNOMEM_ACTOR_ID) gets its match enforced.
526
- const assertedRealActor = this.actor.kind !== 'system';
581
+ // bootstrap deadlock the server-side half of this already fixed.
582
+ // `assertActor: false` is the same bypass for a fallback of any other
583
+ // kind: a caller whose "actor" is only a historical guess (never a real
584
+ // --actor, SYNOMEM_ACTOR_ID, or project binding) that should adopt
585
+ // whatever the credential actually names rather than fail outright.
586
+ // Either way, only a caller that named a real identity gets it enforced.
587
+ const assertedRealActor = this.assertActor && this.actor.kind !== 'system';
527
588
  if (
528
589
  binding.workspaceId !== this.workspaceId ||
529
590
  (assertedRealActor &&
package/src/schemas.ts CHANGED
@@ -74,6 +74,15 @@ export const agentAliasSchema = z
74
74
  */
75
75
  export const agentLookupSchema = z.string().trim().min(1).max(100);
76
76
 
77
+ /** A topic's canonical, opaque, immutable ID: a ULID, uppercase Crockford base32. */
78
+ export const topicIdSchema = z.string().regex(/^[0-9A-HJKMNP-TV-Z]{26}$/);
79
+ /**
80
+ * A record's topics. Deliberately capped far lower than tags: a topic is a
81
+ * stable subject a record genuinely belongs to, not a place to accumulate
82
+ * every word that might one day be searched for.
83
+ */
84
+ const topicIdsFieldSchema = z.array(topicIdSchema).max(10).optional();
85
+
77
86
  export const actorSchema = z.object({
78
87
  kind: z.enum(['human', 'agent', 'system']),
79
88
  id: agentIdSchema,
@@ -201,6 +210,49 @@ export const updateAgentSchema = z
201
210
  })
202
211
  .strict();
203
212
 
213
+ /**
214
+ * A topic alias, folded to canonical lowercase the same way an agent alias is
215
+ * — so `Synomem` and `synomem` cannot be claimed as two different topics.
216
+ */
217
+ export const topicAliasSchema = z
218
+ .string()
219
+ .trim()
220
+ .min(1)
221
+ .max(63)
222
+ .regex(/^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$/, 'Use ASCII letters, digits, and hyphens')
223
+ .transform((alias) => alias.toLowerCase());
224
+ export const topicNameSchema = z.string().trim().min(1).max(80);
225
+ /** A name offered for lookup, which may be an ID or an alias in any casing. */
226
+ export const topicLookupSchema = z.string().trim().min(1).max(100);
227
+
228
+ export const topicProfileSchema = z.object({
229
+ id: topicIdSchema,
230
+ displayName: topicNameSchema,
231
+ aliases: z.array(topicAliasSchema).max(20).optional(),
232
+ status: z.enum(['active', 'archived']).default('active'),
233
+ createdAt: z.string().datetime({ offset: true }),
234
+ });
235
+
236
+ export const createTopicSchema = z
237
+ .object({
238
+ displayName: topicNameSchema,
239
+ aliases: z.array(topicAliasSchema).max(20).optional(),
240
+ })
241
+ .strict();
242
+
243
+ export const updateTopicSchema = z
244
+ .object({
245
+ displayName: topicNameSchema.optional(),
246
+ aliases: z.array(topicAliasSchema).max(20).optional(),
247
+ })
248
+ .strict();
249
+
250
+ export const topicListInputSchema = z
251
+ .object({
252
+ status: z.enum(['active', 'archived']).optional(),
253
+ })
254
+ .strict();
255
+
204
256
  /**
205
257
  * A runtime binding is a claim about where an agent runs, so the fields stay
206
258
  * deliberately loose: Synomem should record a runtime it has never heard of
@@ -251,6 +303,7 @@ const kudosGivenSchema = baseEventSchema.extend({
251
303
  reason: z.string().trim().min(1).max(5000),
252
304
  evidence: z.array(evidenceSchema).max(50).optional(),
253
305
  tags: z.array(kudosTagSchema).max(50).optional(),
306
+ topicIds: topicIdsFieldSchema,
254
307
  visibility: z.enum(['private', 'workspace', 'public']),
255
308
  });
256
309
 
@@ -286,6 +339,19 @@ const agentUpdatedSchema = baseEventSchema.extend({
286
339
  }),
287
340
  });
288
341
 
342
+ const topicCreatedSchema = baseEventSchema.extend({
343
+ type: z.literal('topic.created'),
344
+ topic: topicProfileSchema,
345
+ });
346
+
347
+ const topicUpdatedSchema = baseEventSchema.extend({
348
+ type: z.literal('topic.updated'),
349
+ topicId: topicIdSchema,
350
+ changes: updateTopicSchema.extend({
351
+ status: z.enum(['active', 'archived']).optional(),
352
+ }),
353
+ });
354
+
289
355
  const memoSentSchema = baseEventSchema.extend({
290
356
  type: z.literal('memo.sent'),
291
357
  recipientAgentId: agentIdSchema,
@@ -298,6 +364,7 @@ const memoSentSchema = baseEventSchema.extend({
298
364
  .regex(/^[^\r\n]+$/),
299
365
  body: z.string().trim().min(1).max(16_000),
300
366
  tags: z.array(kudosTagSchema).max(20).optional(),
367
+ topicIds: topicIdsFieldSchema,
301
368
  visibility: z.enum(['private', 'workspace', 'public']),
302
369
  });
303
370
 
@@ -324,6 +391,7 @@ const noteCreatedSchema = baseEventSchema.extend({
324
391
  .regex(/^[^\r\n]+$/),
325
392
  body: z.string().trim().min(1).max(32_000),
326
393
  tags: z.array(kudosTagSchema).max(20).optional(),
394
+ topicIds: topicIdsFieldSchema,
327
395
  visibility: z.literal('private'),
328
396
  });
329
397
  const noteRevisedSchema = baseEventSchema.extend({
@@ -337,6 +405,7 @@ const noteRevisedSchema = baseEventSchema.extend({
337
405
  .regex(/^[^\r\n]+$/),
338
406
  body: z.string().trim().min(1).max(32_000),
339
407
  tags: z.array(kudosTagSchema).max(20).optional(),
408
+ topicIds: topicIdsFieldSchema,
340
409
  visibility: z.literal('private'),
341
410
  });
342
411
  const noteArchivedSchema = baseEventSchema.extend({
@@ -358,6 +427,7 @@ const postFields = {
358
427
  .regex(/^[^\r\n]+$/),
359
428
  body: z.string().trim().min(1).max(32_000),
360
429
  tags: z.array(kudosTagSchema).max(20).optional(),
430
+ topicIds: topicIdsFieldSchema,
361
431
  };
362
432
  const postCreatedSchema = baseEventSchema.extend({
363
433
  type: z.literal('post.created'),
@@ -425,6 +495,7 @@ const taskFields = {
425
495
  priority: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)]),
426
496
  due: taskDueSchema.optional(),
427
497
  tags: z.array(kudosTagSchema).max(20).optional(),
498
+ topicIds: topicIdsFieldSchema,
428
499
  visibility: z.enum(['private', 'workspace', 'public']),
429
500
  };
430
501
  const taskCreatedSchema = baseEventSchema.extend({
@@ -478,6 +549,7 @@ const todoFields = {
478
549
  priority: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)]),
479
550
  due: taskDueSchema.optional(),
480
551
  tags: z.array(kudosTagSchema).max(20).optional(),
552
+ topicIds: topicIdsFieldSchema,
481
553
  };
482
554
  const todoCreatedSchema = baseEventSchema.extend({
483
555
  type: z.literal('todo.created'),
@@ -518,6 +590,8 @@ export const eventSchema = z.discriminatedUnion('type', [
518
590
  revokedSchema,
519
591
  agentCreatedSchema,
520
592
  agentUpdatedSchema,
593
+ topicCreatedSchema,
594
+ topicUpdatedSchema,
521
595
  memoSentSchema,
522
596
  memoReadSchema,
523
597
  memoArchivedSchema,
@@ -555,6 +629,7 @@ const giveKudosInputSchema = kudosGivenSchema
555
629
  title: kudosTitleSchema,
556
630
  evidence: z.array(evidenceSchema).max(10).optional(),
557
631
  tags: z.array(kudosTagSchema).max(20).optional(),
632
+ topicIds: topicIdsFieldSchema,
558
633
  });
559
634
 
560
635
  function enforceKudosPayloadSize(value: unknown, context: z.RefinementCtx): void {
@@ -575,6 +650,7 @@ export const listInputSchema = z
575
650
  actorId: agentIdSchema.optional(),
576
651
  actorKind: z.enum(['human', 'agent', 'system']).optional(),
577
652
  tag: z.string().trim().min(1).max(64).optional(),
653
+ topicId: topicIdSchema.optional(),
578
654
  status: z.enum(['acknowledged', 'unacknowledged']).optional(),
579
655
  visibility: z.enum(['private', 'workspace', 'public']).optional(),
580
656
  revoked: z.boolean().optional(),
@@ -606,6 +682,7 @@ export const sendMemoSchema = z
606
682
  subject: memoSentSchema.shape.subject,
607
683
  body: memoSentSchema.shape.body,
608
684
  tags: z.array(kudosTagSchema).max(20).optional(),
685
+ topicIds: topicIdsFieldSchema,
609
686
  visibility: z.enum(['private', 'workspace', 'public']).optional(),
610
687
  ...mutationMetadata,
611
688
  })
@@ -624,6 +701,7 @@ export const updatePostSchema = z
624
701
  title: postFields.title.optional(),
625
702
  body: postFields.body.optional(),
626
703
  tags: postFields.tags,
704
+ topicIds: postFields.topicIds,
627
705
  idempotencyKey: z.string().trim().min(1).max(200).optional(),
628
706
  })
629
707
  .strict();
@@ -634,6 +712,7 @@ export const createNoteSchema = z
634
712
  title: noteCreatedSchema.shape.title,
635
713
  body: noteCreatedSchema.shape.body,
636
714
  tags: z.array(kudosTagSchema).max(20).optional(),
715
+ topicIds: topicIdsFieldSchema,
637
716
  ...mutationMetadata,
638
717
  })
639
718
  .strict();
@@ -644,6 +723,7 @@ export const reviseNoteSchema = z
644
723
  title: noteCreatedSchema.shape.title.optional(),
645
724
  body: noteCreatedSchema.shape.body.optional(),
646
725
  tags: z.array(kudosTagSchema).max(20).optional(),
726
+ topicIds: topicIdsFieldSchema,
647
727
  ...mutationMetadata,
648
728
  })
649
729
  .strict();
@@ -655,6 +735,7 @@ export const createTaskSchema = z
655
735
  priority: taskCreatedSchema.shape.priority.optional(),
656
736
  due: taskDueSchema.optional(),
657
737
  tags: z.array(kudosTagSchema).max(20).optional(),
738
+ topicIds: topicIdsFieldSchema,
658
739
  visibility: z.enum(['private', 'workspace', 'public']).optional(),
659
740
  ...mutationMetadata,
660
741
  })
@@ -668,6 +749,7 @@ export const updateTaskSchema = z
668
749
  priority: taskCreatedSchema.shape.priority.optional(),
669
750
  due: taskDueSchema.nullable().optional(),
670
751
  tags: z.array(kudosTagSchema).max(20).optional(),
752
+ topicIds: topicIdsFieldSchema,
671
753
  visibility: z.enum(['private', 'workspace', 'public']).optional(),
672
754
  ...mutationMetadata,
673
755
  })
@@ -685,6 +767,7 @@ export const createTodoSchema = z
685
767
  priority: todoCreatedSchema.shape.priority.optional(),
686
768
  due: taskDueSchema.optional(),
687
769
  tags: z.array(kudosTagSchema).max(20).optional(),
770
+ topicIds: topicIdsFieldSchema,
688
771
  ...mutationMetadata,
689
772
  })
690
773
  .strict();
@@ -697,6 +780,7 @@ export const updateTodoSchema = z
697
780
  priority: todoCreatedSchema.shape.priority.optional(),
698
781
  due: taskDueSchema.nullable().optional(),
699
782
  tags: z.array(kudosTagSchema).max(20).optional(),
783
+ topicIds: topicIdsFieldSchema,
700
784
  ...mutationMetadata,
701
785
  })
702
786
  .strict();
@@ -714,6 +798,7 @@ export const itemListInputSchema = z
714
798
  awaitingSince: z.string().datetime({ offset: true }).optional(),
715
799
  overdueAsOf: z.string().datetime({ offset: true }).optional(),
716
800
  tag: kudosTagSchema.optional(),
801
+ topicId: topicIdSchema.optional(),
717
802
  status: z.string().trim().min(1).max(50).optional(),
718
803
  pending: z.boolean().optional(),
719
804
  visibility: z.enum(['private', 'workspace', 'public']).optional(),
package/src/service.ts CHANGED
@@ -43,6 +43,11 @@ import type {
43
43
  UpdateAgentInput,
44
44
  UpdateTaskInput,
45
45
  UpdateTodoInput,
46
+ Topic,
47
+ CreateTopicInput,
48
+ UpdateTopicInput,
49
+ TopicListInput,
50
+ TopicResolution,
46
51
  } from './types.js';
47
52
 
48
53
  export interface SynomemServiceCapabilities {
@@ -90,6 +95,19 @@ export interface SynomemDomainService {
90
95
  bindRuntime(input: BindRuntimeInput): Promise<AgentRuntimeBinding>;
91
96
  unbindRuntime(bindingId: string): Promise<boolean>;
92
97
  };
98
+ /**
99
+ * A controlled, reusable subject a record can be filed under — one
100
+ * canonical display name and a set of aliases, distinct from a tag.
101
+ */
102
+ readonly topics: {
103
+ create(input: CreateTopicInput): Promise<Topic>;
104
+ update(idOrAlias: string, changes: UpdateTopicInput): Promise<Topic>;
105
+ get(idOrAlias: string): Promise<Topic>;
106
+ list(input?: TopicListInput): Promise<Topic[]>;
107
+ resolve(query: string): Promise<TopicResolution>;
108
+ archive(idOrAlias: string): Promise<Topic>;
109
+ restore(idOrAlias: string): Promise<Topic>;
110
+ };
93
111
  readonly posts: {
94
112
  create(input: CreatePostInput): Promise<{
95
113
  record: PostRecord;