@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
package/src/config/env.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { getLogger } from "../util/logger.js";
|
|
17
17
|
import { checkUnrecognizedEnvVars } from "./env-registry.js";
|
|
18
|
+
import { getConfig } from "./loader.js";
|
|
18
19
|
|
|
19
20
|
const log = getLogger("env");
|
|
20
21
|
|
|
@@ -144,7 +145,19 @@ export function setPlatformBaseUrl(value: string | undefined): void {
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
export function getPlatformBaseUrl(): string {
|
|
147
|
-
|
|
148
|
+
let configUrl: string | undefined;
|
|
149
|
+
try {
|
|
150
|
+
const val = getConfig().platform.baseUrl;
|
|
151
|
+
if (val) configUrl = val;
|
|
152
|
+
} catch {
|
|
153
|
+
// Config not yet available (early bootstrap) — fall through
|
|
154
|
+
}
|
|
155
|
+
return (
|
|
156
|
+
configUrl ||
|
|
157
|
+
str("VELLUM_PLATFORM_URL") ||
|
|
158
|
+
_platformBaseUrlOverride ||
|
|
159
|
+
"https://platform.vellum.ai"
|
|
160
|
+
);
|
|
148
161
|
}
|
|
149
162
|
|
|
150
163
|
let _platformAssistantIdOverride: string | undefined;
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
"key": "feature_flags.ces-managed-sidecar.enabled",
|
|
112
112
|
"label": "CES Managed Sidecar Transport",
|
|
113
113
|
"description": "Use managed sidecar transport for CES communication when running in a containerized environment",
|
|
114
|
-
"defaultEnabled":
|
|
114
|
+
"defaultEnabled": true
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
117
|
"id": "ces-credential-backend",
|
package/src/config/loader.ts
CHANGED
|
@@ -40,19 +40,16 @@ function ensureMigratedDataDir(): void {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* so 5 parses are needed (N+1) to fully cascade.
|
|
43
|
+
* Parse a raw config through the Zod schema, applying all nested defaults.
|
|
44
|
+
*
|
|
45
|
+
* All nested object schemas use `.default(SubSchema.parse({}))` which
|
|
46
|
+
* pre-computes fully-resolved defaults at schema construction time, so a
|
|
47
|
+
* single parse is sufficient to cascade defaults through every nesting level.
|
|
49
48
|
*/
|
|
50
49
|
export function applyNestedDefaults(config: unknown): AssistantConfig {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
return current as AssistantConfig;
|
|
50
|
+
return structuredClone(
|
|
51
|
+
AssistantConfigSchema.parse(config),
|
|
52
|
+
) as AssistantConfig;
|
|
56
53
|
}
|
|
57
54
|
|
|
58
55
|
function cloneDefaultConfig(): AssistantConfig {
|
package/src/config/schema.ts
CHANGED
|
@@ -68,6 +68,8 @@ export {
|
|
|
68
68
|
IngressRateLimitConfigSchema,
|
|
69
69
|
IngressWebhookConfigSchema,
|
|
70
70
|
} from "./schemas/ingress.js";
|
|
71
|
+
export type { JournalConfig } from "./schemas/journal.js";
|
|
72
|
+
export { JournalConfigSchema } from "./schemas/journal.js";
|
|
71
73
|
export type { AuditLogConfig, LogFileConfig } from "./schemas/logging.js";
|
|
72
74
|
export {
|
|
73
75
|
AuditLogConfigSchema,
|
|
@@ -202,6 +204,7 @@ import {
|
|
|
202
204
|
ThinkingConfigSchema,
|
|
203
205
|
} from "./schemas/inference.js";
|
|
204
206
|
import { IngressConfigSchema } from "./schemas/ingress.js";
|
|
207
|
+
import { JournalConfigSchema } from "./schemas/journal.js";
|
|
205
208
|
import {
|
|
206
209
|
AuditLogConfigSchema,
|
|
207
210
|
LogFileConfigSchema,
|
|
@@ -219,10 +222,7 @@ import {
|
|
|
219
222
|
PermissionsConfigSchema,
|
|
220
223
|
SecretDetectionConfigSchema,
|
|
221
224
|
} from "./schemas/security.js";
|
|
222
|
-
import {
|
|
223
|
-
ServicesSchema,
|
|
224
|
-
VALID_INFERENCE_PROVIDERS,
|
|
225
|
-
} from "./schemas/services.js";
|
|
225
|
+
import { ServicesSchema } from "./schemas/services.js";
|
|
226
226
|
import { SkillsConfigSchema } from "./schemas/skills.js";
|
|
227
227
|
import {
|
|
228
228
|
RateLimitConfigSchema,
|
|
@@ -233,18 +233,6 @@ import { WorkspaceGitConfigSchema } from "./schemas/workspace-git.js";
|
|
|
233
233
|
export const AssistantConfigSchema = z
|
|
234
234
|
.object({
|
|
235
235
|
services: ServicesSchema.default(ServicesSchema.parse({})),
|
|
236
|
-
providerOrder: z
|
|
237
|
-
.array(
|
|
238
|
-
z.enum(VALID_INFERENCE_PROVIDERS, {
|
|
239
|
-
error: `Each providerOrder entry must be one of: ${VALID_INFERENCE_PROVIDERS.join(
|
|
240
|
-
", ",
|
|
241
|
-
)}`,
|
|
242
|
-
}),
|
|
243
|
-
)
|
|
244
|
-
.default([])
|
|
245
|
-
.describe(
|
|
246
|
-
"Fallback order of LLM providers — the assistant tries each in sequence if the previous one fails",
|
|
247
|
-
),
|
|
248
236
|
maxTokens: z
|
|
249
237
|
.number({ error: "maxTokens must be a number" })
|
|
250
238
|
.int("maxTokens must be an integer")
|
|
@@ -281,6 +269,7 @@ export const AssistantConfigSchema = z
|
|
|
281
269
|
"Custom pricing overrides for specific provider/model combinations",
|
|
282
270
|
),
|
|
283
271
|
heartbeat: HeartbeatConfigSchema.default(HeartbeatConfigSchema.parse({})),
|
|
272
|
+
journal: JournalConfigSchema.default(JournalConfigSchema.parse({})),
|
|
284
273
|
mcp: McpConfigSchema.default(McpConfigSchema.parse({})),
|
|
285
274
|
acp: AcpConfigSchema.default(AcpConfigSchema.parse({})),
|
|
286
275
|
skills: SkillsConfigSchema.default(SkillsConfigSchema.parse({})),
|
|
@@ -70,6 +70,23 @@ export const CallsVoiceConfigSchema = z
|
|
|
70
70
|
})
|
|
71
71
|
.default("elevenlabs")
|
|
72
72
|
.describe("Text-to-speech provider for phone calls"),
|
|
73
|
+
hints: z
|
|
74
|
+
.array(
|
|
75
|
+
z.string({ error: "calls.voice.hints values must be strings" }),
|
|
76
|
+
)
|
|
77
|
+
.default([])
|
|
78
|
+
.describe(
|
|
79
|
+
"Static vocabulary hints for speech recognition — proper nouns, domain terms, and other words the STT provider should prioritize",
|
|
80
|
+
),
|
|
81
|
+
interruptSensitivity: z
|
|
82
|
+
.enum(["low", "medium", "high"], {
|
|
83
|
+
error:
|
|
84
|
+
"calls.voice.interruptSensitivity must be one of: low, medium, high",
|
|
85
|
+
})
|
|
86
|
+
.default("low")
|
|
87
|
+
.describe(
|
|
88
|
+
"How aggressively the STT provider detects the start of caller speech — low reduces false interrupts from background noise",
|
|
89
|
+
),
|
|
73
90
|
})
|
|
74
91
|
.describe("Voice and speech settings for phone calls");
|
|
75
92
|
|
|
@@ -26,8 +26,8 @@ export const ThinkingConfigSchema = z
|
|
|
26
26
|
.describe("Extended thinking (chain-of-thought) configuration");
|
|
27
27
|
|
|
28
28
|
export const EffortSchema = z
|
|
29
|
-
.enum(["low", "medium", "high"], {
|
|
30
|
-
error: 'effort must be "low", "medium", or "
|
|
29
|
+
.enum(["low", "medium", "high", "max"], {
|
|
30
|
+
error: 'effort must be "low", "medium", "high", or "max"',
|
|
31
31
|
})
|
|
32
32
|
.default("high")
|
|
33
33
|
.describe(
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const JournalConfigSchema = z
|
|
4
|
+
.object({
|
|
5
|
+
contextWindowSize: z
|
|
6
|
+
.number({ error: "journal.contextWindowSize must be a number" })
|
|
7
|
+
.int("journal.contextWindowSize must be an integer")
|
|
8
|
+
.min(0, "journal.contextWindowSize must be >= 0")
|
|
9
|
+
.default(10)
|
|
10
|
+
.describe(
|
|
11
|
+
"Number of recent journal entries to include in context (0 to disable)",
|
|
12
|
+
),
|
|
13
|
+
})
|
|
14
|
+
.describe("Journal context window configuration");
|
|
15
|
+
|
|
16
|
+
export type JournalConfig = z.infer<typeof JournalConfigSchema>;
|
|
@@ -12,7 +12,7 @@ export const MemoryExtractionConfigSchema = z
|
|
|
12
12
|
.enum(["latency-optimized", "quality-optimized", "vision-optimized"], {
|
|
13
13
|
error: "memory.extraction.modelIntent must be a valid model intent",
|
|
14
14
|
})
|
|
15
|
-
.default("
|
|
15
|
+
.default("quality-optimized")
|
|
16
16
|
.describe(
|
|
17
17
|
"Model selection strategy for extraction — trade off speed vs quality",
|
|
18
18
|
),
|
|
@@ -39,7 +39,7 @@ export const MemorySummarizationConfigSchema = z
|
|
|
39
39
|
.enum(["latency-optimized", "quality-optimized", "vision-optimized"], {
|
|
40
40
|
error: "memory.summarization.modelIntent must be a valid model intent",
|
|
41
41
|
})
|
|
42
|
-
.default("
|
|
42
|
+
.default("quality-optimized")
|
|
43
43
|
.describe(
|
|
44
44
|
"Model selection strategy for summarization — trade off speed vs quality",
|
|
45
45
|
),
|
package/src/config/types.ts
CHANGED
|
@@ -38,7 +38,7 @@ export function generateUserFileSlug(displayName: string): string {
|
|
|
38
38
|
.toLowerCase()
|
|
39
39
|
.replace(/[^a-z0-9]+/g, "-")
|
|
40
40
|
.replace(/^-+|-+$/g, "")
|
|
41
|
-
.slice(0,
|
|
41
|
+
.slice(0, 100) || "user";
|
|
42
42
|
|
|
43
43
|
const db = getDb();
|
|
44
44
|
const rows = db
|
|
@@ -47,7 +47,7 @@ export function generateUserFileSlug(displayName: string): string {
|
|
|
47
47
|
.where(like(contacts.userFile, `${escapeLike(slug)}%`))
|
|
48
48
|
.all();
|
|
49
49
|
|
|
50
|
-
const taken = new Set(rows.map((r) => r.userFile));
|
|
50
|
+
const taken = new Set(rows.map((r) => r.userFile?.toLowerCase()));
|
|
51
51
|
|
|
52
52
|
const base = `${slug}.md`;
|
|
53
53
|
if (!taken.has(base)) return base;
|
|
@@ -127,7 +127,7 @@ export function discoverLocalCes():
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// Fallback: check for source entry point in the monorepo
|
|
130
|
-
const monorepoRoot = join(import.meta.dir, "..", "..", ".."
|
|
130
|
+
const monorepoRoot = join(import.meta.dir, "..", "..", "..");
|
|
131
131
|
const sourceEntry = join(
|
|
132
132
|
monorepoRoot,
|
|
133
133
|
"credential-executor",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CesClient } from "./client.js";
|
|
2
|
+
|
|
3
|
+
export interface AwaitCesClientWithTimeoutOptions {
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
onTimeout?: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_CES_STARTUP_TIMEOUT_MS = 20_000;
|
|
9
|
+
|
|
10
|
+
export async function awaitCesClientWithTimeout(
|
|
11
|
+
clientPromise: Promise<CesClient | undefined>,
|
|
12
|
+
options: AwaitCesClientWithTimeoutOptions = {},
|
|
13
|
+
): Promise<CesClient | undefined> {
|
|
14
|
+
const {
|
|
15
|
+
timeoutMs = DEFAULT_CES_STARTUP_TIMEOUT_MS,
|
|
16
|
+
onTimeout = () => {},
|
|
17
|
+
} = options;
|
|
18
|
+
|
|
19
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
return await Promise.race([
|
|
23
|
+
clientPromise,
|
|
24
|
+
new Promise<undefined>((resolve) => {
|
|
25
|
+
timeoutId = setTimeout(() => {
|
|
26
|
+
onTimeout();
|
|
27
|
+
resolve(undefined);
|
|
28
|
+
}, timeoutMs);
|
|
29
|
+
}),
|
|
30
|
+
]);
|
|
31
|
+
} finally {
|
|
32
|
+
if (timeoutId !== undefined) {
|
|
33
|
+
clearTimeout(timeoutId);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { loadConfig } from "../config/loader.js";
|
|
2
|
-
import {
|
|
2
|
+
import { getProvider, listProviders } from "../providers/registry.js";
|
|
3
3
|
import {
|
|
4
4
|
APPROVAL_COPY_MAX_TOKENS,
|
|
5
5
|
APPROVAL_COPY_SYSTEM_PROMPT,
|
|
@@ -91,10 +91,7 @@ export function createApprovalCopyGenerator(): ApprovalCopyGenerator {
|
|
|
91
91
|
const config = loadConfig();
|
|
92
92
|
let provider;
|
|
93
93
|
try {
|
|
94
|
-
provider =
|
|
95
|
-
config.services.inference.provider,
|
|
96
|
-
config.providerOrder,
|
|
97
|
-
);
|
|
94
|
+
provider = getProvider(config.services.inference.provider);
|
|
98
95
|
} catch {
|
|
99
96
|
return null;
|
|
100
97
|
}
|
|
@@ -148,10 +145,7 @@ export function createApprovalConversationGenerator(): ApprovalConversationGener
|
|
|
148
145
|
if (!listProviders().includes(config.services.inference.provider)) {
|
|
149
146
|
throw new Error("No provider available for approval conversation");
|
|
150
147
|
}
|
|
151
|
-
const provider =
|
|
152
|
-
config.services.inference.provider,
|
|
153
|
-
config.providerOrder,
|
|
154
|
-
);
|
|
148
|
+
const provider = getProvider(config.services.inference.provider);
|
|
155
149
|
|
|
156
150
|
const pendingDescription = context.pendingApprovals
|
|
157
151
|
.map((p) => `- Request ${p.requestId}: tool "${p.toolName}"`)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ProviderError } from "../util/errors.js";
|
|
1
|
+
import { ProviderError, ProviderNotConfiguredError } from "../util/errors.js";
|
|
2
2
|
import type {
|
|
3
3
|
ConversationErrorCode,
|
|
4
4
|
ConversationErrorMessage,
|
|
@@ -149,6 +149,18 @@ export function classifyConversationError(
|
|
|
149
149
|
(error instanceof Error ? error.stack : undefined) ?? message;
|
|
150
150
|
const debugDetails = truncateDebugDetails(rawDetails);
|
|
151
151
|
|
|
152
|
+
// Dedicated classification for missing provider API key
|
|
153
|
+
if (error instanceof ProviderNotConfiguredError) {
|
|
154
|
+
return {
|
|
155
|
+
code: "PROVIDER_NOT_CONFIGURED",
|
|
156
|
+
userMessage:
|
|
157
|
+
"No API key configured for inference. Add one in Settings to start chatting.",
|
|
158
|
+
retryable: true,
|
|
159
|
+
errorCategory: "provider_not_configured",
|
|
160
|
+
debugDetails,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
152
164
|
// Phase-specific overrides
|
|
153
165
|
if (ctx.phase === "regenerate") {
|
|
154
166
|
const base = classifyCore(error, message);
|
|
@@ -92,7 +92,6 @@ export async function prepareMemoryContext(
|
|
|
92
92
|
degraded: false,
|
|
93
93
|
injectedText: "",
|
|
94
94
|
semanticHits: 0,
|
|
95
|
-
recencyHits: 0,
|
|
96
95
|
mergedCount: 0,
|
|
97
96
|
selectedCount: 0,
|
|
98
97
|
injectedTokens: 0,
|
|
@@ -188,7 +187,7 @@ export async function prepareMemoryContext(
|
|
|
188
187
|
}
|
|
189
188
|
: undefined,
|
|
190
189
|
semanticHits: recall.semanticHits,
|
|
191
|
-
recencyHits:
|
|
190
|
+
recencyHits: 0,
|
|
192
191
|
tier1Count: recall.tier1Count ?? 0,
|
|
193
192
|
tier2Count: recall.tier2Count ?? 0,
|
|
194
193
|
hybridSearchLatencyMs: recall.hybridSearchMs ?? 0,
|
|
@@ -31,7 +31,10 @@ import { routeGuardianReply } from "../runtime/guardian-reply-router.js";
|
|
|
31
31
|
import { getLogger } from "../util/logger.js";
|
|
32
32
|
import type { MessageQueue } from "./conversation-queue-manager.js";
|
|
33
33
|
import type { QueueDrainReason } from "./conversation-queue-manager.js";
|
|
34
|
-
import type {
|
|
34
|
+
import type {
|
|
35
|
+
ChannelCapabilities,
|
|
36
|
+
TrustContext,
|
|
37
|
+
} from "./conversation-runtime-assembly.js";
|
|
35
38
|
import { resolveSlash, type SlashContext } from "./conversation-slash.js";
|
|
36
39
|
import { getModelInfo } from "./handlers/config-model.js";
|
|
37
40
|
import type {
|
|
@@ -80,6 +83,11 @@ export interface ProcessConversationContext {
|
|
|
80
83
|
/** Assistant identity — used for scoping notification preferences. */
|
|
81
84
|
readonly assistantId?: string;
|
|
82
85
|
trustContext?: TrustContext;
|
|
86
|
+
channelCapabilities?: ChannelCapabilities;
|
|
87
|
+
/** Per-turn snapshot of trustContext, frozen at message-processing start. */
|
|
88
|
+
currentTurnTrustContext?: TrustContext;
|
|
89
|
+
/** Per-turn snapshot of channelCapabilities, frozen at message-processing start. */
|
|
90
|
+
currentTurnChannelCapabilities?: ChannelCapabilities;
|
|
83
91
|
ensureActorScopedHistory(): Promise<void>;
|
|
84
92
|
persistUserMessage(
|
|
85
93
|
content: string,
|
|
@@ -282,6 +290,11 @@ export async function drainQueue(
|
|
|
282
290
|
}
|
|
283
291
|
}
|
|
284
292
|
|
|
293
|
+
// Snapshot persona context at turn start so later tool turns can't pick up
|
|
294
|
+
// a different actor's context if a concurrent request mutates the live fields.
|
|
295
|
+
conversation.currentTurnTrustContext = conversation.trustContext;
|
|
296
|
+
conversation.currentTurnChannelCapabilities = conversation.channelCapabilities;
|
|
297
|
+
|
|
285
298
|
// Resolve slash commands for queued messages
|
|
286
299
|
const slashResult = await resolveSlash(
|
|
287
300
|
next.content,
|
|
@@ -561,6 +574,10 @@ export async function processMessage(
|
|
|
561
574
|
displayContent?: string,
|
|
562
575
|
): Promise<string> {
|
|
563
576
|
await conversation.ensureActorScopedHistory();
|
|
577
|
+
// Snapshot persona context at turn start so later tool turns can't pick up
|
|
578
|
+
// a different actor's context if a concurrent request mutates the live fields.
|
|
579
|
+
conversation.currentTurnTrustContext = conversation.trustContext;
|
|
580
|
+
conversation.currentTurnChannelCapabilities = conversation.channelCapabilities;
|
|
564
581
|
conversation.currentActiveSurfaceId = activeSurfaceId;
|
|
565
582
|
conversation.currentPage = currentPage;
|
|
566
583
|
const trimmedContent = content.trim();
|
|
@@ -2,7 +2,10 @@ import { v4 as uuid } from "uuid";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
getApp,
|
|
5
|
+
getAppDirPath,
|
|
5
6
|
getAppPreview,
|
|
7
|
+
inlineDistAssets,
|
|
8
|
+
isMultifileApp,
|
|
6
9
|
resolveAppDir,
|
|
7
10
|
updateApp,
|
|
8
11
|
} from "../memory/app-store.js";
|
|
@@ -1364,8 +1367,34 @@ export async function surfaceProxyResolver(
|
|
|
1364
1367
|
|
|
1365
1368
|
const storedPreview = getAppPreview(app.id);
|
|
1366
1369
|
const { dirName } = resolveAppDir(app.id);
|
|
1370
|
+
|
|
1371
|
+
// For multifile TSX apps, resolve HTML from compiled dist/index.html
|
|
1372
|
+
// rather than the root index.html (which is empty for formatVersion 2).
|
|
1373
|
+
let html = app.htmlDefinition;
|
|
1374
|
+
if (isMultifileApp(app)) {
|
|
1375
|
+
const { existsSync, readFileSync } = await import("node:fs");
|
|
1376
|
+
const { join } = await import("node:path");
|
|
1377
|
+
const appDir = getAppDirPath(app.id);
|
|
1378
|
+
const distIndex = join(appDir, "dist", "index.html");
|
|
1379
|
+
if (!existsSync(distIndex)) {
|
|
1380
|
+
const { compileApp } = await import("../bundler/app-compiler.js");
|
|
1381
|
+
const result = await compileApp(appDir);
|
|
1382
|
+
if (!result.ok) {
|
|
1383
|
+
log.warn(
|
|
1384
|
+
{ appId, errors: result.errors },
|
|
1385
|
+
"Auto-compile failed on app_open",
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
if (existsSync(distIndex)) {
|
|
1390
|
+
html = inlineDistAssets(appDir, readFileSync(distIndex, "utf-8"));
|
|
1391
|
+
} else {
|
|
1392
|
+
html = `<p>App compilation failed. Edit a source file to trigger a rebuild.</p>`;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1367
1396
|
const surfaceData: DynamicPageSurfaceData = {
|
|
1368
|
-
html
|
|
1397
|
+
html,
|
|
1369
1398
|
appId: app.id,
|
|
1370
1399
|
dirName,
|
|
1371
1400
|
preview: {
|
|
@@ -176,6 +176,14 @@ export class Conversation {
|
|
|
176
176
|
/** @internal */ currentPage?: string;
|
|
177
177
|
/** @internal */ channelCapabilities?: ChannelCapabilities;
|
|
178
178
|
/** @internal */ trustContext?: TrustContext;
|
|
179
|
+
/**
|
|
180
|
+
* Per-turn snapshots of persona-relevant context, captured at the start of
|
|
181
|
+
* each message processing turn. The system prompt callback reads these
|
|
182
|
+
* instead of the live fields so that a concurrent request cannot swap
|
|
183
|
+
* another actor's persona mid-turn.
|
|
184
|
+
*/
|
|
185
|
+
/** @internal */ currentTurnTrustContext?: TrustContext;
|
|
186
|
+
/** @internal */ currentTurnChannelCapabilities?: ChannelCapabilities;
|
|
179
187
|
/** @internal */ authContext?: AuthContext;
|
|
180
188
|
/** @internal */ loadedHistoryTrustClass?: TrustClass;
|
|
181
189
|
/** @internal */ voiceCallControlPrompt?: string;
|
|
@@ -354,18 +362,21 @@ export class Conversation {
|
|
|
354
362
|
const resolveSystemPromptCallback = (
|
|
355
363
|
_history: import("../providers/types.js").Message[],
|
|
356
364
|
): ResolvedSystemPrompt => {
|
|
357
|
-
const persona = resolvePersonaContext(
|
|
358
|
-
this.trustContext,
|
|
359
|
-
this.channelCapabilities,
|
|
360
|
-
);
|
|
361
365
|
const resolved = {
|
|
362
366
|
systemPrompt: hasSystemPromptOverride
|
|
363
367
|
? systemPrompt
|
|
364
|
-
:
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
368
|
+
: (() => {
|
|
369
|
+
const persona = resolvePersonaContext(
|
|
370
|
+
this.currentTurnTrustContext,
|
|
371
|
+
this.currentTurnChannelCapabilities,
|
|
372
|
+
);
|
|
373
|
+
return buildSystemPrompt({
|
|
374
|
+
hasNoClient: this.hasNoClient,
|
|
375
|
+
userPersona: persona.userPersona,
|
|
376
|
+
channelPersona: persona.channelPersona,
|
|
377
|
+
userSlug: persona.userSlug,
|
|
378
|
+
});
|
|
379
|
+
})(),
|
|
369
380
|
maxTokens: configuredMaxTokens,
|
|
370
381
|
};
|
|
371
382
|
return resolved;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { loadConfig } from "../config/loader.js";
|
|
2
|
-
import {
|
|
2
|
+
import { getProvider } from "../providers/registry.js";
|
|
3
3
|
import {
|
|
4
4
|
buildGuardianActionGenerationPrompt,
|
|
5
5
|
getGuardianActionFallbackMessage,
|
|
@@ -29,10 +29,7 @@ export function createGuardianActionCopyGenerator(): GuardianActionCopyGenerator
|
|
|
29
29
|
const config = loadConfig();
|
|
30
30
|
let provider;
|
|
31
31
|
try {
|
|
32
|
-
provider =
|
|
33
|
-
config.services.inference.provider,
|
|
34
|
-
config.providerOrder,
|
|
35
|
-
);
|
|
32
|
+
provider = getProvider(config.services.inference.provider);
|
|
36
33
|
} catch {
|
|
37
34
|
return null;
|
|
38
35
|
}
|
|
@@ -134,10 +131,7 @@ const VALID_FOLLOWUP_DISPOSITIONS: ReadonlySet<string> = new Set([
|
|
|
134
131
|
export function createGuardianFollowUpConversationGenerator(): GuardianFollowUpConversationGenerator {
|
|
135
132
|
return async (context) => {
|
|
136
133
|
const config = loadConfig();
|
|
137
|
-
const provider =
|
|
138
|
-
config.services.inference.provider,
|
|
139
|
-
config.providerOrder,
|
|
140
|
-
);
|
|
134
|
+
const provider = getProvider(config.services.inference.provider);
|
|
141
135
|
|
|
142
136
|
const userPrompt = [
|
|
143
137
|
`Original question from the voice call: "${context.questionText}"`,
|
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -28,6 +28,10 @@ import {
|
|
|
28
28
|
CesUnavailableError,
|
|
29
29
|
createCesProcessManager,
|
|
30
30
|
} from "../credential-execution/process-manager.js";
|
|
31
|
+
import {
|
|
32
|
+
awaitCesClientWithTimeout,
|
|
33
|
+
DEFAULT_CES_STARTUP_TIMEOUT_MS,
|
|
34
|
+
} from "../credential-execution/startup-timeout.js";
|
|
31
35
|
import { HeartbeatService } from "../heartbeat/heartbeat-service.js";
|
|
32
36
|
import { getHookManager } from "../hooks/manager.js";
|
|
33
37
|
import { installTemplates } from "../hooks/templates.js";
|
|
@@ -161,7 +165,8 @@ export interface CesStartupResult {
|
|
|
161
165
|
export async function startCesProcess(
|
|
162
166
|
config: AssistantConfig,
|
|
163
167
|
): Promise<CesStartupResult> {
|
|
164
|
-
const shouldStartCes =
|
|
168
|
+
const shouldStartCes =
|
|
169
|
+
isCesToolsEnabled(config) || isCesCredentialBackendEnabled(config);
|
|
165
170
|
if (!shouldStartCes) {
|
|
166
171
|
return {
|
|
167
172
|
client: undefined,
|
|
@@ -456,23 +461,25 @@ export async function runDaemon(): Promise<void> {
|
|
|
456
461
|
|
|
457
462
|
// When the credential backend flag is enabled, CES startup must complete
|
|
458
463
|
// BEFORE provider initialization so credential reads can go through CES.
|
|
459
|
-
// Block with a
|
|
464
|
+
// Block with a 20-second timeout — fall back to direct credential store
|
|
460
465
|
// on timeout.
|
|
461
466
|
if (isCesCredentialBackendEnabled(config)) {
|
|
462
467
|
const cesResult = await cesStartupPromise;
|
|
463
468
|
// startCesProcess() returns immediately — the actual handshake runs
|
|
464
|
-
// inside clientPromise. Await it (with a
|
|
469
|
+
// inside clientPromise. Await it (with a 20s timeout) so the CES client
|
|
465
470
|
// is available before provider initialization.
|
|
466
471
|
if (cesResult.clientPromise) {
|
|
467
|
-
const client = await
|
|
472
|
+
const client = await awaitCesClientWithTimeout(
|
|
468
473
|
cesResult.clientPromise,
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
474
|
+
{
|
|
475
|
+
timeoutMs: DEFAULT_CES_STARTUP_TIMEOUT_MS,
|
|
476
|
+
onTimeout: () => {
|
|
477
|
+
log.warn(
|
|
478
|
+
"CES handshake timed out after 20s — falling back to direct credential store",
|
|
479
|
+
);
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
);
|
|
476
483
|
if (client) {
|
|
477
484
|
setCesClient(client);
|
|
478
485
|
}
|
package/src/daemon/server.ts
CHANGED
|
@@ -41,7 +41,7 @@ import { getOrCreateConversation } from "../memory/conversation-key-store.js";
|
|
|
41
41
|
import { buildSystemPrompt } from "../prompts/system-prompt.js";
|
|
42
42
|
import { RateLimitProvider } from "../providers/ratelimit.js";
|
|
43
43
|
import {
|
|
44
|
-
|
|
44
|
+
getProvider,
|
|
45
45
|
initializeProviders,
|
|
46
46
|
} from "../providers/registry.js";
|
|
47
47
|
import { buildAssistantEvent } from "../runtime/assistant-event.js";
|
|
@@ -702,9 +702,8 @@ export class DaemonServer {
|
|
|
702
702
|
|
|
703
703
|
const createPromise = (async () => {
|
|
704
704
|
const config = getConfig();
|
|
705
|
-
let provider =
|
|
705
|
+
let provider = getProvider(
|
|
706
706
|
config.services.inference.provider,
|
|
707
|
-
config.providerOrder,
|
|
708
707
|
);
|
|
709
708
|
const { rateLimit } = config;
|
|
710
709
|
if (rateLimit.maxRequestsPerMinute > 0) {
|
package/src/memory/app-store.ts
CHANGED
|
@@ -64,6 +64,37 @@ export function isMultifileApp(app: AppDefinition): boolean {
|
|
|
64
64
|
return app.formatVersion === 2;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Inline dist assets (main.js, main.css) into the compiled HTML so it can be
|
|
69
|
+
* delivered as a self-contained string via loadHTMLString/SSE without needing
|
|
70
|
+
* the client to resolve external script/stylesheet URLs.
|
|
71
|
+
*/
|
|
72
|
+
export function inlineDistAssets(appDir: string, html: string): string {
|
|
73
|
+
const distDir = join(appDir, "dist");
|
|
74
|
+
|
|
75
|
+
// Inline main.js
|
|
76
|
+
const jsPath = join(distDir, "main.js");
|
|
77
|
+
if (existsSync(jsPath)) {
|
|
78
|
+
const js = readFileSync(jsPath, "utf-8");
|
|
79
|
+
html = html.replace(
|
|
80
|
+
/<script\s+type="module"\s+src="main\.js"\s*><\/script>/,
|
|
81
|
+
`<script type="module">${js}</script>`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Inline main.css
|
|
86
|
+
const cssPath = join(distDir, "main.css");
|
|
87
|
+
if (existsSync(cssPath)) {
|
|
88
|
+
const css = readFileSync(cssPath, "utf-8");
|
|
89
|
+
html = html.replace(
|
|
90
|
+
/<link\s+rel="stylesheet"\s+href="main\.css"\s*>/,
|
|
91
|
+
`<style>${css}</style>`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return html;
|
|
96
|
+
}
|
|
97
|
+
|
|
67
98
|
export interface AppRecord {
|
|
68
99
|
id: string;
|
|
69
100
|
appId: string;
|
package/src/memory/db-init.ts
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
createSequenceTables,
|
|
35
35
|
createTasksAndWorkItemsTables,
|
|
36
36
|
createWatchersAndLogsTables,
|
|
37
|
+
migrateAddSourceTypeColumns,
|
|
37
38
|
migrateAssistantContactMetadata,
|
|
38
39
|
migrateBackfillAudioAttachmentMimeTypes,
|
|
39
40
|
migrateBackfillContactInteractionStats,
|
|
@@ -504,6 +505,9 @@ export function initializeDb(): void {
|
|
|
504
505
|
// 89. Add user_file column to contacts for per-user persona file mapping
|
|
505
506
|
migrateContactsUserFileColumn(database);
|
|
506
507
|
|
|
508
|
+
// 90. Add source_type and source_message_role columns to memory_items
|
|
509
|
+
migrateAddSourceTypeColumns(database);
|
|
510
|
+
|
|
507
511
|
validateMigrationState(database);
|
|
508
512
|
|
|
509
513
|
if (process.env.BUN_TEST === "1") {
|