@vellumai/assistant 0.5.7 → 0.5.8
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/Dockerfile +2 -1
- package/docker-entrypoint.sh +9 -0
- package/docs/architecture/memory.md +13 -11
- package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/approval-cascade.test.ts +0 -1
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/ces-rpc-credential-backend.test.ts +3 -3
- package/src/__tests__/ces-startup-timeout.test.ts +40 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-schema.test.ts +2 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop.test.ts +2 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
- package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
- package/src/__tests__/conversation-queue.test.ts +0 -1
- package/src/__tests__/conversation-slash-queue.test.ts +0 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/credential-execution-client.test.ts +5 -2
- package/src/__tests__/credential-execution-feature-gates.test.ts +31 -16
- package/src/__tests__/credential-execution-managed-contract.test.ts +2 -2
- package/src/__tests__/credential-security-e2e.test.ts +1 -1
- package/src/__tests__/credential-security-invariants.test.ts +2 -5
- package/src/__tests__/credentials-cli.test.ts +4 -3
- package/src/__tests__/daemon-credential-client.test.ts +123 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
- package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
- package/src/__tests__/journal-context.test.ts +335 -0
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
- package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
- package/src/__tests__/memory-recall-quality.test.ts +48 -17
- package/src/__tests__/memory-regressions.test.ts +408 -363
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
- package/src/__tests__/non-member-access-request.test.ts +2 -2
- package/src/__tests__/notification-decision-strategy.test.ts +71 -0
- package/src/__tests__/oauth-cli.test.ts +5 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
- package/src/__tests__/provider-error-scenarios.test.ts +0 -267
- package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
- package/src/__tests__/relay-server.test.ts +1 -2
- package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -1
- package/src/__tests__/secure-keys.test.ts +18 -15
- package/src/__tests__/skill-memory.test.ts +17 -3
- package/src/__tests__/stale-approval-dedup.test.ts +171 -0
- package/src/__tests__/stt-hints.test.ts +437 -0
- package/src/__tests__/task-memory-cleanup.test.ts +14 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
- package/src/__tests__/voice-quality.test.ts +58 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +5 -3
- package/src/acp/agent-process.ts +9 -1
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-request-resolvers.ts +164 -38
- package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
- package/src/calls/call-controller.ts +9 -5
- package/src/calls/fish-audio-client.ts +26 -14
- package/src/calls/stt-hints.ts +189 -0
- package/src/calls/tts-text-sanitizer.ts +61 -0
- package/src/calls/twilio-routes.ts +32 -4
- package/src/calls/voice-quality.ts +15 -3
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/avatar.ts +2 -2
- package/src/cli/commands/credentials.ts +110 -94
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/keys.ts +7 -7
- package/src/cli/commands/memory.ts +1 -1
- package/src/cli/commands/oauth/connections.ts +11 -29
- package/src/cli/commands/oauth/platform.ts +389 -43
- package/src/cli/lib/daemon-credential-client.ts +284 -0
- package/src/cli.ts +1 -1
- package/src/config/bundled-skills/AGENTS.md +34 -0
- package/src/config/bundled-skills/acp/SKILL.md +10 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +15 -2
- package/src/config/bundled-skills/settings/TOOLS.json +46 -1
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
- package/src/config/bundled-skills/slack/SKILL.md +1 -1
- package/src/config/bundled-tool-registry.ts +4 -0
- package/src/config/defaults.ts +0 -2
- package/src/config/env-registry.ts +4 -4
- package/src/config/env.ts +14 -1
- package/src/config/feature-flag-registry.json +1 -1
- package/src/config/loader.ts +8 -11
- package/src/config/schema.ts +5 -16
- package/src/config/schemas/calls.ts +17 -0
- package/src/config/schemas/inference.ts +2 -2
- package/src/config/schemas/journal.ts +16 -0
- package/src/config/schemas/memory-processing.ts +2 -2
- package/src/config/types.ts +1 -0
- package/src/contacts/contact-store.ts +2 -2
- package/src/credential-execution/executable-discovery.ts +1 -1
- package/src/credential-execution/startup-timeout.ts +36 -0
- package/src/daemon/approval-generators.ts +3 -9
- package/src/daemon/conversation-error.ts +13 -1
- package/src/daemon/conversation-memory.ts +1 -2
- package/src/daemon/conversation-process.ts +18 -1
- package/src/daemon/conversation-surfaces.ts +30 -1
- package/src/daemon/conversation.ts +20 -9
- package/src/daemon/guardian-action-generators.ts +3 -9
- package/src/daemon/lifecycle.ts +18 -11
- package/src/daemon/message-types/conversations.ts +1 -0
- package/src/daemon/server.ts +2 -3
- package/src/memory/app-store.ts +31 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/indexer.ts +19 -10
- package/src/memory/items-extractor.ts +315 -322
- package/src/memory/job-handlers/summarization.ts +26 -16
- package/src/memory/jobs-store.ts +33 -1
- package/src/memory/journal-memory.ts +214 -0
- package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/retriever.test.ts +37 -25
- package/src/memory/retriever.ts +24 -49
- package/src/memory/schema/memory-core.ts +2 -0
- package/src/memory/search/formatting.ts +7 -44
- package/src/memory/search/staleness.ts +4 -0
- package/src/memory/search/tier-classifier.ts +10 -2
- package/src/memory/search/types.ts +2 -5
- package/src/memory/task-memory-cleanup.ts +4 -3
- package/src/notifications/adapters/slack.ts +168 -6
- package/src/notifications/broadcaster.ts +1 -0
- package/src/notifications/copy-composer.ts +59 -2
- package/src/notifications/signal.ts +2 -0
- package/src/notifications/types.ts +2 -0
- package/src/prompts/journal-context.ts +133 -0
- package/src/prompts/persona-resolver.ts +80 -24
- package/src/prompts/system-prompt.ts +8 -0
- package/src/prompts/templates/SOUL.md +10 -0
- package/src/providers/provider-send-message.ts +3 -32
- package/src/providers/registry.ts +2 -139
- package/src/providers/types.ts +1 -1
- package/src/runtime/access-request-helper.ts +4 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
- package/src/runtime/auth/route-policy.ts +2 -0
- package/src/runtime/gateway-client.ts +47 -4
- package/src/runtime/guardian-decision-types.ts +45 -4
- package/src/runtime/http-server.ts +5 -2
- package/src/runtime/routes/access-request-decision.ts +2 -2
- package/src/runtime/routes/app-management-routes.ts +2 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
- package/src/runtime/routes/channel-readiness-routes.ts +9 -4
- package/src/runtime/routes/debug-routes.ts +12 -9
- package/src/runtime/routes/guardian-approval-interception.ts +168 -11
- package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
- package/src/runtime/routes/identity-routes.ts +1 -1
- package/src/runtime/routes/inbound-message-handler.ts +31 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
- package/src/runtime/routes/integrations/twilio.ts +52 -10
- package/src/runtime/routes/memory-item-routes.test.ts +3 -3
- package/src/runtime/routes/memory-item-routes.ts +25 -11
- package/src/runtime/routes/secret-routes.ts +141 -10
- package/src/runtime/routes/tts-routes.ts +11 -1
- package/src/security/ces-credential-client.ts +18 -9
- package/src/security/ces-rpc-credential-backend.ts +4 -3
- package/src/security/credential-backend.ts +10 -4
- package/src/security/secure-keys.ts +21 -4
- package/src/skills/catalog-install.ts +4 -36
- package/src/skills/skill-memory.ts +1 -0
- package/src/subagent/manager.ts +2 -5
- package/src/tools/acp/spawn.ts +78 -1
- package/src/tools/credentials/vault.ts +5 -3
- package/src/tools/memory/definitions.ts +3 -2
- package/src/tools/memory/handlers.ts +10 -7
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/util/browser.ts +15 -0
- package/src/util/platform.ts +1 -1
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +4 -4
- package/src/workspace/migrations/017-seed-persona-dirs.ts +2 -1
- package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -4
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/provider-commit-message-generator.ts +12 -21
- package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
- package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
- package/src/memory/search/lexical.ts +0 -48
- package/src/providers/failover.ts +0 -186
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI helper for credential operations that routes through the daemon HTTP API
|
|
3
|
+
* when the daemon is running, falling back to direct secure-keys.ts reads
|
|
4
|
+
* when it is not.
|
|
5
|
+
*
|
|
6
|
+
* Follows the daemon HTTP fetch pattern established in conversations.ts
|
|
7
|
+
* (health check, JWT minting, HTTP call).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import providerEnvVarsRegistry from "../../../../meta/provider-env-vars.json" with { type: "json" };
|
|
11
|
+
import { getRuntimeHttpHost, getRuntimeHttpPort } from "../../config/env.js";
|
|
12
|
+
import { API_KEY_PROVIDERS } from "../../config/loader.js";
|
|
13
|
+
import { healthCheckHost, isHttpHealthy } from "../../daemon/daemon-control.js";
|
|
14
|
+
import {
|
|
15
|
+
initAuthSigningKey,
|
|
16
|
+
loadOrCreateSigningKey,
|
|
17
|
+
mintDaemonDeliveryToken,
|
|
18
|
+
} from "../../runtime/auth/token-service.js";
|
|
19
|
+
import type { DeleteResult } from "../../security/credential-backend.js";
|
|
20
|
+
import { credentialKey } from "../../security/credential-key.js";
|
|
21
|
+
import type { SecureKeyResult } from "../../security/secure-keys.js";
|
|
22
|
+
import {
|
|
23
|
+
deleteSecureKeyAsync,
|
|
24
|
+
getSecureKeyAsync,
|
|
25
|
+
getSecureKeyResultAsync,
|
|
26
|
+
setSecureKeyAsync,
|
|
27
|
+
} from "../../security/secure-keys.js";
|
|
28
|
+
import { getLogger } from "../../util/logger.js";
|
|
29
|
+
|
|
30
|
+
const log = getLogger("daemon-credential-client");
|
|
31
|
+
const CREDENTIAL_KEY_PREFIX = "credential/";
|
|
32
|
+
|
|
33
|
+
const PROVIDER_ENV_VARS: Record<string, string> =
|
|
34
|
+
providerEnvVarsRegistry.providers;
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Private daemon fetch helper
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Attempt an authenticated HTTP request to the running daemon.
|
|
42
|
+
*
|
|
43
|
+
* Returns the Response for ANY HTTP response (including non-ok status codes)
|
|
44
|
+
* so callers can distinguish "daemon rejected the request" from "daemon
|
|
45
|
+
* unreachable". Returns `null` only when the daemon is genuinely unreachable
|
|
46
|
+
* (health check fails or network error). Callers fall back to direct
|
|
47
|
+
* secure-keys.ts only when this returns `null`.
|
|
48
|
+
*/
|
|
49
|
+
async function daemonFetch(
|
|
50
|
+
path: string,
|
|
51
|
+
init?: RequestInit,
|
|
52
|
+
): Promise<Response | null> {
|
|
53
|
+
try {
|
|
54
|
+
if (!(await isHttpHealthy())) return null;
|
|
55
|
+
|
|
56
|
+
const port = getRuntimeHttpPort();
|
|
57
|
+
const host = healthCheckHost(getRuntimeHttpHost());
|
|
58
|
+
initAuthSigningKey(loadOrCreateSigningKey());
|
|
59
|
+
const token = mintDaemonDeliveryToken();
|
|
60
|
+
|
|
61
|
+
const res = await fetch(`http://${host}:${port}${path}`, {
|
|
62
|
+
...init,
|
|
63
|
+
headers: {
|
|
64
|
+
...init?.headers,
|
|
65
|
+
Authorization: `Bearer ${token}`,
|
|
66
|
+
"Content-Type": "application/json",
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (!res.ok) {
|
|
71
|
+
log.warn(
|
|
72
|
+
{ path, status: res.status },
|
|
73
|
+
"Daemon credential request returned non-ok status",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return res;
|
|
78
|
+
} catch (err) {
|
|
79
|
+
log.warn(
|
|
80
|
+
{ path, error: err instanceof Error ? err.message : String(err) },
|
|
81
|
+
"Daemon credential request error — falling back to direct access",
|
|
82
|
+
);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Internal helpers
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Derive the canonical credential storage key from a "service:field" name.
|
|
93
|
+
* Mirrors the parsing in secret-routes.ts handleAddSecret / handleDeleteSecret.
|
|
94
|
+
*
|
|
95
|
+
* Uses lastIndexOf to split on the *last* colon so compound service names
|
|
96
|
+
* (e.g. "integration:google") are preserved intact while the single-segment
|
|
97
|
+
* field name is extracted correctly.
|
|
98
|
+
*/
|
|
99
|
+
function deriveCredentialStorageKey(name: string): string {
|
|
100
|
+
// Already a canonical storage key (credential/service/field) — return as-is
|
|
101
|
+
// to avoid double-encoding (e.g. "credential/integration:google/access_token"
|
|
102
|
+
// would otherwise become "credential/credential/integration/google/access_token").
|
|
103
|
+
if (name.startsWith("credential/")) {
|
|
104
|
+
return name;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const colonIdx = name.lastIndexOf(":");
|
|
108
|
+
if (colonIdx < 1 || colonIdx === name.length - 1) {
|
|
109
|
+
// Malformed — return raw name so the caller stores *something*.
|
|
110
|
+
// The daemon would reject this with a 400, so this only fires in
|
|
111
|
+
// the offline fallback path with bad input.
|
|
112
|
+
return name;
|
|
113
|
+
}
|
|
114
|
+
const service = name.slice(0, colonIdx);
|
|
115
|
+
const field = name.slice(colonIdx + 1);
|
|
116
|
+
return credentialKey(service, field);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function deriveReadSecretRequest(account: string): {
|
|
120
|
+
type: "api_key" | "credential";
|
|
121
|
+
name: string;
|
|
122
|
+
} {
|
|
123
|
+
if (account.startsWith(CREDENTIAL_KEY_PREFIX)) {
|
|
124
|
+
const rest = account.slice(CREDENTIAL_KEY_PREFIX.length);
|
|
125
|
+
const slashIdx = rest.indexOf("/");
|
|
126
|
+
if (slashIdx > 0 && slashIdx < rest.length - 1) {
|
|
127
|
+
return {
|
|
128
|
+
type: "credential",
|
|
129
|
+
name: `${rest.slice(0, slashIdx)}:${rest.slice(slashIdx + 1)}`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
account.includes(":") &&
|
|
136
|
+
!API_KEY_PROVIDERS.includes(account as (typeof API_KEY_PROVIDERS)[number])
|
|
137
|
+
) {
|
|
138
|
+
return { type: "credential", name: account };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { type: "api_key", name: account };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// Exported wrapper functions
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Retrieve a secret value from the daemon (via POST /v1/secrets/read with
|
|
150
|
+
* reveal: true). Falls back to direct `getSecureKeyAsync()` when the daemon
|
|
151
|
+
* is not running.
|
|
152
|
+
*/
|
|
153
|
+
export async function getSecureKeyViaDaemon(
|
|
154
|
+
account: string,
|
|
155
|
+
): Promise<string | undefined> {
|
|
156
|
+
const request = deriveReadSecretRequest(account);
|
|
157
|
+
const res = await daemonFetch("/v1/secrets/read", {
|
|
158
|
+
method: "POST",
|
|
159
|
+
body: JSON.stringify({ ...request, reveal: true }),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (res?.ok) {
|
|
163
|
+
const json = (await res.json()) as { found: boolean; value?: string };
|
|
164
|
+
return json.found ? json.value : undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Fall back to direct read when daemon is unreachable (null) OR returned
|
|
168
|
+
// a non-ok response — reads are safe to retry via direct access.
|
|
169
|
+
return getSecureKeyAsync(account);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Retrieve a secret value with richer result metadata. Uses the daemon
|
|
174
|
+
* POST /v1/secrets/read endpoint (reveal: true), falling back to
|
|
175
|
+
* `getSecureKeyResultAsync()`.
|
|
176
|
+
*/
|
|
177
|
+
export async function getSecureKeyResultViaDaemon(
|
|
178
|
+
account: string,
|
|
179
|
+
): Promise<SecureKeyResult> {
|
|
180
|
+
const request = deriveReadSecretRequest(account);
|
|
181
|
+
const res = await daemonFetch("/v1/secrets/read", {
|
|
182
|
+
method: "POST",
|
|
183
|
+
body: JSON.stringify({ ...request, reveal: true }),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (res?.ok) {
|
|
187
|
+
const json = (await res.json()) as {
|
|
188
|
+
found: boolean;
|
|
189
|
+
value?: string;
|
|
190
|
+
unreachable?: boolean;
|
|
191
|
+
};
|
|
192
|
+
if (json.found && json.value != null) {
|
|
193
|
+
return { value: json.value, unreachable: false };
|
|
194
|
+
}
|
|
195
|
+
return { value: undefined, unreachable: json.unreachable ?? false };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Fall back to direct read when daemon is unreachable (null) OR returned
|
|
199
|
+
// a non-ok response — reads are safe to retry via direct access.
|
|
200
|
+
return getSecureKeyResultAsync(account);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Store a secret via the daemon POST /v1/secrets endpoint. Falls back to
|
|
205
|
+
* direct `setSecureKeyAsync()` when the daemon is not running.
|
|
206
|
+
*/
|
|
207
|
+
export async function setSecureKeyViaDaemon(
|
|
208
|
+
type: string,
|
|
209
|
+
name: string,
|
|
210
|
+
value: string,
|
|
211
|
+
): Promise<boolean> {
|
|
212
|
+
const res = await daemonFetch("/v1/secrets", {
|
|
213
|
+
method: "POST",
|
|
214
|
+
body: JSON.stringify({ type, name, value }),
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
if (res?.ok) {
|
|
218
|
+
const json = (await res.json()) as { success: boolean };
|
|
219
|
+
return json.success;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (res) {
|
|
223
|
+
// Daemon is running but deliberately rejected the write (e.g. 422
|
|
224
|
+
// validation failure, 400 bad input). Do NOT fall back — the daemon's
|
|
225
|
+
// rejection is authoritative and bypassing it would skip validation.
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Daemon unreachable — fall back to direct write.
|
|
230
|
+
// For credentials, derive the canonical storage key (credential/service/field)
|
|
231
|
+
// to match the daemon path which uses credentialKey().
|
|
232
|
+
const storageKey =
|
|
233
|
+
type === "credential" ? deriveCredentialStorageKey(name) : name;
|
|
234
|
+
return setSecureKeyAsync(storageKey, value);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Delete a secret via the daemon DELETE /v1/secrets endpoint. Falls back to
|
|
239
|
+
* direct `deleteSecureKeyAsync()` when the daemon is not running.
|
|
240
|
+
*/
|
|
241
|
+
export async function deleteSecureKeyViaDaemon(
|
|
242
|
+
type: string,
|
|
243
|
+
name: string,
|
|
244
|
+
): Promise<DeleteResult> {
|
|
245
|
+
const res = await daemonFetch("/v1/secrets", {
|
|
246
|
+
method: "DELETE",
|
|
247
|
+
body: JSON.stringify({ type, name }),
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (res?.ok) {
|
|
251
|
+
const json = (await res.json()) as { success: boolean };
|
|
252
|
+
return json.success ? "deleted" : "error";
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (res) {
|
|
256
|
+
// Daemon is running but rejected the delete. Map common status codes
|
|
257
|
+
// to appropriate results without falling back to direct access.
|
|
258
|
+
if (res.status === 404) return "not-found";
|
|
259
|
+
return "error";
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Daemon unreachable — fall back to direct delete.
|
|
263
|
+
// For credentials, derive the canonical storage key (credential/service/field)
|
|
264
|
+
// to match the daemon path which uses credentialKey().
|
|
265
|
+
const storageKey =
|
|
266
|
+
type === "credential" ? deriveCredentialStorageKey(name) : name;
|
|
267
|
+
return deleteSecureKeyAsync(storageKey);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Retrieve a provider API key via the daemon, with env var fallback.
|
|
272
|
+
*
|
|
273
|
+
* Mirrors the behavior of `getProviderKeyAsync()` from secure-keys.ts:
|
|
274
|
+
* first checks the secure store (via daemon), then falls back to the
|
|
275
|
+
* corresponding `<PROVIDER>_API_KEY` environment variable.
|
|
276
|
+
*/
|
|
277
|
+
export async function getProviderKeyViaDaemon(
|
|
278
|
+
provider: string,
|
|
279
|
+
): Promise<string | undefined> {
|
|
280
|
+
const stored = await getSecureKeyViaDaemon(provider);
|
|
281
|
+
if (stored) return stored;
|
|
282
|
+
const envVar = PROVIDER_ENV_VARS[provider];
|
|
283
|
+
return envVar ? process.env[envVar] : undefined;
|
|
284
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -728,7 +728,7 @@ export async function startCli(): Promise<void> {
|
|
|
728
728
|
case "memory_recalled":
|
|
729
729
|
spinner.stop();
|
|
730
730
|
process.stdout.write(
|
|
731
|
-
`\n\x1B[2m[Memory recalled: ${msg.injectedTokens} tokens | t1 ${msg.tier1Count} t2 ${msg.tier2Count} | semantic ${msg.semanticHits} |
|
|
731
|
+
`\n\x1B[2m[Memory recalled: ${msg.injectedTokens} tokens | t1 ${msg.tier1Count} t2 ${msg.tier2Count} | semantic ${msg.semanticHits} | merged ${msg.mergedCount} → selected ${msg.selectedCount}${msg.sparseVectorUsed ? " (sparse)" : ""} | hybrid ${msg.hybridSearchLatencyMs}ms | ${msg.provider}/${msg.model} | ${msg.latencyMs}ms]\x1B[0m\n`,
|
|
732
732
|
);
|
|
733
733
|
spinner.start("Thinking...");
|
|
734
734
|
break;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Bundled Skills — Agent Instructions
|
|
2
|
+
|
|
3
|
+
## Registering Tool Executors
|
|
4
|
+
|
|
5
|
+
When you add a new tool executor to a bundled skill's `TOOLS.json`, you **must** also register it in `assistant/src/config/bundled-tool-registry.ts`. Each new executor needs two things:
|
|
6
|
+
|
|
7
|
+
1. **A static import** at the top of the file, grouped under the skill's section comment.
|
|
8
|
+
2. **A registry entry** in the `bundledToolRegistry` map.
|
|
9
|
+
|
|
10
|
+
### Example (settings skill)
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
// ── settings ───────────────────────────────────────────────────────────────────
|
|
14
|
+
import * as avatarUpdate from "./bundled-skills/settings/tools/avatar-update.js";
|
|
15
|
+
// ... other imports ...
|
|
16
|
+
|
|
17
|
+
export const bundledToolRegistry = new Map<string, SkillToolScript>([
|
|
18
|
+
// settings
|
|
19
|
+
["settings:tools/avatar-update.ts", avatarUpdate],
|
|
20
|
+
// ... other entries ...
|
|
21
|
+
]);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The map key format is `skillDirBasename:executorPath` (e.g. `settings:tools/avatar-update.ts`).
|
|
25
|
+
|
|
26
|
+
### Why this is required
|
|
27
|
+
|
|
28
|
+
`knip` (`lint:unused`) flags dynamically-loaded executor files as unused exports unless they have a static import somewhere in the dependency graph. The registry provides that static import while also enabling the compiled Bun binary to bundle the scripts (since dynamic imports from the filesystem don't work inside `/$bunfs/`).
|
|
29
|
+
|
|
30
|
+
You can regenerate the full registry with:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
bun run scripts/generate-bundled-tool-registry.ts
|
|
34
|
+
```
|
|
@@ -50,6 +50,16 @@ When the user first tries to use ACP and it's not configured, set it up automati
|
|
|
50
50
|
- NEVER use `claude`, `claude -p`, `claude --acp`, or any other command. Only `claude-agent-acp` speaks the ACP JSON-RPC protocol.
|
|
51
51
|
- NEVER change an existing ACP config to use a different command. If the config already has `claude-agent-acp`, leave it alone.
|
|
52
52
|
|
|
53
|
+
## Updating the adapter
|
|
54
|
+
|
|
55
|
+
If `acp_spawn` reports that `claude-agent-acp` is outdated, ask the user before updating. To update:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm install -g @zed-industries/claude-agent-acp@latest
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Then retry the `acp_spawn` call.
|
|
62
|
+
|
|
53
63
|
## Tips
|
|
54
64
|
|
|
55
65
|
- The spawned agent runs autonomously with its own tools, file editing, and terminal access.
|
|
@@ -632,7 +632,3 @@ When the user wants to triage or bulk-act on items, generate an interactive UI w
|
|
|
632
632
|
## External Links
|
|
633
633
|
|
|
634
634
|
Use `vellum.openLink(url, metadata)` to make items clickable. Construct deep-link URLs when possible. Include `metadata.provider` and `metadata.type` for context.
|
|
635
|
-
|
|
636
|
-
## Branding
|
|
637
|
-
|
|
638
|
-
A "Built on Vellum" badge is auto-injected into every page. Do NOT add your own.
|
|
@@ -50,7 +50,7 @@ function upsertMemoryItem(opts: {
|
|
|
50
50
|
Math.max(existing.importance ?? 0, opts.importance),
|
|
51
51
|
),
|
|
52
52
|
lastSeenAt: now,
|
|
53
|
-
|
|
53
|
+
sourceType: "extraction",
|
|
54
54
|
})
|
|
55
55
|
.where(eq(memoryItems.id, existing.id))
|
|
56
56
|
.run();
|
|
@@ -67,7 +67,7 @@ function upsertMemoryItem(opts: {
|
|
|
67
67
|
confidence: 0.8,
|
|
68
68
|
importance: clampUnitInterval(opts.importance),
|
|
69
69
|
fingerprint,
|
|
70
|
-
|
|
70
|
+
sourceType: "extraction",
|
|
71
71
|
scopeId: opts.scopeId,
|
|
72
72
|
firstSeenAt: now,
|
|
73
73
|
lastSeenAt: now,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: settings
|
|
3
|
-
description: Manage assistant voice and system settings
|
|
3
|
+
description: Manage assistant voice, avatar, and system settings
|
|
4
4
|
compatibility: "Designed for Vellum personal assistants"
|
|
5
5
|
metadata:
|
|
6
6
|
emoji: "\u2699\uFE0F"
|
|
@@ -8,4 +8,17 @@ metadata:
|
|
|
8
8
|
display-name: "Settings"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
Tools for managing assistant settings: voice configuration (TTS voice, PTT activation key, conversation timeout), system settings navigation, and in-app settings tab navigation.
|
|
11
|
+
Tools for managing assistant settings: voice configuration (TTS voice, PTT activation key, conversation timeout), avatar management, system settings navigation, and in-app settings tab navigation.
|
|
12
|
+
|
|
13
|
+
## Avatar
|
|
14
|
+
|
|
15
|
+
When the user asks to set, change, or update your avatar to an image they provide:
|
|
16
|
+
|
|
17
|
+
1. Find the uploaded image in the conversation attachments directory
|
|
18
|
+
2. Use the `set_avatar` tool with the `image_path` parameter set to the workspace-relative path (e.g. `conversations/<id>/attachments/Dropped Image.png`)
|
|
19
|
+
|
|
20
|
+
The tool copies the image to the canonical location (`data/avatar/avatar-image.png`) and notifies the app to refresh automatically.
|
|
21
|
+
|
|
22
|
+
When the user asks to remove, clear, or reset their avatar, use the `remove_avatar` tool. It deletes only the custom image and preserves the native character traits so the character avatar is restored automatically.
|
|
23
|
+
|
|
24
|
+
**Do NOT use `bash`, `cp`, or `rm` for avatar operations** — always use `set_avatar` / `remove_avatar` so the app is properly notified and character traits are preserved.
|
|
@@ -11,7 +11,13 @@
|
|
|
11
11
|
"properties": {
|
|
12
12
|
"setting": {
|
|
13
13
|
"type": "string",
|
|
14
|
-
"enum": [
|
|
14
|
+
"enum": [
|
|
15
|
+
"activation_key",
|
|
16
|
+
"conversation_timeout",
|
|
17
|
+
"fish_audio_reference_id",
|
|
18
|
+
"tts_provider",
|
|
19
|
+
"tts_voice_id"
|
|
20
|
+
],
|
|
15
21
|
"description": "The voice setting to change"
|
|
16
22
|
},
|
|
17
23
|
"value": {
|
|
@@ -79,6 +85,45 @@
|
|
|
79
85
|
},
|
|
80
86
|
"executor": "tools/navigate-settings-tab.ts",
|
|
81
87
|
"execution_target": "host"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"name": "set_avatar",
|
|
91
|
+
"description": "Set the assistant's avatar to an image file. Copies the source image to the canonical avatar location (data/avatar/avatar-image.png), removes any native character avatar files, and notifies the app to refresh. Use this when the user asks to set, change, or update the avatar to a specific image (e.g. from a conversation attachment).",
|
|
92
|
+
"category": "system",
|
|
93
|
+
"risk": "low",
|
|
94
|
+
"input_schema": {
|
|
95
|
+
"type": "object",
|
|
96
|
+
"properties": {
|
|
97
|
+
"image_path": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"description": "Path to the image file. Can be absolute or relative to the workspace directory (e.g. 'conversations/<id>/attachments/Dropped Image.png')."
|
|
100
|
+
},
|
|
101
|
+
"activity": {
|
|
102
|
+
"type": "string",
|
|
103
|
+
"description": "Brief non-technical explanation of what you are doing, shown to the user as a status update."
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"required": ["image_path"]
|
|
107
|
+
},
|
|
108
|
+
"executor": "tools/avatar-update.ts",
|
|
109
|
+
"execution_target": "sandbox"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "remove_avatar",
|
|
113
|
+
"description": "Remove the custom avatar image and restore the native character avatar. Only deletes the custom image — preserves character traits so the character avatar is automatically restored. Use this when the user asks to remove, clear, or reset their avatar.",
|
|
114
|
+
"category": "system",
|
|
115
|
+
"risk": "low",
|
|
116
|
+
"input_schema": {
|
|
117
|
+
"type": "object",
|
|
118
|
+
"properties": {
|
|
119
|
+
"activity": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"description": "Brief non-technical explanation of what you are doing, shown to the user as a status update."
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"executor": "tools/avatar-remove.ts",
|
|
126
|
+
"execution_target": "sandbox"
|
|
82
127
|
}
|
|
83
128
|
]
|
|
84
129
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { existsSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { buildAssistantEvent } from "../../../../runtime/assistant-event.js";
|
|
5
|
+
import { assistantEventHub } from "../../../../runtime/assistant-event-hub.js";
|
|
6
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../../../runtime/assistant-scope.js";
|
|
7
|
+
import type {
|
|
8
|
+
ToolContext,
|
|
9
|
+
ToolExecutionResult,
|
|
10
|
+
} from "../../../../tools/types.js";
|
|
11
|
+
import { getLogger } from "../../../../util/logger.js";
|
|
12
|
+
import { getWorkspaceDir } from "../../../../util/platform.js";
|
|
13
|
+
|
|
14
|
+
const log = getLogger("avatar-remove");
|
|
15
|
+
|
|
16
|
+
export async function run(
|
|
17
|
+
_input: Record<string, unknown>,
|
|
18
|
+
_context: ToolContext,
|
|
19
|
+
): Promise<ToolExecutionResult> {
|
|
20
|
+
const avatarDir = join(getWorkspaceDir(), "data", "avatar");
|
|
21
|
+
const avatarPath = join(avatarDir, "avatar-image.png");
|
|
22
|
+
|
|
23
|
+
if (!existsSync(avatarPath)) {
|
|
24
|
+
return {
|
|
25
|
+
content: "No custom avatar to remove — already using the default.",
|
|
26
|
+
isError: false,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
unlinkSync(avatarPath);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
34
|
+
log.error({ error: message }, "Failed to remove avatar image");
|
|
35
|
+
return { content: `Error removing avatar: ${message}`, isError: true };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// character-traits.json is intentionally preserved so the native
|
|
39
|
+
// character avatar is restored automatically.
|
|
40
|
+
|
|
41
|
+
// Notify connected clients so the avatar refreshes immediately.
|
|
42
|
+
assistantEventHub
|
|
43
|
+
.publish(
|
|
44
|
+
buildAssistantEvent(DAEMON_INTERNAL_ASSISTANT_ID, {
|
|
45
|
+
type: "avatar_updated",
|
|
46
|
+
avatarPath,
|
|
47
|
+
}),
|
|
48
|
+
)
|
|
49
|
+
.catch((err) => {
|
|
50
|
+
log.warn({ err }, "Failed to publish avatar_updated event");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
log.info("Custom avatar removed, reverting to character avatar");
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
content: "Custom avatar removed. The character avatar has been restored.",
|
|
57
|
+
isError: false,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { buildAssistantEvent } from "../../../../runtime/assistant-event.js";
|
|
5
|
+
import { assistantEventHub } from "../../../../runtime/assistant-event-hub.js";
|
|
6
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../../../runtime/assistant-scope.js";
|
|
7
|
+
import type {
|
|
8
|
+
ToolContext,
|
|
9
|
+
ToolExecutionResult,
|
|
10
|
+
} from "../../../../tools/types.js";
|
|
11
|
+
import { getLogger } from "../../../../util/logger.js";
|
|
12
|
+
import { getWorkspaceDir } from "../../../../util/platform.js";
|
|
13
|
+
|
|
14
|
+
const log = getLogger("avatar-update");
|
|
15
|
+
|
|
16
|
+
/** Canonical path where the custom avatar PNG is stored. */
|
|
17
|
+
function getAvatarPath(): string {
|
|
18
|
+
return join(getWorkspaceDir(), "data", "avatar", "avatar-image.png");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function run(
|
|
22
|
+
input: Record<string, unknown>,
|
|
23
|
+
_context: ToolContext,
|
|
24
|
+
): Promise<ToolExecutionResult> {
|
|
25
|
+
const sourcePath = input.image_path as string | undefined;
|
|
26
|
+
|
|
27
|
+
if (!sourcePath || typeof sourcePath !== "string") {
|
|
28
|
+
return {
|
|
29
|
+
content:
|
|
30
|
+
'Error: "image_path" is required. Provide the path to the image file (absolute or relative to workspace).',
|
|
31
|
+
isError: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const workspaceDir = getWorkspaceDir();
|
|
36
|
+
const resolvedSource = sourcePath.startsWith("/")
|
|
37
|
+
? sourcePath
|
|
38
|
+
: join(workspaceDir, sourcePath);
|
|
39
|
+
|
|
40
|
+
if (!existsSync(resolvedSource)) {
|
|
41
|
+
return {
|
|
42
|
+
content: `Error: source file not found: ${resolvedSource}`,
|
|
43
|
+
isError: true,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const avatarPath = getAvatarPath();
|
|
48
|
+
const avatarDir = dirname(avatarPath);
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
mkdirSync(avatarDir, { recursive: true });
|
|
52
|
+
copyFileSync(resolvedSource, avatarPath);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
55
|
+
log.error({ error: message }, "Failed to copy avatar image");
|
|
56
|
+
return { content: `Error copying avatar: ${message}`, isError: true };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Keep character-traits.json and character-ascii.txt so the character
|
|
60
|
+
// avatar can be restored if the custom image is later removed.
|
|
61
|
+
|
|
62
|
+
// Notify connected clients so the avatar refreshes immediately.
|
|
63
|
+
assistantEventHub
|
|
64
|
+
.publish(
|
|
65
|
+
buildAssistantEvent(DAEMON_INTERNAL_ASSISTANT_ID, {
|
|
66
|
+
type: "avatar_updated",
|
|
67
|
+
avatarPath,
|
|
68
|
+
}),
|
|
69
|
+
)
|
|
70
|
+
.catch((err) => {
|
|
71
|
+
log.warn({ err }, "Failed to publish avatar_updated event");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
log.info({ avatarPath, source: resolvedSource }, "Avatar updated");
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
content: `Avatar updated from ${sourcePath}. The app will refresh automatically.`,
|
|
78
|
+
isError: false,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -76,4 +76,4 @@ For setting up recurring digests, load the `slack-digest-setup` skill which cove
|
|
|
76
76
|
|
|
77
77
|
## Connection
|
|
78
78
|
|
|
79
|
-
Before using any Slack tool, verify that Slack is connected. If not connected,
|
|
79
|
+
Before using any Slack tool, verify that Slack is connected. If not connected, load the **slack-app-setup** skill (`skill_load` with `skill: "slack-app-setup"`) and follow its step-by-step guided flow. Do NOT improvise setup instructions — the `slack-app-setup` skill is the single source of truth for Slack connection setup. Slack uses Socket Mode (not OAuth) and does not require redirect URLs or any OAuth flow.
|
|
@@ -132,6 +132,8 @@ import * as sequenceImport from "./bundled-skills/sequences/tools/sequence-impor
|
|
|
132
132
|
import * as sequenceList from "./bundled-skills/sequences/tools/sequence-list.js";
|
|
133
133
|
import * as sequenceUpdate from "./bundled-skills/sequences/tools/sequence-update.js";
|
|
134
134
|
// ── settings ───────────────────────────────────────────────────────────────────
|
|
135
|
+
import * as avatarRemove from "./bundled-skills/settings/tools/avatar-remove.js";
|
|
136
|
+
import * as avatarUpdate from "./bundled-skills/settings/tools/avatar-update.js";
|
|
135
137
|
import * as navigateSettingsTab from "./bundled-skills/settings/tools/navigate-settings-tab.js";
|
|
136
138
|
import * as openSystemSettings from "./bundled-skills/settings/tools/open-system-settings.js";
|
|
137
139
|
import * as voiceConfigUpdate from "./bundled-skills/settings/tools/voice-config-update.js";
|
|
@@ -323,6 +325,8 @@ export const bundledToolRegistry = new Map<string, SkillToolScript>([
|
|
|
323
325
|
["sequences:tools/sequence-analytics.ts", sequenceAnalytics],
|
|
324
326
|
|
|
325
327
|
// settings
|
|
328
|
+
["settings:tools/avatar-update.ts", avatarUpdate],
|
|
329
|
+
["settings:tools/avatar-remove.ts", avatarRemove],
|
|
326
330
|
["settings:tools/voice-config-update.ts", voiceConfigUpdate],
|
|
327
331
|
["settings:tools/open-system-settings.ts", openSystemSettings],
|
|
328
332
|
["settings:tools/navigate-settings-tab.ts", navigateSettingsTab],
|
package/src/config/defaults.ts
CHANGED
|
@@ -2,6 +2,4 @@ import { applyNestedDefaults } from "./loader.js";
|
|
|
2
2
|
import type { AssistantConfig } from "./types.js";
|
|
3
3
|
|
|
4
4
|
// Single source of truth: Zod schema field-level .default() values.
|
|
5
|
-
// Uses applyNestedDefaults to cascade through nested .default({}) calls,
|
|
6
|
-
// which Zod 4 doesn't resolve in a single parse pass.
|
|
7
5
|
export const DEFAULT_CONFIG: AssistantConfig = applyNestedDefaults({});
|
|
@@ -55,12 +55,12 @@ export function getIsContainerized(): boolean {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* deployments where the workspace is a separate volume.
|
|
58
|
+
* VELLUM_WORKSPACE_DIR — string, default: undefined
|
|
59
|
+
* Overrides the default workspace directory.
|
|
60
|
+
* Used in containerized deployments where the workspace is a separate volume.
|
|
61
61
|
*/
|
|
62
62
|
export function getWorkspaceDirOverride(): string | undefined {
|
|
63
|
-
return str("
|
|
63
|
+
return str("VELLUM_WORKSPACE_DIR");
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// ── Known env var names ──────────────────────────────────────────────────────
|