@troykelly/openclaw-projects 0.0.23 → 0.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-client.d.ts +2 -0
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +3 -0
- package/dist/api-client.js.map +1 -1
- package/dist/gateway/oauth-rpc-methods.d.ts +39 -0
- package/dist/gateway/oauth-rpc-methods.d.ts.map +1 -1
- package/dist/gateway/oauth-rpc-methods.js +108 -1
- package/dist/gateway/oauth-rpc-methods.js.map +1 -1
- package/dist/register-openclaw.d.ts +7 -0
- package/dist/register-openclaw.d.ts.map +1 -1
- package/dist/register-openclaw.js +410 -53
- package/dist/register-openclaw.js.map +1 -1
- package/dist/tools/memory-recall.d.ts +6 -6
- package/dist/tools/memory-store.d.ts +12 -12
- package/dist/tools/notes.d.ts +4 -4
- package/dist/tools/skill-store.d.ts +6 -6
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -500,28 +500,161 @@ const contactGetSchema = {
|
|
|
500
500
|
const contactCreateSchema = {
|
|
501
501
|
type: 'object',
|
|
502
502
|
properties: {
|
|
503
|
-
|
|
503
|
+
display_name: {
|
|
504
504
|
type: 'string',
|
|
505
|
-
description: '
|
|
506
|
-
minLength: 1,
|
|
505
|
+
description: 'Full display name (required for organizations/groups, optional for persons if given_name or family_name provided)',
|
|
507
506
|
maxLength: 200,
|
|
508
507
|
},
|
|
508
|
+
given_name: {
|
|
509
|
+
type: 'string',
|
|
510
|
+
description: 'Given (first) name',
|
|
511
|
+
maxLength: 100,
|
|
512
|
+
},
|
|
513
|
+
family_name: {
|
|
514
|
+
type: 'string',
|
|
515
|
+
description: 'Family (last) name',
|
|
516
|
+
maxLength: 100,
|
|
517
|
+
},
|
|
518
|
+
nickname: {
|
|
519
|
+
type: 'string',
|
|
520
|
+
description: 'Nickname or short name',
|
|
521
|
+
maxLength: 100,
|
|
522
|
+
},
|
|
523
|
+
contact_kind: {
|
|
524
|
+
type: 'string',
|
|
525
|
+
description: 'Contact type',
|
|
526
|
+
enum: ['person', 'organisation', 'group', 'agent'],
|
|
527
|
+
default: 'person',
|
|
528
|
+
},
|
|
509
529
|
email: {
|
|
510
530
|
type: 'string',
|
|
511
|
-
description: '
|
|
531
|
+
description: 'Primary email address (creates an email endpoint)',
|
|
512
532
|
format: 'email',
|
|
513
533
|
},
|
|
514
534
|
phone: {
|
|
515
535
|
type: 'string',
|
|
516
|
-
description: '
|
|
536
|
+
description: 'Primary phone number (creates a phone endpoint)',
|
|
517
537
|
},
|
|
518
538
|
notes: {
|
|
519
539
|
type: 'string',
|
|
520
540
|
description: 'Notes about the contact',
|
|
521
541
|
maxLength: 5000,
|
|
522
542
|
},
|
|
543
|
+
tags: {
|
|
544
|
+
type: 'array',
|
|
545
|
+
description: 'Tags to assign to the contact (max 20)',
|
|
546
|
+
items: { type: 'string', maxLength: 100 },
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
required: [],
|
|
550
|
+
};
|
|
551
|
+
/**
|
|
552
|
+
* Contact update tool JSON Schema (#1600)
|
|
553
|
+
*/
|
|
554
|
+
const contactUpdateSchema = {
|
|
555
|
+
type: 'object',
|
|
556
|
+
properties: {
|
|
557
|
+
contact_id: {
|
|
558
|
+
type: 'string',
|
|
559
|
+
description: 'ID of the contact to update',
|
|
560
|
+
format: 'uuid',
|
|
561
|
+
},
|
|
562
|
+
display_name: {
|
|
563
|
+
type: 'string',
|
|
564
|
+
description: 'Updated display name',
|
|
565
|
+
maxLength: 200,
|
|
566
|
+
},
|
|
567
|
+
given_name: { type: 'string', maxLength: 100 },
|
|
568
|
+
family_name: { type: 'string', maxLength: 100 },
|
|
569
|
+
nickname: { type: 'string', maxLength: 100 },
|
|
570
|
+
notes: { type: 'string', maxLength: 5000 },
|
|
571
|
+
tags: {
|
|
572
|
+
type: 'array',
|
|
573
|
+
description: 'Replace all tags (empty array removes all)',
|
|
574
|
+
items: { type: 'string', maxLength: 100 },
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
required: ['contact_id'],
|
|
578
|
+
};
|
|
579
|
+
/**
|
|
580
|
+
* Contact merge tool JSON Schema (#1600)
|
|
581
|
+
*/
|
|
582
|
+
const contactMergeSchema = {
|
|
583
|
+
type: 'object',
|
|
584
|
+
properties: {
|
|
585
|
+
survivor_id: {
|
|
586
|
+
type: 'string',
|
|
587
|
+
description: 'ID of the contact to keep (survivor)',
|
|
588
|
+
format: 'uuid',
|
|
589
|
+
},
|
|
590
|
+
loser_id: {
|
|
591
|
+
type: 'string',
|
|
592
|
+
description: 'ID of the contact to merge into the survivor (will be soft-deleted)',
|
|
593
|
+
format: 'uuid',
|
|
594
|
+
},
|
|
595
|
+
},
|
|
596
|
+
required: ['survivor_id', 'loser_id'],
|
|
597
|
+
};
|
|
598
|
+
/**
|
|
599
|
+
* Contact tag add tool JSON Schema (#1600)
|
|
600
|
+
*/
|
|
601
|
+
const contactTagAddSchema = {
|
|
602
|
+
type: 'object',
|
|
603
|
+
properties: {
|
|
604
|
+
contact_id: {
|
|
605
|
+
type: 'string',
|
|
606
|
+
description: 'Contact ID',
|
|
607
|
+
format: 'uuid',
|
|
608
|
+
},
|
|
609
|
+
tags: {
|
|
610
|
+
type: 'array',
|
|
611
|
+
description: 'Tags to add (1–20 tags)',
|
|
612
|
+
items: { type: 'string', maxLength: 100 },
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
required: ['contact_id', 'tags'],
|
|
616
|
+
};
|
|
617
|
+
/**
|
|
618
|
+
* Contact tag remove tool JSON Schema (#1600)
|
|
619
|
+
*/
|
|
620
|
+
const contactTagRemoveSchema = {
|
|
621
|
+
type: 'object',
|
|
622
|
+
properties: {
|
|
623
|
+
contact_id: {
|
|
624
|
+
type: 'string',
|
|
625
|
+
description: 'Contact ID',
|
|
626
|
+
format: 'uuid',
|
|
627
|
+
},
|
|
628
|
+
tag: {
|
|
629
|
+
type: 'string',
|
|
630
|
+
description: 'Tag to remove',
|
|
631
|
+
maxLength: 100,
|
|
632
|
+
},
|
|
633
|
+
},
|
|
634
|
+
required: ['contact_id', 'tag'],
|
|
635
|
+
};
|
|
636
|
+
/**
|
|
637
|
+
* Contact resolve tool JSON Schema (#1601)
|
|
638
|
+
* Resolves a sender identity (phone, email, name) to a contact match.
|
|
639
|
+
*/
|
|
640
|
+
const contactResolveSchema = {
|
|
641
|
+
type: 'object',
|
|
642
|
+
properties: {
|
|
643
|
+
phone: {
|
|
644
|
+
type: 'string',
|
|
645
|
+
description: 'Sender phone number to resolve',
|
|
646
|
+
},
|
|
647
|
+
email: {
|
|
648
|
+
type: 'string',
|
|
649
|
+
description: 'Sender email address to resolve',
|
|
650
|
+
format: 'email',
|
|
651
|
+
},
|
|
652
|
+
name: {
|
|
653
|
+
type: 'string',
|
|
654
|
+
description: 'Sender name for fuzzy matching',
|
|
655
|
+
maxLength: 200,
|
|
656
|
+
},
|
|
523
657
|
},
|
|
524
|
-
required: ['name'],
|
|
525
658
|
};
|
|
526
659
|
/**
|
|
527
660
|
* SMS send tool JSON Schema
|
|
@@ -1316,7 +1449,7 @@ export async function refreshNamespacesAsync(state) {
|
|
|
1316
1449
|
return;
|
|
1317
1450
|
state.refreshInFlight = true;
|
|
1318
1451
|
try {
|
|
1319
|
-
const response = await state.apiClient.get('/api/namespaces', { user_id: state.user_id });
|
|
1452
|
+
const response = await state.apiClient.get('/api/namespaces', { user_id: state.user_id, user_email: state.user_email });
|
|
1320
1453
|
if (!response.success) {
|
|
1321
1454
|
state.logger.warn('Namespace discovery failed, keeping cached list', { error: response.error.message });
|
|
1322
1455
|
// Do NOT update timestamp on failure — let the next check retry sooner
|
|
@@ -1354,7 +1487,9 @@ export async function refreshNamespacesAsync(state) {
|
|
|
1354
1487
|
* Create tool execution handlers
|
|
1355
1488
|
*/
|
|
1356
1489
|
function createToolHandlers(state) {
|
|
1357
|
-
const { config, logger, apiClient, user_id, resolvedNamespace } = state;
|
|
1490
|
+
const { config, logger, apiClient, user_id, user_email, resolvedNamespace } = state;
|
|
1491
|
+
/** Build RequestOptions with user_id and user_email for identity resolution (#1567) */
|
|
1492
|
+
const reqOpts = () => ({ user_id, user_email });
|
|
1358
1493
|
/**
|
|
1359
1494
|
* Get the effective namespace for a store/create operation.
|
|
1360
1495
|
* Uses explicit tool param if provided, otherwise falls back to config default.
|
|
@@ -1398,7 +1533,7 @@ function createToolHandlers(state) {
|
|
|
1398
1533
|
const ns = getRecallNamespaces(params);
|
|
1399
1534
|
if (ns.length > 0)
|
|
1400
1535
|
queryParams.set('namespaces', ns.join(','));
|
|
1401
|
-
const response = await apiClient.get(`/api/memories/search?${queryParams}`,
|
|
1536
|
+
const response = await apiClient.get(`/api/memories/search?${queryParams}`, reqOpts());
|
|
1402
1537
|
if (!response.success) {
|
|
1403
1538
|
return { success: false, error: response.error.message };
|
|
1404
1539
|
}
|
|
@@ -1484,7 +1619,7 @@ function createToolHandlers(state) {
|
|
|
1484
1619
|
if (location.place_label)
|
|
1485
1620
|
payload.place_label = location.place_label;
|
|
1486
1621
|
}
|
|
1487
|
-
const response = await apiClient.post('/api/memories/unified', payload,
|
|
1622
|
+
const response = await apiClient.post('/api/memories/unified', payload, reqOpts());
|
|
1488
1623
|
if (!response.success) {
|
|
1489
1624
|
return { success: false, error: response.error.message };
|
|
1490
1625
|
}
|
|
@@ -1505,7 +1640,7 @@ function createToolHandlers(state) {
|
|
|
1505
1640
|
const { memory_id, query } = params;
|
|
1506
1641
|
try {
|
|
1507
1642
|
if (memory_id) {
|
|
1508
|
-
const response = await apiClient.delete(`/api/memories/${memory_id}`,
|
|
1643
|
+
const response = await apiClient.delete(`/api/memories/${memory_id}`, reqOpts());
|
|
1509
1644
|
if (!response.success) {
|
|
1510
1645
|
return { success: false, error: response.error.message };
|
|
1511
1646
|
}
|
|
@@ -1521,7 +1656,7 @@ function createToolHandlers(state) {
|
|
|
1521
1656
|
const forgetNs = getRecallNamespaces(params);
|
|
1522
1657
|
if (forgetNs.length > 0)
|
|
1523
1658
|
forgetQp.set('namespaces', forgetNs.join(','));
|
|
1524
|
-
const searchResponse = await apiClient.get(`/api/memories/search?${forgetQp}`,
|
|
1659
|
+
const searchResponse = await apiClient.get(`/api/memories/search?${forgetQp}`, reqOpts());
|
|
1525
1660
|
if (!searchResponse.success) {
|
|
1526
1661
|
return { success: false, error: searchResponse.error.message };
|
|
1527
1662
|
}
|
|
@@ -1534,7 +1669,7 @@ function createToolHandlers(state) {
|
|
|
1534
1669
|
}
|
|
1535
1670
|
// Single high-confidence match → auto-delete
|
|
1536
1671
|
if (matches.length === 1 && (matches[0].similarity ?? 0) > 0.9) {
|
|
1537
|
-
const delResponse = await apiClient.delete(`/api/memories/${matches[0].id}`,
|
|
1672
|
+
const delResponse = await apiClient.delete(`/api/memories/${matches[0].id}`, reqOpts());
|
|
1538
1673
|
if (!delResponse.success) {
|
|
1539
1674
|
return { success: false, error: delResponse.error.message };
|
|
1540
1675
|
}
|
|
@@ -1574,7 +1709,7 @@ function createToolHandlers(state) {
|
|
|
1574
1709
|
const projListNs = getRecallNamespaces(params);
|
|
1575
1710
|
if (projListNs.length > 0)
|
|
1576
1711
|
queryParams.set('namespaces', projListNs.join(','));
|
|
1577
|
-
const response = await apiClient.get(`/api/work-items?${queryParams}`,
|
|
1712
|
+
const response = await apiClient.get(`/api/work-items?${queryParams}`, reqOpts());
|
|
1578
1713
|
if (!response.success) {
|
|
1579
1714
|
return { success: false, error: response.error.message };
|
|
1580
1715
|
}
|
|
@@ -1593,7 +1728,7 @@ function createToolHandlers(state) {
|
|
|
1593
1728
|
async project_get(params) {
|
|
1594
1729
|
const { project_id } = params;
|
|
1595
1730
|
try {
|
|
1596
|
-
const response = await apiClient.get(`/api/work-items/${project_id}?user_email=${encodeURIComponent(user_id)}`,
|
|
1731
|
+
const response = await apiClient.get(`/api/work-items/${project_id}?user_email=${encodeURIComponent(user_id)}`, reqOpts());
|
|
1597
1732
|
if (!response.success) {
|
|
1598
1733
|
return { success: false, error: response.error.message };
|
|
1599
1734
|
}
|
|
@@ -1614,7 +1749,7 @@ function createToolHandlers(state) {
|
|
|
1614
1749
|
async project_create(params) {
|
|
1615
1750
|
const { name, description, status = 'active', } = params;
|
|
1616
1751
|
try {
|
|
1617
|
-
const response = await apiClient.post('/api/work-items', { title: name, description, item_type: 'project', status, user_email: user_id, namespace: getStoreNamespace(params) },
|
|
1752
|
+
const response = await apiClient.post('/api/work-items', { title: name, description, item_type: 'project', status, user_email: user_id, namespace: getStoreNamespace(params) }, reqOpts());
|
|
1618
1753
|
if (!response.success) {
|
|
1619
1754
|
return { success: false, error: response.error.message };
|
|
1620
1755
|
}
|
|
@@ -1649,7 +1784,7 @@ function createToolHandlers(state) {
|
|
|
1649
1784
|
const todoListNs = getRecallNamespaces(params);
|
|
1650
1785
|
if (todoListNs.length > 0)
|
|
1651
1786
|
queryParams.set('namespaces', todoListNs.join(','));
|
|
1652
|
-
const response = await apiClient.get(`/api/work-items?${queryParams}`,
|
|
1787
|
+
const response = await apiClient.get(`/api/work-items?${queryParams}`, reqOpts());
|
|
1653
1788
|
if (!response.success) {
|
|
1654
1789
|
return { success: false, error: response.error.message };
|
|
1655
1790
|
}
|
|
@@ -1686,7 +1821,7 @@ function createToolHandlers(state) {
|
|
|
1686
1821
|
body.parent_work_item_id = project_id;
|
|
1687
1822
|
if (dueDate)
|
|
1688
1823
|
body.not_after = dueDate;
|
|
1689
|
-
const response = await apiClient.post('/api/work-items', body,
|
|
1824
|
+
const response = await apiClient.post('/api/work-items', body, reqOpts());
|
|
1690
1825
|
if (!response.success) {
|
|
1691
1826
|
return { success: false, error: response.error.message };
|
|
1692
1827
|
}
|
|
@@ -1706,7 +1841,7 @@ function createToolHandlers(state) {
|
|
|
1706
1841
|
async todo_complete(params) {
|
|
1707
1842
|
const { todoId } = params;
|
|
1708
1843
|
try {
|
|
1709
|
-
const response = await apiClient.patch(`/api/work-items/${todoId}/status?user_email=${encodeURIComponent(user_id)}`, { status: 'completed' },
|
|
1844
|
+
const response = await apiClient.patch(`/api/work-items/${todoId}/status?user_email=${encodeURIComponent(user_id)}`, { status: 'completed' }, reqOpts());
|
|
1710
1845
|
if (!response.success) {
|
|
1711
1846
|
return { success: false, error: response.error.message };
|
|
1712
1847
|
}
|
|
@@ -1739,7 +1874,7 @@ function createToolHandlers(state) {
|
|
|
1739
1874
|
const todoSearchNs = getRecallNamespaces(params);
|
|
1740
1875
|
if (todoSearchNs.length > 0)
|
|
1741
1876
|
queryParams.set('namespaces', todoSearchNs.join(','));
|
|
1742
|
-
const response = await apiClient.get(`/api/search?${queryParams}`,
|
|
1877
|
+
const response = await apiClient.get(`/api/search?${queryParams}`, reqOpts());
|
|
1743
1878
|
if (!response.success) {
|
|
1744
1879
|
return { success: false, error: response.error.message };
|
|
1745
1880
|
}
|
|
@@ -1829,7 +1964,7 @@ function createToolHandlers(state) {
|
|
|
1829
1964
|
async contact_get(params) {
|
|
1830
1965
|
const { contact_id } = params;
|
|
1831
1966
|
try {
|
|
1832
|
-
const response = await apiClient.get(`/api/contacts/${contact_id}?user_email=${encodeURIComponent(user_id)}`,
|
|
1967
|
+
const response = await apiClient.get(`/api/contacts/${contact_id}?user_email=${encodeURIComponent(user_id)}`, reqOpts());
|
|
1833
1968
|
if (!response.success) {
|
|
1834
1969
|
return { success: false, error: response.error.message };
|
|
1835
1970
|
}
|
|
@@ -1852,10 +1987,37 @@ function createToolHandlers(state) {
|
|
|
1852
1987
|
}
|
|
1853
1988
|
},
|
|
1854
1989
|
async contact_create(params) {
|
|
1855
|
-
const {
|
|
1990
|
+
const { display_name, given_name, family_name, nickname, contact_kind, email, phone, notes, tags } = params;
|
|
1991
|
+
// Must have display_name or at least given_name/family_name
|
|
1992
|
+
const name = display_name || [given_name, family_name].filter(Boolean).join(' ');
|
|
1993
|
+
if (!name) {
|
|
1994
|
+
return { success: false, error: 'Either display_name or given_name/family_name is required' };
|
|
1995
|
+
}
|
|
1856
1996
|
try {
|
|
1857
|
-
|
|
1858
|
-
|
|
1997
|
+
const body = {
|
|
1998
|
+
user_email: user_id,
|
|
1999
|
+
namespace: getStoreNamespace(params),
|
|
2000
|
+
notes,
|
|
2001
|
+
contact_kind: contact_kind ?? 'person',
|
|
2002
|
+
tags,
|
|
2003
|
+
};
|
|
2004
|
+
if (display_name)
|
|
2005
|
+
body.display_name = display_name;
|
|
2006
|
+
if (given_name)
|
|
2007
|
+
body.given_name = given_name;
|
|
2008
|
+
if (family_name)
|
|
2009
|
+
body.family_name = family_name;
|
|
2010
|
+
if (nickname)
|
|
2011
|
+
body.nickname = nickname;
|
|
2012
|
+
// Build endpoints array from convenience fields
|
|
2013
|
+
const endpoints = [];
|
|
2014
|
+
if (email)
|
|
2015
|
+
endpoints.push({ type: 'email', value: email });
|
|
2016
|
+
if (phone)
|
|
2017
|
+
endpoints.push({ type: 'phone', value: phone });
|
|
2018
|
+
if (endpoints.length > 0)
|
|
2019
|
+
body.endpoints = endpoints;
|
|
2020
|
+
const response = await apiClient.post('/api/contacts', body, reqOpts());
|
|
1859
2021
|
if (!response.success) {
|
|
1860
2022
|
return { success: false, error: response.error.message };
|
|
1861
2023
|
}
|
|
@@ -1863,7 +2025,7 @@ function createToolHandlers(state) {
|
|
|
1863
2025
|
success: true,
|
|
1864
2026
|
data: {
|
|
1865
2027
|
content: `Contact "${name}" created successfully (ID: ${response.data.id})`,
|
|
1866
|
-
details: { id: response.data.id },
|
|
2028
|
+
details: { id: response.data.id, display_name: response.data.display_name ?? name },
|
|
1867
2029
|
},
|
|
1868
2030
|
};
|
|
1869
2031
|
}
|
|
@@ -1872,6 +2034,147 @@ function createToolHandlers(state) {
|
|
|
1872
2034
|
return { success: false, error: 'Failed to create contact' };
|
|
1873
2035
|
}
|
|
1874
2036
|
},
|
|
2037
|
+
async contact_update(params) {
|
|
2038
|
+
const { contact_id, ...updates } = params;
|
|
2039
|
+
if (!contact_id)
|
|
2040
|
+
return { success: false, error: 'contact_id is required' };
|
|
2041
|
+
try {
|
|
2042
|
+
const body = { namespace: getStoreNamespace(params) };
|
|
2043
|
+
for (const [k, v] of Object.entries(updates)) {
|
|
2044
|
+
if (k !== 'namespace' && v !== undefined)
|
|
2045
|
+
body[k] = v;
|
|
2046
|
+
}
|
|
2047
|
+
const response = await apiClient.patch(`/api/contacts/${contact_id}`, body, reqOpts());
|
|
2048
|
+
if (!response.success) {
|
|
2049
|
+
return { success: false, error: response.error.message };
|
|
2050
|
+
}
|
|
2051
|
+
return {
|
|
2052
|
+
success: true,
|
|
2053
|
+
data: {
|
|
2054
|
+
content: `Contact ${contact_id} updated successfully`,
|
|
2055
|
+
details: { id: contact_id, display_name: response.data.display_name },
|
|
2056
|
+
},
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
catch (error) {
|
|
2060
|
+
logger.error('contact_update failed', { error });
|
|
2061
|
+
return { success: false, error: 'Failed to update contact' };
|
|
2062
|
+
}
|
|
2063
|
+
},
|
|
2064
|
+
async contact_merge(params) {
|
|
2065
|
+
const { survivor_id, loser_id } = params;
|
|
2066
|
+
if (!survivor_id || !loser_id)
|
|
2067
|
+
return { success: false, error: 'survivor_id and loser_id are required' };
|
|
2068
|
+
try {
|
|
2069
|
+
const response = await apiClient.post('/api/contacts/merge', { survivor_id, loser_id, namespace: getStoreNamespace(params) }, reqOpts());
|
|
2070
|
+
if (!response.success) {
|
|
2071
|
+
return { success: false, error: response.error.message };
|
|
2072
|
+
}
|
|
2073
|
+
return {
|
|
2074
|
+
success: true,
|
|
2075
|
+
data: {
|
|
2076
|
+
content: `Contacts merged successfully. Survivor: ${survivor_id}`,
|
|
2077
|
+
details: response.data,
|
|
2078
|
+
},
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
catch (error) {
|
|
2082
|
+
logger.error('contact_merge failed', { error });
|
|
2083
|
+
return { success: false, error: 'Failed to merge contacts' };
|
|
2084
|
+
}
|
|
2085
|
+
},
|
|
2086
|
+
async contact_tag_add(params) {
|
|
2087
|
+
const { contact_id, tags } = params;
|
|
2088
|
+
if (!contact_id || !tags?.length)
|
|
2089
|
+
return { success: false, error: 'contact_id and tags are required' };
|
|
2090
|
+
try {
|
|
2091
|
+
const response = await apiClient.post(`/api/contacts/${contact_id}/tags`, { tags }, reqOpts());
|
|
2092
|
+
if (!response.success) {
|
|
2093
|
+
return { success: false, error: response.error.message };
|
|
2094
|
+
}
|
|
2095
|
+
return {
|
|
2096
|
+
success: true,
|
|
2097
|
+
data: {
|
|
2098
|
+
content: `Added ${tags.length} tag(s) to contact ${contact_id}: ${tags.join(', ')}`,
|
|
2099
|
+
details: { contact_id, tags },
|
|
2100
|
+
},
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
catch (error) {
|
|
2104
|
+
logger.error('contact_tag_add failed', { error });
|
|
2105
|
+
return { success: false, error: 'Failed to add tags' };
|
|
2106
|
+
}
|
|
2107
|
+
},
|
|
2108
|
+
async contact_tag_remove(params) {
|
|
2109
|
+
const { contact_id, tag } = params;
|
|
2110
|
+
if (!contact_id || !tag)
|
|
2111
|
+
return { success: false, error: 'contact_id and tag are required' };
|
|
2112
|
+
try {
|
|
2113
|
+
const response = await apiClient.delete(`/api/contacts/${contact_id}/tags/${encodeURIComponent(tag)}`, reqOpts());
|
|
2114
|
+
if (!response.success) {
|
|
2115
|
+
return { success: false, error: response.error.message };
|
|
2116
|
+
}
|
|
2117
|
+
return {
|
|
2118
|
+
success: true,
|
|
2119
|
+
data: {
|
|
2120
|
+
content: `Removed tag "${tag}" from contact ${contact_id}`,
|
|
2121
|
+
details: { contact_id, tag },
|
|
2122
|
+
},
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
catch (error) {
|
|
2126
|
+
logger.error('contact_tag_remove failed', { error });
|
|
2127
|
+
return { success: false, error: 'Failed to remove tag' };
|
|
2128
|
+
}
|
|
2129
|
+
},
|
|
2130
|
+
async contact_resolve(params) {
|
|
2131
|
+
const { phone, email, name } = params;
|
|
2132
|
+
if (!phone && !email && !name) {
|
|
2133
|
+
return { success: false, error: 'At least one of phone, email, or name is required' };
|
|
2134
|
+
}
|
|
2135
|
+
try {
|
|
2136
|
+
const queryParams = new URLSearchParams();
|
|
2137
|
+
if (phone)
|
|
2138
|
+
queryParams.set('phone', phone);
|
|
2139
|
+
if (email)
|
|
2140
|
+
queryParams.set('email', email);
|
|
2141
|
+
if (name)
|
|
2142
|
+
queryParams.set('name', name);
|
|
2143
|
+
const response = await apiClient.get(`/api/contacts/suggest-match?${queryParams}`, reqOpts());
|
|
2144
|
+
if (!response.success) {
|
|
2145
|
+
return { success: false, error: response.error.message };
|
|
2146
|
+
}
|
|
2147
|
+
const matches = response.data.matches ?? [];
|
|
2148
|
+
if (matches.length === 0) {
|
|
2149
|
+
return {
|
|
2150
|
+
success: true,
|
|
2151
|
+
data: {
|
|
2152
|
+
content: 'No matching contacts found for the provided sender information.',
|
|
2153
|
+
details: { matches: [], resolved: false },
|
|
2154
|
+
},
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
2157
|
+
const best = matches[0];
|
|
2158
|
+
const content = matches
|
|
2159
|
+
.map((m) => `- ${m.display_name} (${Math.round(m.confidence * 100)}% match, ID: ${m.contact_id})`)
|
|
2160
|
+
.join('\n');
|
|
2161
|
+
return {
|
|
2162
|
+
success: true,
|
|
2163
|
+
data: {
|
|
2164
|
+
content: `Found ${matches.length} matching contact(s):\n${content}`,
|
|
2165
|
+
details: {
|
|
2166
|
+
matches,
|
|
2167
|
+
resolved: best.confidence >= 0.8,
|
|
2168
|
+
best_match: best,
|
|
2169
|
+
},
|
|
2170
|
+
},
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
catch (error) {
|
|
2174
|
+
logger.error('contact_resolve failed', { error });
|
|
2175
|
+
return { success: false, error: 'Failed to resolve sender identity' };
|
|
2176
|
+
}
|
|
2177
|
+
},
|
|
1875
2178
|
async sms_send(params) {
|
|
1876
2179
|
const { to, body, idempotency_key } = params;
|
|
1877
2180
|
// Check Twilio configuration
|
|
@@ -1908,7 +2211,7 @@ function createToolHandlers(state) {
|
|
|
1908
2211
|
hasIdempotencyKey: !!idempotency_key,
|
|
1909
2212
|
});
|
|
1910
2213
|
try {
|
|
1911
|
-
const response = await apiClient.post('/api/twilio/sms/send', { to, body, idempotency_key },
|
|
2214
|
+
const response = await apiClient.post('/api/twilio/sms/send', { to, body, idempotency_key }, reqOpts());
|
|
1912
2215
|
if (!response.success) {
|
|
1913
2216
|
logger.error('sms_send API error', {
|
|
1914
2217
|
user_id,
|
|
@@ -1989,7 +2292,7 @@ function createToolHandlers(state) {
|
|
|
1989
2292
|
hasIdempotencyKey: !!idempotency_key,
|
|
1990
2293
|
});
|
|
1991
2294
|
try {
|
|
1992
|
-
const response = await apiClient.post('/api/postmark/email/send', { to, subject, body, html_body, thread_id, idempotency_key },
|
|
2295
|
+
const response = await apiClient.post('/api/postmark/email/send', { to, subject, body, html_body, thread_id, idempotency_key }, reqOpts());
|
|
1993
2296
|
if (!response.success) {
|
|
1994
2297
|
logger.error('email_send API error', {
|
|
1995
2298
|
user_id,
|
|
@@ -2064,7 +2367,7 @@ function createToolHandlers(state) {
|
|
|
2064
2367
|
queryParams.set('include_thread', 'true');
|
|
2065
2368
|
}
|
|
2066
2369
|
// Unified search API: returns { results: [{ type, id, title, snippet, score, metadata }], total }
|
|
2067
|
-
const response = await apiClient.get(`/api/search?${queryParams}`,
|
|
2370
|
+
const response = await apiClient.get(`/api/search?${queryParams}`, reqOpts());
|
|
2068
2371
|
if (!response.success) {
|
|
2069
2372
|
logger.error('message_search API error', {
|
|
2070
2373
|
user_id,
|
|
@@ -2177,7 +2480,7 @@ function createToolHandlers(state) {
|
|
|
2177
2480
|
if (contact_id) {
|
|
2178
2481
|
queryParams.set('contact_id', contact_id);
|
|
2179
2482
|
}
|
|
2180
|
-
const response = await apiClient.get(`/api/search?${queryParams}`,
|
|
2483
|
+
const response = await apiClient.get(`/api/search?${queryParams}`, reqOpts());
|
|
2181
2484
|
if (!response.success) {
|
|
2182
2485
|
logger.error('thread_list API error', {
|
|
2183
2486
|
user_id,
|
|
@@ -2254,7 +2557,7 @@ function createToolHandlers(state) {
|
|
|
2254
2557
|
try {
|
|
2255
2558
|
const queryParams = new URLSearchParams();
|
|
2256
2559
|
queryParams.set('limit', String(message_limit));
|
|
2257
|
-
const response = await apiClient.get(`/api/threads/${thread_id}/history?${queryParams}`,
|
|
2560
|
+
const response = await apiClient.get(`/api/threads/${thread_id}/history?${queryParams}`, reqOpts());
|
|
2258
2561
|
if (!response.success) {
|
|
2259
2562
|
logger.error('thread_get API error', {
|
|
2260
2563
|
user_id,
|
|
@@ -2362,7 +2665,7 @@ function createToolHandlers(state) {
|
|
|
2362
2665
|
if (notes) {
|
|
2363
2666
|
body.notes = notes;
|
|
2364
2667
|
}
|
|
2365
|
-
const response = await apiClient.post('/api/relationships/set', body,
|
|
2668
|
+
const response = await apiClient.post('/api/relationships/set', body, reqOpts());
|
|
2366
2669
|
if (!response.success) {
|
|
2367
2670
|
return { success: false, error: response.error.message };
|
|
2368
2671
|
}
|
|
@@ -2413,7 +2716,7 @@ function createToolHandlers(state) {
|
|
|
2413
2716
|
else {
|
|
2414
2717
|
// Search for contact by name (Issue #1172: scope by user_email)
|
|
2415
2718
|
const searchParams = new URLSearchParams({ search: contact, limit: '1', user_email: user_id });
|
|
2416
|
-
const searchResponse = await apiClient.get(`/api/contacts?${searchParams}`,
|
|
2719
|
+
const searchResponse = await apiClient.get(`/api/contacts?${searchParams}`, reqOpts());
|
|
2417
2720
|
if (!searchResponse.success) {
|
|
2418
2721
|
return { success: false, error: searchResponse.error.message };
|
|
2419
2722
|
}
|
|
@@ -2424,7 +2727,7 @@ function createToolHandlers(state) {
|
|
|
2424
2727
|
contact_id = contacts[0].id;
|
|
2425
2728
|
}
|
|
2426
2729
|
// Use graph traversal endpoint which returns related_contacts
|
|
2427
|
-
const response = await apiClient.get(`/api/contacts/${contact_id}/relationships?user_email=${encodeURIComponent(user_id)}`,
|
|
2730
|
+
const response = await apiClient.get(`/api/contacts/${contact_id}/relationships?user_email=${encodeURIComponent(user_id)}`, reqOpts());
|
|
2428
2731
|
if (!response.success) {
|
|
2429
2732
|
if (response.error.code === 'NOT_FOUND') {
|
|
2430
2733
|
return { success: false, error: 'Contact not found.' };
|
|
@@ -2492,7 +2795,7 @@ function createToolHandlers(state) {
|
|
|
2492
2795
|
if (maxDownloads !== undefined) {
|
|
2493
2796
|
body.max_downloads = maxDownloads;
|
|
2494
2797
|
}
|
|
2495
|
-
const response = await apiClient.post(`/api/files/${fileId}/share`, body,
|
|
2798
|
+
const response = await apiClient.post(`/api/files/${fileId}/share`, body, reqOpts());
|
|
2496
2799
|
if (!response.success) {
|
|
2497
2800
|
logger.error('file_share API error', {
|
|
2498
2801
|
user_id,
|
|
@@ -2604,7 +2907,7 @@ function createToolHandlers(state) {
|
|
|
2604
2907
|
const queryParams = new URLSearchParams({ limit: String(limit), offset: String(offset) });
|
|
2605
2908
|
if (channel_type)
|
|
2606
2909
|
queryParams.set('channel_type', channel_type);
|
|
2607
|
-
const response = await apiClient.get(`/api/prompt-templates?${queryParams.toString()}`,
|
|
2910
|
+
const response = await apiClient.get(`/api/prompt-templates?${queryParams.toString()}`, reqOpts());
|
|
2608
2911
|
if (!response.success) {
|
|
2609
2912
|
return { success: false, error: response.error.message || 'Failed to list prompt templates' };
|
|
2610
2913
|
}
|
|
@@ -2622,7 +2925,7 @@ function createToolHandlers(state) {
|
|
|
2622
2925
|
async prompt_template_get(params) {
|
|
2623
2926
|
const { id } = params;
|
|
2624
2927
|
try {
|
|
2625
|
-
const response = await apiClient.get(`/api/prompt-templates/${id}`,
|
|
2928
|
+
const response = await apiClient.get(`/api/prompt-templates/${id}`, reqOpts());
|
|
2626
2929
|
if (!response.success) {
|
|
2627
2930
|
return { success: false, error: response.error.message || 'Prompt template not found' };
|
|
2628
2931
|
}
|
|
@@ -2637,7 +2940,7 @@ function createToolHandlers(state) {
|
|
|
2637
2940
|
async prompt_template_create(params) {
|
|
2638
2941
|
const { label, content, channel_type, is_default } = params;
|
|
2639
2942
|
try {
|
|
2640
|
-
const response = await apiClient.post('/api/prompt-templates', { label, content, channel_type, is_default },
|
|
2943
|
+
const response = await apiClient.post('/api/prompt-templates', { label, content, channel_type, is_default }, reqOpts());
|
|
2641
2944
|
if (!response.success) {
|
|
2642
2945
|
return { success: false, error: response.error.message || 'Failed to create prompt template' };
|
|
2643
2946
|
}
|
|
@@ -2651,7 +2954,7 @@ function createToolHandlers(state) {
|
|
|
2651
2954
|
async prompt_template_update(params) {
|
|
2652
2955
|
const { id, ...updates } = params;
|
|
2653
2956
|
try {
|
|
2654
|
-
const response = await apiClient.put(`/api/prompt-templates/${id}`, updates,
|
|
2957
|
+
const response = await apiClient.put(`/api/prompt-templates/${id}`, updates, reqOpts());
|
|
2655
2958
|
if (!response.success) {
|
|
2656
2959
|
return { success: false, error: response.error.message || 'Failed to update prompt template' };
|
|
2657
2960
|
}
|
|
@@ -2665,7 +2968,7 @@ function createToolHandlers(state) {
|
|
|
2665
2968
|
async prompt_template_delete(params) {
|
|
2666
2969
|
const { id } = params;
|
|
2667
2970
|
try {
|
|
2668
|
-
const response = await apiClient.delete(`/api/prompt-templates/${id}`,
|
|
2971
|
+
const response = await apiClient.delete(`/api/prompt-templates/${id}`, reqOpts());
|
|
2669
2972
|
if (!response.success) {
|
|
2670
2973
|
return { success: false, error: response.error.message || 'Failed to delete prompt template' };
|
|
2671
2974
|
}
|
|
@@ -2689,7 +2992,7 @@ function createToolHandlers(state) {
|
|
|
2689
2992
|
queryParams.set('limit', String(limit));
|
|
2690
2993
|
if (offset !== undefined)
|
|
2691
2994
|
queryParams.set('offset', String(offset));
|
|
2692
|
-
const response = await apiClient.get(`/api/inbound-destinations?${queryParams.toString()}`,
|
|
2995
|
+
const response = await apiClient.get(`/api/inbound-destinations?${queryParams.toString()}`, reqOpts());
|
|
2693
2996
|
if (!response.success) {
|
|
2694
2997
|
return { success: false, error: response.error.message || 'Failed to list inbound destinations' };
|
|
2695
2998
|
}
|
|
@@ -2707,7 +3010,7 @@ function createToolHandlers(state) {
|
|
|
2707
3010
|
async inbound_destination_get(params) {
|
|
2708
3011
|
const { id } = params;
|
|
2709
3012
|
try {
|
|
2710
|
-
const response = await apiClient.get(`/api/inbound-destinations/${id}`,
|
|
3013
|
+
const response = await apiClient.get(`/api/inbound-destinations/${id}`, reqOpts());
|
|
2711
3014
|
if (!response.success) {
|
|
2712
3015
|
return { success: false, error: response.error.message || 'Inbound destination not found' };
|
|
2713
3016
|
}
|
|
@@ -2731,7 +3034,7 @@ function createToolHandlers(state) {
|
|
|
2731
3034
|
async inbound_destination_update(params) {
|
|
2732
3035
|
const { id, ...updates } = params;
|
|
2733
3036
|
try {
|
|
2734
|
-
const response = await apiClient.put(`/api/inbound-destinations/${id}`, updates,
|
|
3037
|
+
const response = await apiClient.put(`/api/inbound-destinations/${id}`, updates, reqOpts());
|
|
2735
3038
|
if (!response.success) {
|
|
2736
3039
|
return { success: false, error: response.error.message || 'Failed to update inbound destination' };
|
|
2737
3040
|
}
|
|
@@ -2745,7 +3048,7 @@ function createToolHandlers(state) {
|
|
|
2745
3048
|
// ── Channel Default tools (Issue #1501) ──────────────────
|
|
2746
3049
|
async channel_default_list() {
|
|
2747
3050
|
try {
|
|
2748
|
-
const response = await apiClient.get('/api/channel-defaults',
|
|
3051
|
+
const response = await apiClient.get('/api/channel-defaults', reqOpts());
|
|
2749
3052
|
if (!response.success) {
|
|
2750
3053
|
return { success: false, error: response.error.message || 'Failed to list channel defaults' };
|
|
2751
3054
|
}
|
|
@@ -2763,7 +3066,7 @@ function createToolHandlers(state) {
|
|
|
2763
3066
|
async channel_default_get(params) {
|
|
2764
3067
|
const { channel_type } = params;
|
|
2765
3068
|
try {
|
|
2766
|
-
const response = await apiClient.get(`/api/channel-defaults/${channel_type}`,
|
|
3069
|
+
const response = await apiClient.get(`/api/channel-defaults/${channel_type}`, reqOpts());
|
|
2767
3070
|
if (!response.success) {
|
|
2768
3071
|
return { success: false, error: response.error.message || 'Channel default not found' };
|
|
2769
3072
|
}
|
|
@@ -2783,7 +3086,7 @@ function createToolHandlers(state) {
|
|
|
2783
3086
|
async channel_default_set(params) {
|
|
2784
3087
|
const { channel_type, agent_id, prompt_template_id, context_id } = params;
|
|
2785
3088
|
try {
|
|
2786
|
-
const response = await apiClient.put(`/api/channel-defaults/${channel_type}`, { agent_id, prompt_template_id, context_id },
|
|
3089
|
+
const response = await apiClient.put(`/api/channel-defaults/${channel_type}`, { agent_id, prompt_template_id, context_id }, reqOpts());
|
|
2787
3090
|
if (!response.success) {
|
|
2788
3091
|
return { success: false, error: response.error.message || 'Failed to set channel default' };
|
|
2789
3092
|
}
|
|
@@ -2797,7 +3100,7 @@ function createToolHandlers(state) {
|
|
|
2797
3100
|
// ── Namespace management handlers (Issue #1536) ──────────────
|
|
2798
3101
|
async namespace_list() {
|
|
2799
3102
|
try {
|
|
2800
|
-
const response = await apiClient.get('/api/namespaces',
|
|
3103
|
+
const response = await apiClient.get('/api/namespaces', reqOpts());
|
|
2801
3104
|
if (!response.success) {
|
|
2802
3105
|
return { success: false, error: response.error.message || 'Failed to list namespaces' };
|
|
2803
3106
|
}
|
|
@@ -2827,7 +3130,7 @@ function createToolHandlers(state) {
|
|
|
2827
3130
|
async namespace_create(params) {
|
|
2828
3131
|
const { name } = params;
|
|
2829
3132
|
try {
|
|
2830
|
-
const response = await apiClient.post('/api/namespaces', { name },
|
|
3133
|
+
const response = await apiClient.post('/api/namespaces', { name }, reqOpts());
|
|
2831
3134
|
if (!response.success) {
|
|
2832
3135
|
return { success: false, error: response.error.message || 'Failed to create namespace' };
|
|
2833
3136
|
}
|
|
@@ -2841,7 +3144,7 @@ function createToolHandlers(state) {
|
|
|
2841
3144
|
async namespace_grant(params) {
|
|
2842
3145
|
const { namespace, email, role, is_default } = params;
|
|
2843
3146
|
try {
|
|
2844
|
-
const response = await apiClient.post(`/api/namespaces/${encodeURIComponent(namespace)}/grants`, { email, role: role || 'member', is_default: is_default ?? false },
|
|
3147
|
+
const response = await apiClient.post(`/api/namespaces/${encodeURIComponent(namespace)}/grants`, { email, role: role || 'member', is_default: is_default ?? false }, reqOpts());
|
|
2845
3148
|
if (!response.success) {
|
|
2846
3149
|
return { success: false, error: response.error.message || 'Failed to grant namespace access' };
|
|
2847
3150
|
}
|
|
@@ -2856,7 +3159,7 @@ function createToolHandlers(state) {
|
|
|
2856
3159
|
async namespace_members(params) {
|
|
2857
3160
|
const { namespace } = params;
|
|
2858
3161
|
try {
|
|
2859
|
-
const response = await apiClient.get(`/api/namespaces/${encodeURIComponent(namespace)}`,
|
|
3162
|
+
const response = await apiClient.get(`/api/namespaces/${encodeURIComponent(namespace)}`, reqOpts());
|
|
2860
3163
|
if (!response.success) {
|
|
2861
3164
|
return { success: false, error: response.error.message || 'Failed to list namespace members' };
|
|
2862
3165
|
}
|
|
@@ -2875,7 +3178,7 @@ function createToolHandlers(state) {
|
|
|
2875
3178
|
async namespace_revoke(params) {
|
|
2876
3179
|
const { namespace, grant_id } = params;
|
|
2877
3180
|
try {
|
|
2878
|
-
const response = await apiClient.delete(`/api/namespaces/${encodeURIComponent(namespace)}/grants/${encodeURIComponent(grant_id)}`,
|
|
3181
|
+
const response = await apiClient.delete(`/api/namespaces/${encodeURIComponent(namespace)}/grants/${encodeURIComponent(grant_id)}`, reqOpts());
|
|
2879
3182
|
if (!response.success) {
|
|
2880
3183
|
return { success: false, error: response.error.message || 'Failed to revoke namespace access' };
|
|
2881
3184
|
}
|
|
@@ -2945,8 +3248,12 @@ export const registerOpenClaw = (api) => {
|
|
|
2945
3248
|
recallNamespaces: resolvedNamespace.recall,
|
|
2946
3249
|
hasStaticRecall,
|
|
2947
3250
|
});
|
|
3251
|
+
// Extract user email from runtime context for identity resolution (#1567).
|
|
3252
|
+
// The agent ID (user_id) may be a short name like "troy" which doesn't match
|
|
3253
|
+
// user_setting.email. The email is needed for FK-constrained operations.
|
|
3254
|
+
const user_email = context.user?.email;
|
|
2948
3255
|
// Store plugin state
|
|
2949
|
-
const state = { config, logger, apiClient, user_id, resolvedNamespace, hasStaticRecall, lastNamespaceRefreshMs: 0, refreshInFlight: false };
|
|
3256
|
+
const state = { config, logger, apiClient, user_id, user_email, resolvedNamespace, hasStaticRecall, lastNamespaceRefreshMs: 0, refreshInFlight: false };
|
|
2950
3257
|
// Create tool handlers
|
|
2951
3258
|
const handlers = createToolHandlers(state);
|
|
2952
3259
|
// Register all 30 tools with correct OpenClaw Gateway execute signature
|
|
@@ -3080,13 +3387,58 @@ export const registerOpenClaw = (api) => {
|
|
|
3080
3387
|
},
|
|
3081
3388
|
{
|
|
3082
3389
|
name: 'contact_create',
|
|
3083
|
-
description: 'Create a new contact.
|
|
3390
|
+
description: 'Create a new contact. Supports structured names (given_name, family_name) or display_name. Optionally include email, phone, tags.',
|
|
3084
3391
|
parameters: withNamespace(contactCreateSchema),
|
|
3085
3392
|
execute: async (_toolCallId, params, _signal, _onUpdate) => {
|
|
3086
3393
|
const result = await handlers.contact_create(params);
|
|
3087
3394
|
return toAgentToolResult(result);
|
|
3088
3395
|
},
|
|
3089
3396
|
},
|
|
3397
|
+
{
|
|
3398
|
+
name: 'contact_update',
|
|
3399
|
+
description: 'Update an existing contact. Can change name, notes, tags, and other fields.',
|
|
3400
|
+
parameters: withNamespace(contactUpdateSchema),
|
|
3401
|
+
execute: async (_toolCallId, params, _signal, _onUpdate) => {
|
|
3402
|
+
const result = await handlers.contact_update(params);
|
|
3403
|
+
return toAgentToolResult(result);
|
|
3404
|
+
},
|
|
3405
|
+
},
|
|
3406
|
+
{
|
|
3407
|
+
name: 'contact_merge',
|
|
3408
|
+
description: 'Merge two contacts into one. The survivor keeps all data; the loser is soft-deleted. Use when duplicate contacts are detected.',
|
|
3409
|
+
parameters: withNamespace(contactMergeSchema),
|
|
3410
|
+
execute: async (_toolCallId, params, _signal, _onUpdate) => {
|
|
3411
|
+
const result = await handlers.contact_merge(params);
|
|
3412
|
+
return toAgentToolResult(result);
|
|
3413
|
+
},
|
|
3414
|
+
},
|
|
3415
|
+
{
|
|
3416
|
+
name: 'contact_tag_add',
|
|
3417
|
+
description: 'Add tags to a contact for categorization.',
|
|
3418
|
+
parameters: withNamespace(contactTagAddSchema),
|
|
3419
|
+
execute: async (_toolCallId, params, _signal, _onUpdate) => {
|
|
3420
|
+
const result = await handlers.contact_tag_add(params);
|
|
3421
|
+
return toAgentToolResult(result);
|
|
3422
|
+
},
|
|
3423
|
+
},
|
|
3424
|
+
{
|
|
3425
|
+
name: 'contact_tag_remove',
|
|
3426
|
+
description: 'Remove a tag from a contact.',
|
|
3427
|
+
parameters: withNamespace(contactTagRemoveSchema),
|
|
3428
|
+
execute: async (_toolCallId, params, _signal, _onUpdate) => {
|
|
3429
|
+
const result = await handlers.contact_tag_remove(params);
|
|
3430
|
+
return toAgentToolResult(result);
|
|
3431
|
+
},
|
|
3432
|
+
},
|
|
3433
|
+
{
|
|
3434
|
+
name: 'contact_resolve',
|
|
3435
|
+
description: 'Resolve a sender identity (phone, email, or name) to an existing contact. Use when an inbound message arrives and you need to identify who sent it.',
|
|
3436
|
+
parameters: withNamespaces(contactResolveSchema),
|
|
3437
|
+
execute: async (_toolCallId, params, _signal, _onUpdate) => {
|
|
3438
|
+
const result = await handlers.contact_resolve(params);
|
|
3439
|
+
return toAgentToolResult(result);
|
|
3440
|
+
},
|
|
3441
|
+
},
|
|
3090
3442
|
{
|
|
3091
3443
|
name: 'sms_send',
|
|
3092
3444
|
description: 'Send an SMS message to a phone number. Use when you need to notify someone via text message. Requires the recipient phone number in E.164 format (e.g., +15551234567).',
|
|
@@ -3598,7 +3950,7 @@ export const registerOpenClaw = (api) => {
|
|
|
3598
3950
|
.description('Show plugin status and statistics')
|
|
3599
3951
|
.action(async () => {
|
|
3600
3952
|
try {
|
|
3601
|
-
const response = await apiClient.get('/api/health', { user_id });
|
|
3953
|
+
const response = await apiClient.get('/api/health', { user_id, user_email });
|
|
3602
3954
|
if (response.success) {
|
|
3603
3955
|
console.log('Plugin Status: Connected');
|
|
3604
3956
|
}
|
|
@@ -3670,6 +4022,11 @@ export const schemas = {
|
|
|
3670
4022
|
contactSearch: withNamespaces(contactSearchSchema),
|
|
3671
4023
|
contactGet: withNamespaces(contactGetSchema),
|
|
3672
4024
|
contactCreate: withNamespace(contactCreateSchema),
|
|
4025
|
+
contactUpdate: withNamespace(contactUpdateSchema),
|
|
4026
|
+
contactMerge: withNamespace(contactMergeSchema),
|
|
4027
|
+
contactTagAdd: withNamespace(contactTagAddSchema),
|
|
4028
|
+
contactTagRemove: withNamespace(contactTagRemoveSchema),
|
|
4029
|
+
contactResolve: withNamespaces(contactResolveSchema),
|
|
3673
4030
|
smsSend: smsSendSchema,
|
|
3674
4031
|
emailSend: emailSendSchema,
|
|
3675
4032
|
messageSearch: withNamespaces(messageSearchSchema),
|