@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
|
@@ -11,6 +11,12 @@ export interface TemporalContextOptions {
|
|
|
11
11
|
nowMs?: number;
|
|
12
12
|
/** IANA timezone (e.g. "America/New_York"). Defaults to host timezone. */
|
|
13
13
|
timeZone?: string;
|
|
14
|
+
/** IANA timezone for the assistant host clock (defaults to process local timezone). */
|
|
15
|
+
hostTimeZone?: string;
|
|
16
|
+
/** IANA timezone configured in user settings (if available). */
|
|
17
|
+
configuredUserTimeZone?: string | null;
|
|
18
|
+
/** IANA timezone inferred from user profile/memory (if available). */
|
|
19
|
+
userTimeZone?: string | null;
|
|
14
20
|
/** Number of future days to list (default 14, hard-capped at 14). */
|
|
15
21
|
horizonDays?: number;
|
|
16
22
|
}
|
|
@@ -19,6 +25,117 @@ const MAX_OUTPUT_CHARS = 1500;
|
|
|
19
25
|
const MAX_HORIZON_ENTRIES = 14;
|
|
20
26
|
|
|
21
27
|
const WEEKDAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] as const;
|
|
28
|
+
const TIMEZONE_SUBJECT_LINE_RE = /^\s*-\s*time\s*zone\s*:\s*(.+)$/i;
|
|
29
|
+
const TIMEZONE_SUBJECT_COMPACT_RE = /^\s*-\s*timezone\s*:\s*(.+)$/i;
|
|
30
|
+
const TIMEZONE_TOKEN_RE = /\b(?:[A-Za-z][A-Za-z0-9_+-]*(?:\/[A-Za-z0-9_+-]+)+|(?:UTC|GMT)(?:[+-]\d{1,2}(?::?\d{2})?)?)\b/gi;
|
|
31
|
+
const UTC_GMT_OFFSET_TOKEN_RE = /^(?:UTC|GMT)([+-])(\d{1,2})(?::?(\d{2}))?$/i;
|
|
32
|
+
|
|
33
|
+
function normalizeOffsetToken(offsetToken: string): string {
|
|
34
|
+
if (offsetToken === 'GMT' || offsetToken === 'UTC') {
|
|
35
|
+
return '+00:00';
|
|
36
|
+
}
|
|
37
|
+
const match = /^(?:GMT|UTC)([+-])(\d{1,2})(?::?(\d{2}))?$/i.exec(offsetToken);
|
|
38
|
+
if (!match) {
|
|
39
|
+
return '+00:00';
|
|
40
|
+
}
|
|
41
|
+
const [, sign, hours, minutes] = match;
|
|
42
|
+
return `${sign}${hours.padStart(2, '0')}:${(minutes ?? '00').padStart(2, '0')}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function canonicalizeUtcGmtOffsetToken(offsetToken: string): string | null {
|
|
46
|
+
if (/^(?:UTC|GMT)$/i.test(offsetToken)) {
|
|
47
|
+
return 'UTC';
|
|
48
|
+
}
|
|
49
|
+
const match = offsetToken.match(UTC_GMT_OFFSET_TOKEN_RE);
|
|
50
|
+
if (!match) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const [, sign, hoursRaw, minutesRaw] = match;
|
|
54
|
+
const hours = Number.parseInt(hoursRaw, 10);
|
|
55
|
+
const minutes = Number.parseInt(minutesRaw ?? '0', 10);
|
|
56
|
+
if (!Number.isInteger(hours) || !Number.isInteger(minutes)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
if (hours > 14 || minutes > 59) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const totalMinutes = (hours * 60 + minutes) * (sign === '+' ? 1 : -1);
|
|
63
|
+
if (Math.abs(totalMinutes) > 14 * 60) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (totalMinutes === 0) {
|
|
67
|
+
return 'UTC';
|
|
68
|
+
}
|
|
69
|
+
const absTotalMinutes = Math.abs(totalMinutes);
|
|
70
|
+
const absHours = Math.floor(absTotalMinutes / 60);
|
|
71
|
+
const absMinutes = absTotalMinutes % 60;
|
|
72
|
+
const offsetSign = totalMinutes > 0 ? '+' : '-';
|
|
73
|
+
|
|
74
|
+
// For whole-hour offsets, prefer `Etc/GMT` for stable canonicalization.
|
|
75
|
+
if (absMinutes === 0) {
|
|
76
|
+
// `Etc/GMT` uses POSIX sign semantics: east-of-UTC offsets use a minus sign.
|
|
77
|
+
const etcSign = totalMinutes > 0 ? '-' : '+';
|
|
78
|
+
return `Etc/GMT${etcSign}${absHours}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Bun/Intl accepts fixed-offset IDs in ±HH:MM format.
|
|
82
|
+
return `${offsetSign}${String(absHours).padStart(2, '0')}:${String(absMinutes).padStart(2, '0')}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function canonicalizeTimeZone(timeZone: string): string | null {
|
|
86
|
+
const trimmed = timeZone.trim();
|
|
87
|
+
if (trimmed.length === 0) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const canonicalOffset = canonicalizeUtcGmtOffsetToken(trimmed);
|
|
91
|
+
if (canonicalOffset) {
|
|
92
|
+
try {
|
|
93
|
+
return new Intl.DateTimeFormat('en-US', { timeZone: canonicalOffset }).resolvedOptions().timeZone;
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
return new Intl.DateTimeFormat('en-US', { timeZone: trimmed }).resolvedOptions().timeZone;
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function extractTimeZoneCandidates(text: string): string[] {
|
|
106
|
+
const matches = (text.match(TIMEZONE_TOKEN_RE) ?? []).map((token) => token.trim()).filter((token) => token.length > 0);
|
|
107
|
+
const ianaTokens = matches.filter((token) => token.includes('/'));
|
|
108
|
+
const offsetTokens = matches.filter((token) => !token.includes('/'));
|
|
109
|
+
return [...ianaTokens, ...offsetTokens];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Extract a valid user timezone from compiled `<dynamic-user-profile>` text.
|
|
114
|
+
*
|
|
115
|
+
* Prefers explicit `timezone:` profile lines, then falls back to scanning the
|
|
116
|
+
* full profile body for valid IANA timezone identifiers.
|
|
117
|
+
*/
|
|
118
|
+
export function extractUserTimeZoneFromDynamicProfile(profileText: string): string | null {
|
|
119
|
+
const trimmed = profileText.trim();
|
|
120
|
+
if (trimmed.length === 0) return null;
|
|
121
|
+
|
|
122
|
+
const candidateTexts: string[] = [];
|
|
123
|
+
for (const line of trimmed.split('\n')) {
|
|
124
|
+
const match = line.match(TIMEZONE_SUBJECT_LINE_RE) ?? line.match(TIMEZONE_SUBJECT_COMPACT_RE);
|
|
125
|
+
if (match) {
|
|
126
|
+
candidateTexts.push(match[1]);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
candidateTexts.push(trimmed);
|
|
130
|
+
|
|
131
|
+
for (const text of candidateTexts) {
|
|
132
|
+
for (const token of extractTimeZoneCandidates(text)) {
|
|
133
|
+
const canonical = canonicalizeTimeZone(token);
|
|
134
|
+
if (canonical) return canonical;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
22
139
|
|
|
23
140
|
/**
|
|
24
141
|
* Get the local date parts for a given instant in the specified timezone.
|
|
@@ -52,6 +169,33 @@ function formatLocalDate(date: Date, timeZone: string): string {
|
|
|
52
169
|
return `${p.year}-${String(p.month).padStart(2, '0')}-${String(p.day).padStart(2, '0')}`;
|
|
53
170
|
}
|
|
54
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Format a Date as local ISO 8601 with timezone offset in the given timezone.
|
|
174
|
+
*/
|
|
175
|
+
function formatLocalIsoWithOffset(date: Date, timeZone: string): string {
|
|
176
|
+
const fmt = new Intl.DateTimeFormat('en-US', {
|
|
177
|
+
timeZone,
|
|
178
|
+
year: 'numeric',
|
|
179
|
+
month: '2-digit',
|
|
180
|
+
day: '2-digit',
|
|
181
|
+
hour: '2-digit',
|
|
182
|
+
minute: '2-digit',
|
|
183
|
+
second: '2-digit',
|
|
184
|
+
hourCycle: 'h23',
|
|
185
|
+
timeZoneName: 'shortOffset',
|
|
186
|
+
});
|
|
187
|
+
const parts = fmt.formatToParts(date);
|
|
188
|
+
const get = (t: string) => parts.find((p) => p.type === t)?.value ?? '';
|
|
189
|
+
const offset = normalizeOffsetToken(get('timeZoneName'));
|
|
190
|
+
const year = get('year');
|
|
191
|
+
const month = get('month');
|
|
192
|
+
const day = get('day');
|
|
193
|
+
const hour = get('hour');
|
|
194
|
+
const minute = get('minute');
|
|
195
|
+
const second = get('second');
|
|
196
|
+
return `${year}-${month}-${day}T${hour}:${minute}:${second}${offset}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
55
199
|
/**
|
|
56
200
|
* Advance a date by `days` calendar days in the given timezone.
|
|
57
201
|
*
|
|
@@ -84,7 +228,30 @@ function addDays(date: Date, days: number, timeZone: string): Date {
|
|
|
84
228
|
*/
|
|
85
229
|
export function buildTemporalContext(options: TemporalContextOptions = {}): string {
|
|
86
230
|
const now = new Date(options.nowMs ?? Date.now());
|
|
87
|
-
const
|
|
231
|
+
const resolvedHostTimeZone = canonicalizeTimeZone(
|
|
232
|
+
options.hostTimeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
233
|
+
) ?? 'UTC';
|
|
234
|
+
const resolvedConfiguredUserTimeZone = options.configuredUserTimeZone
|
|
235
|
+
? canonicalizeTimeZone(options.configuredUserTimeZone)
|
|
236
|
+
: null;
|
|
237
|
+
const resolvedUserTimeZone = options.userTimeZone
|
|
238
|
+
? canonicalizeTimeZone(options.userTimeZone)
|
|
239
|
+
: null;
|
|
240
|
+
const resolvedTimeZone = options.timeZone
|
|
241
|
+
? canonicalizeTimeZone(options.timeZone)
|
|
242
|
+
: null;
|
|
243
|
+
const timeZone = resolvedTimeZone
|
|
244
|
+
?? resolvedConfiguredUserTimeZone
|
|
245
|
+
?? resolvedUserTimeZone
|
|
246
|
+
?? resolvedHostTimeZone;
|
|
247
|
+
const userTimeZone = resolvedConfiguredUserTimeZone ?? resolvedUserTimeZone;
|
|
248
|
+
const timeZoneSource = resolvedTimeZone
|
|
249
|
+
? 'explicit_override'
|
|
250
|
+
: resolvedConfiguredUserTimeZone
|
|
251
|
+
? 'user_settings'
|
|
252
|
+
: resolvedUserTimeZone
|
|
253
|
+
? 'user_profile_memory'
|
|
254
|
+
: 'assistant_host_fallback';
|
|
88
255
|
const horizonDays = Math.min(options.horizonDays ?? MAX_HORIZON_ENTRIES, MAX_HORIZON_ENTRIES);
|
|
89
256
|
|
|
90
257
|
const todayParts = localDateParts(now, timeZone);
|
|
@@ -114,6 +281,12 @@ export function buildTemporalContext(options: TemporalContextOptions = {}): stri
|
|
|
114
281
|
`<temporal_context>`,
|
|
115
282
|
`Today: ${todayStr} (${todayWeekday})`,
|
|
116
283
|
`Timezone: ${timeZone}`,
|
|
284
|
+
`Current local time: ${formatLocalIsoWithOffset(now, timeZone)}`,
|
|
285
|
+
`Current UTC time: ${now.toISOString()}`,
|
|
286
|
+
`Clock source: assistant host machine`,
|
|
287
|
+
`Assistant host timezone: ${resolvedHostTimeZone}`,
|
|
288
|
+
`User timezone: ${userTimeZone ?? 'unknown'}`,
|
|
289
|
+
`Timezone source: ${timeZoneSource}`,
|
|
117
290
|
``,
|
|
118
291
|
`Week definitions: work week = Monday–Friday, weekend = Saturday–Sunday`,
|
|
119
292
|
``,
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { loadConfig } from '../config/loader.js';
|
|
2
|
+
import { getFailoverProvider } from '../providers/registry.js';
|
|
3
|
+
import {
|
|
4
|
+
buildGuardianActionGenerationPrompt,
|
|
5
|
+
getGuardianActionFallbackMessage,
|
|
6
|
+
GUARDIAN_ACTION_COPY_MAX_TOKENS,
|
|
7
|
+
GUARDIAN_ACTION_COPY_SYSTEM_PROMPT,
|
|
8
|
+
GUARDIAN_ACTION_COPY_TIMEOUT_MS,
|
|
9
|
+
includesRequiredKeywords,
|
|
10
|
+
} from '../runtime/guardian-action-message-composer.js';
|
|
11
|
+
import type {
|
|
12
|
+
GuardianActionCopyGenerator,
|
|
13
|
+
GuardianFollowUpConversationGenerator,
|
|
14
|
+
GuardianFollowUpDisposition,
|
|
15
|
+
GuardianFollowUpTurnResult,
|
|
16
|
+
} from '../runtime/http-types.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create the daemon-owned guardian action copy generator that resolves
|
|
20
|
+
* providers and calls `provider.sendMessage` to generate guardian action
|
|
21
|
+
* copy text. Uses `latency-optimized` model intent since these are
|
|
22
|
+
* time-sensitive voice responses.
|
|
23
|
+
*
|
|
24
|
+
* This keeps all provider awareness in the daemon lifecycle, away from
|
|
25
|
+
* the runtime composer.
|
|
26
|
+
*/
|
|
27
|
+
export function createGuardianActionCopyGenerator(): GuardianActionCopyGenerator {
|
|
28
|
+
return async (context, options = {}) => {
|
|
29
|
+
const config = loadConfig();
|
|
30
|
+
let provider;
|
|
31
|
+
try {
|
|
32
|
+
provider = getFailoverProvider(config.provider, config.providerOrder);
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const fallbackText = options.fallbackText?.trim() || getGuardianActionFallbackMessage(context);
|
|
38
|
+
const requiredKeywords = options.requiredKeywords?.map((kw) => kw.trim()).filter((kw) => kw.length > 0);
|
|
39
|
+
const prompt = buildGuardianActionGenerationPrompt(context, fallbackText, requiredKeywords);
|
|
40
|
+
|
|
41
|
+
const response = await provider.sendMessage(
|
|
42
|
+
[{ role: 'user', content: [{ type: 'text', text: prompt }] }],
|
|
43
|
+
[],
|
|
44
|
+
GUARDIAN_ACTION_COPY_SYSTEM_PROMPT,
|
|
45
|
+
{
|
|
46
|
+
config: {
|
|
47
|
+
max_tokens: options.maxTokens ?? GUARDIAN_ACTION_COPY_MAX_TOKENS,
|
|
48
|
+
modelIntent: 'latency-optimized',
|
|
49
|
+
},
|
|
50
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? GUARDIAN_ACTION_COPY_TIMEOUT_MS),
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const block = response.content.find((entry) => entry.type === 'text');
|
|
55
|
+
const text = block && 'text' in block ? block.text.trim() : '';
|
|
56
|
+
if (!text) return null;
|
|
57
|
+
const cleaned = text
|
|
58
|
+
.replace(/^["'`]+/, '')
|
|
59
|
+
.replace(/["'`]+$/, '')
|
|
60
|
+
.trim();
|
|
61
|
+
if (!cleaned) return null;
|
|
62
|
+
if (!includesRequiredKeywords(cleaned, requiredKeywords)) return null;
|
|
63
|
+
return cleaned;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Guardian follow-up conversation generator
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
const FOLLOWUP_CONVERSATION_TIMEOUT_MS = 8_000;
|
|
72
|
+
const FOLLOWUP_CONVERSATION_MAX_TOKENS = 300;
|
|
73
|
+
|
|
74
|
+
const FOLLOWUP_CONVERSATION_SYSTEM_PROMPT =
|
|
75
|
+
'You are an assistant helping route a guardian\'s reply to a post-timeout follow-up message. '
|
|
76
|
+
+ 'A voice caller asked a question, but the call timed out before the guardian could answer. '
|
|
77
|
+
+ 'The guardian has now replied late, and was asked whether they want to call the caller back, '
|
|
78
|
+
+ 'send them a text message, or skip it. '
|
|
79
|
+
+ 'Analyze the guardian\'s latest reply to determine their intent. '
|
|
80
|
+
+ 'When uncertain, default to keep_pending and ask a clarifying question. '
|
|
81
|
+
+ 'Always provide a natural, helpful reply along with your decision.';
|
|
82
|
+
|
|
83
|
+
const FOLLOWUP_CONVERSATION_TOOL_NAME = 'followup_decision';
|
|
84
|
+
|
|
85
|
+
const FOLLOWUP_CONVERSATION_TOOL_SCHEMA = {
|
|
86
|
+
name: FOLLOWUP_CONVERSATION_TOOL_NAME,
|
|
87
|
+
description:
|
|
88
|
+
'Record the guardian\'s follow-up decision and a natural reply. '
|
|
89
|
+
+ 'Call this tool with the determined disposition and a reply to the guardian.',
|
|
90
|
+
input_schema: {
|
|
91
|
+
type: 'object' as const,
|
|
92
|
+
properties: {
|
|
93
|
+
disposition: {
|
|
94
|
+
type: 'string',
|
|
95
|
+
enum: ['call_back', 'message_back', 'decline', 'keep_pending'],
|
|
96
|
+
description:
|
|
97
|
+
'The guardian\'s intent: call_back to call the original caller, '
|
|
98
|
+
+ 'message_back to send a text message, decline to skip the follow-up, '
|
|
99
|
+
+ 'keep_pending if the intent is unclear (ask for clarification).',
|
|
100
|
+
},
|
|
101
|
+
replyText: {
|
|
102
|
+
type: 'string',
|
|
103
|
+
description: 'A natural language reply to send back to the guardian.',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
required: ['disposition', 'replyText'],
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const VALID_FOLLOWUP_DISPOSITIONS: ReadonlySet<string> = new Set([
|
|
111
|
+
'call_back',
|
|
112
|
+
'message_back',
|
|
113
|
+
'decline',
|
|
114
|
+
'keep_pending',
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Create the daemon-owned guardian follow-up conversation generator.
|
|
119
|
+
* Uses tool/function calling to produce structured dispositions alongside
|
|
120
|
+
* natural reply text. Follows the same pattern as
|
|
121
|
+
* createApprovalConversationGenerator().
|
|
122
|
+
*/
|
|
123
|
+
export function createGuardianFollowUpConversationGenerator(): GuardianFollowUpConversationGenerator {
|
|
124
|
+
return async (context) => {
|
|
125
|
+
const config = loadConfig();
|
|
126
|
+
const provider = getFailoverProvider(config.provider, config.providerOrder);
|
|
127
|
+
|
|
128
|
+
const userPrompt = [
|
|
129
|
+
`Original question from the voice call: "${context.questionText}"`,
|
|
130
|
+
`Guardian's late answer: "${context.lateAnswerText}"`,
|
|
131
|
+
`\nGuardian's latest reply: ${context.guardianReply}`,
|
|
132
|
+
].join('\n');
|
|
133
|
+
|
|
134
|
+
const response = await provider.sendMessage(
|
|
135
|
+
[{ role: 'user', content: [{ type: 'text', text: userPrompt }] }],
|
|
136
|
+
[FOLLOWUP_CONVERSATION_TOOL_SCHEMA],
|
|
137
|
+
FOLLOWUP_CONVERSATION_SYSTEM_PROMPT,
|
|
138
|
+
{
|
|
139
|
+
config: {
|
|
140
|
+
max_tokens: FOLLOWUP_CONVERSATION_MAX_TOKENS,
|
|
141
|
+
modelIntent: 'latency-optimized',
|
|
142
|
+
},
|
|
143
|
+
signal: AbortSignal.timeout(FOLLOWUP_CONVERSATION_TIMEOUT_MS),
|
|
144
|
+
},
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// Extract the tool_use block from the response
|
|
148
|
+
const toolUseBlock = response.content.find(
|
|
149
|
+
(block) => block.type === 'tool_use' && block.name === FOLLOWUP_CONVERSATION_TOOL_NAME,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
if (!toolUseBlock || toolUseBlock.type !== 'tool_use') {
|
|
153
|
+
throw new Error('Provider did not return a tool_use block for follow-up decision');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const input = toolUseBlock.input as Record<string, unknown>;
|
|
157
|
+
|
|
158
|
+
// Strict validation of the structured output
|
|
159
|
+
const disposition = input.disposition;
|
|
160
|
+
if (typeof disposition !== 'string' || !VALID_FOLLOWUP_DISPOSITIONS.has(disposition)) {
|
|
161
|
+
throw new Error(`Invalid disposition: ${String(disposition)}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const replyText = input.replyText;
|
|
165
|
+
if (typeof replyText !== 'string' || replyText.trim().length === 0) {
|
|
166
|
+
throw new Error('Missing or empty replyText in tool_use response');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const result: GuardianFollowUpTurnResult = {
|
|
170
|
+
disposition: disposition as GuardianFollowUpDisposition,
|
|
171
|
+
replyText: replyText.trim(),
|
|
172
|
+
};
|
|
173
|
+
return result;
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Guardian verification intent resolution for deterministic first-turn routing.
|
|
2
|
+
// Exports `resolveGuardianVerificationIntent` as the single public entry point.
|
|
3
|
+
// When a direct guardian setup request is detected, the session pipeline
|
|
4
|
+
// rewrites the message to force immediate entry into the guardian-verify-setup
|
|
5
|
+
// skill flow, bypassing the normal agent loop's tendency to produce conceptual
|
|
6
|
+
// preambles before loading the skill.
|
|
7
|
+
|
|
8
|
+
export type GuardianVerificationIntentResult =
|
|
9
|
+
| { kind: 'none' }
|
|
10
|
+
| { kind: 'direct_setup'; rewrittenContent: string; channelHint?: 'sms' | 'voice' | 'telegram' };
|
|
11
|
+
|
|
12
|
+
// ── Direct setup patterns ────────────────────────────────────────────────
|
|
13
|
+
// These capture imperative requests to start guardian verification.
|
|
14
|
+
|
|
15
|
+
const DIRECT_SETUP_PATTERNS: RegExp[] = [
|
|
16
|
+
/\b(?:help\s+me\s+)?(?:confirm|verify)\s+(?:me|myself)\s+as\s+(?:your\s+|the\s+)?guardian\b/i,
|
|
17
|
+
/\b(?:set|add)\s+(?:me|myself)\s+(?:up\s+)?as\s+(?:your\s+|the\s+)?guardian\b/i,
|
|
18
|
+
/\bverify\s+(?:me\s+as\s+)?guardian\b/i,
|
|
19
|
+
/\bset\s+(?:me\s+as\s+)?guardian\b/i,
|
|
20
|
+
/\bguardian\s+verif(?:y|ication)\s*(?:setup|set\s*up)?\b/i,
|
|
21
|
+
/\bset\s*up\s+guardian\s+verif(?:y|ication)\b/i,
|
|
22
|
+
/\b(?:help\s+me\s+)?set\s+(?:myself\s+)?(?:up\s+)?as\s+(?:your\s+)?guardian\s+(?:for|via|by|over|on|through)\s+/i,
|
|
23
|
+
/\bguardian\s+(?:for|via|by|over|on|through)\s+(?:sms|text|phone|voice|telegram|call)\b/i,
|
|
24
|
+
/\bbecome\s+(?:your\s+|the\s+)?guardian\b/i,
|
|
25
|
+
/\bregister\s+(?:me\s+)?as\s+(?:your\s+|the\s+)?guardian\b/i,
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
// ── Conceptual / security question patterns ──────────────────────────────
|
|
29
|
+
// These indicate the user is asking *about* guardian verification
|
|
30
|
+
// rather than requesting to start it. Return passthrough for these.
|
|
31
|
+
|
|
32
|
+
const CONCEPTUAL_PATTERNS: RegExp[] = [
|
|
33
|
+
/^\s*(how|what|why|when|where|who|which)\b/i,
|
|
34
|
+
/\bwhat\s+is\s+(?:a\s+)?guardian\b/i,
|
|
35
|
+
/\bwhy\s+can'?t\s+(?:you|i)\b/i,
|
|
36
|
+
/\bhow\s+does\s+(?:guardian|verification)\s+work\b/i,
|
|
37
|
+
/\bexplain\s+(?:the\s+)?(?:guardian|verification)\b/i,
|
|
38
|
+
/\btell\s+me\s+about\s+(?:guardian|verification)\b/i,
|
|
39
|
+
/\bis\s+(?:there\s+)?(?:a\s+way|any\s+way)\s+to\b/i,
|
|
40
|
+
/\bcan\s+(?:you\s+)?(?:tell|explain|describe)\b/i,
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
// ── Channel hint extraction ──────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
const CHANNEL_HINT_PATTERNS: Array<{ pattern: RegExp; channel: 'sms' | 'voice' | 'telegram' }> = [
|
|
46
|
+
{ pattern: /\b(?:sms|text\s*(?:message)?)\b/i, channel: 'sms' },
|
|
47
|
+
{ pattern: /\b(?:voice|call|phone\s*call|by\s+phone)\b/i, channel: 'voice' },
|
|
48
|
+
{ pattern: /\btelegram\b/i, channel: 'telegram' },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
/** Common polite/filler words stripped before checking intent-only status. */
|
|
52
|
+
const FILLER_PATTERN =
|
|
53
|
+
/\b(please|pls|plz|can\s+you|could\s+you|would\s+you|now|right\s+now|thanks|thank\s+you|thx|ty|for\s+me|ok(ay)?|hey|hi|hello|just|i\s+want\s+to|i'd\s+like\s+to|i\s+need\s+to|let's|let\s+me)\b/gi;
|
|
54
|
+
|
|
55
|
+
// ── Internal helpers ─────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
function isConceptualQuestion(text: string): boolean {
|
|
58
|
+
const cleaned = text.replace(/^\s*(hey|hi|hello|please|pls|plz)[,\s]+/i, '');
|
|
59
|
+
return CONCEPTUAL_PATTERNS.some((p) => p.test(cleaned));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isDirectSetupIntent(text: string): boolean {
|
|
63
|
+
return DIRECT_SETUP_PATTERNS.some((p) => p.test(text));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function extractChannelHint(text: string): 'sms' | 'voice' | 'telegram' | undefined {
|
|
67
|
+
for (const { pattern, channel } of CHANNEL_HINT_PATTERNS) {
|
|
68
|
+
if (pattern.test(text)) return channel;
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Structured intent resolver ───────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Resolves guardian verification intent from user text.
|
|
77
|
+
*
|
|
78
|
+
* Pipeline:
|
|
79
|
+
* 1. Skip slash commands entirely
|
|
80
|
+
* 2. Conceptual question gate -- questions return `none`
|
|
81
|
+
* 3. Detect direct setup patterns
|
|
82
|
+
* 4. On match, build a deterministic model instruction to load guardian-verify-setup
|
|
83
|
+
*/
|
|
84
|
+
export function resolveGuardianVerificationIntent(text: string): GuardianVerificationIntentResult {
|
|
85
|
+
const trimmed = text.trim();
|
|
86
|
+
|
|
87
|
+
// Never intercept slash commands
|
|
88
|
+
if (trimmed.startsWith('/')) {
|
|
89
|
+
return { kind: 'none' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Conceptual questions pass through to normal agent processing
|
|
93
|
+
if (isConceptualQuestion(trimmed)) {
|
|
94
|
+
return { kind: 'none' };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Strip fillers for pattern matching but keep original for context
|
|
98
|
+
const withoutFillers = trimmed.replace(FILLER_PATTERN, '').replace(/\s{2,}/g, ' ').trim();
|
|
99
|
+
|
|
100
|
+
if (!isDirectSetupIntent(withoutFillers)) {
|
|
101
|
+
return { kind: 'none' };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const channelHint = extractChannelHint(trimmed);
|
|
105
|
+
|
|
106
|
+
// Build the rewritten content that deterministically loads the skill
|
|
107
|
+
const lines = [
|
|
108
|
+
'The user wants to verify themselves as the trusted guardian.',
|
|
109
|
+
'Please invoke the "Guardian Verify Setup" skill (ID: guardian-verify-setup) immediately using skill_load.',
|
|
110
|
+
];
|
|
111
|
+
if (channelHint) {
|
|
112
|
+
lines.push(`The user specified the ${channelHint} channel. Pass this context when starting the verification flow.`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
kind: 'direct_setup',
|
|
117
|
+
rewrittenContent: lines.join('\n'),
|
|
118
|
+
channelHint,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -6,7 +6,6 @@ import { join } from 'node:path';
|
|
|
6
6
|
import { v4 as uuid } from 'uuid';
|
|
7
7
|
|
|
8
8
|
import { packageApp } from '../../bundler/app-bundler.js';
|
|
9
|
-
import { getRuntimeHttpPort } from '../../config/env.js';
|
|
10
9
|
import { defaultGallery } from '../../gallery/default-gallery.js';
|
|
11
10
|
import { getAppDiff, getAppFileAtVersion, getAppHistory, restoreAppVersion } from '../../memory/app-git-service.js';
|
|
12
11
|
import { createApp, createAppRecord, deleteAppRecord, getApp, getAppPreview, listApps, queryAppRecords, updateApp,updateAppRecord } from '../../memory/app-store.js';
|
|
@@ -368,8 +367,7 @@ export async function handleShareAppCloud(
|
|
|
368
367
|
const bundleData = readFileSync(result.bundlePath);
|
|
369
368
|
const { shareToken } = createSharedAppLink(bundleData, result.manifest);
|
|
370
369
|
|
|
371
|
-
const
|
|
372
|
-
const shareUrl = `http://localhost:${port}/v1/apps/shared/${shareToken}`;
|
|
370
|
+
const shareUrl = `/v1/apps/shared/${shareToken}`;
|
|
373
371
|
|
|
374
372
|
ctx.send(socket, {
|
|
375
373
|
type: 'share_app_cloud_response',
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import * as net from 'node:net';
|
|
2
2
|
|
|
3
|
+
import type { ChannelId } from '../../channels/types.js';
|
|
3
4
|
import * as externalConversationStore from '../../memory/external-conversation-store.js';
|
|
4
5
|
import {
|
|
5
6
|
createVerificationChallenge,
|
|
7
|
+
findActiveSession,
|
|
6
8
|
getGuardianBinding,
|
|
7
9
|
getPendingChallenge,
|
|
8
10
|
revokeBinding as revokeGuardianBinding,
|
|
9
11
|
revokePendingChallenges,
|
|
10
|
-
findActiveSession,
|
|
11
12
|
} from '../../runtime/channel-guardian-service.js';
|
|
12
13
|
import { type ChannelReadinessService, createReadinessService } from '../../runtime/channel-readiness-service.js';
|
|
13
14
|
import {
|
|
14
|
-
startOutbound,
|
|
15
|
-
resendOutbound,
|
|
16
15
|
cancelOutbound,
|
|
16
|
+
resendOutbound,
|
|
17
|
+
startOutbound,
|
|
17
18
|
} from '../../runtime/guardian-outbound-actions.js';
|
|
18
19
|
import { normalizeAssistantId } from '../../util/platform.js';
|
|
19
|
-
import type { ChannelId } from '../../channels/types.js';
|
|
20
20
|
import type {
|
|
21
21
|
ChannelReadinessRequest,
|
|
22
22
|
GuardianVerificationRequest,
|
|
@@ -34,10 +34,10 @@ export type GuardianVerificationResult = Omit<GuardianVerificationResponse, 'typ
|
|
|
34
34
|
// ---------------------------------------------------------------------------
|
|
35
35
|
|
|
36
36
|
export {
|
|
37
|
+
DESTINATION_RATE_WINDOW_MS,
|
|
38
|
+
MAX_SENDS_PER_DESTINATION_WINDOW,
|
|
37
39
|
MAX_SENDS_PER_SESSION,
|
|
38
40
|
RESEND_COOLDOWN_MS,
|
|
39
|
-
MAX_SENDS_PER_DESTINATION_WINDOW,
|
|
40
|
-
DESTINATION_RATE_WINDOW_MS,
|
|
41
41
|
} from '../../runtime/guardian-outbound-actions.js';
|
|
42
42
|
|
|
43
43
|
// ---------------------------------------------------------------------------
|
|
@@ -182,10 +182,10 @@ export function handleGuardianVerification(
|
|
|
182
182
|
channel,
|
|
183
183
|
});
|
|
184
184
|
} else if (msg.action === 'start_outbound') {
|
|
185
|
-
const result = startOutbound({ channel, assistantId, destination: msg.destination, rebind: msg.rebind });
|
|
185
|
+
const result = startOutbound({ channel, assistantId, destination: msg.destination, rebind: msg.rebind, originConversationId: msg.originConversationId });
|
|
186
186
|
ctx.send(socket, { type: 'guardian_verification_response', ...result });
|
|
187
187
|
} else if (msg.action === 'resend_outbound') {
|
|
188
|
-
const result = resendOutbound({ channel, assistantId });
|
|
188
|
+
const result = resendOutbound({ channel, assistantId, originConversationId: msg.originConversationId });
|
|
189
189
|
ctx.send(socket, { type: 'guardian_verification_response', ...result });
|
|
190
190
|
} else if (msg.action === 'cancel_outbound') {
|
|
191
191
|
const result = cancelOutbound({ channel, assistantId });
|
|
@@ -85,7 +85,7 @@ export function handleHeartbeatConfig(
|
|
|
85
85
|
activeHoursEnd: null,
|
|
86
86
|
nextRunAt: null,
|
|
87
87
|
success: false,
|
|
88
|
-
error: `Unknown action: ${String(
|
|
88
|
+
error: `Unknown action: ${String(msg.action)}`,
|
|
89
89
|
});
|
|
90
90
|
}
|
|
91
91
|
} catch (err) {
|