@vellumai/assistant 0.3.26 → 0.3.28
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 +48 -1
- package/Dockerfile +2 -2
- package/package.json +1 -1
- package/scripts/ipc/generate-swift.ts +6 -2
- package/src/__tests__/agent-loop.test.ts +119 -0
- package/src/__tests__/bundled-asset.test.ts +107 -0
- package/src/__tests__/canonical-guardian-store.test.ts +636 -0
- package/src/__tests__/channel-approval-routes.test.ts +174 -1
- package/src/__tests__/emit-signal-routing-intent.test.ts +43 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +205 -345
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +599 -0
- package/src/__tests__/guardian-dispatch.test.ts +19 -19
- package/src/__tests__/guardian-routing-invariants.test.ts +954 -0
- package/src/__tests__/mcp-cli.test.ts +77 -0
- package/src/__tests__/non-member-access-request.test.ts +31 -29
- package/src/__tests__/notification-decision-fallback.test.ts +61 -3
- package/src/__tests__/notification-decision-strategy.test.ts +17 -0
- package/src/__tests__/notification-guardian-path.test.ts +13 -15
- package/src/__tests__/onboarding-template-contract.test.ts +116 -21
- package/src/__tests__/secret-scanner-executor.test.ts +59 -0
- package/src/__tests__/secret-scanner.test.ts +8 -0
- package/src/__tests__/sensitive-output-placeholders.test.ts +208 -0
- package/src/__tests__/session-runtime-assembly.test.ts +76 -47
- package/src/__tests__/tool-grant-request-escalation.test.ts +497 -0
- package/src/agent/loop.ts +46 -3
- package/src/approvals/guardian-decision-primitive.ts +285 -0
- package/src/approvals/guardian-request-resolvers.ts +539 -0
- package/src/calls/guardian-dispatch.ts +46 -40
- package/src/calls/relay-server.ts +147 -2
- package/src/calls/types.ts +1 -1
- package/src/config/system-prompt.ts +2 -1
- package/src/config/templates/BOOTSTRAP.md +47 -31
- package/src/config/templates/USER.md +5 -0
- package/src/config/update-bulletin-template-path.ts +4 -1
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +22 -17
- package/src/daemon/handlers/guardian-actions.ts +45 -66
- package/src/daemon/ipc-contract/guardian-actions.ts +7 -0
- package/src/daemon/lifecycle.ts +3 -16
- package/src/daemon/server.ts +18 -0
- package/src/daemon/session-agent-loop-handlers.ts +5 -4
- package/src/daemon/session-agent-loop.ts +32 -5
- package/src/daemon/session-process.ts +68 -307
- package/src/daemon/session-runtime-assembly.ts +112 -24
- package/src/daemon/session-tool-setup.ts +1 -0
- package/src/daemon/session.ts +1 -0
- package/src/home-base/prebuilt/seed.ts +2 -1
- package/src/hooks/templates.ts +2 -1
- package/src/memory/canonical-guardian-store.ts +524 -0
- package/src/memory/channel-guardian-store.ts +1 -0
- package/src/memory/db-init.ts +16 -0
- package/src/memory/guardian-action-store.ts +7 -60
- package/src/memory/guardian-approvals.ts +9 -4
- package/src/memory/migrations/036-normalize-phone-identities.ts +289 -0
- package/src/memory/migrations/118-reminder-routing-intent.ts +3 -3
- package/src/memory/migrations/121-canonical-guardian-requests.ts +59 -0
- package/src/memory/migrations/122-canonical-guardian-requester-chat-id.ts +15 -0
- package/src/memory/migrations/123-canonical-guardian-deliveries-destination-index.ts +15 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +5 -0
- package/src/memory/schema-migration.ts +1 -0
- package/src/memory/schema.ts +52 -0
- package/src/notifications/copy-composer.ts +16 -4
- package/src/notifications/decision-engine.ts +57 -0
- package/src/permissions/defaults.ts +2 -0
- package/src/runtime/access-request-helper.ts +137 -0
- package/src/runtime/actor-trust-resolver.ts +225 -0
- package/src/runtime/channel-guardian-service.ts +12 -4
- package/src/runtime/guardian-context-resolver.ts +32 -7
- package/src/runtime/guardian-decision-types.ts +6 -0
- package/src/runtime/guardian-reply-router.ts +687 -0
- package/src/runtime/http-server.ts +8 -0
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +116 -0
- package/src/runtime/routes/conversation-routes.ts +18 -0
- package/src/runtime/routes/guardian-action-routes.ts +100 -109
- package/src/runtime/routes/inbound-message-handler.ts +170 -525
- package/src/runtime/tool-grant-request-helper.ts +195 -0
- package/src/tools/executor.ts +13 -1
- package/src/tools/sensitive-output-placeholders.ts +203 -0
- package/src/tools/tool-approval-handler.ts +44 -1
- package/src/tools/types.ts +11 -0
- package/src/util/bundled-asset.ts +31 -0
- package/src/util/canonicalize-identity.ts +52 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
applyCanonicalGuardianDecision,
|
|
3
|
+
} from '../../approvals/guardian-decision-primitive.js';
|
|
4
|
+
import { getCanonicalGuardianRequest } from '../../memory/canonical-guardian-store.js';
|
|
3
5
|
import type { ApprovalAction } from '../../runtime/channel-approval-types.js';
|
|
4
|
-
import { handleChannelDecision } from '../../runtime/channel-approvals.js';
|
|
5
|
-
import * as pendingInteractions from '../../runtime/pending-interactions.js';
|
|
6
|
-
import { handleAccessRequestDecision } from '../../runtime/routes/access-request-decision.js';
|
|
7
6
|
import { listGuardianDecisionPrompts } from '../../runtime/routes/guardian-action-routes.js';
|
|
8
7
|
import type { GuardianActionDecision, GuardianActionsPendingRequest } from '../ipc-protocol.js';
|
|
9
8
|
import { defineHandlers, log } from './shared.js';
|
|
@@ -16,7 +15,8 @@ export const guardianActionsHandlers = defineHandlers({
|
|
|
16
15
|
ctx.send(socket, { type: 'guardian_actions_pending_response', conversationId: msg.conversationId, prompts });
|
|
17
16
|
},
|
|
18
17
|
|
|
19
|
-
guardian_action_decision: (msg: GuardianActionDecision, socket, ctx) => {
|
|
18
|
+
guardian_action_decision: async (msg: GuardianActionDecision, socket, ctx) => {
|
|
19
|
+
try {
|
|
20
20
|
// Validate the action is one of the known actions
|
|
21
21
|
if (!VALID_ACTIONS.has(msg.action)) {
|
|
22
22
|
log.warn({ requestId: msg.requestId, action: msg.action }, 'Invalid guardian action');
|
|
@@ -29,92 +29,71 @@ export const guardianActionsHandlers = defineHandlers({
|
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
log.warn({ requestId: msg.requestId, expected:
|
|
32
|
+
// Verify conversationId scoping before applying the canonical decision.
|
|
33
|
+
// A caller must not be able to cross-resolve requests from a different conversation.
|
|
34
|
+
if (msg.conversationId) {
|
|
35
|
+
const canonicalRequest = getCanonicalGuardianRequest(msg.requestId);
|
|
36
|
+
if (canonicalRequest && canonicalRequest.conversationId && canonicalRequest.conversationId !== msg.conversationId) {
|
|
37
|
+
log.warn({ requestId: msg.requestId, expected: canonicalRequest.conversationId, got: msg.conversationId }, 'conversationId mismatch');
|
|
38
38
|
ctx.send(socket, {
|
|
39
39
|
type: 'guardian_action_decision_response',
|
|
40
40
|
applied: false,
|
|
41
|
-
reason: '
|
|
41
|
+
reason: 'not_found',
|
|
42
42
|
requestId: msg.requestId,
|
|
43
43
|
});
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
|
-
|
|
47
|
-
// Access request approvals need a separate decision path — they don't have
|
|
48
|
-
// pending interactions and use verification sessions instead.
|
|
49
|
-
if (approval.toolName === 'ingress_access_request') {
|
|
50
|
-
const mappedAction = msg.action === 'reject' ? 'deny' as const : 'approve' as const;
|
|
51
|
-
// Use 'desktop' as the actor identity because this endpoint is
|
|
52
|
-
// unauthenticated — we cannot verify the caller is the assigned
|
|
53
|
-
// guardian, so we record a generic desktop origin instead of
|
|
54
|
-
// falsely attributing the decision to guardianExternalUserId.
|
|
55
|
-
const decisionResult = handleAccessRequestDecision(
|
|
56
|
-
approval,
|
|
57
|
-
mappedAction,
|
|
58
|
-
'desktop',
|
|
59
|
-
);
|
|
60
|
-
ctx.send(socket, {
|
|
61
|
-
type: 'guardian_action_decision_response',
|
|
62
|
-
applied: decisionResult.type !== 'stale',
|
|
63
|
-
requestId: msg.requestId,
|
|
64
|
-
reason: decisionResult.type === 'stale' ? 'stale' : undefined,
|
|
65
|
-
});
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const result = applyGuardianDecision({
|
|
70
|
-
approval,
|
|
71
|
-
decision: { action: msg.action as 'approve_once' | 'approve_always' | 'reject', source: 'plain_text', requestId: msg.requestId },
|
|
72
|
-
actorExternalUserId: undefined,
|
|
73
|
-
actorChannel: 'vellum',
|
|
74
|
-
});
|
|
75
|
-
ctx.send(socket, {
|
|
76
|
-
type: 'guardian_action_decision_response',
|
|
77
|
-
applied: result.applied,
|
|
78
|
-
reason: result.reason,
|
|
79
|
-
requestId: result.requestId ?? msg.requestId,
|
|
80
|
-
});
|
|
81
|
-
return;
|
|
82
46
|
}
|
|
83
47
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
48
|
+
const canonicalResult = await applyCanonicalGuardianDecision({
|
|
49
|
+
requestId: msg.requestId,
|
|
50
|
+
action: msg.action as ApprovalAction,
|
|
51
|
+
actorContext: {
|
|
52
|
+
externalUserId: undefined,
|
|
53
|
+
channel: 'vellum',
|
|
54
|
+
isTrusted: true,
|
|
55
|
+
},
|
|
56
|
+
userText: undefined,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (canonicalResult.applied) {
|
|
60
|
+
// When the CAS committed but the resolver failed, the side effect
|
|
61
|
+
// (e.g. minting a verification session) did not happen. From the
|
|
62
|
+
// caller's perspective the decision was not truly applied.
|
|
63
|
+
if (canonicalResult.resolverFailed) {
|
|
91
64
|
ctx.send(socket, {
|
|
92
65
|
type: 'guardian_action_decision_response',
|
|
93
66
|
applied: false,
|
|
94
|
-
reason: '
|
|
95
|
-
|
|
67
|
+
reason: 'resolver_failed',
|
|
68
|
+
resolverFailureReason: canonicalResult.resolverFailureReason,
|
|
69
|
+
requestId: canonicalResult.requestId,
|
|
96
70
|
});
|
|
97
71
|
return;
|
|
98
72
|
}
|
|
99
73
|
|
|
100
|
-
const result = handleChannelDecision(
|
|
101
|
-
interaction.conversationId,
|
|
102
|
-
{ action: msg.action as ApprovalAction, source: 'plain_text', requestId: msg.requestId },
|
|
103
|
-
);
|
|
104
74
|
ctx.send(socket, {
|
|
105
75
|
type: 'guardian_action_decision_response',
|
|
106
|
-
applied:
|
|
107
|
-
requestId:
|
|
76
|
+
applied: true,
|
|
77
|
+
requestId: canonicalResult.requestId,
|
|
108
78
|
});
|
|
109
79
|
return;
|
|
110
80
|
}
|
|
111
81
|
|
|
112
|
-
|
|
82
|
+
// Return the reason for failure (stale, expired, not_found, etc.)
|
|
113
83
|
ctx.send(socket, {
|
|
114
84
|
type: 'guardian_action_decision_response',
|
|
115
85
|
applied: false,
|
|
116
|
-
reason:
|
|
86
|
+
reason: canonicalResult.reason,
|
|
117
87
|
requestId: msg.requestId,
|
|
118
88
|
});
|
|
89
|
+
} catch (err) {
|
|
90
|
+
log.error({ err, requestId: msg.requestId }, 'guardian_action_decision: unhandled error');
|
|
91
|
+
ctx.send(socket, {
|
|
92
|
+
type: 'guardian_action_decision_response',
|
|
93
|
+
applied: false,
|
|
94
|
+
reason: 'internal_error',
|
|
95
|
+
requestId: msg.requestId,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
119
98
|
},
|
|
120
99
|
});
|
|
@@ -31,6 +31,12 @@ export interface GuardianActionsPendingResponse {
|
|
|
31
31
|
expiresAt: number;
|
|
32
32
|
conversationId: string;
|
|
33
33
|
callSessionId: string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Canonical request kind (e.g. 'tool_approval', 'pending_question').
|
|
36
|
+
* Present when the prompt originates from the canonical guardian request
|
|
37
|
+
* store. Absent for legacy-only prompts.
|
|
38
|
+
*/
|
|
39
|
+
kind?: string;
|
|
34
40
|
}>;
|
|
35
41
|
}
|
|
36
42
|
|
|
@@ -38,6 +44,7 @@ export interface GuardianActionDecisionResponse {
|
|
|
38
44
|
type: 'guardian_action_decision_response';
|
|
39
45
|
applied: boolean;
|
|
40
46
|
reason?: string;
|
|
47
|
+
resolverFailureReason?: string;
|
|
41
48
|
requestId?: string;
|
|
42
49
|
userText?: string;
|
|
43
50
|
}
|
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -59,7 +59,6 @@ import type { ServerMessage } from './ipc-protocol.js';
|
|
|
59
59
|
import { initializeProvidersAndTools, registerMessagingProviders,registerWatcherProviders } from './providers-setup.js';
|
|
60
60
|
import { seedInterfaceFiles } from './seed-files.js';
|
|
61
61
|
import { DaemonServer } from './server.js';
|
|
62
|
-
import { setApprovalConversationGenerator, setGuardianActionCopyGenerator, setGuardianFollowUpConversationGenerator } from './session-process.js';
|
|
63
62
|
import { initSlashPairingContext } from './session-slash.js';
|
|
64
63
|
import { installShutdownHandlers } from './shutdown-handlers.js';
|
|
65
64
|
|
|
@@ -320,21 +319,9 @@ export async function runDaemon(): Promise<void> {
|
|
|
320
319
|
server.persistAndProcessMessage(conversationId, content, attachmentIds, options, sourceChannel, sourceInterface),
|
|
321
320
|
interfacesDir: getInterfacesDir(),
|
|
322
321
|
approvalCopyGenerator: createApprovalCopyGenerator(),
|
|
323
|
-
approvalConversationGenerator: (
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
return gen;
|
|
327
|
-
})(),
|
|
328
|
-
guardianActionCopyGenerator: (() => {
|
|
329
|
-
const gen = createGuardianActionCopyGenerator();
|
|
330
|
-
setGuardianActionCopyGenerator(gen);
|
|
331
|
-
return gen;
|
|
332
|
-
})(),
|
|
333
|
-
guardianFollowUpConversationGenerator: (() => {
|
|
334
|
-
const gen = createGuardianFollowUpConversationGenerator();
|
|
335
|
-
setGuardianFollowUpConversationGenerator(gen);
|
|
336
|
-
return gen;
|
|
337
|
-
})(),
|
|
322
|
+
approvalConversationGenerator: createApprovalConversationGenerator(),
|
|
323
|
+
guardianActionCopyGenerator: createGuardianActionCopyGenerator(),
|
|
324
|
+
guardianFollowUpConversationGenerator: createGuardianFollowUpConversationGenerator(),
|
|
338
325
|
sendMessageDeps: {
|
|
339
326
|
getOrCreateSession: (conversationId) =>
|
|
340
327
|
server.getSessionForMessages(conversationId),
|
package/src/daemon/server.ts
CHANGED
|
@@ -10,6 +10,10 @@ import { buildSystemPrompt } from '../config/system-prompt.js';
|
|
|
10
10
|
import type { HeartbeatService } from '../heartbeat/heartbeat-service.js';
|
|
11
11
|
import { bootstrapHomeBaseAppLink } from '../home-base/bootstrap.js';
|
|
12
12
|
import * as attachmentsStore from '../memory/attachments-store.js';
|
|
13
|
+
import {
|
|
14
|
+
createCanonicalGuardianRequest,
|
|
15
|
+
generateCanonicalRequestCode,
|
|
16
|
+
} from '../memory/canonical-guardian-store.js';
|
|
13
17
|
import * as conversationStore from '../memory/conversation-store.js';
|
|
14
18
|
import { provenanceFromGuardianContext } from '../memory/conversation-store.js';
|
|
15
19
|
import { RateLimitProvider } from '../providers/ratelimit.js';
|
|
@@ -114,6 +118,20 @@ function makePendingInteractionRegistrar(
|
|
|
114
118
|
persistentDecisionsAllowed: msg.persistentDecisionsAllowed,
|
|
115
119
|
},
|
|
116
120
|
});
|
|
121
|
+
|
|
122
|
+
// Create a canonical guardian request so IPC/HTTP handlers can find it
|
|
123
|
+
// via applyCanonicalGuardianDecision.
|
|
124
|
+
createCanonicalGuardianRequest({
|
|
125
|
+
id: msg.requestId,
|
|
126
|
+
kind: 'tool_approval',
|
|
127
|
+
sourceType: 'desktop',
|
|
128
|
+
sourceChannel: 'vellum',
|
|
129
|
+
conversationId,
|
|
130
|
+
toolName: msg.toolName,
|
|
131
|
+
status: 'pending',
|
|
132
|
+
requestCode: generateCanonicalRequestCode(),
|
|
133
|
+
expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
|
|
134
|
+
});
|
|
117
135
|
} else if (msg.type === 'secret_request') {
|
|
118
136
|
pendingInteractions.register(msg.requestId, {
|
|
119
137
|
session,
|
|
@@ -330,6 +330,7 @@ export async function handleMessageComplete(
|
|
|
330
330
|
// Clean assistant content and accumulate directives
|
|
331
331
|
const { cleanedContent, directives: msgDirectives, warnings: msgWarnings } =
|
|
332
332
|
cleanAssistantContent(event.message.content);
|
|
333
|
+
const cleanedBlocks = cleanedContent as ContentBlock[];
|
|
333
334
|
state.accumulatedDirectives.push(...msgDirectives);
|
|
334
335
|
state.directiveWarnings.push(...msgWarnings);
|
|
335
336
|
if (msgDirectives.length > 0) {
|
|
@@ -340,7 +341,7 @@ export async function handleMessageComplete(
|
|
|
340
341
|
}
|
|
341
342
|
|
|
342
343
|
// Build content with UI surfaces
|
|
343
|
-
const contentWithSurfaces: ContentBlock[] = [...
|
|
344
|
+
const contentWithSurfaces: ContentBlock[] = [...cleanedBlocks];
|
|
344
345
|
for (const surface of deps.ctx.currentTurnSurfaces) {
|
|
345
346
|
contentWithSurfaces.push({
|
|
346
347
|
type: 'ui_surface',
|
|
@@ -371,9 +372,9 @@ export async function handleMessageComplete(
|
|
|
371
372
|
deps.ctx.currentTurnSurfaces = [];
|
|
372
373
|
|
|
373
374
|
// Emit trace event
|
|
374
|
-
const charCount =
|
|
375
|
-
.filter((b)
|
|
376
|
-
.reduce((sum
|
|
375
|
+
const charCount = cleanedBlocks
|
|
376
|
+
.filter((b): b is Extract<ContentBlock, { type: 'text' }> => b.type === 'text')
|
|
377
|
+
.reduce((sum, b) => sum + b.text.length, 0);
|
|
377
378
|
const toolUseCount = event.message.content
|
|
378
379
|
.filter((b) => b.type === 'tool_use')
|
|
379
380
|
.length;
|
|
@@ -25,6 +25,7 @@ import { stripMemoryRecallMessages } from '../memory/retriever.js';
|
|
|
25
25
|
import type { PermissionPrompter } from '../permissions/prompter.js';
|
|
26
26
|
import type { ContentBlock,Message } from '../providers/types.js';
|
|
27
27
|
import type { Provider } from '../providers/types.js';
|
|
28
|
+
import { resolveActorTrust } from '../runtime/actor-trust-resolver.js';
|
|
28
29
|
import type { UsageActor } from '../usage/actors.js';
|
|
29
30
|
import { getLogger } from '../util/logger.js';
|
|
30
31
|
import { truncate } from '../util/truncate.js';
|
|
@@ -55,9 +56,11 @@ import { raceWithTimeout,stripMediaPayloadsForRetry } from './session-media-retr
|
|
|
55
56
|
import { prepareMemoryContext } from './session-memory.js';
|
|
56
57
|
import type { MessageQueue } from './session-queue-manager.js';
|
|
57
58
|
import type { QueueDrainReason } from './session-queue-manager.js';
|
|
58
|
-
import type { ActiveSurfaceContext, ChannelCapabilities, ChannelTurnContextParams, GuardianRuntimeContext,InterfaceTurnContextParams } from './session-runtime-assembly.js';
|
|
59
|
+
import type { ActiveSurfaceContext, ChannelCapabilities, ChannelTurnContextParams, GuardianRuntimeContext, InboundActorContext, InterfaceTurnContextParams } from './session-runtime-assembly.js';
|
|
59
60
|
import {
|
|
60
61
|
applyRuntimeInjections,
|
|
62
|
+
inboundActorContextFromGuardian,
|
|
63
|
+
inboundActorContextFromTrust,
|
|
61
64
|
stripInjectedContext,
|
|
62
65
|
} from './session-runtime-assembly.js';
|
|
63
66
|
import type { SkillProjectionCache } from './session-skill-tools.js';
|
|
@@ -102,6 +105,7 @@ export interface AgentLoopSessionContext {
|
|
|
102
105
|
channelCapabilities?: ChannelCapabilities;
|
|
103
106
|
commandIntent?: { type: string; payload?: string; languageCode?: string };
|
|
104
107
|
guardianContext?: GuardianRuntimeContext;
|
|
108
|
+
assistantId?: string;
|
|
105
109
|
voiceCallControlPrompt?: string;
|
|
106
110
|
|
|
107
111
|
readonly coreToolNames: Set<string>;
|
|
@@ -349,6 +353,28 @@ export async function runAgentLoopImpl(
|
|
|
349
353
|
conversationOriginInterface: getConversationOriginInterface(ctx.conversationId),
|
|
350
354
|
};
|
|
351
355
|
|
|
356
|
+
// Resolve the inbound actor context for the model's <inbound_actor_context>
|
|
357
|
+
// block. When the session carries enough identity info, use the unified
|
|
358
|
+
// actor trust resolver so trusted_contact classifications propagate
|
|
359
|
+
// correctly (the legacy guardian-context path collapses non-guardian to
|
|
360
|
+
// 'unknown'). The guardian context is still used for policy gating — only
|
|
361
|
+
// the model context block uses the trust-resolved output.
|
|
362
|
+
let resolvedInboundActorContext: InboundActorContext | null = null;
|
|
363
|
+
if (ctx.guardianContext) {
|
|
364
|
+
const gc = ctx.guardianContext;
|
|
365
|
+
if (gc.requesterExternalUserId && gc.requesterChatId) {
|
|
366
|
+
const actorTrust = resolveActorTrust({
|
|
367
|
+
assistantId: ctx.assistantId ?? 'self',
|
|
368
|
+
sourceChannel: gc.sourceChannel,
|
|
369
|
+
externalChatId: gc.requesterChatId,
|
|
370
|
+
senderExternalUserId: gc.requesterExternalUserId,
|
|
371
|
+
});
|
|
372
|
+
resolvedInboundActorContext = inboundActorContextFromTrust(actorTrust);
|
|
373
|
+
} else {
|
|
374
|
+
resolvedInboundActorContext = inboundActorContextFromGuardian(gc);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
352
378
|
const isInteractiveResolved = options?.isInteractive ?? (!ctx.hasNoClient && !ctx.headlessLock);
|
|
353
379
|
runMessages = applyRuntimeInjections(runMessages, {
|
|
354
380
|
softConflictInstruction,
|
|
@@ -358,7 +384,7 @@ export async function runAgentLoopImpl(
|
|
|
358
384
|
channelCommandContext: ctx.commandIntent ?? null,
|
|
359
385
|
channelTurnContext,
|
|
360
386
|
interfaceTurnContext,
|
|
361
|
-
|
|
387
|
+
inboundActorContext: resolvedInboundActorContext,
|
|
362
388
|
temporalContext,
|
|
363
389
|
voiceCallControlPrompt: ctx.voiceCallControlPrompt ?? null,
|
|
364
390
|
isNonInteractive: !isInteractiveResolved,
|
|
@@ -477,7 +503,7 @@ export async function runAgentLoopImpl(
|
|
|
477
503
|
channelCommandContext: ctx.commandIntent ?? null,
|
|
478
504
|
channelTurnContext,
|
|
479
505
|
interfaceTurnContext,
|
|
480
|
-
|
|
506
|
+
inboundActorContext: resolvedInboundActorContext,
|
|
481
507
|
temporalContext,
|
|
482
508
|
voiceCallControlPrompt: ctx.voiceCallControlPrompt ?? null,
|
|
483
509
|
isNonInteractive: !isInteractiveResolved,
|
|
@@ -515,7 +541,7 @@ export async function runAgentLoopImpl(
|
|
|
515
541
|
channelCommandContext: ctx.commandIntent ?? null,
|
|
516
542
|
channelTurnContext,
|
|
517
543
|
interfaceTurnContext,
|
|
518
|
-
|
|
544
|
+
inboundActorContext: resolvedInboundActorContext,
|
|
519
545
|
temporalContext,
|
|
520
546
|
voiceCallControlPrompt: ctx.voiceCallControlPrompt ?? null,
|
|
521
547
|
isNonInteractive: !isInteractiveResolved,
|
|
@@ -604,7 +630,8 @@ export async function runAgentLoopImpl(
|
|
|
604
630
|
const newMessages = updatedHistory.slice(preRunHistoryLength).map((msg) => {
|
|
605
631
|
if (msg.role !== 'assistant') return msg;
|
|
606
632
|
const { cleanedContent } = cleanAssistantContent(msg.content);
|
|
607
|
-
|
|
633
|
+
const cleanedBlocks = cleanedContent as ContentBlock[];
|
|
634
|
+
return { ...msg, content: cleanedBlocks };
|
|
608
635
|
});
|
|
609
636
|
|
|
610
637
|
const hasAssistantResponse = newMessages.some((msg) => msg.role === 'assistant');
|