@prmichaelsen/remember-mcp 3.17.2 → 3.17.4

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/AGENT.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Agent Context Protocol (ACP)
2
2
 
3
3
  **Also Known As**: The Agent Directory Pattern
4
- **Version**: 5.13.1
4
+ **Version**: 5.15.0
5
5
  **Created**: 2026-02-11
6
6
  **Status**: Production Pattern
7
7
 
@@ -3,9 +3,9 @@
3
3
  > **🤖 Agent Directive**: If you are reading this file, the command `@acp-init` has been invoked. Follow the steps below to execute this command.
4
4
 
5
5
  **Namespace**: acp
6
- **Version**: 1.0.0
6
+ **Version**: 1.1.0
7
7
  **Created**: 2026-02-16
8
- **Last Updated**: 2026-02-16
8
+ **Last Updated**: 2026-03-09
9
9
  **Status**: Active
10
10
  **Scripts**: None
11
11
 
@@ -17,6 +17,38 @@
17
17
 
18
18
  ---
19
19
 
20
+ ## Arguments
21
+
22
+ | Argument | Aliases | Description |
23
+ |---|---|---|
24
+ | `--quick` | `-q` | Fast init: skips version checks, source file review, and documentation sync. Equivalent to `--skip checks,files,sync` |
25
+ | `--skip <items>` | | Comma-separated list of steps to skip. Valid items: `checks`, `sessions`, `docs`, `global`, `keys`, `files`, `sync`, `progress` |
26
+
27
+ ### Skip Items Reference
28
+
29
+ | Item | Steps Skipped | Description |
30
+ |---|---|---|
31
+ | `checks` | Step 1 | ACP version update check |
32
+ | `sessions` | Step 1.5 | Session registration and sibling display |
33
+ | `docs` | Step 2 | Reading agent documentation (progress, designs, milestones, tasks, patterns) |
34
+ | `global` | Step 2.5 | Global package discovery |
35
+ | `keys` | Step 2.8 | Key file index reading |
36
+ | `files` | Steps 3-4 | Source file identification and review |
37
+ | `sync` | Steps 5-6 | Documentation drift detection and stale doc updates |
38
+ | `progress` | Step 7 | Progress tracking updates |
39
+
40
+ ### Argument Parsing
41
+
42
+ Arguments are parsed from the user's invocation using natural language matching:
43
+ - `@acp-init --quick` or `@acp-init -q`
44
+ - `@acp-init --skip checks,sync`
45
+ - `@acp-init --quick --skip sessions` (quick mode plus additional skips)
46
+ - `@acp-init --skip checks,files,sync,progress` (granular control)
47
+
48
+ When `--quick` is combined with `--skip`, the skip sets are merged (union).
49
+
50
+ ---
51
+
20
52
  ## What This Command Does
21
53
 
22
54
  This command performs a comprehensive initialization of the agent's context for working on an ACP-structured project. It checks for ACP updates, reads all documentation in the `agent/` directory, reviews key source files to understand the current implementation, updates any stale documentation, and refreshes progress tracking.
@@ -39,6 +71,8 @@ Unlike `@acp-status` which only reads progress.yaml, or `@acp-proceed` which foc
39
71
 
40
72
  ### 1. Check for ACP Updates
41
73
 
74
+ **Skip item**: `checks` | **Skipped by**: `--quick`
75
+
42
76
  Check if newer version of ACP is available.
43
77
 
44
78
  **Actions**:
@@ -51,6 +85,8 @@ Check if newer version of ACP is available.
51
85
 
52
86
  ### 1.5. Register Session and Show Siblings (Optional)
53
87
 
88
+ **Skip item**: `sessions`
89
+
54
90
  Register this agent session and display any active sibling sessions.
55
91
 
56
92
  **Actions**:
@@ -70,6 +106,8 @@ Active Sessions: 2 others
70
106
 
71
107
  ### 2. Read All Agent Documentation
72
108
 
109
+ **Skip item**: `docs`
110
+
73
111
  Load complete context from the agent/ directory.
74
112
 
75
113
  **Actions**:
@@ -85,6 +123,8 @@ Load complete context from the agent/ directory.
85
123
 
86
124
  ### 2.5. Discover Global Packages (Optional)
87
125
 
126
+ **Skip item**: `global`
127
+
88
128
  Check for globally installed ACP packages.
89
129
 
90
130
  **Actions**:
