@vellumai/assistant 0.4.2 → 0.4.4
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/.env.example +3 -0
- package/ARCHITECTURE.md +124 -10
- package/README.md +43 -35
- package/docs/trusted-contact-access.md +20 -0
- package/package.json +1 -1
- package/scripts/ipc/generate-swift.ts +1 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +58 -120
- package/src/__tests__/access-request-decision.test.ts +0 -1
- package/src/__tests__/actor-token-service.test.ts +1099 -0
- package/src/__tests__/agent-loop.test.ts +51 -0
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -5
- package/src/__tests__/assistant-id-boundary-guard.test.ts +415 -0
- package/src/__tests__/call-controller.test.ts +49 -0
- package/src/__tests__/call-pointer-message-composer.test.ts +171 -0
- package/src/__tests__/call-pointer-messages.test.ts +93 -3
- package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +42 -0
- package/src/__tests__/call-routes-http.test.ts +0 -25
- package/src/__tests__/callback-handoff-copy.test.ts +186 -0
- package/src/__tests__/channel-approval-routes.test.ts +133 -12
- package/src/__tests__/channel-guardian.test.ts +0 -86
- package/src/__tests__/channel-readiness-service.test.ts +10 -16
- package/src/__tests__/checker.test.ts +33 -12
- package/src/__tests__/config-schema.test.ts +6 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +410 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +256 -0
- package/src/__tests__/conversation-routes.test.ts +12 -3
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/daemon-server-session-init.test.ts +4 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +39 -13
- package/src/__tests__/guardian-dispatch.test.ts +8 -0
- package/src/__tests__/guardian-outbound-http.test.ts +4 -5
- package/src/__tests__/guardian-question-mode.test.ts +200 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +178 -0
- package/src/__tests__/guardian-routing-state.test.ts +525 -0
- package/src/__tests__/handle-user-message-secret-resume.test.ts +2 -0
- package/src/__tests__/handlers-telegram-config.test.ts +0 -83
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +55 -0
- package/src/__tests__/headless-browser-navigate.test.ts +2 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -1
- package/src/__tests__/ingress-routes-http.test.ts +55 -0
- package/src/__tests__/ipc-snapshot.test.ts +18 -51
- package/src/__tests__/non-member-access-request.test.ts +159 -9
- package/src/__tests__/notification-decision-fallback.test.ts +129 -4
- package/src/__tests__/notification-decision-strategy.test.ts +106 -2
- package/src/__tests__/notification-guardian-path.test.ts +3 -0
- package/src/__tests__/recording-intent-handler.test.ts +1 -0
- package/src/__tests__/relay-server.test.ts +1475 -33
- package/src/__tests__/send-endpoint-busy.test.ts +5 -0
- package/src/__tests__/session-agent-loop.test.ts +1 -0
- package/src/__tests__/session-confirmation-signals.test.ts +523 -0
- package/src/__tests__/session-init.benchmark.test.ts +0 -2
- package/src/__tests__/session-runtime-assembly.test.ts +4 -1
- package/src/__tests__/session-surfaces-task-progress.test.ts +44 -1
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +81 -2
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -1
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +21 -2
- package/src/__tests__/tool-grant-request-escalation.test.ts +333 -27
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +678 -0
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1064 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +11 -1
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
- package/src/__tests__/trusted-contact-verification.test.ts +0 -1
- package/src/__tests__/twilio-config.test.ts +2 -13
- package/src/__tests__/twilio-routes.test.ts +4 -3
- package/src/__tests__/update-bulletin.test.ts +0 -1
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-decision-primitive.ts +12 -3
- package/src/approvals/guardian-request-resolvers.ts +169 -11
- package/src/calls/call-constants.ts +29 -0
- package/src/calls/call-controller.ts +11 -3
- package/src/calls/call-domain.ts +33 -11
- package/src/calls/call-pointer-message-composer.ts +154 -0
- package/src/calls/call-pointer-messages.ts +106 -27
- package/src/calls/guardian-dispatch.ts +4 -2
- package/src/calls/relay-server.ts +921 -112
- package/src/calls/twilio-config.ts +4 -11
- package/src/calls/twilio-routes.ts +4 -6
- package/src/calls/types.ts +3 -1
- package/src/calls/voice-session-bridge.ts +4 -3
- package/src/cli/core-commands.ts +7 -4
- package/src/cli.ts +5 -4
- package/src/config/bundled-skills/agentmail/SKILL.md +4 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +309 -10
- package/src/config/bundled-skills/app-builder/TOOLS.json +1 -1
- package/src/config/bundled-skills/email-setup/SKILL.md +1 -1
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +105 -81
- package/src/config/bundled-skills/messaging/SKILL.md +61 -12
- package/src/config/bundled-skills/messaging/TOOLS.json +58 -0
- package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +6 -1
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +35 -0
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +52 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +30 -39
- package/src/config/bundled-skills/twitter/SKILL.md +3 -3
- package/src/config/bundled-skills/vercel-token-setup/SKILL.md +215 -0
- package/src/config/calls-schema.ts +36 -0
- package/src/config/env.ts +22 -0
- package/src/config/feature-flag-registry.json +8 -8
- package/src/config/schema.ts +2 -2
- package/src/config/skills.ts +11 -0
- package/src/config/system-prompt.ts +11 -1
- package/src/config/templates/SOUL.md +2 -0
- package/src/config/vellum-skills/sms-setup/SKILL.md +71 -82
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +8 -1
- package/src/config/vellum-skills/twilio-setup/SKILL.md +88 -73
- package/src/daemon/call-pointer-generators.ts +59 -0
- package/src/daemon/computer-use-session.ts +2 -5
- package/src/daemon/handlers/apps.ts +76 -20
- package/src/daemon/handlers/config-channels.ts +9 -61
- package/src/daemon/handlers/config-inbox.ts +11 -3
- package/src/daemon/handlers/config-ingress.ts +28 -3
- package/src/daemon/handlers/config-telegram.ts +12 -0
- package/src/daemon/handlers/config.ts +2 -6
- package/src/daemon/handlers/index.ts +2 -1
- package/src/daemon/handlers/pairing.ts +2 -0
- package/src/daemon/handlers/publish.ts +11 -46
- package/src/daemon/handlers/sessions.ts +59 -5
- package/src/daemon/handlers/shared.ts +17 -2
- package/src/daemon/ipc-contract/apps.ts +1 -0
- package/src/daemon/ipc-contract/inbox.ts +4 -0
- package/src/daemon/ipc-contract/integrations.ts +1 -97
- package/src/daemon/ipc-contract/messages.ts +47 -1
- package/src/daemon/ipc-contract/notifications.ts +11 -0
- package/src/daemon/ipc-contract-inventory.json +2 -4
- package/src/daemon/lifecycle.ts +17 -0
- package/src/daemon/server.ts +16 -2
- package/src/daemon/session-agent-loop-handlers.ts +20 -0
- package/src/daemon/session-agent-loop.ts +24 -12
- package/src/daemon/session-lifecycle.ts +1 -1
- package/src/daemon/session-process.ts +11 -1
- package/src/daemon/session-runtime-assembly.ts +6 -1
- package/src/daemon/session-surfaces.ts +32 -3
- package/src/daemon/session.ts +88 -1
- package/src/daemon/tool-side-effects.ts +22 -0
- package/src/home-base/prebuilt/brain-graph.html +1483 -0
- package/src/home-base/prebuilt/index.html +40 -0
- package/src/inbound/platform-callback-registration.ts +157 -0
- package/src/memory/canonical-guardian-store.ts +1 -1
- package/src/memory/conversation-crud.ts +2 -1
- package/src/memory/conversation-title-service.ts +16 -2
- package/src/memory/db-init.ts +8 -0
- package/src/memory/delivery-crud.ts +2 -1
- package/src/memory/guardian-action-store.ts +2 -1
- package/src/memory/guardian-approvals.ts +3 -2
- package/src/memory/ingress-invite-store.ts +12 -2
- package/src/memory/ingress-member-store.ts +4 -3
- package/src/memory/migrations/038-actor-token-records.ts +39 -0
- package/src/memory/migrations/124-voice-invite-display-metadata.ts +14 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/schema.ts +26 -5
- package/src/messaging/provider-types.ts +24 -0
- package/src/messaging/provider.ts +7 -0
- package/src/messaging/providers/gmail/adapter.ts +127 -0
- package/src/messaging/providers/sms/adapter.ts +40 -37
- package/src/notifications/adapters/macos.ts +45 -2
- package/src/notifications/broadcaster.ts +16 -0
- package/src/notifications/copy-composer.ts +50 -2
- package/src/notifications/decision-engine.ts +22 -9
- package/src/notifications/destination-resolver.ts +16 -2
- package/src/notifications/emit-signal.ts +18 -9
- package/src/notifications/guardian-question-mode.ts +419 -0
- package/src/notifications/signal.ts +14 -3
- package/src/permissions/checker.ts +13 -1
- package/src/permissions/prompter.ts +14 -0
- package/src/providers/anthropic/client.ts +20 -0
- package/src/providers/provider-send-message.ts +15 -3
- package/src/runtime/access-request-helper.ts +82 -4
- package/src/runtime/actor-token-service.ts +234 -0
- package/src/runtime/actor-token-store.ts +236 -0
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/assistant-scope.ts +10 -0
- package/src/runtime/channel-approvals.ts +5 -3
- package/src/runtime/channel-readiness-service.ts +23 -64
- package/src/runtime/channel-readiness-types.ts +3 -4
- package/src/runtime/channel-retry-sweep.ts +4 -1
- package/src/runtime/confirmation-request-guardian-bridge.ts +197 -0
- package/src/runtime/guardian-action-followup-executor.ts +1 -1
- package/src/runtime/guardian-context-resolver.ts +82 -0
- package/src/runtime/guardian-outbound-actions.ts +5 -7
- package/src/runtime/guardian-reply-router.ts +67 -30
- package/src/runtime/guardian-vellum-migration.ts +57 -0
- package/src/runtime/http-server.ts +75 -31
- package/src/runtime/http-types.ts +13 -0
- package/src/runtime/ingress-service.ts +14 -0
- package/src/runtime/invite-redemption-service.ts +10 -1
- package/src/runtime/local-actor-identity.ts +76 -0
- package/src/runtime/middleware/actor-token.ts +271 -0
- package/src/runtime/middleware/twilio-validation.ts +2 -4
- package/src/runtime/routes/approval-routes.ts +82 -7
- package/src/runtime/routes/brain-graph-routes.ts +222 -0
- package/src/runtime/routes/call-routes.ts +2 -1
- package/src/runtime/routes/channel-readiness-routes.ts +71 -0
- package/src/runtime/routes/channel-route-shared.ts +3 -3
- package/src/runtime/routes/conversation-attention-routes.ts +2 -1
- package/src/runtime/routes/conversation-routes.ts +142 -53
- package/src/runtime/routes/events-routes.ts +22 -8
- package/src/runtime/routes/guardian-action-routes.ts +45 -3
- package/src/runtime/routes/guardian-approval-interception.ts +29 -0
- package/src/runtime/routes/guardian-bootstrap-routes.ts +145 -0
- package/src/runtime/routes/inbound-conversation.ts +4 -3
- package/src/runtime/routes/inbound-message-handler.ts +147 -5
- package/src/runtime/routes/ingress-routes.ts +2 -0
- package/src/runtime/routes/integration-routes.ts +7 -15
- package/src/runtime/routes/pairing-routes.ts +163 -0
- package/src/runtime/routes/twilio-routes.ts +934 -0
- package/src/runtime/tool-grant-request-helper.ts +3 -1
- package/src/security/oauth2.ts +27 -2
- package/src/security/token-manager.ts +46 -10
- package/src/tools/browser/browser-execution.ts +4 -3
- package/src/tools/browser/browser-handoff.ts +10 -18
- package/src/tools/browser/browser-manager.ts +80 -25
- package/src/tools/browser/browser-screencast.ts +35 -119
- package/src/tools/calls/call-start.ts +2 -1
- package/src/tools/permission-checker.ts +15 -4
- package/src/tools/terminal/parser.ts +12 -0
- package/src/tools/tool-approval-handler.ts +244 -19
- package/src/workspace/git-service.ts +19 -0
- package/src/__tests__/handlers-twilio-config.test.ts +0 -1928
- package/src/daemon/handlers/config-twilio.ts +0 -1082
|
@@ -7,6 +7,8 @@ import { v4 as uuid } from 'uuid';
|
|
|
7
7
|
|
|
8
8
|
import { packageApp } from '../../bundler/app-bundler.js';
|
|
9
9
|
import { defaultGallery } from '../../gallery/default-gallery.js';
|
|
10
|
+
import { resolveHomeBaseAppId } from '../../home-base/bootstrap.js';
|
|
11
|
+
import { isPrebuiltHomeBaseApp } from '../../home-base/prebuilt-home-base-updater.js';
|
|
10
12
|
import { getAppDiff, getAppFileAtVersion, getAppHistory, restoreAppVersion } from '../../memory/app-git-service.js';
|
|
11
13
|
import { createApp, createAppRecord, deleteAppRecord, getApp, getAppPreview, listApps, queryAppRecords, updateApp,updateAppRecord } from '../../memory/app-store.js';
|
|
12
14
|
import { createSharedAppLink } from '../../memory/shared-app-links-store.js';
|
|
@@ -77,28 +79,54 @@ export function handleAppDataRequest(
|
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
export function handleAppOpenRequest(msg: { appId: string }, socket: net.Socket, ctx: HandlerContext): void {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
try {
|
|
83
|
+
const appId = msg.appId;
|
|
84
|
+
if (!appId) {
|
|
85
|
+
ctx.send(socket, { type: 'error', message: 'app_open_request requires appId' });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const app = getApp(appId);
|
|
90
|
+
if (app) {
|
|
91
|
+
const surfaceId = `app-open-${uuid()}`;
|
|
92
|
+
ctx.send(socket, {
|
|
93
|
+
type: 'ui_surface_show',
|
|
94
|
+
sessionId: 'app-panel',
|
|
95
|
+
surfaceId,
|
|
96
|
+
surfaceType: 'dynamic_page',
|
|
97
|
+
title: app.name,
|
|
98
|
+
data: { html: app.htmlDefinition, appId: app.id, appType: app.appType },
|
|
99
|
+
display: 'panel',
|
|
100
|
+
} as UiSurfaceShow);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Fallback: the ID might be a surfaceId from an ephemeral ui_show surface
|
|
105
|
+
// (not a persistent app). Search active sessions for cached surface data.
|
|
106
|
+
for (const session of ctx.sessions.values()) {
|
|
107
|
+
const cached = session.surfaceState.get(appId);
|
|
108
|
+
if (cached && cached.surfaceType === 'dynamic_page') {
|
|
109
|
+
const newSurfaceId = `app-open-${uuid()}`;
|
|
110
|
+
ctx.send(socket, {
|
|
111
|
+
type: 'ui_surface_show',
|
|
112
|
+
sessionId: 'app-panel',
|
|
113
|
+
surfaceId: newSurfaceId,
|
|
114
|
+
surfaceType: 'dynamic_page',
|
|
115
|
+
title: cached.title ?? (cached.data as { preview?: { title?: string } }).preview?.title,
|
|
116
|
+
data: cached.data,
|
|
117
|
+
display: 'panel',
|
|
118
|
+
} as UiSurfaceShow);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
85
122
|
|
|
86
|
-
|
|
87
|
-
if (!app) {
|
|
123
|
+
log.warn({ appId }, 'App not found in store or session surfaces');
|
|
88
124
|
ctx.send(socket, { type: 'error', message: `App not found: ${appId}` });
|
|
89
|
-
|
|
125
|
+
} catch (err) {
|
|
126
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
127
|
+
log.error({ err, appId: msg.appId }, 'Failed to handle app open request');
|
|
128
|
+
ctx.send(socket, { type: 'error', message: `Failed to open app: ${message}` });
|
|
90
129
|
}
|
|
91
|
-
|
|
92
|
-
const surfaceId = `app-open-${uuid()}`;
|
|
93
|
-
ctx.send(socket, {
|
|
94
|
-
type: 'ui_surface_show',
|
|
95
|
-
sessionId: 'app-panel',
|
|
96
|
-
surfaceId,
|
|
97
|
-
surfaceType: 'dynamic_page',
|
|
98
|
-
title: app.name,
|
|
99
|
-
data: { html: app.htmlDefinition, appId: app.id, appType: app.appType },
|
|
100
|
-
display: 'panel',
|
|
101
|
-
} as UiSurfaceShow);
|
|
102
130
|
}
|
|
103
131
|
|
|
104
132
|
export function handleAppUpdatePreview(msg: AppUpdatePreviewRequest, socket: net.Socket, ctx: HandlerContext): void {
|
|
@@ -114,7 +142,35 @@ export function handleAppUpdatePreview(msg: AppUpdatePreviewRequest, socket: net
|
|
|
114
142
|
|
|
115
143
|
export function handleAppsList(socket: net.Socket, ctx: HandlerContext): void {
|
|
116
144
|
try {
|
|
117
|
-
const
|
|
145
|
+
const allApps = listApps();
|
|
146
|
+
const homeBaseId = resolveHomeBaseAppId();
|
|
147
|
+
|
|
148
|
+
// When no home base was found by ID, do a single targeted search for an app
|
|
149
|
+
// matching the HTML marker. listApps() returns metadata-only (no htmlDefinition),
|
|
150
|
+
// so the HTML marker check requires loading the full app from disk. We limit
|
|
151
|
+
// this expensive operation to the case where homeBaseId is null.
|
|
152
|
+
const excludeIds = new Set<string>();
|
|
153
|
+
if (homeBaseId) {
|
|
154
|
+
excludeIds.add(homeBaseId);
|
|
155
|
+
} else {
|
|
156
|
+
for (const a of allApps) {
|
|
157
|
+
if (isPrebuiltHomeBaseApp(a)) {
|
|
158
|
+
excludeIds.add(a.id);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const fullApp = getApp(a.id);
|
|
162
|
+
if (fullApp && isPrebuiltHomeBaseApp(fullApp)) {
|
|
163
|
+
excludeIds.add(a.id);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const apps = allApps.filter((a) => {
|
|
170
|
+
if (excludeIds.has(a.id)) return false;
|
|
171
|
+
if (isPrebuiltHomeBaseApp(a)) return false;
|
|
172
|
+
return true;
|
|
173
|
+
});
|
|
118
174
|
ctx.send(socket, {
|
|
119
175
|
type: 'apps_list_response',
|
|
120
176
|
apps: apps.map((a) => {
|
|
@@ -3,6 +3,7 @@ import * as net from 'node:net';
|
|
|
3
3
|
import type { ChannelId } from '../../channels/types.js';
|
|
4
4
|
import * as externalConversationStore from '../../memory/external-conversation-store.js';
|
|
5
5
|
import { findMember, revokeMember } from '../../memory/ingress-member-store.js';
|
|
6
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../../runtime/assistant-scope.js';
|
|
6
7
|
import {
|
|
7
8
|
createVerificationChallenge,
|
|
8
9
|
findActiveSession,
|
|
@@ -17,9 +18,7 @@ import {
|
|
|
17
18
|
resendOutbound,
|
|
18
19
|
startOutbound,
|
|
19
20
|
} from '../../runtime/guardian-outbound-actions.js';
|
|
20
|
-
import { normalizeAssistantId } from '../../util/platform.js';
|
|
21
21
|
import type {
|
|
22
|
-
ChannelReadinessRequest,
|
|
23
22
|
GuardianVerificationRequest,
|
|
24
23
|
GuardianVerificationResponse,
|
|
25
24
|
} from '../ipc-protocol.js';
|
|
@@ -60,11 +59,10 @@ export function getReadinessService(): ChannelReadinessService {
|
|
|
60
59
|
|
|
61
60
|
export function createGuardianChallenge(
|
|
62
61
|
channel?: ChannelId,
|
|
63
|
-
assistantId?: string,
|
|
64
62
|
rebind?: boolean,
|
|
65
63
|
sessionId?: string,
|
|
66
64
|
): GuardianVerificationResult {
|
|
67
|
-
const resolvedAssistantId =
|
|
65
|
+
const resolvedAssistantId = DAEMON_INTERNAL_ASSISTANT_ID;
|
|
68
66
|
const resolvedChannel = channel ?? 'telegram';
|
|
69
67
|
|
|
70
68
|
const existingBinding = getGuardianBinding(resolvedAssistantId, resolvedChannel);
|
|
@@ -89,9 +87,8 @@ export function createGuardianChallenge(
|
|
|
89
87
|
|
|
90
88
|
export function getGuardianStatus(
|
|
91
89
|
channel?: ChannelId,
|
|
92
|
-
assistantId?: string,
|
|
93
90
|
): GuardianVerificationResult {
|
|
94
|
-
const resolvedAssistantId =
|
|
91
|
+
const resolvedAssistantId = DAEMON_INTERNAL_ASSISTANT_ID;
|
|
95
92
|
const resolvedChannel = channel ?? 'telegram';
|
|
96
93
|
|
|
97
94
|
const binding = getGuardianBinding(resolvedAssistantId, resolvedChannel);
|
|
@@ -161,17 +158,15 @@ export function handleGuardianVerification(
|
|
|
161
158
|
socket: net.Socket,
|
|
162
159
|
ctx: HandlerContext,
|
|
163
160
|
): void {
|
|
164
|
-
|
|
165
|
-
// same key the inbound-call path will use for lookups (typically "self").
|
|
166
|
-
const assistantId = normalizeAssistantId(msg.assistantId ?? 'self');
|
|
161
|
+
const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
|
|
167
162
|
const channel = msg.channel ?? 'telegram';
|
|
168
163
|
|
|
169
164
|
try {
|
|
170
165
|
if (msg.action === 'create_challenge') {
|
|
171
|
-
const result = createGuardianChallenge(channel,
|
|
166
|
+
const result = createGuardianChallenge(channel, msg.rebind, msg.sessionId);
|
|
172
167
|
ctx.send(socket, { type: 'guardian_verification_response', ...result });
|
|
173
168
|
} else if (msg.action === 'status') {
|
|
174
|
-
const result = getGuardianStatus(channel
|
|
169
|
+
const result = getGuardianStatus(channel);
|
|
175
170
|
ctx.send(socket, { type: 'guardian_verification_response', ...result });
|
|
176
171
|
} else if (msg.action === 'revoke') {
|
|
177
172
|
// Capture binding before revoking so we can revoke the guardian's
|
|
@@ -200,13 +195,13 @@ export function handleGuardianVerification(
|
|
|
200
195
|
channel,
|
|
201
196
|
});
|
|
202
197
|
} else if (msg.action === 'start_outbound') {
|
|
203
|
-
const result = startOutbound({ channel,
|
|
198
|
+
const result = startOutbound({ channel, destination: msg.destination, rebind: msg.rebind, originConversationId: msg.originConversationId });
|
|
204
199
|
ctx.send(socket, { type: 'guardian_verification_response', ...result });
|
|
205
200
|
} else if (msg.action === 'resend_outbound') {
|
|
206
|
-
const result = resendOutbound({ channel,
|
|
201
|
+
const result = resendOutbound({ channel, originConversationId: msg.originConversationId });
|
|
207
202
|
ctx.send(socket, { type: 'guardian_verification_response', ...result });
|
|
208
203
|
} else if (msg.action === 'cancel_outbound') {
|
|
209
|
-
const result = cancelOutbound({ channel
|
|
204
|
+
const result = cancelOutbound({ channel });
|
|
210
205
|
ctx.send(socket, { type: 'guardian_verification_response', ...result });
|
|
211
206
|
} else {
|
|
212
207
|
ctx.send(socket, {
|
|
@@ -229,53 +224,6 @@ export function handleGuardianVerification(
|
|
|
229
224
|
}
|
|
230
225
|
|
|
231
226
|
|
|
232
|
-
// ---------------------------------------------------------------------------
|
|
233
|
-
// Channel readiness handler
|
|
234
|
-
// ---------------------------------------------------------------------------
|
|
235
|
-
|
|
236
|
-
export async function handleChannelReadiness(
|
|
237
|
-
msg: ChannelReadinessRequest,
|
|
238
|
-
socket: net.Socket,
|
|
239
|
-
ctx: HandlerContext,
|
|
240
|
-
): Promise<void> {
|
|
241
|
-
try {
|
|
242
|
-
const service = getReadinessService();
|
|
243
|
-
|
|
244
|
-
if (msg.action === 'refresh') {
|
|
245
|
-
if (msg.channel) {
|
|
246
|
-
service.invalidateChannel(msg.channel, msg.assistantId);
|
|
247
|
-
} else {
|
|
248
|
-
service.invalidateAll();
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const snapshots = await service.getReadiness(msg.channel, msg.includeRemote, msg.assistantId);
|
|
253
|
-
|
|
254
|
-
ctx.send(socket, {
|
|
255
|
-
type: 'channel_readiness_response',
|
|
256
|
-
success: true,
|
|
257
|
-
snapshots: snapshots.map((s) => ({
|
|
258
|
-
channel: s.channel,
|
|
259
|
-
ready: s.ready,
|
|
260
|
-
checkedAt: s.checkedAt,
|
|
261
|
-
stale: s.stale,
|
|
262
|
-
reasons: s.reasons,
|
|
263
|
-
localChecks: s.localChecks,
|
|
264
|
-
remoteChecks: s.remoteChecks,
|
|
265
|
-
})),
|
|
266
|
-
});
|
|
267
|
-
} catch (err) {
|
|
268
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
269
|
-
log.error({ err }, 'Failed to handle channel readiness');
|
|
270
|
-
ctx.send(socket, {
|
|
271
|
-
type: 'channel_readiness_response',
|
|
272
|
-
success: false,
|
|
273
|
-
error: message,
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
227
|
export const channelHandlers = defineHandlers({
|
|
279
|
-
channel_readiness: handleChannelReadiness,
|
|
280
228
|
guardian_verification: handleGuardianVerification,
|
|
281
229
|
});
|
|
@@ -40,6 +40,8 @@ export function handleIngressInvite(
|
|
|
40
40
|
note: msg.note,
|
|
41
41
|
maxUses: msg.maxUses,
|
|
42
42
|
expiresInMs: msg.expiresInMs,
|
|
43
|
+
friendName: msg.friendName,
|
|
44
|
+
guardianName: msg.guardianName,
|
|
43
45
|
});
|
|
44
46
|
if (!result.ok) {
|
|
45
47
|
ctx.send(socket, { type: 'ingress_invite_response', success: false, error: result.error });
|
|
@@ -303,9 +305,15 @@ async function executeApprove(
|
|
|
303
305
|
}
|
|
304
306
|
}
|
|
305
307
|
session.setAssistantId(assistantId);
|
|
306
|
-
// The guardian approved this escalation
|
|
307
|
-
//
|
|
308
|
-
|
|
308
|
+
// The guardian already approved this escalation via the inbox, so we
|
|
309
|
+
// directly set guardian trust. Going through resolveLocalIpcGuardianContext
|
|
310
|
+
// would look up the vellum binding's guardian ID and compare it against
|
|
311
|
+
// a different channel's binding (e.g. telegram/sms), misclassifying the
|
|
312
|
+
// actor as 'unknown'.
|
|
313
|
+
session.setGuardianContext({
|
|
314
|
+
sourceChannel: sourceChannel ?? 'vellum',
|
|
315
|
+
trustClass: 'guardian',
|
|
316
|
+
});
|
|
309
317
|
session.setCommandIntent(null);
|
|
310
318
|
|
|
311
319
|
// Process the message through the agent loop (no IPC event callback
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
setIngressPublicBaseUrl,
|
|
11
11
|
} from '../../config/env.js';
|
|
12
12
|
import { loadRawConfig, saveRawConfig } from '../../config/loader.js';
|
|
13
|
+
import { registerCallbackRoute, resolveCallbackUrl, shouldUsePlatformCallbacks } from '../../inbound/platform-callback-registration.js';
|
|
13
14
|
import {
|
|
14
15
|
getTwilioSmsWebhookUrl,
|
|
15
16
|
getTwilioStatusCallbackUrl,
|
|
@@ -92,9 +93,21 @@ export async function syncTwilioWebhooks(
|
|
|
92
93
|
ingressConfig: IngressConfig,
|
|
93
94
|
): Promise<{ success: boolean; warning?: string }> {
|
|
94
95
|
try {
|
|
95
|
-
const voiceUrl =
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
const voiceUrl = await resolveCallbackUrl(
|
|
97
|
+
() => getTwilioVoiceWebhookUrl(ingressConfig),
|
|
98
|
+
'webhooks/twilio/voice',
|
|
99
|
+
'twilio_voice',
|
|
100
|
+
);
|
|
101
|
+
const statusCallbackUrl = await resolveCallbackUrl(
|
|
102
|
+
() => getTwilioStatusCallbackUrl(ingressConfig),
|
|
103
|
+
'webhooks/twilio/status',
|
|
104
|
+
'twilio_status',
|
|
105
|
+
);
|
|
106
|
+
const smsUrl = await resolveCallbackUrl(
|
|
107
|
+
() => getTwilioSmsWebhookUrl(ingressConfig),
|
|
108
|
+
'webhooks/twilio/sms',
|
|
109
|
+
'twilio_sms',
|
|
110
|
+
);
|
|
98
111
|
await updatePhoneNumberWebhooks(accountSid, authToken, phoneNumber, {
|
|
99
112
|
voiceUrl,
|
|
100
113
|
statusCallbackUrl,
|
|
@@ -183,6 +196,18 @@ export async function handleIngressConfig(
|
|
|
183
196
|
// Use the effective URL from process.env (which accounts for the
|
|
184
197
|
// fallback branch above) rather than the raw `value` from the UI.
|
|
185
198
|
const effectiveUrl = isEnabled ? getIngressPublicBaseUrl() : undefined;
|
|
199
|
+
|
|
200
|
+
// When containerized with a platform, register the Telegram callback
|
|
201
|
+
// route so the platform knows how to forward Telegram webhooks.
|
|
202
|
+
// This must happen independently of effectiveUrl — in containerized
|
|
203
|
+
// deployments without ingress.publicBaseUrl, platform callbacks are the
|
|
204
|
+
// only way to receive Telegram webhooks.
|
|
205
|
+
if (shouldUsePlatformCallbacks()) {
|
|
206
|
+
registerCallbackRoute('webhooks/telegram', 'telegram').catch((err) => {
|
|
207
|
+
log.warn({ err }, 'Failed to register Telegram platform callback route');
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
186
211
|
triggerGatewayReconcile(effectiveUrl);
|
|
187
212
|
|
|
188
213
|
// Best-effort Twilio webhook reconciliation: when ingress is being
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as net from 'node:net';
|
|
2
2
|
|
|
3
3
|
import { getIngressPublicBaseUrl } from '../../config/env.js';
|
|
4
|
+
import { registerCallbackRoute, shouldUsePlatformCallbacks } from '../../inbound/platform-callback-registration.js';
|
|
4
5
|
import { deleteSecureKey,getSecureKey, setSecureKey } from '../../security/secure-keys.js';
|
|
5
6
|
import { deleteCredentialMetadata, getCredentialMetadata,upsertCredentialMetadata } from '../../tools/credentials/metadata-store.js';
|
|
6
7
|
import type { TelegramConfigRequest, TelegramConfigResponse } from '../ipc-protocol.js';
|
|
@@ -161,6 +162,17 @@ export async function setTelegramConfig(botToken?: string): Promise<TelegramConf
|
|
|
161
162
|
hasWebhookSecret,
|
|
162
163
|
};
|
|
163
164
|
|
|
165
|
+
// When containerized with a platform, register the Telegram callback
|
|
166
|
+
// route so the platform knows how to forward Telegram webhooks.
|
|
167
|
+
// This must happen independently of effectiveUrl — in containerized
|
|
168
|
+
// deployments without ingress.publicBaseUrl, platform callbacks are the
|
|
169
|
+
// only way to receive Telegram webhooks.
|
|
170
|
+
if (shouldUsePlatformCallbacks()) {
|
|
171
|
+
registerCallbackRoute('webhooks/telegram', 'telegram').catch((err) => {
|
|
172
|
+
log.warn({ err }, 'Failed to register Telegram platform callback route');
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
164
176
|
// Trigger gateway reconcile so the webhook registration updates immediately
|
|
165
177
|
const effectiveUrl = getIngressPublicBaseUrl();
|
|
166
178
|
if (effectiveUrl) {
|
|
@@ -11,13 +11,12 @@
|
|
|
11
11
|
* config-platform.ts — Platform base URL configuration
|
|
12
12
|
* config-integrations.ts — Vercel API & Twitter integration
|
|
13
13
|
* config-telegram.ts — Telegram bot configuration
|
|
14
|
-
* config-
|
|
15
|
-
* config-channels.ts — Channel guardian & readiness
|
|
14
|
+
* config-channels.ts — Channel guardian verification
|
|
16
15
|
* config-tools.ts — Env vars, tool permission simulation, tool names
|
|
17
16
|
*/
|
|
18
17
|
|
|
19
18
|
// Re-export individual handlers for direct import by tests and other modules
|
|
20
|
-
export { getReadinessService,
|
|
19
|
+
export { getReadinessService, handleGuardianVerification } from './config-channels.js';
|
|
21
20
|
export { handleHeartbeatChecklistRead, handleHeartbeatChecklistWrite,handleHeartbeatConfig, handleHeartbeatRunNow, handleHeartbeatRunsList } from './config-heartbeat.js';
|
|
22
21
|
export { computeGatewayTarget, handleIngressConfig, syncTwilioWebhooks,triggerGatewayReconcile } from './config-ingress.js';
|
|
23
22
|
export { handleTwitterIntegrationConfig,handleVercelApiConfig } from './config-integrations.js';
|
|
@@ -28,7 +27,6 @@ export { handleShareToSlack, handleSlackWebhookConfig } from './config-slack.js'
|
|
|
28
27
|
export { handleTelegramConfig, summarizeTelegramError } from './config-telegram.js';
|
|
29
28
|
export { handleEnvVarsRequest, handleToolNamesList,handleToolPermissionSimulate } from './config-tools.js';
|
|
30
29
|
export { handleAcceptStarterBundle,handleAddTrustRule, handleRemoveTrustRule, handleTrustRulesList, handleUpdateTrustRule } from './config-trust.js';
|
|
31
|
-
export { handleTwilioConfig } from './config-twilio.js';
|
|
32
30
|
export { broadcastClientSettingsUpdate, handleVoiceConfigUpdate, normalizeActivationKey } from './config-voice.js';
|
|
33
31
|
|
|
34
32
|
// Assemble the combined dispatch map from domain-specific handler groups
|
|
@@ -43,7 +41,6 @@ import { slackHandlers } from './config-slack.js';
|
|
|
43
41
|
import { telegramHandlers } from './config-telegram.js';
|
|
44
42
|
import { toolHandlers } from './config-tools.js';
|
|
45
43
|
import { trustHandlers } from './config-trust.js';
|
|
46
|
-
import { twilioHandlers } from './config-twilio.js';
|
|
47
44
|
import { voiceHandlers } from './config-voice.js';
|
|
48
45
|
|
|
49
46
|
export const configHandlers = {
|
|
@@ -55,7 +52,6 @@ export const configHandlers = {
|
|
|
55
52
|
...platformHandlers,
|
|
56
53
|
...integrationHandlers,
|
|
57
54
|
...telegramHandlers,
|
|
58
|
-
...twilioHandlers,
|
|
59
55
|
...channelHandlers,
|
|
60
56
|
...toolHandlers,
|
|
61
57
|
...heartbeatHandlers,
|
|
@@ -2,6 +2,7 @@ import * as net from 'node:net';
|
|
|
2
2
|
|
|
3
3
|
import { type Confidence, recordConversationSeenSignal, type SignalType } from '../../memory/conversation-attention-store.js';
|
|
4
4
|
import { updateDeliveryClientOutcome } from '../../notifications/deliveries-store.js';
|
|
5
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../../runtime/assistant-scope.js';
|
|
5
6
|
import type { ClientMessage } from '../ipc-protocol.js';
|
|
6
7
|
import { handleRideShotgunStart, handleRideShotgunStop } from '../ride-shotgun-handler.js';
|
|
7
8
|
import { handleWatchObservation } from '../watch-handler.js';
|
|
@@ -104,7 +105,7 @@ const inlineHandlers = defineHandlers({
|
|
|
104
105
|
try {
|
|
105
106
|
recordConversationSeenSignal({
|
|
106
107
|
conversationId: msg.conversationId,
|
|
107
|
-
assistantId:
|
|
108
|
+
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
108
109
|
sourceChannel: msg.sourceChannel,
|
|
109
110
|
signalType: msg.signalType as SignalType,
|
|
110
111
|
confidence: msg.confidence as Confidence,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as net from 'node:net';
|
|
2
2
|
|
|
3
|
+
import { cleanupPairingState } from '../../runtime/routes/pairing-routes.js';
|
|
3
4
|
import {
|
|
4
5
|
approveDevice,
|
|
5
6
|
clearAllDevices,
|
|
@@ -46,6 +47,7 @@ function handlePairingApprovalResponse(
|
|
|
46
47
|
|
|
47
48
|
if (msg.decision === 'deny') {
|
|
48
49
|
pairingStoreRef.deny(msg.pairingRequestId);
|
|
50
|
+
cleanupPairingState(msg.pairingRequestId);
|
|
49
51
|
log.info({ pairingRequestId: msg.pairingRequestId }, 'Pairing request denied');
|
|
50
52
|
return;
|
|
51
53
|
}
|
|
@@ -4,15 +4,13 @@ import * as net from 'node:net';
|
|
|
4
4
|
import { v4 as uuid } from 'uuid';
|
|
5
5
|
|
|
6
6
|
import { createPublishedPage, getPublishedPageByDeploymentId, getPublishedPageByHash, markDeleted, updatePublishedPage } from '../../memory/published-pages-store.js';
|
|
7
|
-
import { setSecureKey } from '../../security/secure-keys.js';
|
|
8
7
|
import { deleteVercelDeployment,deployHtmlToVercel } from '../../services/vercel-deploy.js';
|
|
9
8
|
import { credentialBroker } from '../../tools/credentials/broker.js';
|
|
10
|
-
import { getCredentialMetadata, upsertCredentialMetadata } from '../../tools/credentials/metadata-store.js';
|
|
11
9
|
import type {
|
|
12
10
|
PublishPageRequest,
|
|
13
11
|
UnpublishPageRequest,
|
|
14
12
|
} from '../ipc-protocol.js';
|
|
15
|
-
import { defineHandlers, type HandlerContext,log
|
|
13
|
+
import { defineHandlers, type HandlerContext,log } from './shared.js';
|
|
16
14
|
|
|
17
15
|
export async function handlePublishPage(
|
|
18
16
|
msg: PublishPageRequest,
|
|
@@ -60,57 +58,24 @@ export async function handlePublishPage(
|
|
|
60
58
|
return { url: result.url, deploymentId: result.deploymentId };
|
|
61
59
|
};
|
|
62
60
|
|
|
63
|
-
|
|
61
|
+
const useResult = await credentialBroker.serverUse({
|
|
64
62
|
service: 'vercel',
|
|
65
63
|
field: 'api_token',
|
|
66
64
|
toolName: 'publish_page',
|
|
67
65
|
execute: publishExecute,
|
|
68
66
|
});
|
|
69
67
|
|
|
70
|
-
// If no credential found,
|
|
68
|
+
// If no credential found, return a structured error so the client can
|
|
69
|
+
// trigger the assistant-driven token setup flow instead of blocking on
|
|
70
|
+
// a vault dialog.
|
|
71
71
|
if (!useResult.success && useResult.reason?.includes('No credential found')) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
description: 'Required to publish site apps to the web. Create a token at vercel.com/account/tokens.',
|
|
78
|
-
placeholder: 'Enter your Vercel API token',
|
|
79
|
-
purpose: 'Publish site apps to the web',
|
|
80
|
-
allowedTools,
|
|
81
|
-
allowedDomains: ['api.vercel.com'],
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
if (!secretResult.value) {
|
|
85
|
-
ctx.send(socket, {
|
|
86
|
-
type: 'publish_page_response',
|
|
87
|
-
success: false,
|
|
88
|
-
error: 'Cancelled',
|
|
89
|
-
});
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (secretResult.delivery === 'transient_send') {
|
|
94
|
-
// One-time send: inject for single use without persisting to keychain.
|
|
95
|
-
// Metadata must exist for broker policy checks.
|
|
96
|
-
if (!getCredentialMetadata('vercel', 'api_token')) {
|
|
97
|
-
upsertCredentialMetadata('vercel', 'api_token', { allowedTools });
|
|
98
|
-
}
|
|
99
|
-
credentialBroker.injectTransient('vercel', 'api_token', secretResult.value);
|
|
100
|
-
} else {
|
|
101
|
-
// Default: persist to keychain
|
|
102
|
-
const storageKey = `credential:vercel:api_token`;
|
|
103
|
-
setSecureKey(storageKey, secretResult.value);
|
|
104
|
-
upsertCredentialMetadata('vercel', 'api_token', { allowedTools });
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Retry with the newly stored credential
|
|
108
|
-
useResult = await credentialBroker.serverUse({
|
|
109
|
-
service: 'vercel',
|
|
110
|
-
field: 'api_token',
|
|
111
|
-
toolName: 'publish_page',
|
|
112
|
-
execute: publishExecute,
|
|
72
|
+
ctx.send(socket, {
|
|
73
|
+
type: 'publish_page_response',
|
|
74
|
+
success: false,
|
|
75
|
+
error: 'Vercel API token not configured',
|
|
76
|
+
errorCode: 'credentials_missing',
|
|
113
77
|
});
|
|
78
|
+
return;
|
|
114
79
|
}
|
|
115
80
|
|
|
116
81
|
if (useResult.success && useResult.result) {
|
|
@@ -17,7 +17,9 @@ import { getAttentionStateByConversationIds } from '../../memory/conversation-at
|
|
|
17
17
|
import * as conversationStore from '../../memory/conversation-store.js';
|
|
18
18
|
import { GENERATING_TITLE, queueGenerateConversationTitle, UNTITLED_FALLBACK } from '../../memory/conversation-title-service.js';
|
|
19
19
|
import * as externalConversationStore from '../../memory/external-conversation-store.js';
|
|
20
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../../runtime/assistant-scope.js';
|
|
20
21
|
import { routeGuardianReply } from '../../runtime/guardian-reply-router.js';
|
|
22
|
+
import { resolveLocalIpcGuardianContext } from '../../runtime/local-actor-identity.js';
|
|
21
23
|
import * as pendingInteractions from '../../runtime/pending-interactions.js';
|
|
22
24
|
import { checkIngressForSecrets } from '../../security/secret-ingress.js';
|
|
23
25
|
import { compileCustomPatterns, redactSecrets } from '../../security/secret-scanner.js';
|
|
@@ -175,6 +177,10 @@ export async function handleUserMessage(
|
|
|
175
177
|
conversationId: msg.sessionId,
|
|
176
178
|
sourceChannel: ipcChannel,
|
|
177
179
|
});
|
|
180
|
+
// Route prompter-originated events (confirmation_request/secret_request)
|
|
181
|
+
// through the IPC wrapper so pending-interactions + canonical tracking
|
|
182
|
+
// are updated before the message is sent to the client.
|
|
183
|
+
session.updateClient(sendEvent, false);
|
|
178
184
|
const ipcInterface = parseInterfaceId(msg.interface);
|
|
179
185
|
if (!ipcInterface) {
|
|
180
186
|
ctx.send(socket, {
|
|
@@ -263,6 +269,7 @@ export async function handleUserMessage(
|
|
|
263
269
|
}
|
|
264
270
|
|
|
265
271
|
rlog.info({ source }, 'Processing user message');
|
|
272
|
+
session.emitActivityState('thinking', 'message_dequeued', 'assistant_turn', dispatchRequestId);
|
|
266
273
|
session.setTurnChannelContext({
|
|
267
274
|
userMessageChannel: ipcChannel,
|
|
268
275
|
assistantMessageChannel: ipcChannel,
|
|
@@ -271,10 +278,12 @@ export async function handleUserMessage(
|
|
|
271
278
|
userMessageInterface: ipcInterface,
|
|
272
279
|
assistantMessageInterface: ipcInterface,
|
|
273
280
|
});
|
|
274
|
-
session.setAssistantId(
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
|
|
281
|
+
session.setAssistantId(DAEMON_INTERNAL_ASSISTANT_ID);
|
|
282
|
+
// Resolve local IPC actor identity through the same trust pipeline
|
|
283
|
+
// used by HTTP channel ingress. The vellum guardian binding provides
|
|
284
|
+
// the guardianPrincipalId, and resolveGuardianContext classifies the
|
|
285
|
+
// local user as 'guardian' via binding match.
|
|
286
|
+
session.setGuardianContext(resolveLocalIpcGuardianContext(ipcChannel));
|
|
278
287
|
session.setCommandIntent(null);
|
|
279
288
|
// Fire-and-forget: don't block the IPC handler so the connection can
|
|
280
289
|
// continue receiving messages (e.g. cancel, confirmations, or
|
|
@@ -599,9 +608,29 @@ export async function handleUserMessage(
|
|
|
599
608
|
conversationId: msg.sessionId,
|
|
600
609
|
pendingRequestIds: pendingRequestIdsForConversation,
|
|
601
610
|
approvalConversationGenerator: desktopApprovalConversationGenerator,
|
|
611
|
+
emissionContext: {
|
|
612
|
+
source: 'inline_nl',
|
|
613
|
+
causedByRequestId: requestId,
|
|
614
|
+
decisionText: messageText.trim(),
|
|
615
|
+
},
|
|
602
616
|
});
|
|
603
617
|
|
|
604
618
|
if (routerResult.consumed && routerResult.type !== 'nl_keep_pending') {
|
|
619
|
+
// Success-path emissions (approved/denied) are handled centrally
|
|
620
|
+
// by handleConfirmationResponse (called via the resolver chain).
|
|
621
|
+
// However, stale/failed paths never reach handleConfirmationResponse,
|
|
622
|
+
// so we emit resolved_stale here for those cases.
|
|
623
|
+
if (routerResult.requestId && !routerResult.decisionApplied) {
|
|
624
|
+
session.emitConfirmationStateChanged({
|
|
625
|
+
sessionId: msg.sessionId,
|
|
626
|
+
requestId: routerResult.requestId,
|
|
627
|
+
state: 'resolved_stale',
|
|
628
|
+
source: 'inline_nl',
|
|
629
|
+
causedByRequestId: requestId,
|
|
630
|
+
decisionText: messageText.trim(),
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
|
|
605
634
|
const consumedChannelMeta = {
|
|
606
635
|
userMessageChannel: ipcChannel,
|
|
607
636
|
assistantMessageChannel: ipcChannel,
|
|
@@ -685,6 +714,19 @@ export async function handleUserMessage(
|
|
|
685
714
|
// will see the denial and can re-request the tool if still needed.
|
|
686
715
|
if (session.hasAnyPendingConfirmation()) {
|
|
687
716
|
rlog.info('Auto-denying pending confirmation(s) due to new user message');
|
|
717
|
+
// Emit authoritative confirmation state for each auto-denied request
|
|
718
|
+
// before the prompter clears them.
|
|
719
|
+
for (const interaction of pendingInteractions.getByConversation(msg.sessionId)) {
|
|
720
|
+
if (interaction.session === session && interaction.kind === 'confirmation') {
|
|
721
|
+
session.emitConfirmationStateChanged({
|
|
722
|
+
sessionId: msg.sessionId,
|
|
723
|
+
requestId: interaction.requestId,
|
|
724
|
+
state: 'denied',
|
|
725
|
+
source: 'auto_deny',
|
|
726
|
+
causedByRequestId: requestId,
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
}
|
|
688
730
|
session.denyAllPendingConfirmations();
|
|
689
731
|
// Keep the pending-interaction tracker aligned with the prompter so
|
|
690
732
|
// stale request IDs are not reused as routing candidates.
|
|
@@ -730,6 +772,8 @@ export function handleConfirmationResponse(
|
|
|
730
772
|
msg.decision,
|
|
731
773
|
msg.selectedPattern,
|
|
732
774
|
msg.selectedScope,
|
|
775
|
+
undefined,
|
|
776
|
+
{ source: 'button' },
|
|
733
777
|
);
|
|
734
778
|
syncCanonicalStatusFromIpcConfirmationDecision(msg.requestId, msg.decision);
|
|
735
779
|
pendingInteractions.resolve(msg.requestId);
|
|
@@ -909,6 +953,7 @@ export async function handleSessionCreate(
|
|
|
909
953
|
conversationId: conversation.id,
|
|
910
954
|
sourceChannel: transportChannel,
|
|
911
955
|
});
|
|
956
|
+
session.updateClient(sendEvent, false);
|
|
912
957
|
session.setTurnChannelContext({
|
|
913
958
|
userMessageChannel: transportChannel,
|
|
914
959
|
assistantMessageChannel: transportChannel,
|
|
@@ -1154,7 +1199,15 @@ export function handleHistoryRequest(
|
|
|
1154
1199
|
surfaceId: s.surfaceId,
|
|
1155
1200
|
surfaceType: s.surfaceType,
|
|
1156
1201
|
title: s.title,
|
|
1157
|
-
data: {
|
|
1202
|
+
data: {
|
|
1203
|
+
...(s.surfaceType === 'dynamic_page'
|
|
1204
|
+
? {
|
|
1205
|
+
...(s.data.preview ? { preview: s.data.preview } : {}),
|
|
1206
|
+
...(s.data.appId ? { appId: s.data.appId } : {}),
|
|
1207
|
+
...(s.data.appType ? { appType: s.data.appType } : {}),
|
|
1208
|
+
}
|
|
1209
|
+
: {}),
|
|
1210
|
+
} as Record<string, unknown>,
|
|
1158
1211
|
...(s.actions ? { actions: s.actions } : {}),
|
|
1159
1212
|
...(s.display ? { display: s.display } : {}),
|
|
1160
1213
|
})))
|
|
@@ -1251,6 +1304,7 @@ export async function handleRegenerate(
|
|
|
1251
1304
|
conversationId: msg.sessionId,
|
|
1252
1305
|
sourceChannel: regenerateChannel,
|
|
1253
1306
|
});
|
|
1307
|
+
session.updateClient(sendEvent, false);
|
|
1254
1308
|
const requestId = uuid();
|
|
1255
1309
|
session.traceEmitter.emit('request_received', 'Regenerate requested', {
|
|
1256
1310
|
requestId,
|