@troykelly/openclaw-projects 0.0.16 → 0.0.20

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 (129) hide show
  1. package/dist/api-client.d.ts +1 -1
  2. package/dist/api-client.d.ts.map +1 -1
  3. package/dist/api-client.js +3 -3
  4. package/dist/api-client.js.map +1 -1
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +10 -10
  8. package/dist/cli.js.map +1 -1
  9. package/dist/config.d.ts +82 -7
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +56 -5
  12. package/dist/config.js.map +1 -1
  13. package/dist/context.d.ts +3 -3
  14. package/dist/context.d.ts.map +1 -1
  15. package/dist/context.js +3 -3
  16. package/dist/context.js.map +1 -1
  17. package/dist/gateway/oauth-rpc-methods.d.ts +44 -44
  18. package/dist/gateway/oauth-rpc-methods.d.ts.map +1 -1
  19. package/dist/gateway/oauth-rpc-methods.js +106 -106
  20. package/dist/gateway/oauth-rpc-methods.js.map +1 -1
  21. package/dist/gateway/rpc-methods.d.ts +3 -3
  22. package/dist/gateway/rpc-methods.d.ts.map +1 -1
  23. package/dist/gateway/rpc-methods.js +11 -11
  24. package/dist/gateway/rpc-methods.js.map +1 -1
  25. package/dist/hooks.d.ts +3 -3
  26. package/dist/hooks.d.ts.map +1 -1
  27. package/dist/hooks.js +42 -42
  28. package/dist/hooks.js.map +1 -1
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +19 -19
  32. package/dist/index.js.map +1 -1
  33. package/dist/register-openclaw.d.ts.map +1 -1
  34. package/dist/register-openclaw.js +741 -262
  35. package/dist/register-openclaw.js.map +1 -1
  36. package/dist/services/notification-service.d.ts +1 -1
  37. package/dist/services/notification-service.d.ts.map +1 -1
  38. package/dist/services/notification-service.js +11 -11
  39. package/dist/services/notification-service.js.map +1 -1
  40. package/dist/tools/contacts.d.ts +7 -7
  41. package/dist/tools/contacts.d.ts.map +1 -1
  42. package/dist/tools/contacts.js +30 -30
  43. package/dist/tools/contacts.js.map +1 -1
  44. package/dist/tools/context-search.d.ts +2 -2
  45. package/dist/tools/context-search.d.ts.map +1 -1
  46. package/dist/tools/context-search.js +14 -14
  47. package/dist/tools/context-search.js.map +1 -1
  48. package/dist/tools/email-send.d.ts +13 -13
  49. package/dist/tools/email-send.d.ts.map +1 -1
  50. package/dist/tools/email-send.js +22 -22
  51. package/dist/tools/email-send.js.map +1 -1
  52. package/dist/tools/entity-links.d.ts +1 -1
  53. package/dist/tools/entity-links.d.ts.map +1 -1
  54. package/dist/tools/entity-links.js +33 -33
  55. package/dist/tools/entity-links.js.map +1 -1
  56. package/dist/tools/file-share.d.ts +15 -15
  57. package/dist/tools/file-share.d.ts.map +1 -1
  58. package/dist/tools/file-share.js +30 -30
  59. package/dist/tools/file-share.js.map +1 -1
  60. package/dist/tools/memory-forget.d.ts +7 -7
  61. package/dist/tools/memory-forget.d.ts.map +1 -1
  62. package/dist/tools/memory-forget.js +34 -34
  63. package/dist/tools/memory-forget.js.map +1 -1
  64. package/dist/tools/memory-recall.d.ts +4 -4
  65. package/dist/tools/memory-recall.d.ts.map +1 -1
  66. package/dist/tools/memory-recall.js +9 -9
  67. package/dist/tools/memory-recall.js.map +1 -1
  68. package/dist/tools/memory-store.d.ts +3 -3
  69. package/dist/tools/memory-store.d.ts.map +1 -1
  70. package/dist/tools/memory-store.js +13 -13
  71. package/dist/tools/memory-store.js.map +1 -1
  72. package/dist/tools/message-search.d.ts +8 -8
  73. package/dist/tools/message-search.d.ts.map +1 -1
  74. package/dist/tools/message-search.js +19 -19
  75. package/dist/tools/message-search.js.map +1 -1
  76. package/dist/tools/notebooks.d.ts +20 -20
  77. package/dist/tools/notebooks.d.ts.map +1 -1
  78. package/dist/tools/notebooks.js +40 -40
  79. package/dist/tools/notebooks.js.map +1 -1
  80. package/dist/tools/notes.d.ts +35 -35
  81. package/dist/tools/notes.d.ts.map +1 -1
  82. package/dist/tools/notes.js +67 -67
  83. package/dist/tools/notes.js.map +1 -1
  84. package/dist/tools/project-search.d.ts +3 -3
  85. package/dist/tools/project-search.d.ts.map +1 -1
  86. package/dist/tools/project-search.js +15 -15
  87. package/dist/tools/project-search.js.map +1 -1
  88. package/dist/tools/projects.d.ts +6 -6
  89. package/dist/tools/projects.d.ts.map +1 -1
  90. package/dist/tools/projects.js +25 -25
  91. package/dist/tools/projects.js.map +1 -1
  92. package/dist/tools/relationships.d.ts +19 -19
  93. package/dist/tools/relationships.d.ts.map +1 -1
  94. package/dist/tools/relationships.js +38 -38
  95. package/dist/tools/relationships.js.map +1 -1
  96. package/dist/tools/skill-store.d.ts +11 -11
  97. package/dist/tools/skill-store.d.ts.map +1 -1
  98. package/dist/tools/skill-store.js +51 -51
  99. package/dist/tools/skill-store.js.map +1 -1
  100. package/dist/tools/sms-send.d.ts +7 -7
  101. package/dist/tools/sms-send.d.ts.map +1 -1
  102. package/dist/tools/sms-send.js +16 -16
  103. package/dist/tools/sms-send.js.map +1 -1
  104. package/dist/tools/threads.d.ts +18 -18
  105. package/dist/tools/threads.d.ts.map +1 -1
  106. package/dist/tools/threads.js +33 -33
  107. package/dist/tools/threads.js.map +1 -1
  108. package/dist/tools/todo-search.d.ts +3 -3
  109. package/dist/tools/todo-search.d.ts.map +1 -1
  110. package/dist/tools/todo-search.js +15 -15
  111. package/dist/tools/todo-search.js.map +1 -1
  112. package/dist/tools/todos.d.ts +13 -13
  113. package/dist/tools/todos.d.ts.map +1 -1
  114. package/dist/tools/todos.js +37 -37
  115. package/dist/tools/todos.js.map +1 -1
  116. package/dist/types/openclaw-api.d.ts +2 -2
  117. package/dist/types/openclaw-api.d.ts.map +1 -1
  118. package/dist/utils/auto-linker.d.ts +2 -2
  119. package/dist/utils/auto-linker.d.ts.map +1 -1
  120. package/dist/utils/auto-linker.js +40 -40
  121. package/dist/utils/auto-linker.js.map +1 -1
  122. package/dist/utils/injection-log-rate-limiter.d.ts +1 -1
  123. package/dist/utils/injection-log-rate-limiter.js +1 -1
  124. package/dist/utils/nominatim.d.ts +1 -1
  125. package/dist/utils/nominatim.d.ts.map +1 -1
  126. package/dist/utils/nominatim.js +1 -1
  127. package/dist/utils/nominatim.js.map +1 -1
  128. package/openclaw.plugin.json +40 -4
  129. package/package.json +2 -2
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { ZodError } from 'zod';
11
11
  import { createApiClient } from './api-client.js';
12
- import { redactConfig, resolveConfigSecretsSync, validateRawConfig } from './config.js';
12
+ import { redactConfig, resolveConfigSecretsSync, resolveNamespaceConfig, validateRawConfig } from './config.js';
13
13
  import { extractContext, getUserScopeKey } from './context.js';
14
14
  import { createOAuthGatewayMethods, registerOAuthGatewayRpcMethods } from './gateway/oauth-rpc-methods.js';
15
15
  import { createGatewayMethods, registerGatewayRpcMethods } from './gateway/rpc-methods.js';
@@ -40,6 +40,37 @@ function toAgentToolResult(result) {
40
40
  content: [{ type: 'text', text: `Error: ${errorText}` }],
41
41
  };
42
42
  }
43
+ /** Namespace property for store/create tools (Issue #1428) */
44
+ const namespaceProperty = {
45
+ type: 'string',
46
+ description: 'Target namespace for this operation. Defaults to the agent\'s configured namespace.',
47
+ pattern: '^[a-z0-9][a-z0-9._-]*$',
48
+ maxLength: 63,
49
+ };
50
+ /** Namespaces property for query/list tools (Issue #1428) */
51
+ const namespacesProperty = {
52
+ type: 'array',
53
+ description: 'Namespaces to search. Defaults to the agent\'s configured recall namespaces.',
54
+ items: {
55
+ type: 'string',
56
+ pattern: '^[a-z0-9][a-z0-9._-]*$',
57
+ maxLength: 63,
58
+ },
59
+ };
60
+ /** Add namespace param to a store/create tool schema (Issue #1428) */
61
+ function withNamespace(schema) {
62
+ return {
63
+ ...schema,
64
+ properties: { ...schema.properties, namespace: namespaceProperty },
65
+ };
66
+ }
67
+ /** Add namespaces param to a query/list tool schema (Issue #1428) */
68
+ function withNamespaces(schema) {
69
+ return {
70
+ ...schema,
71
+ properties: { ...schema.properties, namespaces: namespacesProperty },
72
+ };
73
+ }
43
74
  /**
44
75
  * Memory recall tool JSON Schema
45
76
  */
