@vellumai/assistant 0.4.29 → 0.4.31
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 +39 -37
- package/Dockerfile +14 -8
- package/README.md +7 -8
- package/docs/architecture/memory.md +28 -29
- package/docs/runbook-trusted-contacts.md +76 -43
- package/package.json +1 -1
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -3
- package/scripts/test.sh +1 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +4 -37
- package/src/__tests__/actor-token-service.test.ts +4 -3
- package/src/__tests__/app-executors.test.ts +7 -17
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -10
- package/src/__tests__/browser-skill-endstate.test.ts +10 -1
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -0
- package/src/__tests__/channel-approval-routes.test.ts +44 -44
- package/src/__tests__/channel-approval.test.ts +8 -0
- package/src/__tests__/channel-approvals.test.ts +39 -1
- package/src/__tests__/channel-guardian.test.ts +15 -5
- package/src/__tests__/channel-reply-delivery.test.ts +31 -0
- package/src/__tests__/config-schema.test.ts +0 -9
- package/src/__tests__/conflict-policy.test.ts +76 -0
- package/src/__tests__/conflict-store.test.ts +14 -20
- package/src/__tests__/contacts-tools.test.ts +8 -61
- package/src/__tests__/contradiction-checker.test.ts +5 -1
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +9 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/gemini-image-service.test.ts +2 -2
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
- package/src/__tests__/guardian-grant-minting.test.ts +6 -6
- package/src/__tests__/guardian-routing-invariants.test.ts +40 -15
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +4 -6
- package/src/__tests__/inbound-invite-redemption.test.ts +1 -1
- package/src/__tests__/integrations-cli.test.ts +3 -27
- package/src/__tests__/intent-routing.test.ts +3 -0
- package/src/__tests__/invite-redemption-service.test.ts +1 -1
- package/src/__tests__/{ingress-routes-http.test.ts → invite-routes-http.test.ts} +40 -320
- package/src/__tests__/ipc-snapshot.test.ts +4 -31
- package/src/__tests__/memory-lifecycle-e2e.test.ts +11 -10
- package/src/__tests__/nl-approval-parser.test.ts +305 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +34 -0
- package/src/__tests__/provider-error-scenarios.test.ts +68 -0
- package/src/__tests__/registry.test.ts +0 -10
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/retry-after-extraction.test.ts +111 -0
- package/src/__tests__/script-proxy-profile-template-fallback.test.ts +127 -0
- package/src/__tests__/script-proxy-session-runtime.test.ts +6 -1
- package/src/__tests__/session-agent-loop.test.ts +0 -2
- package/src/__tests__/session-conflict-gate.test.ts +243 -388
- package/src/__tests__/session-media-retry.test.ts +147 -0
- package/src/__tests__/session-profile-injection.test.ts +0 -2
- package/src/__tests__/session-runtime-assembly.test.ts +2 -3
- package/src/__tests__/session-skill-tools.test.ts +0 -49
- package/src/__tests__/session-workspace-cache-state.test.ts +0 -1
- package/src/__tests__/session-workspace-injection.test.ts +0 -1
- package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +9 -5
- package/src/__tests__/skill-feature-flags.test.ts +18 -12
- package/src/__tests__/skill-load-feature-flag.test.ts +4 -3
- package/src/__tests__/slack-block-formatting.test.ts +100 -0
- package/src/__tests__/slack-inbound-verification.test.ts +346 -0
- package/src/__tests__/slack-reaction-approvals.test.ts +77 -0
- package/src/__tests__/slack-skill.test.ts +3 -2
- package/src/__tests__/starter-task-flow.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +2 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -1
- package/src/__tests__/trusted-contact-verification.test.ts +3 -1
- package/src/__tests__/voice-invite-redemption.test.ts +1 -1
- package/src/amazon/client.ts +7 -24
- package/src/approvals/guardian-decision-primitive.ts +11 -7
- package/src/approvals/guardian-request-resolvers.ts +5 -3
- package/src/calls/relay-server.ts +44 -11
- package/src/channels/config.ts +1 -1
- package/src/cli/integrations.ts +10 -66
- package/src/config/bundled-skills/app-builder/SKILL.md +193 -1500
- package/src/config/bundled-skills/app-builder/TOOLS.json +70 -18
- package/src/config/bundled-skills/browser/TOOLS.json +59 -2
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +4 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +50 -2
- package/src/config/bundled-skills/contacts/SKILL.md +49 -53
- package/src/config/bundled-skills/contacts/TOOLS.json +26 -22
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +40 -62
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +17 -43
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +18 -57
- package/src/config/bundled-skills/document/TOOLS.json +8 -0
- package/src/config/bundled-skills/email-setup/SKILL.md +10 -7
- package/src/config/bundled-skills/followups/TOOLS.json +12 -0
- package/src/config/bundled-skills/google-calendar/TOOLS.json +124 -26
- package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +54 -21
- package/src/config/bundled-skills/image-studio/TOOLS.json +12 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +14 -8
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +13 -3
- package/src/config/bundled-skills/media-processing/SKILL.md +1 -1
- package/src/config/bundled-skills/media-processing/TOOLS.json +28 -0
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +26 -6
- package/src/config/bundled-skills/messaging/TOOLS.json +228 -182
- package/src/config/bundled-skills/notifications/SKILL.md +3 -2
- package/src/config/bundled-skills/notifications/TOOLS.json +7 -13
- package/src/config/bundled-skills/phone-calls/TOOLS.json +13 -1
- package/src/config/bundled-skills/playbooks/TOOLS.json +16 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +15 -2
- package/src/config/bundled-skills/schedule/SKILL.md +33 -15
- package/src/config/bundled-skills/schedule/TOOLS.json +17 -1
- package/src/config/bundled-skills/slack/SKILL.md +30 -1
- package/src/config/bundled-skills/slack/TOOLS.json +89 -2
- package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +146 -0
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +120 -0
- package/src/config/bundled-skills/slack-app-setup/SKILL.md +200 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +22 -2
- package/src/config/bundled-skills/tasks/TOOLS.json +86 -14
- package/src/config/bundled-skills/transcribe/TOOLS.json +4 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +20 -0
- package/src/config/bundled-tool-registry.ts +2 -5
- package/src/config/channel-permission-profiles.ts +155 -0
- package/src/config/env.ts +4 -1
- package/src/config/memory-schema.ts +0 -10
- package/src/config/system-prompt.ts +6 -0
- package/src/contacts/contact-store.ts +221 -56
- package/src/contacts/contacts-write.ts +14 -3
- package/src/contacts/types.ts +35 -4
- package/src/daemon/assistant-attachments.ts +23 -3
- package/src/daemon/guardian-verification-intent.ts +7 -4
- package/src/daemon/handlers/apps.ts +1 -2
- package/src/daemon/handlers/config-heartbeat.ts +1 -2
- package/src/daemon/handlers/config-inbox.ts +16 -134
- package/src/daemon/handlers/contacts.ts +2 -2
- package/src/daemon/handlers/guardian-actions.ts +21 -88
- package/src/daemon/handlers/sessions.ts +2 -2
- package/src/daemon/ipc-contract/apps.ts +0 -1
- package/src/daemon/ipc-contract/contacts.ts +2 -2
- package/src/daemon/ipc-contract/inbox.ts +7 -66
- package/src/daemon/ipc-contract/sessions.ts +1 -0
- package/src/daemon/ipc-contract/surfaces.ts +0 -1
- package/src/daemon/ipc-contract-inventory.json +2 -4
- package/src/daemon/lifecycle.ts +14 -2
- package/src/daemon/session-agent-loop-handlers.ts +9 -0
- package/src/daemon/session-agent-loop.ts +2 -45
- package/src/daemon/session-attachments.ts +5 -1
- package/src/daemon/session-conflict-gate.ts +21 -82
- package/src/daemon/session-error.ts +18 -0
- package/src/daemon/session-lifecycle.ts +4 -5
- package/src/daemon/session-media-retry.ts +15 -1
- package/src/daemon/session-memory.ts +7 -52
- package/src/daemon/session-process.ts +3 -1
- package/src/daemon/session-runtime-assembly.ts +18 -35
- package/src/daemon/session-surfaces.ts +0 -1
- package/src/daemon/session-tool-setup.ts +7 -4
- package/src/events/domain-events.ts +2 -1
- package/src/heartbeat/heartbeat-service.ts +5 -1
- package/src/home-base/prebuilt/seed.ts +0 -1
- package/src/influencer/client.ts +7 -24
- package/src/media/gemini-image-service.ts +48 -3
- package/src/memory/app-store.ts +0 -4
- package/src/memory/conflict-intent.ts +3 -6
- package/src/memory/conflict-policy.ts +34 -0
- package/src/memory/conflict-store.ts +10 -18
- package/src/memory/contradiction-checker.ts +2 -2
- package/src/memory/conversation-attention-store.ts +3 -1
- package/src/memory/db-init.ts +8 -0
- package/src/memory/job-handlers/conflict.ts +0 -7
- package/src/memory/migrations/133-assistant-contact-metadata.ts +21 -0
- package/src/memory/migrations/134-contacts-notes-column.ts +51 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +31 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/schema.ts +12 -17
- package/src/memory/slack-thread-store.ts +187 -0
- package/src/messaging/index.ts +0 -1
- package/src/messaging/providers/slack/client.ts +84 -26
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/messaging/types.ts +0 -38
- package/src/notifications/adapters/slack.ts +90 -0
- package/src/notifications/destination-resolver.ts +42 -1
- package/src/notifications/emit-signal.ts +17 -1
- package/src/oauth/provider-profiles.ts +22 -0
- package/src/providers/anthropic/client.ts +3 -0
- package/src/providers/openai/client.ts +3 -0
- package/src/providers/retry.ts +9 -1
- package/src/runtime/actor-trust-resolver.ts +8 -0
- package/src/runtime/auth/require-bound-guardian.ts +44 -0
- package/src/runtime/auth/route-policy.ts +4 -8
- package/src/runtime/channel-approval-types.ts +18 -0
- package/src/runtime/channel-approvals.ts +8 -0
- package/src/runtime/channel-invite-transport.ts +1 -1
- package/src/runtime/channel-reply-delivery.ts +62 -3
- package/src/runtime/gateway-client.ts +36 -2
- package/src/runtime/gateway-internal-client.ts +86 -0
- package/src/runtime/guardian-action-service.ts +128 -0
- package/src/runtime/guardian-outbound-actions.ts +3 -3
- package/src/runtime/guardian-reply-router.ts +4 -4
- package/src/runtime/guardian-verification-templates.ts +16 -1
- package/src/runtime/http-server.ts +29 -46
- package/src/runtime/invite-redemption-service.ts +1 -1
- package/src/runtime/{ingress-service.ts → invite-service.ts} +5 -157
- package/src/runtime/nl-approval-parser.ts +138 -0
- package/src/runtime/routes/approval-routes.ts +1 -40
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +6 -3
- package/src/runtime/routes/channel-route-shared.ts +35 -1
- package/src/runtime/routes/contact-routes.ts +494 -47
- package/src/runtime/routes/conversation-routes.ts +2 -1
- package/src/runtime/routes/global-search-routes.ts +2 -2
- package/src/runtime/routes/guardian-action-routes.ts +19 -111
- package/src/runtime/routes/guardian-approval-interception.ts +78 -1
- package/src/runtime/routes/guardian-bootstrap-routes.ts +6 -1
- package/src/runtime/routes/inbound-message-handler.ts +40 -12
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +227 -1
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +108 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +2 -1
- package/src/runtime/routes/{ingress-routes.ts → invite-routes.ts} +10 -110
- package/src/runtime/routes/migration-routes.ts +17 -17
- package/src/runtime/slack-block-formatting.ts +176 -0
- package/src/schedule/scheduler.ts +11 -2
- package/src/tools/apps/executors.ts +16 -15
- package/src/tools/calls/call-end.ts +1 -1
- package/src/tools/computer-use/definitions.ts +16 -0
- package/src/tools/credentials/vault.ts +86 -2
- package/src/tools/network/script-proxy/session-manager.ts +28 -3
- package/src/tools/permission-checker.ts +18 -0
- package/src/tools/terminal/shell.ts +15 -5
- package/src/tools/tool-approval-handler.ts +48 -4
- package/src/tools/types.ts +38 -1
- package/src/util/errors.ts +5 -1
- package/src/util/retry.ts +21 -0
- package/src/watcher/providers/slack.ts +33 -3
- package/src/workspace/git-service.ts +6 -4
- package/src/__tests__/get-weather.test.ts +0 -393
- package/src/__tests__/weather-skill-regression.test.ts +0 -276
- package/src/autonomy/autonomy-resolver.ts +0 -62
- package/src/autonomy/autonomy-store.ts +0 -138
- package/src/autonomy/disposition-mapper.ts +0 -31
- package/src/autonomy/index.ts +0 -11
- package/src/autonomy/types.ts +0 -43
- package/src/config/bundled-skills/weather/SKILL.md +0 -38
- package/src/config/bundled-skills/weather/TOOLS.json +0 -32
- package/src/config/bundled-skills/weather/icon.svg +0 -24
- package/src/config/bundled-skills/weather/tools/get-weather.ts +0 -12
- package/src/messaging/triage-engine.ts +0 -344
- package/src/tools/weather/service.ts +0 -712
- /package/src/memory/{ingress-invite-store.ts → invite-store.ts} +0 -0
|
@@ -453,50 +453,7 @@ export async function runAgentLoopImpl(
|
|
|
453
453
|
onEvent,
|
|
454
454
|
);
|
|
455
455
|
|
|
456
|
-
|
|
457
|
-
const loopChannelMeta = {
|
|
458
|
-
...provenanceFromTrustContext(ctx.trustContext),
|
|
459
|
-
userMessageChannel: capturedTurnChannelContext.userMessageChannel,
|
|
460
|
-
assistantMessageChannel:
|
|
461
|
-
capturedTurnChannelContext.assistantMessageChannel,
|
|
462
|
-
userMessageInterface: capturedTurnInterfaceContext.userMessageInterface,
|
|
463
|
-
assistantMessageInterface:
|
|
464
|
-
capturedTurnInterfaceContext.assistantMessageInterface,
|
|
465
|
-
};
|
|
466
|
-
const assistantMessage = createAssistantMessage(
|
|
467
|
-
memoryResult.conflictClarification,
|
|
468
|
-
);
|
|
469
|
-
await conversationStore.addMessage(
|
|
470
|
-
ctx.conversationId,
|
|
471
|
-
"assistant",
|
|
472
|
-
JSON.stringify(assistantMessage.content),
|
|
473
|
-
loopChannelMeta,
|
|
474
|
-
);
|
|
475
|
-
ctx.messages.push(assistantMessage);
|
|
476
|
-
onEvent({
|
|
477
|
-
type: "assistant_text_delta",
|
|
478
|
-
text: memoryResult.conflictClarification,
|
|
479
|
-
sessionId: ctx.conversationId,
|
|
480
|
-
});
|
|
481
|
-
ctx.traceEmitter.emit(
|
|
482
|
-
"message_complete",
|
|
483
|
-
"Conflict clarification requested (relevant)",
|
|
484
|
-
{
|
|
485
|
-
requestId: reqId,
|
|
486
|
-
status: "info",
|
|
487
|
-
attributes: { conflictGate: "relevant" },
|
|
488
|
-
},
|
|
489
|
-
);
|
|
490
|
-
onEvent({ type: "message_complete", sessionId: ctx.conversationId });
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
const {
|
|
495
|
-
recall,
|
|
496
|
-
dynamicProfile,
|
|
497
|
-
softConflictInstruction,
|
|
498
|
-
recallInjectionStrategy,
|
|
499
|
-
} = memoryResult;
|
|
456
|
+
const { recall, dynamicProfile, recallInjectionStrategy } = memoryResult;
|
|
500
457
|
runMessages = memoryResult.runMessages;
|
|
501
458
|
|
|
502
459
|
// Build active surface context
|
|
@@ -585,7 +542,6 @@ export async function runAgentLoopImpl(
|
|
|
585
542
|
|
|
586
543
|
// Shared injection options — reused whenever we need to re-inject after reduction.
|
|
587
544
|
const injectionOpts = {
|
|
588
|
-
softConflictInstruction,
|
|
589
545
|
activeSurface,
|
|
590
546
|
workspaceTopLevelContext: ctx.workspaceTopLevelContext,
|
|
591
547
|
channelCapabilities: ctx.channelCapabilities ?? null,
|
|
@@ -1238,6 +1194,7 @@ export async function runAgentLoopImpl(
|
|
|
1238
1194
|
ctx.hasNoClient,
|
|
1239
1195
|
),
|
|
1240
1196
|
state.lastAssistantMessageId,
|
|
1197
|
+
state.toolContentBlockToolNames,
|
|
1241
1198
|
);
|
|
1242
1199
|
const { assistantAttachments, emittedAttachments } = attachmentResult;
|
|
1243
1200
|
|
|
@@ -124,6 +124,7 @@ export async function resolveAssistantAttachments(
|
|
|
124
124
|
workingDir: string,
|
|
125
125
|
approveHostRead: ApproveHostRead,
|
|
126
126
|
lastAssistantMessageId: string | undefined,
|
|
127
|
+
toolContentBlockToolNames?: ReadonlyMap<number, string>,
|
|
127
128
|
): Promise<AttachmentResolutionResult> {
|
|
128
129
|
let assistantAttachments: AssistantAttachmentDraft[] = [];
|
|
129
130
|
const emittedAttachments: UserMessageAttachment[] = [];
|
|
@@ -170,7 +171,10 @@ export async function resolveAssistantAttachments(
|
|
|
170
171
|
"Directive resolution complete",
|
|
171
172
|
);
|
|
172
173
|
|
|
173
|
-
const toolDrafts = contentBlocksToDrafts(
|
|
174
|
+
const toolDrafts = contentBlocksToDrafts(
|
|
175
|
+
accumulatedToolContentBlocks,
|
|
176
|
+
toolContentBlockToolNames,
|
|
177
|
+
);
|
|
174
178
|
// Most recent tool outputs (e.g., final browser screenshot) should win
|
|
175
179
|
// the MAX_ASSISTANT_ATTACHMENTS cap over older intermediate screenshots.
|
|
176
180
|
toolDrafts.reverse();
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Conflict-gate logic extracted from Session.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Handles pending memory conflicts internally: dismisses non-user-evidenced
|
|
5
|
+
* and non-actionable conflicts, and attempts resolution when the user's reply
|
|
6
|
+
* looks like an explicit clarification with topical relevance. Never produces
|
|
7
|
+
* user-facing clarification text.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
import { resolveConflictClarification } from "../memory/clarification-resolver.js";
|
|
@@ -14,47 +16,34 @@ import {
|
|
|
14
16
|
} from "../memory/conflict-intent.js";
|
|
15
17
|
import {
|
|
16
18
|
isConflictKindPairEligible,
|
|
19
|
+
isConflictUserEvidenced,
|
|
17
20
|
isStatementConflictEligible,
|
|
18
21
|
} from "../memory/conflict-policy.js";
|
|
19
22
|
import type { PendingConflictDetail } from "../memory/conflict-store.js";
|
|
20
23
|
import {
|
|
21
24
|
applyConflictResolution,
|
|
22
25
|
listPendingConflictDetails,
|
|
23
|
-
markConflictAsked,
|
|
24
26
|
resolveConflict,
|
|
25
27
|
} from "../memory/conflict-store.js";
|
|
26
28
|
|
|
27
|
-
export interface ConflictGateDecision {
|
|
28
|
-
question: string;
|
|
29
|
-
relevant: boolean;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
29
|
export class ConflictGate {
|
|
33
|
-
private turnCounter = 0;
|
|
34
|
-
private lastAskedTurn = new Map<string, number>();
|
|
35
|
-
|
|
36
30
|
async evaluate(
|
|
37
31
|
userMessage: string,
|
|
38
32
|
conflictConfig: {
|
|
39
33
|
enabled: boolean;
|
|
40
34
|
gateMode: string;
|
|
41
35
|
relevanceThreshold: number;
|
|
42
|
-
reaskCooldownTurns: number;
|
|
43
36
|
resolverLlmTimeoutMs: number;
|
|
44
|
-
askOnIrrelevantTurns: boolean;
|
|
45
37
|
conflictableKinds: readonly string[];
|
|
46
38
|
},
|
|
47
39
|
scopeId = "default",
|
|
48
|
-
): Promise<
|
|
49
|
-
if (!conflictConfig.enabled || conflictConfig.gateMode !== "soft")
|
|
50
|
-
return null;
|
|
40
|
+
): Promise<void> {
|
|
41
|
+
if (!conflictConfig.enabled || conflictConfig.gateMode !== "soft") return;
|
|
51
42
|
|
|
52
|
-
this.turnCounter += 1;
|
|
53
|
-
const threshold = conflictConfig.relevanceThreshold;
|
|
54
|
-
const cooldownTurns = Math.max(1, conflictConfig.reaskCooldownTurns);
|
|
55
43
|
const pendingBeforeResolve = listPendingConflictDetails(scopeId, 50);
|
|
56
44
|
|
|
57
|
-
// Dismiss non-actionable conflicts (kind/statement policy
|
|
45
|
+
// Dismiss non-actionable conflicts (kind/statement policy, incoherent pair,
|
|
46
|
+
// or assistant-inferred-only provenance with no user evidence)
|
|
58
47
|
const dismissedIds = new Set<string>();
|
|
59
48
|
for (const conflict of pendingBeforeResolve) {
|
|
60
49
|
const dismissReason = this.getDismissReason(
|
|
@@ -73,13 +62,15 @@ export class ConflictGate {
|
|
|
73
62
|
const actionablePending = pendingBeforeResolve.filter(
|
|
74
63
|
(c) => !dismissedIds.has(c.id),
|
|
75
64
|
);
|
|
65
|
+
|
|
66
|
+
// Attempt resolution only for explicit clarification-like replies with
|
|
67
|
+
// topical relevance to the conflict statements
|
|
76
68
|
const clarificationReply = looksLikeClarificationReply(userMessage);
|
|
77
69
|
const candidatesBeforeResolve = actionablePending.filter((conflict) => {
|
|
78
70
|
const relevance = computeConflictRelevance(userMessage, conflict);
|
|
79
71
|
return shouldAttemptConflictResolution({
|
|
80
72
|
clarificationReply,
|
|
81
73
|
relevance,
|
|
82
|
-
wasRecentlyAsked: this.wasRecentlyAsked(conflict.id, cooldownTurns),
|
|
83
74
|
});
|
|
84
75
|
});
|
|
85
76
|
await this.resolvePendingConflicts(
|
|
@@ -87,45 +78,6 @@ export class ConflictGate {
|
|
|
87
78
|
conflictConfig.resolverLlmTimeoutMs,
|
|
88
79
|
candidatesBeforeResolve,
|
|
89
80
|
);
|
|
90
|
-
|
|
91
|
-
const pending = listPendingConflictDetails(scopeId, 50);
|
|
92
|
-
if (pending.length === 0) return null;
|
|
93
|
-
|
|
94
|
-
const scored = pending.map((conflict) => ({
|
|
95
|
-
conflict,
|
|
96
|
-
relevance: computeConflictRelevance(userMessage, conflict),
|
|
97
|
-
}));
|
|
98
|
-
// Try relevant conflicts first
|
|
99
|
-
const askable = scored
|
|
100
|
-
.filter((entry) => entry.relevance >= threshold)
|
|
101
|
-
.find((entry) => this.shouldAsk(entry.conflict.id, cooldownTurns));
|
|
102
|
-
|
|
103
|
-
// If no relevant conflict to ask and askOnIrrelevantTurns is enabled, try ones
|
|
104
|
-
// below the threshold (including zero-relevance). Zero-relevance conflicts are
|
|
105
|
-
// surfaced but not tracked as asked, preventing wasRecentlyAsked from triggering
|
|
106
|
-
// heuristic resolution on subsequent unrelated turns.
|
|
107
|
-
const candidateToAsk =
|
|
108
|
-
askable ??
|
|
109
|
-
(conflictConfig.askOnIrrelevantTurns
|
|
110
|
-
? scored.find(
|
|
111
|
-
(entry) =>
|
|
112
|
-
entry.relevance < threshold &&
|
|
113
|
-
this.shouldAsk(entry.conflict.id, cooldownTurns),
|
|
114
|
-
)
|
|
115
|
-
: undefined);
|
|
116
|
-
|
|
117
|
-
if (!candidateToAsk) return null;
|
|
118
|
-
|
|
119
|
-
if (askable || candidateToAsk.relevance > 0) {
|
|
120
|
-
this.lastAskedTurn.set(candidateToAsk.conflict.id, this.turnCounter);
|
|
121
|
-
markConflictAsked(candidateToAsk.conflict.id);
|
|
122
|
-
}
|
|
123
|
-
return {
|
|
124
|
-
question:
|
|
125
|
-
candidateToAsk.conflict.clarificationQuestion ??
|
|
126
|
-
buildFallbackConflictQuestion(candidateToAsk.conflict),
|
|
127
|
-
relevant: candidateToAsk.relevance >= threshold,
|
|
128
|
-
};
|
|
129
81
|
}
|
|
130
82
|
|
|
131
83
|
private async resolvePendingConflicts(
|
|
@@ -156,18 +108,6 @@ export class ConflictGate {
|
|
|
156
108
|
}
|
|
157
109
|
}
|
|
158
110
|
|
|
159
|
-
private shouldAsk(conflictId: string, cooldownTurns: number): boolean {
|
|
160
|
-
const lastAsked = this.lastAskedTurn.get(conflictId);
|
|
161
|
-
if (lastAsked === undefined) return true;
|
|
162
|
-
return this.turnCounter - lastAsked >= cooldownTurns;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
private wasRecentlyAsked(conflictId: string, cooldownTurns: number): boolean {
|
|
166
|
-
const lastAsked = this.lastAskedTurn.get(conflictId);
|
|
167
|
-
if (lastAsked === undefined) return false;
|
|
168
|
-
return this.turnCounter - lastAsked <= cooldownTurns;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
111
|
/**
|
|
172
112
|
* Returns a dismissal reason if the conflict should be dismissed, or null if actionable.
|
|
173
113
|
*/
|
|
@@ -211,18 +151,17 @@ export class ConflictGate {
|
|
|
211
151
|
) {
|
|
212
152
|
return "Dismissed by conflict policy (incoherent — zero statement overlap).";
|
|
213
153
|
}
|
|
154
|
+
// Dismiss conflicts where neither side has user-evidenced provenance
|
|
155
|
+
if (
|
|
156
|
+
!isConflictUserEvidenced(
|
|
157
|
+
conflict.existingVerificationState,
|
|
158
|
+
conflict.candidateVerificationState,
|
|
159
|
+
)
|
|
160
|
+
) {
|
|
161
|
+
return "Dismissed by conflict policy (no user-evidenced provenance).";
|
|
162
|
+
}
|
|
214
163
|
return null;
|
|
215
164
|
}
|
|
216
165
|
}
|
|
217
166
|
|
|
218
|
-
export function buildFallbackConflictQuestion(
|
|
219
|
-
conflict: PendingConflictDetail,
|
|
220
|
-
): string {
|
|
221
|
-
return [
|
|
222
|
-
"I have two conflicting notes and need your confirmation.",
|
|
223
|
-
`A) ${conflict.existingStatement}`,
|
|
224
|
-
`B) ${conflict.candidateStatement}`,
|
|
225
|
-
"Which one should I keep?",
|
|
226
|
-
].join("\n");
|
|
227
|
-
}
|
|
228
167
|
export { computeConflictRelevance, looksLikeClarificationReply };
|
|
@@ -188,6 +188,24 @@ function classifyCore(
|
|
|
188
188
|
retryable: false,
|
|
189
189
|
};
|
|
190
190
|
}
|
|
191
|
+
if (/credit balance is too low|insufficient.*credits?/i.test(message)) {
|
|
192
|
+
return {
|
|
193
|
+
code: "PROVIDER_BILLING",
|
|
194
|
+
userMessage: "Your API key has insufficient credits.",
|
|
195
|
+
retryable: false,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
if (
|
|
199
|
+
/invalid.*api.?key|invalid.*x-api-key|authentication.?error|invalid.authentication/i.test(
|
|
200
|
+
message,
|
|
201
|
+
)
|
|
202
|
+
) {
|
|
203
|
+
return {
|
|
204
|
+
code: "PROVIDER_BILLING",
|
|
205
|
+
userMessage: "Your API key is invalid.",
|
|
206
|
+
retryable: false,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
191
209
|
return {
|
|
192
210
|
code: "PROVIDER_API",
|
|
193
211
|
userMessage: "The AI provider rejected the request.",
|
|
@@ -13,7 +13,10 @@ import * as conversationStore from "../memory/conversation-store.js";
|
|
|
13
13
|
import type { PermissionPrompter } from "../permissions/prompter.js";
|
|
14
14
|
import type { SecretPrompter } from "../permissions/secret-prompter.js";
|
|
15
15
|
import type { ContentBlock, Message } from "../providers/types.js";
|
|
16
|
-
import
|
|
16
|
+
import {
|
|
17
|
+
isUntrustedTrustClass,
|
|
18
|
+
type TrustClass,
|
|
19
|
+
} from "../runtime/actor-trust-resolver.js";
|
|
17
20
|
import { unregisterSessionSender } from "../tools/browser/browser-screencast.js";
|
|
18
21
|
import { getLogger } from "../util/logger.js";
|
|
19
22
|
import { repairHistory } from "./history-repair.js";
|
|
@@ -47,10 +50,6 @@ function parseProvenanceTrustClass(
|
|
|
47
50
|
return undefined;
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
function isUntrustedTrustClass(trustClass: TrustClass | undefined): boolean {
|
|
51
|
-
return trustClass === "trusted_contact" || trustClass === "unknown";
|
|
52
|
-
}
|
|
53
|
-
|
|
54
53
|
function filterMessagesForUntrustedActor(
|
|
55
54
|
messages: conversationStore.MessageRow[],
|
|
56
55
|
): conversationStore.MessageRow[] {
|
|
@@ -19,15 +19,29 @@ export function stripMediaPayloadsForRetry(messages: Message[]): {
|
|
|
19
19
|
latestUserIndex: number | null;
|
|
20
20
|
} {
|
|
21
21
|
let latestUserIndex: number | null = null;
|
|
22
|
+
let lastSummaryUserIndex: number | null = null;
|
|
22
23
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
23
24
|
const msg = messages[i];
|
|
24
25
|
if (msg.role !== "user") continue;
|
|
25
|
-
if (getSummaryFromContextMessage(msg) != null) continue;
|
|
26
26
|
if (isToolResultOnlyMessage(msg)) continue;
|
|
27
|
+
if (getSummaryFromContextMessage(msg) != null) {
|
|
28
|
+
// Track the last summary message as a fallback — after aggressive
|
|
29
|
+
// compaction (minKeepRecentUserTurns: 0), the summary may be the only
|
|
30
|
+
// user message left and it can contain preserved image blocks that
|
|
31
|
+
// should not be stripped.
|
|
32
|
+
if (lastSummaryUserIndex == null) lastSummaryUserIndex = i;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
27
35
|
latestUserIndex = i;
|
|
28
36
|
break;
|
|
29
37
|
}
|
|
30
38
|
|
|
39
|
+
// Fall back to the summary message when compaction consumed all user turns,
|
|
40
|
+
// so images preserved by compaction are not unconditionally stripped.
|
|
41
|
+
if (latestUserIndex == null && lastSummaryUserIndex != null) {
|
|
42
|
+
latestUserIndex = lastSummaryUserIndex;
|
|
43
|
+
}
|
|
44
|
+
|
|
31
45
|
let modified = false;
|
|
32
46
|
let replacedBlocks = 0;
|
|
33
47
|
let keptLatestMediaBlocks = 0;
|
|
@@ -24,7 +24,6 @@ export interface MemoryRecallResult {
|
|
|
24
24
|
runMessages: Message[];
|
|
25
25
|
recall: Awaited<ReturnType<typeof buildMemoryRecall>>;
|
|
26
26
|
dynamicProfile: { text: string };
|
|
27
|
-
softConflictInstruction: string | null;
|
|
28
27
|
recallInjectionStrategy: RecallInjectionStrategy;
|
|
29
28
|
}
|
|
30
29
|
|
|
@@ -37,7 +36,7 @@ export interface MemoryPrepareContext {
|
|
|
37
36
|
scopeId: string;
|
|
38
37
|
includeDefaultFallback: boolean;
|
|
39
38
|
trustClass: "guardian" | "trusted_contact" | "unknown";
|
|
40
|
-
/** When false (e.g. scheduled tasks), skip conflict
|
|
39
|
+
/** When false (e.g. scheduled tasks), skip conflict gate evaluation. */
|
|
41
40
|
isInteractive?: boolean;
|
|
42
41
|
}
|
|
43
42
|
|
|
@@ -64,7 +63,7 @@ export async function prepareMemoryContext(
|
|
|
64
63
|
userMessageId: string,
|
|
65
64
|
abortSignal: AbortSignal,
|
|
66
65
|
onEvent: (msg: ServerMessage) => void,
|
|
67
|
-
): Promise<MemoryRecallResult
|
|
66
|
+
): Promise<MemoryRecallResult> {
|
|
68
67
|
// Provenance-based trust gating: untrusted actors skip all memory operations
|
|
69
68
|
// (recall, dynamic profile, conflict gate) to prevent untrusted content from
|
|
70
69
|
// influencing memory-augmented responses.
|
|
@@ -94,9 +93,7 @@ export async function prepareMemoryContext(
|
|
|
94
93
|
topCandidates: [],
|
|
95
94
|
} as Awaited<ReturnType<typeof buildMemoryRecall>>,
|
|
96
95
|
dynamicProfile: { text: "" },
|
|
97
|
-
softConflictInstruction: null,
|
|
98
96
|
recallInjectionStrategy: "prepend_user_block",
|
|
99
|
-
conflictClarification: null,
|
|
100
97
|
};
|
|
101
98
|
}
|
|
102
99
|
|
|
@@ -129,65 +126,25 @@ export async function prepareMemoryContext(
|
|
|
129
126
|
topCandidates: [],
|
|
130
127
|
} as Awaited<ReturnType<typeof buildMemoryRecall>>,
|
|
131
128
|
dynamicProfile: { text: "" },
|
|
132
|
-
softConflictInstruction: null,
|
|
133
129
|
recallInjectionStrategy: "prepend_user_block",
|
|
134
|
-
conflictClarification: null,
|
|
135
130
|
};
|
|
136
131
|
}
|
|
137
132
|
|
|
138
133
|
const runtimeConfig = getConfig();
|
|
139
134
|
const memoryEnabled = runtimeConfig.memory?.enabled !== false;
|
|
140
135
|
|
|
141
|
-
// Conflict gate —
|
|
142
|
-
//
|
|
136
|
+
// Conflict gate — evaluate for side effects (background resolution/dismissal)
|
|
137
|
+
// but do not return any user-facing payload. Non-interactive sessions skip
|
|
138
|
+
// entirely since there is no human context for conflict evaluation.
|
|
143
139
|
const isInteractive = ctx.isInteractive !== false;
|
|
144
140
|
const conflictConfig =
|
|
145
141
|
memoryEnabled && isInteractive
|
|
146
142
|
? runtimeConfig.memory?.conflicts
|
|
147
143
|
: undefined;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
: null;
|
|
151
|
-
|
|
152
|
-
if (conflictGateResult?.relevant) {
|
|
153
|
-
return {
|
|
154
|
-
runMessages: ctx.messages,
|
|
155
|
-
recall: {
|
|
156
|
-
enabled: false,
|
|
157
|
-
degraded: false,
|
|
158
|
-
injectedText: "",
|
|
159
|
-
lexicalHits: 0,
|
|
160
|
-
semanticHits: 0,
|
|
161
|
-
recencyHits: 0,
|
|
162
|
-
entityHits: 0,
|
|
163
|
-
relationSeedEntityCount: 0,
|
|
164
|
-
relationTraversedEdgeCount: 0,
|
|
165
|
-
relationNeighborEntityCount: 0,
|
|
166
|
-
relationExpandedItemCount: 0,
|
|
167
|
-
earlyTerminated: false,
|
|
168
|
-
mergedCount: 0,
|
|
169
|
-
selectedCount: 0,
|
|
170
|
-
rerankApplied: false,
|
|
171
|
-
injectedTokens: 0,
|
|
172
|
-
latencyMs: 0,
|
|
173
|
-
topCandidates: [],
|
|
174
|
-
} as Awaited<ReturnType<typeof buildMemoryRecall>>,
|
|
175
|
-
dynamicProfile: { text: "" },
|
|
176
|
-
softConflictInstruction: null,
|
|
177
|
-
recallInjectionStrategy: "prepend_user_block",
|
|
178
|
-
conflictClarification: [
|
|
179
|
-
conflictGateResult.question,
|
|
180
|
-
"",
|
|
181
|
-
"I need this clarification before I can give guidance that depends on that preference.",
|
|
182
|
-
].join("\n"),
|
|
183
|
-
};
|
|
144
|
+
if (conflictConfig) {
|
|
145
|
+
await ctx.conflictGate.evaluate(content, conflictConfig, ctx.scopeId);
|
|
184
146
|
}
|
|
185
147
|
|
|
186
|
-
const softConflictInstruction =
|
|
187
|
-
conflictGateResult && !conflictGateResult.relevant
|
|
188
|
-
? conflictGateResult.question
|
|
189
|
-
: null;
|
|
190
|
-
|
|
191
148
|
// Dynamic profile
|
|
192
149
|
const profileConfig = memoryEnabled
|
|
193
150
|
? runtimeConfig.memory?.profile
|
|
@@ -312,8 +269,6 @@ export async function prepareMemoryContext(
|
|
|
312
269
|
runMessages,
|
|
313
270
|
recall,
|
|
314
271
|
dynamicProfile,
|
|
315
|
-
softConflictInstruction,
|
|
316
272
|
recallInjectionStrategy,
|
|
317
|
-
conflictClarification: null,
|
|
318
273
|
};
|
|
319
274
|
}
|
|
@@ -541,7 +541,9 @@ export async function processMessage(
|
|
|
541
541
|
messageText: trimmedContent,
|
|
542
542
|
channel: "vellum",
|
|
543
543
|
actor: {
|
|
544
|
-
|
|
544
|
+
actorPrincipalId:
|
|
545
|
+
session.trustContext?.guardianPrincipalId ?? undefined,
|
|
546
|
+
actorExternalUserId: session.trustContext?.guardianExternalUserId,
|
|
545
547
|
channel: "vellum",
|
|
546
548
|
guardianPrincipalId:
|
|
547
549
|
session.trustContext?.guardianPrincipalId ?? undefined,
|
|
@@ -115,6 +115,10 @@ export interface InboundActorContext {
|
|
|
115
115
|
memberPolicy?: string;
|
|
116
116
|
/** Denial reason when access is blocked. */
|
|
117
117
|
denialReason?: string;
|
|
118
|
+
/** Free-text notes about this contact. */
|
|
119
|
+
contactNotes?: string;
|
|
120
|
+
/** Number of prior interactions with this contact. */
|
|
121
|
+
contactInteractionCount?: number;
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
/**
|
|
@@ -159,6 +163,9 @@ export function inboundActorContextFromTrust(
|
|
|
159
163
|
: undefined,
|
|
160
164
|
memberPolicy: ctx.memberRecord?.channel.policy ?? undefined,
|
|
161
165
|
denialReason: ctx.denialReason,
|
|
166
|
+
contactNotes: ctx.memberRecord?.contact.notes ?? undefined,
|
|
167
|
+
contactInteractionCount:
|
|
168
|
+
ctx.memberRecord?.contact.interactionCount ?? undefined,
|
|
162
169
|
};
|
|
163
170
|
}
|
|
164
171
|
|
|
@@ -392,24 +399,6 @@ export interface ActiveSurfaceContext {
|
|
|
392
399
|
appFiles?: string[];
|
|
393
400
|
}
|
|
394
401
|
|
|
395
|
-
/**
|
|
396
|
-
* Append a memory-conflict clarification instruction to the last user message.
|
|
397
|
-
*/
|
|
398
|
-
export function injectClarificationRequestIntoUserMessage(
|
|
399
|
-
message: Message,
|
|
400
|
-
question: string,
|
|
401
|
-
): Message {
|
|
402
|
-
const instruction = [
|
|
403
|
-
"[Memory clarification request]",
|
|
404
|
-
`Ask this once in your response: ${question}`,
|
|
405
|
-
"After asking, continue helping with the current request.",
|
|
406
|
-
].join("\n");
|
|
407
|
-
return {
|
|
408
|
-
...message,
|
|
409
|
-
content: [...message.content, { type: "text", text: `\n\n${instruction}` }],
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
|
|
413
402
|
const MAX_CONTEXT_LENGTH = 100_000;
|
|
414
403
|
|
|
415
404
|
function truncateHtml(html: string, budget: number): string {
|
|
@@ -791,6 +780,14 @@ export function buildInboundActorContextBlock(
|
|
|
791
780
|
lines.push(`member_policy: ${ctx.memberPolicy}`);
|
|
792
781
|
}
|
|
793
782
|
lines.push(`denial_reason: ${ctx.denialReason ?? "none"}`);
|
|
783
|
+
// Contact metadata — only included when the sender has a contact record
|
|
784
|
+
// with non-default values.
|
|
785
|
+
if (ctx.contactNotes) {
|
|
786
|
+
lines.push(`contact_notes: ${ctx.contactNotes}`);
|
|
787
|
+
}
|
|
788
|
+
if (ctx.contactInteractionCount != null && ctx.contactInteractionCount > 0) {
|
|
789
|
+
lines.push(`contact_interaction_count: ${ctx.contactInteractionCount}`);
|
|
790
|
+
}
|
|
794
791
|
if (
|
|
795
792
|
ctx.actorMemberDisplayName &&
|
|
796
793
|
ctx.actorSenderDisplayName &&
|
|
@@ -1050,9 +1047,9 @@ export function stripInjectedContext(
|
|
|
1050
1047
|
* - `'full'` (default): all injections are applied.
|
|
1051
1048
|
* - `'minimal'`: only safety-critical context is injected (channel turn,
|
|
1052
1049
|
* interface turn, inbound actor, non-interactive marker, voice call
|
|
1053
|
-
* control, channel capabilities
|
|
1054
|
-
*
|
|
1055
|
-
*
|
|
1050
|
+
* control, channel capabilities). High-token optional blocks (workspace
|
|
1051
|
+
* top-level, temporal, channel command, active surface) are skipped to
|
|
1052
|
+
* reduce context pressure.
|
|
1056
1053
|
*/
|
|
1057
1054
|
export type InjectionMode = "full" | "minimal";
|
|
1058
1055
|
|
|
@@ -1065,7 +1062,6 @@ export type InjectionMode = "full" | "minimal";
|
|
|
1065
1062
|
export function applyRuntimeInjections(
|
|
1066
1063
|
runMessages: Message[],
|
|
1067
1064
|
options: {
|
|
1068
|
-
softConflictInstruction?: string | null;
|
|
1069
1065
|
activeSurface?: ActiveSurfaceContext | null;
|
|
1070
1066
|
workspaceTopLevelContext?: string | null;
|
|
1071
1067
|
channelCapabilities?: ChannelCapabilities | null;
|
|
@@ -1113,19 +1109,6 @@ export function applyRuntimeInjections(
|
|
|
1113
1109
|
}
|
|
1114
1110
|
}
|
|
1115
1111
|
|
|
1116
|
-
if (options.softConflictInstruction) {
|
|
1117
|
-
const userTail = result[result.length - 1];
|
|
1118
|
-
if (userTail && userTail.role === "user") {
|
|
1119
|
-
result = [
|
|
1120
|
-
...result.slice(0, -1),
|
|
1121
|
-
injectClarificationRequestIntoUserMessage(
|
|
1122
|
-
userTail,
|
|
1123
|
-
options.softConflictInstruction,
|
|
1124
|
-
),
|
|
1125
|
-
];
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
1112
|
if (mode === "full" && options.activeSurface) {
|
|
1130
1113
|
const userTail = result[result.length - 1];
|
|
1131
1114
|
if (userTail && userTail.role === "user") {
|
|
@@ -7,10 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { isHttpAuthDisabled } from "../config/env.js";
|
|
10
|
-
import
|
|
11
|
-
ProxyApprovalCallback,
|
|
12
|
-
ProxyApprovalRequest,
|
|
13
|
-
} from "../outbound-proxy/index.js";
|
|
10
|
+
import { getBindingByConversation } from "../memory/external-conversation-store.js";
|
|
14
11
|
import {
|
|
15
12
|
generateAllowlistOptions,
|
|
16
13
|
generateScopeOptions,
|
|
@@ -32,6 +29,8 @@ import { requestComputerControlTool } from "../tools/computer-use/request-comput
|
|
|
32
29
|
import type { ToolExecutor } from "../tools/executor.js";
|
|
33
30
|
import { getAllToolDefinitions } from "../tools/registry.js";
|
|
34
31
|
import type {
|
|
32
|
+
ProxyApprovalCallback,
|
|
33
|
+
ProxyApprovalRequest,
|
|
35
34
|
ToolExecutionResult,
|
|
36
35
|
ToolLifecycleEventHandler,
|
|
37
36
|
} from "../tools/types.js";
|
|
@@ -163,6 +162,10 @@ export function createToolExecutor(
|
|
|
163
162
|
ctx.surfaceActionRequestIds?.has(ctx.currentRequestId ?? "") ?? false,
|
|
164
163
|
requesterExternalUserId: ctx.trustContext?.requesterExternalUserId,
|
|
165
164
|
requesterChatId: ctx.trustContext?.requesterChatId,
|
|
165
|
+
channelPermissionChannelId:
|
|
166
|
+
ctx.trustContext?.sourceChannel === "slack"
|
|
167
|
+
? getBindingByConversation(ctx.conversationId)?.externalChatId
|
|
168
|
+
: undefined,
|
|
166
169
|
onOutput,
|
|
167
170
|
signal: ctx.abortController?.signal,
|
|
168
171
|
sandboxOverride: ctx.sandboxOverride,
|
|
@@ -79,7 +79,11 @@ export class HeartbeatService {
|
|
|
79
79
|
|
|
80
80
|
// Active hours guard — only applied when both bounds are set.
|
|
81
81
|
// The schema rejects configs where only one bound is provided.
|
|
82
|
-
if (
|
|
82
|
+
if (
|
|
83
|
+
!force &&
|
|
84
|
+
config.activeHoursStart != null &&
|
|
85
|
+
config.activeHoursEnd != null
|
|
86
|
+
) {
|
|
83
87
|
const hour = this.deps.getCurrentHour?.() ?? new Date().getHours();
|
|
84
88
|
if (
|
|
85
89
|
!isWithinActiveHours(
|