@vellumai/assistant 0.3.19 → 0.3.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +151 -15
- package/Dockerfile +1 -0
- package/README.md +40 -4
- package/docs/architecture/integrations.md +7 -11
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +54 -0
- package/src/__tests__/approval-primitive.test.ts +540 -0
- package/src/__tests__/assistant-feature-flag-guard.test.ts +206 -0
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +198 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +272 -0
- package/src/__tests__/call-controller.test.ts +439 -108
- package/src/__tests__/channel-invite-transport.test.ts +264 -0
- package/src/__tests__/cli.test.ts +42 -1
- package/src/__tests__/config-schema.test.ts +11 -127
- package/src/__tests__/config-watcher.test.ts +0 -8
- package/src/__tests__/daemon-lifecycle.test.ts +1 -0
- package/src/__tests__/daemon-server-session-init.test.ts +8 -2
- package/src/__tests__/diff.test.ts +22 -0
- package/src/__tests__/guardian-action-copy-generator.test.ts +5 -0
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +300 -32
- package/src/__tests__/guardian-action-late-reply.test.ts +546 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +774 -0
- package/src/__tests__/guardian-control-plane-policy.test.ts +36 -3
- package/src/__tests__/guardian-dispatch.test.ts +124 -0
- package/src/__tests__/guardian-grant-minting.test.ts +6 -17
- package/src/__tests__/inbound-invite-redemption.test.ts +367 -0
- package/src/__tests__/invite-redemption-service.test.ts +306 -0
- package/src/__tests__/ipc-snapshot.test.ts +57 -0
- package/src/__tests__/notification-decision-fallback.test.ts +88 -0
- package/src/__tests__/sandbox-diagnostics.test.ts +6 -249
- package/src/__tests__/sandbox-host-parity.test.ts +6 -13
- package/src/__tests__/scoped-approval-grants.test.ts +6 -6
- package/src/__tests__/scoped-grant-security-matrix.test.ts +5 -4
- package/src/__tests__/script-proxy-session-manager.test.ts +1 -19
- package/src/__tests__/session-load-history-repair.test.ts +169 -2
- package/src/__tests__/session-runtime-assembly.test.ts +33 -5
- package/src/__tests__/skill-feature-flags-integration.test.ts +171 -0
- package/src/__tests__/skill-feature-flags.test.ts +188 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +141 -0
- package/src/__tests__/skill-mirror-parity.test.ts +1 -0
- package/src/__tests__/skill-projection-feature-flag.test.ts +363 -0
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/terminal-sandbox.test.ts +142 -9
- package/src/__tests__/terminal-tools.test.ts +2 -93
- package/src/__tests__/thread-seed-composer.test.ts +18 -0
- package/src/__tests__/tool-approval-handler.test.ts +350 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +8 -10
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +46 -84
- package/src/agent/loop.ts +36 -1
- package/src/approvals/approval-primitive.ts +381 -0
- package/src/approvals/guardian-decision-primitive.ts +191 -0
- package/src/calls/call-controller.ts +252 -209
- package/src/calls/call-domain.ts +44 -6
- package/src/calls/guardian-dispatch.ts +48 -0
- package/src/calls/types.ts +1 -1
- package/src/calls/voice-session-bridge.ts +46 -30
- package/src/cli/core-commands.ts +0 -4
- package/src/cli.ts +76 -34
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +179 -0
- package/src/config/assistant-feature-flags.ts +162 -0
- package/src/config/bundled-skills/api-mapping/icon.svg +18 -0
- package/src/config/bundled-skills/messaging/TOOLS.json +30 -0
- package/src/config/bundled-skills/messaging/tools/slack-delete-message.ts +24 -0
- package/src/config/bundled-skills/notifications/SKILL.md +1 -1
- package/src/config/bundled-skills/reminder/SKILL.md +49 -2
- package/src/config/bundled-skills/time-based-actions/SKILL.md +49 -2
- package/src/config/bundled-skills/voice-setup/SKILL.md +122 -0
- package/src/config/core-schema.ts +1 -1
- package/src/config/env-registry.ts +10 -0
- package/src/config/feature-flag-registry.json +61 -0
- package/src/config/loader.ts +22 -1
- package/src/config/sandbox-schema.ts +0 -39
- package/src/config/schema.ts +6 -2
- package/src/config/skill-state.ts +34 -0
- package/src/config/skills-schema.ts +0 -1
- package/src/config/skills.ts +9 -0
- package/src/config/system-prompt.ts +110 -46
- package/src/config/templates/SOUL.md +1 -1
- package/src/config/types.ts +19 -1
- package/src/config/vellum-skills/catalog.json +1 -1
- package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +1 -0
- package/src/config/vellum-skills/sms-setup/SKILL.md +1 -1
- package/src/config/vellum-skills/telegram-setup/SKILL.md +1 -1
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +104 -3
- package/src/config/vellum-skills/twilio-setup/SKILL.md +1 -1
- package/src/daemon/config-watcher.ts +0 -1
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/guardian-invite-intent.ts +124 -0
- package/src/daemon/handlers/avatar.ts +68 -0
- package/src/daemon/handlers/browser.ts +2 -2
- package/src/daemon/handlers/guardian-actions.ts +120 -0
- package/src/daemon/handlers/index.ts +4 -0
- package/src/daemon/handlers/sessions.ts +19 -0
- package/src/daemon/handlers/shared.ts +3 -1
- package/src/daemon/install-cli-launchers.ts +58 -13
- package/src/daemon/ipc-contract/guardian-actions.ts +53 -0
- package/src/daemon/ipc-contract/sessions.ts +8 -2
- package/src/daemon/ipc-contract/settings.ts +25 -2
- package/src/daemon/ipc-contract-inventory.json +10 -0
- package/src/daemon/ipc-contract.ts +4 -0
- package/src/daemon/lifecycle.ts +6 -2
- package/src/daemon/main.ts +1 -0
- package/src/daemon/server.ts +1 -0
- package/src/daemon/session-lifecycle.ts +52 -7
- package/src/daemon/session-memory.ts +45 -0
- package/src/daemon/session-process.ts +258 -432
- package/src/daemon/session-runtime-assembly.ts +12 -0
- package/src/daemon/session-skill-tools.ts +14 -1
- package/src/daemon/session-tool-setup.ts +5 -0
- package/src/daemon/session.ts +11 -0
- package/src/daemon/tool-side-effects.ts +35 -9
- package/src/index.ts +0 -2
- package/src/memory/conversation-display-order-migration.ts +44 -0
- package/src/memory/conversation-queries.ts +2 -0
- package/src/memory/conversation-store.ts +91 -0
- package/src/memory/db-init.ts +5 -1
- package/src/memory/embedding-local.ts +13 -8
- package/src/memory/guardian-action-store.ts +125 -2
- package/src/memory/ingress-invite-store.ts +95 -1
- package/src/memory/migrations/035-guardian-action-supersession.ts +23 -0
- package/src/memory/migrations/index.ts +2 -1
- package/src/memory/schema.ts +5 -1
- package/src/memory/scoped-approval-grants.ts +14 -5
- package/src/messaging/providers/slack/client.ts +12 -0
- package/src/messaging/providers/slack/types.ts +5 -0
- package/src/notifications/decision-engine.ts +49 -12
- package/src/notifications/emit-signal.ts +7 -0
- package/src/notifications/signal.ts +7 -0
- package/src/notifications/thread-seed-composer.ts +2 -1
- package/src/runtime/channel-approval-types.ts +16 -6
- package/src/runtime/channel-approvals.ts +19 -15
- package/src/runtime/channel-invite-transport.ts +85 -0
- package/src/runtime/channel-invite-transports/telegram.ts +105 -0
- package/src/runtime/guardian-action-grant-minter.ts +92 -35
- package/src/runtime/guardian-action-message-composer.ts +30 -0
- package/src/runtime/guardian-decision-types.ts +91 -0
- package/src/runtime/http-server.ts +23 -1
- package/src/runtime/ingress-service.ts +22 -0
- package/src/runtime/invite-redemption-service.ts +181 -0
- package/src/runtime/invite-redemption-templates.ts +39 -0
- package/src/runtime/routes/call-routes.ts +2 -1
- package/src/runtime/routes/guardian-action-routes.ts +206 -0
- package/src/runtime/routes/guardian-approval-interception.ts +66 -190
- package/src/runtime/routes/inbound-message-handler.ts +486 -394
- package/src/runtime/routes/pairing-routes.ts +4 -0
- package/src/security/encrypted-store.ts +31 -17
- package/src/security/keychain.ts +176 -2
- package/src/security/secure-keys.ts +97 -0
- package/src/security/tool-approval-digest.ts +1 -1
- package/src/tools/browser/browser-execution.ts +2 -2
- package/src/tools/browser/browser-manager.ts +46 -32
- package/src/tools/browser/browser-screencast.ts +2 -2
- package/src/tools/calls/call-start.ts +1 -1
- package/src/tools/executor.ts +22 -17
- package/src/tools/network/script-proxy/session-manager.ts +1 -5
- package/src/tools/skills/load.ts +22 -8
- package/src/tools/system/avatar-generator.ts +119 -0
- package/src/tools/system/navigate-settings.ts +65 -0
- package/src/tools/system/open-system-settings.ts +75 -0
- package/src/tools/system/voice-config.ts +121 -32
- package/src/tools/terminal/backends/native.ts +40 -19
- package/src/tools/terminal/backends/types.ts +3 -3
- package/src/tools/terminal/parser.ts +1 -1
- package/src/tools/terminal/sandbox-diagnostics.ts +6 -87
- package/src/tools/terminal/sandbox.ts +1 -12
- package/src/tools/terminal/shell.ts +3 -31
- package/src/tools/tool-approval-handler.ts +141 -3
- package/src/tools/tool-manifest.ts +6 -0
- package/src/tools/types.ts +6 -0
- package/src/util/diff.ts +36 -13
- package/Dockerfile.sandbox +0 -5
- package/src/__tests__/doordash-client.test.ts +0 -187
- package/src/__tests__/doordash-session.test.ts +0 -154
- package/src/__tests__/signup-e2e.test.ts +0 -354
- package/src/__tests__/terminal-sandbox-docker.test.ts +0 -1065
- package/src/__tests__/terminal-sandbox.integration.test.ts +0 -180
- package/src/cli/doordash.ts +0 -1057
- package/src/config/bundled-skills/doordash/SKILL.md +0 -163
- package/src/config/templates/LOOKS.md +0 -25
- package/src/doordash/cart-queries.ts +0 -787
- package/src/doordash/client.ts +0 -1016
- package/src/doordash/order-queries.ts +0 -85
- package/src/doordash/queries.ts +0 -13
- package/src/doordash/query-extractor.ts +0 -94
- package/src/doordash/search-queries.ts +0 -203
- package/src/doordash/session.ts +0 -84
- package/src/doordash/store-queries.ts +0 -246
- package/src/doordash/types.ts +0 -367
- package/src/tools/terminal/backends/docker.ts +0 -379
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical assistant feature-flag resolver.
|
|
3
|
+
*
|
|
4
|
+
* Loads default flag values from the unified registry at
|
|
5
|
+
* `meta/feature-flags/feature-flag-registry.json` and resolves the effective
|
|
6
|
+
* enabled/disabled state for each declared assistant-scope flag by consulting
|
|
7
|
+
* (in priority order):
|
|
8
|
+
* 1. `config.assistantFeatureFlagValues[key]` (explicit override)
|
|
9
|
+
* 2. defaults registry `defaultEnabled` (for declared keys)
|
|
10
|
+
* 3. `true` (for undeclared keys)
|
|
11
|
+
*
|
|
12
|
+
* Key format:
|
|
13
|
+
* Canonical: `feature_flags.<id>.enabled`
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
17
|
+
import { dirname, join } from 'node:path';
|
|
18
|
+
|
|
19
|
+
import type { AssistantConfig } from './schema.js';
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Types
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
export interface FeatureFlagDefault {
|
|
26
|
+
defaultEnabled: boolean;
|
|
27
|
+
description: string;
|
|
28
|
+
label: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type FeatureFlagDefaultsRegistry = Record<string, FeatureFlagDefault>;
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Registry loading (singleton, loaded once)
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
let cachedDefaults: FeatureFlagDefaultsRegistry | undefined;
|
|
38
|
+
|
|
39
|
+
const REGISTRY_FILENAME = 'feature-flag-registry.json';
|
|
40
|
+
|
|
41
|
+
function loadDefaultsRegistry(): FeatureFlagDefaultsRegistry {
|
|
42
|
+
if (cachedDefaults) return cachedDefaults;
|
|
43
|
+
|
|
44
|
+
const thisDir = import.meta.dirname ?? __dirname;
|
|
45
|
+
const envPath = process.env.FEATURE_FLAG_DEFAULTS_PATH?.trim();
|
|
46
|
+
const candidates = [
|
|
47
|
+
// Explicit override (primarily for tests / controlled environments)
|
|
48
|
+
...(envPath ? [envPath] : []),
|
|
49
|
+
// Bundled: co-located copy in the same directory as this source file.
|
|
50
|
+
// Works in Docker / packaged builds where the repo-root `meta/` dir
|
|
51
|
+
// is not available.
|
|
52
|
+
join(thisDir, REGISTRY_FILENAME),
|
|
53
|
+
// Packaged macOS app layout: the daemon binary lives at
|
|
54
|
+
// <App>.app/Contents/MacOS/vellum-daemon and the registry is copied
|
|
55
|
+
// to <App>.app/Contents/Resources/ by build.sh. In bun --compile
|
|
56
|
+
// binaries, import.meta.dirname resolves to /$bunfs/root (virtual),
|
|
57
|
+
// so we need to resolve relative to the real executable path.
|
|
58
|
+
join(dirname(process.execPath), '..', 'Resources', REGISTRY_FILENAME),
|
|
59
|
+
// Development: relative to this source file's directory, walking up
|
|
60
|
+
// to the repo root to reach `meta/feature-flags/`.
|
|
61
|
+
join(thisDir, '..', '..', '..', 'meta', 'feature-flags', REGISTRY_FILENAME),
|
|
62
|
+
// Alternate: from repo root via cwd
|
|
63
|
+
join(process.cwd(), 'meta', 'feature-flags', REGISTRY_FILENAME),
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
for (const candidate of candidates) {
|
|
67
|
+
if (existsSync(candidate)) {
|
|
68
|
+
try {
|
|
69
|
+
const raw = readFileSync(candidate, 'utf-8');
|
|
70
|
+
const parsed = JSON.parse(raw);
|
|
71
|
+
cachedDefaults = parseRegistryToDefaults(parsed);
|
|
72
|
+
return cachedDefaults;
|
|
73
|
+
} catch {
|
|
74
|
+
// Malformed file — fall through to next candidate
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
cachedDefaults = {};
|
|
80
|
+
return cachedDefaults;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Parse the unified registry JSON into a flat key -> default map,
|
|
85
|
+
* filtering to assistant-scope flags only.
|
|
86
|
+
*/
|
|
87
|
+
function parseRegistryToDefaults(parsed: unknown): FeatureFlagDefaultsRegistry {
|
|
88
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return {};
|
|
89
|
+
|
|
90
|
+
const registry = parsed as { version?: number; flags?: unknown[] };
|
|
91
|
+
if (!Array.isArray(registry.flags)) return {};
|
|
92
|
+
|
|
93
|
+
const result: FeatureFlagDefaultsRegistry = {};
|
|
94
|
+
for (const flag of registry.flags) {
|
|
95
|
+
if (!flag || typeof flag !== 'object' || Array.isArray(flag)) continue;
|
|
96
|
+
const entry = flag as Record<string, unknown>;
|
|
97
|
+
if (entry.scope !== 'assistant') continue;
|
|
98
|
+
if (typeof entry.key !== 'string') continue;
|
|
99
|
+
if (typeof entry.defaultEnabled !== 'boolean') continue;
|
|
100
|
+
|
|
101
|
+
result[entry.key as string] = {
|
|
102
|
+
defaultEnabled: entry.defaultEnabled,
|
|
103
|
+
description: typeof entry.description === 'string' ? entry.description : '',
|
|
104
|
+
label: typeof entry.label === 'string' ? entry.label : '',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// Public API
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Resolve whether an assistant feature flag is enabled.
|
|
116
|
+
*
|
|
117
|
+
* Resolution order:
|
|
118
|
+
* 1. `config.assistantFeatureFlagValues[key]` (explicit override)
|
|
119
|
+
* 2. defaults registry `defaultEnabled` (for declared assistant-scope keys)
|
|
120
|
+
* 3. `true` (for undeclared keys with no override)
|
|
121
|
+
*/
|
|
122
|
+
export function isAssistantFeatureFlagEnabled(key: string, config: AssistantConfig): boolean {
|
|
123
|
+
const defaults = loadDefaultsRegistry();
|
|
124
|
+
const declared = defaults[key];
|
|
125
|
+
|
|
126
|
+
// 1. Check canonical section
|
|
127
|
+
const newValues = (config as AssistantConfigWithFeatureFlags).assistantFeatureFlagValues;
|
|
128
|
+
if (newValues) {
|
|
129
|
+
const explicit = newValues[key];
|
|
130
|
+
if (typeof explicit === 'boolean') return explicit;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 2. For declared keys, use the registry default
|
|
134
|
+
if (declared) {
|
|
135
|
+
return declared.defaultEnabled;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 3. Undeclared keys with no persisted override default to enabled
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Return the loaded defaults registry (for introspection/tooling).
|
|
144
|
+
*/
|
|
145
|
+
export function getAssistantFeatureFlagDefaults(): FeatureFlagDefaultsRegistry {
|
|
146
|
+
return loadDefaultsRegistry();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Reset the cached defaults registry. Intended for tests only.
|
|
151
|
+
*/
|
|
152
|
+
export function _resetDefaultsCache(): void {
|
|
153
|
+
cachedDefaults = undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
// Internal type augmentation for the new config field
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
interface AssistantConfigWithFeatureFlags extends AssistantConfig {
|
|
161
|
+
assistantFeatureFlagValues?: Record<string, boolean>;
|
|
162
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="16" height="16" fill="#0f172a"/>
|
|
3
|
+
<rect x="2" y="2" width="12" height="12" fill="#1e293b"/>
|
|
4
|
+
<rect x="3" y="3" width="10" height="10" fill="#0f172a" stroke="#3b82f6" stroke-width="1"/>
|
|
5
|
+
<rect x="4" y="4" width="2" height="2" fill="#3b82f6"/>
|
|
6
|
+
<rect x="10" y="4" width="2" height="2" fill="#3b82f6"/>
|
|
7
|
+
<rect x="4" y="10" width="2" height="2" fill="#3b82f6"/>
|
|
8
|
+
<rect x="10" y="10" width="2" height="2" fill="#3b82f6"/>
|
|
9
|
+
<line x1="5" y1="5" x2="11" y2="5" stroke="#10b981" stroke-width="1"/>
|
|
10
|
+
<line x1="5" y1="5" x2="5" y2="11" stroke="#10b981" stroke-width="1"/>
|
|
11
|
+
<line x1="11" y1="5" x2="11" y2="11" stroke="#10b981" stroke-width="1"/>
|
|
12
|
+
<line x1="5" y1="11" x2="11" y2="11" stroke="#10b981" stroke-width="1"/>
|
|
13
|
+
<rect x="7" y="7" width="2" height="2" fill="#f59e0b"/>
|
|
14
|
+
<line x1="5" y1="8" x2="7" y2="8" stroke="#60a5fa" stroke-width="1"/>
|
|
15
|
+
<line x1="9" y1="8" x2="11" y2="8" stroke="#60a5fa" stroke-width="1"/>
|
|
16
|
+
<line x1="8" y1="5" x2="8" y2="7" stroke="#60a5fa" stroke-width="1"/>
|
|
17
|
+
<line x1="8" y1="9" x2="8" y2="11" stroke="#60a5fa" stroke-width="1"/>
|
|
18
|
+
</svg>
|
|
@@ -256,6 +256,36 @@
|
|
|
256
256
|
"executor": "tools/slack-add-reaction.ts",
|
|
257
257
|
"execution_target": "host"
|
|
258
258
|
},
|
|
259
|
+
{
|
|
260
|
+
"name": "slack_delete_message",
|
|
261
|
+
"description": "Delete a Slack message posted by the bot. Include a confidence score (0-1).",
|
|
262
|
+
"category": "messaging",
|
|
263
|
+
"risk": "high",
|
|
264
|
+
"input_schema": {
|
|
265
|
+
"type": "object",
|
|
266
|
+
"properties": {
|
|
267
|
+
"channel": {
|
|
268
|
+
"type": "string",
|
|
269
|
+
"description": "Slack channel ID"
|
|
270
|
+
},
|
|
271
|
+
"timestamp": {
|
|
272
|
+
"type": "string",
|
|
273
|
+
"description": "Message timestamp (ts) to delete"
|
|
274
|
+
},
|
|
275
|
+
"confidence": {
|
|
276
|
+
"type": "number",
|
|
277
|
+
"description": "Confidence score (0-1) for this action"
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
"required": [
|
|
281
|
+
"channel",
|
|
282
|
+
"timestamp",
|
|
283
|
+
"confidence"
|
|
284
|
+
]
|
|
285
|
+
},
|
|
286
|
+
"executor": "tools/slack-delete-message.ts",
|
|
287
|
+
"execution_target": "host"
|
|
288
|
+
},
|
|
259
289
|
{
|
|
260
290
|
"name": "slack_leave_channel",
|
|
261
291
|
"description": "Leave a Slack channel. Include a confidence score (0-1).",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { deleteMessage } from '../../../../messaging/providers/slack/client.js';
|
|
2
|
+
import { getMessagingProvider } from '../../../../messaging/registry.js';
|
|
3
|
+
import { withValidToken } from '../../../../security/token-manager.js';
|
|
4
|
+
import type { ToolContext, ToolExecutionResult } from '../../../../tools/types.js';
|
|
5
|
+
import { err, ok } from './shared.js';
|
|
6
|
+
|
|
7
|
+
export async function run(input: Record<string, unknown>, _context: ToolContext): Promise<ToolExecutionResult> {
|
|
8
|
+
const channel = input.channel as string;
|
|
9
|
+
const timestamp = input.timestamp as string;
|
|
10
|
+
|
|
11
|
+
if (!channel || !timestamp) {
|
|
12
|
+
return err('channel and timestamp are both required.');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const provider = getMessagingProvider('slack');
|
|
17
|
+
return withValidToken(provider.credentialService, async (token) => {
|
|
18
|
+
await deleteMessage(token, channel, timestamp);
|
|
19
|
+
return ok(`Message deleted.`);
|
|
20
|
+
});
|
|
21
|
+
} catch (e) {
|
|
22
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -14,7 +14,7 @@ Use `send_notification` for user-facing alerts and notifications. This tool rout
|
|
|
14
14
|
|
|
15
15
|
## Deduplication (`dedupe_key`)
|
|
16
16
|
|
|
17
|
-
- `dedupe_key` suppresses duplicate signals
|
|
17
|
+
- `dedupe_key` suppresses duplicate signals **permanently**. A second notification with the same key is **dropped entirely** for the lifetime of the assistant's event store. Once a key has been used, it cannot be reused — any future notification with the same key will be silently discarded.
|
|
18
18
|
- Never reuse a `dedupe_key` across logically distinct notifications, even if they are related. The key means "this exact event already fired," not "these events are in the same category."
|
|
19
19
|
- If you omit `dedupe_key`, the LLM decision engine may generate one automatically based on signal context. This means even keyless signals can be deduplicated if the engine considers them duplicates of a recent event.
|
|
20
20
|
|
|
@@ -15,12 +15,34 @@ Create, list, and cancel one-time reminders. Reminders fire at a specific future
|
|
|
15
15
|
|
|
16
16
|
Control how the reminder is delivered at trigger time with `routing_intent`:
|
|
17
17
|
|
|
18
|
-
- **single_channel**
|
|
18
|
+
- **single_channel** — deliver to one best channel
|
|
19
19
|
- **multi_channel** — deliver to a subset of channels
|
|
20
|
-
- **all_channels** — deliver to every available channel
|
|
20
|
+
- **all_channels** (default) — deliver to every available channel
|
|
21
21
|
|
|
22
22
|
Optionally pass `routing_hints` (a JSON object) to influence routing decisions (e.g. preferred channels, exclusions). When omitted, defaults to `{}`.
|
|
23
23
|
|
|
24
|
+
### Routing Defaults
|
|
25
|
+
|
|
26
|
+
Use the following heuristics to pick `routing_intent`:
|
|
27
|
+
|
|
28
|
+
- **Default to `all_channels`** for most reminders. Users setting reminders usually want to be notified wherever they are, and redundant notifications are less harmful than missed ones.
|
|
29
|
+
- **Use `single_channel`** only when the user explicitly specifies a single channel (e.g. "remind me on Telegram") or the reminder is low-stakes and noise reduction matters.
|
|
30
|
+
- **Check `user_message_channel`** from the turn context. If the user is currently active on a specific channel (e.g. `vellum`), always include that channel. Pass it as a routing hint:
|
|
31
|
+
```
|
|
32
|
+
routing_hints: { preferred_channels: ["vellum"] }
|
|
33
|
+
routing_intent: "all_channels"
|
|
34
|
+
```
|
|
35
|
+
- **Never use `single_channel` as a passive default.** If you haven't thought about which channel to use, use `all_channels`.
|
|
36
|
+
|
|
37
|
+
### Examples
|
|
38
|
+
|
|
39
|
+
| Scenario | routing_intent | routing_hints |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| User sets reminder from desktop app | `all_channels` | `{ preferred_channels: ["vellum"] }` |
|
|
42
|
+
| User says "remind me on Telegram" | `single_channel` | `{ preferred_channels: ["telegram"] }` |
|
|
43
|
+
| User sets reminder from Telegram | `all_channels` | `{ preferred_channels: ["telegram"] }` |
|
|
44
|
+
| No channel preference expressed | `all_channels` | `{}` |
|
|
45
|
+
|
|
24
46
|
## Usage Notes
|
|
25
47
|
|
|
26
48
|
- Use reminders ONLY for time-triggered notifications (e.g. "remind me at 3pm", "remind me in 2 hours").
|
|
@@ -28,3 +50,28 @@ Optionally pass `routing_hints` (a JSON object) to influence routing decisions (
|
|
|
28
50
|
- For task tracking ("add to my tasks", "add to my queue"), use task_list_add instead.
|
|
29
51
|
- `fire_at` must be a strict ISO 8601 timestamp with timezone offset or Z (e.g. `2025-03-15T09:00:00-05:00` or `2025-03-15T09:00:00Z`). Ambiguous timestamps without timezone info will be rejected.
|
|
30
52
|
- `label` is a short human-readable summary shown in the notification.
|
|
53
|
+
|
|
54
|
+
### Anchored & Ambiguous Relative Time
|
|
55
|
+
|
|
56
|
+
Phrases like "at the 45 minute mark", "at the top of the hour", "on the half-hour", "at noon", "20 minutes in", or "when I hit an hour" are **clock-position or anchored relative time** expressions. Do NOT treat them as offsets from now.
|
|
57
|
+
|
|
58
|
+
**Resolution rules (in priority order):**
|
|
59
|
+
|
|
60
|
+
1. **Clock-position expressions** — map directly to a wall-clock time:
|
|
61
|
+
- "top of the hour" / "on the hour" → next :00 (e.g. 10:00 AM)
|
|
62
|
+
- "the X minute mark" / "at :XX" → current hour's :XX; if already past, advance one hour
|
|
63
|
+
- "the half-hour mark" / "half past" → nearest upcoming :30
|
|
64
|
+
- "noon" / "midnight" → 12:00 PM or 12:00 AM today; if past, tomorrow
|
|
65
|
+
- "quarter past" / "quarter to" → :15 or :45 of current or next hour
|
|
66
|
+
|
|
67
|
+
2. **Session-anchored expressions** — if the user mentioned a start time earlier in conversation ("I got here at 9", "meeting started at 2pm"), compute `start_time + offset`.
|
|
68
|
+
|
|
69
|
+
3. **Ask only if truly ambiguous** — if neither rule 1 nor rule 2 resolves, ask: "Do you mean [clock time] or [X minutes from now]?" Never silently default to "from now."
|
|
70
|
+
|
|
71
|
+
**Examples:**
|
|
72
|
+
- "at the 45 min mark" (now: 9:39) → 9:45 AM
|
|
73
|
+
- "at the 45 min mark" (now: 9:50) → 10:45 AM
|
|
74
|
+
- "top of the hour" (now: 9:39) → 10:00 AM
|
|
75
|
+
- "at noon" → 12:00 PM today
|
|
76
|
+
- "20 minutes in, I started at 2pm" → 2:20 PM
|
|
77
|
+
- "at the hour mark" with no start time → ask for clarification
|
|
@@ -55,6 +55,31 @@ When the user says "in X minutes/hours", compute the ISO 8601 timestamp yourself
|
|
|
55
55
|
- Format as ISO 8601 with timezone: `2025-03-15T09:05:00-05:00`
|
|
56
56
|
- Pass to `reminder_create` as `fire_at`
|
|
57
57
|
|
|
58
|
+
### Anchored & Ambiguous Relative Time
|
|
59
|
+
|
|
60
|
+
Phrases like "at the 45 minute mark", "at the top of the hour", "on the half-hour", "at noon", "20 minutes in", or "when I hit an hour" are **clock-position or anchored relative time** expressions. Do NOT treat them as offsets from now.
|
|
61
|
+
|
|
62
|
+
**Resolution rules (in priority order):**
|
|
63
|
+
|
|
64
|
+
1. **Clock-position expressions** — map directly to a wall-clock time:
|
|
65
|
+
- "top of the hour" / "on the hour" → next :00 (e.g. 10:00 AM)
|
|
66
|
+
- "the X minute mark" / "at :XX" → current hour's :XX; if already past, advance one hour
|
|
67
|
+
- "the half-hour mark" / "half past" → nearest upcoming :30
|
|
68
|
+
- "noon" / "midnight" → 12:00 PM or 12:00 AM today; if past, tomorrow
|
|
69
|
+
- "quarter past" / "quarter to" → :15 or :45 of current or next hour
|
|
70
|
+
|
|
71
|
+
2. **Session-anchored expressions** — if the user mentioned a start time earlier in conversation ("I got here at 9", "meeting started at 2pm"), compute `start_time + offset`.
|
|
72
|
+
|
|
73
|
+
3. **Ask only if truly ambiguous** — if neither rule 1 nor rule 2 resolves, ask: "Do you mean [clock time] or [X minutes from now]?" Never silently default to "from now."
|
|
74
|
+
|
|
75
|
+
**Examples:**
|
|
76
|
+
- "at the 45 min mark" (now: 9:39) → 9:45 AM
|
|
77
|
+
- "at the 45 min mark" (now: 9:50) → 10:45 AM
|
|
78
|
+
- "top of the hour" (now: 9:39) → 10:00 AM
|
|
79
|
+
- "at noon" → 12:00 PM today
|
|
80
|
+
- "20 minutes in, I started at 2pm" → 2:20 PM
|
|
81
|
+
- "at the hour mark" with no start time → ask for clarification
|
|
82
|
+
|
|
58
83
|
## "Remind me to X" Disambiguation
|
|
59
84
|
|
|
60
85
|
The word "remind" is ambiguous. Route based on whether a time is specified:
|
|
@@ -77,12 +102,34 @@ Use `notify` for simple alerts. Use `execute` when the reminder should trigger t
|
|
|
77
102
|
## Reminder Routing
|
|
78
103
|
|
|
79
104
|
`reminder_create` supports a `routing_intent` parameter that controls how the reminder is delivered at trigger time:
|
|
80
|
-
- **`single_channel`**
|
|
105
|
+
- **`single_channel`** — deliver to one best channel
|
|
81
106
|
- **`multi_channel`** — deliver to a subset of channels
|
|
82
|
-
- **`all_channels`** — deliver to every available channel
|
|
107
|
+
- **`all_channels`** (default) — deliver to every available channel
|
|
83
108
|
|
|
84
109
|
You can also pass `routing_hints` (a JSON object) to influence routing decisions (e.g. preferred channels, exclusions).
|
|
85
110
|
|
|
111
|
+
### Routing Defaults
|
|
112
|
+
|
|
113
|
+
Use the following heuristics to pick `routing_intent`:
|
|
114
|
+
|
|
115
|
+
- **Default to `all_channels`** for most reminders. Users setting reminders usually want to be notified wherever they are, and redundant notifications are less harmful than missed ones.
|
|
116
|
+
- **Use `single_channel`** only when the user explicitly specifies a single channel (e.g. "remind me on Telegram") or the reminder is low-stakes and noise reduction matters.
|
|
117
|
+
- **Check `user_message_channel`** from the turn context. If the user is currently active on a specific channel (e.g. `vellum`), always include that channel. Pass it as a routing hint:
|
|
118
|
+
```
|
|
119
|
+
routing_hints: { preferred_channels: ["vellum"] }
|
|
120
|
+
routing_intent: "all_channels"
|
|
121
|
+
```
|
|
122
|
+
- **Never use `single_channel` as a passive default.** If you haven't thought about which channel to use, use `all_channels`.
|
|
123
|
+
|
|
124
|
+
### Examples
|
|
125
|
+
|
|
126
|
+
| Scenario | routing_intent | routing_hints |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| User sets reminder from desktop app | `all_channels` | `{ preferred_channels: ["vellum"] }` |
|
|
129
|
+
| User says "remind me on Telegram" | `single_channel` | `{ preferred_channels: ["telegram"] }` |
|
|
130
|
+
| User sets reminder from Telegram | `all_channels` | `{ preferred_channels: ["telegram"] }` |
|
|
131
|
+
| No channel preference expressed | `all_channels` | `{}` |
|
|
132
|
+
|
|
86
133
|
## Tool Summary
|
|
87
134
|
|
|
88
135
|
| Tool | Timing | Recurrence | Purpose |
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "Voice Setup"
|
|
3
|
+
description: "Complete voice configuration in chat — PTT key, wake word, microphone permissions, ElevenLabs TTS, and troubleshooting"
|
|
4
|
+
user-invocable: true
|
|
5
|
+
metadata: {"vellum": {"emoji": "🎙️", "os": ["darwin"]}}
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are helping the user set up and troubleshoot voice features (push-to-talk, wake word, text-to-speech) entirely within this conversation. Do NOT direct the user to the Settings page for initial setup — handle everything in-chat using the tools below.
|
|
9
|
+
|
|
10
|
+
## Available Tools
|
|
11
|
+
|
|
12
|
+
- `voice_config_update` — Change any voice setting (PTT key, wake word enabled/keyword/timeout)
|
|
13
|
+
- `open_system_settings` — Open macOS System Settings to a specific privacy pane
|
|
14
|
+
- `navigate_settings_tab` — Open the Vellum settings panel to the Voice tab
|
|
15
|
+
- `credential_store` — Collect API keys securely (for ElevenLabs TTS)
|
|
16
|
+
|
|
17
|
+
## Setup Flow
|
|
18
|
+
|
|
19
|
+
Walk the user through each section in order. Skip sections they don't need. Ask before proceeding to the next section.
|
|
20
|
+
|
|
21
|
+
### 1. Microphone Permission
|
|
22
|
+
|
|
23
|
+
Check `<channel_capabilities>` for `microphone_permission_granted`.
|
|
24
|
+
|
|
25
|
+
**If `false` or missing:**
|
|
26
|
+
1. Explain that macOS requires microphone permission for voice features.
|
|
27
|
+
2. Use `open_system_settings` with `pane: "microphone"` to open the right System Settings pane.
|
|
28
|
+
3. Tell the user: "I've opened System Settings to the Microphone section. Please toggle **Vellum Assistant** on, then come back here."
|
|
29
|
+
4. After they confirm, verify by checking capabilities on the next turn.
|
|
30
|
+
|
|
31
|
+
**If `true`:** Tell them microphone is already granted and move on.
|
|
32
|
+
|
|
33
|
+
### 2. Push-to-Talk Activation Key
|
|
34
|
+
|
|
35
|
+
Present common PTT key options:
|
|
36
|
+
- **Right Option (⌥)** — Default, good general choice
|
|
37
|
+
- **Fn** — Dedicated key on most Mac keyboards
|
|
38
|
+
- **Right Command (⌘)** — Easy to reach
|
|
39
|
+
- **Right Control (⌃)** — Familiar from gaming
|
|
40
|
+
|
|
41
|
+
Ask which key they prefer, then use `voice_config_update` with `setting: "activation_key"` and the chosen value.
|
|
42
|
+
|
|
43
|
+
**Common issues to mention:**
|
|
44
|
+
- If they pick a key that conflicts with their emoji picker (Fn or Globe on newer Macs), warn them and suggest an alternative.
|
|
45
|
+
- If they use a terminal app heavily, warn that some keys may be captured by the terminal.
|
|
46
|
+
|
|
47
|
+
### 3. Wake Word (Optional)
|
|
48
|
+
|
|
49
|
+
Ask if they want to enable wake word detection (hands-free activation by saying a keyword).
|
|
50
|
+
|
|
51
|
+
**If yes:**
|
|
52
|
+
1. Use `voice_config_update` with `setting: "wake_word_enabled"`, `value: true`.
|
|
53
|
+
2. Ask what wake word they want. Common choices: "Hey Vellum", "Computer", "Jarvis", their assistant's name.
|
|
54
|
+
3. Use `voice_config_update` with `setting: "wake_word_keyword"` and their chosen word.
|
|
55
|
+
4. Ask about timeout (how long the mic stays active after wake word). Options: 5s, 10s (default), 15s, 30s, 60s.
|
|
56
|
+
5. Use `voice_config_update` with `setting: "wake_word_timeout"` and their chosen value.
|
|
57
|
+
|
|
58
|
+
**Speech Recognition permission:** Wake word requires Speech Recognition access. Check capabilities — if not granted, use `open_system_settings` with `pane: "speech_recognition"`.
|
|
59
|
+
|
|
60
|
+
### 4. Text-to-Speech / ElevenLabs (Optional)
|
|
61
|
+
|
|
62
|
+
Ask if they want high-quality text-to-speech voices via ElevenLabs (optional — standard TTS works without it).
|
|
63
|
+
|
|
64
|
+
**If yes:**
|
|
65
|
+
1. Tell them they need an ElevenLabs API key. They can get one at https://elevenlabs.io (free tier available).
|
|
66
|
+
2. Use `credential_store` with `action: "prompt"`, `service: "elevenlabs"`, `field: "api_key"` to show a secure input dialog.
|
|
67
|
+
3. After the key is stored, confirm success.
|
|
68
|
+
|
|
69
|
+
### 5. Verification
|
|
70
|
+
|
|
71
|
+
After setup is complete:
|
|
72
|
+
1. Summarize what was configured.
|
|
73
|
+
2. Suggest they test by pressing their PTT key (or saying their wake word) and speaking.
|
|
74
|
+
3. Offer to open the Voice settings tab if they want to review: use `navigate_settings_tab` with `tab: "Voice"`.
|
|
75
|
+
|
|
76
|
+
## Troubleshooting Decision Trees
|
|
77
|
+
|
|
78
|
+
When the user reports a problem, follow the appropriate decision tree:
|
|
79
|
+
|
|
80
|
+
### "PTT isn't working" / "Can't record"
|
|
81
|
+
1. **Microphone permission** — Check `microphone_permission_granted` in capabilities. If false, guide through granting it.
|
|
82
|
+
2. **Key check** — Ask what key they're using. Confirm it matches their configured PTT key.
|
|
83
|
+
3. **Emoji picker conflict** — On newer Macs, Fn/Globe opens the emoji picker. If they're using Fn, suggest switching to Right Option or Right Command.
|
|
84
|
+
4. **Speech Recognition permission** — Some voice features need this. Use `open_system_settings` with `pane: "speech_recognition"`.
|
|
85
|
+
5. **App focus** — PTT may not work when Vellum is not the frontmost app or if another app has captured the key.
|
|
86
|
+
|
|
87
|
+
### "Recording but no text" / "Transcription not working"
|
|
88
|
+
1. **Speech Recognition permission** — Must be granted for transcription.
|
|
89
|
+
2. **Microphone input** — Ask if they see the recording indicator. If yes, the mic works but transcription is failing.
|
|
90
|
+
3. **Locale/language** — Speech recognition works best with the system language. Ask if they're speaking in a different language.
|
|
91
|
+
4. **Background noise** — Excessive noise can prevent transcription. Suggest a quieter environment or a closer microphone.
|
|
92
|
+
|
|
93
|
+
### "Wake word not detecting"
|
|
94
|
+
1. **Enabled check** — Confirm wake word is enabled in their settings.
|
|
95
|
+
2. **Keyword** — Confirm what keyword they're using. Shorter or common words may have lower accuracy.
|
|
96
|
+
3. **Ambient noise** — Wake word detection is sensitive to background noise.
|
|
97
|
+
4. **Permissions** — Both Microphone and Speech Recognition permissions are required.
|
|
98
|
+
5. **Timeout** — If wake word activates but cuts off too quickly, increase the timeout.
|
|
99
|
+
|
|
100
|
+
### "Changed a setting but it didn't work"
|
|
101
|
+
1. **IPC broadcast** — The setting should take effect immediately. If it didn't, suggest restarting the assistant.
|
|
102
|
+
2. **Verify** — Open the Voice settings tab with `navigate_settings_tab` to confirm the setting was persisted.
|
|
103
|
+
|
|
104
|
+
## Deep Debugging
|
|
105
|
+
|
|
106
|
+
For persistent issues, suggest checking system logs:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
log stream --predicate 'subsystem == "com.vellum.assistant"' --level debug
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Key log categories:
|
|
113
|
+
- `voice` — PTT activation, recording state
|
|
114
|
+
- `wake-word` — Wake word detection events
|
|
115
|
+
- `speech` — Speech recognition results
|
|
116
|
+
|
|
117
|
+
## Rules
|
|
118
|
+
|
|
119
|
+
- Always handle setup conversationally in-chat. Do NOT tell the user to go to Settings for initial configuration.
|
|
120
|
+
- Use `navigate_settings_tab` only for review/verification after in-chat setup, not as the primary setup method.
|
|
121
|
+
- Be concise. Don't explain every option exhaustively — present the most common choices and let the user ask for more.
|
|
122
|
+
- If a permission is denied, acknowledge it gracefully and explain what features won't work without it.
|
|
@@ -124,7 +124,7 @@ export const ContextWindowConfigSchema = z.object({
|
|
|
124
124
|
.number({ error: 'contextWindow.maxInputTokens must be a number' })
|
|
125
125
|
.int('contextWindow.maxInputTokens must be an integer')
|
|
126
126
|
.positive('contextWindow.maxInputTokens must be a positive integer')
|
|
127
|
-
.default(
|
|
127
|
+
.default(200000),
|
|
128
128
|
targetInputTokens: z
|
|
129
129
|
.number({ error: 'contextWindow.targetInputTokens must be a number' })
|
|
130
130
|
.int('contextWindow.targetInputTokens must be an integer')
|
|
@@ -124,6 +124,16 @@ export function getEnableMonitoring(): boolean {
|
|
|
124
124
|
return flag('VELLUM_ENABLE_MONITORING');
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* IS_CONTAINERIZED — boolean, default: false
|
|
129
|
+
* When true, indicates the assistant is running inside a container (e.g. Docker).
|
|
130
|
+
* Any new data that needs to survive restarts must be written to BASE_DATA_DIR,
|
|
131
|
+
* which is mapped to a persistent volume.
|
|
132
|
+
*/
|
|
133
|
+
export function getIsContainerized(): boolean {
|
|
134
|
+
return flag('IS_CONTAINERIZED');
|
|
135
|
+
}
|
|
136
|
+
|
|
127
137
|
// ── Known env var names ──────────────────────────────────────────────────────
|
|
128
138
|
|
|
129
139
|
/**
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"flags": [
|
|
4
|
+
{
|
|
5
|
+
"id": "sms",
|
|
6
|
+
"scope": "assistant",
|
|
7
|
+
"key": "feature_flags.sms.enabled",
|
|
8
|
+
"label": "SMS",
|
|
9
|
+
"description": "Show SMS setup in Settings > Connect and enable the SMS setup skill",
|
|
10
|
+
"defaultEnabled": true
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "hatch-new-assistant",
|
|
14
|
+
"scope": "assistant",
|
|
15
|
+
"key": "feature_flags.hatch-new-assistant.enabled",
|
|
16
|
+
"label": "Hatch New Assistant",
|
|
17
|
+
"description": "Show Hatch New Assistant action in Settings > Account",
|
|
18
|
+
"defaultEnabled": true
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "guardian-verify-setup",
|
|
22
|
+
"scope": "assistant",
|
|
23
|
+
"key": "feature_flags.guardian-verify-setup.enabled",
|
|
24
|
+
"label": "Guardian Verification Setup",
|
|
25
|
+
"description": "Enable guardian verification routing in the system prompt",
|
|
26
|
+
"defaultEnabled": true
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "browser",
|
|
30
|
+
"scope": "assistant",
|
|
31
|
+
"key": "feature_flags.browser.enabled",
|
|
32
|
+
"label": "Browser",
|
|
33
|
+
"description": "Enable browser skill prerequisites section in the system prompt",
|
|
34
|
+
"defaultEnabled": true
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "twitter",
|
|
38
|
+
"scope": "assistant",
|
|
39
|
+
"key": "feature_flags.twitter.enabled",
|
|
40
|
+
"label": "Twitter",
|
|
41
|
+
"description": "Enable X (Twitter) skill section in the system prompt",
|
|
42
|
+
"defaultEnabled": true
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "user-hosted-enabled",
|
|
46
|
+
"scope": "macos",
|
|
47
|
+
"key": "user_hosted_enabled",
|
|
48
|
+
"label": "User Hosted Enabled",
|
|
49
|
+
"description": "Enable user-hosted onboarding flow",
|
|
50
|
+
"defaultEnabled": false
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "local-http-enabled",
|
|
54
|
+
"scope": "macos",
|
|
55
|
+
"key": "local_http_enabled",
|
|
56
|
+
"label": "Local HTTP Enabled",
|
|
57
|
+
"description": "Enable local HTTP transport mode in macOS app",
|
|
58
|
+
"defaultEnabled": false
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
package/src/config/loader.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { existsSync, readFileSync, statSync,writeFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
2
3
|
|
|
3
4
|
import { deleteSecureKey,getSecureKey, setSecureKey } from '../security/secure-keys.js';
|
|
4
5
|
import { ConfigError } from '../util/errors.js';
|
|
@@ -113,6 +114,7 @@ export function loadConfig(): AssistantConfig {
|
|
|
113
114
|
const configPath = getConfigPath();
|
|
114
115
|
|
|
115
116
|
let fileConfig: Record<string, unknown> = {};
|
|
117
|
+
let configFileExisted = true;
|
|
116
118
|
if (existsSync(configPath)) {
|
|
117
119
|
const mode = statSync(configPath).mode;
|
|
118
120
|
if (mode & 0o077) {
|
|
@@ -127,6 +129,8 @@ export function loadConfig(): AssistantConfig {
|
|
|
127
129
|
} catch (err) {
|
|
128
130
|
throw new ConfigError(`Failed to parse config at ${configPath}: ${err}`);
|
|
129
131
|
}
|
|
132
|
+
} else {
|
|
133
|
+
configFileExisted = false;
|
|
130
134
|
}
|
|
131
135
|
|
|
132
136
|
// Pre-validate apiKeys shape before migration (must be a plain object)
|
|
@@ -182,6 +186,23 @@ export function loadConfig(): AssistantConfig {
|
|
|
182
186
|
// Validate and apply defaults via Zod schema
|
|
183
187
|
const config = validateWithSchema(fileConfig);
|
|
184
188
|
|
|
189
|
+
// If the config file didn't exist, write the full defaults to disk so
|
|
190
|
+
// users can discover and edit all available options.
|
|
191
|
+
if (!configFileExisted) {
|
|
192
|
+
try {
|
|
193
|
+
const dir = dirname(configPath);
|
|
194
|
+
if (!existsSync(dir)) {
|
|
195
|
+
mkdirSync(dir, { recursive: true });
|
|
196
|
+
}
|
|
197
|
+
// Strip apiKeys (managed in secure storage) and dataDir (runtime-derived)
|
|
198
|
+
const { apiKeys: _, dataDir: _d, ...persistable } = config;
|
|
199
|
+
writeFileSync(configPath, JSON.stringify(persistable, null, 2) + '\n');
|
|
200
|
+
log.info('Wrote default config to %s', configPath);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
log.warn({ err }, 'Failed to write default config file');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
185
206
|
// Set cached before secure-key/env overrides so re-entrant calls
|
|
186
207
|
// return the in-flight config instead of bare defaults.
|
|
187
208
|
cached = config;
|