vellum 0.2.11 → 0.2.12
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/bun.lock +6 -2
- package/package.json +2 -2
- package/src/__tests__/gateway-only-enforcement.test.ts +9 -35
- package/src/__tests__/oauth2-gateway-transport.test.ts +14 -33
- package/src/__tests__/skills.test.ts +2 -2
- package/src/__tests__/twilio-routes.test.ts +78 -153
- package/src/__tests__/twitter-auth-handler.test.ts +1 -1
- package/src/cli/main-screen.tsx +15 -117
- package/src/config/bundled-skills/macos-automation/SKILL.md +66 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +334 -0
- package/src/config/system-prompt.ts +9 -59
- package/src/daemon/lifecycle.ts +20 -7
- package/src/memory/db.ts +36 -0
- package/src/security/oauth2.ts +8 -8
- package/src/util/logger.ts +4 -4
package/src/memory/db.ts
CHANGED
|
@@ -592,6 +592,42 @@ export function initializeDb(): void {
|
|
|
592
592
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_items_scope_id ON memory_items(scope_id)`);
|
|
593
593
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_memory_summaries_scope_id ON memory_summaries(scope_id)`);
|
|
594
594
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_conversation_keys_key ON conversation_keys(conversation_key)`);
|
|
595
|
+
// Deduplicate before creating unique index — existing DBs may have duplicate content_hash values.
|
|
596
|
+
// Re-point message_attachments to the survivor (MIN rowid per content_hash), then delete dupes.
|
|
597
|
+
{
|
|
598
|
+
const raw = (database as unknown as { $client: Database }).$client;
|
|
599
|
+
raw.exec(/*sql*/ `
|
|
600
|
+
UPDATE message_attachments
|
|
601
|
+
SET attachment_id = (
|
|
602
|
+
SELECT a_survivor.id
|
|
603
|
+
FROM attachments a_survivor
|
|
604
|
+
WHERE a_survivor.content_hash = (
|
|
605
|
+
SELECT a_dup.content_hash FROM attachments a_dup
|
|
606
|
+
WHERE a_dup.id = message_attachments.attachment_id
|
|
607
|
+
)
|
|
608
|
+
ORDER BY a_survivor.rowid
|
|
609
|
+
LIMIT 1
|
|
610
|
+
)
|
|
611
|
+
WHERE attachment_id IN (
|
|
612
|
+
SELECT id FROM attachments
|
|
613
|
+
WHERE content_hash IS NOT NULL
|
|
614
|
+
AND rowid NOT IN (
|
|
615
|
+
SELECT MIN(rowid) FROM attachments
|
|
616
|
+
WHERE content_hash IS NOT NULL
|
|
617
|
+
GROUP BY content_hash
|
|
618
|
+
)
|
|
619
|
+
)
|
|
620
|
+
`);
|
|
621
|
+
raw.exec(/*sql*/ `
|
|
622
|
+
DELETE FROM attachments
|
|
623
|
+
WHERE content_hash IS NOT NULL
|
|
624
|
+
AND rowid NOT IN (
|
|
625
|
+
SELECT MIN(rowid) FROM attachments
|
|
626
|
+
WHERE content_hash IS NOT NULL
|
|
627
|
+
GROUP BY content_hash
|
|
628
|
+
)
|
|
629
|
+
`);
|
|
630
|
+
}
|
|
595
631
|
database.run(/*sql*/ `CREATE UNIQUE INDEX IF NOT EXISTS idx_attachments_content_dedup ON attachments(content_hash) WHERE content_hash IS NOT NULL`);
|
|
596
632
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_message_attachments_message_id ON message_attachments(message_id)`);
|
|
597
633
|
database.run(/*sql*/ `CREATE INDEX IF NOT EXISTS idx_message_attachments_attachment_id ON message_attachments(attachment_id)`);
|
package/src/security/oauth2.ts
CHANGED
|
@@ -103,7 +103,7 @@ async function exchangeCodeForTokens(
|
|
|
103
103
|
|
|
104
104
|
if (!tokenResp.ok) {
|
|
105
105
|
const rawBody = await tokenResp.text().catch(() => '');
|
|
106
|
-
|
|
106
|
+
const safeDetail: Record<string, unknown> = {};
|
|
107
107
|
let errorCode = '';
|
|
108
108
|
try {
|
|
109
109
|
const parsed = JSON.parse(rawBody) as Record<string, unknown>;
|
|
@@ -149,9 +149,9 @@ async function runGatewayFlow(
|
|
|
149
149
|
codeChallenge: string,
|
|
150
150
|
state: string,
|
|
151
151
|
): Promise<OAuth2FlowResult> {
|
|
152
|
-
const { loadConfig } =
|
|
153
|
-
const { getOAuthCallbackUrl } =
|
|
154
|
-
const { registerPendingCallback } =
|
|
152
|
+
const { loadConfig } = await import('../config/loader.js');
|
|
153
|
+
const { getOAuthCallbackUrl } = await import('../inbound/public-ingress-urls.js');
|
|
154
|
+
const { registerPendingCallback } = await import('./oauth-callback-registry.js');
|
|
155
155
|
|
|
156
156
|
const appConfig = loadConfig();
|
|
157
157
|
const redirectUri = getOAuthCallbackUrl(appConfig);
|
|
@@ -193,7 +193,7 @@ async function runGatewayFlow(
|
|
|
193
193
|
export async function startOAuth2Flow(
|
|
194
194
|
config: OAuth2Config,
|
|
195
195
|
callbacks: OAuth2FlowCallbacks,
|
|
196
|
-
|
|
196
|
+
_options?: OAuth2FlowOptions,
|
|
197
197
|
): Promise<OAuth2FlowResult> {
|
|
198
198
|
const codeVerifier = generateCodeVerifier();
|
|
199
199
|
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
@@ -202,8 +202,8 @@ export async function startOAuth2Flow(
|
|
|
202
202
|
// Always enforce gateway transport and require a public ingress URL
|
|
203
203
|
let hasPublicUrl = false;
|
|
204
204
|
try {
|
|
205
|
-
const { loadConfig } =
|
|
206
|
-
const { getPublicBaseUrl } =
|
|
205
|
+
const { loadConfig } = await import('../config/loader.js');
|
|
206
|
+
const { getPublicBaseUrl } = await import('../inbound/public-ingress-urls.js');
|
|
207
207
|
getPublicBaseUrl(loadConfig());
|
|
208
208
|
hasPublicUrl = true;
|
|
209
209
|
} catch {
|
|
@@ -248,7 +248,7 @@ export async function refreshOAuth2Token(
|
|
|
248
248
|
|
|
249
249
|
if (!resp.ok) {
|
|
250
250
|
const rawBody = await resp.text().catch(() => '');
|
|
251
|
-
|
|
251
|
+
const safeDetail: Record<string, unknown> = {};
|
|
252
252
|
let errorCode = '';
|
|
253
253
|
try {
|
|
254
254
|
const parsed = JSON.parse(rawBody) as Record<string, unknown>;
|
package/src/util/logger.ts
CHANGED
|
@@ -55,7 +55,7 @@ let activeLogFileConfig: LogFileConfig | null = null;
|
|
|
55
55
|
|
|
56
56
|
function buildRotatingLogger(config: LogFileConfig): pino.Logger {
|
|
57
57
|
if (!config.dir) {
|
|
58
|
-
return pino({ name: 'assistant' });
|
|
58
|
+
return pino({ name: 'assistant' }, pinoPretty({ destination: 1 }));
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
if (!existsSync(config.dir)) {
|
|
@@ -86,7 +86,7 @@ function buildRotatingLogger(config: LogFileConfig): pino.Logger {
|
|
|
86
86
|
{ name: 'assistant', level },
|
|
87
87
|
pino.multistream([
|
|
88
88
|
{ stream: fileStream, level: 'info' as const },
|
|
89
|
-
{ stream:
|
|
89
|
+
{ stream: pinoPretty({ destination: 1 }), level: 'info' as const },
|
|
90
90
|
]),
|
|
91
91
|
);
|
|
92
92
|
}
|
|
@@ -142,14 +142,14 @@ function getRootLogger(): pino.Logger {
|
|
|
142
142
|
{ level: 'info' },
|
|
143
143
|
pino.multistream([
|
|
144
144
|
{ stream: fileStream, level: 'info' as const },
|
|
145
|
-
{ stream:
|
|
145
|
+
{ stream: pinoPretty({ destination: 1 }), level: 'info' as const },
|
|
146
146
|
]),
|
|
147
147
|
);
|
|
148
148
|
} else {
|
|
149
149
|
rootLogger = pino({ level: 'info' }, fileStream);
|
|
150
150
|
}
|
|
151
151
|
} catch {
|
|
152
|
-
rootLogger = pino({ level: process.env.VELLUM_DEBUG === '1' ? 'debug' : 'info' },
|
|
152
|
+
rootLogger = pino({ level: process.env.VELLUM_DEBUG === '1' ? 'debug' : 'info' }, pinoPretty({ destination: 2 }));
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
return rootLogger;
|