@@ -186,7 +217,7 @@ const memoryStoreSchema = {
186
217
  const memoryForgetSchema = {
187
218
  type: 'object',
188
219
  properties: {
189
- memoryId: {
220
+ memory_id: {
190
221
  type: 'string',
191
222
  description: 'ID of the memory to forget',
192
223
  format: 'uuid',
@@ -224,13 +255,13 @@ const projectListSchema = {
224
255
  const projectGetSchema = {
225
256
  type: 'object',
226
257
  properties: {
227
- projectId: {
258
+ project_id: {
228
259
  type: 'string',
229
260
  description: 'Project ID to retrieve',
230
261
  format: 'uuid',
231
262
  },
232
263
  },
233
- required: ['projectId'],
264
+ required: ['project_id'],
234
265
  };
235
266
  /**
236
267
  * Project create tool JSON Schema
@@ -264,7 +295,7 @@ const projectCreateSchema = {
264
295
  const todoListSchema = {
265
296
  type: 'object',
266
297
  properties: {
267
- projectId: {
298
+ project_id: {
268
299
  type: 'string',
269
300
  description: 'Filter by project ID',
270
301
  format: 'uuid',
@@ -305,7 +336,7 @@ const todoCreateSchema = {
305
336
  description: 'Todo description',
306
337
  maxLength: 5000,
307
338
  },
308
- projectId: {
339
+ project_id: {
309
340
  type: 'string',
310
341
  description: 'Project to add the todo to',
311
342
  format: 'uuid',
@@ -455,13 +486,13 @@ const contactSearchSchema = {
455
486
  const contactGetSchema = {
456
487
  type: 'object',
457
488
  properties: {
458
- contactId: {
489
+ contact_id: {
459
490
  type: 'string',
460
491
  description: 'Contact ID to retrieve',
461
492
  format: 'uuid',
462
493
  },
463
494
  },
464
- required: ['contactId'],
495
+ required: ['contact_id'],
465
496
  };
466
497
  /**
467
498
  * Contact create tool JSON Schema
@@ -509,7 +540,7 @@ const smsSendSchema = {
509
540
  minLength: 1,
510
541
  maxLength: 1600,
511
542
  },
512
- idempotencyKey: {
543
+ idempotency_key: {
513
544
  type: 'string',
514
545
  description: 'Optional key to prevent duplicate sends',
515
546
  },
@@ -538,15 +569,15 @@ const emailSendSchema = {
538
569
  description: 'Plain text email body',
539
570
  minLength: 1,
540
571
  },
541
- htmlBody: {
572
+ html_body: {
542
573
  type: 'string',
543
574
  description: 'Optional HTML email body',
544
575
  },
545
- threadId: {
576
+ thread_id: {
546
577
  type: 'string',
547
578
  description: 'Optional thread ID for replies',
548
579
  },
549
- idempotencyKey: {
580
+ idempotency_key: {
550
581
  type: 'string',
551
582
  description: 'Optional unique key to prevent duplicate sends',
552
583
  },
@@ -570,7 +601,7 @@ const messageSearchSchema = {
570
601
  enum: ['sms', 'email', 'all'],
571
602
  default: 'all',
572
603
  },
573
- contactId: {
604
+ contact_id: {
574
605
  type: 'string',
575
606
  description: 'Filter by contact ID',
576
607
  format: 'uuid',
@@ -582,7 +613,7 @@ const messageSearchSchema = {
582
613
  maximum: 100,
583
614
  default: 10,
584
615
  },
585
- includeThread: {
616
+ include_thread: {
586
617
  type: 'boolean',
587
618
  description: 'Include full thread context',
588
619
  default: false,
@@ -601,7 +632,7 @@ const threadListSchema = {
601
632
  description: 'Filter by channel type',
602
633
  enum: ['sms', 'email'],
603
634
  },
604
- contactId: {
635
+ contact_id: {
605
636
  type: 'string',
606
637
  description: 'Filter by contact ID',
607
638
  format: 'uuid',
@@ -621,11 +652,11 @@ const threadListSchema = {
621
652
  const threadGetSchema = {
622
653
  type: 'object',
623
654
  properties: {
624
- threadId: {
655
+ thread_id: {
625
656
  type: 'string',
626
657
  description: 'Thread ID to retrieve',
627
658
  },
628
- messageLimit: {
659
+ message_limit: {
629
660
  type: 'integer',
630
661
  description: 'Maximum messages to return',
631
662
  minimum: 1,
@@ -633,7 +664,7 @@ const threadGetSchema = {
633
664
  default: 50,
634
665
  },
635
666
  },
636
- required: ['threadId'],
667
+ required: ['thread_id'],
637
668
  };
638
669
  /**
639
670
  * Relationship set tool JSON Schema
@@ -693,25 +724,25 @@ const relationshipQuerySchema = {
693
724
  const fileShareSchema = {
694
725
  type: 'object',
695
726
  properties: {
696
- fileId: {
727
+ file_id: {
697
728
  type: 'string',
698
729
  description: 'The file ID to create a share link for',
699
730
  format: 'uuid',
700
731
  },
701
- expiresIn: {
732
+ expires_in: {
702
733
  type: 'integer',
703
734
  description: 'Link expiry time in seconds (default: 3600, max: 604800)',
704
735
  minimum: 60,
705
736
  maximum: 604800,
706
737
  default: 3600,
707
738
  },
708
- maxDownloads: {
739
+ max_downloads: {
709
740
  type: 'integer',
710
741
  description: 'Optional maximum number of downloads',
711
742
  minimum: 1,
712
743
  },
713
744
  },
714
- required: ['fileId'],
745
+ required: ['file_id'],
715
746
  };
716
747
  /**
717
748
  * Skill store put tool JSON Schema
@@ -1110,11 +1141,126 @@ const linksRemoveSchema = {
1110
1141
  },
1111
1142
  required: ['source_type', 'source_id', 'target_type', 'target_ref'],
1112
1143
  };
1144
+ // Prompt template tool schemas (Epic #1497, Issue #1499)
1145
+ const promptTemplateListSchema = {
1146
+ type: 'object',
1147
+ properties: {
1148
+ channel_type: { type: 'string', description: 'Filter by channel type: sms, email, ha_observation, general', enum: ['sms', 'email', 'ha_observation', 'general'] },
1149
+ limit: { type: 'number', description: 'Max results to return (default 20)', minimum: 1, maximum: 100 },
1150
+ offset: { type: 'number', description: 'Pagination offset (default 0)', minimum: 0 },
1151
+ },
1152
+ required: [],
1153
+ };
1154
+ const promptTemplateGetSchema = {
1155
+ type: 'object',
1156
+ properties: {
1157
+ id: { type: 'string', description: 'ID of the prompt template to retrieve' },
1158
+ },
1159
+ required: ['id'],
1160
+ };
1161
+ const promptTemplateCreateSchema = {
1162
+ type: 'object',
1163
+ properties: {
1164
+ label: { type: 'string', description: 'Human-readable name for the template', minLength: 1 },
1165
+ content: { type: 'string', description: 'The prompt text', minLength: 1 },
1166
+ channel_type: { type: 'string', description: 'Channel type: sms, email, ha_observation, general', enum: ['sms', 'email', 'ha_observation', 'general'] },
1167
+ is_default: { type: 'boolean', description: 'Whether this is the default template for its channel type' },
1168
+ },
1169
+ required: ['label', 'content', 'channel_type'],
1170
+ };
1171
+ const promptTemplateUpdateSchema = {
1172
+ type: 'object',
1173
+ properties: {
1174
+ id: { type: 'string', description: 'ID of the prompt template to update' },
1175
+ label: { type: 'string', description: 'New label' },
1176
+ content: { type: 'string', description: 'New prompt text' },
1177
+ channel_type: { type: 'string', description: 'New channel type', enum: ['sms', 'email', 'ha_observation', 'general'] },
1178
+ is_default: { type: 'boolean', description: 'Set as default for channel type' },
1179
+ },
1180
+ required: ['id'],
1181
+ };
1182
+ const promptTemplateDeleteSchema = {
1183
+ type: 'object',
1184
+ properties: {
1185
+ id: { type: 'string', description: 'ID of the prompt template to delete (soft-delete)' },
1186
+ },
1187
+ required: ['id'],
1188
+ };
1189
+ // ── Inbound Destination schemas (Issue #1500) ──────────────
1190
+ const inboundDestinationListSchema = {
1191
+ type: 'object',
1192
+ properties: {
1193
+ channel_type: { type: 'string', description: 'Filter by channel type (sms, email)' },
1194
+ search: { type: 'string', description: 'Search by address or display name' },
1195
+ limit: { type: 'number', description: 'Max results (default 50, max 100)' },
1196
+ offset: { type: 'number', description: 'Offset for pagination' },
1197
+ },
1198
+ };
1199
+ const inboundDestinationGetSchema = {
1200
+ type: 'object',
1201
+ properties: {
1202
+ id: { type: 'string', description: 'ID of the inbound destination' },
1203
+ },
1204
+ required: ['id'],
1205
+ };
1206
+ const inboundDestinationUpdateSchema = {
1207
+ type: 'object',
1208
+ properties: {
1209
+ id: { type: 'string', description: 'ID of the inbound destination to update' },
1210
+ display_name: { type: 'string', description: 'Human-readable display name' },
1211
+ agent_id: { type: 'string', description: 'Agent ID for routing (null to clear)' },
1212
+ prompt_template_id: { type: 'string', description: 'Prompt template ID for routing (null to clear)' },
1213
+ context_id: { type: 'string', description: 'Context ID for routing (null to clear)' },
1214
+ },
1215
+ required: ['id'],
1216
+ };
1217
+ // ── Channel Default schemas (Issue #1501) ──────────────────
1218
+ const channelDefaultListSchema = {
1219
+ type: 'object',
1220
+ properties: {},
1221
+ };
1222
+ const channelDefaultGetSchema = {
1223
+ type: 'object',
1224
+ properties: {
1225
+ channel_type: { type: 'string', description: 'Channel type: sms, email, or ha_observation' },
1226
+ },
1227
+ required: ['channel_type'],
1228
+ };
1229
+ const channelDefaultSetSchema = {
1230
+ type: 'object',
1231
+ properties: {
1232
+ channel_type: { type: 'string', description: 'Channel type: sms, email, or ha_observation' },
1233
+ agent_id: { type: 'string', description: 'Agent ID for this channel type' },
1234
+ prompt_template_id: { type: 'string', description: 'Prompt template ID (optional)' },
1235
+ context_id: { type: 'string', description: 'Context ID (optional)' },
1236
+ },
1237
+ required: ['channel_type', 'agent_id'],
1238
+ };
1113
1239
  /**
1114
1240
  * Create tool execution handlers
1115
1241
  */
1116
1242
  function createToolHandlers(state) {
1117
- const { config, logger, apiClient, userId } = state;
1243
+ const { config, logger, apiClient, user_id, resolvedNamespace } = state;
1244
+ /**
1245
+ * Get the effective namespace for a store/create operation.
1246
+ * Uses explicit tool param if provided, otherwise falls back to config default.
1247
+ */
1248
+ function getStoreNamespace(params) {
1249
+ const ns = params.namespace;
1250
+ if (typeof ns === 'string' && ns.length > 0)
1251
+ return ns;
1252
+ return resolvedNamespace.default;
1253
+ }
1254
+ /**
1255
+ * Get the effective namespaces for a query/list operation.
1256
+ * Uses explicit tool param if provided, otherwise falls back to config recall list.
1257
+ */
1258
+ function getRecallNamespaces(params) {
1259
+ const ns = params.namespaces;
1260
+ if (Array.isArray(ns) && ns.length > 0)
1261
+ return ns;
1262
+ return resolvedNamespace.recall;
1263
+ }
1118
1264
  return {
1119
1265
  async memory_recall(params) {
1120
1266
  const { query, limit = config.maxRecallMemories, category, tags, relationship_id, location, location_radius_km, location_weight, } = params;
@@ -1128,7 +1274,11 @@ function createToolHandlers(state) {
1128
1274
  queryParams.set('tags', tags.join(','));
1129
1275
  if (relationship_id)
1130
1276
  queryParams.set('relationship_id', relationship_id);
1131
- const response = await apiClient.get(`/api/memories/search?${queryParams}`, { userId });
1277
+ // Namespace scoping (Issue #1428)
1278
+ const ns = getRecallNamespaces(params);
1279
+ if (ns.length > 0)
1280
+ queryParams.set('namespaces', ns.join(','));
1281
+ const response = await apiClient.get(`/api/memories/search?${queryParams}`, { user_id });
1132
1282
  if (!response.success) {
1133
1283
  return { success: false, error: response.error.message };
1134
1284
  }
@@ -1167,7 +1317,7 @@ function createToolHandlers(state) {
1167
1317
  success: true,
1168
1318
  data: {
1169
1319
  content,
1170
- details: { count: memories.length, memories, userId },
1320
+ details: { count: memories.length, memories, user_id },
1171
1321
  },
1172
1322
  };
1173
1323
  }
@@ -1190,6 +1340,7 @@ function createToolHandlers(state) {
1190
1340
  content: memoryText,
1191
1341
  memory_type: category === 'entity' ? 'reference' : category === 'other' ? 'note' : category,
1192
1342
  importance,
1343
+ namespace: getStoreNamespace(params), // Issue #1428
1193
1344
  };
1194
1345
  if (tags && tags.length > 0)
1195
1346
  payload.tags = tags;
@@ -1203,8 +1354,8 @@ function createToolHandlers(state) {
1203
1354
  const geocoded = await reverseGeocode(location.lat, location.lng, config.nominatimUrl);
1204
1355
  if (geocoded) {
1205
1356
  payload.address = geocoded.address;
1206
- if (!location.place_label && geocoded.placeLabel) {
1207
- payload.place_label = geocoded.placeLabel;
1357
+ if (!location.place_label && geocoded.place_label) {
1358
+ payload.place_label = geocoded.place_label;
1208
1359
  }
1209
1360
  }
1210
1361
  }
@@ -1213,7 +1364,7 @@ function createToolHandlers(state) {
1213
1364
  if (location.place_label)
1214
1365
  payload.place_label = location.place_label;
1215
1366
  }
1216
- const response = await apiClient.post('/api/memories/unified', payload, { userId });
1367
+ const response = await apiClient.post('/api/memories/unified', payload, { user_id });
1217
1368
  if (!response.success) {
1218
1369
  return { success: false, error: response.error.message };
1219
1370
  }
@@ -1231,22 +1382,26 @@ function createToolHandlers(state) {
1231
1382
  }
1232
1383
  },
1233
1384
  async memory_forget(params) {
1234
- const { memoryId, query } = params;
1385
+ const { memory_id, query } = params;
1235
1386
  try {
1236
- if (memoryId) {
1237
- const response = await apiClient.delete(`/api/memories/${memoryId}`, { userId });
1387
+ if (memory_id) {
1388
+ const response = await apiClient.delete(`/api/memories/${memory_id}`, { user_id });
1238
1389
  if (!response.success) {
1239
1390
  return { success: false, error: response.error.message };
1240
1391
  }
1241
1392
  return {
1242
1393
  success: true,
1243
- data: { content: `Memory ${memoryId} forgotten successfully` },
1394
+ data: { content: `Memory ${memory_id} forgotten successfully` },
1244
1395
  };
1245
1396
  }
1246
1397
  if (query) {
1247
1398
  // Match OpenClaw gateway memory_forget behavior:
1248
1399
  // Search → single high-confidence match auto-deletes, multiple returns candidates.
1249
- const searchResponse = await apiClient.get(`/api/memories/search?q=${encodeURIComponent(query)}&limit=5`, { userId });
1400
+ const forgetQp = new URLSearchParams({ q: query, limit: '5' });
1401
+ const forgetNs = getRecallNamespaces(params);
1402
+ if (forgetNs.length > 0)
1403
+ forgetQp.set('namespaces', forgetNs.join(','));
1404
+ const searchResponse = await apiClient.get(`/api/memories/search?${forgetQp}`, { user_id });
1250
1405
  if (!searchResponse.success) {
1251
1406
  return { success: false, error: searchResponse.error.message };
1252
1407
  }
@@ -1259,7 +1414,7 @@ function createToolHandlers(state) {
1259
1414
  }
1260
1415
  // Single high-confidence match → auto-delete
1261
1416
  if (matches.length === 1 && (matches[0].similarity ?? 0) > 0.9) {
1262
- const delResponse = await apiClient.delete(`/api/memories/${matches[0].id}`, { userId });
1417
+ const delResponse = await apiClient.delete(`/api/memories/${matches[0].id}`, { user_id });
1263
1418
  if (!delResponse.success) {
1264
1419
  return { success: false, error: delResponse.error.message };
1265
1420
  }
@@ -1276,12 +1431,12 @@ function createToolHandlers(state) {
1276
1431
  return {
1277
1432
  success: true,
1278
1433
  data: {
1279
- content: `Found ${matches.length} candidates. Specify memoryId:\n${list}`,
1434
+ content: `Found ${matches.length} candidates. Specify memory_id:\n${list}`,
1280
1435
  details: { action: 'candidates', candidates: matches.map((m) => ({ id: m.id, content: m.content, similarity: m.similarity })) },
1281
1436
  },
1282
1437
  };
1283
1438
  }
1284
- return { success: false, error: 'Either memoryId or query is required' };
1439
+ return { success: false, error: 'Either memory_id or query is required' };
1285
1440
  }
1286
1441
  catch (error) {
1287
1442
  logger.error('memory_forget failed', { error });
@@ -1294,8 +1449,12 @@ function createToolHandlers(state) {
1294
1449
  const queryParams = new URLSearchParams({ item_type: 'project', limit: String(limit) });
1295
1450
  if (status !== 'all')
1296
1451
  queryParams.set('status', status);
1297
- queryParams.set('user_email', userId); // Issue #1172: scope by user
1298
- const response = await apiClient.get(`/api/work-items?${queryParams}`, { userId });
1452
+ queryParams.set('user_email', user_id); // Issue #1172: scope by user
1453
+ // Namespace scoping (Issue #1428)
1454
+ const projListNs = getRecallNamespaces(params);
1455
+ if (projListNs.length > 0)
1456
+ queryParams.set('namespaces', projListNs.join(','));
1457
+ const response = await apiClient.get(`/api/work-items?${queryParams}`, { user_id });
1299
1458
  if (!response.success) {
1300
1459
  return { success: false, error: response.error.message };
1301
1460
  }
@@ -1312,9 +1471,9 @@ function createToolHandlers(state) {
1312
1471
  }
1313
1472
  },
1314
1473
  async project_get(params) {
1315
- const { projectId } = params;
1474
+ const { project_id } = params;
1316
1475
  try {
1317
- const response = await apiClient.get(`/api/work-items/${projectId}?user_email=${encodeURIComponent(userId)}`, { userId });
1476
+ const response = await apiClient.get(`/api/work-items/${project_id}?user_email=${encodeURIComponent(user_id)}`, { user_id });
1318
1477
  if (!response.success) {
1319
1478
  return { success: false, error: response.error.message };
1320
1479
  }
@@ -1335,7 +1494,7 @@ function createToolHandlers(state) {
1335
1494
  async project_create(params) {
1336
1495
  const { name, description, status = 'active', } = params;
1337
1496
  try {
1338
- const response = await apiClient.post('/api/work-items', { title: name, description, item_type: 'project', status, user_email: userId }, { userId });
1497
+ const response = await apiClient.post('/api/work-items', { title: name, description, item_type: 'project', status, user_email: user_id, namespace: getStoreNamespace(params) }, { user_id });
1339
1498
  if (!response.success) {
1340
1499
  return { success: false, error: response.error.message };
1341
1500
  }
@@ -1353,20 +1512,24 @@ function createToolHandlers(state) {
1353
1512
  }
1354
1513
  },
1355
1514
  async todo_list(params) {
1356
- const { projectId, completed, limit = 50, offset = 0, } = params;
1515
+ const { project_id, completed, limit = 50, offset = 0, } = params;
1357
1516
  try {
1358
1517
  const queryParams = new URLSearchParams({
1359
1518
  item_type: 'task',
1360
1519
  limit: String(limit),
1361
1520
  offset: String(offset),
1362
- user_email: userId, // Issue #1172: scope by user
1521
+ user_email: user_id, // Issue #1172: scope by user
1363
1522
  });
1364
- if (projectId)
1365
- queryParams.set('parent_work_item_id', projectId);
1523
+ if (project_id)
1524
+ queryParams.set('parent_work_item_id', project_id);
1366
1525
  if (completed !== undefined) {
1367
1526
  queryParams.set('status', completed ? 'completed' : 'active');
1368
1527
  }
1369
- const response = await apiClient.get(`/api/work-items?${queryParams}`, { userId });
1528
+ // Namespace scoping (Issue #1428)
1529
+ const todoListNs = getRecallNamespaces(params);
1530
+ if (todoListNs.length > 0)
1531
+ queryParams.set('namespaces', todoListNs.join(','));
1532
+ const response = await apiClient.get(`/api/work-items?${queryParams}`, { user_id });
1370
1533
  if (!response.success) {
1371
1534
  return { success: false, error: response.error.message };
1372
1535
  }
@@ -1396,14 +1559,14 @@ function createToolHandlers(state) {
1396
1559
  }
1397
1560
  },
1398
1561
  async todo_create(params) {
1399
- const { title, description, projectId, priority = 'medium', dueDate, } = params;
1562
+ const { title, description, project_id, priority = 'medium', dueDate, } = params;
1400
1563
  try {
1401
- const body = { title, description, item_type: 'task', priority, user_email: userId };
1402
- if (projectId)
1403
- body.parent_work_item_id = projectId;
1564
+ const body = { title, description, item_type: 'task', priority, user_email: user_id, namespace: getStoreNamespace(params) };
1565
+ if (project_id)
1566
+ body.parent_work_item_id = project_id;
1404
1567
  if (dueDate)
1405
1568
  body.not_after = dueDate;
1406
- const response = await apiClient.post('/api/work-items', body, { userId });
1569
+ const response = await apiClient.post('/api/work-items', body, { user_id });
1407
1570
  if (!response.success) {
1408
1571
  return { success: false, error: response.error.message };
1409
1572
  }
@@ -1423,7 +1586,7 @@ function createToolHandlers(state) {
1423
1586
  async todo_complete(params) {
1424
1587
  const { todoId } = params;
1425
1588
  try {
1426
- const response = await apiClient.patch(`/api/work-items/${todoId}/status?user_email=${encodeURIComponent(userId)}`, { status: 'completed' }, { userId });
1589
+ const response = await apiClient.patch(`/api/work-items/${todoId}/status?user_email=${encodeURIComponent(user_id)}`, { status: 'completed' }, { user_id });
1427
1590
  if (!response.success) {
1428
1591
  return { success: false, error: response.error.message };
1429
1592
  }
@@ -1450,9 +1613,13 @@ function createToolHandlers(state) {
1450
1613
  types: 'work_item',
1451
1614
  limit: String(fetchLimit),
1452
1615
  semantic: 'true',
1453
- user_email: userId, // Issue #1216: scope results to current user
1616
+ user_email: user_id, // Issue #1216: scope results to current user
1454
1617
  });
1455
- const response = await apiClient.get(`/api/search?${queryParams}`, { userId });
1618
+ // Namespace scoping (Issue #1428)
1619
+ const todoSearchNs = getRecallNamespaces(params);
1620
+ if (todoSearchNs.length > 0)
1621
+ queryParams.set('namespaces', todoSearchNs.join(','));
1622
+ const response = await apiClient.get(`/api/search?${queryParams}`, { user_id });
1456
1623
  if (!response.success) {
1457
1624
  return { success: false, error: response.error.message };
1458
1625
  }
@@ -1470,7 +1637,7 @@ function createToolHandlers(state) {
1470
1637
  success: true,
1471
1638
  data: {
1472
1639
  content: 'No matching work items found.',
1473
- details: { count: 0, results: [], searchType: response.data.search_type },
1640
+ details: { count: 0, results: [], search_type: response.data.search_type },
1474
1641
  },
1475
1642
  };
1476
1643
  }
@@ -1496,7 +1663,7 @@ function createToolHandlers(state) {
1496
1663
  kind: r.metadata?.kind,
1497
1664
  status: r.metadata?.status,
1498
1665
  })),
1499
- searchType: response.data.search_type,
1666
+ search_type: response.data.search_type,
1500
1667
  },
1501
1668
  },
1502
1669
  };
@@ -1507,25 +1674,28 @@ function createToolHandlers(state) {
1507
1674
  }
1508
1675
  },
1509
1676
  async project_search(params) {
1510
- const tool = createProjectSearchTool({ client: apiClient, logger, config, userId });
1677
+ const tool = createProjectSearchTool({ client: apiClient, logger, config, user_id });
1511
1678
  return tool.execute(params);
1512
1679
  },
1513
1680
  async context_search(params) {
1514
- const tool = createContextSearchTool({ client: apiClient, logger, config, userId });
1681
+ const tool = createContextSearchTool({ client: apiClient, logger, config, user_id });
1515
1682
  return tool.execute(params);
1516
1683
  },
1517
1684
  async contact_search(params) {
1518
1685
  const { query, limit = 10 } = params;
1519
1686
  try {
1520
- const queryParams = new URLSearchParams({ search: query, limit: String(limit), user_email: userId });
1687
+ const queryParams = new URLSearchParams({ search: query, limit: String(limit), user_email: user_id });
1688
+ const contactSearchNs = getRecallNamespaces(params);
1689
+ if (contactSearchNs.length > 0)
1690
+ queryParams.set('namespaces', contactSearchNs.join(','));
1521
1691
  const response = await apiClient.get(`/api/contacts?${queryParams}`, {
1522
- userId,
1692
+ user_id,
1523
1693
  });
1524
1694
  if (!response.success) {
1525
1695
  return { success: false, error: response.error.message };
1526
1696
  }
1527
1697
  const contacts = response.data.contacts ?? [];
1528
- const content = contacts.length > 0 ? contacts.map((c) => `- ${c.displayName}${c.email ? ` (${c.email})` : ''}`).join('\n') : 'No contacts found.';
1698
+ const content = contacts.length > 0 ? contacts.map((c) => `- ${c.display_name}${c.email ? ` (${c.email})` : ''}`).join('\n') : 'No contacts found.';
1529
1699
  return {
1530
1700
  success: true,
1531
1701
  data: { content, details: { count: contacts.length, contacts } },
@@ -1537,9 +1707,9 @@ function createToolHandlers(state) {
1537
1707
  }
1538
1708
  },
1539
1709
  async contact_get(params) {
1540
- const { contactId } = params;
1710
+ const { contact_id } = params;
1541
1711
  try {
1542
- const response = await apiClient.get(`/api/contacts/${contactId}?user_email=${encodeURIComponent(userId)}`, { userId });
1712
+ const response = await apiClient.get(`/api/contacts/${contact_id}?user_email=${encodeURIComponent(user_id)}`, { user_id });
1543
1713
  if (!response.success) {
1544
1714
  return { success: false, error: response.error.message };
1545
1715
  }
@@ -1564,8 +1734,8 @@ function createToolHandlers(state) {
1564
1734
  async contact_create(params) {
1565
1735
  const { name, notes } = params;
1566
1736
  try {
1567
- // API requires displayName, not name. Email/phone are stored as separate contact_endpoint records.
1568
- const response = await apiClient.post('/api/contacts', { displayName: name, notes, user_email: userId }, { userId });
1737
+ // API requires display_name, not name. Email/phone are stored as separate contact_endpoint records.
1738
+ const response = await apiClient.post('/api/contacts', { display_name: name, notes, user_email: user_id, namespace: getStoreNamespace(params) }, { user_id });
1569
1739
  if (!response.success) {
1570
1740
  return { success: false, error: response.error.message };
1571
1741
  }
@@ -1583,7 +1753,7 @@ function createToolHandlers(state) {
1583
1753
  }
1584
1754
  },
1585
1755
  async sms_send(params) {
1586
- const { to, body, idempotencyKey } = params;
1756
+ const { to, body, idempotency_key } = params;
1587
1757
  // Check Twilio configuration
1588
1758
  if (!config.twilioAccountSid || !config.twilioAuthToken || !config.twilioPhoneNumber) {
1589
1759
  return {
@@ -1613,15 +1783,15 @@ function createToolHandlers(state) {
1613
1783
  };
1614
1784
  }
1615
1785
  logger.info('sms_send invoked', {
1616
- userId,
1786
+ user_id,
1617
1787
  bodyLength: body.length,
1618
- hasIdempotencyKey: !!idempotencyKey,
1788
+ hasIdempotencyKey: !!idempotency_key,
1619
1789
  });
1620
1790
  try {
1621
- const response = await apiClient.post('/api/twilio/sms/send', { to, body, idempotencyKey }, { userId });
1791
+ const response = await apiClient.post('/api/twilio/sms/send', { to, body, idempotency_key }, { user_id });
1622
1792
  if (!response.success) {
1623
1793
  logger.error('sms_send API error', {
1624
- userId,
1794
+ user_id,
1625
1795
  status: response.error.status,
1626
1796
  code: response.error.code,
1627
1797
  });
@@ -1630,23 +1800,23 @@ function createToolHandlers(state) {
1630
1800
  error: response.error.message || 'Failed to send SMS',
1631
1801
  };
1632
1802
  }
1633
- const { messageId, threadId, status } = response.data;
1803
+ const { message_id, thread_id, status } = response.data;
1634
1804
  logger.debug('sms_send completed', {
1635
- userId,
1636
- messageId,
1805
+ user_id,
1806
+ message_id,
1637
1807
  status,
1638
1808
  });
1639
1809
  return {
1640
1810
  success: true,
1641
1811
  data: {
1642
- content: `SMS sent successfully (ID: ${messageId}, Status: ${status})`,
1643
- details: { messageId, threadId, status, userId },
1812
+ content: `SMS sent successfully (ID: ${message_id}, Status: ${status})`,
1813
+ details: { message_id, thread_id, status, user_id },
1644
1814
  },
1645
1815
  };
1646
1816
  }
1647
1817
  catch (error) {
1648
1818
  logger.error('sms_send failed', {
1649
- userId,
1819
+ user_id,
1650
1820
  error: error instanceof Error ? error.message : String(error),
1651
1821
  });
1652
1822
  // Sanitize error message (remove phone numbers for privacy)
@@ -1661,7 +1831,7 @@ function createToolHandlers(state) {
1661
1831
  }
1662
1832
  },
1663
1833
  async email_send(params) {
1664
- const { to, subject, body, htmlBody, threadId, idempotencyKey } = params;
1834
+ const { to, subject, body, html_body, thread_id, idempotency_key } = params;
1665
1835
  // Validate email format
1666
1836
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1667
1837
  if (!emailRegex.test(to)) {
@@ -1691,18 +1861,18 @@ function createToolHandlers(state) {
1691
1861
  };
1692
1862
  }
1693
1863
  logger.info('email_send invoked', {
1694
- userId,
1864
+ user_id,
1695
1865
  subjectLength: subject.length,
1696
1866
  bodyLength: body.length,
1697
- hasHtmlBody: !!htmlBody,
1698
- hasThreadId: !!threadId,
1699
- hasIdempotencyKey: !!idempotencyKey,
1867
+ hasHtmlBody: !!html_body,
1868
+ hasThreadId: !!thread_id,
1869
+ hasIdempotencyKey: !!idempotency_key,
1700
1870
  });
1701
1871
  try {
1702
- const response = await apiClient.post('/api/postmark/email/send', { to, subject, body, htmlBody, threadId, idempotencyKey }, { userId });
1872
+ const response = await apiClient.post('/api/postmark/email/send', { to, subject, body, html_body, thread_id, idempotency_key }, { user_id });
1703
1873
  if (!response.success) {
1704
1874
  logger.error('email_send API error', {
1705
- userId,
1875
+ user_id,
1706
1876
  status: response.error.status,
1707
1877
  code: response.error.code,
1708
1878
  });
@@ -1711,23 +1881,23 @@ function createToolHandlers(state) {
1711
1881
  error: response.error.message || 'Failed to send email',
1712
1882
  };
1713
1883
  }
1714
- const { messageId, threadId: responseThreadId, status } = response.data;
1884
+ const { message_id, thread_id: responseThreadId, status } = response.data;
1715
1885
  logger.debug('email_send completed', {
1716
- userId,
1717
- messageId,
1886
+ user_id,
1887
+ message_id,
1718
1888
  status,
1719
1889
  });
1720
1890
  return {
1721
1891
  success: true,
1722
1892
  data: {
1723
- content: `Email sent successfully (ID: ${messageId}, Status: ${status})`,
1724
- details: { messageId, threadId: responseThreadId, status, userId },
1893
+ content: `Email sent successfully (ID: ${message_id}, Status: ${status})`,
1894
+ details: { message_id, thread_id: responseThreadId, status, user_id },
1725
1895
  },
1726
1896
  };
1727
1897
  }
1728
1898
  catch (error) {
1729
1899
  logger.error('email_send failed', {
1730
- userId,
1900
+ user_id,
1731
1901
  error: error instanceof Error ? error.message : String(error),
1732
1902
  });
1733
1903
  // Sanitize error message (remove email addresses for privacy)
@@ -1742,7 +1912,7 @@ function createToolHandlers(state) {
1742
1912
  }
1743
1913
  },
1744
1914
  async message_search(params) {
1745
- const { query, channel = 'all', contactId, limit = 10, includeThread = false, } = params;
1915
+ const { query, channel = 'all', contact_id, limit = 10, include_thread = false, } = params;
1746
1916
  // Validate query
1747
1917
  if (!query || query.length === 0) {
1748
1918
  return {
@@ -1751,12 +1921,12 @@ function createToolHandlers(state) {
1751
1921
  };
1752
1922
  }
1753
1923
  logger.info('message_search invoked', {
1754
- userId,
1924
+ user_id,
1755
1925
  queryLength: query.length,
1756
1926
  channel,
1757
- hasContactId: !!contactId,
1927
+ hasContactId: !!contact_id,
1758
1928
  limit,
1759
- includeThread,
1929
+ include_thread,
1760
1930
  });
1761
1931
  try {
1762
1932
  // Build query parameters
@@ -1767,17 +1937,17 @@ function createToolHandlers(state) {
1767
1937
  if (channel !== 'all') {
1768
1938
  queryParams.set('channel', channel);
1769
1939
  }
1770
- if (contactId) {
1771
- queryParams.set('contactId', contactId);
1940
+ if (contact_id) {
1941
+ queryParams.set('contact_id', contact_id);
1772
1942
  }
1773
- if (includeThread) {
1774
- queryParams.set('includeThread', 'true');
1943
+ if (include_thread) {
1944
+ queryParams.set('include_thread', 'true');
1775
1945
  }
1776
1946
  // Unified search API: returns { results: [{ type, id, title, snippet, score, metadata }], total }
1777
- const response = await apiClient.get(`/api/search?${queryParams}`, { userId });
1947
+ const response = await apiClient.get(`/api/search?${queryParams}`, { user_id });
1778
1948
  if (!response.success) {
1779
1949
  logger.error('message_search API error', {
1780
- userId,
1950
+ user_id,
1781
1951
  status: response.error.status,
1782
1952
  code: response.error.code,
1783
1953
  });
@@ -1793,12 +1963,12 @@ function createToolHandlers(state) {
1793
1963
  body: r.snippet,
1794
1964
  direction: r.metadata?.direction || 'inbound',
1795
1965
  channel: r.metadata?.channel || 'unknown',
1796
- contactName: r.metadata?.contactName,
1966
+ contact_name: r.metadata?.contact_name,
1797
1967
  timestamp: r.metadata?.received_at || '',
1798
1968
  similarity: r.score,
1799
1969
  }));
1800
1970
  logger.debug('message_search completed', {
1801
- userId,
1971
+ user_id,
1802
1972
  resultCount: messages.length,
1803
1973
  total,
1804
1974
  });
@@ -1813,11 +1983,11 @@ function createToolHandlers(state) {
1813
1983
  promptGuardUrl: config.promptGuardUrl,
1814
1984
  });
1815
1985
  if (detection.detected) {
1816
- const logDecision = injectionLogLimiter.shouldLog(userId);
1986
+ const logDecision = injectionLogLimiter.shouldLog(user_id);
1817
1987
  if (logDecision.log) {
1818
1988
  logger.warn(logDecision.summary ? 'injection detection log summary for previous window' : 'potential prompt injection detected in message_search result', {
1819
- userId,
1820
- messageId: m.id,
1989
+ user_id,
1990
+ message_id: m.id,
1821
1991
  patterns: detection.patterns,
1822
1992
  source: detection.source,
1823
1993
  ...(logDecision.suppressed > 0 && { suppressedCount: logDecision.suppressed }),
@@ -1834,18 +2004,18 @@ function createToolHandlers(state) {
1834
2004
  ? messages
1835
2005
  .map((m) => {
1836
2006
  const prefix = m.direction === 'inbound' ? '←' : '→';
1837
- const contact = sanitizeMetadataField(m.contactName || 'Unknown', nonce);
2007
+ const contact = sanitizeMetadataField(m.contact_name || 'Unknown', nonce);
1838
2008
  const safeChannel = sanitizeMetadataField(m.channel, nonce);
1839
2009
  const similarity = `(${Math.round(m.similarity * 100)}%)`;
1840
2010
  const rawBody = m.body || '';
1841
2011
  const truncatedBody = rawBody.substring(0, 100) + (rawBody.length > 100 ? '...' : '');
1842
- const bodyText = sanitizeMessageForContext(truncatedBody, {
2012
+ const body_text = sanitizeMessageForContext(truncatedBody, {
1843
2013
  direction: m.direction,
1844
2014
  channel: m.channel,
1845
- sender: m.contactName || 'Unknown',
2015
+ sender: m.contact_name || 'Unknown',
1846
2016
  nonce,
1847
2017
  });
1848
- return `${prefix} [${safeChannel}] ${contact} ${similarity}: ${bodyText}`;
2018
+ return `${prefix} [${safeChannel}] ${contact} ${similarity}: ${body_text}`;
1849
2019
  })
1850
2020
  .join('\n')
1851
2021
  : 'No messages found matching your query.';
@@ -1853,13 +2023,13 @@ function createToolHandlers(state) {
1853
2023
  success: true,
1854
2024
  data: {
1855
2025
  content,
1856
- details: { messages, total, userId },
2026
+ details: { messages, total, user_id },
1857
2027
  },
1858
2028
  };
1859
2029
  }
1860
2030
  catch (error) {
1861
2031
  logger.error('message_search failed', {
1862
- userId,
2032
+ user_id,
1863
2033
  error: error instanceof Error ? error.message : String(error),
1864
2034
  });
1865
2035
  return {
@@ -1869,11 +2039,11 @@ function createToolHandlers(state) {
1869
2039
  }
1870
2040
  },
1871
2041
  async thread_list(params) {
1872
- const { channel, contactId, limit = 20, } = params;
2042
+ const { channel, contact_id, limit = 20, } = params;
1873
2043
  logger.info('thread_list invoked', {
1874
- userId,
2044
+ user_id,
1875
2045
  channel,
1876
- hasContactId: !!contactId,
2046
+ hasContactId: !!contact_id,
1877
2047
  limit,
1878
2048
  });
1879
2049
  try {
@@ -1884,13 +2054,13 @@ function createToolHandlers(state) {
1884
2054
  if (channel) {
1885
2055
  queryParams.set('channel', channel);
1886
2056
  }
1887
- if (contactId) {
1888
- queryParams.set('contactId', contactId);
2057
+ if (contact_id) {
2058
+ queryParams.set('contact_id', contact_id);
1889
2059
  }
1890
- const response = await apiClient.get(`/api/search?${queryParams}`, { userId });
2060
+ const response = await apiClient.get(`/api/search?${queryParams}`, { user_id });
1891
2061
  if (!response.success) {
1892
2062
  logger.error('thread_list API error', {
1893
- userId,
2063
+ user_id,
1894
2064
  status: response.error.status,
1895
2065
  code: response.error.code,
1896
2066
  });
@@ -1903,7 +2073,7 @@ function createToolHandlers(state) {
1903
2073
  const results = response.data.results ?? response.data.threads ?? [];
1904
2074
  const total = response.data.total ?? results.length;
1905
2075
  logger.debug('thread_list completed', {
1906
- userId,
2076
+ user_id,
1907
2077
  threadCount: results.length,
1908
2078
  total,
1909
2079
  });
@@ -1917,9 +2087,9 @@ function createToolHandlers(state) {
1917
2087
  // Handle both thread and search result formats
1918
2088
  if ('channel' in r) {
1919
2089
  const t = r;
1920
- const safeContact = sanitizeMetadataField(t.contactName || t.endpointValue || 'Unknown', threadListNonce);
2090
+ const safeContact = sanitizeMetadataField(t.contact_name || t.endpoint_value || 'Unknown', threadListNonce);
1921
2091
  const safeChannel = sanitizeMetadataField(t.channel, threadListNonce);
1922
- const msgCount = t.messageCount ? `${t.messageCount} message${t.messageCount !== 1 ? 's' : ''}` : '';
2092
+ const msgCount = t.message_count ? `${t.message_count} message${t.message_count !== 1 ? 's' : ''}` : '';
1923
2093
  return `[${safeChannel}] ${safeContact}${msgCount ? ` - ${msgCount}` : ''}`;
1924
2094
  }
1925
2095
  const safeTitle = r.title ? sanitizeMetadataField(r.title, threadListNonce) : '';
@@ -1932,13 +2102,13 @@ function createToolHandlers(state) {
1932
2102
  success: true,
1933
2103
  data: {
1934
2104
  content,
1935
- details: { threads: results, total, userId },
2105
+ details: { threads: results, total, user_id },
1936
2106
  },
1937
2107
  };
1938
2108
  }
1939
2109
  catch (error) {
1940
2110
  logger.error('thread_list failed', {
1941
- userId,
2111
+ user_id,
1942
2112
  error: error instanceof Error ? error.message : String(error),
1943
2113
  });
1944
2114
  return {
@@ -1948,27 +2118,27 @@ function createToolHandlers(state) {
1948
2118
  }
1949
2119
  },
1950
2120
  async thread_get(params) {
1951
- const { threadId, messageLimit = 50 } = params;
1952
- // Validate threadId
1953
- if (!threadId || threadId.length === 0) {
2121
+ const { thread_id, message_limit = 50 } = params;
2122
+ // Validate thread_id
2123
+ if (!thread_id || thread_id.length === 0) {
1954
2124
  return {
1955
2125
  success: false,
1956
- error: 'threadId: Thread ID is required',
2126
+ error: 'thread_id: Thread ID is required',
1957
2127
  };
1958
2128
  }
1959
2129
  logger.info('thread_get invoked', {
1960
- userId,
1961
- threadId,
1962
- messageLimit,
2130
+ user_id,
2131
+ thread_id,
2132
+ message_limit,
1963
2133
  });
1964
2134
  try {
1965
2135
  const queryParams = new URLSearchParams();
1966
- queryParams.set('limit', String(messageLimit));
1967
- const response = await apiClient.get(`/api/threads/${threadId}/history?${queryParams}`, { userId });
2136
+ queryParams.set('limit', String(message_limit));
2137
+ const response = await apiClient.get(`/api/threads/${thread_id}/history?${queryParams}`, { user_id });
1968
2138
  if (!response.success) {
1969
2139
  logger.error('thread_get API error', {
1970
- userId,
1971
- threadId,
2140
+ user_id,
2141
+ thread_id,
1972
2142
  status: response.error.status,
1973
2143
  code: response.error.code,
1974
2144
  });
@@ -1979,13 +2149,13 @@ function createToolHandlers(state) {
1979
2149
  }
1980
2150
  const { thread, messages } = response.data;
1981
2151
  logger.debug('thread_get completed', {
1982
- userId,
1983
- threadId,
1984
- messageCount: messages.length,
2152
+ user_id,
2153
+ thread_id,
2154
+ message_count: messages.length,
1985
2155
  });
1986
2156
  // Generate a per-invocation nonce for boundary markers (#1255)
1987
2157
  const { nonce: threadGetNonce } = createBoundaryMarkers();
1988
- const contact = sanitizeMetadataField(thread.contactName || thread.endpointValue || 'Unknown', threadGetNonce);
2158
+ const contact = sanitizeMetadataField(thread.contact_name || thread.endpoint_value || 'Unknown', threadGetNonce);
1989
2159
  const safeChannel = sanitizeMetadataField(thread.channel, threadGetNonce);
1990
2160
  const header = `Thread with ${contact} [${safeChannel}]`;
1991
2161
  // Detect and log potential injection patterns in inbound messages
@@ -1996,12 +2166,12 @@ function createToolHandlers(state) {
1996
2166
  promptGuardUrl: config.promptGuardUrl,
1997
2167
  });
1998
2168
  if (detection.detected) {
1999
- const logDecision = injectionLogLimiter.shouldLog(userId);
2169
+ const logDecision = injectionLogLimiter.shouldLog(user_id);
2000
2170
  if (logDecision.log) {
2001
2171
  logger.warn(logDecision.summary ? 'injection detection log summary for previous window' : 'potential prompt injection detected in thread_get result', {
2002
- userId,
2003
- threadId,
2004
- messageId: m.id,
2172
+ user_id,
2173
+ thread_id,
2174
+ message_id: m.id,
2005
2175
  patterns: detection.patterns,
2006
2176
  source: detection.source,
2007
2177
  ...(logDecision.suppressed > 0 && { suppressedCount: logDecision.suppressed }),
@@ -2014,7 +2184,7 @@ function createToolHandlers(state) {
2014
2184
  ? messages
2015
2185
  .map((m) => {
2016
2186
  const prefix = m.direction === 'inbound' ? '←' : '→';
2017
- const timestamp = new Date(m.createdAt).toLocaleString();
2187
+ const timestamp = new Date(m.created_at).toLocaleString();
2018
2188
  const body = sanitizeMessageForContext(m.body || '', {
2019
2189
  direction: m.direction,
2020
2190
  channel: thread.channel,
@@ -2030,14 +2200,14 @@ function createToolHandlers(state) {
2030
2200
  success: true,
2031
2201
  data: {
2032
2202
  content,
2033
- details: { thread, messages, userId },
2203
+ details: { thread, messages, user_id },
2034
2204
  },
2035
2205
  };
2036
2206
  }
2037
2207
  catch (error) {
2038
2208
  logger.error('thread_get failed', {
2039
- userId,
2040
- threadId,
2209
+ user_id,
2210
+ thread_id,
2041
2211
  error: error instanceof Error ? error.message : String(error),
2042
2212
  });
2043
2213
  return {
@@ -2055,7 +2225,7 @@ function createToolHandlers(state) {
2055
2225
  };
2056
2226
  }
2057
2227
  logger.info('relationship_set invoked', {
2058
- userId,
2228
+ user_id,
2059
2229
  contactALength: contact_a.length,
2060
2230
  contactBLength: contact_b.length,
2061
2231
  relationshipLength: relationship.length,
@@ -2066,30 +2236,31 @@ function createToolHandlers(state) {
2066
2236
  contact_a,
2067
2237
  contact_b,
2068
2238
  relationship_type: relationship,
2069
- user_email: userId, // Issue #1172: scope by user
2239
+ user_email: user_id, // Issue #1172: scope by user
2240
+ namespace: getStoreNamespace(params), // Issue #1428
2070
2241
  };
2071
2242
  if (notes) {
2072
2243
  body.notes = notes;
2073
2244
  }
2074
- const response = await apiClient.post('/api/relationships/set', body, { userId });
2245
+ const response = await apiClient.post('/api/relationships/set', body, { user_id });
2075
2246
  if (!response.success) {
2076
2247
  return { success: false, error: response.error.message };
2077
2248
  }
2078
- const { relationship: rel, contactA, contactB, relationshipType, created } = response.data;
2249
+ const { relationship: rel, contact_a: respA, contact_b: respB, relationship_type, created } = response.data;
2079
2250
  const content = created
2080
- ? `Recorded: ${contactA.displayName} [${relationshipType.label}] ${contactB.displayName}`
2081
- : `Relationship already exists: ${contactA.displayName} [${relationshipType.label}] ${contactB.displayName}`;
2251
+ ? `Recorded: ${respA.display_name} [${relationship_type.label}] ${respB.display_name}`
2252
+ : `Relationship already exists: ${respA.display_name} [${relationship_type.label}] ${respB.display_name}`;
2082
2253
  return {
2083
2254
  success: true,
2084
2255
  data: {
2085
2256
  content,
2086
2257
  details: {
2087
- relationshipId: rel.id,
2258
+ relationship_id: rel.id,
2088
2259
  created,
2089
- contactA,
2090
- contactB,
2091
- relationshipType,
2092
- userId,
2260
+ contact_a: respA,
2261
+ contact_b: respB,
2262
+ relationship_type,
2263
+ user_id,
2093
2264
  },
2094
2265
  },
2095
2266
  };
@@ -2108,21 +2279,21 @@ function createToolHandlers(state) {
2108
2279
  };
2109
2280
  }
2110
2281
  logger.info('relationship_query invoked', {
2111
- userId,
2282
+ user_id,
2112
2283
  contactLength: contact.length,
2113
2284
  hasTypeFilter: !!type_filter,
2114
2285
  });
2115
2286
  try {
2116
2287
  // Resolve contact to a UUID — accept UUID directly or search by name
2117
2288
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2118
- let contactId;
2289
+ let contact_id;
2119
2290
  if (uuidRegex.test(contact)) {
2120
- contactId = contact;
2291
+ contact_id = contact;
2121
2292
  }
2122
2293
  else {
2123
2294
  // Search for contact by name (Issue #1172: scope by user_email)
2124
- const searchParams = new URLSearchParams({ search: contact, limit: '1', user_email: userId });
2125
- const searchResponse = await apiClient.get(`/api/contacts?${searchParams}`, { userId });
2295
+ const searchParams = new URLSearchParams({ search: contact, limit: '1', user_email: user_id });
2296
+ const searchResponse = await apiClient.get(`/api/contacts?${searchParams}`, { user_id });
2126
2297
  if (!searchResponse.success) {
2127
2298
  return { success: false, error: searchResponse.error.message };
2128
2299
  }
@@ -2130,43 +2301,43 @@ function createToolHandlers(state) {
2130
2301
  if (contacts.length === 0) {
2131
2302
  return { success: false, error: 'Contact not found.' };
2132
2303
  }
2133
- contactId = contacts[0].id;
2304
+ contact_id = contacts[0].id;
2134
2305
  }
2135
- // Use graph traversal endpoint which returns relatedContacts
2136
- const response = await apiClient.get(`/api/contacts/${contactId}/relationships?user_email=${encodeURIComponent(userId)}`, { userId });
2306
+ // Use graph traversal endpoint which returns related_contacts
2307
+ const response = await apiClient.get(`/api/contacts/${contact_id}/relationships?user_email=${encodeURIComponent(user_id)}`, { user_id });
2137
2308
  if (!response.success) {
2138
2309
  if (response.error.code === 'NOT_FOUND') {
2139
2310
  return { success: false, error: 'Contact not found.' };
2140
2311
  }
2141
2312
  return { success: false, error: response.error.message };
2142
2313
  }
2143
- let { relatedContacts } = response.data;
2144
- const { contactName } = response.data;
2314
+ let { related_contacts } = response.data;
2315
+ const { contact_name } = response.data;
2145
2316
  // Apply type_filter client-side if provided
2146
- if (type_filter && relatedContacts.length > 0) {
2317
+ if (type_filter && related_contacts.length > 0) {
2147
2318
  const filterLower = type_filter.toLowerCase();
2148
- relatedContacts = relatedContacts.filter((rel) => rel.relationshipTypeName.toLowerCase().includes(filterLower) || rel.relationshipTypeLabel.toLowerCase().includes(filterLower));
2319
+ related_contacts = related_contacts.filter((rel) => rel.relationship_type_name.toLowerCase().includes(filterLower) || rel.relationship_type_label.toLowerCase().includes(filterLower));
2149
2320
  }
2150
- if (relatedContacts.length === 0) {
2321
+ if (related_contacts.length === 0) {
2151
2322
  return {
2152
2323
  success: true,
2153
2324
  data: {
2154
- content: `No relationships found for ${contactName}.`,
2155
- details: { contactId, contactName, relatedContacts: [], userId },
2325
+ content: `No relationships found for ${contact_name}.`,
2326
+ details: { contact_id, contact_name, related_contacts: [], user_id },
2156
2327
  },
2157
2328
  };
2158
2329
  }
2159
- const lines = [`Relationships for ${contactName}:`];
2160
- for (const rel of relatedContacts) {
2161
- const kindTag = rel.contactKind !== 'person' ? ` [${rel.contactKind}]` : '';
2330
+ const lines = [`Relationships for ${contact_name}:`];
2331
+ for (const rel of related_contacts) {
2332
+ const kindTag = rel.contact_kind !== 'person' ? ` [${rel.contact_kind}]` : '';
2162
2333
  const notesTag = rel.notes ? ` -- ${rel.notes}` : '';
2163
- lines.push(`- ${rel.relationshipTypeLabel}: ${rel.contactName}${kindTag}${notesTag}`);
2334
+ lines.push(`- ${rel.relationship_type_label}: ${rel.contact_name}${kindTag}${notesTag}`);
2164
2335
  }
2165
2336
  return {
2166
2337
  success: true,
2167
2338
  data: {
2168
2339
  content: lines.join('\n'),
2169
- details: { contactId, contactName, relatedContacts, userId },
2340
+ details: { contact_id, contact_name, related_contacts, user_id },
2170
2341
  },
2171
2342
  };
2172
2343
  }
@@ -2176,36 +2347,36 @@ function createToolHandlers(state) {
2176
2347
  }
2177
2348
  },
2178
2349
  async file_share(params) {
2179
- const { fileId, expiresIn = 3600, maxDownloads, } = params;
2350
+ const { file_id: fileId, expires_in: expiresIn = 3600, max_downloads: maxDownloads, } = params;
2180
2351
  if (!fileId) {
2181
2352
  return {
2182
2353
  success: false,
2183
- error: 'fileId is required',
2354
+ error: 'file_id is required',
2184
2355
  };
2185
2356
  }
2186
- // Validate expiresIn range
2357
+ // Validate expires_in range
2187
2358
  if (expiresIn < 60 || expiresIn > 604800) {
2188
2359
  return {
2189
2360
  success: false,
2190
- error: 'expiresIn must be between 60 and 604800 seconds (1 minute to 7 days)',
2361
+ error: 'expires_in must be between 60 and 604800 seconds (1 minute to 7 days)',
2191
2362
  };
2192
2363
  }
2193
2364
  logger.info('file_share invoked', {
2194
- userId,
2195
- fileId,
2196
- expiresIn,
2197
- maxDownloads,
2365
+ user_id,
2366
+ file_id: fileId,
2367
+ expires_in: expiresIn,
2368
+ max_downloads: maxDownloads,
2198
2369
  });
2199
2370
  try {
2200
- const body = { expiresIn };
2371
+ const body = { expires_in: expiresIn };
2201
2372
  if (maxDownloads !== undefined) {
2202
- body.maxDownloads = maxDownloads;
2373
+ body.max_downloads = maxDownloads;
2203
2374
  }
2204
- const response = await apiClient.post(`/api/files/${fileId}/share`, body, { userId });
2375
+ const response = await apiClient.post(`/api/files/${fileId}/share`, body, { user_id });
2205
2376
  if (!response.success) {
2206
2377
  logger.error('file_share API error', {
2207
- userId,
2208
- fileId,
2378
+ user_id,
2379
+ file_id: fileId,
2209
2380
  status: response.error.status,
2210
2381
  code: response.error.code,
2211
2382
  });
@@ -2214,12 +2385,12 @@ function createToolHandlers(state) {
2214
2385
  error: response.error.message || 'Failed to create share link',
2215
2386
  };
2216
2387
  }
2217
- const { url, shareToken, expiresAt, filename, contentType, sizeBytes } = response.data;
2388
+ const { url, share_token, expires_at, filename, content_type, size_bytes } = response.data;
2218
2389
  logger.debug('file_share completed', {
2219
- userId,
2220
- fileId,
2221
- shareToken,
2222
- expiresAt,
2390
+ user_id,
2391
+ file_id: fileId,
2392
+ share_token,
2393
+ expires_at,
2223
2394
  });
2224
2395
  // Format file size
2225
2396
  const formatSize = (bytes) => {
@@ -2242,7 +2413,7 @@ function createToolHandlers(state) {
2242
2413
  return `${Math.floor(seconds / 86400)} days`;
2243
2414
  };
2244
2415
  const expiryText = formatDuration(expiresIn);
2245
- const sizeText = formatSize(sizeBytes);
2416
+ const sizeText = formatSize(size_bytes);
2246
2417
  const downloadLimit = maxDownloads ? ` (max ${maxDownloads} downloads)` : '';
2247
2418
  return {
2248
2419
  success: true,
@@ -2250,21 +2421,21 @@ function createToolHandlers(state) {
2250
2421
  content: `Share link created for "${filename}" (${sizeText}). ` + `Valid for ${expiryText}${downloadLimit}.\n\nURL: ${url}`,
2251
2422
  details: {
2252
2423
  url,
2253
- shareToken,
2254
- expiresAt,
2255
- expiresIn,
2424
+ share_token,
2425
+ expires_at,
2426
+ expires_in: expiresIn,
2256
2427
  filename,
2257
- contentType,
2258
- sizeBytes,
2259
- userId,
2428
+ content_type,
2429
+ size_bytes,
2430
+ user_id,
2260
2431
  },
2261
2432
  },
2262
2433
  };
2263
2434
  }
2264
2435
  catch (error) {
2265
2436
  logger.error('file_share failed', {
2266
- userId,
2267
- fileId,
2437
+ user_id,
2438
+ file_id: fileId,
2268
2439
  error: error instanceof Error ? error.message : String(error),
2269
2440
  });
2270
2441
  return {
@@ -2276,7 +2447,7 @@ function createToolHandlers(state) {
2276
2447
  // Skill store tools: delegate to tool modules for Zod validation,
2277
2448
  // credential detection, text sanitization, and error sanitization (Issue #824)
2278
2449
  ...(() => {
2279
- const toolOptions = { client: apiClient, logger, config, userId };
2450
+ const toolOptions = { client: apiClient, logger, config, user_id };
2280
2451
  const putTool = createSkillStorePutTool(toolOptions);
2281
2452
  const getTool = createSkillStoreGetTool(toolOptions);
2282
2453
  const listTool = createSkillStoreListTool(toolOptions);
@@ -2296,7 +2467,7 @@ function createToolHandlers(state) {
2296
2467
  })(),
2297
2468
  // Entity link tools: delegate to tool modules (Issue #1220)
2298
2469
  ...(() => {
2299
- const toolOptions = { client: apiClient, logger, config, userId };
2470
+ const toolOptions = { client: apiClient, logger, config, user_id };
2300
2471
  const setTool = createLinksSetTool(toolOptions);
2301
2472
  const queryTool = createLinksQueryTool(toolOptions);
2302
2473
  const removeTool = createLinksRemoveTool(toolOptions);
@@ -2306,6 +2477,203 @@ function createToolHandlers(state) {
2306
2477
  links_remove: (params) => removeTool.execute(params),
2307
2478
  };
2308
2479
  })(),
2480
+ // Prompt template tools (Epic #1497, Issue #1499)
2481
+ async prompt_template_list(params) {
2482
+ const { channel_type, limit = 20, offset = 0 } = params;
2483
+ try {
2484
+ const queryParams = new URLSearchParams({ limit: String(limit), offset: String(offset) });
2485
+ if (channel_type)
2486
+ queryParams.set('channel_type', channel_type);
2487
+ const response = await apiClient.get(`/api/prompt-templates?${queryParams.toString()}`, { user_id });
2488
+ if (!response.success) {
2489
+ return { success: false, error: response.error.message || 'Failed to list prompt templates' };
2490
+ }
2491
+ const items = response.data.items ?? [];
2492
+ const content = items.length === 0
2493
+ ? 'No prompt templates found.'
2494
+ : items.map((t) => `- **${t.label}** [${t.channel_type}]${t.is_default ? ' (default)' : ''}`).join('\n');
2495
+ return { success: true, data: { content, details: { items, total: response.data.total ?? items.length } } };
2496
+ }
2497
+ catch (error) {
2498
+ logger.error('prompt_template_list failed', { error });
2499
+ return { success: false, error: 'Failed to list prompt templates' };
2500
+ }
2501
+ },
2502
+ async prompt_template_get(params) {
2503
+ const { id } = params;
2504
+ try {
2505
+ const response = await apiClient.get(`/api/prompt-templates/${id}`, { user_id });
2506
+ if (!response.success) {
2507
+ return { success: false, error: response.error.message || 'Prompt template not found' };
2508
+ }
2509
+ const t = response.data;
2510
+ return { success: true, data: { content: `**${t.label}** [${t.channel_type}]${t.is_default ? ' (default)' : ''}\n\n${t.content}`, details: t } };
2511
+ }
2512
+ catch (error) {
2513
+ logger.error('prompt_template_get failed', { error });
2514
+ return { success: false, error: 'Failed to get prompt template' };
2515
+ }
2516
+ },
2517
+ async prompt_template_create(params) {
2518
+ const { label, content, channel_type, is_default } = params;
2519
+ try {
2520
+ const response = await apiClient.post('/api/prompt-templates', { label, content, channel_type, is_default }, { user_id });
2521
+ if (!response.success) {
2522
+ return { success: false, error: response.error.message || 'Failed to create prompt template' };
2523
+ }
2524
+ return { success: true, data: { content: `Created prompt template "${response.data.label}" (${response.data.id})`, details: response.data } };
2525
+ }
2526
+ catch (error) {
2527
+ logger.error('prompt_template_create failed', { error });
2528
+ return { success: false, error: 'Failed to create prompt template' };
2529
+ }
2530
+ },
2531
+ async prompt_template_update(params) {
2532
+ const { id, ...updates } = params;
2533
+ try {
2534
+ const response = await apiClient.put(`/api/prompt-templates/${id}`, updates, { user_id });
2535
+ if (!response.success) {
2536
+ return { success: false, error: response.error.message || 'Failed to update prompt template' };
2537
+ }
2538
+ return { success: true, data: { content: `Updated prompt template "${response.data.label}"`, details: response.data } };
2539
+ }
2540
+ catch (error) {
2541
+ logger.error('prompt_template_update failed', { error });
2542
+ return { success: false, error: 'Failed to update prompt template' };
2543
+ }
2544
+ },
2545
+ async prompt_template_delete(params) {
2546
+ const { id } = params;
2547
+ try {
2548
+ const response = await apiClient.delete(`/api/prompt-templates/${id}`, { user_id });
2549
+ if (!response.success) {
2550
+ return { success: false, error: response.error.message || 'Failed to delete prompt template' };
2551
+ }
2552
+ return { success: true, data: { content: `Deleted prompt template ${id}`, details: { id } } };
2553
+ }
2554
+ catch (error) {
2555
+ logger.error('prompt_template_delete failed', { error });
2556
+ return { success: false, error: 'Failed to delete prompt template' };
2557
+ }
2558
+ },
2559
+ // ── Inbound Destination tools (Issue #1500) ──────────────
2560
+ async inbound_destination_list(params) {
2561
+ const { channel_type, search, limit, offset } = params;
2562
+ try {
2563
+ const queryParams = new URLSearchParams();
2564
+ if (channel_type)
2565
+ queryParams.set('channel_type', channel_type);
2566
+ if (search)
2567
+ queryParams.set('search', search);
2568
+ if (limit !== undefined)
2569
+ queryParams.set('limit', String(limit));
2570
+ if (offset !== undefined)
2571
+ queryParams.set('offset', String(offset));
2572
+ const response = await apiClient.get(`/api/inbound-destinations?${queryParams.toString()}`, { user_id });
2573
+ if (!response.success) {
2574
+ return { success: false, error: response.error.message || 'Failed to list inbound destinations' };
2575
+ }
2576
+ const items = response.data.items ?? [];
2577
+ const content = items.length === 0
2578
+ ? 'No inbound destinations found.'
2579
+ : items.map((d) => `- **${d.address}** [${d.channel_type}]${d.display_name ? ` — ${d.display_name}` : ''}${d.agent_id ? ` (agent: ${d.agent_id})` : ''}`).join('\n');
2580
+ return { success: true, data: { content, details: { items, total: response.data.total ?? items.length } } };
2581
+ }
2582
+ catch (error) {
2583
+ logger.error('inbound_destination_list failed', { error });
2584
+ return { success: false, error: 'Failed to list inbound destinations' };
2585
+ }
2586
+ },
2587
+ async inbound_destination_get(params) {
2588
+ const { id } = params;
2589
+ try {
2590
+ const response = await apiClient.get(`/api/inbound-destinations/${id}`, { user_id });
2591
+ if (!response.success) {
2592
+ return { success: false, error: response.error.message || 'Inbound destination not found' };
2593
+ }
2594
+ const d = response.data;
2595
+ const lines = [`**${d.address}** [${d.channel_type}]`];
2596
+ if (d.display_name)
2597
+ lines.push(`Display: ${d.display_name}`);
2598
+ if (d.agent_id)
2599
+ lines.push(`Agent: ${d.agent_id}`);
2600
+ if (d.prompt_template_id)
2601
+ lines.push(`Prompt Template: ${d.prompt_template_id}`);
2602
+ if (d.context_id)
2603
+ lines.push(`Context: ${d.context_id}`);
2604
+ return { success: true, data: { content: lines.join('\n'), details: d } };
2605
+ }
2606
+ catch (error) {
2607
+ logger.error('inbound_destination_get failed', { error });
2608
+ return { success: false, error: 'Failed to get inbound destination' };
2609
+ }
2610
+ },
2611
+ async inbound_destination_update(params) {
2612
+ const { id, ...updates } = params;
2613
+ try {
2614
+ const response = await apiClient.put(`/api/inbound-destinations/${id}`, updates, { user_id });
2615
+ if (!response.success) {
2616
+ return { success: false, error: response.error.message || 'Failed to update inbound destination' };
2617
+ }
2618
+ return { success: true, data: { content: `Updated inbound destination "${response.data.address}"`, details: response.data } };
2619
+ }
2620
+ catch (error) {
2621
+ logger.error('inbound_destination_update failed', { error });
2622
+ return { success: false, error: 'Failed to update inbound destination' };
2623
+ }
2624
+ },
2625
+ // ── Channel Default tools (Issue #1501) ──────────────────
2626
+ async channel_default_list() {
2627
+ try {
2628
+ const response = await apiClient.get('/api/channel-defaults', { user_id });
2629
+ if (!response.success) {
2630
+ return { success: false, error: response.error.message || 'Failed to list channel defaults' };
2631
+ }
2632
+ const items = Array.isArray(response.data) ? response.data : [];
2633
+ const content = items.length === 0
2634
+ ? 'No channel defaults configured.'
2635
+ : items.map((d) => `- **${d.channel_type}**: agent=${d.agent_id}${d.prompt_template_id ? ` prompt=${d.prompt_template_id}` : ''}`).join('\n');
2636
+ return { success: true, data: { content, details: { items } } };
2637
+ }
2638
+ catch (error) {
2639
+ logger.error('channel_default_list failed', { error });
2640
+ return { success: false, error: 'Failed to list channel defaults' };
2641
+ }
2642
+ },
2643
+ async channel_default_get(params) {
2644
+ const { channel_type } = params;
2645
+ try {
2646
+ const response = await apiClient.get(`/api/channel-defaults/${channel_type}`, { user_id });
2647
+ if (!response.success) {
2648
+ return { success: false, error: response.error.message || 'Channel default not found' };
2649
+ }
2650
+ const d = response.data;
2651
+ const lines = [`**${d.channel_type}** → agent: ${d.agent_id}`];
2652
+ if (d.prompt_template_id)
2653
+ lines.push(`Prompt Template: ${d.prompt_template_id}`);
2654
+ if (d.context_id)
2655
+ lines.push(`Context: ${d.context_id}`);
2656
+ return { success: true, data: { content: lines.join('\n'), details: d } };
2657
+ }
2658
+ catch (error) {
2659
+ logger.error('channel_default_get failed', { error });
2660
+ return { success: false, error: 'Failed to get channel default' };
2661
+ }
2662
+ },
2663
+ async channel_default_set(params) {
2664
+ const { channel_type, agent_id, prompt_template_id, context_id } = params;
2665
+ try {
2666
+ const response = await apiClient.put(`/api/channel-defaults/${channel_type}`, { agent_id, prompt_template_id, context_id }, { user_id });
2667
+ if (!response.success) {
2668
+ return { success: false, error: response.error.message || 'Failed to set channel default' };
2669
+ }
2670
+ return { success: true, data: { content: `Set ${channel_type} default → agent: ${response.data.agent_id}`, details: response.data } };
2671
+ }
2672
+ catch (error) {
2673
+ logger.error('channel_default_set failed', { error });
2674
+ return { success: false, error: 'Failed to set channel default' };
2675
+ }
2676
+ },
2309
2677
  };
2310
2678
  }
2311
2679
  /**
@@ -2349,12 +2717,21 @@ export const registerOpenClaw = (api) => {
2349
2717
  const apiClient = createApiClient({ config, logger });
2350
2718
  // Extract context and user ID
2351
2719
  const context = extractContext(api.runtime);
2352
- const userId = getUserScopeKey({
2720
+ const user_id = getUserScopeKey({
2353
2721
  agentId: context.agent.agentId,
2354
2722
  sessionKey: context.session.sessionId,
2355
- }, config.userScoping);
2723
+ },
2724
+ // Backward compat: use userScoping if provided, otherwise default to 'agent'
2725
+ config.userScoping ?? 'agent');
2726
+ // Resolve namespace config (Issue #1428)
2727
+ const resolvedNamespace = resolveNamespaceConfig(config.namespace, context.agent.agentId);
2728
+ logger.info('Namespace config resolved', {
2729
+ agentId: context.agent.agentId,
2730
+ defaultNamespace: resolvedNamespace.default,
2731
+ recallNamespaces: resolvedNamespace.recall,
2732
+ });
2356
2733
  // Store plugin state
2357
- const state = { config, logger, apiClient, userId };
2734
+ const state = { config, logger, apiClient, user_id, resolvedNamespace };
2358
2735
  // Create tool handlers
2359
2736
  const handlers = createToolHandlers(state);
2360
2737
  // Register all 30 tools with correct OpenClaw Gateway execute signature
@@ -2363,7 +2740,7 @@ export const registerOpenClaw = (api) => {
2363
2740
  {
2364
2741
  name: 'memory_recall',
2365
2742
  description: 'Search through long-term memories. Use when you need context about user preferences, past decisions, or previously discussed topics.',
2366
- parameters: memoryRecallSchema,
2743
+ parameters: withNamespaces(memoryRecallSchema),
2367
2744
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2368
2745
  const result = await handlers.memory_recall(params);
2369
2746
  return toAgentToolResult(result);
@@ -2372,7 +2749,7 @@ export const registerOpenClaw = (api) => {
2372
2749
  {
2373
2750
  name: 'memory_store',
2374
2751
  description: 'Store a new memory for future reference. Use when the user shares important preferences, facts, or decisions.',
2375
- parameters: memoryStoreSchema,
2752
+ parameters: withNamespace(memoryStoreSchema),
2376
2753
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2377
2754
  const result = await handlers.memory_store(params);
2378
2755
  return toAgentToolResult(result);
@@ -2381,7 +2758,7 @@ export const registerOpenClaw = (api) => {
2381
2758
  {
2382
2759
  name: 'memory_forget',
2383
2760
  description: 'Remove a memory by ID or search query. Use when information is outdated or the user requests deletion.',
2384
- parameters: memoryForgetSchema,
2761
+ parameters: withNamespaces(memoryForgetSchema),
2385
2762
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2386
2763
  const result = await handlers.memory_forget(params);
2387
2764
  return toAgentToolResult(result);
@@ -2390,7 +2767,7 @@ export const registerOpenClaw = (api) => {
2390
2767
  {
2391
2768
  name: 'project_list',
2392
2769
  description: 'List projects for the user. Use to see what projects exist or filter by status.',
2393
- parameters: projectListSchema,
2770
+ parameters: withNamespaces(projectListSchema),
2394
2771
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2395
2772
  const result = await handlers.project_list(params);
2396
2773
  return toAgentToolResult(result);
@@ -2399,7 +2776,7 @@ export const registerOpenClaw = (api) => {
2399
2776
  {
2400
2777
  name: 'project_get',
2401
2778
  description: 'Get details about a specific project. Use when you need full project information.',
2402
- parameters: projectGetSchema,
2779
+ parameters: withNamespaces(projectGetSchema),
2403
2780
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2404
2781
  const result = await handlers.project_get(params);
2405
2782
  return toAgentToolResult(result);
@@ -2408,7 +2785,7 @@ export const registerOpenClaw = (api) => {
2408
2785
  {
2409
2786
  name: 'project_create',
2410
2787
  description: 'Create a new project. Use when the user wants to start tracking a new initiative.',
2411
- parameters: projectCreateSchema,
2788
+ parameters: withNamespace(projectCreateSchema),
2412
2789
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2413
2790
  const result = await handlers.project_create(params);
2414
2791
  return toAgentToolResult(result);
@@ -2417,7 +2794,7 @@ export const registerOpenClaw = (api) => {
2417
2794
  {
2418
2795
  name: 'todo_list',
2419
2796
  description: 'List todos, optionally filtered by project or status. Use to see pending tasks.',
2420
- parameters: todoListSchema,
2797
+ parameters: withNamespaces(todoListSchema),
2421
2798
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2422
2799
  const result = await handlers.todo_list(params);
2423
2800
  return toAgentToolResult(result);
@@ -2426,7 +2803,7 @@ export const registerOpenClaw = (api) => {
2426
2803
  {
2427
2804
  name: 'todo_create',
2428
2805
  description: 'Create a new todo item. Use when the user wants to track a task.',
2429
- parameters: todoCreateSchema,
2806
+ parameters: withNamespace(todoCreateSchema),
2430
2807
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2431
2808
  const result = await handlers.todo_create(params);
2432
2809
  return toAgentToolResult(result);
@@ -2435,7 +2812,7 @@ export const registerOpenClaw = (api) => {
2435
2812
  {
2436
2813
  name: 'todo_complete',
2437
2814
  description: 'Mark a todo as complete. Use when a task is done.',
2438
- parameters: todoCompleteSchema,
2815
+ parameters: withNamespaces(todoCompleteSchema),
2439
2816
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2440
2817
  const result = await handlers.todo_complete(params);
2441
2818
  return toAgentToolResult(result);
@@ -2444,7 +2821,7 @@ export const registerOpenClaw = (api) => {
2444
2821
  {
2445
2822
  name: 'todo_search',
2446
2823
  description: 'Search todos and work items by natural language query. Uses semantic and text search to find relevant items. Optionally filter by kind or status.',
2447
- parameters: todoSearchSchema,
2824
+ parameters: withNamespaces(todoSearchSchema),
2448
2825
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2449
2826
  const result = await handlers.todo_search(params);
2450
2827
  return toAgentToolResult(result);
@@ -2453,7 +2830,7 @@ export const registerOpenClaw = (api) => {
2453
2830
  {
2454
2831
  name: 'project_search',
2455
2832
  description: 'Search projects by natural language query. Uses semantic and text search to find relevant projects. Optionally filter by status (active, completed, archived).',
2456
- parameters: projectSearchSchema,
2833
+ parameters: withNamespaces(projectSearchSchema),
2457
2834
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2458
2835
  const result = await handlers.project_search(params);
2459
2836
  return toAgentToolResult(result);
@@ -2462,7 +2839,7 @@ export const registerOpenClaw = (api) => {
2462
2839
  {
2463
2840
  name: 'context_search',
2464
2841
  description: 'Search across memories, todos, projects, and messages simultaneously. Use when you need broad context about a topic, person, or project. Returns a blended ranked list from all entity types. Optionally filter by entity_types to narrow the search.',
2465
- parameters: contextSearchSchema,
2842
+ parameters: withNamespaces(contextSearchSchema),
2466
2843
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2467
2844
  const result = await handlers.context_search(params);
2468
2845
  return toAgentToolResult(result);
@@ -2471,7 +2848,7 @@ export const registerOpenClaw = (api) => {
2471
2848
  {
2472
2849
  name: 'contact_search',
2473
2850
  description: 'Search contacts by name, email, or other fields. Use to find people.',
2474
- parameters: contactSearchSchema,
2851
+ parameters: withNamespaces(contactSearchSchema),
2475
2852
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2476
2853
  const result = await handlers.contact_search(params);
2477
2854
  return toAgentToolResult(result);
@@ -2480,7 +2857,7 @@ export const registerOpenClaw = (api) => {
2480
2857
  {
2481
2858
  name: 'contact_get',
2482
2859
  description: 'Get details about a specific contact. Use when you need full contact information.',
2483
- parameters: contactGetSchema,
2860
+ parameters: withNamespaces(contactGetSchema),
2484
2861
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2485
2862
  const result = await handlers.contact_get(params);
2486
2863
  return toAgentToolResult(result);
@@ -2489,7 +2866,7 @@ export const registerOpenClaw = (api) => {
2489
2866
  {
2490
2867
  name: 'contact_create',
2491
2868
  description: 'Create a new contact. Use when the user mentions someone new to track.',
2492
- parameters: contactCreateSchema,
2869
+ parameters: withNamespace(contactCreateSchema),
2493
2870
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2494
2871
  const result = await handlers.contact_create(params);
2495
2872
  return toAgentToolResult(result);
@@ -2516,7 +2893,7 @@ export const registerOpenClaw = (api) => {
2516
2893
  {
2517
2894
  name: 'message_search',
2518
2895
  description: 'Search message history semantically. Use when you need to find past conversations, messages about specific topics, or communications with contacts. Supports filtering by channel (SMS/email) and contact.',
2519
- parameters: messageSearchSchema,
2896
+ parameters: withNamespaces(messageSearchSchema),
2520
2897
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2521
2898
  const result = await handlers.message_search(params);
2522
2899
  return toAgentToolResult(result);
@@ -2525,7 +2902,7 @@ export const registerOpenClaw = (api) => {
2525
2902
  {
2526
2903
  name: 'thread_list',
2527
2904
  description: 'List message threads (conversations). Use to see recent conversations with contacts. Can filter by channel (SMS/email) or contact.',
2528
- parameters: threadListSchema,
2905
+ parameters: withNamespaces(threadListSchema),
2529
2906
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2530
2907
  const result = await handlers.thread_list(params);
2531
2908
  return toAgentToolResult(result);
@@ -2534,7 +2911,7 @@ export const registerOpenClaw = (api) => {
2534
2911
  {
2535
2912
  name: 'thread_get',
2536
2913
  description: 'Get a thread with its message history. Use to view the full conversation in a thread.',
2537
- parameters: threadGetSchema,
2914
+ parameters: withNamespaces(threadGetSchema),
2538
2915
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2539
2916
  const result = await handlers.thread_get(params);
2540
2917
  return toAgentToolResult(result);
@@ -2543,7 +2920,7 @@ export const registerOpenClaw = (api) => {
2543
2920
  {
2544
2921
  name: 'relationship_set',
2545
2922
  description: "Record a relationship between two people, groups, or organisations. Examples: 'Troy is Alex\\'s partner', 'Sam is a member of The Kelly Household', 'Troy works for Acme Corp'. The system handles directionality and type matching automatically.",
2546
- parameters: relationshipSetSchema,
2923
+ parameters: withNamespace(relationshipSetSchema),
2547
2924
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2548
2925
  const result = await handlers.relationship_set(params);
2549
2926
  return toAgentToolResult(result);
@@ -2552,7 +2929,7 @@ export const registerOpenClaw = (api) => {
2552
2929
  {
2553
2930
  name: 'relationship_query',
2554
2931
  description: "Query a contact's relationships. Returns all relationships including family, partners, group memberships, professional connections, etc. Handles directional relationships automatically.",
2555
- parameters: relationshipQuerySchema,
2932
+ parameters: withNamespaces(relationshipQuerySchema),
2556
2933
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2557
2934
  const result = await handlers.relationship_query(params);
2558
2935
  return toAgentToolResult(result);
@@ -2633,7 +3010,7 @@ export const registerOpenClaw = (api) => {
2633
3010
  {
2634
3011
  name: 'links_set',
2635
3012
  description: 'Create a link between two entities (memory, todo, project, contact, GitHub issue, or URL). Links are bidirectional and can be traversed from either end. Use to connect related items for cross-reference and context discovery.',
2636
- parameters: linksSetSchema,
3013
+ parameters: withNamespace(linksSetSchema),
2637
3014
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2638
3015
  const result = await handlers.links_set(params);
2639
3016
  return toAgentToolResult(result);
@@ -2642,7 +3019,7 @@ export const registerOpenClaw = (api) => {
2642
3019
  {
2643
3020
  name: 'links_query',
2644
3021
  description: 'Query all links for an entity (memory, todo, project, or contact). Returns connected entities including other items, GitHub issues, and URLs. Optionally filter by link target types.',
2645
- parameters: linksQuerySchema,
3022
+ parameters: withNamespaces(linksQuerySchema),
2646
3023
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2647
3024
  const result = await handlers.links_query(params);
2648
3025
  return toAgentToolResult(result);
@@ -2651,12 +3028,114 @@ export const registerOpenClaw = (api) => {
2651
3028
  {
2652
3029
  name: 'links_remove',
2653
3030
  description: 'Remove a link between two entities. Deletes both directions of the link. Use when a connection is no longer relevant or was created in error.',
2654
- parameters: linksRemoveSchema,
3031
+ parameters: withNamespaces(linksRemoveSchema),
2655
3032
  execute: async (_toolCallId, params, _signal, _onUpdate) => {
2656
3033
  const result = await handlers.links_remove(params);
2657
3034
  return toAgentToolResult(result);
2658
3035
  },
2659
3036
  },
3037
+ // Prompt template tools (Epic #1497, Issue #1499)
3038
+ {
3039
+ name: 'prompt_template_list',
3040
+ description: 'List prompt templates used for inbound message triage. Filter by channel type (sms, email, ha_observation, general).',
3041
+ parameters: promptTemplateListSchema,
3042
+ execute: async (_toolCallId, params, _signal, _onUpdate) => {
3043
+ const result = await handlers.prompt_template_list(params);
3044
+ return toAgentToolResult(result);
3045
+ },
3046
+ },
3047
+ {
3048
+ name: 'prompt_template_get',
3049
+ description: 'Get a prompt template by ID. Returns the full template including prompt content.',
3050
+ parameters: promptTemplateGetSchema,
3051
+ execute: async (_toolCallId, params, _signal, _onUpdate) => {
3052
+ const result = await handlers.prompt_template_get(params);
3053
+ return toAgentToolResult(result);
3054
+ },
3055
+ },
3056
+ {
3057
+ name: 'prompt_template_create',
3058
+ description: 'Create a new prompt template for inbound message triage. Requires agentadmin access.',
3059
+ parameters: promptTemplateCreateSchema,
3060
+ execute: async (_toolCallId, params, _signal, _onUpdate) => {
3061
+ const result = await handlers.prompt_template_create(params);
3062
+ return toAgentToolResult(result);
3063
+ },
3064
+ },
3065
+ {
3066
+ name: 'prompt_template_update',
3067
+ description: 'Update an existing prompt template. Can change label, content, channel type, or set as default.',
3068
+ parameters: promptTemplateUpdateSchema,
3069
+ execute: async (_toolCallId, params, _signal, _onUpdate) => {
3070
+ const result = await handlers.prompt_template_update(params);
3071
+ return toAgentToolResult(result);
3072
+ },
3073
+ },
3074
+ {
3075
+ name: 'prompt_template_delete',
3076
+ description: 'Soft-delete a prompt template (sets is_active to false). Template can still be viewed but will not be used for routing.',
3077
+ parameters: promptTemplateDeleteSchema,
3078
+ execute: async (_toolCallId, params, _signal, _onUpdate) => {
3079
+ const result = await handlers.prompt_template_delete(params);
3080
+ return toAgentToolResult(result);
3081
+ },
3082
+ },
3083
+ // ── Inbound Destination tools (Issue #1500) ──────────────
3084
+ {
3085
+ name: 'inbound_destination_list',
3086
+ description: 'List discovered inbound destinations (phone numbers and email addresses). Auto-created when messages arrive.',
3087
+ parameters: inboundDestinationListSchema,
3088
+ execute: async (_toolCallId, params, _signal, _onUpdate) => {
3089
+ const result = await handlers.inbound_destination_list(params);
3090
+ return toAgentToolResult(result);
3091
+ },
3092
+ },
3093
+ {
3094
+ name: 'inbound_destination_get',
3095
+ description: 'Get an inbound destination by ID. Returns routing config (agent, prompt template, context).',
3096
+ parameters: inboundDestinationGetSchema,
3097
+ execute: async (_toolCallId, params, _signal, _onUpdate) => {
3098
+ const result = await handlers.inbound_destination_get(params);
3099
+ return toAgentToolResult(result);
3100
+ },
3101
+ },
3102
+ {
3103
+ name: 'inbound_destination_update',
3104
+ description: 'Update routing overrides for an inbound destination. Set agent, prompt template, or context for routing.',
3105
+ parameters: inboundDestinationUpdateSchema,
3106
+ execute: async (_toolCallId, params, _signal, _onUpdate) => {
3107
+ const result = await handlers.inbound_destination_update(params);
3108
+ return toAgentToolResult(result);
3109
+ },
3110
+ },
3111
+ // ── Channel Default tools (Issue #1501) ──────────────────
3112
+ {
3113
+ name: 'channel_default_list',
3114
+ description: 'List all channel defaults (per-channel routing config). Shows which agent handles each channel type.',
3115
+ parameters: channelDefaultListSchema,
3116
+ execute: async (_toolCallId, params, _signal, _onUpdate) => {
3117
+ const result = await handlers.channel_default_list();
3118
+ return toAgentToolResult(result);
3119
+ },
3120
+ },
3121
+ {
3122
+ name: 'channel_default_get',
3123
+ description: 'Get the default routing config for a specific channel type (sms, email, ha_observation).',
3124
+ parameters: channelDefaultGetSchema,
3125
+ execute: async (_toolCallId, params, _signal, _onUpdate) => {
3126
+ const result = await handlers.channel_default_get(params);
3127
+ return toAgentToolResult(result);
3128
+ },
3129
+ },
3130
+ {
3131
+ name: 'channel_default_set',
3132
+ description: 'Set or update the default routing config for a channel type. Requires agentadmin access.',
3133
+ parameters: channelDefaultSetSchema,
3134
+ execute: async (_toolCallId, params, _signal, _onUpdate) => {
3135
+ const result = await handlers.channel_default_set(params);
3136
+ return toAgentToolResult(result);
3137
+ },
3138
+ },
2660
3139
  ];
2661
3140
  for (const tool of tools) {
2662
3141
  api.registerTool(tool);
@@ -2674,7 +3153,7 @@ export const registerOpenClaw = (api) => {
2674
3153
  client: apiClient,
2675
3154
  logger,
2676
3155
  config,
2677
- userId,
3156
+ user_id,
2678
3157
  timeoutMs: HOOK_TIMEOUT_MS,
2679
3158
  });
2680
3159
  /**
@@ -2719,7 +3198,7 @@ export const registerOpenClaw = (api) => {
2719
3198
  client: apiClient,
2720
3199
  logger,
2721
3200
  config,
2722
- userId,
3201
+ user_id,
2723
3202
  timeoutMs: HOOK_TIMEOUT_MS * 2, // Allow more time for capture (10s)
2724
3203
  });
2725
3204
  /**
@@ -2728,7 +3207,7 @@ export const registerOpenClaw = (api) => {
2728
3207
  */
2729
3208
  const agentEndHandler = async (event, _ctx) => {
2730
3209
  logger.debug('Auto-capture hook triggered', {
2731
- messageCount: event.messages?.length ?? 0,
3210
+ message_count: event.messages?.length ?? 0,
2732
3211
  success: event.success,
2733
3212
  });
2734
3213
  try {
@@ -2775,8 +3254,8 @@ export const registerOpenClaw = (api) => {
2775
3254
  */
2776
3255
  const messageReceivedHandler = async (event, _ctx) => {
2777
3256
  // Skip if no thread ID (nothing to link to)
2778
- if (!event.threadId) {
2779
- logger.debug('Auto-link skipped: no threadId in message_received event');
3257
+ if (!event.thread_id) {
3258
+ logger.debug('Auto-link skipped: no thread_id in message_received event');
2780
3259
  return;
2781
3260
  }
2782
3261
  // Skip if no content and no sender info (nothing to match on)
@@ -2788,9 +3267,9 @@ export const registerOpenClaw = (api) => {
2788
3267
  await autoLinkInboundMessage({
2789
3268
  client: apiClient,
2790
3269
  logger,
2791
- userId,
3270
+ user_id,
2792
3271
  message: {
2793
- threadId: event.threadId,
3272
+ thread_id: event.thread_id,
2794
3273
  senderEmail: event.senderEmail ?? (event.sender?.includes('@') ? event.sender : undefined),
2795
3274
  senderPhone: event.senderPhone ?? (event.sender && !event.sender.includes('@') ? event.sender : undefined),
2796
3275
  content: event.content ?? '',
@@ -2815,14 +3294,14 @@ export const registerOpenClaw = (api) => {
2815
3294
  const gatewayMethods = createGatewayMethods({
2816
3295
  logger,
2817
3296
  apiClient,
2818
- userId,
3297
+ user_id,
2819
3298
  });
2820
3299
  registerGatewayRpcMethods(api, gatewayMethods);
2821
3300
  // Register OAuth Gateway RPC methods (Issue #1054)
2822
3301
  const oauthGatewayMethods = createOAuthGatewayMethods({
2823
3302
  logger,
2824
3303
  apiClient,
2825
- userId,
3304
+ user_id,
2826
3305
  });
2827
3306
  registerOAuthGatewayRpcMethods(api, oauthGatewayMethods);
2828
3307
  // Register background notification service (Issue #325)
@@ -2843,7 +3322,7 @@ export const registerOpenClaw = (api) => {
2843
3322
  const notificationService = createNotificationService({
2844
3323
  logger,
2845
3324
  apiClient,
2846
- userId,
3325
+ user_id,
2847
3326
  events: eventEmitter,
2848
3327
  config: {
2849
3328
  enabled: config.autoRecall, // Only enable if auto-recall is enabled
@@ -2858,7 +3337,7 @@ export const registerOpenClaw = (api) => {
2858
3337
  .description('Show plugin status and statistics')
2859
3338
  .action(async () => {
2860
3339
  try {
2861
- const response = await apiClient.get('/api/health', { userId });
3340
+ const response = await apiClient.get('/api/health', { user_id });
2862
3341
  if (response.success) {
2863
3342
  console.log('Plugin Status: Connected');
2864
3343
  }
@@ -2892,7 +3371,7 @@ export const registerOpenClaw = (api) => {
2892
3371
  logger.info('OpenClaw Projects plugin registered', {
2893
3372
  agentId: context.agent.agentId,
2894
3373
  sessionId: context.session.sessionId,
2895
- userId,
3374
+ user_id,
2896
3375
  toolCount: tools.length,
2897
3376
  config: redactConfig(config),
2898
3377
  });
@@ -2901,27 +3380,27 @@ export const registerOpenClaw = (api) => {
2901
3380
  export default registerOpenClaw;
2902
3381
  /** Export JSON Schemas for external use */
2903
3382
  export const schemas = {
2904
- memoryRecall: memoryRecallSchema,
2905
- memoryStore: memoryStoreSchema,
2906
- memoryForget: memoryForgetSchema,
2907
- projectList: projectListSchema,
2908
- projectGet: projectGetSchema,
2909
- projectCreate: projectCreateSchema,
2910
- todoList: todoListSchema,
2911
- todoCreate: todoCreateSchema,
2912
- todoComplete: todoCompleteSchema,
2913
- todoSearch: todoSearchSchema,
2914
- projectSearch: projectSearchSchema,
2915
- contactSearch: contactSearchSchema,
2916
- contactGet: contactGetSchema,
2917
- contactCreate: contactCreateSchema,
3383
+ memoryRecall: withNamespaces(memoryRecallSchema),
3384
+ memoryStore: withNamespace(memoryStoreSchema),
3385
+ memoryForget: withNamespaces(memoryForgetSchema),
3386
+ projectList: withNamespaces(projectListSchema),
3387
+ projectGet: withNamespaces(projectGetSchema),
3388
+ projectCreate: withNamespace(projectCreateSchema),
3389
+ todoList: withNamespaces(todoListSchema),
3390
+ todoCreate: withNamespace(todoCreateSchema),
3391
+ todoComplete: withNamespaces(todoCompleteSchema),
3392
+ todoSearch: withNamespaces(todoSearchSchema),
3393
+ projectSearch: withNamespaces(projectSearchSchema),
3394
+ contactSearch: withNamespaces(contactSearchSchema),
3395
+ contactGet: withNamespaces(contactGetSchema),
3396
+ contactCreate: withNamespace(contactCreateSchema),
2918
3397
  smsSend: smsSendSchema,
2919
3398
  emailSend: emailSendSchema,
2920
- messageSearch: messageSearchSchema,
2921
- threadList: threadListSchema,
2922
- threadGet: threadGetSchema,
2923
- relationshipSet: relationshipSetSchema,
2924
- relationshipQuery: relationshipQuerySchema,
3399
+ messageSearch: withNamespaces(messageSearchSchema),
3400
+ threadList: withNamespaces(threadListSchema),
3401
+ threadGet: withNamespaces(threadGetSchema),
3402
+ relationshipSet: withNamespace(relationshipSetSchema),
3403
+ relationshipQuery: withNamespaces(relationshipQuerySchema),
2925
3404
  fileShare: fileShareSchema,
2926
3405
  skillStorePut: skillStorePutSchema,
2927
3406
  skillStoreGet: skillStoreGetSchema,