@vellumai/assistant 0.3.14 → 0.3.16
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 +142 -0
- package/Dockerfile +2 -2
- package/README.md +5 -5
- package/docs/architecture/http-token-refresh.md +252 -0
- package/docs/architecture/memory.md +5 -4
- package/docs/architecture/scheduling.md +4 -88
- package/docs/runbook-trusted-contacts.md +283 -0
- package/docs/trusted-contact-access.md +247 -0
- package/package.json +1 -1
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +2 -6
- package/src/__tests__/access-request-decision.test.ts +331 -0
- package/src/__tests__/asset-materialize-tool.test.ts +7 -7
- package/src/__tests__/asset-search-tool.test.ts +15 -15
- package/src/__tests__/attachments-store.test.ts +13 -13
- package/src/__tests__/call-controller.test.ts +150 -4
- package/src/__tests__/call-conversation-messages.test.ts +2 -2
- package/src/__tests__/call-pointer-messages.test.ts +28 -0
- package/src/__tests__/call-start-guardian-guard.test.ts +93 -0
- package/src/__tests__/channel-approval-routes.test.ts +108 -12
- package/src/__tests__/channel-guardian.test.ts +16 -14
- package/src/__tests__/checker.test.ts +24 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +2 -2
- package/src/__tests__/config-watcher.test.ts +358 -0
- package/src/__tests__/conversation-pairing.test.ts +24 -24
- package/src/__tests__/conversation-store.test.ts +36 -36
- package/src/__tests__/date-context.test.ts +179 -1
- package/src/__tests__/db-migration-rollback.test.ts +4 -7
- package/src/__tests__/deterministic-verification-control-plane.test.ts +5 -5
- package/src/__tests__/emit-signal-routing-intent.test.ts +179 -0
- package/src/__tests__/gateway-only-guard.test.ts +188 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +451 -0
- package/src/__tests__/guardian-action-copy-generator.test.ts +197 -0
- package/src/__tests__/guardian-action-followup-executor.test.ts +379 -0
- package/src/__tests__/guardian-action-followup-store.test.ts +376 -0
- package/src/__tests__/guardian-action-late-reply.test.ts +294 -0
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +71 -0
- package/src/__tests__/guardian-action-sweep.test.ts +9 -9
- package/src/__tests__/guardian-control-plane-policy.test.ts +1 -3
- package/src/__tests__/guardian-outbound-http.test.ts +202 -10
- package/src/__tests__/guardian-verification-intent-routing.test.ts +179 -0
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +141 -0
- package/src/__tests__/handlers-telegram-config.test.ts +6 -6
- package/src/__tests__/hooks-runner.test.ts +13 -4
- package/src/__tests__/ingress-routes-http.test.ts +443 -0
- package/src/__tests__/intent-routing.test.ts +14 -0
- package/src/__tests__/ipc-snapshot.test.ts +2 -5
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
- package/src/__tests__/memory-regressions.test.ts +16 -12
- package/src/__tests__/non-member-access-request.test.ts +282 -0
- package/src/__tests__/notification-decision-strategy.test.ts +136 -0
- package/src/__tests__/notification-routing-intent.test.ts +11 -2
- package/src/__tests__/notification-thread-candidates.test.ts +166 -0
- package/src/__tests__/recording-intent-fallback.test.ts +0 -1
- package/src/__tests__/recording-intent-handler.test.ts +6 -3
- package/src/__tests__/recording-intent.test.ts +3 -2
- package/src/__tests__/recording-state-machine.test.ts +337 -26
- package/src/__tests__/registry.test.ts +17 -8
- package/src/__tests__/relay-server.test.ts +105 -0
- package/src/__tests__/reminder.test.ts +13 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +4 -4
- package/src/__tests__/scheduler-recurrence.test.ts +50 -0
- package/src/__tests__/server-history-render.test.ts +8 -8
- package/src/__tests__/session-agent-loop.test.ts +1 -0
- package/src/__tests__/session-runtime-assembly.test.ts +49 -0
- package/src/__tests__/session-skill-tools.test.ts +1 -0
- package/src/__tests__/skill-projection.benchmark.test.ts +11 -3
- package/src/__tests__/slack-channel-config.test.ts +230 -0
- package/src/__tests__/subagent-manager-notify.test.ts +4 -4
- package/src/__tests__/swarm-session-integration.test.ts +2 -2
- package/src/__tests__/system-prompt.test.ts +43 -0
- package/src/__tests__/task-management-tools.test.ts +3 -3
- package/src/__tests__/task-tools.test.ts +3 -3
- package/src/__tests__/trust-store.test.ts +17 -1
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +491 -0
- package/src/__tests__/trusted-contact-multichannel.test.ts +409 -0
- package/src/__tests__/trusted-contact-verification.test.ts +360 -0
- package/src/__tests__/update-bulletin-format.test.ts +119 -0
- package/src/__tests__/update-bulletin-state.test.ts +129 -0
- package/src/__tests__/update-bulletin.test.ts +260 -0
- package/src/__tests__/update-template-contract.test.ts +29 -0
- package/src/agent/loop.ts +2 -2
- package/src/amazon/client.ts +2 -3
- package/src/calls/call-controller.ts +115 -34
- package/src/calls/call-conversation-messages.ts +2 -2
- package/src/calls/call-domain.ts +10 -3
- package/src/calls/call-pointer-messages.ts +17 -5
- package/src/calls/guardian-action-sweep.ts +77 -36
- package/src/calls/relay-server.ts +51 -12
- package/src/calls/twilio-routes.ts +3 -1
- package/src/calls/types.ts +1 -1
- package/src/calls/voice-session-bridge.ts +4 -4
- package/src/cli/core-commands.ts +3 -3
- package/src/cli/map.ts +8 -5
- package/src/config/bundled-skills/phone-calls/SKILL.md +16 -1
- package/src/config/bundled-skills/tasks/SKILL.md +1 -1
- package/src/config/bundled-skills/tasks/TOOLS.json +4 -4
- package/src/config/bundled-skills/time-based-actions/SKILL.md +11 -1
- package/src/config/computer-use-prompt.ts +1 -0
- package/src/config/core-schema.ts +16 -0
- package/src/config/env-registry.ts +1 -0
- package/src/config/env.ts +16 -1
- package/src/config/memory-schema.ts +5 -0
- package/src/config/schema.ts +4 -0
- package/src/config/system-prompt.ts +69 -2
- package/src/config/templates/BOOTSTRAP.md +1 -1
- package/src/config/templates/IDENTITY.md +8 -4
- package/src/config/templates/SOUL.md +14 -0
- package/src/config/templates/UPDATES.md +16 -0
- package/src/config/templates/USER.md +5 -1
- package/src/config/types.ts +1 -0
- package/src/config/update-bulletin-format.ts +52 -0
- package/src/config/update-bulletin-state.ts +49 -0
- package/src/config/update-bulletin.ts +82 -0
- package/src/config/vellum-skills/catalog.json +6 -0
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
- package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +44 -10
- package/src/config/vellum-skills/telegram-setup/SKILL.md +4 -4
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +147 -0
- package/src/config/vellum-skills/twilio-setup/SKILL.md +2 -2
- package/src/context/window-manager.ts +43 -3
- package/src/daemon/config-watcher.ts +1 -0
- package/src/daemon/connection-policy.ts +21 -1
- package/src/daemon/daemon-control.ts +164 -7
- package/src/daemon/date-context.ts +174 -1
- package/src/daemon/guardian-action-generators.ts +175 -0
- package/src/daemon/guardian-verification-intent.ts +120 -0
- package/src/daemon/handlers/apps.ts +1 -3
- package/src/daemon/handlers/config-channels.ts +8 -8
- package/src/daemon/handlers/config-heartbeat.ts +1 -1
- package/src/daemon/handlers/config-inbox.ts +55 -159
- package/src/daemon/handlers/config-ingress.ts +1 -1
- package/src/daemon/handlers/config-integrations.ts +1 -1
- package/src/daemon/handlers/config-platform.ts +1 -1
- package/src/daemon/handlers/config-scheduling.ts +2 -2
- package/src/daemon/handlers/config-slack-channel.ts +190 -0
- package/src/daemon/handlers/config-telegram.ts +1 -1
- package/src/daemon/handlers/config-twilio.ts +1 -1
- package/src/daemon/handlers/config-voice.ts +100 -0
- package/src/daemon/handlers/config.ts +3 -0
- package/src/daemon/handlers/index.ts +1 -1
- package/src/daemon/handlers/misc.ts +84 -6
- package/src/daemon/handlers/navigate-settings.ts +27 -0
- package/src/daemon/handlers/recording.ts +270 -144
- package/src/daemon/handlers/sessions.ts +107 -24
- package/src/daemon/handlers/subagents.ts +3 -3
- package/src/daemon/handlers/work-items.ts +10 -7
- package/src/daemon/ipc-contract/integrations.ts +9 -1
- package/src/daemon/ipc-contract/messages.ts +4 -0
- package/src/daemon/ipc-contract/sessions.ts +1 -1
- package/src/daemon/ipc-contract/settings.ts +26 -0
- package/src/daemon/ipc-contract/shared.ts +2 -0
- package/src/daemon/ipc-contract/work-items.ts +1 -7
- package/src/daemon/ipc-contract-inventory.json +5 -1
- package/src/daemon/ipc-contract.ts +5 -1
- package/src/daemon/lifecycle.ts +306 -266
- package/src/daemon/recording-executor.ts +1 -1
- package/src/daemon/recording-intent.ts +0 -41
- package/src/daemon/response-tier.ts +2 -2
- package/src/daemon/server.ts +6 -6
- package/src/daemon/session-agent-loop-handlers.ts +34 -9
- package/src/daemon/session-agent-loop.ts +15 -8
- package/src/daemon/session-history.ts +3 -2
- package/src/daemon/session-media-retry.ts +3 -0
- package/src/daemon/session-messaging.ts +38 -4
- package/src/daemon/session-notifiers.ts +2 -2
- package/src/daemon/session-process.ts +256 -23
- package/src/daemon/session-queue-manager.ts +2 -0
- package/src/daemon/session-runtime-assembly.ts +39 -0
- package/src/daemon/session-skill-tools.ts +13 -4
- package/src/daemon/session-tool-setup.ts +6 -7
- package/src/daemon/session.ts +19 -8
- package/src/daemon/tls-certs.ts +55 -13
- package/src/daemon/tool-side-effects.ts +13 -5
- package/src/gallery/default-gallery.ts +32 -9
- package/src/influencer/client.ts +2 -1
- package/src/memory/channel-delivery-store.ts +37 -567
- package/src/memory/channel-guardian-store.ts +66 -1317
- package/src/memory/conflict-store.ts +4 -4
- package/src/memory/conversation-attention-store.ts +4 -7
- package/src/memory/conversation-crud.ts +668 -0
- package/src/memory/conversation-queries.ts +361 -0
- package/src/memory/conversation-store.ts +45 -983
- package/src/memory/db-connection.ts +3 -0
- package/src/memory/db-init.ts +25 -0
- package/src/memory/delivery-channels.ts +175 -0
- package/src/memory/delivery-crud.ts +211 -0
- package/src/memory/delivery-status.ts +199 -0
- package/src/memory/embedding-backend.ts +70 -4
- package/src/memory/embedding-local.ts +12 -2
- package/src/memory/entity-extractor.ts +3 -8
- package/src/memory/fts-reconciler.ts +121 -0
- package/src/memory/guardian-action-store.ts +366 -3
- package/src/memory/guardian-approvals.ts +569 -0
- package/src/memory/guardian-bindings.ts +130 -0
- package/src/memory/guardian-rate-limits.ts +196 -0
- package/src/memory/guardian-verification.ts +520 -0
- package/src/memory/job-handlers/index-maintenance.ts +2 -1
- package/src/memory/job-utils.ts +8 -5
- package/src/memory/jobs-store.ts +66 -6
- package/src/memory/jobs-worker.ts +23 -1
- package/src/memory/migrations/030-guardian-action-followup.ts +21 -0
- package/src/memory/migrations/030-guardian-verification-purpose.ts +17 -0
- package/src/memory/migrations/031-conversations-thread-type-index.ts +5 -0
- package/src/memory/migrations/100-core-tables.ts +1 -1
- package/src/memory/migrations/101-watchers-and-logs.ts +4 -0
- package/src/memory/migrations/108-tasks-and-work-items.ts +1 -1
- package/src/memory/migrations/112-assistant-inbox.ts +1 -1
- package/src/memory/migrations/113-late-migrations.ts +1 -1
- package/src/memory/migrations/116-messages-fts.ts +13 -0
- package/src/memory/migrations/119-schema-indexes-and-columns.ts +37 -0
- package/src/memory/migrations/120-fk-cascade-rebuilds.ts +161 -0
- package/src/memory/migrations/index.ts +8 -3
- package/src/memory/migrations/validate-migration-state.ts +114 -15
- package/src/memory/qdrant-circuit-breaker.ts +105 -0
- package/src/memory/retriever.ts +46 -13
- package/src/memory/schema-migration.ts +3 -0
- package/src/memory/schema.ts +25 -7
- package/src/memory/search/semantic.ts +8 -90
- package/src/notifications/README.md +1 -1
- package/src/notifications/broadcaster.ts +20 -2
- package/src/notifications/conversation-pairing.ts +3 -3
- package/src/notifications/decision-engine.ts +173 -8
- package/src/notifications/deliveries-store.ts +27 -8
- package/src/notifications/preferences-store.ts +7 -7
- package/src/notifications/thread-candidates.ts +234 -0
- package/src/notifications/types.ts +18 -0
- package/src/permissions/defaults.ts +11 -1
- package/src/permissions/prompter.ts +17 -0
- package/src/permissions/trust-store.ts +2 -0
- package/src/providers/failover.ts +19 -0
- package/src/providers/registry.ts +46 -1
- package/src/runtime/approval-message-composer.ts +1 -1
- package/src/runtime/channel-guardian-service.ts +15 -3
- package/src/runtime/channel-retry-sweep.ts +7 -2
- package/src/runtime/guardian-action-conversation-turn.ts +85 -0
- package/src/runtime/guardian-action-followup-executor.ts +301 -0
- package/src/runtime/guardian-action-message-composer.ts +245 -0
- package/src/runtime/guardian-outbound-actions.ts +35 -15
- package/src/runtime/guardian-verification-templates.ts +15 -9
- package/src/runtime/http-errors.ts +93 -0
- package/src/runtime/http-server.ts +140 -51
- package/src/runtime/http-types.ts +53 -0
- package/src/runtime/ingress-service.ts +237 -0
- package/src/runtime/middleware/error-handler.ts +4 -3
- package/src/runtime/middleware/rate-limiter.ts +160 -0
- package/src/runtime/middleware/request-logger.ts +71 -0
- package/src/runtime/middleware/twilio-validation.ts +7 -6
- package/src/runtime/pending-interactions.ts +12 -0
- package/src/runtime/routes/access-request-decision.ts +215 -0
- package/src/runtime/routes/app-routes.ts +25 -18
- package/src/runtime/routes/approval-routes.ts +18 -47
- package/src/runtime/routes/attachment-routes.ts +15 -41
- package/src/runtime/routes/call-routes.ts +20 -20
- package/src/runtime/routes/channel-delivery-routes.ts +6 -5
- package/src/runtime/routes/contact-routes.ts +4 -9
- package/src/runtime/routes/conversation-attention-routes.ts +5 -4
- package/src/runtime/routes/conversation-routes.ts +26 -57
- package/src/runtime/routes/debug-routes.ts +71 -0
- package/src/runtime/routes/events-routes.ts +3 -2
- package/src/runtime/routes/guardian-approval-interception.ts +221 -0
- package/src/runtime/routes/identity-routes.ts +14 -10
- package/src/runtime/routes/inbound-conversation.ts +3 -2
- package/src/runtime/routes/inbound-message-handler.ts +527 -62
- package/src/runtime/routes/ingress-routes.ts +174 -0
- package/src/runtime/routes/integration-routes.ts +82 -20
- package/src/runtime/routes/pairing-routes.ts +11 -10
- package/src/runtime/routes/secret-routes.ts +10 -18
- package/src/runtime/verification-rate-limiter.ts +83 -0
- package/src/schedule/schedule-store.ts +13 -1
- package/src/schedule/scheduler.ts +2 -2
- package/src/security/secret-ingress.ts +5 -2
- package/src/security/secret-scanner.ts +72 -6
- package/src/subagent/manager.ts +6 -4
- package/src/swarm/plan-validator.ts +4 -1
- package/src/tasks/task-runner.ts +3 -1
- package/src/tools/browser/api-map.ts +9 -6
- package/src/tools/calls/call-start.ts +20 -0
- package/src/tools/executor.ts +50 -568
- package/src/tools/permission-checker.ts +272 -0
- package/src/tools/registry.ts +14 -6
- package/src/tools/reminder/reminder-store.ts +7 -7
- package/src/tools/reminder/reminder.ts +6 -3
- package/src/tools/secret-detection-handler.ts +301 -0
- package/src/tools/subagent/message.ts +1 -1
- package/src/tools/system/voice-config.ts +62 -0
- package/src/tools/tasks/index.ts +3 -3
- package/src/tools/tasks/work-item-list.ts +3 -3
- package/src/tools/tasks/work-item-update.ts +4 -5
- package/src/tools/tool-approval-handler.ts +192 -0
- package/src/tools/tool-manifest.ts +2 -0
- package/src/watcher/watcher-store.ts +9 -9
- package/src/work-items/work-item-runner.ts +9 -6
- /package/src/memory/migrations/{026-embeddings-nullable-vector-json.ts → 026a-embeddings-nullable-vector-json.ts} +0 -0
- /package/src/memory/migrations/{027-guardian-bootstrap-token.ts → 027a-guardian-bootstrap-token.ts} +0 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* answer resolves the request and all other deliveries are marked answered.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { and, eq, inArray, lt } from 'drizzle-orm';
|
|
10
|
+
import { and, desc, eq, inArray, lt } from 'drizzle-orm';
|
|
11
11
|
import { v4 as uuid } from 'uuid';
|
|
12
12
|
|
|
13
13
|
import { getLogger } from '../util/logger.js';
|
|
@@ -25,6 +25,9 @@ const log = getLogger('guardian-action-store');
|
|
|
25
25
|
|
|
26
26
|
export type GuardianActionRequestStatus = 'pending' | 'answered' | 'expired' | 'cancelled';
|
|
27
27
|
export type GuardianActionDeliveryStatus = 'pending' | 'sent' | 'failed' | 'answered' | 'expired' | 'cancelled';
|
|
28
|
+
export type ExpiredReason = 'call_timeout' | 'sweep_timeout' | 'cancelled';
|
|
29
|
+
export type FollowupState = 'none' | 'awaiting_guardian_choice' | 'dispatching' | 'completed' | 'declined' | 'failed';
|
|
30
|
+
export type FollowupAction = 'call_back' | 'message_back' | 'decline';
|
|
28
31
|
|
|
29
32
|
export interface GuardianActionRequest {
|
|
30
33
|
id: string;
|
|
@@ -42,6 +45,12 @@ export interface GuardianActionRequest {
|
|
|
42
45
|
answeredByExternalUserId: string | null;
|
|
43
46
|
answeredAt: number | null;
|
|
44
47
|
expiresAt: number;
|
|
48
|
+
expiredReason: ExpiredReason | null;
|
|
49
|
+
followupState: FollowupState;
|
|
50
|
+
lateAnswerText: string | null;
|
|
51
|
+
lateAnsweredAt: number | null;
|
|
52
|
+
followupAction: FollowupAction | null;
|
|
53
|
+
followupCompletedAt: number | null;
|
|
45
54
|
createdAt: number;
|
|
46
55
|
updatedAt: number;
|
|
47
56
|
}
|
|
@@ -82,6 +91,12 @@ function rowToRequest(row: typeof guardianActionRequests.$inferSelect): Guardian
|
|
|
82
91
|
answeredByExternalUserId: row.answeredByExternalUserId,
|
|
83
92
|
answeredAt: row.answeredAt,
|
|
84
93
|
expiresAt: row.expiresAt,
|
|
94
|
+
expiredReason: (row.expiredReason as ExpiredReason) ?? null,
|
|
95
|
+
followupState: (row.followupState as FollowupState) ?? 'none',
|
|
96
|
+
lateAnswerText: row.lateAnswerText ?? null,
|
|
97
|
+
lateAnsweredAt: row.lateAnsweredAt ?? null,
|
|
98
|
+
followupAction: (row.followupAction as FollowupAction) ?? null,
|
|
99
|
+
followupCompletedAt: row.followupCompletedAt ?? null,
|
|
85
100
|
createdAt: row.createdAt,
|
|
86
101
|
updatedAt: row.updatedAt,
|
|
87
102
|
};
|
|
@@ -143,6 +158,12 @@ export function createGuardianActionRequest(params: {
|
|
|
143
158
|
answeredByExternalUserId: null,
|
|
144
159
|
answeredAt: null,
|
|
145
160
|
expiresAt: params.expiresAt,
|
|
161
|
+
expiredReason: null,
|
|
162
|
+
followupState: 'none' as const,
|
|
163
|
+
lateAnswerText: null,
|
|
164
|
+
lateAnsweredAt: null,
|
|
165
|
+
followupAction: null,
|
|
166
|
+
followupCompletedAt: null,
|
|
146
167
|
createdAt: now,
|
|
147
168
|
updatedAt: now,
|
|
148
169
|
};
|
|
@@ -171,6 +192,26 @@ export function getByPendingQuestionId(questionId: string): GuardianActionReques
|
|
|
171
192
|
return row ? rowToRequest(row) : null;
|
|
172
193
|
}
|
|
173
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Find the most recent pending guardian action request for a given call session.
|
|
197
|
+
* Used by the consultation timeout handler to mark the linked request as timed out.
|
|
198
|
+
*/
|
|
199
|
+
export function getPendingRequestByCallSessionId(callSessionId: string): GuardianActionRequest | null {
|
|
200
|
+
const db = getDb();
|
|
201
|
+
const row = db
|
|
202
|
+
.select()
|
|
203
|
+
.from(guardianActionRequests)
|
|
204
|
+
.where(
|
|
205
|
+
and(
|
|
206
|
+
eq(guardianActionRequests.callSessionId, callSessionId),
|
|
207
|
+
eq(guardianActionRequests.status, 'pending'),
|
|
208
|
+
),
|
|
209
|
+
)
|
|
210
|
+
.orderBy(desc(guardianActionRequests.createdAt))
|
|
211
|
+
.get();
|
|
212
|
+
return row ? rowToRequest(row) : null;
|
|
213
|
+
}
|
|
214
|
+
|
|
174
215
|
/**
|
|
175
216
|
* First-response-wins resolution. Checks that the request is still
|
|
176
217
|
* 'pending' before updating; returns the updated request on success
|
|
@@ -217,13 +258,14 @@ export function resolveGuardianActionRequest(
|
|
|
217
258
|
|
|
218
259
|
/**
|
|
219
260
|
* Expire a guardian action request and all its deliveries.
|
|
261
|
+
* When reason is not provided, defaults to 'sweep_timeout' for backward compatibility.
|
|
220
262
|
*/
|
|
221
|
-
export function expireGuardianActionRequest(id: string): void {
|
|
263
|
+
export function expireGuardianActionRequest(id: string, reason?: ExpiredReason): void {
|
|
222
264
|
const db = getDb();
|
|
223
265
|
const now = Date.now();
|
|
224
266
|
|
|
225
267
|
db.update(guardianActionRequests)
|
|
226
|
-
.set({ status: 'expired', updatedAt: now })
|
|
268
|
+
.set({ status: 'expired', expiredReason: reason ?? 'sweep_timeout', updatedAt: now })
|
|
227
269
|
.where(
|
|
228
270
|
and(
|
|
229
271
|
eq(guardianActionRequests.id, id),
|
|
@@ -303,6 +345,175 @@ export function cancelGuardianActionRequest(id: string): void {
|
|
|
303
345
|
.run();
|
|
304
346
|
}
|
|
305
347
|
|
|
348
|
+
// ---------------------------------------------------------------------------
|
|
349
|
+
// Follow-up lifecycle helpers
|
|
350
|
+
// ---------------------------------------------------------------------------
|
|
351
|
+
|
|
352
|
+
/** Valid non-terminal followup_state transitions for progressFollowupState.
|
|
353
|
+
* Terminal states (completed, declined, failed) are only reachable via
|
|
354
|
+
* finalizeFollowup, which properly sets followupCompletedAt. */
|
|
355
|
+
const FOLLOWUP_TRANSITIONS: Record<FollowupState, FollowupState[]> = {
|
|
356
|
+
none: [],
|
|
357
|
+
awaiting_guardian_choice: ['dispatching'],
|
|
358
|
+
dispatching: [],
|
|
359
|
+
completed: [],
|
|
360
|
+
declined: [],
|
|
361
|
+
failed: [],
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
/** Valid terminal transitions for finalizeFollowup. Maps from current
|
|
365
|
+
* followup_state to the terminal states reachable from it. */
|
|
366
|
+
const FOLLOWUP_FINALIZE_TRANSITIONS: Partial<Record<FollowupState, FollowupState[]>> = {
|
|
367
|
+
awaiting_guardian_choice: ['declined'],
|
|
368
|
+
dispatching: ['completed', 'failed'],
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Atomically set status='expired' and expired_reason on a pending request.
|
|
373
|
+
* Returns the updated request on success, or null if the request was not
|
|
374
|
+
* in 'pending' status (first-writer-wins).
|
|
375
|
+
*/
|
|
376
|
+
export function markTimedOutWithReason(id: string, reason: ExpiredReason): GuardianActionRequest | null {
|
|
377
|
+
const db = getDb();
|
|
378
|
+
const now = Date.now();
|
|
379
|
+
|
|
380
|
+
db.update(guardianActionRequests)
|
|
381
|
+
.set({ status: 'expired', expiredReason: reason, updatedAt: now })
|
|
382
|
+
.where(
|
|
383
|
+
and(
|
|
384
|
+
eq(guardianActionRequests.id, id),
|
|
385
|
+
eq(guardianActionRequests.status, 'pending'),
|
|
386
|
+
),
|
|
387
|
+
)
|
|
388
|
+
.run();
|
|
389
|
+
|
|
390
|
+
if (rawChanges() === 0) return null;
|
|
391
|
+
|
|
392
|
+
// Also expire active deliveries
|
|
393
|
+
db.update(guardianActionDeliveries)
|
|
394
|
+
.set({ status: 'expired', updatedAt: now })
|
|
395
|
+
.where(
|
|
396
|
+
and(
|
|
397
|
+
eq(guardianActionDeliveries.requestId, id),
|
|
398
|
+
inArray(guardianActionDeliveries.status, ['pending', 'sent']),
|
|
399
|
+
),
|
|
400
|
+
)
|
|
401
|
+
.run();
|
|
402
|
+
|
|
403
|
+
return getGuardianActionRequest(id);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Atomically transition an expired request into the follow-up flow.
|
|
408
|
+
* Sets followup_state='awaiting_guardian_choice', records the late answer
|
|
409
|
+
* text and timestamp. Only succeeds if status='expired' and followup_state='none'.
|
|
410
|
+
* Returns the updated request on success, or null on conflict.
|
|
411
|
+
*/
|
|
412
|
+
export function startFollowupFromExpiredRequest(
|
|
413
|
+
id: string,
|
|
414
|
+
lateAnswerText: string,
|
|
415
|
+
): GuardianActionRequest | null {
|
|
416
|
+
const db = getDb();
|
|
417
|
+
const now = Date.now();
|
|
418
|
+
|
|
419
|
+
db.update(guardianActionRequests)
|
|
420
|
+
.set({
|
|
421
|
+
followupState: 'awaiting_guardian_choice',
|
|
422
|
+
lateAnswerText,
|
|
423
|
+
lateAnsweredAt: now,
|
|
424
|
+
updatedAt: now,
|
|
425
|
+
})
|
|
426
|
+
.where(
|
|
427
|
+
and(
|
|
428
|
+
eq(guardianActionRequests.id, id),
|
|
429
|
+
eq(guardianActionRequests.status, 'expired'),
|
|
430
|
+
eq(guardianActionRequests.followupState, 'none'),
|
|
431
|
+
),
|
|
432
|
+
)
|
|
433
|
+
.run();
|
|
434
|
+
|
|
435
|
+
if (rawChanges() === 0) return null;
|
|
436
|
+
return getGuardianActionRequest(id);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Atomically progress the followup_state. Validates that the transition
|
|
441
|
+
* is allowed (see FOLLOWUP_TRANSITIONS). Optionally sets the followup_action.
|
|
442
|
+
* Returns the updated request on success, or null if the transition was
|
|
443
|
+
* invalid or the prior state didn't match.
|
|
444
|
+
*/
|
|
445
|
+
export function progressFollowupState(
|
|
446
|
+
id: string,
|
|
447
|
+
newState: FollowupState,
|
|
448
|
+
action?: FollowupAction,
|
|
449
|
+
): GuardianActionRequest | null {
|
|
450
|
+
const request = getGuardianActionRequest(id);
|
|
451
|
+
if (!request) return null;
|
|
452
|
+
|
|
453
|
+
const allowed = FOLLOWUP_TRANSITIONS[request.followupState];
|
|
454
|
+
if (!allowed.includes(newState)) return null;
|
|
455
|
+
|
|
456
|
+
const db = getDb();
|
|
457
|
+
const now = Date.now();
|
|
458
|
+
|
|
459
|
+
const updates: Record<string, unknown> = {
|
|
460
|
+
followupState: newState,
|
|
461
|
+
updatedAt: now,
|
|
462
|
+
};
|
|
463
|
+
if (action !== undefined) updates.followupAction = action;
|
|
464
|
+
|
|
465
|
+
db.update(guardianActionRequests)
|
|
466
|
+
.set(updates)
|
|
467
|
+
.where(
|
|
468
|
+
and(
|
|
469
|
+
eq(guardianActionRequests.id, id),
|
|
470
|
+
eq(guardianActionRequests.status, 'expired'),
|
|
471
|
+
eq(guardianActionRequests.followupState, request.followupState),
|
|
472
|
+
),
|
|
473
|
+
)
|
|
474
|
+
.run();
|
|
475
|
+
|
|
476
|
+
if (rawChanges() === 0) return null;
|
|
477
|
+
return getGuardianActionRequest(id);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Finalize a follow-up by setting the terminal followup_state and
|
|
482
|
+
* recording followup_completed_at. Only succeeds from a non-terminal state
|
|
483
|
+
* and only on expired requests.
|
|
484
|
+
*/
|
|
485
|
+
export function finalizeFollowup(
|
|
486
|
+
id: string,
|
|
487
|
+
finalState: 'completed' | 'declined' | 'failed',
|
|
488
|
+
): GuardianActionRequest | null {
|
|
489
|
+
const request = getGuardianActionRequest(id);
|
|
490
|
+
if (!request) return null;
|
|
491
|
+
|
|
492
|
+
const allowed = FOLLOWUP_FINALIZE_TRANSITIONS[request.followupState];
|
|
493
|
+
if (!allowed?.includes(finalState)) return null;
|
|
494
|
+
|
|
495
|
+
const db = getDb();
|
|
496
|
+
const now = Date.now();
|
|
497
|
+
|
|
498
|
+
db.update(guardianActionRequests)
|
|
499
|
+
.set({
|
|
500
|
+
followupState: finalState,
|
|
501
|
+
followupCompletedAt: now,
|
|
502
|
+
updatedAt: now,
|
|
503
|
+
})
|
|
504
|
+
.where(
|
|
505
|
+
and(
|
|
506
|
+
eq(guardianActionRequests.id, id),
|
|
507
|
+
eq(guardianActionRequests.status, 'expired'),
|
|
508
|
+
eq(guardianActionRequests.followupState, request.followupState),
|
|
509
|
+
),
|
|
510
|
+
)
|
|
511
|
+
.run();
|
|
512
|
+
|
|
513
|
+
if (rawChanges() === 0) return null;
|
|
514
|
+
return getGuardianActionRequest(id);
|
|
515
|
+
}
|
|
516
|
+
|
|
306
517
|
// ---------------------------------------------------------------------------
|
|
307
518
|
// Guardian Action Deliveries
|
|
308
519
|
// ---------------------------------------------------------------------------
|
|
@@ -411,6 +622,158 @@ export function getPendingDeliveryByConversation(conversationId: string): Guardi
|
|
|
411
622
|
}
|
|
412
623
|
}
|
|
413
624
|
|
|
625
|
+
/**
|
|
626
|
+
* Look up sent deliveries for expired requests eligible for follow-up.
|
|
627
|
+
* Used by inbound message routing to match late guardian answers to expired requests.
|
|
628
|
+
*/
|
|
629
|
+
export function getExpiredDeliveriesByDestination(
|
|
630
|
+
assistantId: string,
|
|
631
|
+
channel: string,
|
|
632
|
+
chatId: string,
|
|
633
|
+
): GuardianActionDelivery[] {
|
|
634
|
+
try {
|
|
635
|
+
const db = getDb();
|
|
636
|
+
|
|
637
|
+
const rows = db
|
|
638
|
+
.select({
|
|
639
|
+
delivery: guardianActionDeliveries,
|
|
640
|
+
})
|
|
641
|
+
.from(guardianActionDeliveries)
|
|
642
|
+
.innerJoin(
|
|
643
|
+
guardianActionRequests,
|
|
644
|
+
eq(guardianActionDeliveries.requestId, guardianActionRequests.id),
|
|
645
|
+
)
|
|
646
|
+
.where(
|
|
647
|
+
and(
|
|
648
|
+
eq(guardianActionRequests.assistantId, assistantId),
|
|
649
|
+
eq(guardianActionRequests.status, 'expired'),
|
|
650
|
+
eq(guardianActionRequests.followupState, 'none'),
|
|
651
|
+
eq(guardianActionDeliveries.destinationChannel, channel),
|
|
652
|
+
eq(guardianActionDeliveries.destinationChatId, chatId),
|
|
653
|
+
eq(guardianActionDeliveries.status, 'expired'),
|
|
654
|
+
),
|
|
655
|
+
)
|
|
656
|
+
.all();
|
|
657
|
+
|
|
658
|
+
return rows.map((r) => rowToDelivery(r.delivery));
|
|
659
|
+
} catch (err) {
|
|
660
|
+
if (err instanceof Error && err.message.includes('no such table')) {
|
|
661
|
+
log.warn({ err }, 'guardian tables not yet created');
|
|
662
|
+
return [];
|
|
663
|
+
}
|
|
664
|
+
throw err;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Look up an expired delivery by destination conversation ID (for mac channel routing).
|
|
670
|
+
*/
|
|
671
|
+
export function getExpiredDeliveryByConversation(conversationId: string): GuardianActionDelivery | null {
|
|
672
|
+
try {
|
|
673
|
+
const db = getDb();
|
|
674
|
+
const rows = db
|
|
675
|
+
.select({ delivery: guardianActionDeliveries })
|
|
676
|
+
.from(guardianActionDeliveries)
|
|
677
|
+
.innerJoin(
|
|
678
|
+
guardianActionRequests,
|
|
679
|
+
eq(guardianActionDeliveries.requestId, guardianActionRequests.id),
|
|
680
|
+
)
|
|
681
|
+
.where(
|
|
682
|
+
and(
|
|
683
|
+
eq(guardianActionDeliveries.destinationConversationId, conversationId),
|
|
684
|
+
eq(guardianActionDeliveries.status, 'expired'),
|
|
685
|
+
eq(guardianActionRequests.status, 'expired'),
|
|
686
|
+
eq(guardianActionRequests.followupState, 'none'),
|
|
687
|
+
),
|
|
688
|
+
)
|
|
689
|
+
.all();
|
|
690
|
+
return rows.length > 0 ? rowToDelivery(rows[0].delivery) : null;
|
|
691
|
+
} catch (err) {
|
|
692
|
+
if (err instanceof Error && err.message.includes('no such table')) {
|
|
693
|
+
log.warn({ err }, 'guardian tables not yet created');
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
throw err;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Look up deliveries for requests in `awaiting_guardian_choice` follow-up state.
|
|
702
|
+
* Used by inbound message routing to intercept guardian follow-up replies
|
|
703
|
+
* on channel paths (Telegram, SMS).
|
|
704
|
+
*/
|
|
705
|
+
export function getFollowupDeliveriesByDestination(
|
|
706
|
+
assistantId: string,
|
|
707
|
+
channel: string,
|
|
708
|
+
chatId: string,
|
|
709
|
+
): GuardianActionDelivery[] {
|
|
710
|
+
try {
|
|
711
|
+
const db = getDb();
|
|
712
|
+
|
|
713
|
+
const rows = db
|
|
714
|
+
.select({
|
|
715
|
+
delivery: guardianActionDeliveries,
|
|
716
|
+
})
|
|
717
|
+
.from(guardianActionDeliveries)
|
|
718
|
+
.innerJoin(
|
|
719
|
+
guardianActionRequests,
|
|
720
|
+
eq(guardianActionDeliveries.requestId, guardianActionRequests.id),
|
|
721
|
+
)
|
|
722
|
+
.where(
|
|
723
|
+
and(
|
|
724
|
+
eq(guardianActionRequests.assistantId, assistantId),
|
|
725
|
+
eq(guardianActionRequests.status, 'expired'),
|
|
726
|
+
eq(guardianActionRequests.followupState, 'awaiting_guardian_choice'),
|
|
727
|
+
eq(guardianActionDeliveries.destinationChannel, channel),
|
|
728
|
+
eq(guardianActionDeliveries.destinationChatId, chatId),
|
|
729
|
+
eq(guardianActionDeliveries.status, 'expired'),
|
|
730
|
+
),
|
|
731
|
+
)
|
|
732
|
+
.all();
|
|
733
|
+
|
|
734
|
+
return rows.map((r) => rowToDelivery(r.delivery));
|
|
735
|
+
} catch (err) {
|
|
736
|
+
if (err instanceof Error && err.message.includes('no such table')) {
|
|
737
|
+
log.warn({ err }, 'guardian tables not yet created');
|
|
738
|
+
return [];
|
|
739
|
+
}
|
|
740
|
+
throw err;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Look up a delivery for a request in `awaiting_guardian_choice` follow-up
|
|
746
|
+
* state by destination conversation ID (for mac channel routing).
|
|
747
|
+
*/
|
|
748
|
+
export function getFollowupDeliveryByConversation(conversationId: string): GuardianActionDelivery | null {
|
|
749
|
+
try {
|
|
750
|
+
const db = getDb();
|
|
751
|
+
const rows = db
|
|
752
|
+
.select({ delivery: guardianActionDeliveries })
|
|
753
|
+
.from(guardianActionDeliveries)
|
|
754
|
+
.innerJoin(
|
|
755
|
+
guardianActionRequests,
|
|
756
|
+
eq(guardianActionDeliveries.requestId, guardianActionRequests.id),
|
|
757
|
+
)
|
|
758
|
+
.where(
|
|
759
|
+
and(
|
|
760
|
+
eq(guardianActionDeliveries.destinationConversationId, conversationId),
|
|
761
|
+
eq(guardianActionDeliveries.status, 'expired'),
|
|
762
|
+
eq(guardianActionRequests.status, 'expired'),
|
|
763
|
+
eq(guardianActionRequests.followupState, 'awaiting_guardian_choice'),
|
|
764
|
+
),
|
|
765
|
+
)
|
|
766
|
+
.all();
|
|
767
|
+
return rows.length > 0 ? rowToDelivery(rows[0].delivery) : null;
|
|
768
|
+
} catch (err) {
|
|
769
|
+
if (err instanceof Error && err.message.includes('no such table')) {
|
|
770
|
+
log.warn({ err }, 'guardian tables not yet created');
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
throw err;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
414
777
|
export function updateDeliveryStatus(
|
|
415
778
|
deliveryId: string,
|
|
416
779
|
status: GuardianActionDeliveryStatus,
|