@vellumai/assistant 0.4.41 → 0.4.42
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/package.json +1 -1
- package/src/bundler/app-compiler.ts +131 -103
- package/src/bundler/compiler-tools.ts +248 -0
- package/src/cli/contacts.ts +255 -6
- package/src/config/bundled-skills/app-builder/SKILL.md +17 -3
- package/src/config/bundled-skills/contacts/SKILL.md +80 -168
- package/src/config/skills.ts +37 -0
- package/src/daemon/handlers/apps.ts +28 -3
- package/src/daemon/main.ts +1 -0
|
@@ -5,13 +5,13 @@ user-invocable: true
|
|
|
5
5
|
metadata: { "vellum": { "emoji": "\ud83d\udc65" } }
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
Manage the user's contacts, relationship graph, access control (trusted contacts), and invite links. This skill covers contact CRUD with multi-channel tracking, controlling who can message the assistant through external channels (Telegram, SMS, voice), and creating/managing invite links that grant access.
|
|
8
|
+
Manage the user's contacts, relationship graph, access control (trusted contacts), and invite links. This skill covers contact CRUD with multi-channel tracking, controlling who can message the assistant through external channels (Telegram, SMS, voice), and creating/managing invite links that grant access.
|
|
9
9
|
|
|
10
10
|
## Prerequisites
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
12
|
+
- `assistant contacts` CLI commands call the service layer directly and do not require the assistant to be running.
|
|
13
|
+
- `assistant channels` and `assistant integrations` CLI commands (e.g. `assistant channels readiness`, `assistant integrations telegram config`) require the assistant to be running because they call the gateway.
|
|
14
|
+
- All CLI commands support `--json` for machine-readable output.
|
|
15
15
|
|
|
16
16
|
## Contact Management
|
|
17
17
|
|
|
@@ -20,49 +20,37 @@ Manage the user's contacts, relationship graph, access control (trusted contacts
|
|
|
20
20
|
Create a new contact or update an existing one in the relationship graph. Use this to track people the user interacts with across channels.
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
|
|
24
|
-
-H "Content-Type: application/json" \
|
|
25
|
-
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
26
|
-
-d '{
|
|
27
|
-
"displayName": "<name>",
|
|
28
|
-
"notes": "<free-text notes about this contact>",
|
|
29
|
-
"channels": [
|
|
30
|
-
{
|
|
31
|
-
"type": "<channel_type>",
|
|
32
|
-
"address": "<address>",
|
|
33
|
-
"isPrimary": true
|
|
34
|
-
}
|
|
35
|
-
]
|
|
36
|
-
}'
|
|
23
|
+
assistant contacts upsert --display-name "<name>" --notes "<notes>" --channels '<json_array>' --json
|
|
37
24
|
```
|
|
38
25
|
|
|
39
|
-
To update an existing contact, include the
|
|
26
|
+
To update an existing contact, include the `--id` flag.
|
|
40
27
|
|
|
41
|
-
Required
|
|
28
|
+
Required flags:
|
|
42
29
|
|
|
43
|
-
- `
|
|
30
|
+
- `--display-name` -- the contact's name
|
|
44
31
|
|
|
45
|
-
Optional
|
|
32
|
+
Optional flags:
|
|
46
33
|
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
- `
|
|
34
|
+
- `--id` -- contact ID to update (omit to create new, or auto-match by channel address)
|
|
35
|
+
- `--notes` -- free-text notes about this contact (e.g. relationship, communication preferences, response expectations)
|
|
36
|
+
- `--role` -- contact role: `contact` or `guardian` (default: `contact`)
|
|
37
|
+
- `--contact-type` -- contact type: `human` or `assistant` (default: `human`)
|
|
38
|
+
- `--channels` -- JSON array of channel objects, each with `type`, `address`, and optional `isPrimary`, `externalUserId`, `externalChatId`, `status`, `policy`
|
|
50
39
|
|
|
51
40
|
### Search contacts
|
|
52
41
|
|
|
53
|
-
Search for contacts by name, channel address, or other criteria
|
|
42
|
+
Search for contacts by name, channel address, or other criteria.
|
|
54
43
|
|
|
55
44
|
```bash
|
|
56
|
-
|
|
57
|
-
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
|
|
45
|
+
assistant contacts list --query "<search_term>" --json
|
|
58
46
|
```
|
|
59
47
|
|
|
60
|
-
Optional
|
|
48
|
+
Optional flags:
|
|
61
49
|
|
|
62
|
-
-
|
|
63
|
-
- `
|
|
64
|
-
- `
|
|
65
|
-
-
|
|
50
|
+
- `--query` -- search by display name (partial match)
|
|
51
|
+
- `--channel-address` -- search by channel address (email, phone, handle)
|
|
52
|
+
- `--channel-type` -- filter by channel type when searching by address
|
|
53
|
+
- `--limit` -- maximum results to return (default 50, max 100)
|
|
66
54
|
|
|
67
55
|
### Merge contacts
|
|
68
56
|
|
|
@@ -74,13 +62,7 @@ When you discover two contacts are the same person (e.g. same person on email an
|
|
|
74
62
|
- Deletes the donor contact
|
|
75
63
|
|
|
76
64
|
```bash
|
|
77
|
-
|
|
78
|
-
-H "Content-Type: application/json" \
|
|
79
|
-
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
80
|
-
-d '{
|
|
81
|
-
"keepId": "<surviving_contact_id>",
|
|
82
|
-
"mergeId": "<donor_contact_id>"
|
|
83
|
-
}'
|
|
65
|
+
assistant contacts merge <surviving_contact_id> <donor_contact_id> --json
|
|
84
66
|
```
|
|
85
67
|
|
|
86
68
|
## Access Control (Trusted Contacts)
|
|
@@ -139,25 +121,13 @@ Use this when the user wants to grant someone access to message the assistant. *
|
|
|
139
121
|
Ask the user: _"I'll add [name/identifier] on [channel] as an allowed contact. Should I proceed?"_
|
|
140
122
|
|
|
141
123
|
```bash
|
|
142
|
-
|
|
143
|
-
-H "Content-Type: application/json" \
|
|
144
|
-
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
145
|
-
-d '{
|
|
146
|
-
"displayName": "<display_name>",
|
|
147
|
-
"channels": [{
|
|
148
|
-
"type": "<channel>",
|
|
149
|
-
"address": "<user_id>",
|
|
150
|
-
"externalUserId": "<user_id>",
|
|
151
|
-
"status": "active",
|
|
152
|
-
"policy": "allow"
|
|
153
|
-
}]
|
|
154
|
-
}'
|
|
124
|
+
assistant contacts upsert --display-name "<display_name>" --channels '[{"type":"<channel>","address":"<user_id>","externalUserId":"<user_id>","status":"active","policy":"allow"}]' --json
|
|
155
125
|
```
|
|
156
126
|
|
|
157
|
-
Required
|
|
127
|
+
Required flags:
|
|
158
128
|
|
|
159
|
-
- `
|
|
160
|
-
-
|
|
129
|
+
- `--display-name` -- human-readable name for the contact
|
|
130
|
+
- `--channels` -- at least one channel entry with:
|
|
161
131
|
- `type` -- the channel type (e.g., `telegram`, `sms`)
|
|
162
132
|
- `address` -- the channel-specific identifier
|
|
163
133
|
- `externalUserId` -- the user's ID on that channel (or `externalChatId` for chat-based channels)
|
|
@@ -172,18 +142,15 @@ Use this when the user wants to remove someone's access. **Always confirm with t
|
|
|
172
142
|
|
|
173
143
|
Ask the user: _"I'll revoke access for [name/identifier]. They will no longer be able to message the assistant. Should I proceed?"_
|
|
174
144
|
|
|
175
|
-
First, list contacts to find the channel's `id` (each entry in a contact's `channels` array has an `id` field -- visible in `
|
|
145
|
+
First, list contacts to find the channel's `id` (each entry in a contact's `channels` array has an `id` field -- visible in `assistant contacts list --json` output), then revoke:
|
|
176
146
|
|
|
177
147
|
**Important**: Before revoking, check the channel's current `status`. If the channel is **blocked**, do not attempt to revoke it -- blocking is stronger than revoking. Inform the user that the contact is already blocked and revoking is not applicable. Only channels with `active` or `pending` status can be revoked.
|
|
178
148
|
|
|
179
149
|
```bash
|
|
180
|
-
|
|
181
|
-
-H "Content-Type: application/json" \
|
|
182
|
-
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
183
|
-
-d '{"status": "revoked", "reason": "<optional reason>"}'
|
|
150
|
+
assistant contacts channels update-status <channel_id> --status revoked --reason "<optional reason>" --json
|
|
184
151
|
```
|
|
185
152
|
|
|
186
|
-
Replace `<channel_id>` with the channel's `id` from the contact's `channels` array.
|
|
153
|
+
Replace `<channel_id>` with the channel's `id` from the contact's `channels` array.
|
|
187
154
|
|
|
188
155
|
### Block a user
|
|
189
156
|
|
|
@@ -192,13 +159,10 @@ Use this when the user wants to explicitly block someone. Blocking is stronger t
|
|
|
192
159
|
Ask the user: _"I'll block [name/identifier]. They will be permanently denied from messaging the assistant. Should I proceed?"_
|
|
193
160
|
|
|
194
161
|
```bash
|
|
195
|
-
|
|
196
|
-
-H "Content-Type: application/json" \
|
|
197
|
-
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
198
|
-
-d '{"status": "blocked", "reason": "<optional reason>"}'
|
|
162
|
+
assistant contacts channels update-status <channel_id> --status blocked --reason "<optional reason>" --json
|
|
199
163
|
```
|
|
200
164
|
|
|
201
|
-
Replace `<channel_id>` with the channel's `id` from the contact's `channels` array (visible in `
|
|
165
|
+
Replace `<channel_id>` with the channel's `id` from the contact's `channels` array (visible in `assistant contacts list --json` output).
|
|
202
166
|
|
|
203
167
|
## Channel Readiness
|
|
204
168
|
|
|
@@ -232,14 +196,7 @@ Use this when the guardian wants to invite someone to message the assistant on T
|
|
|
232
196
|
**Important**: The shell snippet below emits a `<vellum-sensitive-output>` directive containing the raw invite token. The tool executor automatically strips this directive and replaces the raw token with a placeholder so the LLM never sees it. The placeholder is resolved back to the real token in the final assistant reply.
|
|
233
197
|
|
|
234
198
|
```bash
|
|
235
|
-
INVITE_JSON=$(
|
|
236
|
-
-H "Content-Type: application/json" \
|
|
237
|
-
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
238
|
-
-d '{
|
|
239
|
-
"sourceChannel": "telegram",
|
|
240
|
-
"maxUses": 1,
|
|
241
|
-
"note": "<optional note, e.g. the person it is for>"
|
|
242
|
-
}')
|
|
199
|
+
INVITE_JSON=$(assistant contacts invites create --source-channel telegram --max-uses 1 --note "<optional note, e.g. the person it is for>" --json)
|
|
243
200
|
|
|
244
201
|
INVITE_TOKEN=$(printf '%s' "$INVITE_JSON" | python3 -c "
|
|
245
202
|
import json, sys
|
|
@@ -263,7 +220,11 @@ fi
|
|
|
263
220
|
# Prefer backend-provided canonical link when available.
|
|
264
221
|
if [ -z "$INVITE_URL" ]; then
|
|
265
222
|
BOT_CONFIG_JSON=$(assistant integrations telegram config --json)
|
|
266
|
-
BOT_USERNAME=$(printf '%s' "$BOT_CONFIG_JSON" |
|
|
223
|
+
BOT_USERNAME=$(printf '%s' "$BOT_CONFIG_JSON" | python3 -c "
|
|
224
|
+
import json, sys
|
|
225
|
+
data = json.load(sys.stdin)
|
|
226
|
+
print(data.get('botUsername', ''), end='')
|
|
227
|
+
")
|
|
267
228
|
if [ -z "$BOT_USERNAME" ]; then
|
|
268
229
|
echo "error:no_share_url_or_bot_username"
|
|
269
230
|
exit 1
|
|
@@ -275,11 +236,11 @@ echo "<vellum-sensitive-output kind=\"invite_code\" value=\"$INVITE_TOKEN\" />"
|
|
|
275
236
|
echo "$INVITE_URL"
|
|
276
237
|
```
|
|
277
238
|
|
|
278
|
-
Optional
|
|
239
|
+
Optional flags:
|
|
279
240
|
|
|
280
|
-
- `
|
|
281
|
-
- `
|
|
282
|
-
-
|
|
241
|
+
- `--max-uses` -- how many times the link can be used (default: 1). Use a higher number for group invites.
|
|
242
|
+
- `--expires-in-ms` -- expiration time in milliseconds from now (e.g., `86400000` for 24 hours). Defaults to 7 days (`604800000`) if omitted.
|
|
243
|
+
- `--note` -- a human-readable label for the invite (e.g., "For Mom", "Family group").
|
|
283
244
|
|
|
284
245
|
The create response contains `{ ok: true, invite: { id, token, share?, ... } }`.
|
|
285
246
|
|
|
@@ -307,33 +268,21 @@ Use this when the guardian wants to authorize a specific phone number to call th
|
|
|
307
268
|
**Important**: The response includes a `voiceCode` field that is only returned at creation time and cannot be retrieved later. Extract and present it clearly.
|
|
308
269
|
|
|
309
270
|
```bash
|
|
310
|
-
|
|
311
|
-
-H "Content-Type: application/json" \
|
|
312
|
-
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
313
|
-
-d '{
|
|
314
|
-
"sourceChannel": "voice",
|
|
315
|
-
"expectedExternalUserId": "<phone_number_E164>",
|
|
316
|
-
"friendName": "<invitee display name>",
|
|
317
|
-
"guardianName": "<guardian display name>",
|
|
318
|
-
"maxUses": 1,
|
|
319
|
-
"note": "<optional note, e.g. the person it is for>"
|
|
320
|
-
}')
|
|
321
|
-
printf '%s\n' "$INVITE_JSON"
|
|
271
|
+
assistant contacts invites create --source-channel voice --expected-external-user-id "<phone_E164>" --friend-name "<invitee_name>" --guardian-name "<guardian_name>" --max-uses 1 --note "<optional note, e.g. the person it is for>" --json
|
|
322
272
|
```
|
|
323
273
|
|
|
324
|
-
Required
|
|
274
|
+
Required flags:
|
|
325
275
|
|
|
326
|
-
- `
|
|
327
|
-
- `
|
|
328
|
-
- `
|
|
329
|
-
- `
|
|
276
|
+
- `--source-channel` -- must be `voice`
|
|
277
|
+
- `--expected-external-user-id` -- the invitee's phone number in E.164 format (e.g., `+15551234567`)
|
|
278
|
+
- `--friend-name` -- the invitee's display name (e.g., "Mom", "Dr. Smith"). Used during the voice verification call to personalize the experience.
|
|
279
|
+
- `--guardian-name` -- the guardian's display name (e.g., "Alex"). Used during the voice verification call so the invitee knows who invited them.
|
|
330
280
|
|
|
331
|
-
Optional
|
|
281
|
+
Optional flags:
|
|
332
282
|
|
|
333
|
-
- `
|
|
334
|
-
- `
|
|
335
|
-
-
|
|
336
|
-
- `note` -- a human-readable label for the invite (e.g., "For Mom", "Dr. Smith")
|
|
283
|
+
- `--max-uses` -- how many times the code can be used (default: 1)
|
|
284
|
+
- `--expires-in-ms` -- expiration time in milliseconds from now (e.g., `86400000` for 24 hours). Defaults to 7 days if omitted.
|
|
285
|
+
- `--note` -- a human-readable label for the invite (e.g., "For Mom", "Dr. Smith")
|
|
337
286
|
|
|
338
287
|
The create response contains `{ ok: true, invite: { id, voiceCode, expectedExternalUserId, friendName, guardianName, ... } }`.
|
|
339
288
|
|
|
@@ -366,16 +315,7 @@ Use this when the guardian wants to invite someone to message the assistant via
|
|
|
366
315
|
**Before creating the invite**, check channel readiness (see [Channel Readiness](#channel-readiness)). If the `email` channel is not ready, tell the guardian what prerequisites are missing instead of creating an unusable invite.
|
|
367
316
|
|
|
368
317
|
```bash
|
|
369
|
-
|
|
370
|
-
-H "Content-Type: application/json" \
|
|
371
|
-
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
372
|
-
-d '{
|
|
373
|
-
"sourceChannel": "email",
|
|
374
|
-
"contactName": "<invitee display name>",
|
|
375
|
-
"maxUses": 1,
|
|
376
|
-
"note": "<optional note, e.g. the person it is for>"
|
|
377
|
-
}')
|
|
378
|
-
printf '%s\n' "$INVITE_JSON"
|
|
318
|
+
assistant contacts invites create --source-channel email --contact-name "<invitee_name>" --max-uses 1 --note "<optional note, e.g. the person it is for>" --json
|
|
379
319
|
```
|
|
380
320
|
|
|
381
321
|
The response contains `{ ok: true, invite: { id, token, inviteCode, guardianInstruction, channelHandle, ... } }`.
|
|
@@ -403,16 +343,7 @@ Use this when the guardian wants to invite someone to message the assistant on W
|
|
|
403
343
|
**Before creating the invite**, check channel readiness (see [Channel Readiness](#channel-readiness)). If the `whatsapp` channel is not ready, tell the guardian what prerequisites are missing instead of creating an unusable invite.
|
|
404
344
|
|
|
405
345
|
```bash
|
|
406
|
-
|
|
407
|
-
-H "Content-Type: application/json" \
|
|
408
|
-
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
409
|
-
-d '{
|
|
410
|
-
"sourceChannel": "whatsapp",
|
|
411
|
-
"contactName": "<invitee display name>",
|
|
412
|
-
"maxUses": 1,
|
|
413
|
-
"note": "<optional note, e.g. the person it is for>"
|
|
414
|
-
}')
|
|
415
|
-
printf '%s\n' "$INVITE_JSON"
|
|
346
|
+
assistant contacts invites create --source-channel whatsapp --contact-name "<invitee_name>" --max-uses 1 --note "<optional note, e.g. the person it is for>" --json
|
|
416
347
|
```
|
|
417
348
|
|
|
418
349
|
The response contains `{ ok: true, invite: { id, token, inviteCode, guardianInstruction, channelHandle?, ... } }`.
|
|
@@ -442,16 +373,7 @@ Use this when the guardian wants to invite someone to message the assistant via
|
|
|
442
373
|
**Before creating the invite**, check channel readiness (see [Channel Readiness](#channel-readiness)). If the `sms` channel is not ready, tell the guardian what prerequisites are missing instead of creating an unusable invite.
|
|
443
374
|
|
|
444
375
|
```bash
|
|
445
|
-
|
|
446
|
-
-H "Content-Type: application/json" \
|
|
447
|
-
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
448
|
-
-d '{
|
|
449
|
-
"sourceChannel": "sms",
|
|
450
|
-
"contactName": "<invitee display name>",
|
|
451
|
-
"maxUses": 1,
|
|
452
|
-
"note": "<optional note, e.g. the person it is for>"
|
|
453
|
-
}')
|
|
454
|
-
printf '%s\n' "$INVITE_JSON"
|
|
376
|
+
assistant contacts invites create --source-channel sms --contact-name "<invitee_name>" --max-uses 1 --note "<optional note, e.g. the person it is for>" --json
|
|
455
377
|
```
|
|
456
378
|
|
|
457
379
|
The response follows the same shape as email and WhatsApp invites (`inviteCode`, `guardianInstruction`, `channelHandle`).
|
|
@@ -465,16 +387,7 @@ Use this when the guardian wants to invite someone to message the assistant on S
|
|
|
465
387
|
**Before creating the invite**, check channel readiness (see [Channel Readiness](#channel-readiness)). If the `slack` channel is not ready, tell the guardian what prerequisites are missing instead of creating an unusable invite.
|
|
466
388
|
|
|
467
389
|
```bash
|
|
468
|
-
|
|
469
|
-
-H "Content-Type: application/json" \
|
|
470
|
-
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN" \
|
|
471
|
-
-d '{
|
|
472
|
-
"sourceChannel": "slack",
|
|
473
|
-
"contactName": "<invitee display name>",
|
|
474
|
-
"maxUses": 1,
|
|
475
|
-
"note": "<optional note, e.g. the person it is for>"
|
|
476
|
-
}')
|
|
477
|
-
printf '%s\n' "$INVITE_JSON"
|
|
390
|
+
assistant contacts invites create --source-channel slack --contact-name "<invitee_name>" --max-uses 1 --note "<optional note, e.g. the person it is for>" --json
|
|
478
391
|
```
|
|
479
392
|
|
|
480
393
|
The response follows the same shape as email, WhatsApp, and SMS invites (`inviteCode`, `guardianInstruction`, `channelHandle`).
|
|
@@ -548,11 +461,10 @@ Ask the user: _"I'll revoke the invite [note or ID]. It will no longer be usable
|
|
|
548
461
|
First, list invites to find the invite's `id`, then revoke:
|
|
549
462
|
|
|
550
463
|
```bash
|
|
551
|
-
|
|
552
|
-
-H "Authorization: Bearer $GATEWAY_AUTH_TOKEN"
|
|
464
|
+
assistant contacts invites revoke <invite_id> --json
|
|
553
465
|
```
|
|
554
466
|
|
|
555
|
-
Replace `<invite_id>` with the invite's `id` from the list response. The same revoke
|
|
467
|
+
Replace `<invite_id>` with the invite's `id` from the list response. The same revoke command is used for both Telegram and voice invites.
|
|
556
468
|
|
|
557
469
|
## Contact Fields
|
|
558
470
|
|
|
@@ -575,60 +487,60 @@ Each channel has:
|
|
|
575
487
|
**All mutating actions (allow, revoke, block, revoke invite) require explicit user confirmation before execution.** This is a safety measure -- modifying who can access the assistant should always be a deliberate choice. Creating an invite (Telegram link, voice invite, email invite, WhatsApp invite, SMS invite, or Slack invite) does not require confirmation since it does not grant access until the invitee redeems it.
|
|
576
488
|
|
|
577
489
|
- Clearly state what action you are about to take and who it affects.
|
|
578
|
-
- Wait for the user to confirm before running the
|
|
490
|
+
- Wait for the user to confirm before running the command.
|
|
579
491
|
- Report the result after execution.
|
|
580
492
|
|
|
581
493
|
## Error Handling
|
|
582
494
|
|
|
583
|
-
- If a
|
|
495
|
+
- If a command returns `{ ok: false, error: "..." }`, report the error message to the user. CLI commands also exit with non-zero status on errors.
|
|
584
496
|
- Common errors:
|
|
585
497
|
- `Channel not found` -- the channel ID may be invalid; list contacts to find the correct channel ID.
|
|
586
498
|
- `Channel already revoked` -- the channel has already been revoked.
|
|
587
499
|
- `Channel already blocked` -- the channel has already been blocked.
|
|
588
500
|
- `Cannot revoke a blocked channel` -- the channel is blocked; blocking is stronger than revoking. Tell the user the contact is already blocked.
|
|
589
|
-
- `sourceChannel is required for create` -- when creating an invite, always pass
|
|
590
|
-
- `expectedExternalUserId is required for voice invites` -- voice invites must include the invitee's phone number
|
|
501
|
+
- `sourceChannel is required for create` -- when creating an invite, always pass `--source-channel`.
|
|
502
|
+
- `expectedExternalUserId is required for voice invites` -- voice invites must include the invitee's phone number via `--expected-external-user-id`.
|
|
591
503
|
- `expectedExternalUserId must be in E.164 format` -- the phone number must start with `+` followed by country code and number (e.g., `+15551234567`).
|
|
592
|
-
- `friendName is required for voice invites` -- voice invites must include the invitee's display name
|
|
593
|
-
- `guardianName is required for voice invites` -- voice invites must include the guardian's display name
|
|
504
|
+
- `friendName is required for voice invites` -- voice invites must include the invitee's display name via `--friend-name`.
|
|
505
|
+
- `guardianName is required for voice invites` -- voice invites must include the guardian's display name via `--guardian-name`.
|
|
594
506
|
- `Invite not found or already revoked` -- the invite ID may be invalid or the invite is already revoked.
|
|
595
507
|
|
|
596
508
|
## Tips
|
|
597
509
|
|
|
598
|
-
- Use contact search with `
|
|
510
|
+
- Use contact search with `--channel-address` to find contacts by their email, phone, or handle.
|
|
599
511
|
- When creating follow-ups, provide a `contact_id` to link the follow-up to a specific contact.
|
|
600
512
|
- When merging contacts, the surviving contact gains all channels and merged notes from the donor.
|
|
601
513
|
|
|
602
514
|
## Typical Workflows
|
|
603
515
|
|
|
604
|
-
**"Who can message me?"** -- List all contacts
|
|
516
|
+
**"Who can message me?"** -- List all contacts with `assistant contacts list --json`, present active channels as a formatted list.
|
|
605
517
|
|
|
606
|
-
**"Add my friend to Telegram"** -- Ask for their Telegram user ID (numeric) and display name, confirm, then create a contact with a channel entry with `policy: "allow"` and `status: "active"`.
|
|
518
|
+
**"Add my friend to Telegram"** -- Ask for their Telegram user ID (numeric) and display name, confirm, then create a contact with `assistant contacts upsert` including a channel entry with `policy: "allow"` and `status: "active"`.
|
|
607
519
|
|
|
608
|
-
**"Remove [name]'s access"** -- List contacts to find them, identify the channel to revoke, confirm the revocation, then
|
|
520
|
+
**"Remove [name]'s access"** -- List contacts with `assistant contacts list --json` to find them, identify the channel to revoke, confirm the revocation, then run `assistant contacts channels update-status <channel_id> --status revoked --json`.
|
|
609
521
|
|
|
610
|
-
**"Block [name]"** -- List contacts to find them, identify the channel to block, confirm the block, then
|
|
522
|
+
**"Block [name]"** -- List contacts with `assistant contacts list --json` to find them, identify the channel to block, confirm the block, then run `assistant contacts channels update-status <channel_id> --status blocked --json`.
|
|
611
523
|
|
|
612
|
-
**"Show me blocked contacts"** -- List contacts and filter for channels with `status: "blocked"`.
|
|
524
|
+
**"Show me blocked contacts"** -- List contacts with `assistant contacts list --json` and filter for channels with `status: "blocked"`.
|
|
613
525
|
|
|
614
|
-
**"Create a Telegram invite link"** / **"Invite someone on Telegram"** -- Create an invite with `
|
|
526
|
+
**"Create a Telegram invite link"** / **"Invite someone on Telegram"** -- Create an invite with `assistant contacts invites create --source-channel telegram`, look up the bot username, build the deep link, and present it with sharing instructions.
|
|
615
527
|
|
|
616
|
-
**"Invite someone by email"** / **"Send an email invite"** -- Create an invite with `
|
|
528
|
+
**"Invite someone by email"** / **"Send an email invite"** -- Create an invite with `assistant contacts invites create --source-channel email`. Present the 6-digit invite code and the assistant's email address. Tell the guardian to share both with the invitee.
|
|
617
529
|
|
|
618
|
-
**"Invite someone on WhatsApp"** -- Create an invite with `
|
|
530
|
+
**"Invite someone on WhatsApp"** -- Create an invite with `assistant contacts invites create --source-channel whatsapp`. Present the 6-digit invite code. If `channelHandle` is returned, also present the assistant's WhatsApp number; otherwise, tell the guardian to share the code and instruct the invitee to send it to the assistant on WhatsApp.
|
|
619
531
|
|
|
620
|
-
**"Invite someone via SMS"** / **"Send a text invite"** -- Create an invite with `
|
|
532
|
+
**"Invite someone via SMS"** / **"Send a text invite"** -- Create an invite with `assistant contacts invites create --source-channel sms`. Present the 6-digit invite code and the assistant's phone number. Tell the guardian to share both with the invitee.
|
|
621
533
|
|
|
622
|
-
**"Invite someone on Slack"** -- Create an invite with `
|
|
534
|
+
**"Invite someone on Slack"** -- Create an invite with `assistant contacts invites create --source-channel slack`. Present the 6-digit invite code and tell the guardian to have the invitee DM the code to the assistant's Slack bot.
|
|
623
535
|
|
|
624
|
-
**"Show my invites"** / **"List active invite links"** -- List invites
|
|
536
|
+
**"Show my invites"** / **"List active invite links"** -- List invites with `assistant contacts invites list --source-channel telegram --json`, present active invites with uses remaining and expiration info. Use the appropriate `--source-channel` value for other channels.
|
|
625
537
|
|
|
626
|
-
**"Revoke invite"** / **"Cancel invite link"** -- List invites to identify the target, confirm, then revoke
|
|
538
|
+
**"Revoke invite"** / **"Cancel invite link"** -- List invites to identify the target, confirm, then revoke with `assistant contacts invites revoke <invite_id> --json`.
|
|
627
539
|
|
|
628
|
-
**"Create a voice invite for +15551234567"** -- Create a voice invite with `
|
|
540
|
+
**"Create a voice invite for +15551234567"** -- Create a voice invite with `assistant contacts invites create --source-channel voice --expected-external-user-id "+15551234567" --friend-name "<name>" --guardian-name "<name>"`. Present the invite code and instructions: the person must call from that number and enter the code.
|
|
629
541
|
|
|
630
|
-
**"Let my mom call in"** / **"Invite someone by phone"** -- Ask for the phone number in E.164 format, create a voice invite
|
|
542
|
+
**"Let my mom call in"** / **"Invite someone by phone"** -- Ask for the phone number in E.164 format, create a voice invite with `assistant contacts invites create --source-channel voice`, and present the code + calling instructions.
|
|
631
543
|
|
|
632
|
-
**"Show my voice invites"** / **"List phone invites"** -- List invites
|
|
544
|
+
**"Show my voice invites"** / **"List phone invites"** -- List invites with `assistant contacts invites list --source-channel voice --json`, present active invites with bound phone number and expiration info.
|
|
633
545
|
|
|
634
|
-
**"Revoke voice invite"** / **"Cancel the phone invite for +15551234567"** -- List voice invites, identify the target by phone number or note, confirm, then revoke
|
|
546
|
+
**"Revoke voice invite"** / **"Cancel the phone invite for +15551234567"** -- List voice invites, identify the target by phone number or note, confirm, then revoke with `assistant contacts invites revoke <invite_id> --json`.
|
package/src/config/skills.ts
CHANGED
|
@@ -25,6 +25,8 @@ import { parseToolManifestFile } from "../skills/tool-manifest.js";
|
|
|
25
25
|
import { computeSkillVersionHash } from "../skills/version-hash.js";
|
|
26
26
|
import { getLogger } from "../util/logger.js";
|
|
27
27
|
import { getWorkspaceSkillsDir } from "../util/platform.js";
|
|
28
|
+
import { isAssistantFeatureFlagEnabled } from "./assistant-feature-flags.js";
|
|
29
|
+
import { getConfig } from "./loader.js";
|
|
28
30
|
import { stripCommentLines } from "./system-prompt.js";
|
|
29
31
|
|
|
30
32
|
const log = getLogger("skills");
|
|
@@ -968,6 +970,39 @@ export function loadSkillCatalog(
|
|
|
968
970
|
return catalog;
|
|
969
971
|
}
|
|
970
972
|
|
|
973
|
+
/**
|
|
974
|
+
* Process feature-gated sections in skill body markdown.
|
|
975
|
+
*
|
|
976
|
+
* Markers:
|
|
977
|
+
* <!-- feature:<flag-id>:start --> ... <!-- feature:<flag-id>:end -->
|
|
978
|
+
* Content included only when the flag is enabled.
|
|
979
|
+
* <!-- feature:<flag-id>:alt --> ... <!-- feature:<flag-id>:alt:end -->
|
|
980
|
+
* Fallback content included only when the flag is disabled.
|
|
981
|
+
*/
|
|
982
|
+
function applyFeatureGatedSections(body: string): string {
|
|
983
|
+
const config = getConfig();
|
|
984
|
+
// Match feature:*:start/end blocks
|
|
985
|
+
const mainRe =
|
|
986
|
+
/<!-- feature:([^:]+):start -->\n?([\s\S]*?)<!-- feature:\1:end -->\n?/g;
|
|
987
|
+
// Match feature:*:alt/alt:end blocks
|
|
988
|
+
const altRe =
|
|
989
|
+
/<!-- feature:([^:]+):alt -->\n?([\s\S]*?)<!-- feature:\1:alt:end -->\n?/g;
|
|
990
|
+
|
|
991
|
+
let result = body;
|
|
992
|
+
|
|
993
|
+
result = result.replace(mainRe, (_match, flagId: string, content: string) => {
|
|
994
|
+
const key = `feature_flags.${flagId}.enabled`;
|
|
995
|
+
return isAssistantFeatureFlagEnabled(key, config) ? content : "";
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
result = result.replace(altRe, (_match, flagId: string, content: string) => {
|
|
999
|
+
const key = `feature_flags.${flagId}.enabled`;
|
|
1000
|
+
return isAssistantFeatureFlagEnabled(key, config) ? "" : content;
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
return result;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
971
1006
|
function loadSkillDefinition(skill: SkillSummary): SkillLookupResult {
|
|
972
1007
|
let loaded: SkillDefinition | null;
|
|
973
1008
|
if (skill.bundled) {
|
|
@@ -992,6 +1027,8 @@ function loadSkillDefinition(skill: SkillSummary): SkillLookupResult {
|
|
|
992
1027
|
}
|
|
993
1028
|
// Replace {baseDir} placeholders with the actual skill directory path
|
|
994
1029
|
loaded.body = loaded.body.replaceAll("{baseDir}", loaded.directoryPath);
|
|
1030
|
+
// Strip feature-gated sections based on assistant feature flags
|
|
1031
|
+
loaded.body = applyFeatureGatedSections(loaded.body);
|
|
995
1032
|
return { skill: loaded };
|
|
996
1033
|
}
|
|
997
1034
|
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
getApp,
|
|
32
32
|
getAppPreview,
|
|
33
33
|
getAppsDir,
|
|
34
|
+
isMultifileApp,
|
|
34
35
|
listApps,
|
|
35
36
|
queryAppRecords,
|
|
36
37
|
updateApp,
|
|
@@ -113,11 +114,11 @@ export function handleAppDataRequest(
|
|
|
113
114
|
}
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
export function handleAppOpenRequest(
|
|
117
|
+
export async function handleAppOpenRequest(
|
|
117
118
|
msg: { appId: string },
|
|
118
119
|
socket: net.Socket,
|
|
119
120
|
ctx: HandlerContext,
|
|
120
|
-
): void {
|
|
121
|
+
): Promise<void> {
|
|
121
122
|
try {
|
|
122
123
|
const appId = msg.appId;
|
|
123
124
|
if (!appId) {
|
|
@@ -131,13 +132,37 @@ export function handleAppOpenRequest(
|
|
|
131
132
|
const app = getApp(appId);
|
|
132
133
|
if (app) {
|
|
133
134
|
const surfaceId = `app-open-${uuid()}`;
|
|
135
|
+
let html = app.htmlDefinition;
|
|
136
|
+
|
|
137
|
+
// Multifile apps store source in src/ and compiled output in dist/.
|
|
138
|
+
// Read dist/index.html instead of the (empty) htmlDefinition.
|
|
139
|
+
if (isMultifileApp(app)) {
|
|
140
|
+
const appDir = join(getAppsDir(), appId);
|
|
141
|
+
const distIndex = join(appDir, "dist", "index.html");
|
|
142
|
+
if (!existsSync(distIndex)) {
|
|
143
|
+
// Auto-compile if dist/ is missing (first open or after clean)
|
|
144
|
+
const result = await compileApp(appDir);
|
|
145
|
+
if (!result.ok) {
|
|
146
|
+
log.warn(
|
|
147
|
+
{ appId, errors: result.errors },
|
|
148
|
+
"Auto-compile failed on app open",
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (existsSync(distIndex)) {
|
|
153
|
+
html = readFileSync(distIndex, "utf-8");
|
|
154
|
+
} else {
|
|
155
|
+
html = `<p>App compilation failed. Edit a source file to trigger a rebuild.</p>`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
134
159
|
ctx.send(socket, {
|
|
135
160
|
type: "ui_surface_show",
|
|
136
161
|
sessionId: "app-panel",
|
|
137
162
|
surfaceId,
|
|
138
163
|
surfaceType: "dynamic_page",
|
|
139
164
|
title: app.name,
|
|
140
|
-
data: { html
|
|
165
|
+
data: { html, appId: app.id },
|
|
141
166
|
display: "panel",
|
|
142
167
|
} as UiSurfaceShow);
|
|
143
168
|
return;
|