@@ -119,6 +159,8 @@ Check for globally installed ACP packages.
119
159
 
120
160
  ### 2.8. Read Key Files from Index
121
161
 
162
+ **Skip item**: `keys`
163
+
122
164
  Load critical project files from the key file index.
123
165
 
124
166
  **Actions**:
@@ -147,6 +189,8 @@ Load critical project files from the key file index.
147
189
 
148
190
  ### 3. Identify Key Source Files
149
191
 
192
+ **Skip item**: `files` | **Skipped by**: `--quick`
193
+
150
194
  Determine which source files are most important to review.
151
195
 
152
196
  **Actions**:
@@ -160,6 +204,8 @@ Determine which source files are most important to review.
160
204
 
161
205
  ### 4. Review Key Source Files
162
206
 
207
+ **Skip item**: `files` | **Skipped by**: `--quick`
208
+
163
209
  Read important source files to understand current implementation.
164
210
 
165
211
  **Actions**:
@@ -174,6 +220,8 @@ Read important source files to understand current implementation.
174
220
 
175
221
  ### 5. Identify Documentation Drift
176
222
 
223
+ **Skip item**: `sync` | **Skipped by**: `--quick`
224
+
177
225
  Compare documentation with actual implementation.
178
226
 
179
227
  **Actions**:
@@ -187,6 +235,8 @@ Compare documentation with actual implementation.
187
235
 
188
236
  ### 6. Update Stale Documentation
189
237
 
238
+ **Skip item**: `sync` | **Skipped by**: `--quick`
239
+
190
240
  Refresh outdated documentation to match current state.
191
241
 
192
242
  **Actions**:
@@ -200,6 +250,8 @@ Refresh outdated documentation to match current state.
200
250
 
201
251
  ### 7. Update Progress Tracking
202
252
 
253
+ **Skip item**: `progress`
254
+
203
255
  Refresh progress.yaml with latest status.
204
256
 
205
257
  **Actions**:
@@ -227,6 +279,26 @@ Provide comprehensive status report.
227
279
 
228
280
  **Expected Outcome**: User has complete context and knows what to do next
229
281
 
282
+ ### 9. Display Usage Tip
283
+
284
+ Show a helpful tip about init flags when no flags were used.
285
+
286
+ **Actions**:
287
+ - If the user invoked `@acp-init` **without** `--quick` or `--skip`, display the following tip at the end of the output:
288
+ ```
289
+ Tip: Use `@acp-init --quick` to skip version checks, source file review, and doc sync for faster startup. Use `--skip <items>` to skip individual steps (e.g. `--skip checks,files`).
290
+ ```
291
+ - If the user already used `--quick` or `--skip`, do **not** display the tip (they already know about it).
292
+
293
+ **Expected Outcome**: Users discover the faster init modes naturally
294
+
295
+ ### Handling Skipped Steps
296
+
297
+ When a step is skipped (via `--quick` or `--skip`), the agent should:
298
+ 1. **Not execute** any actions for that step
299
+ 2. **Not display** the step's section header or output block
300
+ 3. Simply omit the step silently — no "skipped" messages needed unless the agent chooses to show a compact summary of what was skipped at the top of the output
301
+
230
302
  ---
231
303
 
232
304
  ## Verification
@@ -372,6 +444,22 @@ Ready to proceed with task-2 completion.
372
444
 
373
445
  **Result**: Complete onboarding - reads all documentation, understands architecture from source code, gets current status, ready to contribute immediately
374
446
 
447
+ ### Example 4: Quick Init
448
+
449
+ **Context**: Returning to a familiar project, just need docs and status
450
+
451
+ **Invocation**: `@acp-init --quick`
452
+
453
+ **Result**: Skips version checks, source file review, and doc sync. Reads agent documentation, key files, reports status — fast startup in ~10 seconds
454
+
455
+ ### Example 5: Selective Skip
456
+
457
+ **Context**: Want everything except version checks and session registration
458
+
459
+ **Invocation**: `@acp-init --skip checks,sessions`
460
+
461
+ **Result**: Full init minus the two skipped steps. All docs read, files reviewed, sync performed, status reported
462
+
375
463
  ---
376
464
 
377
465
  ## Related Commands
@@ -450,9 +538,9 @@ Ready to proceed with task-2 completion.
450
538
 
451
539
  **Namespace**: acp
452
540
  **Command**: init
