vellum 0.2.13 → 0.2.14
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/README.md +32 -0
- package/bun.lock +2 -2
- package/docs/skills.md +4 -4
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +213 -3
- package/src/__tests__/app-git-history.test.ts +176 -0
- package/src/__tests__/app-git-service.test.ts +169 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +315 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +8 -8
- package/src/__tests__/browser-skill-endstate.test.ts +6 -6
- package/src/__tests__/call-bridge.test.ts +105 -13
- package/src/__tests__/call-domain.test.ts +163 -0
- package/src/__tests__/call-orchestrator.test.ts +113 -0
- package/src/__tests__/call-routes-http.test.ts +246 -6
- package/src/__tests__/channel-approval-routes.test.ts +438 -0
- package/src/__tests__/channel-approval.test.ts +266 -0
- package/src/__tests__/channel-approvals.test.ts +393 -0
- package/src/__tests__/channel-delivery-store.test.ts +447 -0
- package/src/__tests__/checker.test.ts +607 -1048
- package/src/__tests__/cli.test.ts +1 -56
- package/src/__tests__/config-schema.test.ts +137 -18
- package/src/__tests__/conflict-intent-tokenization.test.ts +141 -0
- package/src/__tests__/conflict-policy.test.ts +121 -0
- package/src/__tests__/conflict-store.test.ts +2 -0
- package/src/__tests__/contacts-tools.test.ts +3 -3
- package/src/__tests__/contradiction-checker.test.ts +99 -1
- package/src/__tests__/credential-security-invariants.test.ts +22 -6
- package/src/__tests__/credential-vault-unit.test.ts +780 -0
- package/src/__tests__/elevenlabs-client.test.ts +62 -0
- package/src/__tests__/ephemeral-permissions.test.ts +73 -23
- package/src/__tests__/filesystem-tools.test.ts +579 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +114 -4
- package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +202 -0
- package/src/__tests__/handlers-cu-observation-blob.test.ts +2 -1
- package/src/__tests__/handlers-ipc-blob-probe.test.ts +2 -1
- package/src/__tests__/handlers-slack-config.test.ts +2 -1
- package/src/__tests__/handlers-telegram-config.test.ts +855 -0
- package/src/__tests__/handlers-twitter-config.test.ts +141 -1
- package/src/__tests__/hooks-runner.test.ts +6 -2
- package/src/__tests__/host-file-edit-tool.test.ts +124 -0
- package/src/__tests__/host-file-read-tool.test.ts +62 -0
- package/src/__tests__/host-file-write-tool.test.ts +59 -0
- package/src/__tests__/host-shell-tool.test.ts +251 -0
- package/src/__tests__/ingress-reconcile.test.ts +581 -0
- package/src/__tests__/ipc-snapshot.test.ts +100 -41
- package/src/__tests__/ipc-validate.test.ts +50 -0
- package/src/__tests__/key-migration.test.ts +23 -0
- package/src/__tests__/memory-regressions.test.ts +99 -0
- package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
- package/src/__tests__/oauth-callback-registry.test.ts +11 -4
- package/src/__tests__/playbook-execution.test.ts +502 -0
- package/src/__tests__/playbook-tools.test.ts +4 -6
- package/src/__tests__/public-ingress-urls.test.ts +34 -0
- package/src/__tests__/qdrant-manager.test.ts +267 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +97 -0
- package/src/__tests__/recurrence-engine.test.ts +9 -0
- package/src/__tests__/recurrence-types.test.ts +8 -0
- package/src/__tests__/registry.test.ts +1 -1
- package/src/__tests__/runtime-runs.test.ts +1 -25
- package/src/__tests__/schedule-store.test.ts +16 -14
- package/src/__tests__/schedule-tools.test.ts +83 -0
- package/src/__tests__/scheduler-recurrence.test.ts +111 -10
- package/src/__tests__/secret-allowlist.test.ts +18 -17
- package/src/__tests__/secret-ingress-handler.test.ts +11 -0
- package/src/__tests__/secret-scanner.test.ts +43 -0
- package/src/__tests__/session-conflict-gate.test.ts +442 -6
- package/src/__tests__/session-init.benchmark.test.ts +3 -0
- package/src/__tests__/session-process-bridge.test.ts +242 -0
- package/src/__tests__/session-skill-tools.test.ts +1 -1
- package/src/__tests__/shell-identity.test.ts +256 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +11 -1
- package/src/__tests__/subagent-tools.test.ts +637 -54
- package/src/__tests__/task-management-tools.test.ts +936 -0
- package/src/__tests__/task-runner.test.ts +2 -2
- package/src/__tests__/terminal-tools.test.ts +840 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +301 -0
- package/src/__tests__/tool-executor.test.ts +85 -151
- package/src/__tests__/tool-permission-simulate-handler.test.ts +336 -0
- package/src/__tests__/trust-store.test.ts +27 -453
- package/src/__tests__/twilio-provider.test.ts +153 -3
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +375 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +4 -4
- package/src/__tests__/twilio-routes.test.ts +17 -262
- package/src/__tests__/twitter-auth-handler.test.ts +2 -1
- package/src/__tests__/twitter-cli-error-shaping.test.ts +208 -0
- package/src/__tests__/twitter-cli-routing.test.ts +252 -0
- package/src/__tests__/twitter-oauth-client.test.ts +209 -0
- package/src/__tests__/workspace-policy.test.ts +213 -0
- package/src/calls/call-bridge.ts +92 -19
- package/src/calls/call-domain.ts +157 -5
- package/src/calls/call-orchestrator.ts +93 -7
- package/src/calls/call-store.ts +6 -0
- package/src/calls/elevenlabs-client.ts +8 -0
- package/src/calls/elevenlabs-config.ts +7 -5
- package/src/calls/twilio-provider.ts +91 -0
- package/src/calls/twilio-routes.ts +32 -37
- package/src/calls/types.ts +3 -1
- package/src/calls/voice-quality.ts +29 -7
- package/src/cli/twitter.ts +200 -21
- package/src/cli.ts +1 -20
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +52 -4
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +55 -4
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +61 -4
- package/src/config/bundled-skills/messaging/SKILL.md +17 -2
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +4 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/shared.ts +5 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +142 -34
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +95 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +51 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +73 -6
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +110 -6
- package/src/config/bundled-skills/public-ingress/SKILL.md +22 -5
- package/src/config/bundled-skills/twitter/SKILL.md +103 -17
- package/src/config/defaults.ts +10 -4
- package/src/config/schema.ts +80 -21
- package/src/config/types.ts +1 -0
- package/src/config/vellum-skills/telegram-setup/SKILL.md +56 -61
- package/src/daemon/assistant-attachments.ts +4 -2
- package/src/daemon/handlers/apps.ts +69 -0
- package/src/daemon/handlers/config.ts +543 -24
- package/src/daemon/handlers/index.ts +1 -0
- package/src/daemon/handlers/sessions.ts +22 -6
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +5 -20
- package/src/daemon/ipc-contract-inventory.json +28 -0
- package/src/daemon/ipc-contract.ts +168 -10
- package/src/daemon/ipc-validate.ts +17 -0
- package/src/daemon/lifecycle.ts +2 -0
- package/src/daemon/server.ts +78 -72
- package/src/daemon/session-attachments.ts +1 -1
- package/src/daemon/session-conflict-gate.ts +62 -6
- package/src/daemon/session-notifiers.ts +1 -1
- package/src/daemon/session-process.ts +62 -3
- package/src/daemon/session-tool-setup.ts +1 -2
- package/src/daemon/tls-certs.ts +189 -0
- package/src/daemon/video-thumbnail.ts +5 -3
- package/src/hooks/manager.ts +5 -9
- package/src/memory/app-git-service.ts +295 -0
- package/src/memory/app-store.ts +21 -0
- package/src/memory/conflict-intent.ts +47 -4
- package/src/memory/conflict-policy.ts +73 -0
- package/src/memory/conflict-store.ts +9 -1
- package/src/memory/contradiction-checker.ts +28 -0
- package/src/memory/conversation-key-store.ts +15 -0
- package/src/memory/db.ts +81 -0
- package/src/memory/embedding-local.ts +3 -13
- package/src/memory/external-conversation-store.ts +234 -0
- package/src/memory/job-handlers/conflict.ts +22 -2
- package/src/memory/jobs-worker.ts +67 -28
- package/src/memory/runs-store.ts +54 -7
- package/src/memory/schema.ts +20 -0
- package/src/messaging/provider.ts +9 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +162 -0
- package/src/messaging/providers/telegram-bot/client.ts +104 -0
- package/src/messaging/providers/telegram-bot/types.ts +15 -0
- package/src/messaging/registry.ts +1 -0
- package/src/permissions/checker.ts +48 -44
- package/src/permissions/prompter.ts +0 -4
- package/src/permissions/shell-identity.ts +227 -0
- package/src/permissions/trust-store.ts +76 -53
- package/src/permissions/types.ts +0 -19
- package/src/permissions/workspace-policy.ts +114 -0
- package/src/providers/retry.ts +12 -37
- package/src/runtime/assistant-event-hub.ts +41 -4
- package/src/runtime/channel-approval-parser.ts +60 -0
- package/src/runtime/channel-approval-types.ts +71 -0
- package/src/runtime/channel-approvals.ts +145 -0
- package/src/runtime/gateway-client.ts +16 -0
- package/src/runtime/http-server.ts +29 -9
- package/src/runtime/routes/call-routes.ts +52 -2
- package/src/runtime/routes/channel-routes.ts +296 -16
- package/src/runtime/routes/events-routes.ts +97 -28
- package/src/runtime/routes/run-routes.ts +2 -7
- package/src/runtime/run-orchestrator.ts +0 -3
- package/src/schedule/recurrence-engine.ts +26 -2
- package/src/schedule/recurrence-types.ts +1 -1
- package/src/schedule/schedule-store.ts +12 -3
- package/src/security/secret-scanner.ts +7 -0
- package/src/tasks/ephemeral-permissions.ts +0 -2
- package/src/tasks/task-scheduler.ts +2 -1
- package/src/tools/calls/call-start.ts +8 -0
- package/src/tools/execution-target.ts +21 -0
- package/src/tools/execution-timeout.ts +49 -0
- package/src/tools/executor.ts +6 -135
- package/src/tools/network/web-search.ts +9 -32
- package/src/tools/policy-context.ts +29 -0
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/terminal/parser.ts +16 -18
- package/src/tools/types.ts +4 -11
- package/src/twitter/oauth-client.ts +102 -0
- package/src/twitter/router.ts +101 -0
- package/src/util/debounce.ts +88 -0
- package/src/util/network-info.ts +47 -0
- package/src/util/platform.ts +29 -4
- package/src/util/promise-guard.ts +37 -0
- package/src/util/retry.ts +98 -0
- package/src/util/truncate.ts +1 -1
- package/src/workspace/git-service.ts +129 -112
- package/src/tools/contacts/contact-merge.ts +0 -55
- package/src/tools/contacts/contact-search.ts +0 -58
- package/src/tools/contacts/contact-upsert.ts +0 -64
- package/src/tools/playbooks/index.ts +0 -4
- package/src/tools/playbooks/playbook-create.ts +0 -96
- package/src/tools/playbooks/playbook-delete.ts +0 -52
- package/src/tools/playbooks/playbook-list.ts +0 -74
- package/src/tools/playbooks/playbook-update.ts +0 -111
|
@@ -1,9 +1,54 @@
|
|
|
1
|
+
import { and, eq } from 'drizzle-orm';
|
|
1
2
|
import type { ToolContext, ToolExecutionResult } from '../../../../tools/types.js';
|
|
2
|
-
import {
|
|
3
|
+
import { getDb } from '../../../../memory/db.js';
|
|
4
|
+
import { memoryItems } from '../../../../memory/schema.js';
|
|
5
|
+
import { parsePlaybookStatement } from '../../../../playbooks/types.js';
|
|
3
6
|
|
|
4
|
-
export async function
|
|
5
|
-
input
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
export async function executePlaybookDelete(input: Record<string, unknown>, context: ToolContext): Promise<ToolExecutionResult> {
|
|
8
|
+
const playbookId = input.playbook_id as string;
|
|
9
|
+
if (!playbookId || typeof playbookId !== 'string') {
|
|
10
|
+
return { content: 'Error: playbook_id is required and must be a string', isError: true };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const scopeId = context.memoryScopeId ?? 'default';
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const db = getDb();
|
|
17
|
+
|
|
18
|
+
const existing = db
|
|
19
|
+
.select()
|
|
20
|
+
.from(memoryItems)
|
|
21
|
+
.where(and(
|
|
22
|
+
eq(memoryItems.id, playbookId),
|
|
23
|
+
eq(memoryItems.kind, 'playbook'),
|
|
24
|
+
eq(memoryItems.scopeId, scopeId),
|
|
25
|
+
))
|
|
26
|
+
.get();
|
|
27
|
+
|
|
28
|
+
if (!existing) {
|
|
29
|
+
return { content: `Error: Playbook with ID "${playbookId}" not found`, isError: true };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const playbook = parsePlaybookStatement(existing.statement);
|
|
33
|
+
const triggerLabel = playbook?.trigger ?? existing.subject;
|
|
34
|
+
|
|
35
|
+
// Soft-delete by marking as superseded rather than hard-deleting,
|
|
36
|
+
// consistent with how other memory items are retired.
|
|
37
|
+
// Setting invalidAt so the cleanup job can eventually hard-delete it.
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
db.update(memoryItems)
|
|
40
|
+
.set({ status: 'superseded', invalidAt: now })
|
|
41
|
+
.where(eq(memoryItems.id, existing.id))
|
|
42
|
+
.run();
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
content: `Playbook deleted (ID: ${existing.id}, trigger: "${triggerLabel}").`,
|
|
46
|
+
isError: false,
|
|
47
|
+
};
|
|
48
|
+
} catch (err) {
|
|
49
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
50
|
+
return { content: `Error deleting playbook: ${msg}`, isError: true };
|
|
51
|
+
}
|
|
9
52
|
}
|
|
53
|
+
|
|
54
|
+
export { executePlaybookDelete as run };
|
|
@@ -1,9 +1,76 @@
|
|
|
1
|
+
import { and, desc, eq, isNull } from 'drizzle-orm';
|
|
1
2
|
import type { ToolContext, ToolExecutionResult } from '../../../../tools/types.js';
|
|
2
|
-
import {
|
|
3
|
+
import { getDb } from '../../../../memory/db.js';
|
|
4
|
+
import { memoryItems } from '../../../../memory/schema.js';
|
|
5
|
+
import { parsePlaybookStatement } from '../../../../playbooks/types.js';
|
|
3
6
|
|
|
4
|
-
export async function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
export async function executePlaybookList(input: Record<string, unknown>, context: ToolContext): Promise<ToolExecutionResult> {
|
|
8
|
+
const scopeId = context.memoryScopeId ?? 'default';
|
|
9
|
+
const channelFilter = typeof input.channel === 'string' ? input.channel : null;
|
|
10
|
+
const categoryFilter = typeof input.category === 'string' ? input.category : null;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const db = getDb();
|
|
14
|
+
|
|
15
|
+
const rows = db
|
|
16
|
+
.select({
|
|
17
|
+
id: memoryItems.id,
|
|
18
|
+
subject: memoryItems.subject,
|
|
19
|
+
statement: memoryItems.statement,
|
|
20
|
+
importance: memoryItems.importance,
|
|
21
|
+
lastSeenAt: memoryItems.lastSeenAt,
|
|
22
|
+
})
|
|
23
|
+
.from(memoryItems)
|
|
24
|
+
.where(and(
|
|
25
|
+
eq(memoryItems.kind, 'playbook'),
|
|
26
|
+
eq(memoryItems.status, 'active'),
|
|
27
|
+
eq(memoryItems.scopeId, scopeId),
|
|
28
|
+
isNull(memoryItems.invalidAt),
|
|
29
|
+
))
|
|
30
|
+
.orderBy(desc(memoryItems.importance))
|
|
31
|
+
.all();
|
|
32
|
+
|
|
33
|
+
if (rows.length === 0) {
|
|
34
|
+
return { content: 'No playbooks found.', isError: false };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const entries: Array<{ id: string; subject: string; statement: string; playbook: NonNullable<ReturnType<typeof parsePlaybookStatement>> }> = [];
|
|
38
|
+
for (const row of rows) {
|
|
39
|
+
const playbook = parsePlaybookStatement(row.statement);
|
|
40
|
+
if (!playbook) continue;
|
|
41
|
+
|
|
42
|
+
// Apply filters
|
|
43
|
+
if (channelFilter && playbook.channel !== channelFilter && playbook.channel !== '*') continue;
|
|
44
|
+
if (categoryFilter && playbook.category !== categoryFilter) continue;
|
|
45
|
+
|
|
46
|
+
entries.push({ id: row.id, subject: row.subject, statement: row.statement, playbook });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (entries.length === 0) {
|
|
50
|
+
const filters = [
|
|
51
|
+
channelFilter ? `channel="${channelFilter}"` : null,
|
|
52
|
+
categoryFilter ? `category="${categoryFilter}"` : null,
|
|
53
|
+
].filter(Boolean).join(', ');
|
|
54
|
+
return { content: `No playbooks found matching ${filters}.`, isError: false };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Sort by priority descending
|
|
58
|
+
entries.sort((a, b) => b.playbook.priority - a.playbook.priority);
|
|
59
|
+
|
|
60
|
+
const lines: string[] = [`Found ${entries.length} playbook(s):\n`];
|
|
61
|
+
for (const { id, playbook } of entries) {
|
|
62
|
+
const channelLabel = playbook.channel === '*' ? 'all channels' : playbook.channel;
|
|
63
|
+
const autonomyLabel = playbook.autonomyLevel === 'auto' ? 'auto'
|
|
64
|
+
: playbook.autonomyLevel === 'draft' ? 'draft' : 'notify';
|
|
65
|
+
lines.push(`- **${playbook.trigger}** (${channelLabel}) → ${playbook.action}`);
|
|
66
|
+
lines.push(` _ID: ${id} | category: ${playbook.category} | autonomy: ${autonomyLabel} | priority: ${playbook.priority}_`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { content: lines.join('\n'), isError: false };
|
|
70
|
+
} catch (err) {
|
|
71
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
72
|
+
return { content: `Error listing playbooks: ${msg}`, isError: true };
|
|
73
|
+
}
|
|
9
74
|
}
|
|
75
|
+
|
|
76
|
+
export { executePlaybookList as run };
|
|
@@ -1,9 +1,113 @@
|
|
|
1
|
+
import { and, eq } from 'drizzle-orm';
|
|
1
2
|
import type { ToolContext, ToolExecutionResult } from '../../../../tools/types.js';
|
|
2
|
-
import {
|
|
3
|
+
import { getDb } from '../../../../memory/db.js';
|
|
4
|
+
import { computeMemoryFingerprint } from '../../../../memory/fingerprint.js';
|
|
5
|
+
import { memoryItems } from '../../../../memory/schema.js';
|
|
6
|
+
import { enqueueMemoryJob } from '../../../../memory/jobs-store.js';
|
|
7
|
+
import { parsePlaybookStatement } from '../../../../playbooks/types.js';
|
|
8
|
+
import type { Playbook, PlaybookAutonomyLevel } from '../../../../playbooks/types.js';
|
|
9
|
+
import { truncate } from '../../../../util/truncate.js';
|
|
3
10
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
const VALID_AUTONOMY_LEVELS = new Set<string>(['auto', 'draft', 'notify']);
|
|
12
|
+
|
|
13
|
+
export async function executePlaybookUpdate(input: Record<string, unknown>, context: ToolContext): Promise<ToolExecutionResult> {
|
|
14
|
+
const playbookId = input.playbook_id as string;
|
|
15
|
+
if (!playbookId || typeof playbookId !== 'string') {
|
|
16
|
+
return { content: 'Error: playbook_id is required and must be a string', isError: true };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const scopeId = context.memoryScopeId ?? 'default';
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const db = getDb();
|
|
23
|
+
|
|
24
|
+
const existing = db
|
|
25
|
+
.select()
|
|
26
|
+
.from(memoryItems)
|
|
27
|
+
.where(and(
|
|
28
|
+
eq(memoryItems.id, playbookId),
|
|
29
|
+
eq(memoryItems.kind, 'playbook'),
|
|
30
|
+
eq(memoryItems.scopeId, scopeId),
|
|
31
|
+
))
|
|
32
|
+
.get();
|
|
33
|
+
|
|
34
|
+
if (!existing) {
|
|
35
|
+
return { content: `Error: Playbook with ID "${playbookId}" not found`, isError: true };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const currentPlaybook = parsePlaybookStatement(existing.statement);
|
|
39
|
+
if (!currentPlaybook) {
|
|
40
|
+
return { content: `Error: Playbook data is corrupted for ID "${playbookId}"`, isError: true };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Merge updates onto existing playbook
|
|
44
|
+
const updated: Playbook = {
|
|
45
|
+
trigger: typeof input.trigger === 'string' ? input.trigger : currentPlaybook.trigger,
|
|
46
|
+
channel: typeof input.channel === 'string' ? input.channel : currentPlaybook.channel,
|
|
47
|
+
category: typeof input.category === 'string' ? input.category : currentPlaybook.category,
|
|
48
|
+
action: typeof input.action === 'string' ? input.action : currentPlaybook.action,
|
|
49
|
+
autonomyLevel:
|
|
50
|
+
typeof input.autonomy_level === 'string' && VALID_AUTONOMY_LEVELS.has(input.autonomy_level)
|
|
51
|
+
? (input.autonomy_level as PlaybookAutonomyLevel)
|
|
52
|
+
: currentPlaybook.autonomyLevel,
|
|
53
|
+
priority: typeof input.priority === 'number' ? input.priority : currentPlaybook.priority,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const statement = JSON.stringify(updated);
|
|
57
|
+
const subject = truncate(`Playbook: ${updated.trigger}`, 80, '');
|
|
58
|
+
const now = Date.now();
|
|
59
|
+
|
|
60
|
+
const fingerprint = computeMemoryFingerprint(scopeId, 'playbook', subject, statement);
|
|
61
|
+
|
|
62
|
+
// Check if another playbook already has this fingerprint
|
|
63
|
+
const collision = db
|
|
64
|
+
.select({ id: memoryItems.id })
|
|
65
|
+
.from(memoryItems)
|
|
66
|
+
.where(and(
|
|
67
|
+
eq(memoryItems.fingerprint, fingerprint),
|
|
68
|
+
eq(memoryItems.scopeId, scopeId),
|
|
69
|
+
))
|
|
70
|
+
.get();
|
|
71
|
+
if (collision && collision.id !== existing.id) {
|
|
72
|
+
return {
|
|
73
|
+
content: `Error: Another playbook with this exact configuration already exists (ID: ${collision.id}).`,
|
|
74
|
+
isError: true,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
db.update(memoryItems)
|
|
79
|
+
.set({
|
|
80
|
+
subject,
|
|
81
|
+
statement,
|
|
82
|
+
fingerprint,
|
|
83
|
+
lastSeenAt: now,
|
|
84
|
+
verificationState: 'user_confirmed',
|
|
85
|
+
})
|
|
86
|
+
.where(eq(memoryItems.id, existing.id))
|
|
87
|
+
.run();
|
|
88
|
+
|
|
89
|
+
enqueueMemoryJob('embed_item', { itemId: existing.id });
|
|
90
|
+
|
|
91
|
+
const autonomyLabel = updated.autonomyLevel === 'auto' ? 'execute automatically'
|
|
92
|
+
: updated.autonomyLevel === 'draft' ? 'draft for review' : 'notify only';
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
content: [
|
|
96
|
+
'Playbook updated successfully.',
|
|
97
|
+
` ID: ${existing.id}`,
|
|
98
|
+
` Trigger: ${updated.trigger}`,
|
|
99
|
+
` Channel: ${updated.channel}`,
|
|
100
|
+
` Category: ${updated.category}`,
|
|
101
|
+
` Action: ${updated.action}`,
|
|
102
|
+
` Autonomy: ${autonomyLabel}`,
|
|
103
|
+
` Priority: ${updated.priority}`,
|
|
104
|
+
].join('\n'),
|
|
105
|
+
isError: false,
|
|
106
|
+
};
|
|
107
|
+
} catch (err) {
|
|
108
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
109
|
+
return { content: `Error updating playbook: ${msg}`, isError: true };
|
|
110
|
+
}
|
|
9
111
|
}
|
|
112
|
+
|
|
113
|
+
export { executePlaybookUpdate as run };
|
|
@@ -68,12 +68,29 @@ ngrok config check
|
|
|
68
68
|
If not authenticated:
|
|
69
69
|
|
|
70
70
|
1. Tell the user: "You need an ngrok account to create tunnels. If you don't have one, sign up at https://dashboard.ngrok.com/signup — it's free."
|
|
71
|
-
2. Once they have an account,
|
|
71
|
+
2. Once they have an account, use `credential_store` to securely collect their auth token. **Never ask the user to paste the token directly in chat.**
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
Use `credential_store` with:
|
|
74
|
+
- action: `prompt`
|
|
75
|
+
- service: `ngrok`
|
|
76
|
+
- field: `authtoken`
|
|
77
|
+
- label: `ngrok Auth Token`
|
|
78
|
+
- description: `Get your auth token from https://dashboard.ngrok.com/get-started/your-authtoken`
|
|
79
|
+
- usage_description: `ngrok authentication token for creating public tunnels`
|
|
80
|
+
|
|
81
|
+
3. Once the credential is stored, configure ngrok by reading the token directly from the OS keychain and piping it to ngrok so the plaintext never enters the conversation:
|
|
82
|
+
|
|
83
|
+
**macOS:**
|
|
84
|
+
```bash
|
|
85
|
+
ngrok config add-authtoken "$(security find-generic-password -s vellum-assistant -a credential:ngrok:authtoken -w)"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Linux:**
|
|
89
|
+
```bash
|
|
90
|
+
ngrok config add-authtoken "$(secret-tool lookup service vellum-assistant account credential:ngrok:authtoken)"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
If the keychain command fails (e.g., headless environment without a keyring), fall back to asking the user to re-enter the token via `credential_store prompt` and then paste it into `ngrok config add-authtoken` manually as a last resort.
|
|
77
94
|
|
|
78
95
|
Verify authentication succeeded by checking `ngrok config check` again.
|
|
79
96
|
|
|
@@ -1,19 +1,113 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "X"
|
|
3
|
-
description: "Read and post on X (formerly Twitter)
|
|
3
|
+
description: "Read and post on X (formerly Twitter) via OAuth or browser session"
|
|
4
4
|
user-invocable: true
|
|
5
5
|
metadata: {"vellum": {"emoji": "𝕏"}}
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
You are an X (formerly Twitter) assistant. Use the `execute_bash` tool to run `vellum x` CLI commands.
|
|
9
9
|
|
|
10
|
+
## Connection Options
|
|
11
|
+
|
|
12
|
+
There are two supported ways to connect to X. Both are fully functional; choose whichever fits the user's situation.
|
|
13
|
+
|
|
14
|
+
### OAuth (recommended with X developer credentials)
|
|
15
|
+
|
|
16
|
+
OAuth uses the official X API v2. It is the most reliable connection method and does not depend on browser sessions.
|
|
17
|
+
|
|
18
|
+
- Supports: **post** and **reply**
|
|
19
|
+
- Read-only operations (timeline, search, home, bookmarks, notifications, likes, followers, following, media) always use the browser path directly, regardless of the strategy setting.
|
|
20
|
+
- Setup: The user connects OAuth credentials through the Settings UI or the `twitter_auth_start` IPC flow.
|
|
21
|
+
- Set the strategy: `vellum x strategy set oauth`
|
|
22
|
+
|
|
23
|
+
### Browser session (no developer credentials needed)
|
|
24
|
+
|
|
25
|
+
The browser path is quick to start and useful when the user does not have X developer app credentials. It captures auth cookies from Chrome and uses them to interact with X.
|
|
26
|
+
|
|
27
|
+
- Supports: **all operations** (post, reply, timeline, search, home, bookmarks, notifications, likes, followers, following, media)
|
|
28
|
+
- Setup: Run `vellum x refresh` to open Chrome and capture session cookies automatically.
|
|
29
|
+
- Set the strategy: `vellum x strategy set browser`
|
|
30
|
+
|
|
31
|
+
### Auto mode (default)
|
|
32
|
+
|
|
33
|
+
When the strategy is `auto` (the default), the router tries OAuth first for supported operations if credentials are available, then falls back to the browser path. This gives the best of both worlds without requiring manual switching.
|
|
34
|
+
|
|
35
|
+
- Set auto mode: `vellum x strategy set auto`
|
|
36
|
+
|
|
37
|
+
## First-Use Decision Flow
|
|
38
|
+
|
|
39
|
+
When the user triggers a Twitter operation and no strategy has been configured yet, follow these steps:
|
|
40
|
+
|
|
41
|
+
1. **Check current status:**
|
|
42
|
+
```bash
|
|
43
|
+
vellum x status --json
|
|
44
|
+
```
|
|
45
|
+
Look at `oauthConnected`, `browserSessionActive`, `preferredStrategy`, and `strategyConfigured` in the response. If `strategyConfigured` is `false`, the user has not yet chosen a strategy and should be guided through setup.
|
|
46
|
+
|
|
47
|
+
2. **Present both options with trade-offs:**
|
|
48
|
+
- **OAuth**: Most reliable and official. Requires X developer app credentials (OAuth Client ID and optional Client Secret). Supports posting and replying. Set up through Settings UI.
|
|
49
|
+
- **Browser session**: Quick to start, no developer credentials needed. Supports all operations including reading timelines and searching. Set up with `vellum x refresh`.
|
|
50
|
+
|
|
51
|
+
3. **Ask the user which they prefer.** Do not choose for them.
|
|
52
|
+
|
|
53
|
+
4. **Execute setup for the chosen path:**
|
|
54
|
+
- If OAuth: Guide the user to the Settings UI to connect their X developer credentials, or initiate the `twitter_auth_start` IPC flow.
|
|
55
|
+
- If browser: Run `vellum x refresh` to capture session cookies from Chrome.
|
|
56
|
+
|
|
57
|
+
5. **Set the preferred strategy:**
|
|
58
|
+
```bash
|
|
59
|
+
vellum x strategy set <oauth|browser|auto>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Failure Recovery Flow
|
|
63
|
+
|
|
64
|
+
When a Twitter operation fails, follow these steps:
|
|
65
|
+
|
|
66
|
+
1. **Detect the failure type from the error output:**
|
|
67
|
+
- `session_expired` or `SessionExpiredError` — the browser session cookies have expired.
|
|
68
|
+
- `OAuth is not configured` — the user chose OAuth but credentials are not set up.
|
|
69
|
+
- `Twitter API error (401)` — OAuth token may be expired or revoked.
|
|
70
|
+
- `UnsupportedOAuthOperationError` — the requested write operation is not available via OAuth.
|
|
71
|
+
- `Cannot connect to daemon` — the Vellum daemon is not running.
|
|
72
|
+
|
|
73
|
+
2. **Explain the likely cause clearly** to the user.
|
|
74
|
+
|
|
75
|
+
3. **Suggest trying the other path as an alternative:**
|
|
76
|
+
- If the browser session expired: suggest setting up OAuth for post/reply operations, or refresh the browser session with `vellum x refresh`.
|
|
77
|
+
- If OAuth failed or is not configured: suggest using the browser path with `vellum x strategy set browser` and `vellum x refresh`.
|
|
78
|
+
- If the operation is unsupported via OAuth: explain that this write operation is not yet supported via OAuth, and suggest using the browser path with `vellum x strategy set browser`.
|
|
79
|
+
|
|
80
|
+
4. **Offer concrete steps to switch:**
|
|
81
|
+
```bash
|
|
82
|
+
# Switch to the other strategy
|
|
83
|
+
vellum x strategy set <oauth|browser|auto>
|
|
84
|
+
|
|
85
|
+
# If switching to browser, refresh the session
|
|
86
|
+
vellum x refresh
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Strategy Management Commands
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Check current strategy
|
|
93
|
+
vellum x strategy
|
|
94
|
+
|
|
95
|
+
# Set strategy to OAuth, browser, or auto
|
|
96
|
+
vellum x strategy set <oauth|browser|auto>
|
|
97
|
+
|
|
98
|
+
# Check full status (session, OAuth, and strategy info)
|
|
99
|
+
vellum x status --json
|
|
100
|
+
```
|
|
101
|
+
|
|
10
102
|
## Posting
|
|
11
103
|
|
|
12
104
|
```bash
|
|
13
105
|
vellum x post "The post text here"
|
|
14
106
|
```
|
|
15
107
|
|
|
16
|
-
Returns JSON with `ok`, `tweetId`, `text`, and `
|
|
108
|
+
Returns JSON with `ok`, `tweetId`, `text`, `url`, and `pathUsed` fields. The `pathUsed` field indicates whether the post was sent via `oauth` or `browser`. Share the URL with the user so they can verify the post.
|
|
109
|
+
|
|
110
|
+
The `post` command routes through the strategy router: it uses OAuth if configured and available, otherwise falls back to the browser path.
|
|
17
111
|
|
|
18
112
|
## Replying
|
|
19
113
|
|
|
@@ -23,8 +117,12 @@ vellum x reply <tweetUrl> "The reply text here"
|
|
|
23
117
|
|
|
24
118
|
The first argument is a tweet URL (e.g. `https://x.com/user/status/123456`) or a bare tweet ID.
|
|
25
119
|
|
|
120
|
+
Like `post`, the `reply` command routes through the strategy router and returns a `pathUsed` field.
|
|
121
|
+
|
|
26
122
|
## Reading
|
|
27
123
|
|
|
124
|
+
Read-only operations always use the browser path directly, regardless of the strategy setting. They work the same whether the strategy is `oauth`, `browser`, or `auto` — the strategy only affects `post` and `reply` commands.
|
|
125
|
+
|
|
28
126
|
### User timeline
|
|
29
127
|
```bash
|
|
30
128
|
vellum x timeline <screenName> [--count N]
|
|
@@ -76,20 +174,6 @@ vellum x media <screenName> [--count N]
|
|
|
76
174
|
```
|
|
77
175
|
Returns tweets that contain media from the user's profile.
|
|
78
176
|
|
|
79
|
-
## Session Management
|
|
80
|
-
|
|
81
|
-
Check if a session exists:
|
|
82
|
-
```bash
|
|
83
|
-
vellum x status --json
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
If there is no session or the session has expired, refresh it:
|
|
87
|
-
```bash
|
|
88
|
-
vellum x refresh
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
This opens Chrome, navigates through x.com automatically, and captures auth cookies. Do NOT tell the user to run this manually — run it yourself.
|
|
92
|
-
|
|
93
177
|
## Workflows
|
|
94
178
|
|
|
95
179
|
### Check Mentions
|
|
@@ -131,4 +215,6 @@ When the user wants to see how their posts are performing:
|
|
|
131
215
|
- All commands return JSON with an `ok` field
|
|
132
216
|
- When drafting replies, match the tone of the conversation — casual threads get casual replies
|
|
133
217
|
- Always show the user what you're about to post and get approval before sending
|
|
134
|
-
- If a session is expired, refresh it
|
|
218
|
+
- If a browser session is expired, refresh it with `vellum x refresh` before retrying, or suggest switching to OAuth for post/reply operations
|
|
219
|
+
- If an operation fails, check `vellum x status --json` to diagnose the issue before retrying
|
|
220
|
+
- The `post` and `reply` commands include a `pathUsed` field in their response so you can tell the user which connection method was used
|
package/src/config/defaults.ts
CHANGED
|
@@ -76,6 +76,7 @@ export const DEFAULT_CONFIG: AssistantConfig = {
|
|
|
76
76
|
},
|
|
77
77
|
jobs: {
|
|
78
78
|
workerConcurrency: 2,
|
|
79
|
+
batchSize: 10,
|
|
79
80
|
},
|
|
80
81
|
retention: {
|
|
81
82
|
keepRawForever: true,
|
|
@@ -117,6 +118,8 @@ export const DEFAULT_CONFIG: AssistantConfig = {
|
|
|
117
118
|
reaskCooldownTurns: 3,
|
|
118
119
|
resolverLlmTimeoutMs: 12000,
|
|
119
120
|
relevanceThreshold: 0.3,
|
|
121
|
+
askOnIrrelevantTurns: false,
|
|
122
|
+
conflictableKinds: ['preference', 'profile', 'constraint', 'instruction', 'style'],
|
|
120
123
|
},
|
|
121
124
|
profile: {
|
|
122
125
|
enabled: true,
|
|
@@ -159,7 +162,7 @@ export const DEFAULT_CONFIG: AssistantConfig = {
|
|
|
159
162
|
blockIngress: true,
|
|
160
163
|
},
|
|
161
164
|
permissions: {
|
|
162
|
-
mode: '
|
|
165
|
+
mode: 'workspace',
|
|
163
166
|
},
|
|
164
167
|
auditLog: {
|
|
165
168
|
retentionDays: 0,
|
|
@@ -233,10 +236,10 @@ export const DEFAULT_CONFIG: AssistantConfig = {
|
|
|
233
236
|
fallbackToStandardOnError: true,
|
|
234
237
|
elevenlabs: {
|
|
235
238
|
voiceId: '',
|
|
236
|
-
voiceModelId: '
|
|
239
|
+
voiceModelId: '',
|
|
240
|
+
speed: 1.0,
|
|
237
241
|
stability: 0.5,
|
|
238
242
|
similarityBoost: 0.75,
|
|
239
|
-
style: 0.0,
|
|
240
243
|
useSpeakerBoost: true,
|
|
241
244
|
agentId: '',
|
|
242
245
|
apiBaseUrl: 'https://api.elevenlabs.io',
|
|
@@ -244,9 +247,12 @@ export const DEFAULT_CONFIG: AssistantConfig = {
|
|
|
244
247
|
},
|
|
245
248
|
},
|
|
246
249
|
model: undefined,
|
|
250
|
+
callerIdentity: {
|
|
251
|
+
allowPerCallOverride: true,
|
|
252
|
+
},
|
|
247
253
|
},
|
|
248
254
|
ingress: {
|
|
249
|
-
enabled:
|
|
255
|
+
enabled: undefined,
|
|
250
256
|
publicBaseUrl: '',
|
|
251
257
|
},
|
|
252
258
|
};
|