synomem 0.1.1 → 0.3.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 (73) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/CHANGELOG.md +37 -0
  3. package/README.md +26 -13
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +379 -51
  6. package/dist/cli.js.map +1 -1
  7. package/dist/client.d.ts +131 -17
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +489 -76
  10. package/dist/client.js.map +1 -1
  11. package/dist/config.d.ts +2 -2
  12. package/dist/config.js +8 -8
  13. package/dist/import.d.ts +356 -13
  14. package/dist/import.d.ts.map +1 -1
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +2 -2
  18. package/dist/index.js.map +1 -1
  19. package/dist/mcp/index.d.ts.map +1 -1
  20. package/dist/mcp/index.js +213 -31
  21. package/dist/mcp/index.js.map +1 -1
  22. package/dist/mcp-server.js +54 -8
  23. package/dist/mcp-server.js.map +1 -1
  24. package/dist/ports/repository.d.ts +21 -1
  25. package/dist/ports/repository.d.ts.map +1 -1
  26. package/dist/projections.d.ts +26 -1
  27. package/dist/projections.d.ts.map +1 -1
  28. package/dist/projections.js +208 -44
  29. package/dist/projections.js.map +1 -1
  30. package/dist/remote.d.ts +82 -9
  31. package/dist/remote.d.ts.map +1 -1
  32. package/dist/remote.js +53 -2
  33. package/dist/remote.js.map +1 -1
  34. package/dist/schemas.d.ts +476 -21
  35. package/dist/schemas.d.ts.map +1 -1
  36. package/dist/schemas.js +204 -26
  37. package/dist/schemas.js.map +1 -1
  38. package/dist/service.d.ts +82 -10
  39. package/dist/service.d.ts.map +1 -1
  40. package/dist/skill-install.d.ts +8 -2
  41. package/dist/skill-install.d.ts.map +1 -1
  42. package/dist/skill-install.js +6 -11
  43. package/dist/skill-install.js.map +1 -1
  44. package/dist/storage.d.ts +51 -1
  45. package/dist/storage.d.ts.map +1 -1
  46. package/dist/storage.js +366 -36
  47. package/dist/storage.js.map +1 -1
  48. package/dist/types.d.ts +310 -38
  49. package/dist/types.d.ts.map +1 -1
  50. package/docs/cli.md +53 -20
  51. package/docs/examples.md +4 -4
  52. package/docs/mcp.md +12 -4
  53. package/docs/skill.md +10 -7
  54. package/docs/storage-format.md +4 -4
  55. package/openapi/synomem-v1.yaml +24 -24
  56. package/package.json +1 -1
  57. package/skills/synomem/SKILL.md +24 -8
  58. package/skills/synomem/agents/openai.yaml +1 -1
  59. package/skills/synomem/references/examples.md +1 -1
  60. package/src/cli.ts +610 -95
  61. package/src/client.ts +587 -79
  62. package/src/config.ts +8 -8
  63. package/src/index.ts +11 -1
  64. package/src/mcp/index.ts +265 -30
  65. package/src/mcp-server.ts +58 -8
  66. package/src/ports/repository.ts +21 -0
  67. package/src/projections.ts +209 -44
  68. package/src/remote.ts +166 -8
  69. package/src/schemas.ts +211 -26
  70. package/src/service.ts +85 -7
  71. package/src/skill-install.ts +14 -17
  72. package/src/storage.ts +472 -34
  73. package/src/types.ts +332 -39
package/src/schemas.ts CHANGED
@@ -24,6 +24,30 @@ export const agentIdSchema = z
24
24
  .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'Use lowercase ASCII letters, digits, and hyphens')
25
25
  .refine((id) => !reservedIds.has(id), 'Reserved agent ID');
26
26
 
