@vellumai/assistant 0.8.11 → 0.8.12-staging.2
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 +15 -17
- package/README.md +0 -6
- package/bun.lock +6 -122
- package/node_modules/@vellumai/gateway-client/bun.lock +1 -0
- package/node_modules/@vellumai/gateway-client/package.json +3 -1
- package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -1
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +87 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +3 -5
- package/openapi.yaml +633 -4
- package/package.json +1 -3
- package/src/__tests__/adaptive-thinking-repair.test.ts +185 -0
- package/src/__tests__/agent-loop-compaction-events.test.ts +7 -6
- package/src/__tests__/anthropic-provider.test.ts +129 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -1
- package/src/__tests__/btw-routes.test.ts +7 -34
- package/src/__tests__/checker.test.ts +6 -12
- package/src/__tests__/config-loader-backfill.test.ts +4 -2
- package/src/__tests__/config-loader-quarantine-notice.test.ts +167 -0
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-error.test.ts +2 -6
- package/src/__tests__/conversation-history-web-search.test.ts +8 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/disk-pressure-tools.test.ts +1 -1
- package/src/__tests__/exploration-drift-hook.test.ts +692 -0
- package/src/__tests__/filing-service.test.ts +8 -3
- package/src/__tests__/guardian-action-store.test.ts +0 -167
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -1
- package/src/__tests__/heartbeat-service.test.ts +5 -2
- package/src/__tests__/identity-intro-cache.test.ts +12 -5
- package/src/__tests__/identity-routes.test.ts +16 -57
- package/src/__tests__/injector-chain.test.ts +8 -3
- package/src/__tests__/injector-config-quarantine-notice.test.ts +115 -0
- package/src/__tests__/llm-catalog-parity.test.ts +16 -0
- package/src/__tests__/llm-usage-store.test.ts +11 -0
- package/src/__tests__/log-export-workspace.test.ts +468 -3
- package/src/__tests__/memory-v2-static-injector.test.ts +22 -0
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +19 -8
- package/src/__tests__/openai-provider.test.ts +34 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +0 -1
- package/src/__tests__/recurrence-engine.test.ts +45 -0
- package/src/__tests__/schedule-routes.test.ts +34 -0
- package/src/__tests__/scheduler-disk-pressure.test.ts +1 -1
- package/src/__tests__/script-proxy-conversation-manager.test.ts +10 -5
- package/src/__tests__/secret-fixtures.ts +20 -0
- package/src/__tests__/skill-tool-factory.test.ts +49 -0
- package/src/__tests__/subagent-role-registry.test.ts +24 -1
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +109 -11
- package/src/__tests__/tool-approval-handler.test.ts +85 -0
- package/src/__tests__/tool-audit-listener.test.ts +86 -0
- package/src/__tests__/tool-error-hook.test.ts +1 -0
- package/src/__tests__/tool-result-spool.test.ts +337 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
- package/src/__tests__/validate-input.test.ts +95 -1
- package/src/__tests__/workspace-migration-098-remove-stale-updates-bulletin-file.test.ts +65 -0
- package/src/__tests__/workspace-migration-099-disable-cache-one-shot-callsites.test.ts +139 -0
- package/src/__tests__/workspace-migration-100-upgrade-quality-profile-to-fable-5.test.ts +174 -0
- package/src/__tests__/workspace-migration-101-upgrade-balanced-economy-to-minimax-m3.test.ts +162 -0
- package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +45 -95
- package/src/acp/__tests__/agent-process.test.ts +315 -2
- package/src/acp/__tests__/prepare-agent-env.test.ts +79 -5
- package/src/acp/agent-process.ts +163 -34
- package/src/acp/prepare-agent-env.ts +55 -15
- package/src/agent/loop.ts +81 -24
- package/src/api/events/usage-progress.ts +28 -0
- package/src/api/index.ts +6 -0
- package/src/background-wake/wake-intent-hooks.test.ts +2 -0
- package/src/bundler/app-bundler.ts +25 -42
- package/src/bundler/app-compiler.ts +8 -0
- package/src/calls/call-controller.ts +1 -1
- package/src/cli/commands/plugins.ts +248 -15
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +318 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +16 -9
- package/src/cli/lib/__tests__/plugin-artifact.test.ts +183 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +158 -0
- package/src/cli/lib/__tests__/plugin-fingerprint.test.ts +245 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +307 -0
- package/src/cli/lib/inspect-plugin.ts +252 -0
- package/src/cli/lib/install-from-github.ts +214 -21
- package/src/cli/lib/list-installed-plugins.ts +17 -6
- package/src/cli/lib/plugin-artifact.ts +103 -0
- package/src/cli/lib/plugin-details.ts +18 -1
- package/src/cli/lib/plugin-fingerprint.ts +197 -0
- package/src/cli/lib/upgrade-plugin.ts +225 -0
- package/src/config/bundled-skills/subagent/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -2
- package/src/config/call-site-defaults.ts +13 -2
- package/src/config/feature-flag-registry.json +8 -16
- package/src/config/loader.ts +52 -59
- package/src/config/schema.ts +0 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +10 -0
- package/src/config/schemas/llm.ts +10 -0
- package/src/config/schemas/memory-v2.ts +13 -0
- package/src/config/schemas/memory-v3.ts +92 -0
- package/src/config/seed-inference-profiles.ts +4 -8
- package/src/context/post-turn-tool-result-truncation.ts +32 -18
- package/src/context/tool-result-spool.ts +104 -0
- package/src/credential-execution/feature-gates.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +41 -16
- package/src/daemon/conversation-error.ts +6 -15
- package/src/daemon/conversation.ts +9 -0
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/lifecycle.ts +1 -20
- package/src/daemon/message-types/conversations.ts +2 -15
- package/src/daemon/trust-context.ts +1 -1
- package/src/events/tool-audit-listener.ts +40 -9
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
- package/src/home/__tests__/home-content-refresh.test.ts +114 -0
- package/src/home/__tests__/suggested-prompts.test.ts +86 -5
- package/src/home/home-content-refresh.ts +43 -31
- package/src/home/home-greeting-cache.ts +8 -1
- package/src/home/home-greeting.ts +13 -9
- package/src/home/suggested-prompts.ts +77 -24
- package/src/ipc/routes/trust-rules.test.ts +66 -72
- package/src/media/image-credentials.ts +2 -2
- package/src/memory/__tests__/compaction-log-store-clickhouse.test.ts +432 -0
- package/src/memory/{compaction-log-writer-clickhouse.ts → compaction-log-store-clickhouse.ts} +264 -55
- package/src/memory/conversation-attention-store.ts +1 -0
- package/src/memory/conversation-bootstrap.ts +18 -9
- package/src/memory/conversation-crud.ts +12 -2
- package/src/memory/conversation-title-service.ts +53 -9
- package/src/memory/delivery-channels.ts +0 -69
- package/src/memory/graph/extraction-job.ts +0 -15
- package/src/memory/guardian-action-store.ts +1 -376
- package/src/memory/llm-usage-store.ts +5 -1
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +2 -2
- package/src/memory/v2/__tests__/consolidation-job.test.ts +183 -2
- package/src/memory/v2/__tests__/injection.test.ts +70 -0
- package/src/memory/v2/__tests__/static-context.test.ts +12 -0
- package/src/memory/v2/consolidation-job.ts +93 -9
- package/src/memory/v2/injection.ts +53 -0
- package/src/memory/v2/prompts/consolidation.ts +1 -0
- package/src/memory/v2/static-context.ts +13 -1
- package/src/memory/v2/sweep-job.ts +1 -1
- package/src/memory/v2/types.ts +5 -0
- package/src/plugin-api/types.ts +7 -0
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +300 -0
- package/src/plugins/defaults/exploration-drift/package.json +15 -0
- package/src/plugins/defaults/index.ts +25 -0
- package/src/plugins/defaults/memory-retrieval/injectors.ts +132 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/card.test.ts +92 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +2 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fresh-set.test.ts +52 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +2 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +136 -5
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +17 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +6 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +5 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +68 -4
- package/src/plugins/defaults/memory-v3-shadow/card.ts +49 -5
- package/src/plugins/defaults/memory-v3-shadow/fresh-set.ts +59 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +4 -2
- package/src/plugins/defaults/memory-v3-shadow/learned-edges.test.ts +169 -0
- package/src/plugins/defaults/memory-v3-shadow/learned-edges.ts +178 -0
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +115 -26
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -9
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +144 -22
- package/src/plugins/defaults/memory-v3-shadow/types.ts +24 -6
- package/src/plugins/defaults/title-generate/hooks/stop.ts +13 -0
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +16 -0
- package/src/prompts/cache-boundary.ts +17 -0
- package/src/prompts/sections.ts +50 -17
- package/src/prompts/system-prompt.ts +12 -4
- package/src/prompts/templates/system-sections.ts +22 -0
- package/src/providers/__tests__/unparseable-tool-args.test.ts +53 -0
- package/src/providers/anthropic/client.ts +74 -28
- package/src/providers/gemini/client.ts +5 -1
- package/src/providers/minimax/client.ts +9 -0
- package/src/providers/model-catalog.ts +28 -0
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/chat-completions-provider.ts +4 -2
- package/src/providers/openai/responses-provider.ts +7 -2
- package/src/providers/retry.ts +8 -0
- package/src/providers/types.ts +11 -0
- package/src/providers/unparseable-tool-args.ts +56 -0
- package/src/runtime/AGENTS.md +6 -0
- package/src/runtime/__tests__/agent-wake.test.ts +2 -2
- package/src/runtime/agent-wake.ts +5 -5
- package/src/runtime/background-job-runner.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +150 -3
- package/src/runtime/migrations/vbundle-import-analyzer.ts +29 -6
- package/src/runtime/migrations/vbundle-import-policy.ts +23 -0
- package/src/runtime/migrations/vbundle-importer.ts +9 -4
- package/src/runtime/migrations/vbundle-streaming-importer.ts +8 -3
- package/src/runtime/pre-first-message-gate.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +241 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +132 -0
- package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +97 -185
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +17 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +348 -0
- package/src/runtime/routes/__tests__/task-routes.test.ts +3 -3
- package/src/runtime/routes/btw-routes.ts +0 -14
- package/src/runtime/routes/conversation-compaction-routes.ts +86 -19
- package/src/runtime/routes/conversation-list-routes.ts +77 -5
- package/src/runtime/routes/conversation-management-routes.ts +54 -0
- package/src/runtime/routes/conversation-query-routes.ts +79 -4
- package/src/runtime/routes/gateway-log-routes.ts +14 -64
- package/src/runtime/routes/home-feed-routes.ts +10 -0
- package/src/runtime/routes/identity-intro-cache.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +76 -20
- package/src/runtime/routes/inbound-message-handler.ts +0 -36
- package/src/runtime/routes/log-export-routes.ts +143 -96
- package/src/runtime/routes/plugins-routes.ts +380 -0
- package/src/runtime/routes/redact-staged-export.ts +259 -0
- package/src/runtime/routes/schedule-routes.ts +19 -2
- package/src/runtime/routes/trust-rules-routes.ts +14 -67
- package/src/schedule/recurrence-engine.ts +34 -0
- package/src/schedule/scheduler.ts +1 -0
- package/src/security/redact-json.ts +61 -0
- package/src/skills/validate-input.ts +41 -1
- package/src/subagent/types.ts +26 -1
- package/src/telemetry/types.ts +15 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +6 -1
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/apps/executors.ts +1 -1
- package/src/tools/skills/skill-tool-factory.ts +19 -8
- package/src/tools/tool-approval-handler.ts +31 -0
- package/src/usage/types.ts +8 -1
- package/src/util/platform.ts +16 -0
- package/src/watcher/engine.ts +1 -0
- package/src/workspace/adaptive-thinking-repair.ts +113 -0
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +70 -67
- package/src/workspace/migrations/098-remove-stale-updates-bulletin-file.ts +31 -0
- package/src/workspace/migrations/099-disable-cache-one-shot-callsites.ts +81 -0
- package/src/workspace/migrations/100-upgrade-quality-profile-to-fable-5.ts +86 -0
- package/src/workspace/migrations/101-upgrade-balanced-economy-to-minimax-m3.ts +70 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +0 -202
- package/src/__tests__/conversation-starters-cadence.test.ts +0 -161
- package/src/__tests__/guardian-action-followup-executor.test.ts +0 -322
- package/src/__tests__/guardian-action-followup-store.test.ts +0 -373
- package/src/__tests__/guardian-action-late-reply.test.ts +0 -1083
- package/src/__tests__/update-bulletin-job.test.ts +0 -292
- package/src/config/schemas/updates.ts +0 -14
- package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +0 -227
- package/src/memory/conversation-starters-cadence.ts +0 -78
- package/src/prompts/update-bulletin-job.ts +0 -180
- package/src/runtime/guardian-action-followup-executor.ts +0 -306
|
@@ -1,52 +1,64 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* On-demand revalidation for LLM-generated home page content.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* The personalized greeting and assistant-generated suggestion prompts
|
|
5
|
+
* are served from caches (see `home-greeting-cache.ts` and
|
|
6
|
+
* `suggested-prompts.ts`). The GET handler stays read-only; when a
|
|
7
|
+
* client fetches the home feed it calls `revalidateHomeContentInBackground()`
|
|
8
|
+
* fire-and-forget, which regenerates whichever caches are stale and
|
|
9
|
+
* publishes a `home_feed_updated` event when fresh content lands so
|
|
10
|
+
* connected clients refetch and the personalized content swaps in.
|
|
8
11
|
*
|
|
9
|
-
*
|
|
10
|
-
* timer
|
|
12
|
+
* This keeps LLM cost proportional to actual Home usage: nothing
|
|
13
|
+
* generates at daemon startup or on a timer, and the per-cache TTLs
|
|
14
|
+
* (4 hours each) bound how often a regeneration can happen.
|
|
11
15
|
*/
|
|
12
16
|
|
|
17
|
+
import { buildAssistantEvent } from "../runtime/assistant-event.js";
|
|
18
|
+
import { assistantEventHub } from "../runtime/assistant-event-hub.js";
|
|
13
19
|
import { getLogger } from "../util/logger.js";
|
|
14
20
|
import { refreshPersonalizedGreeting } from "./home-greeting.js";
|
|
15
21
|
import { refreshAssistantSuggestedPrompts } from "./suggested-prompts.js";
|
|
16
22
|
|
|
17
23
|
const log = getLogger("home-content-refresh");
|
|
18
24
|
|
|
19
|
-
|
|
25
|
+
let inFlight: Promise<void> | null = null;
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
async function refreshAll(): Promise<void> {
|
|
24
|
-
await Promise.all([
|
|
27
|
+
async function revalidateAll(): Promise<void> {
|
|
28
|
+
const [greetingRefreshed, promptsRefreshed] = await Promise.all([
|
|
25
29
|
refreshPersonalizedGreeting(),
|
|
26
30
|
refreshAssistantSuggestedPrompts(),
|
|
27
31
|
]);
|
|
28
|
-
}
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* schedules re-generation every 30 minutes.
|
|
34
|
-
*/
|
|
35
|
-
export function startHomeContentRefresh(): void {
|
|
36
|
-
void refreshAll().catch((err) =>
|
|
37
|
-
log.warn({ err }, "Initial home content refresh failed"),
|
|
38
|
-
);
|
|
33
|
+
if (!greetingRefreshed && !promptsRefreshed) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
await assistantEventHub.publish(
|
|
38
|
+
buildAssistantEvent({
|
|
39
|
+
type: "home_feed_updated",
|
|
40
|
+
updatedAt: new Date().toISOString(),
|
|
41
|
+
newItemCount: 0,
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Regenerate stale home content in the background. Returns immediately;
|
|
48
|
+
* callers (the home feed GET handler) must not await generation. Both
|
|
49
|
+
* refreshers no-op when their cache is fresh, so calling this on every
|
|
50
|
+
* feed fetch is cheap — at most one generation per cache TTL window.
|
|
51
|
+
* Concurrent calls share a single in-flight revalidation.
|
|
52
|
+
*/
|
|
53
|
+
export function revalidateHomeContentInBackground(): void {
|
|
54
|
+
if (inFlight) {
|
|
55
|
+
return;
|
|
51
56
|
}
|
|
57
|
+
inFlight = revalidateAll()
|
|
58
|
+
.catch((err) => {
|
|
59
|
+
log.warn({ err }, "Home content revalidation failed");
|
|
60
|
+
})
|
|
61
|
+
.finally(() => {
|
|
62
|
+
inFlight = null;
|
|
63
|
+
});
|
|
52
64
|
}
|
|
@@ -79,14 +79,21 @@ export function getCachedHomeGreeting(): string | null {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Persist a freshly generated greeting. Returns `true` when the cache
|
|
84
|
+
* write landed; `false` on failure so callers don't report fresh content
|
|
85
|
+
* that the next read cannot serve.
|
|
86
|
+
*/
|
|
87
|
+
export function setCachedHomeGreeting(text: string): boolean {
|
|
83
88
|
try {
|
|
84
89
|
const hash = computeIdentityContentHash();
|
|
85
90
|
const now = String(Date.now());
|
|
86
91
|
setMemoryCheckpoint(CHECKPOINT_KEY_TEXT, text);
|
|
87
92
|
setMemoryCheckpoint(CHECKPOINT_KEY_HASH, hash);
|
|
88
93
|
setMemoryCheckpoint(CHECKPOINT_KEY_TIMESTAMP, now);
|
|
94
|
+
return true;
|
|
89
95
|
} catch {
|
|
90
96
|
// Cache write failure is non-fatal — next request will regenerate.
|
|
97
|
+
return false;
|
|
91
98
|
}
|
|
92
99
|
}
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
* caches the result for 4 hours (busted when identity files change).
|
|
7
7
|
*
|
|
8
8
|
* The GET handler reads only from cache (`getPersonalizedGreeting`).
|
|
9
|
-
* Generation runs
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Generation runs on demand via `refreshPersonalizedGreeting`, invoked
|
|
10
|
+
* fire-and-forget by the home-content revalidation coordinator when a
|
|
11
|
+
* client fetches the home feed and the cache is stale (see
|
|
12
|
+
* `home-content-refresh.ts`). Nothing generates at daemon startup or on
|
|
13
|
+
* a timer — LLM cost is only incurred when a user actually views Home.
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
16
|
import { resolveCallSiteConfig } from "../config/llm-resolver.js";
|
|
@@ -37,13 +39,14 @@ export function getPersonalizedGreeting(): string | null {
|
|
|
37
39
|
|
|
38
40
|
/**
|
|
39
41
|
* Generate a personalized greeting via LLM and write it to cache.
|
|
40
|
-
* No-ops when the cache is still fresh. Intended for
|
|
41
|
-
* invocation
|
|
42
|
+
* No-ops when the cache is still fresh. Intended for fire-and-forget
|
|
43
|
+
* background invocation, not the GET path. Returns `true` when a new
|
|
44
|
+
* greeting was generated and cached.
|
|
42
45
|
*/
|
|
43
|
-
export async function refreshPersonalizedGreeting(): Promise<
|
|
46
|
+
export async function refreshPersonalizedGreeting(): Promise<boolean> {
|
|
44
47
|
const cached = getCachedHomeGreeting();
|
|
45
48
|
if (cached) {
|
|
46
|
-
return;
|
|
49
|
+
return false;
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
try {
|
|
@@ -52,7 +55,7 @@ export async function refreshPersonalizedGreeting(): Promise<void> {
|
|
|
52
55
|
|
|
53
56
|
const provider = await getConfiguredProvider("homeGreeting");
|
|
54
57
|
if (!provider) {
|
|
55
|
-
return;
|
|
58
|
+
return false;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
const systemPrompt = buildSystemPrompt({
|
|
@@ -77,9 +80,10 @@ export async function refreshPersonalizedGreeting(): Promise<void> {
|
|
|
77
80
|
|
|
78
81
|
const text = result.text.trim();
|
|
79
82
|
if (text) {
|
|
80
|
-
setCachedHomeGreeting(text);
|
|
83
|
+
return setCachedHomeGreeting(text);
|
|
81
84
|
}
|
|
82
85
|
} catch (err) {
|
|
83
86
|
log.warn({ err }, "Failed to generate personalized home greeting");
|
|
84
87
|
}
|
|
88
|
+
return false;
|
|
85
89
|
}
|
|
@@ -3,13 +3,25 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Returns an array of `SuggestedPrompt` items shown at the top of the
|
|
5
5
|
* Home page as conversation starters. All prompts are generated by the
|
|
6
|
-
* assistant based on the user's connected services and context —
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* assistant based on the user's connected services and context — read
|
|
7
|
+
* from a checkpoint-backed cache in the GET path. Generation runs on
|
|
8
|
+
* demand via `refreshAssistantSuggestedPrompts`, invoked fire-and-forget
|
|
9
|
+
* by the home-content revalidation coordinator when a client fetches the
|
|
10
|
+
* home feed and the cache is stale (see `home-content-refresh.ts`).
|
|
11
|
+
* Nothing generates at daemon startup or on a timer.
|
|
12
|
+
*
|
|
13
|
+
* The cache persists in the `memory_checkpoints` table so a daemon
|
|
14
|
+
* restart does not force a regeneration. OAuth connect/disconnect paths
|
|
15
|
+
* invalidate it explicitly so suggestions track integration state.
|
|
9
16
|
*/
|
|
10
17
|
|
|
11
18
|
import { resolveCallSiteConfig } from "../config/llm-resolver.js";
|
|
12
19
|
import { getConfig } from "../config/loader.js";
|
|
20
|
+
import {
|
|
21
|
+
deleteMemoryCheckpoint,
|
|
22
|
+
getMemoryCheckpoint,
|
|
23
|
+
setMemoryCheckpoint,
|
|
24
|
+
} from "../memory/checkpoints.js";
|
|
13
25
|
import { buildSystemPrompt } from "../prompts/system-prompt.js";
|
|
14
26
|
import { getConfiguredProvider } from "../providers/provider-send-message.js";
|
|
15
27
|
import { buildAssistantEvent } from "../runtime/assistant-event.js";
|
|
@@ -22,14 +34,46 @@ import type { SuggestedPrompt } from "./feed-types.js";
|
|
|
22
34
|
const log = getLogger("suggested-prompts");
|
|
23
35
|
|
|
24
36
|
const LLM_SUGGESTIONS_TIMEOUT_MS = 5_000;
|
|
25
|
-
const LLM_CACHE_TTL_MS =
|
|
37
|
+
const LLM_CACHE_TTL_MS = 4 * 60 * 60 * 1000; // 4 hours
|
|
38
|
+
|
|
39
|
+
const CHECKPOINT_KEY_JSON = "home:suggested_prompts:json";
|
|
40
|
+
const CHECKPOINT_KEY_TIMESTAMP = "home:suggested_prompts:cached_at";
|
|
26
41
|
|
|
27
42
|
// ---------------------------------------------------------------------------
|
|
28
|
-
//
|
|
43
|
+
// Checkpoint-backed cache for LLM-generated suggestions
|
|
29
44
|
// ---------------------------------------------------------------------------
|
|
30
45
|
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
function readCachedPrompts(): SuggestedPrompt[] | null {
|
|
47
|
+
try {
|
|
48
|
+
const json = getMemoryCheckpoint(CHECKPOINT_KEY_JSON);
|
|
49
|
+
const timestampStr = getMemoryCheckpoint(CHECKPOINT_KEY_TIMESTAMP);
|
|
50
|
+
if (!json || !timestampStr) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const cachedAt = Number(timestampStr);
|
|
54
|
+
if (isNaN(cachedAt) || Date.now() - cachedAt > LLM_CACHE_TTL_MS) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const parsed = JSON.parse(json);
|
|
58
|
+
if (!Array.isArray(parsed)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return parsed as SuggestedPrompt[];
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function writeCachedPrompts(prompts: SuggestedPrompt[]): boolean {
|
|
68
|
+
try {
|
|
69
|
+
setMemoryCheckpoint(CHECKPOINT_KEY_JSON, JSON.stringify(prompts));
|
|
70
|
+
setMemoryCheckpoint(CHECKPOINT_KEY_TIMESTAMP, String(Date.now()));
|
|
71
|
+
return true;
|
|
72
|
+
} catch {
|
|
73
|
+
// Cache write failure is non-fatal — the next refresh regenerates.
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
33
77
|
|
|
34
78
|
/**
|
|
35
79
|
* Return cached assistant-generated prompts. No LLM calls happen in
|
|
@@ -37,22 +81,23 @@ let cachedLLMPromptsAt = 0;
|
|
|
37
81
|
* background refresh populates the cache.
|
|
38
82
|
*/
|
|
39
83
|
export async function getSuggestedPrompts(): Promise<SuggestedPrompt[]> {
|
|
40
|
-
|
|
41
|
-
return [...cachedLLMPrompts];
|
|
42
|
-
}
|
|
43
|
-
return [];
|
|
84
|
+
return readCachedPrompts() ?? [];
|
|
44
85
|
}
|
|
45
86
|
|
|
46
87
|
/**
|
|
47
|
-
* Drops the
|
|
48
|
-
*
|
|
88
|
+
* Drops the suggestion cache so the next on-demand refresh regenerates
|
|
89
|
+
* prompts with current integration state.
|
|
49
90
|
*
|
|
50
91
|
* Called from OAuth connect/disconnect paths so suggestions reflect the
|
|
51
|
-
* new state within one reload instead of waiting for the
|
|
92
|
+
* new state within one reload instead of waiting for the TTL.
|
|
52
93
|
*/
|
|
53
94
|
export function invalidateAssistantSuggestedPromptsCache(): void {
|
|
54
|
-
|
|
55
|
-
|
|
95
|
+
try {
|
|
96
|
+
deleteMemoryCheckpoint(CHECKPOINT_KEY_JSON);
|
|
97
|
+
deleteMemoryCheckpoint(CHECKPOINT_KEY_TIMESTAMP);
|
|
98
|
+
} catch {
|
|
99
|
+
// Invalidation failure is non-fatal — the TTL still bounds staleness.
|
|
100
|
+
}
|
|
56
101
|
assistantEventHub
|
|
57
102
|
.publish(
|
|
58
103
|
buildAssistantEvent({
|
|
@@ -70,21 +115,29 @@ export function invalidateAssistantSuggestedPromptsCache(): void {
|
|
|
70
115
|
}
|
|
71
116
|
|
|
72
117
|
/**
|
|
73
|
-
* Generate LLM-based suggestion prompts and write them to the
|
|
74
|
-
*
|
|
75
|
-
* invocation
|
|
118
|
+
* Generate LLM-based suggestion prompts and write them to the cache.
|
|
119
|
+
* No-ops when the cache is still fresh. Intended for fire-and-forget
|
|
120
|
+
* background invocation, not the GET path. Returns `true` when a new
|
|
121
|
+
* batch was generated and cached.
|
|
76
122
|
*/
|
|
77
|
-
export async function refreshAssistantSuggestedPrompts(): Promise<
|
|
78
|
-
if (
|
|
79
|
-
return;
|
|
123
|
+
export async function refreshAssistantSuggestedPrompts(): Promise<boolean> {
|
|
124
|
+
if (readCachedPrompts() !== null) {
|
|
125
|
+
return false;
|
|
80
126
|
}
|
|
81
127
|
|
|
82
128
|
try {
|
|
83
129
|
const llmPrompts = await generateAssistantPrompts();
|
|
84
|
-
|
|
85
|
-
|
|
130
|
+
// Cache empty results too — an empty or unparseable LLM response
|
|
131
|
+
// would otherwise leave the cache unpopulated and every Home feed
|
|
132
|
+
// GET would re-trigger generation until the TTL window closed.
|
|
133
|
+
// Only report success when the cache write landed AND there is new
|
|
134
|
+
// content — otherwise the coordinator would publish
|
|
135
|
+
// home_feed_updated for content the next GET cannot serve.
|
|
136
|
+
const wrote = writeCachedPrompts(llmPrompts);
|
|
137
|
+
return wrote && llmPrompts.length > 0;
|
|
86
138
|
} catch (err) {
|
|
87
139
|
log.warn({ err }, "Failed to refresh assistant suggested prompts");
|
|
140
|
+
return false;
|
|
88
141
|
}
|
|
89
142
|
}
|
|
90
143
|
|
|
@@ -1,123 +1,117 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unit tests for the trust rule IPC proxy routes.
|
|
3
|
-
*
|
|
4
|
-
* Covers:
|
|
5
|
-
* - trust_rules_list: no params, tool filter, include_all, origin filter
|
|
6
|
-
* - error path: non-OK gateway response surfaces body .error message
|
|
7
3
|
*/
|
|
8
4
|
|
|
9
5
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
10
6
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
mock.module("../../config/env.js", () => ({
|
|
16
|
-
getGatewayInternalBaseUrl: () => "http://localhost:7822",
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
type MockResponse = {
|
|
20
|
-
ok: boolean;
|
|
21
|
-
status: number;
|
|
22
|
-
json: () => Promise<unknown>;
|
|
7
|
+
type IpcCall = {
|
|
8
|
+
method: string;
|
|
9
|
+
params?: Record<string, unknown>;
|
|
23
10
|
};
|
|
24
11
|
|
|
25
|
-
let
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
json: async () => ({ rules: [] }),
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
let capturedFetchCalls: Array<{ url: string; init?: RequestInit }> = [];
|
|
12
|
+
let ipcCalls: IpcCall[] = [];
|
|
13
|
+
let ipcResult: unknown = { rules: [] };
|
|
14
|
+
let ipcError: Error | undefined;
|
|
32
15
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
16
|
+
const ipcCallPersistentMock = mock(
|
|
17
|
+
async (method: string, params?: Record<string, unknown>) => {
|
|
18
|
+
ipcCalls.push({ method, params });
|
|
19
|
+
if (ipcError) throw ipcError;
|
|
20
|
+
return ipcResult;
|
|
21
|
+
},
|
|
22
|
+
);
|
|
37
23
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// Import module under test AFTER mocks are set up
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
24
|
+
mock.module("../gateway-client.js", () => ({
|
|
25
|
+
ipcCallPersistent: ipcCallPersistentMock,
|
|
26
|
+
}));
|
|
43
27
|
|
|
44
28
|
import { ROUTES as trustRuleRoutes } from "../../runtime/routes/trust-rules-routes.js";
|
|
45
29
|
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
// Helpers
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
|
|
50
30
|
function findRoute(method: string) {
|
|
51
31
|
const route = trustRuleRoutes.find((r) => r.operationId === method);
|
|
52
32
|
if (!route) throw new Error(`Route not found: ${method}`);
|
|
53
33
|
return route;
|
|
54
34
|
}
|
|
55
35
|
|
|
56
|
-
// ---------------------------------------------------------------------------
|
|
57
|
-
// Tests
|
|
58
|
-
// ---------------------------------------------------------------------------
|
|
59
|
-
|
|
60
36
|
describe("trustRuleRoutes", () => {
|
|
61
37
|
beforeEach(() => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
json: async () => ({ rules: [] }),
|
|
67
|
-
};
|
|
68
|
-
mockFetch.mockClear();
|
|
38
|
+
ipcCalls = [];
|
|
39
|
+
ipcResult = { rules: [] };
|
|
40
|
+
ipcError = undefined;
|
|
41
|
+
ipcCallPersistentMock.mockClear();
|
|
69
42
|
});
|
|
70
43
|
|
|
71
44
|
describe("trust_rules_list", () => {
|
|
72
|
-
test("no params
|
|
45
|
+
test("no params calls trust_rules_list with empty params", async () => {
|
|
73
46
|
const route = findRoute("trust_rules_list");
|
|
74
47
|
await route.handler({ body: {} });
|
|
75
48
|
|
|
76
|
-
expect(
|
|
77
|
-
expect(capturedFetchCalls[0].url).toBe(
|
|
78
|
-
"http://localhost:7822/v1/trust-rules",
|
|
79
|
-
);
|
|
80
|
-
expect(capturedFetchCalls[0].init).toBeUndefined();
|
|
49
|
+
expect(ipcCalls).toEqual([{ method: "trust_rules_list", params: {} }]);
|
|
81
50
|
});
|
|
82
51
|
|
|
83
|
-
test("{ tool: 'bash' }
|
|
52
|
+
test("{ tool: 'bash' } is forwarded", async () => {
|
|
84
53
|
const route = findRoute("trust_rules_list");
|
|
85
54
|
await route.handler({ body: { tool: "bash" } });
|
|
86
55
|
|
|
87
|
-
expect(
|
|
88
|
-
"
|
|
89
|
-
);
|
|
56
|
+
expect(ipcCalls).toEqual([
|
|
57
|
+
{ method: "trust_rules_list", params: { tool: "bash" } },
|
|
58
|
+
]);
|
|
90
59
|
});
|
|
91
60
|
|
|
92
|
-
test("{ include_all: true }
|
|
61
|
+
test("{ include_all: true } is forwarded", async () => {
|
|
93
62
|
const route = findRoute("trust_rules_list");
|
|
94
63
|
await route.handler({ body: { include_all: true } });
|
|
95
64
|
|
|
96
|
-
expect(
|
|
97
|
-
"
|
|
98
|
-
);
|
|
65
|
+
expect(ipcCalls).toEqual([
|
|
66
|
+
{ method: "trust_rules_list", params: { include_all: true } },
|
|
67
|
+
]);
|
|
99
68
|
});
|
|
100
69
|
|
|
101
|
-
test("{ origin: 'user_defined' }
|
|
70
|
+
test("{ origin: 'user_defined' } is forwarded", async () => {
|
|
102
71
|
const route = findRoute("trust_rules_list");
|
|
103
72
|
await route.handler({ body: { origin: "user_defined" } });
|
|
104
73
|
|
|
105
|
-
expect(
|
|
106
|
-
|
|
107
|
-
|
|
74
|
+
expect(ipcCalls).toEqual([
|
|
75
|
+
{
|
|
76
|
+
method: "trust_rules_list",
|
|
77
|
+
params: { origin: "user_defined" },
|
|
78
|
+
},
|
|
79
|
+
]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("returns the parsed gateway IPC response", async () => {
|
|
83
|
+
ipcResult = {
|
|
84
|
+
rules: [
|
|
85
|
+
{
|
|
86
|
+
id: "rule-123",
|
|
87
|
+
tool: "bash",
|
|
88
|
+
pattern: "echo hello",
|
|
89
|
+
risk: "low",
|
|
90
|
+
description: "Allow echo hello",
|
|
91
|
+
origin: "user_defined",
|
|
92
|
+
userModified: false,
|
|
93
|
+
deleted: false,
|
|
94
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
95
|
+
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const route = findRoute("trust_rules_list");
|
|
101
|
+
const result = await route.handler({ body: {} });
|
|
102
|
+
|
|
103
|
+
expect(result).toEqual(ipcResult);
|
|
108
104
|
});
|
|
109
105
|
});
|
|
110
106
|
|
|
111
107
|
describe("error path", () => {
|
|
112
|
-
test("
|
|
113
|
-
|
|
114
|
-
ok: false,
|
|
115
|
-
status: 404,
|
|
116
|
-
json: async () => ({ error: "Not found" }),
|
|
117
|
-
};
|
|
108
|
+
test("gateway IPC error is propagated", async () => {
|
|
109
|
+
ipcError = new Error("Gateway IPC call timed out");
|
|
118
110
|
|
|
119
111
|
const route = findRoute("trust_rules_list");
|
|
120
|
-
await expect(route.handler({ body: {} })).rejects.toThrow(
|
|
112
|
+
await expect(route.handler({ body: {} })).rejects.toThrow(
|
|
113
|
+
"Gateway IPC call timed out",
|
|
114
|
+
);
|
|
121
115
|
});
|
|
122
116
|
});
|
|
123
117
|
});
|
|
@@ -66,8 +66,8 @@ export async function resolveImageGenCredentials(opts: {
|
|
|
66
66
|
function providerKeyHint(provider: ImageGenProvider): string {
|
|
67
67
|
switch (provider) {
|
|
68
68
|
case "gemini":
|
|
69
|
-
return "No Gemini API key configured. Please set your Gemini API key in Settings
|
|
69
|
+
return "No Gemini API key configured. Please set your Gemini API key in Settings → Models & Services.";
|
|
70
70
|
case "openai":
|
|
71
|
-
return "No OpenAI API key configured. Please set your OpenAI API key in Settings
|
|
71
|
+
return "No OpenAI API key configured. Please set your OpenAI API key in Settings → Models & Services.";
|
|
72
72
|
}
|
|
73
73
|
}
|