@vellumai/assistant 0.4.49 → 0.4.51
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 +24 -33
- package/README.md +3 -3
- package/docs/architecture/integrations.md +2 -2
- package/docs/architecture/keychain-broker.md +6 -6
- package/docs/architecture/memory.md +180 -119
- package/knip.json +32 -0
- package/package.json +3 -2
- package/src/__tests__/agent-loop.test.ts +3 -1
- package/src/__tests__/anthropic-provider.test.ts +114 -23
- package/src/__tests__/approval-cascade.test.ts +1 -15
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
- package/src/__tests__/btw-routes.test.ts +61 -5
- package/src/__tests__/canonical-guardian-store.test.ts +95 -0
- package/src/__tests__/checker.test.ts +13 -0
- package/src/__tests__/config-schema.test.ts +1 -68
- package/src/__tests__/config-watcher.test.ts +8 -0
- package/src/__tests__/context-memory-e2e.test.ts +11 -100
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-security-e2e.test.ts +1 -0
- package/src/__tests__/credential-security-invariants.test.ts +8 -7
- package/src/__tests__/credential-vault-unit.test.ts +23 -18
- package/src/__tests__/credential-vault.test.ts +30 -18
- package/src/__tests__/credentials-cli.test.ts +257 -82
- package/src/__tests__/cu-unified-flow.test.ts +532 -0
- package/src/__tests__/date-context.test.ts +93 -77
- package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
- package/src/__tests__/history-repair.test.ts +245 -0
- package/src/__tests__/host-cu-proxy.test.ts +165 -3
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +36 -7
- package/src/__tests__/integration-status.test.ts +31 -30
- package/src/__tests__/invite-redemption-service.test.ts +166 -13
- package/src/__tests__/invite-routes-http.test.ts +166 -5
- package/src/__tests__/keychain-broker-client.test.ts +4 -4
- package/src/__tests__/list-messages-attachments.test.ts +193 -0
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
- package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
- package/src/__tests__/memory-recall-quality.test.ts +244 -407
- package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
- package/src/__tests__/memory-regressions.test.ts +477 -2841
- package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
- package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
- package/src/__tests__/mime-builder.test.ts +28 -0
- package/src/__tests__/native-web-search.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +824 -31
- package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
- package/src/__tests__/oauth-store.test.ts +363 -17
- package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
- package/src/__tests__/registry.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +55 -1
- package/src/__tests__/schedule-tools.test.ts +32 -0
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +183 -0
- package/src/__tests__/secure-keys.test.ts +78 -18
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/session-abort-tool-results.test.ts +1 -14
- package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
- package/src/__tests__/session-agent-loop.test.ts +19 -15
- package/src/__tests__/session-confirmation-signals.test.ts +1 -15
- package/src/__tests__/session-error.test.ts +124 -2
- package/src/__tests__/session-history-web-search.test.ts +918 -0
- package/src/__tests__/session-pre-run-repair.test.ts +1 -14
- package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
- package/src/__tests__/session-queue.test.ts +37 -27
- package/src/__tests__/session-runtime-assembly.test.ts +54 -0
- package/src/__tests__/session-slash-known.test.ts +1 -15
- package/src/__tests__/session-slash-queue.test.ts +1 -15
- package/src/__tests__/session-slash-unknown.test.ts +1 -15
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
- package/src/__tests__/session-workspace-injection.test.ts +3 -37
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
- package/src/__tests__/skills-install-extract.test.ts +93 -0
- package/src/__tests__/skills.test.ts +2 -2
- package/src/__tests__/skillssh-registry.test.ts +451 -0
- package/src/__tests__/slack-channel-config.test.ts +10 -8
- package/src/__tests__/trust-store.test.ts +15 -0
- package/src/__tests__/twilio-config.test.ts +11 -10
- package/src/__tests__/twilio-provider.test.ts +9 -4
- package/src/__tests__/voice-invite-redemption.test.ts +85 -5
- package/src/agent/ax-tree-compaction.test.ts +51 -0
- package/src/agent/loop.ts +39 -12
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-request-resolvers.ts +14 -2
- package/src/bundler/compiler-tools.ts +66 -2
- package/src/calls/call-domain.ts +134 -3
- package/src/calls/call-store.ts +6 -0
- package/src/calls/relay-server.ts +44 -6
- package/src/calls/relay-setup-router.ts +17 -1
- package/src/calls/twilio-config.ts +5 -4
- package/src/calls/twilio-provider.ts +14 -9
- package/src/calls/twilio-rest.ts +10 -7
- package/src/calls/types.ts +3 -1
- package/src/cli/commands/config.ts +14 -9
- package/src/cli/commands/contacts.ts +3 -0
- package/src/cli/commands/credentials.ts +170 -174
- package/src/cli/commands/doctor.ts +11 -8
- package/src/cli/commands/keys.ts +9 -9
- package/src/cli/commands/mcp.ts +46 -59
- package/src/cli/commands/memory.ts +16 -165
- package/src/cli/commands/oauth/apps.ts +68 -10
- package/src/cli/commands/oauth/connections.ts +475 -105
- package/src/cli/commands/oauth/index.ts +3 -3
- package/src/cli/commands/oauth/providers.ts +18 -4
- package/src/cli/commands/sessions.ts +5 -2
- package/src/cli/commands/skills.ts +173 -1
- package/src/cli/http-client.ts +0 -20
- package/src/cli/main-screen.tsx +2 -2
- package/src/cli/program.ts +5 -6
- package/src/cli.ts +20 -22
- package/src/config/__tests__/feature-flag-registry-bundled.test.ts +39 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
- package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
- package/src/config/bundled-skills/contacts/SKILL.md +35 -11
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/gmail/SKILL.md +1 -1
- package/src/config/bundled-skills/gmail/TOOLS.json +52 -0
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +13 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +9 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +9 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +5 -1
- package/src/config/bundled-skills/google-calendar/TOOLS.json +20 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +8 -2
- package/src/config/bundled-skills/messaging/SKILL.md +1 -1
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +7 -5
- package/src/config/bundled-skills/slack/tools/shared.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +1 -1
- package/src/config/bundled-tool-registry.ts +2 -5
- package/src/config/loader.ts +6 -42
- package/src/config/schema.ts +1 -12
- package/src/config/schemas/memory-lifecycle.ts +0 -9
- package/src/config/schemas/memory-processing.ts +0 -180
- package/src/config/schemas/memory-retrieval.ts +32 -104
- package/src/config/schemas/memory.ts +0 -10
- package/src/config/types.ts +0 -4
- package/src/contacts/contact-store.ts +39 -2
- package/src/contacts/contacts-write.ts +9 -0
- package/src/context/window-manager.ts +4 -1
- package/src/daemon/config-watcher.ts +55 -2
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +114 -31
- package/src/daemon/handlers/config-ingress.ts +2 -2
- package/src/daemon/handlers/config-slack-channel.ts +59 -39
- package/src/daemon/handlers/config-telegram.ts +23 -14
- package/src/daemon/handlers/session-history.ts +1 -358
- package/src/daemon/handlers/sessions.ts +18 -13
- package/src/daemon/handlers/shared.ts +3 -17
- package/src/daemon/handlers/skills.ts +20 -1
- package/src/daemon/history-repair.ts +72 -8
- package/src/daemon/host-cu-proxy.ts +55 -26
- package/src/daemon/lifecycle.ts +39 -4
- package/src/daemon/mcp-reload-service.ts +2 -2
- package/src/daemon/message-types/computer-use.ts +1 -12
- package/src/daemon/message-types/memory.ts +4 -16
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/sessions.ts +4 -42
- package/src/daemon/server.ts +6 -1
- package/src/daemon/session-agent-loop-handlers.ts +38 -0
- package/src/daemon/session-agent-loop.ts +334 -48
- package/src/daemon/session-error.ts +89 -6
- package/src/daemon/session-history.ts +17 -7
- package/src/daemon/session-media-retry.ts +6 -2
- package/src/daemon/session-memory.ts +69 -149
- package/src/daemon/session-process.ts +10 -1
- package/src/daemon/session-runtime-assembly.ts +49 -19
- package/src/daemon/session-slash.ts +3 -5
- package/src/daemon/session-surfaces.ts +4 -1
- package/src/daemon/session-tool-setup.ts +7 -1
- package/src/daemon/session.ts +12 -2
- package/src/email/providers/index.ts +2 -2
- package/src/instrument.ts +61 -1
- package/src/media/avatar-router.ts +1 -1
- package/src/memory/admin.ts +2 -191
- package/src/memory/canonical-guardian-store.ts +38 -2
- package/src/memory/conversation-crud.ts +0 -33
- package/src/memory/conversation-queries.ts +25 -83
- package/src/memory/db-init.ts +32 -0
- package/src/memory/embedding-backend.ts +84 -8
- package/src/memory/embedding-types.ts +9 -1
- package/src/memory/indexer.ts +7 -46
- package/src/memory/invite-store.ts +19 -0
- package/src/memory/items-extractor.ts +274 -76
- package/src/memory/job-handlers/backfill.ts +2 -127
- package/src/memory/job-handlers/cleanup.ts +2 -16
- package/src/memory/job-handlers/extraction.ts +2 -138
- package/src/memory/job-handlers/index-maintenance.ts +1 -6
- package/src/memory/job-handlers/summarization.ts +3 -148
- package/src/memory/job-utils.ts +21 -59
- package/src/memory/jobs-store.ts +1 -159
- package/src/memory/jobs-worker.ts +9 -52
- package/src/memory/migrations/104-core-indexes.ts +3 -3
- package/src/memory/migrations/149-oauth-tables.ts +2 -0
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
- package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
- package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
- package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
- package/src/memory/migrations/154-drop-fts.ts +20 -0
- package/src/memory/migrations/155-drop-conflicts.ts +7 -0
- package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
- package/src/memory/migrations/157-invite-contact-id.ts +104 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/migrations/registry.ts +6 -0
- package/src/memory/qdrant-client.ts +148 -51
- package/src/memory/raw-query.ts +1 -1
- package/src/memory/retriever.test.ts +294 -273
- package/src/memory/retriever.ts +421 -645
- package/src/memory/schema/calls.ts +2 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/memory-core.ts +3 -48
- package/src/memory/schema/oauth.ts +2 -0
- package/src/memory/search/formatting.ts +263 -176
- package/src/memory/search/lexical.ts +1 -254
- package/src/memory/search/ranking.ts +0 -455
- package/src/memory/search/semantic.ts +100 -14
- package/src/memory/search/staleness.ts +47 -0
- package/src/memory/search/tier-classifier.ts +21 -0
- package/src/memory/search/types.ts +15 -77
- package/src/memory/task-memory-cleanup.ts +4 -6
- package/src/messaging/provider.ts +1 -1
- package/src/messaging/providers/gmail/adapter.ts +1 -1
- package/src/messaging/providers/gmail/mime-builder.ts +17 -7
- package/src/messaging/providers/telegram-bot/adapter.ts +17 -8
- package/src/messaging/providers/whatsapp/adapter.ts +13 -9
- package/src/messaging/registry.ts +9 -5
- package/src/oauth/byo-connection.test.ts +40 -25
- package/src/oauth/connect-orchestrator.ts +4 -10
- package/src/oauth/connection-resolver.ts +20 -6
- package/src/oauth/manual-token-connection.ts +5 -5
- package/src/oauth/oauth-store.ts +183 -31
- package/src/oauth/platform-connection.test.ts +1 -1
- package/src/oauth/provider-behaviors.ts +503 -4
- package/src/oauth/seed-providers.ts +214 -8
- package/src/oauth/token-persistence.ts +31 -16
- package/src/permissions/defaults.ts +1 -0
- package/src/permissions/trust-store.ts +23 -1
- package/src/playbooks/playbook-compiler.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -2
- package/src/providers/anthropic/client.ts +56 -126
- package/src/providers/types.ts +7 -1
- package/src/runtime/AGENTS.md +9 -0
- package/src/runtime/auth/route-policy.ts +6 -3
- package/src/runtime/channel-readiness-service.ts +48 -40
- package/src/runtime/guardian-reply-router.ts +24 -22
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/http-types.ts +2 -0
- package/src/runtime/invite-redemption-service.ts +72 -12
- package/src/runtime/invite-service.ts +43 -0
- package/src/runtime/middleware/twilio-validation.ts +1 -1
- package/src/runtime/pending-interactions.ts +2 -2
- package/src/runtime/routes/brain-graph-routes.ts +10 -90
- package/src/runtime/routes/btw-routes.ts +10 -5
- package/src/runtime/routes/conversation-routes.ts +56 -11
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
- package/src/runtime/routes/integrations/slack/channel.ts +2 -2
- package/src/runtime/routes/integrations/telegram.ts +2 -2
- package/src/runtime/routes/integrations/twilio.ts +17 -17
- package/src/runtime/routes/invite-routes.ts +29 -4
- package/src/runtime/routes/memory-item-routes.test.ts +754 -0
- package/src/runtime/routes/memory-item-routes.ts +503 -0
- package/src/runtime/routes/secret-routes.ts +17 -0
- package/src/runtime/routes/session-management-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +3 -3
- package/src/runtime/routes/trust-rules-routes.ts +14 -0
- package/src/runtime/routes/workspace-routes.ts +9 -4
- package/src/runtime/routes/workspace-utils.ts +8 -2
- package/src/schedule/integration-status.ts +26 -19
- package/src/security/keychain-broker-client.ts +17 -4
- package/src/security/oauth2.ts +6 -7
- package/src/security/secure-keys.ts +44 -19
- package/src/security/token-manager.ts +46 -39
- package/src/services/vercel-deploy.ts +0 -24
- package/src/signals/confirm.ts +78 -0
- package/src/signals/mcp-reload.ts +18 -0
- package/src/skills/catalog-install.ts +74 -18
- package/src/skills/skillssh-registry.ts +503 -0
- package/src/tools/assets/search.ts +5 -1
- package/src/tools/computer-use/definitions.ts +0 -10
- package/src/tools/computer-use/registry.ts +1 -1
- package/src/tools/credentials/vault.ts +22 -7
- package/src/tools/memory/definitions.ts +4 -13
- package/src/tools/memory/handlers.test.ts +83 -103
- package/src/tools/memory/handlers.ts +50 -85
- package/src/tools/network/script-proxy/session-manager.ts +8 -8
- package/src/tools/schedule/create.ts +10 -3
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/skills/load.ts +25 -2
- package/src/watcher/provider-types.ts +1 -1
- package/src/watcher/providers/github.ts +1 -1
- package/src/watcher/providers/gmail.ts +3 -3
- package/src/watcher/providers/google-calendar.ts +3 -3
- package/src/watcher/providers/linear.ts +1 -1
- package/src/__tests__/clarification-resolver.test.ts +0 -193
- package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
- package/src/__tests__/conflict-policy.test.ts +0 -269
- package/src/__tests__/conflict-store.test.ts +0 -372
- package/src/__tests__/contradiction-checker.test.ts +0 -361
- package/src/__tests__/entity-extractor.test.ts +0 -211
- package/src/__tests__/entity-search.test.ts +0 -1117
- package/src/__tests__/profile-compiler.test.ts +0 -392
- package/src/__tests__/session-conflict-gate.test.ts +0 -1228
- package/src/__tests__/session-profile-injection.test.ts +0 -557
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
- package/src/daemon/session-conflict-gate.ts +0 -167
- package/src/daemon/session-dynamic-profile.ts +0 -77
- package/src/memory/clarification-resolver.ts +0 -417
- package/src/memory/conflict-intent.ts +0 -205
- package/src/memory/conflict-policy.ts +0 -127
- package/src/memory/conflict-store.ts +0 -410
- package/src/memory/contradiction-checker.ts +0 -508
- package/src/memory/entity-extractor.ts +0 -535
- package/src/memory/format-recall.ts +0 -47
- package/src/memory/fts-reconciler.ts +0 -165
- package/src/memory/job-handlers/conflict.ts +0 -200
- package/src/memory/profile-compiler.ts +0 -195
- package/src/memory/recall-cache.ts +0 -117
- package/src/memory/search/entity.ts +0 -535
- package/src/memory/search/query-expansion.test.ts +0 -70
- package/src/memory/search/query-expansion.ts +0 -118
- package/src/runtime/routes/mcp-routes.ts +0 -20
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { ChannelId } from "../channels/types.js";
|
|
11
|
-
import { findContactChannel } from "../contacts/contact-store.js";
|
|
11
|
+
import { findContactChannel, getContact } from "../contacts/contact-store.js";
|
|
12
12
|
import { upsertContactChannel } from "../contacts/contacts-write.js";
|
|
13
13
|
import { getSqlite } from "../memory/db.js";
|
|
14
14
|
import {
|
|
@@ -135,7 +135,17 @@ export function redeemInvite(params: {
|
|
|
135
135
|
const existingChannel = contactResult?.channel ?? null;
|
|
136
136
|
const existingContact = contactResult?.contact ?? null;
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
// If the invite targets a specific contact and the sender's existing channel
|
|
139
|
+
// belongs to a different contact, ignore the existing match — the invite
|
|
140
|
+
// should bind the sender's identity to the target contact, not the existing one.
|
|
141
|
+
const targetMismatch =
|
|
142
|
+
existingContact && existingContact.id !== invite.contactId;
|
|
143
|
+
|
|
144
|
+
if (
|
|
145
|
+
existingChannel &&
|
|
146
|
+
existingChannel.status === "active" &&
|
|
147
|
+
!targetMismatch
|
|
148
|
+
) {
|
|
139
149
|
return { ok: true, type: "already_member", memberId: existingChannel.id };
|
|
140
150
|
}
|
|
141
151
|
|
|
@@ -150,7 +160,7 @@ export function redeemInvite(params: {
|
|
|
150
160
|
// in a non-active state (revoked/pending), reactivate it via upsertContactChannel
|
|
151
161
|
// and consume an invite use atomically. The fresh-member path below also
|
|
152
162
|
// uses upsertContactChannel to keep contacts in sync.
|
|
153
|
-
if (existingChannel) {
|
|
163
|
+
if (existingChannel && !targetMismatch) {
|
|
154
164
|
// Sentinel error used to trigger a transaction rollback when the invite
|
|
155
165
|
// was concurrently revoked/expired between pre-validation and write time.
|
|
156
166
|
const STALE_INVITE = Symbol("stale_invite");
|
|
@@ -189,6 +199,7 @@ export function redeemInvite(params: {
|
|
|
189
199
|
inviteId: invite.id,
|
|
190
200
|
verifiedAt: Date.now(),
|
|
191
201
|
verifiedVia: "invite",
|
|
202
|
+
contactId: invite.contactId,
|
|
192
203
|
});
|
|
193
204
|
|
|
194
205
|
const recorded = recordInviteUse({
|
|
@@ -220,6 +231,16 @@ export function redeemInvite(params: {
|
|
|
220
231
|
|
|
221
232
|
// Fresh member creation: upsert into contacts tables and consume an invite
|
|
222
233
|
// use atomically, mirroring the reactivation path above.
|
|
234
|
+
// When the invite targets a specific contact (targetMismatch path), preserve
|
|
235
|
+
// the target contact's guardian-assigned display name if it has one.
|
|
236
|
+
let freshDisplayName = displayName;
|
|
237
|
+
if (invite.contactId) {
|
|
238
|
+
const targetContact = getContact(invite.contactId);
|
|
239
|
+
if (targetContact?.displayName?.trim().length) {
|
|
240
|
+
freshDisplayName = targetContact.displayName;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
223
244
|
const STALE_INVITE_FRESH = Symbol("stale_invite_fresh");
|
|
224
245
|
let freshResult: ReturnType<typeof upsertContactChannel> | undefined;
|
|
225
246
|
try {
|
|
@@ -229,13 +250,14 @@ export function redeemInvite(params: {
|
|
|
229
250
|
sourceChannel,
|
|
230
251
|
externalUserId,
|
|
231
252
|
externalChatId,
|
|
232
|
-
displayName,
|
|
253
|
+
displayName: freshDisplayName,
|
|
233
254
|
username,
|
|
234
255
|
status: "active",
|
|
235
256
|
policy: "allow",
|
|
236
257
|
inviteId: invite.id,
|
|
237
258
|
verifiedAt: Date.now(),
|
|
238
259
|
verifiedVia: "invite",
|
|
260
|
+
contactId: invite.contactId,
|
|
239
261
|
});
|
|
240
262
|
|
|
241
263
|
const recorded = recordInviteUse({
|
|
@@ -338,8 +360,18 @@ export function redeemVoiceInviteCode(params: {
|
|
|
338
360
|
externalUserId: canonicalCallerId,
|
|
339
361
|
});
|
|
340
362
|
const existingVoiceChannel = voiceContactResult?.channel ?? null;
|
|
363
|
+
const voiceContact = voiceContactResult?.contact ?? null;
|
|
364
|
+
|
|
365
|
+
// If the invite targets a specific contact and the sender's existing channel
|
|
366
|
+
// belongs to a different contact, ignore the existing match — the invite
|
|
367
|
+
// should bind the sender's identity to the target contact, not the existing one.
|
|
368
|
+
const targetMismatch = voiceContact && voiceContact.id !== invite.contactId;
|
|
341
369
|
|
|
342
|
-
if (
|
|
370
|
+
if (
|
|
371
|
+
existingVoiceChannel &&
|
|
372
|
+
existingVoiceChannel.status === "active" &&
|
|
373
|
+
!targetMismatch
|
|
374
|
+
) {
|
|
343
375
|
return {
|
|
344
376
|
ok: true,
|
|
345
377
|
type: "already_member",
|
|
@@ -356,12 +388,17 @@ export function redeemVoiceInviteCode(params: {
|
|
|
356
388
|
const STALE_INVITE = Symbol("stale_invite");
|
|
357
389
|
let memberId: string | undefined;
|
|
358
390
|
|
|
359
|
-
//
|
|
360
|
-
//
|
|
361
|
-
|
|
362
|
-
const preservedDisplayName = voiceContact?.displayName?.trim().length
|
|
391
|
+
// When the invite targets a specific contact (targetMismatch path), preserve
|
|
392
|
+
// the target contact's guardian-assigned display name if it has one.
|
|
393
|
+
let preservedDisplayName = voiceContact?.displayName?.trim().length
|
|
363
394
|
? voiceContact.displayName
|
|
364
395
|
: (invite.friendName ?? undefined);
|
|
396
|
+
if (targetMismatch && invite.contactId) {
|
|
397
|
+
const targetContact = getContact(invite.contactId);
|
|
398
|
+
if (targetContact?.displayName?.trim().length) {
|
|
399
|
+
preservedDisplayName = targetContact.displayName;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
365
402
|
|
|
366
403
|
try {
|
|
367
404
|
getSqlite()
|
|
@@ -376,6 +413,7 @@ export function redeemVoiceInviteCode(params: {
|
|
|
376
413
|
inviteId: invite.id,
|
|
377
414
|
verifiedAt: Date.now(),
|
|
378
415
|
verifiedVia: "invite",
|
|
416
|
+
contactId: invite.contactId,
|
|
379
417
|
});
|
|
380
418
|
memberId = writeResult!.channel.id;
|
|
381
419
|
|
|
@@ -476,7 +514,17 @@ export function redeemInviteByCode(params: {
|
|
|
476
514
|
const existingChannel = contactResult?.channel ?? null;
|
|
477
515
|
const existingContact = contactResult?.contact ?? null;
|
|
478
516
|
|
|
479
|
-
|
|
517
|
+
// If the invite targets a specific contact and the sender's existing channel
|
|
518
|
+
// belongs to a different contact, ignore the existing match — the invite
|
|
519
|
+
// should bind the sender's identity to the target contact, not the existing one.
|
|
520
|
+
const targetMismatch =
|
|
521
|
+
existingContact && existingContact.id !== invite.contactId;
|
|
522
|
+
|
|
523
|
+
if (
|
|
524
|
+
existingChannel &&
|
|
525
|
+
existingChannel.status === "active" &&
|
|
526
|
+
!targetMismatch
|
|
527
|
+
) {
|
|
480
528
|
return { ok: true, type: "already_member", memberId: existingChannel.id };
|
|
481
529
|
}
|
|
482
530
|
|
|
@@ -489,7 +537,7 @@ export function redeemInviteByCode(params: {
|
|
|
489
537
|
|
|
490
538
|
// Inactive member reactivation: reactivate via upsertContactChannel and consume
|
|
491
539
|
// an invite use atomically.
|
|
492
|
-
if (existingChannel) {
|
|
540
|
+
if (existingChannel && !targetMismatch) {
|
|
493
541
|
const STALE_INVITE_REACTIVATE = Symbol("stale_invite_reactivate");
|
|
494
542
|
const canonicalMemberId = existingChannel.externalUserId
|
|
495
543
|
? canonicalizeInboundIdentity(
|
|
@@ -525,6 +573,7 @@ export function redeemInviteByCode(params: {
|
|
|
525
573
|
inviteId: invite.id,
|
|
526
574
|
verifiedAt: Date.now(),
|
|
527
575
|
verifiedVia: "invite",
|
|
576
|
+
contactId: invite.contactId,
|
|
528
577
|
});
|
|
529
578
|
|
|
530
579
|
const recorded = recordInviteUse({
|
|
@@ -553,6 +602,16 @@ export function redeemInviteByCode(params: {
|
|
|
553
602
|
|
|
554
603
|
// Fresh member creation: upsert into contacts tables and consume an invite
|
|
555
604
|
// use atomically.
|
|
605
|
+
// When the invite targets a specific contact (targetMismatch path), preserve
|
|
606
|
+
// the target contact's guardian-assigned display name if it has one.
|
|
607
|
+
let freshDisplayName = displayName;
|
|
608
|
+
if (invite.contactId) {
|
|
609
|
+
const targetContact = getContact(invite.contactId);
|
|
610
|
+
if (targetContact?.displayName?.trim().length) {
|
|
611
|
+
freshDisplayName = targetContact.displayName;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
556
615
|
const STALE_INVITE_FRESH = Symbol("stale_invite_fresh");
|
|
557
616
|
let freshResult: ReturnType<typeof upsertContactChannel> | undefined;
|
|
558
617
|
try {
|
|
@@ -562,13 +621,14 @@ export function redeemInviteByCode(params: {
|
|
|
562
621
|
sourceChannel,
|
|
563
622
|
externalUserId,
|
|
564
623
|
externalChatId,
|
|
565
|
-
displayName,
|
|
624
|
+
displayName: freshDisplayName,
|
|
566
625
|
username,
|
|
567
626
|
status: "active",
|
|
568
627
|
policy: "allow",
|
|
569
628
|
inviteId: invite.id,
|
|
570
629
|
verifiedAt: Date.now(),
|
|
571
630
|
verifiedVia: "invite",
|
|
631
|
+
contactId: invite.contactId,
|
|
572
632
|
});
|
|
573
633
|
|
|
574
634
|
const recorded = recordInviteUse({
|
|
@@ -8,14 +8,17 @@
|
|
|
8
8
|
* /v1/contacts/channels endpoints.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { startInviteCall } from "../calls/call-domain.js";
|
|
11
12
|
import { isChannelId } from "../channels/types.js";
|
|
12
13
|
import {
|
|
13
14
|
createInvite,
|
|
15
|
+
findById,
|
|
14
16
|
findByTokenHash,
|
|
15
17
|
hashToken,
|
|
16
18
|
type IngressInvite,
|
|
17
19
|
type InviteStatus,
|
|
18
20
|
listInvites,
|
|
21
|
+
markInviteExpired,
|
|
19
22
|
revokeInvite,
|
|
20
23
|
} from "../memory/invite-store.js";
|
|
21
24
|
import {
|
|
@@ -156,11 +159,16 @@ export async function createIngressInvite(params: {
|
|
|
156
159
|
voiceCodeDigits?: number;
|
|
157
160
|
friendName?: string;
|
|
158
161
|
guardianName?: string;
|
|
162
|
+
contactId: string;
|
|
159
163
|
}): Promise<IngressResult<InviteResponseData>> {
|
|
160
164
|
if (!params.sourceChannel) {
|
|
161
165
|
return { ok: false, error: "sourceChannel is required for create" };
|
|
162
166
|
}
|
|
163
167
|
|
|
168
|
+
if (!params.contactId) {
|
|
169
|
+
return { ok: false, error: "contactId is required for create" };
|
|
170
|
+
}
|
|
171
|
+
|
|
164
172
|
// For voice invites: generate a one-time numeric code, hash it, and pass
|
|
165
173
|
// the hash to the store. The plaintext code is included in the response
|
|
166
174
|
// exactly once and never stored.
|
|
@@ -210,6 +218,7 @@ export async function createIngressInvite(params: {
|
|
|
210
218
|
|
|
211
219
|
const { invite, rawToken } = createInvite({
|
|
212
220
|
sourceChannel: params.sourceChannel,
|
|
221
|
+
contactId: params.contactId,
|
|
213
222
|
note: params.note,
|
|
214
223
|
maxUses: params.maxUses,
|
|
215
224
|
expiresInMs: params.expiresInMs,
|
|
@@ -250,6 +259,10 @@ export async function createIngressInvite(params: {
|
|
|
250
259
|
});
|
|
251
260
|
}
|
|
252
261
|
|
|
262
|
+
if (isVoice && params.friendName) {
|
|
263
|
+
guardianInstruction = `${params.friendName} will need this code when they answer. Share it with them first.`;
|
|
264
|
+
}
|
|
265
|
+
|
|
253
266
|
// Voice invites must not expose the token — callers must redeem via the
|
|
254
267
|
// identity-bound voice code flow, not the generic token redemption path.
|
|
255
268
|
return {
|
|
@@ -291,6 +304,36 @@ export function revokeIngressInvite(
|
|
|
291
304
|
return { ok: true, data: inviteToResponse(revoked) };
|
|
292
305
|
}
|
|
293
306
|
|
|
307
|
+
export async function triggerInviteCall(
|
|
308
|
+
inviteId: string,
|
|
309
|
+
): Promise<IngressResult<{ callSid: string }>> {
|
|
310
|
+
if (!inviteId) return { ok: false, error: "inviteId is required" };
|
|
311
|
+
const invite = findById(inviteId);
|
|
312
|
+
if (!invite) return { ok: false, error: "Invite not found" };
|
|
313
|
+
if (invite.status !== "active")
|
|
314
|
+
return { ok: false, error: "Invite is not active" };
|
|
315
|
+
if (invite.expiresAt && invite.expiresAt <= Date.now()) {
|
|
316
|
+
markInviteExpired(invite.id);
|
|
317
|
+
return { ok: false, error: "Invite has expired" };
|
|
318
|
+
}
|
|
319
|
+
if (invite.sourceChannel !== "phone")
|
|
320
|
+
return { ok: false, error: "Only phone invites support call triggering" };
|
|
321
|
+
if (
|
|
322
|
+
!invite.expectedExternalUserId ||
|
|
323
|
+
!invite.friendName ||
|
|
324
|
+
!invite.guardianName
|
|
325
|
+
) {
|
|
326
|
+
return { ok: false, error: "Invite is missing required voice metadata" };
|
|
327
|
+
}
|
|
328
|
+
const result = await startInviteCall({
|
|
329
|
+
phoneNumber: invite.expectedExternalUserId,
|
|
330
|
+
friendName: invite.friendName,
|
|
331
|
+
guardianName: invite.guardianName,
|
|
332
|
+
});
|
|
333
|
+
if (!result.ok) return { ok: false, error: result.error };
|
|
334
|
+
return { ok: true, data: { callSid: result.callSid } };
|
|
335
|
+
}
|
|
336
|
+
|
|
294
337
|
export function redeemIngressInvite(params: {
|
|
295
338
|
token?: string;
|
|
296
339
|
externalUserId?: string;
|
|
@@ -57,7 +57,7 @@ export async function validateTwilioWebhook(
|
|
|
57
57
|
): Promise<{ body: string } | Response> {
|
|
58
58
|
const rawBody = await req.text();
|
|
59
59
|
|
|
60
|
-
const authToken = TwilioConversationRelayProvider.getAuthToken();
|
|
60
|
+
const authToken = await TwilioConversationRelayProvider.getAuthToken();
|
|
61
61
|
|
|
62
62
|
if (!authToken) {
|
|
63
63
|
log.error(
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* host_bash_request, host_file_request, or host_cu_request, the onEvent
|
|
7
7
|
* callback registers the interaction here. Standalone HTTP endpoints
|
|
8
8
|
* (/v1/confirm, /v1/secret, /v1/trust-rules, /v1/host-bash-result,
|
|
9
|
-
* /v1/host-file-result, /v1/host-cu-result) look up the session from
|
|
10
|
-
*
|
|
9
|
+
* /v1/host-file-result, /v1/host-cu-result) look up the session from this
|
|
10
|
+
* tracker to resolve the interaction.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type { Session } from "../daemon/session.js";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Route handlers for the brain graph visualization endpoint.
|
|
3
3
|
*
|
|
4
4
|
* Queries the memory database to return a knowledge graph shaped for brain-lobe
|
|
5
|
-
* visualization, with
|
|
5
|
+
* visualization, with memory items mapped to brain regions based on their kind.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { readFileSync } from "node:fs";
|
|
@@ -11,65 +11,23 @@ import { join } from "node:path";
|
|
|
11
11
|
import { count } from "drizzle-orm";
|
|
12
12
|
|
|
13
13
|
import { getDb } from "../../memory/db.js";
|
|
14
|
-
import {
|
|
15
|
-
memoryEntities,
|
|
16
|
-
memoryEntityRelations,
|
|
17
|
-
memoryItems,
|
|
18
|
-
} from "../../memory/schema.js";
|
|
14
|
+
import { memoryItems } from "../../memory/schema.js";
|
|
19
15
|
import { resolveBundledDir } from "../../util/bundled-asset.js";
|
|
20
16
|
import type { RouteDefinition } from "../http-router.js";
|
|
21
17
|
|
|
22
|
-
function getLobeRegion(entityType: string): string {
|
|
23
|
-
switch (entityType) {
|
|
24
|
-
case "person":
|
|
25
|
-
case "organization":
|
|
26
|
-
return "right-social";
|
|
27
|
-
case "project":
|
|
28
|
-
case "company":
|
|
29
|
-
return "left-planning";
|
|
30
|
-
case "tool":
|
|
31
|
-
return "left-technical";
|
|
32
|
-
case "concept":
|
|
33
|
-
return "right-creative";
|
|
34
|
-
case "location":
|
|
35
|
-
return "right-spatial";
|
|
36
|
-
default:
|
|
37
|
-
return "center";
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function getEntityColor(entityType: string): string {
|
|
42
|
-
switch (entityType) {
|
|
43
|
-
case "person":
|
|
44
|
-
return "#22c55e";
|
|
45
|
-
case "project":
|
|
46
|
-
return "#f97316";
|
|
47
|
-
case "tool":
|
|
48
|
-
return "#06b6d4";
|
|
49
|
-
case "company":
|
|
50
|
-
return "#a855f7";
|
|
51
|
-
case "organization":
|
|
52
|
-
return "#a855f7";
|
|
53
|
-
case "concept":
|
|
54
|
-
return "#eab308";
|
|
55
|
-
case "location":
|
|
56
|
-
return "#14b8a6";
|
|
57
|
-
default:
|
|
58
|
-
return "#94a3b8";
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
18
|
function getMemoryKindColor(kind: string): string {
|
|
63
19
|
switch (kind) {
|
|
64
|
-
case "
|
|
20
|
+
case "identity":
|
|
65
21
|
return "#8b5cf6";
|
|
66
22
|
case "preference":
|
|
67
23
|
return "#3b82f6";
|
|
24
|
+
case "project":
|
|
25
|
+
return "#10b981";
|
|
26
|
+
case "decision":
|
|
27
|
+
return "#f59e0b";
|
|
68
28
|
case "constraint":
|
|
69
29
|
return "#ef4444";
|
|
70
|
-
case "
|
|
71
|
-
return "#f59e0b";
|
|
72
|
-
case "style":
|
|
30
|
+
case "event":
|
|
73
31
|
return "#ec4899";
|
|
74
32
|
default:
|
|
75
33
|
return "#94a3b8";
|
|
@@ -80,27 +38,6 @@ export function handleGetBrainGraph(): Response {
|
|
|
80
38
|
try {
|
|
81
39
|
const db = getDb();
|
|
82
40
|
|
|
83
|
-
const entityRows = db
|
|
84
|
-
.select({
|
|
85
|
-
id: memoryEntities.id,
|
|
86
|
-
name: memoryEntities.name,
|
|
87
|
-
type: memoryEntities.type,
|
|
88
|
-
mentionCount: memoryEntities.mentionCount,
|
|
89
|
-
firstSeenAt: memoryEntities.firstSeenAt,
|
|
90
|
-
lastSeenAt: memoryEntities.lastSeenAt,
|
|
91
|
-
})
|
|
92
|
-
.from(memoryEntities)
|
|
93
|
-
.all();
|
|
94
|
-
|
|
95
|
-
const relationRows = db
|
|
96
|
-
.select({
|
|
97
|
-
sourceEntityId: memoryEntityRelations.sourceEntityId,
|
|
98
|
-
targetEntityId: memoryEntityRelations.targetEntityId,
|
|
99
|
-
relation: memoryEntityRelations.relation,
|
|
100
|
-
})
|
|
101
|
-
.from(memoryEntityRelations)
|
|
102
|
-
.all();
|
|
103
|
-
|
|
104
41
|
const kindCountRows = db
|
|
105
42
|
.select({
|
|
106
43
|
kind: memoryItems.kind,
|
|
@@ -110,23 +47,6 @@ export function handleGetBrainGraph(): Response {
|
|
|
110
47
|
.groupBy(memoryItems.kind)
|
|
111
48
|
.all();
|
|
112
49
|
|
|
113
|
-
const entities = entityRows.map((entity) => ({
|
|
114
|
-
id: entity.id,
|
|
115
|
-
name: entity.name,
|
|
116
|
-
type: entity.type,
|
|
117
|
-
lobeRegion: getLobeRegion(entity.type),
|
|
118
|
-
color: getEntityColor(entity.type),
|
|
119
|
-
mentionCount: entity.mentionCount,
|
|
120
|
-
firstSeenAt: entity.firstSeenAt,
|
|
121
|
-
lastSeenAt: entity.lastSeenAt,
|
|
122
|
-
}));
|
|
123
|
-
|
|
124
|
-
const relations = relationRows.map((rel) => ({
|
|
125
|
-
sourceId: rel.sourceEntityId,
|
|
126
|
-
targetId: rel.targetEntityId,
|
|
127
|
-
relation: rel.relation,
|
|
128
|
-
}));
|
|
129
|
-
|
|
130
50
|
const memorySummary = kindCountRows.map((row) => ({
|
|
131
51
|
kind: row.kind,
|
|
132
52
|
count: row.count,
|
|
@@ -139,8 +59,8 @@ export function handleGetBrainGraph(): Response {
|
|
|
139
59
|
);
|
|
140
60
|
|
|
141
61
|
return Response.json({
|
|
142
|
-
entities,
|
|
143
|
-
relations,
|
|
62
|
+
entities: [],
|
|
63
|
+
relations: [],
|
|
144
64
|
memorySummary,
|
|
145
65
|
totalKnowledgeCount,
|
|
146
66
|
generatedAt: new Date().toISOString(),
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { buildToolDefinitions } from "../../daemon/session-tool-setup.js";
|
|
12
|
-
import {
|
|
12
|
+
import { getConversationByKey } from "../../memory/conversation-key-store.js";
|
|
13
13
|
import {
|
|
14
14
|
createTimeout,
|
|
15
15
|
userMessage,
|
|
@@ -53,10 +53,15 @@ async function handleBtw(
|
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
)
|
|
56
|
+
// Look up an existing conversation — never create one. BTW is ephemeral
|
|
57
|
+
// (the file header promises "No messages are persisted"), so we must not
|
|
58
|
+
// call getOrCreateConversation which would insert a DB row. When no
|
|
59
|
+
// conversation exists (e.g. greeting generation for a draft thread), we
|
|
60
|
+
// still get a usable session via getOrCreateSession with the raw key; the
|
|
61
|
+
// session lives only in memory and disappears on restart.
|
|
62
|
+
const mapping = getConversationByKey(conversationKey);
|
|
63
|
+
const sessionId = mapping?.conversationId ?? conversationKey;
|
|
64
|
+
const session = await deps.sendMessageDeps.getOrCreateSession(sessionId);
|
|
60
65
|
|
|
61
66
|
const messages = [...session.getMessages(), userMessage(content.trim())];
|
|
62
67
|
const tools = buildToolDefinitions();
|
|
@@ -315,16 +315,53 @@ export function handleListMessages(
|
|
|
315
315
|
let prevAssistantTimestamp = 0;
|
|
316
316
|
const messages: RuntimeMessagePayload[] = parsed.map((m) => {
|
|
317
317
|
let msgAttachments: RuntimeAttachmentMetadata[] = [];
|
|
318
|
-
if (m.
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
318
|
+
if (m.id) {
|
|
319
|
+
if (m.role === "user") {
|
|
320
|
+
// Use metadata-only query first to avoid loading large base64
|
|
321
|
+
// blobs for non-image attachments (documents, audio). Then
|
|
322
|
+
// selectively fetch full data only for images so the client can
|
|
323
|
+
// generate thumbnails for inline display on history restore.
|
|
324
|
+
const linked = attachmentsStore.getAttachmentMetadataForMessage(m.id);
|
|
325
|
+
if (linked.length > 0) {
|
|
326
|
+
msgAttachments = linked.map((a) => {
|
|
327
|
+
if (a.mimeType.startsWith("image/")) {
|
|
328
|
+
const full = attachmentsStore.getAttachmentById(a.id);
|
|
329
|
+
return {
|
|
330
|
+
id: a.id,
|
|
331
|
+
filename: a.originalFilename,
|
|
332
|
+
mimeType: a.mimeType,
|
|
333
|
+
sizeBytes: a.sizeBytes,
|
|
334
|
+
kind: a.kind,
|
|
335
|
+
...(full?.dataBase64 ? { data: full.dataBase64 } : {}),
|
|
336
|
+
...(a.thumbnailBase64
|
|
337
|
+
? { thumbnailData: a.thumbnailBase64 }
|
|
338
|
+
: {}),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
id: a.id,
|
|
343
|
+
filename: a.originalFilename,
|
|
344
|
+
mimeType: a.mimeType,
|
|
345
|
+
sizeBytes: a.sizeBytes,
|
|
346
|
+
kind: a.kind,
|
|
347
|
+
...(a.thumbnailBase64
|
|
348
|
+
? { thumbnailData: a.thumbnailBase64 }
|
|
349
|
+
: {}),
|
|
350
|
+
};
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
const linked = attachmentsStore.getAttachmentMetadataForMessage(m.id);
|
|
355
|
+
if (linked.length > 0) {
|
|
356
|
+
msgAttachments = linked.map((a) => ({
|
|
357
|
+
id: a.id,
|
|
358
|
+
filename: a.originalFilename,
|
|
359
|
+
mimeType: a.mimeType,
|
|
360
|
+
sizeBytes: a.sizeBytes,
|
|
361
|
+
kind: a.kind,
|
|
362
|
+
...(a.thumbnailBase64 ? { thumbnailData: a.thumbnailBase64 } : {}),
|
|
363
|
+
}));
|
|
364
|
+
}
|
|
328
365
|
}
|
|
329
366
|
}
|
|
330
367
|
|
|
@@ -648,9 +685,17 @@ export async function handleSendMessage(
|
|
|
648
685
|
session.setHostFileProxy(fileProxy);
|
|
649
686
|
}
|
|
650
687
|
if (!session.isProcessing() || !session.hostCuProxy) {
|
|
651
|
-
const cuProxy = new HostCuProxy(onEvent)
|
|
688
|
+
const cuProxy = new HostCuProxy(onEvent, (requestId) => {
|
|
689
|
+
pendingInteractions.resolve(requestId);
|
|
690
|
+
});
|
|
652
691
|
session.setHostCuProxy(cuProxy);
|
|
653
692
|
}
|
|
693
|
+
// Only preactivate CU when the session is idle — if the session is
|
|
694
|
+
// processing, this message will be queued and preactivation is deferred
|
|
695
|
+
// to dequeue time in drainQueueImpl to avoid mutating in-flight turn state.
|
|
696
|
+
if (!session.isProcessing()) {
|
|
697
|
+
session.addPreactivatedSkillId("computer-use");
|
|
698
|
+
}
|
|
654
699
|
} else if (!session.isProcessing()) {
|
|
655
700
|
session.setHostBashProxy(undefined);
|
|
656
701
|
session.setHostFileProxy(undefined);
|
|
@@ -334,7 +334,7 @@ export async function enforceIngressAcl(
|
|
|
334
334
|
dmCallbackUrl,
|
|
335
335
|
{
|
|
336
336
|
chatId: senderUserId,
|
|
337
|
-
text: "I've notified the owner.
|
|
337
|
+
text: "I've notified the owner that you'd like to chat with me. If they approve your request, they'll share a 6-digit verification code with you. You can reply with the code here.",
|
|
338
338
|
assistantId,
|
|
339
339
|
},
|
|
340
340
|
mintBearerToken(),
|
|
@@ -423,11 +423,12 @@ export async function enforceIngressAcl(
|
|
|
423
423
|
|
|
424
424
|
if (resolvedMember) {
|
|
425
425
|
if (resolvedMember.channel.status !== "active") {
|
|
426
|
+
const isBlockedMember = resolvedMember.channel.status === "blocked";
|
|
426
427
|
// Same bypass logic as the no-member branch: verification codes and
|
|
427
|
-
// bootstrap commands must pass through
|
|
428
|
-
// revoked
|
|
428
|
+
// bootstrap commands must pass through for re-verifiable states
|
|
429
|
+
// (pending/revoked), but never for blocked members.
|
|
429
430
|
let denyInactiveMember = true;
|
|
430
|
-
if (isGuardianVerifyCode) {
|
|
431
|
+
if (!isBlockedMember && isGuardianVerifyCode) {
|
|
431
432
|
const hasPendingChallenge = !!getPendingSession(sourceChannel);
|
|
432
433
|
const hasActiveOutboundSession = !!findActiveSession(sourceChannel);
|
|
433
434
|
if (hasPendingChallenge || hasActiveOutboundSession) {
|
|
@@ -444,7 +445,7 @@ export async function enforceIngressAcl(
|
|
|
444
445
|
);
|
|
445
446
|
}
|
|
446
447
|
}
|
|
447
|
-
if (isBootstrapCommand) {
|
|
448
|
+
if (!isBlockedMember && isBootstrapCommand) {
|
|
448
449
|
const bootstrapPayload = (
|
|
449
450
|
rawCommandIntentForAcl as Record<string, unknown>
|
|
450
451
|
).payload as string;
|
|
@@ -471,9 +472,11 @@ export async function enforceIngressAcl(
|
|
|
471
472
|
}
|
|
472
473
|
|
|
473
474
|
// ── Invite token intercept (inactive member) ──
|
|
474
|
-
//
|
|
475
|
-
//
|
|
476
|
-
|
|
475
|
+
// Invite tokens can reactivate revoked/pending members without
|
|
476
|
+
// requiring guardian approval, but blocked members are excluded so
|
|
477
|
+
// they are short-circuited at the ACL layer rather than entering the
|
|
478
|
+
// redemption path.
|
|
479
|
+
if (!isBlockedMember && inviteToken && denyInactiveMember) {
|
|
477
480
|
const inviteResult = await handleInviteTokenIntercept({
|
|
478
481
|
rawToken: inviteToken,
|
|
479
482
|
sourceChannel,
|
|
@@ -496,9 +499,15 @@ export async function enforceIngressAcl(
|
|
|
496
499
|
}
|
|
497
500
|
|
|
498
501
|
// ── 6-digit invite code intercept (inactive member) ──
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
|
|
502
|
+
// Codes can reactivate revoked/pending members; non-matching codes
|
|
503
|
+
// fall through. Blocked members are excluded here for consistency —
|
|
504
|
+
// the redemption service would reject them anyway, but early exit
|
|
505
|
+
// avoids unnecessary work.
|
|
506
|
+
if (
|
|
507
|
+
!isBlockedMember &&
|
|
508
|
+
denyInactiveMember &&
|
|
509
|
+
/^\d{6}$/.test(trimmedContent)
|
|
510
|
+
) {
|
|
502
511
|
const codeInterceptResult = await handleInviteCodeIntercept({
|
|
503
512
|
code: trimmedContent,
|
|
504
513
|
sourceChannel,
|
|
@@ -579,7 +588,7 @@ export async function enforceIngressAcl(
|
|
|
579
588
|
dmCallbackUrl,
|
|
580
589
|
{
|
|
581
590
|
chatId: senderUserId,
|
|
582
|
-
text: "I've notified the owner.
|
|
591
|
+
text: "I've notified the owner that you'd like to chat with me. If they approve your request, they'll share a 6-digit verification code with you. You can reply with the code here.",
|
|
583
592
|
assistantId,
|
|
584
593
|
},
|
|
585
594
|
mintBearerToken(),
|
|
@@ -20,8 +20,8 @@ import type { RouteDefinition } from "../../../http-router.js";
|
|
|
20
20
|
/**
|
|
21
21
|
* GET /v1/integrations/slack/channel/config
|
|
22
22
|
*/
|
|
23
|
-
export function handleGetSlackChannelConfig(): Response {
|
|
24
|
-
const result = getSlackChannelConfig();
|
|
23
|
+
export async function handleGetSlackChannelConfig(): Promise<Response> {
|
|
24
|
+
const result = await getSlackChannelConfig();
|
|
25
25
|
return Response.json(result);
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -20,8 +20,8 @@ import type { RouteDefinition } from "../../http-router.js";
|
|
|
20
20
|
/**
|
|
21
21
|
* GET /v1/integrations/telegram/config
|
|
22
22
|
*/
|
|
23
|
-
export function handleGetTelegramConfig(): Response {
|
|
24
|
-
const result = getTelegramConfig();
|
|
23
|
+
export async function handleGetTelegramConfig(): Promise<Response> {
|
|
24
|
+
const result = await getTelegramConfig();
|
|
25
25
|
return Response.json(result);
|
|
26
26
|
}
|
|
27
27
|
|