27
+ /**
28
+ * An alias as written, folded to the canonical lowercase form.
29
+ *
30
+ * People type `Mike` and `mike` interchangeably, so accepting either and
31
+ * storing one keeps a single alias from being claimed twice in two casings.
32
+ */
33
+ export const agentAliasSchema = z
34
+ .string()
35
+ .trim()
36
+ .min(1)
37
+ .max(63)
38
+ .regex(/^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$/, 'Use ASCII letters, digits, and hyphens')
39
+ .transform((alias) => alias.toLowerCase())
40
+ .refine((alias) => !reservedIds.has(alias), 'Reserved agent ID');
41
+
42
+ /**
43
+ * A name offered for lookup, which may be an ID or an alias in any casing.
44
+ *
45
+ * Lookups are deliberately more permissive than writes: rejecting `Mycroft`
46
+ * for its capital letter would tell the caller nothing useful about whether
47
+ * that agent exists.
48
+ */
49
+ export const agentLookupSchema = z.string().trim().min(1).max(100);
50
+
27
51
  export const actorSchema = z.object({
28
52
  kind: z.enum(['human', 'agent', 'system']),
29
53
  id: agentIdSchema,
@@ -88,7 +112,7 @@ export const evidenceSchema = z
88
112
  export const profileSchema = z.object({
89
113
  id: agentIdSchema,
90
114
  displayName: z.string().trim().min(1).max(200),
91
- aliases: z.array(agentIdSchema).max(50).optional(),
115
+ aliases: z.array(agentAliasSchema).max(50).optional(),
92
116
  description: z.string().trim().max(2000).optional(),
93
117
  createdAt: z.string().datetime({ offset: true }),
94
118
  metadata: metadataSchema.optional(),
@@ -97,6 +121,21 @@ export const profileSchema = z.object({
97
121
  export const createAgentSchema = profileSchema.omit({ createdAt: true });
98
122
  export const updateAgentSchema = createAgentSchema.omit({ id: true }).partial();
99
123
 
124
+ /**
125
+ * A runtime binding is a claim about where an agent runs, so the fields stay
126
+ * deliberately loose: Synomem should record a runtime it has never heard of
127
+ * rather than reject an install it cannot classify.
128
+ */
129
+ export const bindRuntimeSchema = z
130
+ .object({
131
+ agentId: z.string().trim().min(1).max(100),
132
+ runtime: z.string().trim().min(1).max(100),
133
+ profile: z.string().trim().min(1).max(100).optional(),
134
+ installationId: z.string().trim().min(1).max(100).optional(),
135
+ capabilities: metadataSchema.optional(),
136
+ })
137
+ .strict();
138
+
100
139
  const baseEventSchema = z.object({
101
140
  schemaVersion: z.literal(1),
102
141
  id: z.string().regex(/^[0-9A-HJKMNP-TV-Z]{26}$/),
@@ -218,7 +257,48 @@ const noteArchivedSchema = baseEventSchema.extend({
218
257
  noteId: z.string().length(26),
219
258
  });
220
259
 
221
- const todoDueSchema = z.discriminatedUnion('kind', [
260
+ /**
261
+ * Post events. A post carries no recipient, assignee or visibility: it is
262
+ * addressed to the workspace, and workspace membership is the audience. Adding
263
+ * a visibility field would create a second, weaker way to hide a record.
264
+ */
265
+ const postFields = {
266
+ title: z
267
+ .string()
268
+ .trim()
269
+ .min(1)
270
+ .max(200)
271
+ .regex(/^[^\r\n]+$/),
272
+ body: z.string().trim().min(1).max(32_000),
273
+ tags: z.array(kudosTagSchema).max(20).optional(),
274
+ };
275
+ const postCreatedSchema = baseEventSchema.extend({
276
+ type: z.literal('post.created'),
277
+ ...postFields,
278
+ replyTo: z.string().length(26).optional(),
279
+ });
280
+ const postEditedSchema = baseEventSchema.extend({
281
+ type: z.literal('post.edited'),
282
+ postId: z.string().length(26),
283
+ ...postFields,
284
+ });
285
+ const postArchivedSchema = baseEventSchema.extend({
286
+ type: z.literal('post.archived'),
287
+ postId: z.string().length(26),
288
+ reason: z.string().trim().min(1).max(2000).optional(),
289
+ });
290
+ const postAcknowledgedSchema = baseEventSchema.extend({
291
+ type: z.literal('post.acknowledged'),
292
+ postId: z.string().length(26),
293
+ note: z.string().trim().min(1).max(2000).optional(),
294
+ });
295
+ const postAcknowledgmentWithdrawnSchema = baseEventSchema.extend({
296
+ type: z.literal('post.acknowledgment.withdrawn'),
297
+ postId: z.string().length(26),
298
+ reason: z.string().trim().min(1).max(2000).optional(),
299
+ });
300
+
301
+ const taskDueSchema = z.discriminatedUnion('kind', [
222
302
  z.object({
223
303
  kind: z.literal('date'),
224
304
  date: z
@@ -247,7 +327,7 @@ const todoDueSchema = z.discriminatedUnion('kind', [
247
327
  }, 'Use a valid IANA time-zone identifier'),
248
328
  }),
249
329
  ]);
250
- const todoFields = {
330
+ const taskFields = {
251
331
  title: z
252
332
  .string()
253
333
  .trim()
@@ -256,15 +336,64 @@ const todoFields = {
256
336
  .regex(/^[^\r\n]+$/),
257
337
  description: z.string().trim().max(16_000).optional(),
258
338
  priority: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)]),
259
- due: todoDueSchema.optional(),
339
+ due: taskDueSchema.optional(),
260
340
  tags: z.array(kudosTagSchema).max(20).optional(),
261
341
  visibility: z.enum(['private', 'workspace', 'public']),
262
342
  };
263
- const todoCreatedSchema = baseEventSchema.extend({
264
- type: z.literal('todo.created'),
343
+ const taskCreatedSchema = baseEventSchema.extend({
344
+ type: z.literal('task.created'),
265
345
  assigneeAgentId: agentIdSchema,
266
346
  assigneeDisplayName: z.string().trim().min(1).max(200),
267
347
  requiresAcceptance: z.boolean(),
348
+ ...taskFields,
349
+ });
350
+ const taskUpdatedSchema = baseEventSchema.extend({
351
+ type: z.literal('task.updated'),
352
+ taskId: z.string().length(26),
353
+ ...taskFields,
354
+ });
355
+ const taskCompletedSchema = baseEventSchema.extend({
356
+ type: z.literal('task.completed'),
357
+ taskId: z.string().length(26),
358
+ note: z.string().trim().min(1).max(2000).optional(),
359
+ });
360
+ const taskReopenedSchema = baseEventSchema.extend({
361
+ type: z.literal('task.reopened'),
362
+ taskId: z.string().length(26),
363
+ });
364
+ const taskAcceptedSchema = baseEventSchema.extend({
365
+ type: z.literal('task.accepted'),
366
+ taskId: z.string().length(26),
367
+ // Optional: accepting without comment is a complete answer on its own.
368
+ response: z.string().trim().min(1).max(2000).optional(),
369
+ });
370
+ const taskRejectedSchema = baseEventSchema.extend({
371
+ type: z.literal('task.rejected'),
372
+ taskId: z.string().length(26),
373
+ // Required. A refusal with no reason tells the assigner only that the work
374
+ // will not happen, not whether to reassign it, wait, or change the request.
375
+ response: z.string().trim().min(1).max(2000),
376
+ });
377
+ const taskCanceledSchema = baseEventSchema.extend({
378
+ type: z.literal('task.canceled'),
379
+ taskId: z.string().length(26),
380
+ reason: z.string().trim().min(1).max(2000).optional(),
381
+ });
382
+
383
+ /**
384
+ * A Todo is owner-only, so it carries no assignee and no acceptance lifecycle.
385
+ * `details` rather than `description` keeps it lexically distinct from a Task in
386
+ * every payload, which makes a mix-up visible in a log rather than silent.
387
+ */
388
+ const todoFields = {
389
+ title: z.string().trim().min(1).max(200),
390
+ details: z.string().trim().min(1).max(4000).optional(),
391
+ priority: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)]),
392
+ due: taskDueSchema.optional(),
393
+ tags: z.array(kudosTagSchema).max(20).optional(),
394
+ };
395
+ const todoCreatedSchema = baseEventSchema.extend({
396
+ type: z.literal('todo.created'),
268
397
  ...todoFields,
269
398
  });
270
399
  const todoUpdatedSchema = baseEventSchema.extend({
@@ -281,22 +410,22 @@ const todoReopenedSchema = baseEventSchema.extend({
281
410
  type: z.literal('todo.reopened'),
282
411
  todoId: z.string().length(26),
283
412
  });
284
- const todoAcceptedSchema = baseEventSchema.extend({
285
- type: z.literal('todo.accepted'),
286
- todoId: z.string().length(26),
287
- });
288
- const todoRejectedSchema = baseEventSchema.extend({
289
- type: z.literal('todo.rejected'),
290
- todoId: z.string().length(26),
291
- reason: z.string().trim().min(1).max(2000).optional(),
292
- });
293
413
  const todoCanceledSchema = baseEventSchema.extend({
294
414
  type: z.literal('todo.canceled'),
295
415
  todoId: z.string().length(26),
296
416
  reason: z.string().trim().min(1).max(2000).optional(),
297
417
  });
418
+ const todoArchivedSchema = baseEventSchema.extend({
419
+ type: z.literal('todo.archived'),
420
+ todoId: z.string().length(26),
421
+ });
298
422
 
299
423
  export const eventSchema = z.discriminatedUnion('type', [
424
+ postCreatedSchema,
425
+ postEditedSchema,
426
+ postArchivedSchema,
427
+ postAcknowledgedSchema,
428
+ postAcknowledgmentWithdrawnSchema,
300
429
  kudosGivenSchema,
301
430
  acknowledgedSchema,
302
431
  revokedSchema,
@@ -308,13 +437,19 @@ export const eventSchema = z.discriminatedUnion('type', [
308
437
  noteCreatedSchema,
309
438
  noteRevisedSchema,
310
439
  noteArchivedSchema,
440
+ taskCreatedSchema,
441
+ taskUpdatedSchema,
442
+ taskCompletedSchema,
443
+ taskReopenedSchema,
444
+ taskAcceptedSchema,
445
+ taskRejectedSchema,
446
+ taskCanceledSchema,
311
447
  todoCreatedSchema,
312
448
  todoUpdatedSchema,
313
449
  todoCompletedSchema,
314
450
  todoReopenedSchema,
315
- todoAcceptedSchema,
316
- todoRejectedSchema,
317
451
  todoCanceledSchema,
452
+ todoArchivedSchema,
318
453
  ]);
319
454
 
320
455
  const giveKudosInputSchema = kudosGivenSchema
@@ -388,6 +523,24 @@ export const sendMemoSchema = z
388
523
  ...mutationMetadata,
389
524
  })
390
525
  .strict();
526
+ export const createPostSchema = z
527
+ .object({
528
+ ...postFields,
529
+ replyTo: z.string().length(26).optional(),
530
+ ...mutationMetadata,
531
+ })
532
+ .strict();
533
+ export const updatePostSchema = z
534
+ .object({
535
+ postId: z.string().length(26),
536
+ expectedVersion: z.number().int().min(1),
537
+ title: postFields.title.optional(),
538
+ body: postFields.body.optional(),
539
+ tags: postFields.tags,
540
+ idempotencyKey: z.string().trim().min(1).max(200).optional(),
541
+ })
542
+ .strict();
543
+
391
544
  export const createNoteSchema = z
392
545
  .object({
393
546
  ownerAgentId: agentIdSchema.optional(),
@@ -407,15 +560,44 @@ export const reviseNoteSchema = z
407
560
  ...mutationMetadata,
408
561
  })
409
562
  .strict();
410
- export const createTodoSchema = z
563
+ export const createTaskSchema = z
411
564
  .object({
412
565
  assigneeAgentId: agentIdSchema.optional(),
566
+ title: taskCreatedSchema.shape.title,
567
+ description: taskCreatedSchema.shape.description,
568
+ priority: taskCreatedSchema.shape.priority.optional(),
569
+ due: taskDueSchema.optional(),
570
+ tags: z.array(kudosTagSchema).max(20).optional(),
571
+ visibility: z.enum(['private', 'workspace', 'public']).optional(),
572
+ ...mutationMetadata,
573
+ })
574
+ .strict();
575
+ export const updateTaskSchema = z
576
+ .object({
577
+ taskId: z.string().length(26),
578
+ expectedVersion: z.number().int().min(1),
579
+ title: taskCreatedSchema.shape.title.optional(),
580
+ description: taskCreatedSchema.shape.description,
581
+ priority: taskCreatedSchema.shape.priority.optional(),
582
+ due: taskDueSchema.nullable().optional(),
583
+ tags: z.array(kudosTagSchema).max(20).optional(),
584
+ visibility: z.enum(['private', 'workspace', 'public']).optional(),
585
+ ...mutationMetadata,
586
+ })
587
+ .strict();
588
+ /**
589
+ * A Todo takes no assignee and no visibility: it belongs to its author and is
590
+ * always private. Omitting those fields from the input — rather than accepting
591
+ * and ignoring them — means an attempt to assign a Todo fails loudly instead of
592
+ * silently producing a private reminder nobody else can see.
593
+ */
594
+ export const createTodoSchema = z
595
+ .object({
413
596
  title: todoCreatedSchema.shape.title,
414
- description: todoCreatedSchema.shape.description,
597
+ details: todoCreatedSchema.shape.details,
415
598
  priority: todoCreatedSchema.shape.priority.optional(),
416
- due: todoDueSchema.optional(),
599
+ due: taskDueSchema.optional(),
417
600
  tags: z.array(kudosTagSchema).max(20).optional(),
418
- visibility: z.enum(['private', 'workspace', 'public']).optional(),
419
601
  ...mutationMetadata,
420
602
  })
421
603
  .strict();
@@ -424,23 +606,26 @@ export const updateTodoSchema = z
424
606
  todoId: z.string().length(26),
425
607
  expectedVersion: z.number().int().min(1),
426
608
  title: todoCreatedSchema.shape.title.optional(),
427
- description: todoCreatedSchema.shape.description,
609
+ details: todoCreatedSchema.shape.details,
428
610
  priority: todoCreatedSchema.shape.priority.optional(),
429
- due: todoDueSchema.nullable().optional(),
611
+ due: taskDueSchema.nullable().optional(),
430
612
  tags: z.array(kudosTagSchema).max(20).optional(),
431
- visibility: z.enum(['private', 'workspace', 'public']).optional(),
432
613
  ...mutationMetadata,
433
614
  })
434
615
  .strict();
616
+
435
617
  export const itemListInputSchema = z
436
618
  .object({
437
619
  kinds: z
438
- .array(z.enum(['kudos', 'memo', 'note', 'todo']))
439
- .max(4)
620
+ .array(z.enum(['kudos', 'memo', 'note', 'post', 'task', 'todo']))
621
+ .max(5)
440
622
  .optional(),
441
623
  participantAgentId: agentIdSchema.optional(),
442
624
  actorId: agentIdSchema.optional(),
443
625
  actorKind: z.enum(['human', 'agent', 'system']).optional(),
626
+ awaitingResponse: z.boolean().optional(),
627
+ awaitingSince: z.string().datetime({ offset: true }).optional(),
628
+ overdueAsOf: z.string().datetime({ offset: true }).optional(),
444
629
  tag: kudosTagSchema.optional(),
445
630
  status: z.string().trim().min(1).max(50).optional(),
446
631
  pending: z.boolean().optional(),
package/src/service.ts CHANGED
@@ -1,13 +1,23 @@
1
1
  import type {
2
2
  ActorIdentity,
3
+ AgentDirectoryEntry,
3
4
  AgentProfile,
5
+ CreatePostInput,
6
+ PostRecord,
7
+ PostRoster,
8
+ UpdatePostInput,
9
+ AgentResolution,
10
+ AgentRuntimeBinding,
11
+ BindRuntimeInput,
4
12
  ChangePage,
5
13
  ChangesInput,
6
14
  CreateAgentInput,
7
15
  CreateNoteInput,
8
16
  CreateNoteResult,
17
+ CreateTaskInput,
9
18
  CreateTodoInput,
10
19
  CreateTodoResult,
20
+ CreateTaskResult,
11
21
  DoctorResult,
12
22
  GiveKudosInput,
13
23
  GiveKudosResult,
@@ -27,8 +37,10 @@ import type {
27
37
  ReviseNoteInput,
28
38
  SendMemoInput,
29
39
  SendMemoResult,
40
+ TaskRecord,
30
41
  TodoRecord,
31
42
  UpdateAgentInput,
43
+ UpdateTaskInput,
32
44
  UpdateTodoInput,
33
45
  } from './types.js';
34
46
 
@@ -45,7 +57,7 @@ export interface SynomemServiceCapabilities {
45
57
  projections: {
46
58
  writeWinsMarkdown: boolean;
47
59
  writeMemoryMarkdown: boolean;
48
- writeTodosMarkdown: boolean;
60
+ writeTasksMarkdown: boolean;
49
61
  writeInboxEntries: boolean;
50
62
  };
51
63
  }
@@ -66,6 +78,33 @@ export interface SynomemDomainService {
66
78
  update(id: string, changes: UpdateAgentInput): Promise<AgentProfile>;
67
79
  get(idOrAlias: string): Promise<AgentProfile>;
68
80
  list(): Promise<AgentProfile[]>;
81
+ resolve(query: string): Promise<AgentResolution>;
82
+ directory(): Promise<AgentDirectoryEntry[]>;
83
+ bindings(idOrAlias: string): Promise<AgentRuntimeBinding[]>;
84
+ bindRuntime(input: BindRuntimeInput): Promise<AgentRuntimeBinding>;
85
+ unbindRuntime(bindingId: string): Promise<boolean>;
86
+ };
87
+ readonly posts: {
88
+ create(input: CreatePostInput): Promise<{
89
+ record: PostRecord;
90
+ created: boolean;
91
+ deduplicated: boolean;
92
+ }>;
93
+ list(input?: Omit<ItemListInput, 'kinds'>): Promise<Page<ItemSummary>>;
94
+ get(id: string): Promise<PostRecord>;
95
+ update(input: UpdatePostInput): Promise<PostRecord>;
96
+ archive(input: {
97
+ postId: string;
98
+ reason?: string;
99
+ idempotencyKey?: string;
100
+ }): Promise<PostRecord>;
101
+ acknowledge(input: {
102
+ postId: string;
103
+ note?: string;
104
+ idempotencyKey?: string;
105
+ }): Promise<PostRecord>;
106
+ withdrawAcknowledgment(input: { postId: string; reason?: string }): Promise<PostRecord>;
107
+ roster(postId: string): Promise<PostRoster>;
69
108
  };
70
109
  readonly kudos: {
71
110
  give(input: GiveKudosInput): Promise<GiveKudosResult>;
@@ -93,17 +132,43 @@ export interface SynomemDomainService {
93
132
  revise(input: ReviseNoteInput): Promise<NoteRecord>;
94
133
  archive(input: { noteId: string; idempotencyKey?: string }): Promise<NoteRecord>;
95
134
  };
135
+ readonly tasks: {
136
+ create(input: CreateTaskInput): Promise<CreateTaskResult>;
137
+ list(input?: Omit<ItemListInput, 'kinds'>): Promise<Page<ItemSummary>>;
138
+ get(id: string): Promise<TaskRecord>;
139
+ update(input: UpdateTaskInput): Promise<TaskRecord>;
140
+ accept(input: {
141
+ taskId: string;
142
+ /** Optional: conditions, timing, or partial capability. */
143
+ response?: string;
144
+ idempotencyKey?: string;
145
+ }): Promise<TaskRecord>;
146
+ reject(input: {
147
+ taskId: string;
148
+ /** Required: a refusal the assigner cannot act on is barely an answer. */
149
+ response: string;
150
+ idempotencyKey?: string;
151
+ }): Promise<TaskRecord>;
152
+ complete(input: {
153
+ taskId: string;
154
+ note?: string;
155
+ idempotencyKey?: string;
156
+ }): Promise<TaskRecord>;
157
+ reopen(input: { taskId: string; idempotencyKey?: string }): Promise<TaskRecord>;
158
+ cancel(input: {
159
+ taskId: string;
160
+ reason?: string;
161
+ idempotencyKey?: string;
162
+ }): Promise<TaskRecord>;
163
+ };
164
+ /**
165
+ * Private self-reminders. No assignee, no acceptance, owner-only reads.
166
+ */
96
167
  readonly todos: {
97
168
  create(input: CreateTodoInput): Promise<CreateTodoResult>;
98
169
  list(input?: Omit<ItemListInput, 'kinds'>): Promise<Page<ItemSummary>>;
99
170
  get(id: string): Promise<TodoRecord>;
100
171
  update(input: UpdateTodoInput): Promise<TodoRecord>;
101
- accept(input: { todoId: string; idempotencyKey?: string }): Promise<TodoRecord>;
102
- reject(input: {
103
- todoId: string;
104
- reason?: string;
105
- idempotencyKey?: string;
106
- }): Promise<TodoRecord>;
107
172
  complete(input: {
108
173
  todoId: string;
109
174
  note?: string;
@@ -115,6 +180,19 @@ export interface SynomemDomainService {
115
180
  reason?: string;
116
181
  idempotencyKey?: string;
117
182
  }): Promise<TodoRecord>;
183
+ archive(input: { todoId: string; idempotencyKey?: string }): Promise<TodoRecord>;
184
+ };
185
+ /**
186
+ * Unanswered and overdue discovery. Derived from durable events; never a
187
+ * statement about whether an agent is reachable.
188
+ */
189
+ readonly discovery: {
190
+ unanswered(
191
+ input?: Omit<ItemListInput, 'awaitingResponse' | 'pending'> & { olderThanHours?: number },
192
+ ): Promise<Page<ItemSummary>>;
193
+ overdue(
194
+ input?: Omit<ItemListInput, 'overdueAsOf'> & { asOf?: string },
195
+ ): Promise<Page<ItemSummary>>;
118
196
  };
119
197
  readonly items: {
120
198
  list(input?: ItemListInput): Promise<Page<ItemSummary>>;
@@ -57,8 +57,14 @@ export interface SkillOptions {
57
57
  apply?: boolean;
58
58
  force?: boolean;
59
59
  link?: boolean;
60
- actorId?: string;
61
- actorName?: string;
60
+ /**
61
+ * The agent this installation binds to.
62
+ *
63
+ * Only the canonical ID is written into the generated registration command.
64
+ * The display name and kind are read from the agent's profile at startup, so
65
+ * a harness cannot sign another agent's name to work it did.
66
+ */
67
+ agentId?: string;
62
68
  userHome?: string;
63
69
  env?: NodeJS.ProcessEnv;
64
70
  source?: string;
@@ -160,19 +166,10 @@ function shellQuote(value: string): string {
160
166
  return `'${value.replaceAll("'", `'\\''`)}'`;
161
167
  }
162
168
 
163
- function mcpCommand(
164
- runtime: SkillRuntime,
165
- actorId?: string,
166
- actorName?: string,
167
- ): string | undefined {
168
- if (!actorId) return undefined;
169
- const identity = actorSchema.parse({
170
- kind: 'agent',
171
- id: actorId,
172
- displayName: actorName ?? actorId,
173
- });
174
- const name = identity.displayName ?? identity.id;
175
- const args = ['--actor-id', identity.id, '--actor-kind', 'agent', '--actor-name', name];
169
+ function mcpCommand(runtime: SkillRuntime, agentId?: string): string | undefined {
170
+ if (!agentId) return undefined;
171
+ const identity = actorSchema.parse({ kind: 'agent', id: agentId });
172
+ const args = ['--agent-id', identity.id];
176
173
  if (runtime === 'codex') {
177
174
  return `codex mcp add synomem -- synomem-mcp ${args.map(shellQuote).join(' ')}`;
178
175
  }
@@ -258,7 +255,7 @@ export function skillStatus(options: SkillOptions = {}): SkillOperationResult {
258
255
  packageVersion: packageVersion(),
259
256
  locations,
260
257
  mcpCommands: locations
261
- .map((location) => mcpCommand(location.runtime, options.actorId, options.actorName))
258
+ .map((location) => mcpCommand(location.runtime, options.agentId))
262
259
  .filter((value): value is string => Boolean(value)),
263
260
  };
264
261
  }
@@ -326,7 +323,7 @@ export function formatSkillResult(
326
323
  lines.push('', 'Dry run only. Re-run with --yes to apply.');
327
324
  if (result.mcpCommands.length) lines.push('', 'MCP registration:', ...result.mcpCommands);
328
325
  else if (operation === 'install') {
329
- lines.push('', 'Tip: add --actor-id <agent-id> to print MCP registration commands.');
326
+ lines.push('', 'Tip: add --agent <agent-id> to print MCP registration commands.');
330
327
  }
331
328
  return lines.join('\n');
332
329
  }