@vellumai/assistant 0.4.30 → 0.4.32
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 +1 -1
- package/Dockerfile +14 -8
- package/README.md +2 -2
- package/docs/architecture/memory.md +28 -29
- package/docs/runbook-trusted-contacts.md +1 -4
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -7
- package/src/__tests__/anthropic-provider.test.ts +86 -1
- package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
- package/src/__tests__/checker.test.ts +37 -98
- package/src/__tests__/commit-message-enrichment-service.test.ts +15 -4
- package/src/__tests__/config-schema.test.ts +6 -14
- package/src/__tests__/conflict-policy.test.ts +76 -0
- package/src/__tests__/conflict-store.test.ts +14 -20
- package/src/__tests__/contacts-tools.test.ts +8 -61
- package/src/__tests__/contradiction-checker.test.ts +5 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/daemon-server-session-init.test.ts +1 -19
- package/src/__tests__/followup-tools.test.ts +0 -30
- package/src/__tests__/gemini-provider.test.ts +79 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
- package/src/__tests__/guardian-routing-invariants.test.ts +6 -4
- package/src/__tests__/ipc-snapshot.test.ts +0 -4
- package/src/__tests__/managed-proxy-context.test.ts +163 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +13 -12
- package/src/__tests__/memory-regressions.test.ts +6 -6
- package/src/__tests__/openai-provider.test.ts +82 -0
- package/src/__tests__/provider-fail-open-selection.test.ts +134 -1
- package/src/__tests__/provider-managed-proxy-integration.test.ts +269 -0
- package/src/__tests__/recurrence-types.test.ts +0 -15
- package/src/__tests__/registry.test.ts +0 -10
- package/src/__tests__/schedule-tools.test.ts +28 -44
- package/src/__tests__/script-proxy-session-runtime.test.ts +6 -1
- package/src/__tests__/session-agent-loop.test.ts +0 -2
- package/src/__tests__/session-conflict-gate.test.ts +243 -388
- package/src/__tests__/session-profile-injection.test.ts +0 -2
- package/src/__tests__/session-runtime-assembly.test.ts +2 -3
- package/src/__tests__/session-skill-tools.test.ts +0 -49
- package/src/__tests__/session-workspace-cache-state.test.ts +0 -1
- package/src/__tests__/session-workspace-injection.test.ts +0 -1
- package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/task-management-tools.test.ts +111 -0
- package/src/__tests__/tool-grant-request-escalation.test.ts +2 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -1
- package/src/__tests__/twilio-config.test.ts +0 -3
- package/src/amazon/session.ts +30 -91
- package/src/approvals/guardian-decision-primitive.ts +11 -7
- package/src/approvals/guardian-request-resolvers.ts +5 -3
- package/src/calls/call-controller.ts +423 -571
- package/src/calls/finalize-call.ts +20 -0
- package/src/calls/relay-access-wait.ts +340 -0
- package/src/calls/relay-server.ts +269 -899
- package/src/calls/relay-setup-router.ts +307 -0
- package/src/calls/relay-verification.ts +280 -0
- package/src/calls/twilio-config.ts +1 -8
- package/src/calls/voice-control-protocol.ts +184 -0
- package/src/calls/voice-session-bridge.ts +1 -8
- package/src/config/agent-schema.ts +1 -1
- package/src/config/bundled-skills/contacts/SKILL.md +7 -18
- package/src/config/bundled-skills/contacts/TOOLS.json +4 -20
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +2 -4
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +6 -12
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +3 -24
- package/src/config/bundled-skills/followups/TOOLS.json +0 -4
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -10
- package/src/config/bundled-tool-registry.ts +0 -5
- package/src/config/core-schema.ts +1 -1
- package/src/config/env.ts +0 -10
- package/src/config/feature-flag-registry.json +1 -1
- package/src/config/loader.ts +19 -0
- package/src/config/memory-schema.ts +0 -10
- package/src/config/schema.ts +2 -2
- package/src/config/system-prompt.ts +6 -0
- package/src/contacts/contact-store.ts +36 -62
- package/src/contacts/contacts-write.ts +14 -3
- package/src/contacts/types.ts +9 -4
- package/src/daemon/handlers/config-heartbeat.ts +1 -2
- package/src/daemon/handlers/contacts.ts +2 -2
- package/src/daemon/handlers/guardian-actions.ts +1 -1
- package/src/daemon/handlers/session-history.ts +398 -0
- package/src/daemon/handlers/session-user-message.ts +982 -0
- package/src/daemon/handlers/sessions.ts +9 -1337
- package/src/daemon/ipc-contract/contacts.ts +2 -2
- package/src/daemon/ipc-contract/sessions.ts +0 -6
- package/src/daemon/ipc-contract-inventory.json +0 -1
- package/src/daemon/lifecycle.ts +0 -29
- package/src/daemon/session-agent-loop.ts +1 -45
- package/src/daemon/session-conflict-gate.ts +21 -82
- package/src/daemon/session-memory.ts +7 -52
- package/src/daemon/session-process.ts +3 -1
- package/src/daemon/session-runtime-assembly.ts +18 -35
- package/src/heartbeat/heartbeat-service.ts +5 -1
- package/src/home-base/app-link-store.ts +0 -7
- package/src/memory/conflict-intent.ts +3 -6
- package/src/memory/conflict-policy.ts +34 -0
- package/src/memory/conflict-store.ts +10 -18
- package/src/memory/contradiction-checker.ts +2 -2
- package/src/memory/conversation-attention-store.ts +1 -1
- package/src/memory/conversation-store.ts +0 -51
- package/src/memory/db-init.ts +8 -0
- package/src/memory/job-handlers/conflict.ts +24 -7
- package/src/memory/migrations/105-contacts-and-triage.ts +4 -7
- package/src/memory/migrations/134-contacts-notes-column.ts +68 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +31 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/migrations/registry.ts +6 -0
- package/src/memory/recall-cache.ts +0 -5
- package/src/memory/schema/calls.ts +274 -0
- package/src/memory/schema/contacts.ts +125 -0
- package/src/memory/schema/conversations.ts +129 -0
- package/src/memory/schema/guardian.ts +172 -0
- package/src/memory/schema/index.ts +8 -0
- package/src/memory/schema/infrastructure.ts +205 -0
- package/src/memory/schema/memory-core.ts +196 -0
- package/src/memory/schema/notifications.ts +191 -0
- package/src/memory/schema/tasks.ts +78 -0
- package/src/memory/schema.ts +1 -1402
- package/src/memory/slack-thread-store.ts +0 -69
- package/src/messaging/index.ts +0 -1
- package/src/messaging/types.ts +0 -38
- package/src/notifications/decisions-store.ts +2 -105
- package/src/notifications/deliveries-store.ts +0 -11
- package/src/notifications/preferences-store.ts +1 -58
- package/src/permissions/checker.ts +6 -17
- package/src/providers/anthropic/client.ts +6 -2
- package/src/providers/gemini/client.ts +13 -2
- package/src/providers/managed-proxy/constants.ts +55 -0
- package/src/providers/managed-proxy/context.ts +77 -0
- package/src/providers/registry.ts +112 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +51 -23
- package/src/runtime/guardian-action-service.ts +3 -2
- package/src/runtime/guardian-outbound-actions.ts +3 -3
- package/src/runtime/guardian-reply-router.ts +4 -4
- package/src/runtime/http-server.ts +83 -710
- package/src/runtime/http-types.ts +0 -16
- package/src/runtime/middleware/auth.ts +0 -12
- package/src/runtime/routes/app-routes.ts +33 -0
- package/src/runtime/routes/approval-routes.ts +32 -0
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +6 -3
- package/src/runtime/routes/attachment-routes.ts +32 -0
- package/src/runtime/routes/brain-graph-routes.ts +27 -0
- package/src/runtime/routes/call-routes.ts +41 -0
- package/src/runtime/routes/channel-readiness-routes.ts +20 -0
- package/src/runtime/routes/channel-routes.ts +70 -0
- package/src/runtime/routes/contact-routes.ts +371 -29
- package/src/runtime/routes/conversation-attention-routes.ts +15 -0
- package/src/runtime/routes/conversation-routes.ts +192 -194
- package/src/runtime/routes/debug-routes.ts +15 -0
- package/src/runtime/routes/events-routes.ts +16 -0
- package/src/runtime/routes/global-search-routes.ts +17 -2
- package/src/runtime/routes/guardian-action-routes.ts +23 -1
- package/src/runtime/routes/guardian-approval-interception.ts +2 -1
- package/src/runtime/routes/guardian-bootstrap-routes.ts +26 -1
- package/src/runtime/routes/guardian-refresh-routes.ts +20 -0
- package/src/runtime/routes/identity-routes.ts +20 -0
- package/src/runtime/routes/inbound-message-handler.ts +8 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +5 -1
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +2 -1
- package/src/runtime/routes/integration-routes.ts +83 -0
- package/src/runtime/routes/invite-routes.ts +31 -0
- package/src/runtime/routes/migration-routes.ts +47 -17
- package/src/runtime/routes/pairing-routes.ts +18 -0
- package/src/runtime/routes/secret-routes.ts +20 -0
- package/src/runtime/routes/surface-action-routes.ts +26 -0
- package/src/runtime/routes/trust-rules-routes.ts +31 -0
- package/src/runtime/routes/twilio-routes.ts +79 -0
- package/src/schedule/recurrence-types.ts +1 -11
- package/src/tools/followups/followup_create.ts +9 -3
- package/src/tools/mcp/mcp-tool-factory.ts +0 -17
- package/src/tools/memory/definitions.ts +0 -6
- package/src/tools/network/script-proxy/session-manager.ts +38 -3
- package/src/tools/schedule/create.ts +1 -3
- package/src/tools/schedule/update.ts +9 -6
- package/src/twitter/session.ts +29 -77
- package/src/util/cookie-session.ts +114 -0
- package/src/workspace/git-service.ts +6 -4
- package/src/__tests__/conversation-routes.test.ts +0 -99
- package/src/__tests__/get-weather.test.ts +0 -393
- package/src/__tests__/task-tools.test.ts +0 -685
- package/src/__tests__/weather-skill-regression.test.ts +0 -276
- package/src/autonomy/autonomy-resolver.ts +0 -62
- package/src/autonomy/autonomy-store.ts +0 -138
- package/src/autonomy/disposition-mapper.ts +0 -31
- package/src/autonomy/index.ts +0 -11
- package/src/autonomy/types.ts +0 -43
- package/src/config/bundled-skills/weather/SKILL.md +0 -38
- package/src/config/bundled-skills/weather/TOOLS.json +0 -36
- package/src/config/bundled-skills/weather/icon.svg +0 -24
- package/src/config/bundled-skills/weather/tools/get-weather.ts +0 -12
- package/src/contacts/startup-migration.ts +0 -21
- package/src/messaging/triage-engine.ts +0 -344
- package/src/tools/weather/service.ts +0 -712
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Legacy startup migration stub.
|
|
3
|
-
*
|
|
4
|
-
* Previously this populated the contacts table from legacy guardian-binding
|
|
5
|
-
* and ingress-member rows on daemon boot. That sync is now handled by the
|
|
6
|
-
* DB migration (131-drop-legacy-member-guardian-tables) which safety-syncs
|
|
7
|
-
* remaining data then drops both legacy tables. This stub is kept to avoid
|
|
8
|
-
* breaking callers; it simply no-ops.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { getLogger } from "../util/logger.js";
|
|
12
|
-
|
|
13
|
-
const log = getLogger("contacts-startup-migration");
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* No-op — legacy tables have been dropped. The DB migration
|
|
17
|
-
* (131-drop-legacy-member-guardian-tables) performed the final sync.
|
|
18
|
-
*/
|
|
19
|
-
export function migrateContactsFromLegacyTables(_assistantId: string): void {
|
|
20
|
-
log.debug("Legacy startup migration skipped — tables already dropped");
|
|
21
|
-
}
|
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Channel-agnostic message triage engine.
|
|
3
|
-
*
|
|
4
|
-
* Classifies an inbound message by combining sender context from the
|
|
5
|
-
* contact graph, matching action playbooks, and an LLM call
|
|
6
|
-
* for final classification. Results are persisted to the triageResults
|
|
7
|
-
* table for accuracy review.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { and, desc, eq, isNull } from "drizzle-orm";
|
|
11
|
-
import { v4 as uuid } from "uuid";
|
|
12
|
-
|
|
13
|
-
import { findContactByAddress } from "../contacts/contact-store.js";
|
|
14
|
-
import type { ContactWithChannels } from "../contacts/types.js";
|
|
15
|
-
import { getDb } from "../memory/db.js";
|
|
16
|
-
import { memoryItems, triageResults } from "../memory/schema.js";
|
|
17
|
-
import type { Playbook } from "../playbooks/types.js";
|
|
18
|
-
import { parsePlaybookStatement } from "../playbooks/types.js";
|
|
19
|
-
import {
|
|
20
|
-
createTimeout,
|
|
21
|
-
extractToolUse,
|
|
22
|
-
getConfiguredProvider,
|
|
23
|
-
userMessage,
|
|
24
|
-
} from "../providers/provider-send-message.js";
|
|
25
|
-
import { getLogger } from "../util/logger.js";
|
|
26
|
-
import { truncate } from "../util/truncate.js";
|
|
27
|
-
import type { InboundMessage, TriageResult } from "./types.js";
|
|
28
|
-
import { DEFAULT_TRIAGE_CATEGORIES } from "./types.js";
|
|
29
|
-
|
|
30
|
-
const log = getLogger("triage-engine");
|
|
31
|
-
|
|
32
|
-
const TRIAGE_MODEL_INTENT = "latency-optimized" as const;
|
|
33
|
-
const TRIAGE_CLASSIFICATION_TIMEOUT_MS = 15_000;
|
|
34
|
-
|
|
35
|
-
// ── Playbook fetching ────────────────────────────────────────────────
|
|
36
|
-
|
|
37
|
-
interface PlaybookMatch {
|
|
38
|
-
id: string;
|
|
39
|
-
playbook: Playbook;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Fetch all active playbooks that could apply to this message's channel,
|
|
44
|
-
* returning both the parsed Playbook and the memory item ID.
|
|
45
|
-
*/
|
|
46
|
-
function fetchMatchingPlaybooks(
|
|
47
|
-
channel: string,
|
|
48
|
-
scopeId = "default",
|
|
49
|
-
): PlaybookMatch[] {
|
|
50
|
-
const db = getDb();
|
|
51
|
-
|
|
52
|
-
const rows = db
|
|
53
|
-
.select({
|
|
54
|
-
id: memoryItems.id,
|
|
55
|
-
statement: memoryItems.statement,
|
|
56
|
-
})
|
|
57
|
-
.from(memoryItems)
|
|
58
|
-
.where(
|
|
59
|
-
and(
|
|
60
|
-
eq(memoryItems.kind, "playbook"),
|
|
61
|
-
eq(memoryItems.status, "active"),
|
|
62
|
-
eq(memoryItems.scopeId, scopeId),
|
|
63
|
-
isNull(memoryItems.invalidAt),
|
|
64
|
-
),
|
|
65
|
-
)
|
|
66
|
-
.orderBy(desc(memoryItems.importance))
|
|
67
|
-
.all();
|
|
68
|
-
|
|
69
|
-
const matches: PlaybookMatch[] = [];
|
|
70
|
-
for (const row of rows) {
|
|
71
|
-
const playbook = parsePlaybookStatement(row.statement);
|
|
72
|
-
if (!playbook) continue;
|
|
73
|
-
|
|
74
|
-
// Include if the playbook applies to all channels or matches this channel
|
|
75
|
-
if (playbook.channel === "*" || playbook.channel === channel) {
|
|
76
|
-
matches.push({ id: row.id, playbook });
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return matches;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ── LLM classification ──────────────────────────────────────────────
|
|
84
|
-
|
|
85
|
-
function buildSystemPrompt(
|
|
86
|
-
contact: ContactWithChannels | null,
|
|
87
|
-
playbookMatches: PlaybookMatch[],
|
|
88
|
-
): string {
|
|
89
|
-
const sections: string[] = [
|
|
90
|
-
`You are a message triage system. Classify the incoming message into a category and suggest an action.`,
|
|
91
|
-
``,
|
|
92
|
-
`Available categories (you may also use a custom category if none fit):`,
|
|
93
|
-
...DEFAULT_TRIAGE_CATEGORIES.map((c) => `- ${c}`),
|
|
94
|
-
];
|
|
95
|
-
|
|
96
|
-
if (contact) {
|
|
97
|
-
sections.push(``, `<sender-context>`, `Name: ${contact.displayName}`);
|
|
98
|
-
if (contact.relationship)
|
|
99
|
-
sections.push(`Relationship: ${contact.relationship}`);
|
|
100
|
-
if (contact.importance !== 0.5)
|
|
101
|
-
sections.push(`Importance: ${contact.importance}`);
|
|
102
|
-
if (contact.responseExpectation)
|
|
103
|
-
sections.push(`Response expectation: ${contact.responseExpectation}`);
|
|
104
|
-
if (contact.preferredTone)
|
|
105
|
-
sections.push(`Preferred tone: ${contact.preferredTone}`);
|
|
106
|
-
sections.push(
|
|
107
|
-
`Interaction count: ${contact.interactionCount}`,
|
|
108
|
-
`</sender-context>`,
|
|
109
|
-
);
|
|
110
|
-
} else {
|
|
111
|
-
sections.push(``, `Sender is not in the contact graph (unknown sender).`);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (playbookMatches.length > 0) {
|
|
115
|
-
sections.push(``, `<action-playbooks>`);
|
|
116
|
-
for (const { playbook } of playbookMatches) {
|
|
117
|
-
const channelLabel =
|
|
118
|
-
playbook.channel === "*" ? "all channels" : playbook.channel;
|
|
119
|
-
const autonomyLabel =
|
|
120
|
-
playbook.autonomyLevel === "auto"
|
|
121
|
-
? "execute automatically"
|
|
122
|
-
: playbook.autonomyLevel === "draft"
|
|
123
|
-
? "draft for review"
|
|
124
|
-
: "notify only";
|
|
125
|
-
sections.push(
|
|
126
|
-
`- WHEN "${playbook.trigger}" on ${channelLabel} → ${playbook.action} [${autonomyLabel}, priority=${playbook.priority}]`,
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
sections.push(`</action-playbooks>`);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
sections.push(
|
|
133
|
-
``,
|
|
134
|
-
`You MUST respond using the \`store_triage_result\` tool. Do not respond with text.`,
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
return sections.join("\n");
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const STORE_TRIAGE_TOOL = {
|
|
141
|
-
name: "store_triage_result",
|
|
142
|
-
description: "Store the triage classification result for this message",
|
|
143
|
-
input_schema: {
|
|
144
|
-
type: "object" as const,
|
|
145
|
-
properties: {
|
|
146
|
-
category: {
|
|
147
|
-
type: "string",
|
|
148
|
-
description:
|
|
149
|
-
"The triage category (e.g. needs_response, fyi, newsletter, cold_outreach, transactional, urgent, scheduling, or a custom category)",
|
|
150
|
-
},
|
|
151
|
-
confidence: {
|
|
152
|
-
type: "number",
|
|
153
|
-
description: "Confidence score between 0 and 1",
|
|
154
|
-
},
|
|
155
|
-
suggestedAction: {
|
|
156
|
-
type: "string",
|
|
157
|
-
description: "A concise description of the recommended action to take",
|
|
158
|
-
},
|
|
159
|
-
matchedPlaybookTriggers: {
|
|
160
|
-
type: "array",
|
|
161
|
-
items: { type: "string" },
|
|
162
|
-
description:
|
|
163
|
-
"List of playbook trigger strings that matched this message (empty array if none)",
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
required: [
|
|
167
|
-
"category",
|
|
168
|
-
"confidence",
|
|
169
|
-
"suggestedAction",
|
|
170
|
-
"matchedPlaybookTriggers",
|
|
171
|
-
],
|
|
172
|
-
},
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
function buildUserPrompt(message: InboundMessage): string {
|
|
176
|
-
const parts: string[] = [
|
|
177
|
-
`Channel: ${message.channel}`,
|
|
178
|
-
`From: ${message.sender}`,
|
|
179
|
-
];
|
|
180
|
-
if (message.subject) parts.push(`Subject: ${message.subject}`);
|
|
181
|
-
if (message.threadId) parts.push(`Thread ID: ${message.threadId}`);
|
|
182
|
-
parts.push(``, `Body:`, message.body);
|
|
183
|
-
|
|
184
|
-
return `Classify this inbound message:\n\n${parts.join("\n")}`;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ── Fallback classification ─────────────────────────────────────────
|
|
188
|
-
|
|
189
|
-
function buildFallbackResult(): TriageResult {
|
|
190
|
-
return {
|
|
191
|
-
category: "needs_response",
|
|
192
|
-
confidence: 0.3,
|
|
193
|
-
suggestedAction: "Review manually — LLM classification unavailable",
|
|
194
|
-
matchedPlaybooks: [],
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// ── Core triage function ────────────────────────────────────────────
|
|
199
|
-
|
|
200
|
-
export async function triageMessage(
|
|
201
|
-
message: InboundMessage,
|
|
202
|
-
scopeId?: string,
|
|
203
|
-
): Promise<TriageResult> {
|
|
204
|
-
// Step 1: Look up sender in contact graph
|
|
205
|
-
const contact = findContactByAddress(message.channel, message.sender);
|
|
206
|
-
|
|
207
|
-
// Step 2: Fetch matching playbooks
|
|
208
|
-
const playbookMatches = fetchMatchingPlaybooks(message.channel, scopeId);
|
|
209
|
-
|
|
210
|
-
// Step 3: Classify with LLM
|
|
211
|
-
const provider = getConfiguredProvider();
|
|
212
|
-
if (!provider) {
|
|
213
|
-
log.warn(
|
|
214
|
-
"Configured provider unavailable for triage classification, returning fallback",
|
|
215
|
-
);
|
|
216
|
-
const result = buildFallbackResult();
|
|
217
|
-
persistTriageResult(message, result, playbookMatches);
|
|
218
|
-
return result;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
let result: TriageResult;
|
|
222
|
-
try {
|
|
223
|
-
result = await classifyWithLLM(message, contact, playbookMatches);
|
|
224
|
-
} catch (err) {
|
|
225
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
226
|
-
log.warn({ err: errMsg }, "Triage LLM call failed, returning fallback");
|
|
227
|
-
result = buildFallbackResult();
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Step 4: Persist the result
|
|
231
|
-
persistTriageResult(message, result, playbookMatches);
|
|
232
|
-
|
|
233
|
-
return result;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
async function classifyWithLLM(
|
|
237
|
-
message: InboundMessage,
|
|
238
|
-
contact: ContactWithChannels | null,
|
|
239
|
-
playbookMatches: PlaybookMatch[],
|
|
240
|
-
): Promise<TriageResult> {
|
|
241
|
-
const provider = getConfiguredProvider()!;
|
|
242
|
-
const { signal, cleanup } = createTimeout(TRIAGE_CLASSIFICATION_TIMEOUT_MS);
|
|
243
|
-
|
|
244
|
-
const systemPrompt = buildSystemPrompt(contact, playbookMatches);
|
|
245
|
-
const userPrompt = buildUserPrompt(message);
|
|
246
|
-
|
|
247
|
-
try {
|
|
248
|
-
const response = await provider.sendMessage(
|
|
249
|
-
[userMessage(userPrompt)],
|
|
250
|
-
[STORE_TRIAGE_TOOL],
|
|
251
|
-
systemPrompt,
|
|
252
|
-
{
|
|
253
|
-
config: {
|
|
254
|
-
modelIntent: TRIAGE_MODEL_INTENT,
|
|
255
|
-
max_tokens: 1024,
|
|
256
|
-
tool_choice: { type: "tool" as const, name: "store_triage_result" },
|
|
257
|
-
},
|
|
258
|
-
signal,
|
|
259
|
-
},
|
|
260
|
-
);
|
|
261
|
-
cleanup();
|
|
262
|
-
|
|
263
|
-
const toolBlock = extractToolUse(response);
|
|
264
|
-
if (!toolBlock) {
|
|
265
|
-
log.warn("No tool_use block in triage response, returning fallback");
|
|
266
|
-
return buildFallbackResult();
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const input = toolBlock.input as {
|
|
270
|
-
category?: string;
|
|
271
|
-
confidence?: number;
|
|
272
|
-
suggestedAction?: string;
|
|
273
|
-
matchedPlaybookTriggers?: string[];
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
const matchedTriggers = new Set(
|
|
277
|
-
Array.isArray(input.matchedPlaybookTriggers)
|
|
278
|
-
? input.matchedPlaybookTriggers
|
|
279
|
-
: [],
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
// Map LLM-identified triggers back to the full playbook data
|
|
283
|
-
const matchedPlaybooks = playbookMatches
|
|
284
|
-
.filter(({ playbook }) => matchedTriggers.has(playbook.trigger))
|
|
285
|
-
.map(({ playbook }) => ({
|
|
286
|
-
trigger: playbook.trigger,
|
|
287
|
-
action: playbook.action,
|
|
288
|
-
autonomyLevel: playbook.autonomyLevel,
|
|
289
|
-
}));
|
|
290
|
-
|
|
291
|
-
const confidence =
|
|
292
|
-
typeof input.confidence === "number"
|
|
293
|
-
? Math.max(0, Math.min(1, input.confidence))
|
|
294
|
-
: 0.5;
|
|
295
|
-
|
|
296
|
-
return {
|
|
297
|
-
category:
|
|
298
|
-
typeof input.category === "string" ? input.category : "needs_response",
|
|
299
|
-
confidence,
|
|
300
|
-
suggestedAction:
|
|
301
|
-
typeof input.suggestedAction === "string"
|
|
302
|
-
? truncate(input.suggestedAction, 500, "")
|
|
303
|
-
: "Review manually",
|
|
304
|
-
matchedPlaybooks,
|
|
305
|
-
};
|
|
306
|
-
} finally {
|
|
307
|
-
cleanup();
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// ── Persistence ─────────────────────────────────────────────────────
|
|
312
|
-
|
|
313
|
-
function persistTriageResult(
|
|
314
|
-
message: InboundMessage,
|
|
315
|
-
result: TriageResult,
|
|
316
|
-
playbookMatches: PlaybookMatch[],
|
|
317
|
-
): void {
|
|
318
|
-
try {
|
|
319
|
-
const db = getDb();
|
|
320
|
-
const matchedIds = playbookMatches
|
|
321
|
-
.filter(({ playbook }) =>
|
|
322
|
-
result.matchedPlaybooks.some((mp) => mp.trigger === playbook.trigger),
|
|
323
|
-
)
|
|
324
|
-
.map(({ id }) => id);
|
|
325
|
-
|
|
326
|
-
db.insert(triageResults)
|
|
327
|
-
.values({
|
|
328
|
-
id: uuid(),
|
|
329
|
-
channel: message.channel,
|
|
330
|
-
sender: message.sender,
|
|
331
|
-
category: result.category,
|
|
332
|
-
confidence: result.confidence,
|
|
333
|
-
suggestedAction: result.suggestedAction,
|
|
334
|
-
matchedPlaybookIds:
|
|
335
|
-
matchedIds.length > 0 ? JSON.stringify(matchedIds) : null,
|
|
336
|
-
messageId: (message.metadata?.messageId as string) ?? null,
|
|
337
|
-
createdAt: Date.now(),
|
|
338
|
-
})
|
|
339
|
-
.run();
|
|
340
|
-
} catch (err) {
|
|
341
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
342
|
-
log.warn({ err: errMsg }, "Failed to persist triage result");
|
|
343
|
-
}
|
|
344
|
-
}
|