@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,180 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
-
|
|
4
|
-
import { getConfig } from "../config/loader.js";
|
|
5
|
-
import {
|
|
6
|
-
getMemoryCheckpoint,
|
|
7
|
-
setMemoryCheckpoint,
|
|
8
|
-
} from "../memory/checkpoints.js";
|
|
9
|
-
import { runBackgroundJob } from "../runtime/background-job-runner.js";
|
|
10
|
-
import { hasReceivedUserMessage } from "../runtime/pre-first-message-gate.js";
|
|
11
|
-
import { getLogger } from "../util/logger.js";
|
|
12
|
-
import {
|
|
13
|
-
getWorkspaceDirDisplay,
|
|
14
|
-
getWorkspacePromptPath,
|
|
15
|
-
} from "../util/platform.js";
|
|
16
|
-
|
|
17
|
-
const log = getLogger("update-bulletin-job");
|
|
18
|
-
|
|
19
|
-
const HASH_CHECKPOINT_KEY = "updates:last_processed_hash";
|
|
20
|
-
const EMPTY_HASH = "empty";
|
|
21
|
-
/**
|
|
22
|
-
* Hard timeout for the update-bulletin agent turn. The agent reads a small
|
|
23
|
-
* markdown file and (usually) deletes it; 10 minutes is generous headroom for
|
|
24
|
-
* a slow model + any tool calls (e.g. memory writes).
|
|
25
|
-
*/
|
|
26
|
-
const UPDATE_BULLETIN_TIMEOUT_MS = 10 * 60 * 1000;
|
|
27
|
-
|
|
28
|
-
function updateBulletinHint(): string {
|
|
29
|
-
const workspace = getWorkspaceDirDisplay();
|
|
30
|
-
return `Check ${workspace}/UPDATES.md — new release notes are present. Apply any assistant-facing behavior changes (new tools, deprecations, memory updates). If the user would benefit from knowing about a user-facing change, surface it only when the next topic makes it relevant — do not interrupt them with a proactive message. When you're done processing, delete the file by running \`cd "${workspace}" && rm UPDATES.md\` (the bare-filename \`rm UPDATES.md\` is auto-allowed; path-qualified deletes are not). A silent no-op is preferable to low-signal chatter.`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
type ReadResult =
|
|
34
|
-
| { kind: "missing" }
|
|
35
|
-
| { kind: "error"; err: unknown }
|
|
36
|
-
| { kind: "ok"; content: string };
|
|
37
|
-
|
|
38
|
-
function computeHash(content: string): string {
|
|
39
|
-
return createHash("sha256").update(content).digest("hex");
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function readTrimmedContent(path: string): ReadResult {
|
|
43
|
-
if (!existsSync(path)) return { kind: "missing" };
|
|
44
|
-
try {
|
|
45
|
-
return { kind: "ok", content: readFileSync(path, "utf-8").trim() };
|
|
46
|
-
} catch (err) {
|
|
47
|
-
return { kind: "error", err };
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Fire-and-forget background processor for the release-notes bulletin.
|
|
53
|
-
*
|
|
54
|
-
* If `<workspace>/UPDATES.md` has new (unprocessed) content, this drives a
|
|
55
|
-
* background conversation through `runBackgroundJob` with a hint pointing at
|
|
56
|
-
* the file. De-duplication uses a sha256 content hash stored in the
|
|
57
|
-
* `updates:last_processed_hash` memory checkpoint — an `"empty"` sentinel
|
|
58
|
-
* represents a missing/blank file so the job skips the common no-op case.
|
|
59
|
-
*
|
|
60
|
-
* The function never throws: any error inside `runBackgroundJob` is captured
|
|
61
|
-
* in its structured result (which already emits an `activity.failed`
|
|
62
|
-
* notification) and surrounding errors are logged at `warn` and swallowed,
|
|
63
|
-
* so callers can safely invoke it in a non-awaited context.
|
|
64
|
-
*
|
|
65
|
-
* Checkpoint write rules (intentionally conservative — prefer retry over
|
|
66
|
-
* poisoning the checkpoint when state is ambiguous):
|
|
67
|
-
* - File missing → checkpoint = `EMPTY_HASH`.
|
|
68
|
-
* - File present but unreadable → checkpoint UNCHANGED, warn logged.
|
|
69
|
-
* - `runBackgroundJob` returned `ok: false` → checkpoint UNCHANGED so the
|
|
70
|
-
* next startup retries. The runner has already emitted an
|
|
71
|
-
* `activity.failed` notification.
|
|
72
|
-
* - Job ran successfully + file deleted/empty → checkpoint = `EMPTY_HASH`.
|
|
73
|
-
* - Job ran successfully + file unchanged → checkpoint = current hash
|
|
74
|
-
* (agent intentionally left the file).
|
|
75
|
-
*/
|
|
76
|
-
export async function runUpdateBulletinJobIfNeeded(): Promise<void> {
|
|
77
|
-
if (getConfig().updates.enabled === false) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Warm-pool guard: don't process release notes before a real user has
|
|
82
|
-
// interacted with the assistant. Provider credentials are typically not
|
|
83
|
-
// registered until the user hatches the image, so this job would fail
|
|
84
|
-
// and leave a "Background job failed: update-bulletin" row in the
|
|
85
|
-
// sidebar the user inherits at hatch time. The checkpoint is left
|
|
86
|
-
// untouched on purpose — once the user sends their first message, the
|
|
87
|
-
// job runs on the next daemon start (or after the next UPDATES.md
|
|
88
|
-
// change) with normal semantics.
|
|
89
|
-
if (!hasReceivedUserMessage()) {
|
|
90
|
-
log.info(
|
|
91
|
-
"update-bulletin-job: skipped — daemon has not received a first user message yet",
|
|
92
|
-
);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
const updatesPath = getWorkspacePromptPath("UPDATES.md");
|
|
98
|
-
const initial = readTrimmedContent(updatesPath);
|
|
99
|
-
|
|
100
|
-
if (initial.kind === "error") {
|
|
101
|
-
log.warn(
|
|
102
|
-
{ err: initial.err, path: updatesPath },
|
|
103
|
-
"update-bulletin-job: failed to read UPDATES.md; leaving checkpoint unchanged so next startup retries",
|
|
104
|
-
);
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (initial.kind === "missing" || initial.content.length === 0) {
|
|
109
|
-
const stored = getMemoryCheckpoint(HASH_CHECKPOINT_KEY);
|
|
110
|
-
if (stored !== EMPTY_HASH) {
|
|
111
|
-
setMemoryCheckpoint(HASH_CHECKPOINT_KEY, EMPTY_HASH);
|
|
112
|
-
}
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const currentHash = computeHash(initial.content);
|
|
117
|
-
const stored = getMemoryCheckpoint(HASH_CHECKPOINT_KEY);
|
|
118
|
-
if (stored === currentHash) {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const result = await runBackgroundJob({
|
|
123
|
-
jobName: "update-bulletin",
|
|
124
|
-
source: "update-bulletin",
|
|
125
|
-
origin: "updates_bulletin",
|
|
126
|
-
prompt: updateBulletinHint(),
|
|
127
|
-
trustContext: {
|
|
128
|
-
sourceChannel: "vellum",
|
|
129
|
-
trustClass: "guardian",
|
|
130
|
-
},
|
|
131
|
-
callSite: "mainAgent",
|
|
132
|
-
timeoutMs: UPDATE_BULLETIN_TIMEOUT_MS,
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
if (!result.ok) {
|
|
136
|
-
log.warn(
|
|
137
|
-
{
|
|
138
|
-
conversationId: result.conversationId,
|
|
139
|
-
errorKind: result.errorKind,
|
|
140
|
-
err: result.error?.message,
|
|
141
|
-
},
|
|
142
|
-
"update-bulletin-job: runBackgroundJob returned ok=false; leaving checkpoint unchanged so next startup retries (failure notification already emitted by runner)",
|
|
143
|
-
);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Re-read after the job completed. We need to know whether the file was
|
|
148
|
-
// deleted or modified to decide whether to advance the checkpoint.
|
|
149
|
-
const after = readTrimmedContent(updatesPath);
|
|
150
|
-
|
|
151
|
-
if (after.kind === "error") {
|
|
152
|
-
log.warn(
|
|
153
|
-
{ err: after.err, path: updatesPath },
|
|
154
|
-
"update-bulletin-job: failed to re-read UPDATES.md after job; leaving checkpoint unchanged so next startup retries",
|
|
155
|
-
);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const fileMissingOrEmpty =
|
|
160
|
-
after.kind === "missing" || after.content.length === 0;
|
|
161
|
-
|
|
162
|
-
if (fileMissingOrEmpty) {
|
|
163
|
-
// The agent (or another process) emptied/removed the file. This is the
|
|
164
|
-
// expected happy path — record the empty sentinel.
|
|
165
|
-
setMemoryCheckpoint(HASH_CHECKPOINT_KEY, EMPTY_HASH);
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Job succeeded and the file is still present — the agent intentionally
|
|
170
|
-
// left it (or modified it). Record the current hash so we don't re-process
|
|
171
|
-
// the same content on next startup.
|
|
172
|
-
setMemoryCheckpoint(HASH_CHECKPOINT_KEY, computeHash(after.content));
|
|
173
|
-
} catch (err) {
|
|
174
|
-
log.warn(
|
|
175
|
-
{ err },
|
|
176
|
-
"update-bulletin-job: outer flow threw; swallowing so callers can fire-and-forget",
|
|
177
|
-
);
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Guardian action follow-up executor.
|
|
3
|
-
*
|
|
4
|
-
* After the conversation engine classifies the guardian's reply as
|
|
5
|
-
* `call_back` and transitions the follow-up state to `dispatching`,
|
|
6
|
-
* this module executes the actual action:
|
|
7
|
-
*
|
|
8
|
-
* - **call_back**: Starts an outbound call to the counterparty with
|
|
9
|
-
* context about the guardian's answer.
|
|
10
|
-
*
|
|
11
|
-
* The executor resolves the counterparty from the original call session,
|
|
12
|
-
* dispatches the appropriate action, and returns a result with generated
|
|
13
|
-
* reply text for the guardian's confirmation message.
|
|
14
|
-
*
|
|
15
|
-
* This module is channel-agnostic: both inbound-message-handler (Telegram
|
|
16
|
-
* channels) and session-process (desktop channel) use it.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { startCall } from "../calls/call-domain.js";
|
|
20
|
-
import { getCallSession } from "../calls/call-store.js";
|
|
21
|
-
import { getOrCreateConversation } from "../memory/conversation-key-store.js";
|
|
22
|
-
import {
|
|
23
|
-
finalizeFollowup,
|
|
24
|
-
type FollowupAction,
|
|
25
|
-
getGuardianActionRequest,
|
|
26
|
-
type GuardianActionRequest,
|
|
27
|
-
} from "../memory/guardian-action-store.js";
|
|
28
|
-
import { getLogger } from "../util/logger.js";
|
|
29
|
-
import { DAEMON_INTERNAL_ASSISTANT_ID } from "./assistant-scope.js";
|
|
30
|
-
import { composeGuardianActionMessageGenerative } from "./guardian-action-message-composer.js";
|
|
31
|
-
import type { GuardianActionCopyGenerator } from "./http-types.js";
|
|
32
|
-
|
|
33
|
-
const log = getLogger("guardian-action-followup-executor");
|
|
34
|
-
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
// Types
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
|
|
39
|
-
export interface CounterpartyInfo {
|
|
40
|
-
phoneNumber: string;
|
|
41
|
-
/** Human-readable identifier (phone number or name if available). */
|
|
42
|
-
displayIdentifier: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export type FollowupExecutionResult =
|
|
46
|
-
| { ok: true; action: FollowupAction; guardianReplyText: string }
|
|
47
|
-
| {
|
|
48
|
-
ok: false;
|
|
49
|
-
action: FollowupAction;
|
|
50
|
-
guardianReplyText: string;
|
|
51
|
-
error: string;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
|
-
// Counterparty resolution
|
|
56
|
-
// ---------------------------------------------------------------------------
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Resolve the counterparty (the external person) from the original call
|
|
60
|
-
* session by call direction.
|
|
61
|
-
*
|
|
62
|
-
* - **Inbound calls** (`initiatedFromConversationId` is null): the external
|
|
63
|
-
* caller is `fromNumber`, the assistant's Twilio number is `toNumber`.
|
|
64
|
-
* - **Outbound calls** (`initiatedFromConversationId` is set): the assistant
|
|
65
|
-
* placed the call so `fromNumber` is the assistant's number and `toNumber`
|
|
66
|
-
* is the external callee.
|
|
67
|
-
*/
|
|
68
|
-
export function resolveCounterparty(
|
|
69
|
-
callSessionId: string,
|
|
70
|
-
): CounterpartyInfo | null {
|
|
71
|
-
const session = getCallSession(callSessionId);
|
|
72
|
-
if (!session) {
|
|
73
|
-
log.warn(
|
|
74
|
-
{ callSessionId },
|
|
75
|
-
"Cannot resolve counterparty: call session not found",
|
|
76
|
-
);
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Outbound calls (startCall) store the assistant's number in fromNumber
|
|
81
|
-
// and the callee in toNumber. They always have initiatedFromConversationId.
|
|
82
|
-
const isOutbound = !!session.initiatedFromConversationId;
|
|
83
|
-
const phoneNumber = isOutbound ? session.toNumber : session.fromNumber;
|
|
84
|
-
|
|
85
|
-
if (!phoneNumber) {
|
|
86
|
-
log.warn(
|
|
87
|
-
{ callSessionId, isOutbound },
|
|
88
|
-
"Cannot resolve counterparty: no phone number on call session",
|
|
89
|
-
);
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
phoneNumber,
|
|
95
|
-
displayIdentifier: phoneNumber,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ---------------------------------------------------------------------------
|
|
100
|
-
// Action dispatchers
|
|
101
|
-
// ---------------------------------------------------------------------------
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Start an outbound call to the counterparty with context about the
|
|
105
|
-
* guardian's answer. Uses the existing call start domain flow.
|
|
106
|
-
*/
|
|
107
|
-
async function executeCallBack(
|
|
108
|
-
request: GuardianActionRequest,
|
|
109
|
-
counterparty: CounterpartyInfo,
|
|
110
|
-
): Promise<{ ok: true } | { ok: false; error: string }> {
|
|
111
|
-
try {
|
|
112
|
-
const task = request.lateAnswerText
|
|
113
|
-
? `Call back regarding a question that was asked earlier: "${request.questionText}". The guardian has provided an answer: "${request.lateAnswerText}". Relay this answer to the person.`
|
|
114
|
-
: `Call back regarding a question that was asked earlier: "${request.questionText}". The guardian wants to follow up with them.`;
|
|
115
|
-
|
|
116
|
-
const callbackContext = [
|
|
117
|
-
`This is a follow-up callback. The person called earlier and asked: "${request.questionText}".`,
|
|
118
|
-
request.lateAnswerText
|
|
119
|
-
? `The guardian's answer is: "${request.lateAnswerText}".`
|
|
120
|
-
: null,
|
|
121
|
-
"Relay this information naturally and ask if they need anything else.",
|
|
122
|
-
]
|
|
123
|
-
.filter(Boolean)
|
|
124
|
-
.join(" ");
|
|
125
|
-
|
|
126
|
-
// Create a conversation for the callback call
|
|
127
|
-
const convKey = `followup-callback:${request.id}`;
|
|
128
|
-
const { conversationId } = getOrCreateConversation(convKey);
|
|
129
|
-
|
|
130
|
-
const result = await startCall({
|
|
131
|
-
phoneNumber: counterparty.phoneNumber,
|
|
132
|
-
task,
|
|
133
|
-
context: callbackContext,
|
|
134
|
-
conversationId,
|
|
135
|
-
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
if (!result.ok) {
|
|
139
|
-
log.warn(
|
|
140
|
-
{ requestId: request.id, error: result.error },
|
|
141
|
-
"Failed to start follow-up callback call",
|
|
142
|
-
);
|
|
143
|
-
return { ok: false, error: result.error };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
log.info(
|
|
147
|
-
{
|
|
148
|
-
requestId: request.id,
|
|
149
|
-
callSessionId: result.session.id,
|
|
150
|
-
counterpartyPhone: counterparty.phoneNumber,
|
|
151
|
-
},
|
|
152
|
-
"Follow-up call_back initiated successfully",
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
return { ok: true };
|
|
156
|
-
} catch (err) {
|
|
157
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
158
|
-
log.error(
|
|
159
|
-
{
|
|
160
|
-
err,
|
|
161
|
-
requestId: request.id,
|
|
162
|
-
counterpartyPhone: counterparty.phoneNumber,
|
|
163
|
-
},
|
|
164
|
-
"Failed to start follow-up callback call",
|
|
165
|
-
);
|
|
166
|
-
return { ok: false, error: message };
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// ---------------------------------------------------------------------------
|
|
171
|
-
// Public API
|
|
172
|
-
// ---------------------------------------------------------------------------
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Execute a follow-up action after the conversation engine has classified
|
|
176
|
-
* the guardian's intent as call_back and the follow-up
|
|
177
|
-
* state has been transitioned to `dispatching`.
|
|
178
|
-
*
|
|
179
|
-
* On success: finalizes the follow-up to `completed` and returns
|
|
180
|
-
* generated confirmation text for the guardian.
|
|
181
|
-
*
|
|
182
|
-
* On failure: finalizes the follow-up to `failed` and returns
|
|
183
|
-
* generated error text for the guardian.
|
|
184
|
-
*/
|
|
185
|
-
export async function executeFollowupAction(
|
|
186
|
-
requestId: string,
|
|
187
|
-
action: FollowupAction,
|
|
188
|
-
generator?: GuardianActionCopyGenerator,
|
|
189
|
-
): Promise<FollowupExecutionResult> {
|
|
190
|
-
const request = getGuardianActionRequest(requestId);
|
|
191
|
-
if (!request) {
|
|
192
|
-
const errorText = await composeGuardianActionMessageGenerative(
|
|
193
|
-
{
|
|
194
|
-
scenario: "followup_action_failed",
|
|
195
|
-
failureReason: "The follow-up request could not be found.",
|
|
196
|
-
},
|
|
197
|
-
{},
|
|
198
|
-
generator,
|
|
199
|
-
);
|
|
200
|
-
return {
|
|
201
|
-
ok: false,
|
|
202
|
-
action,
|
|
203
|
-
guardianReplyText: errorText,
|
|
204
|
-
error: "Request not found",
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (request.followupState !== "dispatching") {
|
|
209
|
-
const errorText = await composeGuardianActionMessageGenerative(
|
|
210
|
-
{
|
|
211
|
-
scenario: "followup_action_failed",
|
|
212
|
-
failureReason:
|
|
213
|
-
"This follow-up is no longer in a valid state for execution.",
|
|
214
|
-
},
|
|
215
|
-
{},
|
|
216
|
-
generator,
|
|
217
|
-
);
|
|
218
|
-
return {
|
|
219
|
-
ok: false,
|
|
220
|
-
action,
|
|
221
|
-
guardianReplyText: errorText,
|
|
222
|
-
error: `Invalid followup state: ${request.followupState}`,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Resolve the counterparty from the original call session
|
|
227
|
-
const counterparty = resolveCounterparty(request.callSessionId);
|
|
228
|
-
if (!counterparty) {
|
|
229
|
-
finalizeFollowup(requestId, "failed");
|
|
230
|
-
const errorText = await composeGuardianActionMessageGenerative(
|
|
231
|
-
{
|
|
232
|
-
scenario: "followup_action_failed",
|
|
233
|
-
failureReason: "I couldn't find the caller's contact information.",
|
|
234
|
-
},
|
|
235
|
-
{},
|
|
236
|
-
generator,
|
|
237
|
-
);
|
|
238
|
-
return {
|
|
239
|
-
ok: false,
|
|
240
|
-
action,
|
|
241
|
-
guardianReplyText: errorText,
|
|
242
|
-
error: "Counterparty not found",
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Execute the action
|
|
247
|
-
let actionResult: { ok: true } | { ok: false; error: string };
|
|
248
|
-
|
|
249
|
-
if (action === "call_back") {
|
|
250
|
-
actionResult = await executeCallBack(request, counterparty);
|
|
251
|
-
} else {
|
|
252
|
-
// decline is already handled in M5 — should not reach the executor.
|
|
253
|
-
finalizeFollowup(requestId, "failed");
|
|
254
|
-
const errorText = await composeGuardianActionMessageGenerative(
|
|
255
|
-
{
|
|
256
|
-
scenario: "followup_action_failed",
|
|
257
|
-
failureReason: "An unexpected action was requested.",
|
|
258
|
-
},
|
|
259
|
-
{},
|
|
260
|
-
generator,
|
|
261
|
-
);
|
|
262
|
-
return {
|
|
263
|
-
ok: false,
|
|
264
|
-
action,
|
|
265
|
-
guardianReplyText: errorText,
|
|
266
|
-
error: `Unsupported action: ${action}`,
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (actionResult.ok) {
|
|
271
|
-
finalizeFollowup(requestId, "completed");
|
|
272
|
-
|
|
273
|
-
const scenario = "followup_call_started" as const;
|
|
274
|
-
const confirmText = await composeGuardianActionMessageGenerative(
|
|
275
|
-
{
|
|
276
|
-
scenario,
|
|
277
|
-
counterpartyPhone: counterparty.phoneNumber,
|
|
278
|
-
questionText: request.questionText,
|
|
279
|
-
lateAnswerText: request.lateAnswerText ?? undefined,
|
|
280
|
-
},
|
|
281
|
-
{},
|
|
282
|
-
generator,
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
return { ok: true, action, guardianReplyText: confirmText };
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Action failed
|
|
289
|
-
finalizeFollowup(requestId, "failed");
|
|
290
|
-
const errorText = await composeGuardianActionMessageGenerative(
|
|
291
|
-
{
|
|
292
|
-
scenario: "followup_action_failed",
|
|
293
|
-
failureReason: actionResult.error,
|
|
294
|
-
counterpartyPhone: counterparty.phoneNumber,
|
|
295
|
-
},
|
|
296
|
-
{},
|
|
297
|
-
generator,
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
return {
|
|
301
|
-
ok: false,
|
|
302
|
-
action,
|
|
303
|
-
guardianReplyText: errorText,
|
|
304
|
-
error: actionResult.error,
|
|
305
|
-
};
|
|
306
|
-
}
|