@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
|
@@ -12,21 +12,17 @@ import {
|
|
|
12
12
|
} from '../../memory/channel-guardian-store.js';
|
|
13
13
|
import { addMessage, getMessages } from '../../memory/conversation-store.js';
|
|
14
14
|
import { getBindingByConversation } from '../../memory/external-conversation-store.js';
|
|
15
|
-
import {
|
|
16
|
-
createInvite,
|
|
17
|
-
type InviteStatus,
|
|
18
|
-
listInvites,
|
|
19
|
-
redeemInvite,
|
|
20
|
-
revokeInvite,
|
|
21
|
-
} from '../../memory/ingress-invite-store.js';
|
|
22
|
-
import {
|
|
23
|
-
blockMember,
|
|
24
|
-
type IngressMember,
|
|
25
|
-
listMembers,
|
|
26
|
-
revokeMember,
|
|
27
|
-
upsertMember,
|
|
28
|
-
} from '../../memory/ingress-member-store.js';
|
|
29
15
|
import { deliverChannelReply } from '../../runtime/gateway-client.js';
|
|
16
|
+
import {
|
|
17
|
+
blockIngressMember,
|
|
18
|
+
createIngressInvite,
|
|
19
|
+
listIngressInvites,
|
|
20
|
+
listIngressMembers,
|
|
21
|
+
redeemIngressInvite,
|
|
22
|
+
revokeIngressInvite,
|
|
23
|
+
revokeIngressMember,
|
|
24
|
+
upsertIngressMember,
|
|
25
|
+
} from '../../runtime/ingress-service.js';
|
|
30
26
|
import type { AssistantInboxEscalationRequest, IngressInviteRequest, IngressMemberRequest } from '../ipc-protocol.js';
|
|
31
27
|
import { defineHandlers, type HandlerContext, log } from './shared.js';
|
|
32
28
|
import { renderHistoryContent } from './shared.js';
|
|
@@ -39,121 +35,60 @@ export function handleIngressInvite(
|
|
|
39
35
|
try {
|
|
40
36
|
switch (msg.action) {
|
|
41
37
|
case 'create': {
|
|
42
|
-
|
|
43
|
-
ctx.send(socket, { type: 'ingress_invite_response', success: false, error: 'sourceChannel is required for create' });
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
const { invite, rawToken } = createInvite({
|
|
38
|
+
const result = createIngressInvite({
|
|
47
39
|
sourceChannel: msg.sourceChannel,
|
|
48
40
|
note: msg.note,
|
|
49
41
|
maxUses: msg.maxUses,
|
|
50
42
|
expiresInMs: msg.expiresInMs,
|
|
51
43
|
});
|
|
52
|
-
|
|
53
|
-
type: 'ingress_invite_response',
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
sourceChannel: invite.sourceChannel,
|
|
58
|
-
token: rawToken,
|
|
59
|
-
tokenHash: invite.tokenHash,
|
|
60
|
-
maxUses: invite.maxUses,
|
|
61
|
-
useCount: invite.useCount,
|
|
62
|
-
expiresAt: invite.expiresAt,
|
|
63
|
-
status: invite.status,
|
|
64
|
-
note: invite.note ?? undefined,
|
|
65
|
-
createdAt: invite.createdAt,
|
|
66
|
-
},
|
|
67
|
-
});
|
|
44
|
+
if (!result.ok) {
|
|
45
|
+
ctx.send(socket, { type: 'ingress_invite_response', success: false, error: result.error });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
ctx.send(socket, { type: 'ingress_invite_response', success: true, invite: result.data });
|
|
68
49
|
return;
|
|
69
50
|
}
|
|
70
51
|
|
|
71
52
|
case 'list': {
|
|
72
|
-
const
|
|
53
|
+
const result = listIngressInvites({
|
|
73
54
|
sourceChannel: msg.sourceChannel,
|
|
74
|
-
status: msg.status
|
|
75
|
-
});
|
|
76
|
-
ctx.send(socket, {
|
|
77
|
-
type: 'ingress_invite_response',
|
|
78
|
-
success: true,
|
|
79
|
-
invites: invites.map((inv) => ({
|
|
80
|
-
id: inv.id,
|
|
81
|
-
sourceChannel: inv.sourceChannel,
|
|
82
|
-
tokenHash: inv.tokenHash,
|
|
83
|
-
maxUses: inv.maxUses,
|
|
84
|
-
useCount: inv.useCount,
|
|
85
|
-
expiresAt: inv.expiresAt,
|
|
86
|
-
status: inv.status,
|
|
87
|
-
note: inv.note ?? undefined,
|
|
88
|
-
createdAt: inv.createdAt,
|
|
89
|
-
})),
|
|
55
|
+
status: msg.status,
|
|
90
56
|
});
|
|
57
|
+
if (!result.ok) {
|
|
58
|
+
ctx.send(socket, { type: 'ingress_invite_response', success: false, error: result.error });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
ctx.send(socket, { type: 'ingress_invite_response', success: true, invites: result.data });
|
|
91
62
|
return;
|
|
92
63
|
}
|
|
93
64
|
|
|
94
65
|
case 'revoke': {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
const revoked = revokeInvite(msg.inviteId);
|
|
100
|
-
if (!revoked) {
|
|
101
|
-
ctx.send(socket, { type: 'ingress_invite_response', success: false, error: 'Invite not found or already revoked' });
|
|
66
|
+
const result = revokeIngressInvite(msg.inviteId);
|
|
67
|
+
if (!result.ok) {
|
|
68
|
+
ctx.send(socket, { type: 'ingress_invite_response', success: false, error: result.error });
|
|
102
69
|
return;
|
|
103
70
|
}
|
|
104
|
-
ctx.send(socket, {
|
|
105
|
-
type: 'ingress_invite_response',
|
|
106
|
-
success: true,
|
|
107
|
-
invite: {
|
|
108
|
-
id: revoked.id,
|
|
109
|
-
sourceChannel: revoked.sourceChannel,
|
|
110
|
-
tokenHash: revoked.tokenHash,
|
|
111
|
-
maxUses: revoked.maxUses,
|
|
112
|
-
useCount: revoked.useCount,
|
|
113
|
-
expiresAt: revoked.expiresAt,
|
|
114
|
-
status: revoked.status,
|
|
115
|
-
note: revoked.note ?? undefined,
|
|
116
|
-
createdAt: revoked.createdAt,
|
|
117
|
-
},
|
|
118
|
-
});
|
|
71
|
+
ctx.send(socket, { type: 'ingress_invite_response', success: true, invite: result.data });
|
|
119
72
|
return;
|
|
120
73
|
}
|
|
121
74
|
|
|
122
75
|
case 'redeem': {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
const result = redeemInvite({
|
|
128
|
-
rawToken: msg.token,
|
|
76
|
+
const result = redeemIngressInvite({
|
|
77
|
+
token: msg.token,
|
|
129
78
|
externalUserId: msg.externalUserId,
|
|
130
79
|
externalChatId: msg.externalChatId,
|
|
131
80
|
sourceChannel: msg.sourceChannel,
|
|
132
81
|
});
|
|
133
|
-
if (
|
|
82
|
+
if (!result.ok) {
|
|
134
83
|
ctx.send(socket, { type: 'ingress_invite_response', success: false, error: result.error });
|
|
135
84
|
return;
|
|
136
85
|
}
|
|
137
|
-
ctx.send(socket, {
|
|
138
|
-
type: 'ingress_invite_response',
|
|
139
|
-
success: true,
|
|
140
|
-
invite: {
|
|
141
|
-
id: result.invite.id,
|
|
142
|
-
sourceChannel: result.invite.sourceChannel,
|
|
143
|
-
tokenHash: result.invite.tokenHash,
|
|
144
|
-
maxUses: result.invite.maxUses,
|
|
145
|
-
useCount: result.invite.useCount,
|
|
146
|
-
expiresAt: result.invite.expiresAt,
|
|
147
|
-
status: result.invite.status,
|
|
148
|
-
note: result.invite.note ?? undefined,
|
|
149
|
-
createdAt: result.invite.createdAt,
|
|
150
|
-
},
|
|
151
|
-
});
|
|
86
|
+
ctx.send(socket, { type: 'ingress_invite_response', success: true, invite: result.data });
|
|
152
87
|
return;
|
|
153
88
|
}
|
|
154
89
|
|
|
155
90
|
default: {
|
|
156
|
-
ctx.send(socket, { type: 'ingress_invite_response', success: false, error: `Unknown action: ${String(
|
|
91
|
+
ctx.send(socket, { type: 'ingress_invite_response', success: false, error: `Unknown action: ${String(msg.action)}` });
|
|
157
92
|
}
|
|
158
93
|
}
|
|
159
94
|
} catch (err) {
|
|
@@ -163,21 +98,6 @@ export function handleIngressInvite(
|
|
|
163
98
|
}
|
|
164
99
|
}
|
|
165
100
|
|
|
166
|
-
function memberToResponse(m: IngressMember) {
|
|
167
|
-
return {
|
|
168
|
-
id: m.id,
|
|
169
|
-
sourceChannel: m.sourceChannel,
|
|
170
|
-
externalUserId: m.externalUserId ?? undefined,
|
|
171
|
-
externalChatId: m.externalChatId ?? undefined,
|
|
172
|
-
displayName: m.displayName ?? undefined,
|
|
173
|
-
username: m.username ?? undefined,
|
|
174
|
-
status: m.status,
|
|
175
|
-
policy: m.policy,
|
|
176
|
-
lastSeenAt: m.lastSeenAt ?? undefined,
|
|
177
|
-
createdAt: m.createdAt,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
101
|
export function handleIngressMember(
|
|
182
102
|
msg: IngressMemberRequest,
|
|
183
103
|
socket: net.Socket,
|
|
@@ -186,30 +106,22 @@ export function handleIngressMember(
|
|
|
186
106
|
try {
|
|
187
107
|
switch (msg.action) {
|
|
188
108
|
case 'list': {
|
|
189
|
-
const
|
|
109
|
+
const result = listIngressMembers({
|
|
190
110
|
assistantId: msg.assistantId,
|
|
191
111
|
sourceChannel: msg.sourceChannel,
|
|
192
112
|
status: msg.status,
|
|
193
113
|
policy: msg.policy,
|
|
194
114
|
});
|
|
195
|
-
|
|
196
|
-
type: 'ingress_member_response',
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
});
|
|
115
|
+
if (!result.ok) {
|
|
116
|
+
ctx.send(socket, { type: 'ingress_member_response', success: false, error: result.error });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
ctx.send(socket, { type: 'ingress_member_response', success: true, members: result.data });
|
|
200
120
|
return;
|
|
201
121
|
}
|
|
202
122
|
|
|
203
123
|
case 'upsert': {
|
|
204
|
-
|
|
205
|
-
ctx.send(socket, { type: 'ingress_member_response', success: false, error: 'sourceChannel is required for upsert' });
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (!msg.externalUserId && !msg.externalChatId) {
|
|
209
|
-
ctx.send(socket, { type: 'ingress_member_response', success: false, error: 'At least one of externalUserId or externalChatId is required for upsert' });
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
const member = upsertMember({
|
|
124
|
+
const result = upsertIngressMember({
|
|
213
125
|
assistantId: msg.assistantId,
|
|
214
126
|
sourceChannel: msg.sourceChannel,
|
|
215
127
|
externalUserId: msg.externalUserId,
|
|
@@ -219,52 +131,36 @@ export function handleIngressMember(
|
|
|
219
131
|
policy: msg.policy,
|
|
220
132
|
status: msg.status,
|
|
221
133
|
});
|
|
222
|
-
|
|
223
|
-
type: 'ingress_member_response',
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
});
|
|
134
|
+
if (!result.ok) {
|
|
135
|
+
ctx.send(socket, { type: 'ingress_member_response', success: false, error: result.error });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
ctx.send(socket, { type: 'ingress_member_response', success: true, member: result.data });
|
|
227
139
|
return;
|
|
228
140
|
}
|
|
229
141
|
|
|
230
142
|
case 'revoke': {
|
|
231
|
-
|
|
232
|
-
|
|
143
|
+
const result = revokeIngressMember(msg.memberId, msg.reason);
|
|
144
|
+
if (!result.ok) {
|
|
145
|
+
ctx.send(socket, { type: 'ingress_member_response', success: false, error: result.error });
|
|
233
146
|
return;
|
|
234
147
|
}
|
|
235
|
-
|
|
236
|
-
if (!revoked) {
|
|
237
|
-
ctx.send(socket, { type: 'ingress_member_response', success: false, error: 'Member not found or cannot be revoked' });
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
ctx.send(socket, {
|
|
241
|
-
type: 'ingress_member_response',
|
|
242
|
-
success: true,
|
|
243
|
-
member: memberToResponse(revoked),
|
|
244
|
-
});
|
|
148
|
+
ctx.send(socket, { type: 'ingress_member_response', success: true, member: result.data });
|
|
245
149
|
return;
|
|
246
150
|
}
|
|
247
151
|
|
|
248
152
|
case 'block': {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
const blocked = blockMember(msg.memberId, msg.reason);
|
|
254
|
-
if (!blocked) {
|
|
255
|
-
ctx.send(socket, { type: 'ingress_member_response', success: false, error: 'Member not found or already blocked' });
|
|
153
|
+
const result = blockIngressMember(msg.memberId, msg.reason);
|
|
154
|
+
if (!result.ok) {
|
|
155
|
+
ctx.send(socket, { type: 'ingress_member_response', success: false, error: result.error });
|
|
256
156
|
return;
|
|
257
157
|
}
|
|
258
|
-
ctx.send(socket, {
|
|
259
|
-
type: 'ingress_member_response',
|
|
260
|
-
success: true,
|
|
261
|
-
member: memberToResponse(blocked),
|
|
262
|
-
});
|
|
158
|
+
ctx.send(socket, { type: 'ingress_member_response', success: true, member: result.data });
|
|
263
159
|
return;
|
|
264
160
|
}
|
|
265
161
|
|
|
266
162
|
default: {
|
|
267
|
-
ctx.send(socket, { type: 'ingress_member_response', success: false, error: `Unknown action: ${String(
|
|
163
|
+
ctx.send(socket, { type: 'ingress_member_response', success: false, error: `Unknown action: ${String(msg.action)}` });
|
|
268
164
|
}
|
|
269
165
|
}
|
|
270
166
|
} catch (err) {
|
|
@@ -342,7 +238,7 @@ export function handleInboxEscalation(
|
|
|
342
238
|
}
|
|
343
239
|
|
|
344
240
|
default: {
|
|
345
|
-
ctx.send(socket, { type: 'assistant_inbox_escalation_response', success: false, error: `Unknown action: ${String(
|
|
241
|
+
ctx.send(socket, { type: 'assistant_inbox_escalation_response', success: false, error: `Unknown action: ${String(msg.action)}` });
|
|
346
242
|
}
|
|
347
243
|
}
|
|
348
244
|
} catch (err) {
|
|
@@ -482,7 +378,7 @@ async function executeDeny(
|
|
|
482
378
|
|
|
483
379
|
// Store a system note about the denial in the conversation
|
|
484
380
|
const denialInterface = isInterfaceId(sourceChannel) ? sourceChannel : undefined;
|
|
485
|
-
addMessage(conversationId, 'assistant', denialText, {
|
|
381
|
+
await addMessage(conversationId, 'assistant', denialText, {
|
|
486
382
|
provenanceActorRole: 'guardian' as const,
|
|
487
383
|
userMessageChannel: sourceChannel,
|
|
488
384
|
assistantMessageChannel: sourceChannel,
|
|
@@ -219,7 +219,7 @@ export async function handleIngressConfig(
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
} else {
|
|
222
|
-
ctx.send(socket, { type: 'ingress_config_response', enabled: false, publicBaseUrl: '', localGatewayTarget, success: false, error: `Unknown action: ${String(
|
|
222
|
+
ctx.send(socket, { type: 'ingress_config_response', enabled: false, publicBaseUrl: '', localGatewayTarget, success: false, error: `Unknown action: ${String(msg.action)}` });
|
|
223
223
|
}
|
|
224
224
|
} catch (err) {
|
|
225
225
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -258,7 +258,7 @@ export function handleTwitterIntegrationConfig(
|
|
|
258
258
|
managedAvailable: false,
|
|
259
259
|
localClientConfigured: false,
|
|
260
260
|
connected: false,
|
|
261
|
-
error: `Unknown action: ${String(
|
|
261
|
+
error: `Unknown action: ${String(msg.action)}`,
|
|
262
262
|
});
|
|
263
263
|
}
|
|
264
264
|
} catch (err) {
|
|
@@ -38,7 +38,7 @@ export async function handlePlatformConfig(
|
|
|
38
38
|
type: 'platform_config_response',
|
|
39
39
|
baseUrl: '',
|
|
40
40
|
success: false,
|
|
41
|
-
error: `Unknown action: ${String(
|
|
41
|
+
error: `Unknown action: ${String(msg.action)}`,
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
} catch (err) {
|
|
@@ -84,10 +84,10 @@ export async function handleScheduleRunNow(
|
|
|
84
84
|
log.info({ jobId: schedule.id, name: schedule.name, taskId }, 'Executing scheduled task manually (run now)');
|
|
85
85
|
const { runTask } = await import('../../tasks/task-runner.js');
|
|
86
86
|
const result = await runTask(
|
|
87
|
-
{ taskId, workingDir: process.cwd() },
|
|
87
|
+
{ taskId, workingDir: process.cwd(), source: 'schedule' },
|
|
88
88
|
async (conversationId, message, taskRunId) => {
|
|
89
89
|
const session = await ctx.getOrCreateSession(conversationId, socket, true);
|
|
90
|
-
|
|
90
|
+
session.taskRunId = taskRunId;
|
|
91
91
|
await session.processMessage(message, [], (event) => {
|
|
92
92
|
ctx.send(socket, event);
|
|
93
93
|
}, undefined, undefined, undefined, { isInteractive: false });
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { deleteSecureKey, getSecureKey, setSecureKey } from '../../security/secure-keys.js';
|
|
2
|
+
import { deleteCredentialMetadata, getCredentialMetadata, upsertCredentialMetadata } from '../../tools/credentials/metadata-store.js';
|
|
3
|
+
import { log } from './shared.js';
|
|
4
|
+
|
|
5
|
+
// -- Result type --
|
|
6
|
+
|
|
7
|
+
export interface SlackChannelConfigResult {
|
|
8
|
+
success: boolean;
|
|
9
|
+
hasBotToken: boolean;
|
|
10
|
+
hasAppToken: boolean;
|
|
11
|
+
connected: boolean;
|
|
12
|
+
teamId?: string;
|
|
13
|
+
teamName?: string;
|
|
14
|
+
botUserId?: string;
|
|
15
|
+
botUsername?: string;
|
|
16
|
+
error?: string;
|
|
17
|
+
warning?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// -- Metadata stored as JSON in accountInfo --
|
|
21
|
+
|
|
22
|
+
interface SlackChannelMetadata {
|
|
23
|
+
teamId?: string;
|
|
24
|
+
teamName?: string;
|
|
25
|
+
botUserId?: string;
|
|
26
|
+
botUsername?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseMetadata(raw: string | undefined): SlackChannelMetadata {
|
|
30
|
+
if (!raw) return {};
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(raw) as SlackChannelMetadata;
|
|
33
|
+
} catch {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// -- Business logic --
|
|
39
|
+
|
|
40
|
+
export function getSlackChannelConfig(): SlackChannelConfigResult {
|
|
41
|
+
const hasBotToken = !!getSecureKey('credential:slack_channel:bot_token');
|
|
42
|
+
const hasAppToken = !!getSecureKey('credential:slack_channel:app_token');
|
|
43
|
+
const meta = getCredentialMetadata('slack_channel', 'bot_token');
|
|
44
|
+
const metadata = parseMetadata(meta?.accountInfo);
|
|
45
|
+
return {
|
|
46
|
+
success: true,
|
|
47
|
+
hasBotToken,
|
|
48
|
+
hasAppToken,
|
|
49
|
+
connected: hasBotToken && hasAppToken,
|
|
50
|
+
...metadata,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function setSlackChannelConfig(
|
|
55
|
+
botToken?: string,
|
|
56
|
+
appToken?: string,
|
|
57
|
+
): Promise<SlackChannelConfigResult> {
|
|
58
|
+
let metadata: SlackChannelMetadata = {};
|
|
59
|
+
let warning: string | undefined;
|
|
60
|
+
|
|
61
|
+
// Validate and store bot token
|
|
62
|
+
if (botToken) {
|
|
63
|
+
// Validate bot token by calling Slack auth.test
|
|
64
|
+
try {
|
|
65
|
+
const res = await fetch('https://slack.com/api/auth.test', {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: { Authorization: `Bearer ${botToken}` },
|
|
68
|
+
});
|
|
69
|
+
const data = (await res.json()) as {
|
|
70
|
+
ok: boolean;
|
|
71
|
+
error?: string;
|
|
72
|
+
team_id?: string;
|
|
73
|
+
team?: string;
|
|
74
|
+
user_id?: string;
|
|
75
|
+
user?: string;
|
|
76
|
+
};
|
|
77
|
+
if (!data.ok) {
|
|
78
|
+
const storedBotToken = !!getSecureKey('credential:slack_channel:bot_token');
|
|
79
|
+
const storedAppToken = !!getSecureKey('credential:slack_channel:app_token');
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
hasBotToken: storedBotToken,
|
|
83
|
+
hasAppToken: storedAppToken,
|
|
84
|
+
connected: storedBotToken && storedAppToken,
|
|
85
|
+
error: `Slack API validation failed: ${data.error ?? 'unknown error'}`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
metadata = {
|
|
89
|
+
teamId: data.team_id,
|
|
90
|
+
teamName: data.team,
|
|
91
|
+
botUserId: data.user_id,
|
|
92
|
+
botUsername: data.user,
|
|
93
|
+
};
|
|
94
|
+
} catch (err) {
|
|
95
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
96
|
+
const storedBotToken = !!getSecureKey('credential:slack_channel:bot_token');
|
|
97
|
+
const storedAppToken = !!getSecureKey('credential:slack_channel:app_token');
|
|
98
|
+
return {
|
|
99
|
+
success: false,
|
|
100
|
+
hasBotToken: storedBotToken,
|
|
101
|
+
hasAppToken: storedAppToken,
|
|
102
|
+
connected: storedBotToken && storedAppToken,
|
|
103
|
+
error: `Failed to validate bot token: ${message}`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const stored = setSecureKey('credential:slack_channel:bot_token', botToken);
|
|
108
|
+
if (!stored) {
|
|
109
|
+
const storedBotToken = !!getSecureKey('credential:slack_channel:bot_token');
|
|
110
|
+
const storedAppToken = !!getSecureKey('credential:slack_channel:app_token');
|
|
111
|
+
return {
|
|
112
|
+
success: false,
|
|
113
|
+
hasBotToken: storedBotToken,
|
|
114
|
+
hasAppToken: storedAppToken,
|
|
115
|
+
connected: storedBotToken && storedAppToken,
|
|
116
|
+
error: 'Failed to store bot token in secure storage',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
upsertCredentialMetadata('slack_channel', 'bot_token', {
|
|
121
|
+
accountInfo: JSON.stringify(metadata),
|
|
122
|
+
});
|
|
123
|
+
} else {
|
|
124
|
+
// Use existing metadata if no new bot token provided
|
|
125
|
+
const existingMeta = getCredentialMetadata('slack_channel', 'bot_token');
|
|
126
|
+
metadata = parseMetadata(existingMeta?.accountInfo);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Validate and store app token
|
|
130
|
+
if (appToken) {
|
|
131
|
+
if (!appToken.startsWith('xapp-')) {
|
|
132
|
+
const storedBotToken = !!getSecureKey('credential:slack_channel:bot_token');
|
|
133
|
+
const storedAppToken = !!getSecureKey('credential:slack_channel:app_token');
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
hasBotToken: storedBotToken,
|
|
137
|
+
hasAppToken: storedAppToken,
|
|
138
|
+
connected: storedBotToken && storedAppToken,
|
|
139
|
+
error: 'Invalid app token: must start with "xapp-"',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const stored = setSecureKey('credential:slack_channel:app_token', appToken);
|
|
144
|
+
if (!stored) {
|
|
145
|
+
const storedBotToken = !!getSecureKey('credential:slack_channel:bot_token');
|
|
146
|
+
const storedAppToken = !!getSecureKey('credential:slack_channel:app_token');
|
|
147
|
+
return {
|
|
148
|
+
success: false,
|
|
149
|
+
hasBotToken: storedBotToken,
|
|
150
|
+
hasAppToken: storedAppToken,
|
|
151
|
+
connected: storedBotToken && storedAppToken,
|
|
152
|
+
error: 'Failed to store app token in secure storage',
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
upsertCredentialMetadata('slack_channel', 'app_token', {});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const hasBotToken = !!getSecureKey('credential:slack_channel:bot_token');
|
|
160
|
+
const hasAppToken = !!getSecureKey('credential:slack_channel:app_token');
|
|
161
|
+
|
|
162
|
+
if (hasBotToken && !hasAppToken) {
|
|
163
|
+
warning = 'Bot token stored but app token is missing β connection incomplete.';
|
|
164
|
+
} else if (!hasBotToken && hasAppToken) {
|
|
165
|
+
warning = 'App token stored but bot token is missing β connection incomplete.';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
success: true,
|
|
170
|
+
hasBotToken,
|
|
171
|
+
hasAppToken,
|
|
172
|
+
connected: hasBotToken && hasAppToken,
|
|
173
|
+
...metadata,
|
|
174
|
+
...(warning ? { warning } : {}),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function clearSlackChannelConfig(): SlackChannelConfigResult {
|
|
179
|
+
deleteSecureKey('credential:slack_channel:bot_token');
|
|
180
|
+
deleteCredentialMetadata('slack_channel', 'bot_token');
|
|
181
|
+
deleteSecureKey('credential:slack_channel:app_token');
|
|
182
|
+
deleteCredentialMetadata('slack_channel', 'app_token');
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
success: true,
|
|
186
|
+
hasBotToken: false,
|
|
187
|
+
hasAppToken: false,
|
|
188
|
+
connected: false,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
@@ -319,7 +319,7 @@ export async function handleTelegramConfig(
|
|
|
319
319
|
hasBotToken: false,
|
|
320
320
|
connected: false,
|
|
321
321
|
hasWebhookSecret: false,
|
|
322
|
-
error: `Unknown action: ${String(
|
|
322
|
+
error: `Unknown action: ${String(msg.action)}`,
|
|
323
323
|
};
|
|
324
324
|
}
|
|
325
325
|
|
|
@@ -1062,7 +1062,7 @@ export async function handleTwilioConfig(
|
|
|
1062
1062
|
type: 'twilio_config_response',
|
|
1063
1063
|
success: false,
|
|
1064
1064
|
hasCredentials: hasTwilioCredentials(),
|
|
1065
|
-
error: `Unknown action: ${String(
|
|
1065
|
+
error: `Unknown action: ${String(msg.action)}`,
|
|
1066
1066
|
});
|
|
1067
1067
|
}
|
|
1068
1068
|
} catch (err) {
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import * as net from 'node:net';
|
|
2
|
+
|
|
3
|
+
import type { VoiceConfigUpdateRequest } from '../ipc-contract/settings.js';
|
|
4
|
+
import { defineHandlers, type HandlerContext, log } from './shared.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Send a client_settings_update message to all connected clients.
|
|
8
|
+
* Used to push configuration changes (e.g. activation key) from the daemon
|
|
9
|
+
* to macOS/iOS clients so they can apply settings immediately.
|
|
10
|
+
*/
|
|
11
|
+
export function broadcastClientSettingsUpdate(
|
|
12
|
+
key: string,
|
|
13
|
+
value: string,
|
|
14
|
+
ctx: HandlerContext,
|
|
15
|
+
): void {
|
|
16
|
+
ctx.broadcast({
|
|
17
|
+
type: 'client_settings_update',
|
|
18
|
+
key,
|
|
19
|
+
value,
|
|
20
|
+
});
|
|
21
|
+
log.info({ key, value }, 'Broadcast client_settings_update');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ββ Activation key validation ββββββββββββββββββββββββββββββββββββββββ
|
|
25
|
+
|
|
26
|
+
const VALID_ACTIVATION_KEYS = ['fn', 'ctrl', 'fn_shift', 'none'] as const;
|
|
27
|
+
export type ActivationKey = (typeof VALID_ACTIVATION_KEYS)[number];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Map natural-language activation key names to canonical enum values.
|
|
31
|
+
* Case-insensitive matching is applied by the caller.
|
|
32
|
+
*/
|
|
33
|
+
const NATURAL_LANGUAGE_MAP: Record<string, ActivationKey> = {
|
|
34
|
+
fn: 'fn',
|
|
35
|
+
globe: 'fn',
|
|
36
|
+
'fn key': 'fn',
|
|
37
|
+
'globe key': 'fn',
|
|
38
|
+
ctrl: 'ctrl',
|
|
39
|
+
control: 'ctrl',
|
|
40
|
+
'ctrl key': 'ctrl',
|
|
41
|
+
'control key': 'ctrl',
|
|
42
|
+
fn_shift: 'fn_shift',
|
|
43
|
+
'fn+shift': 'fn_shift',
|
|
44
|
+
'fn shift': 'fn_shift',
|
|
45
|
+
'shift+fn': 'fn_shift',
|
|
46
|
+
none: 'none',
|
|
47
|
+
off: 'none',
|
|
48
|
+
disabled: 'none',
|
|
49
|
+
disable: 'none',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Validate and normalise a user-provided activation key string.
|
|
54
|
+
* Accepts both canonical enum values and natural-language variants.
|
|
55
|
+
* Returns the canonical value on success, or an error message on failure.
|
|
56
|
+
*/
|
|
57
|
+
export function normalizeActivationKey(
|
|
58
|
+
input: string,
|
|
59
|
+
): { ok: true; value: ActivationKey } | { ok: false; reason: string } {
|
|
60
|
+
const trimmed = input.trim().toLowerCase();
|
|
61
|
+
|
|
62
|
+
// Direct enum match
|
|
63
|
+
if ((VALID_ACTIVATION_KEYS as readonly string[]).includes(trimmed)) {
|
|
64
|
+
return { ok: true, value: trimmed as ActivationKey };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Natural-language match
|
|
68
|
+
const mapped = NATURAL_LANGUAGE_MAP[trimmed];
|
|
69
|
+
if (mapped) {
|
|
70
|
+
return { ok: true, value: mapped };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
reason: `Invalid activation key "${input}". Valid values: fn (Fn/Globe key), ctrl (Control key), fn_shift (Fn+Shift), none (disable PTT).`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Process a voice configuration update request from a session or IPC client.
|
|
81
|
+
* Validates the activation key and broadcasts the change to all connected clients.
|
|
82
|
+
*/
|
|
83
|
+
export function handleVoiceConfigUpdate(
|
|
84
|
+
msg: VoiceConfigUpdateRequest,
|
|
85
|
+
_socket: net.Socket,
|
|
86
|
+
ctx: HandlerContext,
|
|
87
|
+
): void {
|
|
88
|
+
const result = normalizeActivationKey(msg.activationKey);
|
|
89
|
+
if (!result.ok) {
|
|
90
|
+
log.warn({ input: msg.activationKey }, result.reason);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
broadcastClientSettingsUpdate('activationKey', result.value, ctx);
|
|
95
|
+
log.info({ activationKey: result.value }, 'Voice config updated: activation key');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const voiceHandlers = defineHandlers({
|
|
99
|
+
voice_config_update: handleVoiceConfigUpdate,
|
|
100
|
+
});
|