453
- **Version**: 1.0.0
541
+ **Version**: 1.1.0
454
542
  **Created**: 2026-02-16
455
- **Last Updated**: 2026-02-16
543
+ **Last Updated**: 2026-03-09
456
544
  **Status**: Active
457
545
  **Compatibility**: ACP 1.0.3+
458
546
  **Author**: ACP Project
@@ -3857,8 +3857,8 @@ var RelationshipService = class {
3857
3857
  }
3858
3858
  // ── Create ──────────────────────────────────────────────────────────
3859
3859
  async create(input) {
3860
- if (input.memory_ids.length < 2) {
3861
- throw new Error("At least 2 memory IDs are required to create a relationship");
3860
+ if (input.memory_ids.length < 1) {
3861
+ throw new Error("At least 1 memory ID is required to create a relationship");
3862
3862
  }
3863
3863
  const validated = await this.validateMemoryIds(input.memory_ids);
3864
3864
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -4669,24 +4669,76 @@ var SpaceService = class {
4669
4669
  const parentId = String(originalMemory.properties.parent_id ?? "");
4670
4670
  const threadRootId = String(originalMemory.properties.thread_root_id ?? parentId);
4671
4671
  const contentPreview = String(originalMemory.properties.content ?? "").slice(0, 200);
4672
- if (successfulPublications.some((p) => p.startsWith("spaces:"))) {
4673
- for (const spaceId of spaces) {
4674
- this.eventBus.emit({ type: "comment.published_to_space", memory_id: request.payload.memory_id, parent_id: parentId, thread_root_id: threadRootId, content_preview: contentPreview, space_id: spaceId, owner_id: this.userId }, actor);
4672
+ let parentOwnerId = "";
4673
+ try {
4674
+ const parentMemory = await fetchMemoryWithAllProperties(this.userCollection, parentId);
4675
+ if (parentMemory) {
4676
+ const parentUserId = String(parentMemory.properties.user_id ?? "");
4677
+ if (parentUserId && parentUserId !== this.userId) {
4678
+ parentOwnerId = parentUserId;
4679
+ } else if (parentUserId === this.userId) {
4680
+ parentOwnerId = this.userId;
4681
+ }
4682
+ }
4683
+ if (!parentOwnerId) {
4684
+ const publicCollection = await ensurePublicCollection(this.weaviateClient);
4685
+ const directHit = await fetchMemoryWithAllProperties(publicCollection, parentId);
4686
+ if (directHit) {
4687
+ parentOwnerId = String(directHit.properties.author_id ?? directHit.properties.user_id ?? "");
4688
+ }
4689
+ if (!parentOwnerId) {
4690
+ const filter = publicCollection.filter.byProperty("original_memory_id").equal(parentId);
4691
+ const result = await publicCollection.query.fetchObjects({ filters: filter, limit: 1 });
4692
+ if (result.objects.length > 0) {
4693
+ parentOwnerId = String(result.objects[0].properties.author_id ?? "");
4694
+ }
4695
+ }
4696
+ }
4697
+ if (!parentOwnerId) {
4698
+ for (const groupId of groups) {
4699
+ try {
4700
+ const groupCollectionName = getCollectionName(CollectionType.GROUPS, groupId);
4701
+ const groupCollection = this.weaviateClient.collections.get(groupCollectionName);
4702
+ const gDirect = await fetchMemoryWithAllProperties(groupCollection, parentId);
4703
+ if (gDirect) {
4704
+ parentOwnerId = String(gDirect.properties.author_id ?? gDirect.properties.user_id ?? "");
4705
+ break;
4706
+ }
4707
+ const gFilter = groupCollection.filter.byProperty("original_memory_id").equal(parentId);
4708
+ const gResult = await groupCollection.query.fetchObjects({ filters: gFilter, limit: 1 });
4709
+ if (gResult.objects.length > 0) {
4710
+ parentOwnerId = String(gResult.objects[0].properties.author_id ?? "");
4711
+ break;
4712
+ }
4713
+ } catch {
4714
+ }
4715
+ }
4675
4716
  }
4717
+ } catch (err2) {
4718
+ this.logger.warn("Failed to resolve parent owner for comment event", { parentId, err: err2 });
4676
4719
  }
4677
- const publishedGroups = groups.filter((g) => successfulPublications.some((p) => p === `group: ${g}`));
4678
- for (const groupId of publishedGroups) {
4679
- this.eventBus.emit({ type: "comment.published_to_group", memory_id: request.payload.memory_id, parent_id: parentId, thread_root_id: threadRootId, content_preview: contentPreview, group_id: groupId, owner_id: this.userId }, actor);
4720
+ if (!parentOwnerId) {
4721
+ this.logger.warn("Skipping comment webhook \u2014 could not resolve parent_owner_id", { parentId, memoryId: request.payload.memory_id });
4722
+ } else {
4723
+ if (successfulPublications.some((p) => p.startsWith("spaces:"))) {
4724
+ for (const spaceId of spaces) {
4725
+ await this.eventBus.emit({ type: "comment.published_to_space", memory_id: request.payload.memory_id, parent_id: parentId, thread_root_id: threadRootId, content_preview: contentPreview, space_id: spaceId, owner_id: this.userId, parent_owner_id: parentOwnerId }, actor);
4726
+ }
4727
+ }
4728
+ const publishedGroups = groups.filter((g) => successfulPublications.some((p) => p === `group: ${g}`));
4729
+ for (const groupId of publishedGroups) {
4730
+ await this.eventBus.emit({ type: "comment.published_to_group", memory_id: request.payload.memory_id, parent_id: parentId, thread_root_id: threadRootId, content_preview: contentPreview, group_id: groupId, owner_id: this.userId, parent_owner_id: parentOwnerId }, actor);
4731
+ }
4680
4732
  }
4681
4733
  } else {
4682
4734
  if (successfulPublications.some((p) => p.startsWith("spaces:"))) {
4683
4735
  for (const spaceId of spaces) {
4684
- this.eventBus.emit({ type: "memory.published_to_space", memory_id: request.payload.memory_id, title, space_id: spaceId, owner_id: this.userId }, actor);
4736
+ await this.eventBus.emit({ type: "memory.published_to_space", memory_id: request.payload.memory_id, title, space_id: spaceId, owner_id: this.userId }, actor);
4685
4737
  }
4686
4738
  }
4687
4739
  const publishedGroups = groups.filter((g) => successfulPublications.some((p) => p === `group: ${g}`));
4688
4740
  for (const groupId of publishedGroups) {
4689
- this.eventBus.emit({ type: "memory.published_to_group", memory_id: request.payload.memory_id, title, group_id: groupId, owner_id: this.userId }, actor);
4741
+ await this.eventBus.emit({ type: "memory.published_to_group", memory_id: request.payload.memory_id, title, group_id: groupId, owner_id: this.userId }, actor);
4690
4742
  }
4691
4743
  }
4692
4744
  }
@@ -4783,7 +4835,7 @@ var SpaceService = class {
4783
4835
  targets.push({ kind: "group", id: groupId });
4784
4836
  }
4785
4837
  if (targets.length > 0) {
4786
- this.eventBus.emit({ type: "memory.retracted", memory_id: request.payload.memory_id, owner_id: this.userId, targets }, { type: "user", id: this.userId });
4838
+ await this.eventBus.emit({ type: "memory.retracted", memory_id: request.payload.memory_id, owner_id: this.userId, targets }, { type: "user", id: this.userId });
4787
4839
  }
4788
4840
  }
4789
4841
  return {
@@ -5487,13 +5539,28 @@ var SpaceService = class {
5487
5539
  } catch {
5488
5540
  }
5489
5541
  }
5490
- if (!props) {
5491
- return { space_ids: [], group_ids: [] };
5542
+ if (props) {
5543
+ return {
5544
+ space_ids: Array.isArray(props.space_ids) ? props.space_ids : [],
5545
+ group_ids: Array.isArray(props.group_ids) ? props.group_ids : []
5546
+ };
5492
5547
  }
5493
- return {
5494
- space_ids: Array.isArray(props.space_ids) ? props.space_ids : [],
5495
- group_ids: Array.isArray(props.group_ids) ? props.group_ids : []
5496
- };
5548
+ try {
5549
+ const collectionName = await this.memoryIndex.lookup(memoryId);
5550
+ if (collectionName) {
5551
+ const collection = this.weaviateClient.collections.get(collectionName);
5552
+ const obj = await fetchMemoryWithAllProperties(collection, memoryId);
5553
+ if (obj) {
5554
+ const p = obj.properties;
5555
+ return {
5556
+ space_ids: Array.isArray(p.space_ids) ? p.space_ids : [],
5557
+ group_ids: Array.isArray(p.group_ids) ? p.group_ids : []
5558
+ };
5559
+ }
5560
+ }
5561
+ } catch {
5562
+ }
5563
+ return { space_ids: [], group_ids: [] };
5497
5564
  }
5498
5565
  };
5499
5566
 
@@ -5719,7 +5786,8 @@ var INITIAL_PERCEPTION = {
5719
5786
  import { createHmac } from "node:crypto";
5720
5787
  function signWebhookPayload(webhookId, timestamp, body, secret) {
5721
5788
  const content = `${webhookId}.${timestamp}.${body}`;
5722
- const hmac = createHmac("sha256", secret).update(content).digest("base64");
5789
+ const secretBytes = Buffer.from(secret, "base64");
5790
+ const hmac = createHmac("sha256", secretBytes).update(content).digest("base64");
5723
5791
  return `v1,${hmac}`;
5724
5792
  }
5725
5793
 
@@ -5745,7 +5813,7 @@ var BatchedWebhookService = class {
5745
5813
  this.timeoutMs = config2.timeoutMs ?? DEFAULT_TIMEOUT_MS;
5746
5814
  this.onError = config2.onError;
5747
5815
  }
5748
- emit(event, actor) {
5816
+ async emit(event, actor) {
5749
5817
  const ownerId = event.owner_id;
5750
5818
  const endpoints = this.resolveEndpoint(ownerId);
5751
5819
  if (endpoints.length === 0) {
@@ -5756,6 +5824,7 @@ var BatchedWebhookService = class {
5756
5824
  return;
5757
5825
  }
5758
5826
  const envelope = this.buildEnvelope(event, actor);
5827
+ const flushPromises = [];
5759
5828
  for (const endpoint of endpoints) {
5760
5829
  const url = endpoint.url;
5761
5830
  let buffer = this.buffers.get(url);
@@ -5765,13 +5834,16 @@ var BatchedWebhookService = class {
5765
5834
  }
5766
5835
  buffer.envelopes.push(envelope);
5767
5836
  if (buffer.envelopes.length >= this.maxBatchSize) {
5768
- this.flush(url);
5837
+ flushPromises.push(this.flush(url));
5769
5838
  } else if (!buffer.timer) {
5770
5839
  buffer.timer = setTimeout(() => this.flush(url), this.flushIntervalMs);
5771
5840
  }
5772
5841
  }
5842
+ if (flushPromises.length > 0) {
5843
+ await Promise.all(flushPromises);
5844
+ }
5773
5845
  }
5774
- flush(url) {
5846
+ async flush(url) {
5775
5847
  const buffer = this.buffers.get(url);
5776
5848
  if (!buffer || buffer.envelopes.length === 0)
5777
5849
  return;
@@ -5782,19 +5854,23 @@ var BatchedWebhookService = class {
5782
5854
  }
5783
5855
  buffer.envelopes = [];
5784
5856
  buffer.timer = null;
5785
- this.sendBatch(url, endpoint, envelopes).catch((err2) => {
5857
+ try {
5858
+ await this.sendBatch(url, endpoint, envelopes);
5859
+ } catch (err2) {
5786
5860
  this.logger.error?.("[BatchedWebhookService] batch delivery failed", {
5787
5861
  error: err2,
5788
5862
  url,
5789
5863
  count: envelopes.length
5790
5864
  });
5791
5865
  this.onError?.(err2, envelopes);
5792
- });
5866
+ }
5793
5867
  }
5794
- dispose() {
5868
+ async dispose() {
5869
+ const promises = [];
5795
5870
  for (const url of this.buffers.keys()) {
5796
- this.flush(url);
5871
+ promises.push(this.flush(url));
5797
5872
  }
5873
+ await Promise.all(promises);
5798
5874
  }
5799
5875
  async sendBatch(url, endpoint, envelopes) {
5800
5876
  const batchId = v4_default();
package/dist/server.js CHANGED
@@ -3861,8 +3861,8 @@ var RelationshipService = class {
3861
3861
  }
3862
3862
  // ── Create ──────────────────────────────────────────────────────────
3863
3863
  async create(input) {
3864
- if (input.memory_ids.length < 2) {
3865
- throw new Error("At least 2 memory IDs are required to create a relationship");
3864
+ if (input.memory_ids.length < 1) {
3865
+ throw new Error("At least 1 memory ID is required to create a relationship");
3866
3866
  }
3867
3867
  const validated = await this.validateMemoryIds(input.memory_ids);
3868
3868
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -4673,24 +4673,76 @@ var SpaceService = class {
4673
4673
  const parentId = String(originalMemory.properties.parent_id ?? "");
4674
4674
  const threadRootId = String(originalMemory.properties.thread_root_id ?? parentId);
4675
4675
  const contentPreview = String(originalMemory.properties.content ?? "").slice(0, 200);
4676
- if (successfulPublications.some((p) => p.startsWith("spaces:"))) {
4677
- for (const spaceId of spaces) {
4678
- this.eventBus.emit({ type: "comment.published_to_space", memory_id: request.payload.memory_id, parent_id: parentId, thread_root_id: threadRootId, content_preview: contentPreview, space_id: spaceId, owner_id: this.userId }, actor);
4676
+ let parentOwnerId = "";
4677
+ try {
4678
+ const parentMemory = await fetchMemoryWithAllProperties(this.userCollection, parentId);
4679
+ if (parentMemory) {
4680
+ const parentUserId = String(parentMemory.properties.user_id ?? "");
4681
+ if (parentUserId && parentUserId !== this.userId) {
4682
+ parentOwnerId = parentUserId;
4683
+ } else if (parentUserId === this.userId) {
4684
+ parentOwnerId = this.userId;
4685
+ }
4686
+ }
4687
+ if (!parentOwnerId) {
4688
+ const publicCollection = await ensurePublicCollection(this.weaviateClient);
4689
+ const directHit = await fetchMemoryWithAllProperties(publicCollection, parentId);
4690
+ if (directHit) {
4691
+ parentOwnerId = String(directHit.properties.author_id ?? directHit.properties.user_id ?? "");
4692
+ }
4693
+ if (!parentOwnerId) {
4694
+ const filter = publicCollection.filter.byProperty("original_memory_id").equal(parentId);
4695
+ const result = await publicCollection.query.fetchObjects({ filters: filter, limit: 1 });
4696
+ if (result.objects.length > 0) {
4697
+ parentOwnerId = String(result.objects[0].properties.author_id ?? "");
4698
+ }
4699
+ }
4700
+ }
4701
+ if (!parentOwnerId) {
4702
+ for (const groupId of groups) {
4703
+ try {
4704
+ const groupCollectionName = getCollectionName(CollectionType.GROUPS, groupId);
4705
+ const groupCollection = this.weaviateClient.collections.get(groupCollectionName);
4706
+ const gDirect = await fetchMemoryWithAllProperties(groupCollection, parentId);
4707
+ if (gDirect) {
4708
+ parentOwnerId = String(gDirect.properties.author_id ?? gDirect.properties.user_id ?? "");
4709
+ break;
4710
+ }
4711
+ const gFilter = groupCollection.filter.byProperty("original_memory_id").equal(parentId);
4712
+ const gResult = await groupCollection.query.fetchObjects({ filters: gFilter, limit: 1 });
4713
+ if (gResult.objects.length > 0) {
4714
+ parentOwnerId = String(gResult.objects[0].properties.author_id ?? "");
4715
+ break;
4716
+ }
4717
+ } catch {
4718
+ }
4719
+ }
4679
4720
  }
4721
+ } catch (err2) {
4722
+ this.logger.warn("Failed to resolve parent owner for comment event", { parentId, err: err2 });
4680
4723
  }
4681
- const publishedGroups = groups.filter((g) => successfulPublications.some((p) => p === `group: ${g}`));
4682
- for (const groupId of publishedGroups) {
4683
- this.eventBus.emit({ type: "comment.published_to_group", memory_id: request.payload.memory_id, parent_id: parentId, thread_root_id: threadRootId, content_preview: contentPreview, group_id: groupId, owner_id: this.userId }, actor);
4724
+ if (!parentOwnerId) {
4725
+ this.logger.warn("Skipping comment webhook \u2014 could not resolve parent_owner_id", { parentId, memoryId: request.payload.memory_id });
4726
+ } else {
4727
+ if (successfulPublications.some((p) => p.startsWith("spaces:"))) {
4728
+ for (const spaceId of spaces) {
4729
+ await this.eventBus.emit({ type: "comment.published_to_space", memory_id: request.payload.memory_id, parent_id: parentId, thread_root_id: threadRootId, content_preview: contentPreview, space_id: spaceId, owner_id: this.userId, parent_owner_id: parentOwnerId }, actor);
4730
+ }
4731
+ }
4732
+ const publishedGroups = groups.filter((g) => successfulPublications.some((p) => p === `group: ${g}`));
4733
+ for (const groupId of publishedGroups) {
4734
+ await this.eventBus.emit({ type: "comment.published_to_group", memory_id: request.payload.memory_id, parent_id: parentId, thread_root_id: threadRootId, content_preview: contentPreview, group_id: groupId, owner_id: this.userId, parent_owner_id: parentOwnerId }, actor);
4735
+ }
4684
4736
  }
4685
4737
  } else {
4686
4738
  if (successfulPublications.some((p) => p.startsWith("spaces:"))) {
4687
4739
  for (const spaceId of spaces) {
4688
- this.eventBus.emit({ type: "memory.published_to_space", memory_id: request.payload.memory_id, title, space_id: spaceId, owner_id: this.userId }, actor);
4740
+ await this.eventBus.emit({ type: "memory.published_to_space", memory_id: request.payload.memory_id, title, space_id: spaceId, owner_id: this.userId }, actor);
4689
4741
  }
4690
4742
  }
4691
4743
  const publishedGroups = groups.filter((g) => successfulPublications.some((p) => p === `group: ${g}`));
4692
4744
  for (const groupId of publishedGroups) {
4693
- this.eventBus.emit({ type: "memory.published_to_group", memory_id: request.payload.memory_id, title, group_id: groupId, owner_id: this.userId }, actor);
4745
+ await this.eventBus.emit({ type: "memory.published_to_group", memory_id: request.payload.memory_id, title, group_id: groupId, owner_id: this.userId }, actor);
4694
4746
  }
4695
4747
  }
4696
4748
  }
@@ -4787,7 +4839,7 @@ var SpaceService = class {
4787
4839
  targets.push({ kind: "group", id: groupId });
4788
4840
  }
4789
4841
  if (targets.length > 0) {
4790
- this.eventBus.emit({ type: "memory.retracted", memory_id: request.payload.memory_id, owner_id: this.userId, targets }, { type: "user", id: this.userId });
4842
+ await this.eventBus.emit({ type: "memory.retracted", memory_id: request.payload.memory_id, owner_id: this.userId, targets }, { type: "user", id: this.userId });
4791
4843
  }
4792
4844
  }
4793
4845
  return {
@@ -5491,13 +5543,28 @@ var SpaceService = class {
5491
5543
  } catch {
5492
5544
  }
5493
5545
  }
5494
- if (!props) {
5495
- return { space_ids: [], group_ids: [] };
5546
+ if (props) {
5547
+ return {
5548
+ space_ids: Array.isArray(props.space_ids) ? props.space_ids : [],
5549
+ group_ids: Array.isArray(props.group_ids) ? props.group_ids : []
5550
+ };
5496
5551
  }
5497
- return {
5498
- space_ids: Array.isArray(props.space_ids) ? props.space_ids : [],
5499
- group_ids: Array.isArray(props.group_ids) ? props.group_ids : []
5500
- };
5552
+ try {
5553
+ const collectionName = await this.memoryIndex.lookup(memoryId);
5554
+ if (collectionName) {
5555
+ const collection = this.weaviateClient.collections.get(collectionName);
5556
+ const obj = await fetchMemoryWithAllProperties(collection, memoryId);
5557
+ if (obj) {
5558
+ const p = obj.properties;
5559
+ return {
5560
+ space_ids: Array.isArray(p.space_ids) ? p.space_ids : [],
5561
+ group_ids: Array.isArray(p.group_ids) ? p.group_ids : []
5562
+ };
5563
+ }
5564
+ }
5565
+ } catch {
5566
+ }
5567
+ return { space_ids: [], group_ids: [] };
5501
5568
  }
5502
5569
  };
5503
5570
 
@@ -5723,7 +5790,8 @@ var INITIAL_PERCEPTION = {
5723
5790
  import { createHmac } from "node:crypto";
5724
5791
  function signWebhookPayload(webhookId, timestamp, body, secret) {
5725
5792
  const content = `${webhookId}.${timestamp}.${body}`;
5726
- const hmac = createHmac("sha256", secret).update(content).digest("base64");
5793
+ const secretBytes = Buffer.from(secret, "base64");
5794
+ const hmac = createHmac("sha256", secretBytes).update(content).digest("base64");
5727
5795
  return `v1,${hmac}`;
5728
5796
  }
5729
5797
 
@@ -5749,7 +5817,7 @@ var BatchedWebhookService = class {
5749
5817
  this.timeoutMs = config3.timeoutMs ?? DEFAULT_TIMEOUT_MS;
5750
5818
  this.onError = config3.onError;
5751
5819
  }
5752
- emit(event, actor) {
5820
+ async emit(event, actor) {
5753
5821
  const ownerId = event.owner_id;
5754
5822
  const endpoints = this.resolveEndpoint(ownerId);
5755
5823
  if (endpoints.length === 0) {
@@ -5760,6 +5828,7 @@ var BatchedWebhookService = class {
5760
5828
  return;
5761
5829
  }
5762
5830
  const envelope = this.buildEnvelope(event, actor);
5831
+ const flushPromises = [];
5763
5832
  for (const endpoint of endpoints) {
5764
5833
  const url = endpoint.url;
5765
5834
  let buffer = this.buffers.get(url);
@@ -5769,13 +5838,16 @@ var BatchedWebhookService = class {
5769
5838
  }
5770
5839
  buffer.envelopes.push(envelope);
5771
5840
  if (buffer.envelopes.length >= this.maxBatchSize) {
5772
- this.flush(url);
5841
+ flushPromises.push(this.flush(url));
5773
5842
  } else if (!buffer.timer) {
5774
5843
  buffer.timer = setTimeout(() => this.flush(url), this.flushIntervalMs);
5775
5844
  }
5776
5845
  }
5846
+ if (flushPromises.length > 0) {
5847
+ await Promise.all(flushPromises);
5848
+ }
5777
5849
  }
5778
- flush(url) {
5850
+ async flush(url) {
5779
5851
  const buffer = this.buffers.get(url);
5780
5852
  if (!buffer || buffer.envelopes.length === 0)
5781
5853
  return;
@@ -5786,19 +5858,23 @@ var BatchedWebhookService = class {
5786
5858
  }
5787
5859
  buffer.envelopes = [];
5788
5860
  buffer.timer = null;
5789
- this.sendBatch(url, endpoint, envelopes).catch((err2) => {
5861
+ try {
5862
+ await this.sendBatch(url, endpoint, envelopes);
5863
+ } catch (err2) {
5790
5864
  this.logger.error?.("[BatchedWebhookService] batch delivery failed", {
5791
5865
  error: err2,
5792
5866
  url,
5793
5867
  count: envelopes.length
5794
5868
  });
5795
5869
  this.onError?.(err2, envelopes);
5796
- });
5870
+ }
5797
5871
  }
5798
- dispose() {
5872
+ async dispose() {
5873
+ const promises = [];
5799
5874
  for (const url of this.buffers.keys()) {
5800
- this.flush(url);
5875
+ promises.push(this.flush(url));
5801
5876
  }
5877
+ await Promise.all(promises);
5802
5878
  }
5803
5879
  async sendBatch(url, endpoint, envelopes) {
5804
5880
  const batchId = v4_default();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prmichaelsen/remember-mcp",
3
- "version": "3.17.2",
3
+ "version": "3.17.4",
4
4
  "description": "Multi-tenant memory system MCP server with vector search and relationships",
5
5
  "main": "dist/server.js",
6
6
  "type": "module",
@@ -35,7 +35,8 @@
35
35
  "test:all": "npm test && npm run test:e2e",
36
36
  "lint": "eslint src/**/*.ts",
37
37
  "typecheck": "tsc --noEmit",
38
- "prepublishOnly": "npm run clean && npm run build"
38
+ "prepublishOnly": "npm run clean && npm run build",
39
+ "update:core": "npm i @prmichaelsen/remember-core@latest && git add package* && git commit -m \"bump: remember-core\" && npm version patch && npm publish"
39
40
  },
40
41
  "keywords": [
41
42
  "mcp",
@@ -51,7 +52,7 @@
51
52
  "@google-cloud/vision": "^5.3.4",
52
53
  "@modelcontextprotocol/sdk": "^1.0.4",
53
54
  "@prmichaelsen/firebase-admin-sdk-v8": "^2.2.0",
54
- "@prmichaelsen/remember-core": "^0.55.0",
55
+ "@prmichaelsen/remember-core": "^0.56.3",
55
56
  "dotenv": "^16.4.5",
56
57
  "uuid": "^13.0.0",
57
58
  "weaviate-client": "^3.2.0"