@dsh-enhanced/assistant-delivery 0.1.32 → 0.1.34
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/README.md +11 -1
- package/lib/acceptance.d.ts +11 -3
- package/lib/acceptance.d.ts.map +1 -1
- package/lib/agent-runtime.d.ts +15 -0
- package/lib/agent-runtime.d.ts.map +1 -1
- package/lib/agent-runtime.js +150 -17
- package/lib/agent-runtime.js.map +1 -1
- package/lib/foreground-observation.d.ts +32 -0
- package/lib/foreground-observation.d.ts.map +1 -0
- package/lib/foreground-observation.js +13 -0
- package/lib/foreground-observation.js.map +1 -0
- package/lib/index.d.ts +6 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/reply-replay.d.ts +52 -0
- package/lib/reply-replay.d.ts.map +1 -0
- package/lib/reply-replay.js +122 -0
- package/lib/reply-replay.js.map +1 -0
- package/lib/service.d.ts +112 -2
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +429 -64
- package/lib/service.js.map +1 -1
- package/lib/sqlite.d.ts +1 -1
- package/lib/sqlite.d.ts.map +1 -1
- package/lib/sqlite.js +146 -3
- package/lib/sqlite.js.map +1 -1
- package/lib/store.d.ts +151 -10
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +520 -7
- package/lib/store.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +14 -10
package/lib/service.js
CHANGED
|
@@ -3,21 +3,23 @@ import { isAbsolute } from 'node:path';
|
|
|
3
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
4
4
|
import { Context, Service } from '@deepseek-ai/cordis';
|
|
5
5
|
import Schema from '@deepseek-ai/schemastery';
|
|
6
|
-
import { acceptanceCanonicalJson } from '@dsh-enhanced/task-acceptance-contract';
|
|
6
|
+
import { acceptanceCanonicalJson, goalDefinitionSituation } from '@dsh-enhanced/task-acceptance-contract';
|
|
7
7
|
import { approvalReviewerOf, isAutoReviewEscalation, } from '@dsh-enhanced/assistant-policy';
|
|
8
8
|
import { TRUSTED_EVALUATION_PRODUCER_PROTOCOL, } from '@dsh-enhanced/assistant-evaluation';
|
|
9
9
|
import { ASSISTANT_GROWTH_CONTRACT_VERSION, growthObjectDigest, validateResolvedWorkflowAutomationTemplate, validateWorkflowAutomationTemplate, validateWorkflowScope, validateWorkflowTraceProjectionReceipt, workflowArgumentShapeDigest, } from '@dsh-enhanced/assistant-growth-contract';
|
|
10
10
|
import { DeliveryAdapterRegistry, DeliveryCoordinator, InboundCoordinator, } from './coordinator.js';
|
|
11
|
-
import { DeliveryStore, DeliveryStoreError } from './store.js';
|
|
11
|
+
import { DeliveryStore, DeliveryStoreError, } from './store.js';
|
|
12
12
|
import { DshDeliveryRuntime } from './agent-runtime.js';
|
|
13
13
|
import { DeliverySessionLeases } from './session-lease-runtime.js';
|
|
14
14
|
import { NativeWebOwner } from './native-web-owner.js';
|
|
15
15
|
import { validateOwnerGoalOutcomeFeedbackLocator, validateOwnerGoalOutcomeFeedbackProof, } from './goal-wake-types.js';
|
|
16
|
+
import { isSynchronousForegroundObservationHandle, validForegroundObservationRegistration } from './foreground-observation.js';
|
|
16
17
|
import { InboundImageMaterializer } from './inbound-images.js';
|
|
17
18
|
import { registerDeliveryTools } from './tools.js';
|
|
18
19
|
import { bindingMatchesOwnerRoute, canonicalBackgroundSourceId as canonicalHostBackgroundSourceId, canonicalConversation, canonicalLocalOperatorId, canonicalOwnerRouteAuthority, externalPrincipalId, ownerRouteAuthorityHash, ownerRouteBindingSnapshot, } from './canonical.js';
|
|
19
20
|
import { isExactDeliveryCommand, parseDeliveryCommand } from './session-commands.js';
|
|
20
21
|
import { deliveryT1PreferenceKeys, deliveryT1PreferenceValues } from './learning-command.js';
|
|
22
|
+
import { assertReplyReplayAllowed, blockAgentRepliesForReplay as createReplyReplayBlock, } from './reply-replay.js';
|
|
21
23
|
import { feedbackSignalInput, parseFeedbackCommand, classifyNaturalPreferenceDirective, observedResponseLanguage, } from './feedback-command.js';
|
|
22
24
|
import { DELIVERY_PREFERENCE_PROJECTION_PROTOCOL, TRUSTED_DELIVERY_PRESENTATION_PRODUCER_PROTOCOL, } from './types.js';
|
|
23
25
|
const trustedDeliveryPreferenceProducers = new WeakSet();
|
|
@@ -242,6 +244,23 @@ export class AssistantDeliveryError extends Error {
|
|
|
242
244
|
this.name = 'AssistantDeliveryError';
|
|
243
245
|
}
|
|
244
246
|
}
|
|
247
|
+
function isOwnerAnchoredGoalsCapability(value) {
|
|
248
|
+
return typeof value === 'object' && value !== null
|
|
249
|
+
&& typeof value.inspectOwnerVerifiedWorkflowSource === 'function'
|
|
250
|
+
&& typeof value.inspectOwnerGoalRunProof === 'function';
|
|
251
|
+
}
|
|
252
|
+
function isOwnerGoalSnapshotLocator(value) {
|
|
253
|
+
if (typeof value !== 'object' || value === null)
|
|
254
|
+
return false;
|
|
255
|
+
const locator = value;
|
|
256
|
+
return Object.keys(locator).sort().join(',') === 'goalId,ownerRouteId,preset,principalId,sessionId,workspace'
|
|
257
|
+
&& typeof locator['ownerRouteId'] === 'string' && locator['ownerRouteId'] !== ''
|
|
258
|
+
&& typeof locator['principalId'] === 'string' && locator['principalId'] !== ''
|
|
259
|
+
&& typeof locator['workspace'] === 'string' && locator['workspace'] !== ''
|
|
260
|
+
&& typeof locator['preset'] === 'string' && locator['preset'] !== ''
|
|
261
|
+
&& typeof locator['sessionId'] === 'string' && locator['sessionId'] !== ''
|
|
262
|
+
&& typeof locator['goalId'] === 'string' && locator['goalId'] !== '';
|
|
263
|
+
}
|
|
245
264
|
const configSchema = Schema.object({
|
|
246
265
|
databasePath: Schema.string().required(),
|
|
247
266
|
spoolPath: Schema.string().required(),
|
|
@@ -524,6 +543,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
524
543
|
preferenceProducerGeneration = `assistant-delivery-preference:${randomUUID()}`;
|
|
525
544
|
acceptanceProducerGeneration = `assistant-delivery-acceptance:${randomUUID()}`;
|
|
526
545
|
acceptanceSink;
|
|
546
|
+
foregroundObserver;
|
|
527
547
|
/** Once a live verifier required acceptance, loss of that verifier must not silently bypass it. */
|
|
528
548
|
acceptanceRequired = false;
|
|
529
549
|
preferenceTurns = new WeakMap();
|
|
@@ -651,6 +671,10 @@ export class AssistantDeliveryService extends Service {
|
|
|
651
671
|
maxTextBytes: config.maxTextBytes,
|
|
652
672
|
prepareForegroundTaskAcceptance: (binding, envelope) => this.prepareForegroundTaskAcceptance(binding, envelope),
|
|
653
673
|
completeForegroundTaskAcceptance: (handle, input) => this.completeForegroundTaskAcceptance(handle, input),
|
|
674
|
+
recordForegroundTaskModelSelection: (handle, input) => this.deliveryStore.recordForegroundTaskModelSelection({ contractId: handle.contractId, ...input }),
|
|
675
|
+
beginForegroundTaskExecution: (binding, envelope) => this.beginForegroundTaskExecution(binding, envelope),
|
|
676
|
+
recordForegroundExecutionModelSelection: (inboxId, input) => this.deliveryStore.recordForegroundExecutionModelSelection({ inboxId, ...input }),
|
|
677
|
+
completeForegroundTaskExecution: (inboxId, input) => this.completeForegroundTaskExecution(inboxId, input),
|
|
654
678
|
modelPickerTtlMs: config.modelPickerTtlMs,
|
|
655
679
|
permissionPickerTtlMs: config.permissionPickerTtlMs,
|
|
656
680
|
getModelSelection: conversation => this.deliveryStore.getModelSelection(conversation),
|
|
@@ -731,6 +755,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
731
755
|
trustedDeliveryPreferenceProducers.delete(this);
|
|
732
756
|
this.preferenceFeedbackSink = undefined;
|
|
733
757
|
this.acceptanceSink = undefined;
|
|
758
|
+
this.foregroundObserver = undefined;
|
|
734
759
|
this.evaluationSink = undefined;
|
|
735
760
|
this.workflowTraceSink = undefined;
|
|
736
761
|
this.automationPresentationBinding?.dispose();
|
|
@@ -1344,7 +1369,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
1344
1369
|
if (Buffer.byteLength(rendered, 'utf8') > this.config.maxTextBytes) {
|
|
1345
1370
|
throw new AssistantDeliveryError('runtime-conflict', 'scheduled goal result exceeds the delivery text limit');
|
|
1346
1371
|
}
|
|
1347
|
-
const situation =
|
|
1372
|
+
const situation = goalDefinitionSituation(proof.goal.definitionDigest);
|
|
1348
1373
|
return this.deliveryStore.enqueueGoalOutcomeTarget({
|
|
1349
1374
|
locator, proof,
|
|
1350
1375
|
intent: {
|
|
@@ -1353,7 +1378,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
1353
1378
|
target: { conversation: finalBinding.conversation, principal: finalBinding.principal },
|
|
1354
1379
|
text: rendered, format: 'markdown',
|
|
1355
1380
|
metadata: Object.freeze({
|
|
1356
|
-
'dsh.learning.schemaVersion': '
|
|
1381
|
+
'dsh.learning.schemaVersion': '4', 'dsh.learning.kind': 'goal-outcome',
|
|
1357
1382
|
'dsh.learning.goalId': locator.goalId, 'dsh.learning.assessmentId': locator.assessmentId,
|
|
1358
1383
|
'dsh.learning.runId': proof.runId, 'dsh.learning.situation': situation,
|
|
1359
1384
|
'dsh.learning.occurredAt': String(proof.receipt.completedAt),
|
|
@@ -1475,6 +1500,91 @@ export class AssistantDeliveryService extends Service {
|
|
|
1475
1500
|
this.acceptanceSink = undefined;
|
|
1476
1501
|
};
|
|
1477
1502
|
}
|
|
1503
|
+
beginForegroundTaskExecution(binding, envelope) {
|
|
1504
|
+
const owner = this.ownerLineageForBinding(binding);
|
|
1505
|
+
if (owner === undefined)
|
|
1506
|
+
return undefined;
|
|
1507
|
+
const inbox = this.deliveryStore.getInboxByProviderEvent(envelope.channel, envelope.account, envelope.eventId);
|
|
1508
|
+
if (!inbox || inbox.status !== 'claimed' || inbox.bindingId !== binding.id
|
|
1509
|
+
|| JSON.stringify(inbox.envelope) !== JSON.stringify(envelope)) {
|
|
1510
|
+
throw new AssistantDeliveryError('missing-binding', 'foreground execution lacks an authenticated owner inbox');
|
|
1511
|
+
}
|
|
1512
|
+
const dispatchedAt = Date.now();
|
|
1513
|
+
this.deliveryStore.bindForegroundTaskExecution({ inboxId: inbox.id,
|
|
1514
|
+
scope: { workspace: binding.workspace, preset: binding.agentPreset }, owner, binding, dispatchedAt });
|
|
1515
|
+
this.observeForegroundBegin(Object.freeze({ protocol: 'assistant-delivery/foreground-task/v1', inboxId: inbox.id,
|
|
1516
|
+
sessionId: binding.sessionId, scope: Object.freeze({ workspace: binding.workspace, preset: binding.agentPreset }),
|
|
1517
|
+
owner: Object.freeze({ principalRecordId: owner.principalRecordId, principalVersion: owner.principalVersion }),
|
|
1518
|
+
binding: Object.freeze({ id: binding.id, version: binding.version, generation: binding.generation }), dispatchedAt }));
|
|
1519
|
+
return inbox.id;
|
|
1520
|
+
}
|
|
1521
|
+
registerForegroundTaskObserver(registration) {
|
|
1522
|
+
this.assertActive();
|
|
1523
|
+
const control = this.context.get('pluginControlPlane', false);
|
|
1524
|
+
if (!validForegroundObservationRegistration(registration) || control === undefined
|
|
1525
|
+
|| control.ownsForegroundTaskObservationRegistration?.(registration) !== true) {
|
|
1526
|
+
throw new AssistantDeliveryError('runtime-conflict', 'foreground observation registration is invalid');
|
|
1527
|
+
}
|
|
1528
|
+
if (this.foregroundObserver !== undefined)
|
|
1529
|
+
throw new AssistantDeliveryError('runtime-conflict', 'foreground observer is already registered');
|
|
1530
|
+
const token = Symbol('assistant-delivery.foreground-observer');
|
|
1531
|
+
const sink = Object.freeze({ token, generation: registration.generation, registration,
|
|
1532
|
+
handles: new Map() });
|
|
1533
|
+
this.foregroundObserver = sink;
|
|
1534
|
+
let live = true;
|
|
1535
|
+
return () => { if (live) {
|
|
1536
|
+
live = false;
|
|
1537
|
+
if (this.foregroundObserver?.token === token)
|
|
1538
|
+
this.foregroundObserver = undefined;
|
|
1539
|
+
} };
|
|
1540
|
+
}
|
|
1541
|
+
observerCurrent(sink) {
|
|
1542
|
+
const control = this.context.get('pluginControlPlane', false);
|
|
1543
|
+
return this.active && this.foregroundObserver?.token === sink.token
|
|
1544
|
+
&& control?.ownsForegroundTaskObservationRegistration?.(sink.registration) === true
|
|
1545
|
+
&& sink.generation === sink.registration.generation;
|
|
1546
|
+
}
|
|
1547
|
+
observeForegroundBegin(task) {
|
|
1548
|
+
const sink = this.foregroundObserver;
|
|
1549
|
+
if (sink === undefined || !this.observerCurrent(sink) || sink.handles.size >= Math.max(16, this.config.maxConcurrency * 4))
|
|
1550
|
+
return;
|
|
1551
|
+
try {
|
|
1552
|
+
const handle = sink.registration.begin(task);
|
|
1553
|
+
if (!isSynchronousForegroundObservationHandle(handle)) {
|
|
1554
|
+
void Promise.resolve(handle).catch(error => this.context.logger.warn(`assistant-delivery: foreground observer begin must be synchronous: ${String(error)}`));
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
if (!this.observerCurrent(sink))
|
|
1558
|
+
return;
|
|
1559
|
+
sink.handles.set(task.inboxId, { task, handle });
|
|
1560
|
+
}
|
|
1561
|
+
catch (error) {
|
|
1562
|
+
this.context.logger.warn(`assistant-delivery: foreground observer begin failed: ${String(error)}`);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
completeForegroundTaskExecution(inboxId, input) {
|
|
1566
|
+
this.deliveryStore.finishForegroundTaskExecution({ inboxId, ...input, completedAt: Date.now() });
|
|
1567
|
+
const sink = this.foregroundObserver;
|
|
1568
|
+
const observed = sink?.handles.get(inboxId);
|
|
1569
|
+
if (sink === undefined || observed === undefined)
|
|
1570
|
+
return;
|
|
1571
|
+
sink.handles.delete(inboxId);
|
|
1572
|
+
if (!this.observerCurrent(sink))
|
|
1573
|
+
return;
|
|
1574
|
+
const actual = this.deliveryStore.inspectForegroundExecutionForOwner({ inboxId, scope: observed.task.scope, owner: observed.task.owner,
|
|
1575
|
+
bindingId: observed.task.binding.id, bindingVersion: observed.task.binding.version, bindingGeneration: observed.task.binding.generation });
|
|
1576
|
+
if (actual === null)
|
|
1577
|
+
return;
|
|
1578
|
+
try {
|
|
1579
|
+
const result = sink.registration.completed(observed.handle, observed.task, actual);
|
|
1580
|
+
if (!isSynchronousForegroundObservationHandle(result)) {
|
|
1581
|
+
void Promise.resolve(result).catch(error => this.context.logger.warn(`assistant-delivery: foreground observer completion must be synchronous: ${String(error)}`));
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
catch (error) {
|
|
1585
|
+
this.context.logger.warn(`assistant-delivery: foreground observer completion failed: ${String(error)}`);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1478
1588
|
prepareForegroundTaskAcceptance(binding, envelope) {
|
|
1479
1589
|
const sink = this.acceptanceSink;
|
|
1480
1590
|
if (sink === undefined) {
|
|
@@ -1979,6 +2089,106 @@ export class AssistantDeliveryService extends Service {
|
|
|
1979
2089
|
}
|
|
1980
2090
|
return route;
|
|
1981
2091
|
}
|
|
2092
|
+
/**
|
|
2093
|
+
* Host-only owner-anchored workflow learning bridge. This method is not
|
|
2094
|
+
* registered as an Agent tool (same Host seam as prepareWorkflowApproval):
|
|
2095
|
+
* only the opt-in growth driver invokes it, and only with a locator plus one
|
|
2096
|
+
* bounded-wake authority. Delivery itself re-fetches both the verified Goals
|
|
2097
|
+
* source and the owner-run-trace proof, so the caller can never supply the
|
|
2098
|
+
* objective, steps, acceptance verdict or proof digest — a spoofed driver
|
|
2099
|
+
* learns nothing. A goal that does not honestly reduce to the single
|
|
2100
|
+
* no-tool `assistant.agent-turn` step is returned as `abstained` with zero
|
|
2101
|
+
* writes; the trace is projected through the existing outbox/drain chain.
|
|
2102
|
+
*/
|
|
2103
|
+
async commitOwnerAnchoredWorkflowTrace(input) {
|
|
2104
|
+
this.assertActive();
|
|
2105
|
+
if (typeof input !== 'object' || input === null
|
|
2106
|
+
|| !isOwnerGoalSnapshotLocator(input.locator)
|
|
2107
|
+
|| typeof input.authority !== 'object' || input.authority === null
|
|
2108
|
+
|| typeof input.authority.assertCurrent !== 'function') {
|
|
2109
|
+
throw new AssistantDeliveryError('runtime-conflict', 'owner-anchored workflow commit request is invalid');
|
|
2110
|
+
}
|
|
2111
|
+
const { locator, authority } = input;
|
|
2112
|
+
authority.assertCurrent();
|
|
2113
|
+
if (authority.ownerRouteId !== locator.ownerRouteId
|
|
2114
|
+
|| authority.scope.principalId !== locator.principalId
|
|
2115
|
+
|| authority.scope.workspace !== locator.workspace
|
|
2116
|
+
|| authority.scope.preset !== locator.preset) {
|
|
2117
|
+
throw new AssistantDeliveryError('policy-denied', 'owner-anchored workflow authority scope does not match its locator');
|
|
2118
|
+
}
|
|
2119
|
+
const goals = this.ctx.get('assistantGoals', false);
|
|
2120
|
+
if (!isOwnerAnchoredGoalsCapability(goals)) {
|
|
2121
|
+
throw new AssistantDeliveryError('runtime-unavailable', 'owner-anchored workflow goals evidence is unavailable');
|
|
2122
|
+
}
|
|
2123
|
+
let source;
|
|
2124
|
+
try {
|
|
2125
|
+
source = await goals.inspectOwnerVerifiedWorkflowSource(locator);
|
|
2126
|
+
}
|
|
2127
|
+
catch (error) {
|
|
2128
|
+
if (error?.code === 'unavailable') {
|
|
2129
|
+
throw new AssistantDeliveryError('runtime-unavailable', 'owner-anchored workflow goals observation is unavailable');
|
|
2130
|
+
}
|
|
2131
|
+
throw new AssistantDeliveryError('missing-binding', 'owner-anchored workflow verified source is not available');
|
|
2132
|
+
}
|
|
2133
|
+
let proof;
|
|
2134
|
+
try {
|
|
2135
|
+
proof = await goals.inspectOwnerGoalRunProof({ ...locator, runId: source.runId });
|
|
2136
|
+
}
|
|
2137
|
+
catch (error) {
|
|
2138
|
+
if (/unavailable/u.test(error instanceof Error ? error.message : '')) {
|
|
2139
|
+
throw new AssistantDeliveryError('runtime-unavailable', 'owner-anchored workflow run proof is unavailable');
|
|
2140
|
+
}
|
|
2141
|
+
throw new AssistantDeliveryError('missing-binding', 'owner-anchored workflow run proof is not available');
|
|
2142
|
+
}
|
|
2143
|
+
// Re-anchor after every await: the bounded-wake authority and the live
|
|
2144
|
+
// owner route must still be the same generation the driver was minted for.
|
|
2145
|
+
authority.assertCurrent();
|
|
2146
|
+
const route = this.resolveOwnerRoute(locator.ownerRouteId);
|
|
2147
|
+
if (externalPrincipalId(route.binding.principal) !== locator.principalId
|
|
2148
|
+
|| route.binding.workspace !== locator.workspace
|
|
2149
|
+
|| route.binding.agentPreset !== locator.preset) {
|
|
2150
|
+
throw new AssistantDeliveryError('missing-binding', 'owner-anchored workflow locator does not match the live owner route');
|
|
2151
|
+
}
|
|
2152
|
+
const failedObservations = source.failedObservations ?? [];
|
|
2153
|
+
const proofSucceeded = proof.steps.filter(step => step.outcome === 'succeeded');
|
|
2154
|
+
const proofFailed = proof.steps.filter(step => step.outcome === 'failed');
|
|
2155
|
+
// The reduction decision below must cover exactly the settled calls the
|
|
2156
|
+
// trusted run-proof digest covers; a mismatch fails closed.
|
|
2157
|
+
if (source.scope.principalId !== locator.principalId
|
|
2158
|
+
|| source.scope.workspace !== locator.workspace || source.scope.preset !== locator.preset
|
|
2159
|
+
|| source.goal.id !== locator.goalId || source.goal.sessionId !== locator.sessionId
|
|
2160
|
+
|| proof.runId !== source.runId || proof.turn !== source.turn
|
|
2161
|
+
|| proof.definitionDigest !== source.goal.definition.digest
|
|
2162
|
+
|| !/^[a-f0-9]{64}$/u.test(proof.traceDigest)
|
|
2163
|
+
|| proofSucceeded.length !== source.steps.length
|
|
2164
|
+
|| proofFailed.length !== failedObservations.length
|
|
2165
|
+
|| JSON.stringify(proofSucceeded.map(step => `${step.id}:${step.name}`).sort())
|
|
2166
|
+
!== JSON.stringify(source.steps.map(step => `${step.id}:${step.toolName}`).sort())
|
|
2167
|
+
|| JSON.stringify(proofFailed.map(step => step.id).sort())
|
|
2168
|
+
!== JSON.stringify(failedObservations.map(step => step.id).sort())) {
|
|
2169
|
+
throw new AssistantDeliveryError('missing-binding', 'owner-anchored workflow evidence does not match its locator');
|
|
2170
|
+
}
|
|
2171
|
+
const committed = this.deliveryStore.commitOwnerAnchoredWorkflowTrace({
|
|
2172
|
+
binding: route.binding,
|
|
2173
|
+
ownerRouteId: route.authorityId,
|
|
2174
|
+
scope: { workspace: locator.workspace, preset: locator.preset },
|
|
2175
|
+
goal: source.goal,
|
|
2176
|
+
runId: source.runId,
|
|
2177
|
+
turn: source.turn,
|
|
2178
|
+
acceptance: {
|
|
2179
|
+
contractId: source.acceptance.contractId,
|
|
2180
|
+
receiptDigest: source.acceptance.receiptDigest,
|
|
2181
|
+
verifiedAt: source.acceptance.verifiedAt,
|
|
2182
|
+
validUntil: source.acceptance.validUntil,
|
|
2183
|
+
},
|
|
2184
|
+
steps: source.steps.map(step => Object.freeze({ id: step.id, toolName: step.toolName })),
|
|
2185
|
+
...(failedObservations.length === 0 ? {} : { failedObservations }),
|
|
2186
|
+
ownerRunTraceDigest: proof.traceDigest,
|
|
2187
|
+
});
|
|
2188
|
+
if (committed.outcome === 'trace-recorded')
|
|
2189
|
+
await this.drainWorkflowTraces();
|
|
2190
|
+
return committed;
|
|
2191
|
+
}
|
|
1982
2192
|
/**
|
|
1983
2193
|
* Host-only, content-free route minting seam for Preference promotion.
|
|
1984
2194
|
* No binding id is accepted from the caller; Delivery resolves the unique
|
|
@@ -2635,12 +2845,11 @@ export class AssistantDeliveryService extends Service {
|
|
|
2635
2845
|
return 'invalid-target';
|
|
2636
2846
|
return await this.dispatchGoalOutcomeFeedback(binding, inbox, target, parsed, goalOutcomeTarget);
|
|
2637
2847
|
}
|
|
2638
|
-
// Ordinary Agent replies have no learning metadata.
|
|
2639
|
-
//
|
|
2640
|
-
// rebuilds
|
|
2641
|
-
// selects a closed deterministic-deidentification template
|
|
2642
|
-
//
|
|
2643
|
-
// repetition, not a cross-plugin quality vote.
|
|
2848
|
+
// Ordinary Agent replies have no learning metadata. A completed Host
|
|
2849
|
+
// execution permits an authenticated owner revision in Evaluation. The
|
|
2850
|
+
// separate local workflow path rebuilds its Inbox/Outbox fence and abstains
|
|
2851
|
+
// unless the source selects a closed deterministic-deidentification template;
|
|
2852
|
+
// legacy replies without an execution receipt retain that local path.
|
|
2644
2853
|
if (metadata?.['dsh.learning.kind'] !== 'automation-run') {
|
|
2645
2854
|
const sourceEventId = target.intent.replyToEventId;
|
|
2646
2855
|
if (typeof sourceEventId !== 'string')
|
|
@@ -2655,7 +2864,17 @@ export class AssistantDeliveryService extends Service {
|
|
|
2655
2864
|
owner: lineage,
|
|
2656
2865
|
bindingId: binding.id,
|
|
2657
2866
|
});
|
|
2658
|
-
const
|
|
2867
|
+
const foreground = lineage === undefined ? null : this.deliveryStore.inspectForegroundExecutionForOwner({
|
|
2868
|
+
inboxId: sourceInbox.id, scope: { workspace: binding.workspace, preset: binding.agentPreset },
|
|
2869
|
+
owner: lineage, bindingId: binding.id, bindingVersion: binding.version, bindingGeneration: binding.generation,
|
|
2870
|
+
});
|
|
2871
|
+
// The owner labels the objective, not whether Host work has stopped.
|
|
2872
|
+
// Do not let Evaluation's delivery producer turn an unknown execution
|
|
2873
|
+
// into a succeeded one merely because its reply reached the user.
|
|
2874
|
+
if ([foreground, accepted].some(execution => execution !== null
|
|
2875
|
+
&& (execution.status !== 'succeeded' || !execution.quiescent)))
|
|
2876
|
+
return 'invalid-target';
|
|
2877
|
+
const canonicalForeground = foreground !== null || accepted !== null;
|
|
2659
2878
|
const projectForegroundOwner = async (inspectOnly) => {
|
|
2660
2879
|
const sink = this.evaluationSink;
|
|
2661
2880
|
if (sink === undefined || lineage === undefined)
|
|
@@ -2707,7 +2926,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
2707
2926
|
if (canonicalForeground && this.evaluationSink === undefined)
|
|
2708
2927
|
return 'unavailable';
|
|
2709
2928
|
try {
|
|
2710
|
-
// Evaluation commits first for
|
|
2929
|
+
// Evaluation commits first for recorded foreground tasks. If the sink
|
|
2711
2930
|
// is unavailable, never acknowledge a Delivery-only success; a replay
|
|
2712
2931
|
// can safely repeat the opaque command and then repair local workflow
|
|
2713
2932
|
// evidence after the canonical owner revision is durable.
|
|
@@ -2855,8 +3074,8 @@ export class AssistantDeliveryService extends Service {
|
|
|
2855
3074
|
const metadata = target.intent.metadata;
|
|
2856
3075
|
const locator = durable.locator;
|
|
2857
3076
|
const immutableProof = durable.proof;
|
|
2858
|
-
const situation =
|
|
2859
|
-
if (metadata?.['dsh.learning.schemaVersion'] !== '
|
|
3077
|
+
const situation = goalDefinitionSituation(immutableProof.goal.definitionDigest);
|
|
3078
|
+
if (metadata?.['dsh.learning.schemaVersion'] !== '4'
|
|
2860
3079
|
|| metadata['dsh.learning.kind'] !== 'goal-outcome'
|
|
2861
3080
|
|| metadata['dsh.learning.goalId'] !== locator.goalId
|
|
2862
3081
|
|| metadata['dsh.learning.assessmentId'] !== locator.assessmentId
|
|
@@ -3143,38 +3362,14 @@ export class AssistantDeliveryService extends Service {
|
|
|
3143
3362
|
return reviewer === 'auto-review' ? next() : 'unavailable';
|
|
3144
3363
|
if (initialRoute.state !== 'bound')
|
|
3145
3364
|
return 'unavailable';
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
}
|
|
3155
|
-
if (authority === undefined || authority.binding.id !== initialRoute.binding.id)
|
|
3156
|
-
return 'unavailable';
|
|
3157
|
-
const sessions = ctx.get('sessions');
|
|
3158
|
-
if (sessions === undefined)
|
|
3159
|
-
return 'unavailable';
|
|
3160
|
-
try {
|
|
3161
|
-
if (!await sessions.flush(request.agent.session))
|
|
3162
|
-
return 'unavailable';
|
|
3163
|
-
}
|
|
3164
|
-
catch {
|
|
3165
|
-
return 'unavailable';
|
|
3166
|
-
}
|
|
3167
|
-
if (signalAborted(request.signal))
|
|
3168
|
-
return 'cancelled';
|
|
3169
|
-
let persisted;
|
|
3170
|
-
try {
|
|
3171
|
-
persisted = this.resolveToolApprovalAuthority(ctx, request);
|
|
3172
|
-
}
|
|
3173
|
-
catch {
|
|
3174
|
-
return 'unavailable';
|
|
3175
|
-
}
|
|
3176
|
-
if (!sameToolApprovalAuthority(authority, persisted))
|
|
3177
|
-
return 'unavailable';
|
|
3365
|
+
// The TTL controller is installed BEFORE every pre-card wait, not just
|
|
3366
|
+
// before the adapter call. resolveToolApprovalAuthority is synchronous, but
|
|
3367
|
+
// the durability `sessions.flush()` between the two authority reads has no
|
|
3368
|
+
// cancellation of its own: a stuck flush listener used to hang the whole
|
|
3369
|
+
// approval waterfall for hours with no card dispatched and no expiry (real
|
|
3370
|
+
// headless-Lark observation). Everything from here on is bounded by the
|
|
3371
|
+
// same fail-closed deadline; abort races resolve 'cancelled' only when the
|
|
3372
|
+
// owning request itself was aborted, otherwise 'unavailable'.
|
|
3178
3373
|
const operationId = `tool-approval:${randomUUID()}`;
|
|
3179
3374
|
const expiresAt = Date.now() + this.config.toolApprovalTtlMs;
|
|
3180
3375
|
const controller = new AbortController();
|
|
@@ -3184,7 +3379,11 @@ export class AssistantDeliveryService extends Service {
|
|
|
3184
3379
|
controller.abort(request.signal?.reason);
|
|
3185
3380
|
};
|
|
3186
3381
|
request.signal?.addEventListener('abort', onRequestAbort, { once: true });
|
|
3187
|
-
|
|
3382
|
+
if (signalAborted(request.signal)) {
|
|
3383
|
+
request.signal?.removeEventListener('abort', onRequestAbort);
|
|
3384
|
+
return 'cancelled';
|
|
3385
|
+
}
|
|
3386
|
+
const deadlineAbort = new Promise(resolve => {
|
|
3188
3387
|
controller.signal.addEventListener('abort', () => {
|
|
3189
3388
|
resolve(requestCancelled ? 'cancelled' : 'unavailable');
|
|
3190
3389
|
}, { once: true });
|
|
@@ -3194,26 +3393,69 @@ export class AssistantDeliveryService extends Service {
|
|
|
3194
3393
|
}, this.config.toolApprovalTtlMs);
|
|
3195
3394
|
timeout.unref?.();
|
|
3196
3395
|
this.toolApprovalControllers.add(controller);
|
|
3197
|
-
const adapterRequest = Object.freeze({
|
|
3198
|
-
operationId,
|
|
3199
|
-
bindingId: authority.binding.id,
|
|
3200
|
-
target: Object.freeze({
|
|
3201
|
-
conversation: Object.freeze({ ...authority.binding.conversation }),
|
|
3202
|
-
principal: Object.freeze({ ...authority.binding.principal }),
|
|
3203
|
-
}),
|
|
3204
|
-
expiresAt,
|
|
3205
|
-
actionHash: authority.actionHash,
|
|
3206
|
-
toolName: request.toolName,
|
|
3207
|
-
callId: authority.callId,
|
|
3208
|
-
...(request.reason === undefined ? {} : { reason: request.reason }),
|
|
3209
|
-
arguments: authority.arguments,
|
|
3210
|
-
});
|
|
3211
3396
|
let outcome;
|
|
3397
|
+
// Declared outside the try block: the post-race authority re-check below
|
|
3398
|
+
// must compare against the exact authority that dispatched the card.
|
|
3399
|
+
let authority;
|
|
3212
3400
|
try {
|
|
3401
|
+
const preCard = await Promise.race([
|
|
3402
|
+
(async () => {
|
|
3403
|
+
let authority;
|
|
3404
|
+
try {
|
|
3405
|
+
authority = this.resolveToolApprovalAuthority(ctx, request);
|
|
3406
|
+
}
|
|
3407
|
+
catch {
|
|
3408
|
+
return 'unavailable';
|
|
3409
|
+
}
|
|
3410
|
+
if (authority === undefined || authority.binding.id !== initialRoute.binding.id)
|
|
3411
|
+
return 'unavailable';
|
|
3412
|
+
const sessions = ctx.get('sessions');
|
|
3413
|
+
if (sessions === undefined)
|
|
3414
|
+
return 'unavailable';
|
|
3415
|
+
try {
|
|
3416
|
+
if (!await sessions.flush(request.agent.session))
|
|
3417
|
+
return 'unavailable';
|
|
3418
|
+
}
|
|
3419
|
+
catch {
|
|
3420
|
+
return 'unavailable';
|
|
3421
|
+
}
|
|
3422
|
+
if (signalAborted(request.signal))
|
|
3423
|
+
return 'cancelled';
|
|
3424
|
+
let persisted;
|
|
3425
|
+
try {
|
|
3426
|
+
persisted = this.resolveToolApprovalAuthority(ctx, request);
|
|
3427
|
+
}
|
|
3428
|
+
catch {
|
|
3429
|
+
return 'unavailable';
|
|
3430
|
+
}
|
|
3431
|
+
if (!sameToolApprovalAuthority(authority, persisted))
|
|
3432
|
+
return 'unavailable';
|
|
3433
|
+
return authority;
|
|
3434
|
+
})(),
|
|
3435
|
+
deadlineAbort,
|
|
3436
|
+
]);
|
|
3437
|
+
if (typeof preCard === 'string')
|
|
3438
|
+
return preCard;
|
|
3439
|
+
const granted = preCard;
|
|
3440
|
+
authority = granted;
|
|
3441
|
+
const adapterRequest = Object.freeze({
|
|
3442
|
+
operationId,
|
|
3443
|
+
bindingId: granted.binding.id,
|
|
3444
|
+
target: Object.freeze({
|
|
3445
|
+
conversation: Object.freeze({ ...granted.binding.conversation }),
|
|
3446
|
+
principal: Object.freeze({ ...granted.binding.principal }),
|
|
3447
|
+
}),
|
|
3448
|
+
expiresAt,
|
|
3449
|
+
actionHash: granted.actionHash,
|
|
3450
|
+
toolName: request.toolName,
|
|
3451
|
+
callId: granted.callId,
|
|
3452
|
+
...(request.reason === undefined ? {} : { reason: request.reason }),
|
|
3453
|
+
arguments: granted.arguments,
|
|
3454
|
+
});
|
|
3213
3455
|
const answer = Promise.resolve()
|
|
3214
|
-
.then(() =>
|
|
3456
|
+
.then(() => granted.adapter.requestToolApproval(adapterRequest, controller.signal))
|
|
3215
3457
|
.then(value => value, () => 'unavailable');
|
|
3216
|
-
outcome = await Promise.race([answer,
|
|
3458
|
+
outcome = await Promise.race([answer, deadlineAbort]);
|
|
3217
3459
|
}
|
|
3218
3460
|
finally {
|
|
3219
3461
|
clearTimeout(timeout);
|
|
@@ -3231,7 +3473,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
3231
3473
|
catch {
|
|
3232
3474
|
return 'unavailable';
|
|
3233
3475
|
}
|
|
3234
|
-
if (current === undefined || !sameToolApprovalAuthority(authority, current))
|
|
3476
|
+
if (authority === undefined || current === undefined || !sameToolApprovalAuthority(authority, current))
|
|
3235
3477
|
return 'unavailable';
|
|
3236
3478
|
if (outcome === 'allowed-once') {
|
|
3237
3479
|
try {
|
|
@@ -3655,6 +3897,123 @@ export class AssistantDeliveryService extends Service {
|
|
|
3655
3897
|
generation: resolved.snapshot.generation,
|
|
3656
3898
|
});
|
|
3657
3899
|
}
|
|
3900
|
+
/** Host-only snapshot of the model selected for this owner's external conversation. */
|
|
3901
|
+
inspectOwnerModelSelection(input) {
|
|
3902
|
+
this.validateOwnerRoute(input);
|
|
3903
|
+
const { binding } = this.resolveOwnerRoute(input.authorityId);
|
|
3904
|
+
if (input.sourceAgent !== undefined) {
|
|
3905
|
+
const session = input.sourceAgent.session;
|
|
3906
|
+
if (String(session.id) !== binding.sessionId || session.header.cwd !== binding.workspace
|
|
3907
|
+
|| session.header.agentPreset !== binding.agentPreset) {
|
|
3908
|
+
throw new AssistantDeliveryError('missing-binding', 'growth source session does not match the owner route');
|
|
3909
|
+
}
|
|
3910
|
+
// Request headers reflect native model-selection hooks; agent.options
|
|
3911
|
+
// can still contain the initial route after the user changes models.
|
|
3912
|
+
const request = session.snapshotEvents().findLast(event => event.type === 'request/header');
|
|
3913
|
+
const selected = request?.type === 'request/header' ? request.data.header.config : undefined;
|
|
3914
|
+
if (!selected?.provider || !selected.model) {
|
|
3915
|
+
throw new AssistantDeliveryError('missing-binding', 'growth source session has no resolved model request');
|
|
3916
|
+
}
|
|
3917
|
+
return Object.freeze({ provider: selected.provider, model: selected.model,
|
|
3918
|
+
...(selected.reasoningEffort === undefined ? {} : { reasoningEffort: selected.reasoningEffort }) });
|
|
3919
|
+
}
|
|
3920
|
+
// Native Web sessions own their model selection in DSH, not this external
|
|
3921
|
+
// conversation ledger. Never substitute Delivery defaults for those sessions.
|
|
3922
|
+
if (binding.conversation.channel === 'web') {
|
|
3923
|
+
throw new AssistantDeliveryError('missing-binding', 'native Web model selection requires a source session');
|
|
3924
|
+
}
|
|
3925
|
+
const selected = this.deliveryStore.getModelSelection(binding.conversation)
|
|
3926
|
+
?? { provider: this.config.agentProvider, model: this.config.agentModel };
|
|
3927
|
+
return Object.freeze({ provider: selected.provider, model: selected.model,
|
|
3928
|
+
...(selected.reasoningEffort === undefined ? {} : { reasoningEffort: selected.reasoningEffort }) });
|
|
3929
|
+
}
|
|
3930
|
+
/**
|
|
3931
|
+
* Resolve one canonical foreground result against its actual owner lineage.
|
|
3932
|
+
* Evaluation's workspace/preset scope alone does not establish ownership.
|
|
3933
|
+
* Historical Sessions remain eligible after /new, but not after owner ABA.
|
|
3934
|
+
*/
|
|
3935
|
+
inspectOwnerForegroundLearningTask(input) {
|
|
3936
|
+
this.assertActive();
|
|
3937
|
+
const routeInput = { authorityId: input.authorityId, principalId: input.principalId,
|
|
3938
|
+
workspace: input.workspace, agentPreset: input.agentPreset };
|
|
3939
|
+
const owner = this.validateOwnerRoute(routeInput);
|
|
3940
|
+
const evaluation = this.context.get('assistantEvaluation', false);
|
|
3941
|
+
if (!evaluation || typeof evaluation.inspectTrustedTaskOwnerRevision !== 'function')
|
|
3942
|
+
return undefined;
|
|
3943
|
+
const scope = evaluation.canonicalHostScope({ workspace: owner.workspace, preset: owner.agentPreset });
|
|
3944
|
+
const canonical = evaluation.getTrustedTaskLearningProjection({ scope, outcomeId: input.outcomeId });
|
|
3945
|
+
if (!canonical || canonical.projection.subjectKind !== 'foreground-turn')
|
|
3946
|
+
return undefined;
|
|
3947
|
+
const inbox = this.deliveryStore.getInbox(canonical.projection.subjectRef);
|
|
3948
|
+
const binding = inbox?.bindingId === undefined ? undefined : this.deliveryStore.getBinding(inbox.bindingId);
|
|
3949
|
+
if (!inbox || !binding || binding.workspace !== owner.workspace || binding.agentPreset !== owner.agentPreset
|
|
3950
|
+
|| externalPrincipalId(binding.principal) !== owner.principalId
|
|
3951
|
+
|| JSON.stringify(inbox.envelope.principal) !== JSON.stringify(binding.principal)
|
|
3952
|
+
|| JSON.stringify(inbox.envelope.conversation) !== JSON.stringify(binding.conversation))
|
|
3953
|
+
return undefined;
|
|
3954
|
+
const accepted = this.deliveryStore.inspectForegroundAcceptedExecutionForOwner({ inboxId: inbox.id,
|
|
3955
|
+
scope: { workspace: owner.workspace, preset: owner.agentPreset }, bindingId: binding.id,
|
|
3956
|
+
owner: { principalRecordId: owner.principalRecordId, principalVersion: owner.principalVersion } });
|
|
3957
|
+
const execution = this.deliveryStore.inspectForegroundExecutionForOwner({ inboxId: inbox.id,
|
|
3958
|
+
scope: { workspace: owner.workspace, preset: owner.agentPreset }, bindingId: binding.id,
|
|
3959
|
+
bindingVersion: binding.version, bindingGeneration: binding.generation,
|
|
3960
|
+
owner: { principalRecordId: owner.principalRecordId, principalVersion: owner.principalVersion } }) ?? accepted;
|
|
3961
|
+
if (execution === null)
|
|
3962
|
+
return undefined;
|
|
3963
|
+
const verifierComponent = (component) => accepted !== null && component.source.kind === 'evaluator' && component.source.id === 'assistant-verifier'
|
|
3964
|
+
&& component.evaluator.id === 'assistant-verifier' && component.evaluator.version === '1'
|
|
3965
|
+
&& component.evidence.some(ref => ref.kind === 'foreground-turn' && ref.ref === inbox.id)
|
|
3966
|
+
&& component.evidence.some(ref => ref.kind === 'execution' && ref.ref === accepted.executionRef)
|
|
3967
|
+
&& component.evidence.some(ref => ref.kind === 'acceptance-contract'
|
|
3968
|
+
&& ref.ref === accepted.contractId && ref.digest === accepted.contractDigest);
|
|
3969
|
+
if (canonical.execution !== undefined && (!verifierComponent(canonical.execution)
|
|
3970
|
+
|| canonical.execution.status !== accepted?.status))
|
|
3971
|
+
return undefined;
|
|
3972
|
+
let judgement = 'unresolved';
|
|
3973
|
+
let ownerRevision;
|
|
3974
|
+
const objective = canonical.objective;
|
|
3975
|
+
if (objective?.source.kind === 'user-feedback') {
|
|
3976
|
+
if (objective.source.id !== 'assistant-delivery/typed-owner-feedback')
|
|
3977
|
+
return undefined;
|
|
3978
|
+
const revision = evaluation.inspectTrustedTaskOwnerRevision({ scope, outcomeId: input.outcomeId,
|
|
3979
|
+
principalRecordId: owner.principalRecordId, principalVersion: owner.principalVersion });
|
|
3980
|
+
if (!revision || revision.outcomeId !== objective.outcomeId || revision.objectiveStatus !== objective.status)
|
|
3981
|
+
return undefined;
|
|
3982
|
+
const outboxRefs = objective.evidence.filter(ref => ref.kind === 'delivery-outbox');
|
|
3983
|
+
if (outboxRefs.length !== 1)
|
|
3984
|
+
return undefined;
|
|
3985
|
+
const outbox = this.deliveryStore.getOutbox(outboxRefs[0].ref);
|
|
3986
|
+
if (!outbox || outbox.intent.bindingId !== binding.id
|
|
3987
|
+
|| outbox.intent.idempotencyKey !== `inbound:${inbox.id}:reply`
|
|
3988
|
+
|| outbox.intent.replyToEventId !== inbox.envelope.eventId
|
|
3989
|
+
|| JSON.stringify(outbox.intent.target.principal) !== JSON.stringify(binding.principal)
|
|
3990
|
+
|| JSON.stringify(outbox.intent.target.conversation) !== JSON.stringify(binding.conversation)
|
|
3991
|
+
|| !['accepted', 'delivered', 'read'].includes(outbox.status))
|
|
3992
|
+
return undefined;
|
|
3993
|
+
judgement = 'owner-feedback';
|
|
3994
|
+
ownerRevision = Object.freeze({ version: revision.version, action: revision.action });
|
|
3995
|
+
}
|
|
3996
|
+
else if (objective !== undefined) {
|
|
3997
|
+
if (!verifierComponent(objective))
|
|
3998
|
+
return undefined;
|
|
3999
|
+
judgement = 'independent-verifier';
|
|
4000
|
+
}
|
|
4001
|
+
// Generic execution receipts establish task provenance, never goal success.
|
|
4002
|
+
// Without an acceptance contract, only authenticated typed owner feedback
|
|
4003
|
+
// may turn a completed ordinary conversation into a learning source.
|
|
4004
|
+
if (accepted === null && judgement !== 'owner-feedback')
|
|
4005
|
+
return undefined;
|
|
4006
|
+
// Fence cross-ledger reads before handing any task text to the consumer.
|
|
4007
|
+
if (!evaluation.isTrustedTaskLearningProjectionReceipt(canonical)
|
|
4008
|
+
|| acceptanceCanonicalJson(owner) !== acceptanceCanonicalJson(this.validateOwnerRoute(routeInput)))
|
|
4009
|
+
return undefined;
|
|
4010
|
+
const objectiveText = inbox.envelope.text;
|
|
4011
|
+
return Object.freeze({ protocol: 'assistant-delivery/owner-foreground-learning/v1', owner, canonical, judgement,
|
|
4012
|
+
...(ownerRevision === undefined ? {} : { ownerRevision }),
|
|
4013
|
+
source: Object.freeze({ sessionId: binding.sessionId, inboxId: inbox.id,
|
|
4014
|
+
objective: objectiveText.slice(0, 4096), truncated: objectiveText.length > 4096, quiescent: execution.quiescent,
|
|
4015
|
+
modelSelectionState: execution.modelSelectionState, ...(execution.modelSelection === undefined ? {} : { modelSelection: execution.modelSelection }) }) });
|
|
4016
|
+
}
|
|
3658
4017
|
/**
|
|
3659
4018
|
* Enqueue against a stable Host-owned route rather than a session binding.
|
|
3660
4019
|
* Policy is evaluated against the exact `route:<authorityId>` resource. The
|
|
@@ -3794,6 +4153,11 @@ export class AssistantDeliveryService extends Service {
|
|
|
3794
4153
|
const prepared = this.prepareAgentReply(agent, input);
|
|
3795
4154
|
return this.deliveryStore.enqueue(prepared.intent);
|
|
3796
4155
|
}
|
|
4156
|
+
/** Host-only replay boundary. The returned tombstone fences this exact Agent object permanently. */
|
|
4157
|
+
blockAgentRepliesForReplay(agent, config) {
|
|
4158
|
+
this.assertActive();
|
|
4159
|
+
return createReplyReplayBlock(agent, config);
|
|
4160
|
+
}
|
|
3797
4161
|
async replyCompletedPreferenceTurn(agent, expectedBinding, envelope, input) {
|
|
3798
4162
|
const prepared = this.prepareAgentReply(agent, {
|
|
3799
4163
|
idempotencyKey: `inbound:${envelope.eventId}:reply`,
|
|
@@ -3818,6 +4182,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
3818
4182
|
return projected ? 'recorded' : 'unavailable';
|
|
3819
4183
|
}
|
|
3820
4184
|
prepareAgentReply(agent, input) {
|
|
4185
|
+
assertReplyReplayAllowed(agent, input);
|
|
3821
4186
|
this.assertActive();
|
|
3822
4187
|
const binding = agent === undefined ? undefined : this.deliveryStore.getBindingBySession(String(agent.session.id));
|
|
3823
4188
|
if (agent === undefined || binding === undefined || binding.status !== 'active'
|