@dsh-enhanced/assistant-delivery 0.1.31 → 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 +13 -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 +270 -47
- 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/native-web-owner.d.ts.map +1 -1
- package/lib/native-web-owner.js +3 -3
- package/lib/native-web-owner.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 +115 -2
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +483 -94
- package/lib/service.js.map +1 -1
- package/lib/session-lease-runtime.d.ts +2 -1
- package/lib/session-lease-runtime.d.ts.map +1 -1
- package/lib/session-lease-runtime.js +3 -3
- package/lib/session-lease-runtime.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();
|
|
@@ -56,6 +58,7 @@ const goalOutcomeFeedbackFooter = [
|
|
|
56
58
|
'目标结果反馈:直接回复本消息并发送 `/feedback achieved`、`/feedback partial` 或 `/feedback not-achieved`。',
|
|
57
59
|
'`helpful` 等只记录偏好,不会被当成目标成败。',
|
|
58
60
|
].join('\n');
|
|
61
|
+
const goalWakeCompletionNotice = '目标已通过独立验收并完成。';
|
|
59
62
|
function isGoalOutcomeFeedbackAuthority(value) {
|
|
60
63
|
return typeof value === 'object' && value !== null
|
|
61
64
|
&& typeof value.issueOwnerGoalOutcomeFeedbackTarget === 'function'
|
|
@@ -241,6 +244,23 @@ export class AssistantDeliveryError extends Error {
|
|
|
241
244
|
this.name = 'AssistantDeliveryError';
|
|
242
245
|
}
|
|
243
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
|
+
}
|
|
244
264
|
const configSchema = Schema.object({
|
|
245
265
|
databasePath: Schema.string().required(),
|
|
246
266
|
spoolPath: Schema.string().required(),
|
|
@@ -523,6 +543,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
523
543
|
preferenceProducerGeneration = `assistant-delivery-preference:${randomUUID()}`;
|
|
524
544
|
acceptanceProducerGeneration = `assistant-delivery-acceptance:${randomUUID()}`;
|
|
525
545
|
acceptanceSink;
|
|
546
|
+
foregroundObserver;
|
|
526
547
|
/** Once a live verifier required acceptance, loss of that verifier must not silently bypass it. */
|
|
527
548
|
acceptanceRequired = false;
|
|
528
549
|
preferenceTurns = new WeakMap();
|
|
@@ -650,6 +671,10 @@ export class AssistantDeliveryService extends Service {
|
|
|
650
671
|
maxTextBytes: config.maxTextBytes,
|
|
651
672
|
prepareForegroundTaskAcceptance: (binding, envelope) => this.prepareForegroundTaskAcceptance(binding, envelope),
|
|
652
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),
|
|
653
678
|
modelPickerTtlMs: config.modelPickerTtlMs,
|
|
654
679
|
permissionPickerTtlMs: config.permissionPickerTtlMs,
|
|
655
680
|
getModelSelection: conversation => this.deliveryStore.getModelSelection(conversation),
|
|
@@ -730,6 +755,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
730
755
|
trustedDeliveryPreferenceProducers.delete(this);
|
|
731
756
|
this.preferenceFeedbackSink = undefined;
|
|
732
757
|
this.acceptanceSink = undefined;
|
|
758
|
+
this.foregroundObserver = undefined;
|
|
733
759
|
this.evaluationSink = undefined;
|
|
734
760
|
this.workflowTraceSink = undefined;
|
|
735
761
|
this.automationPresentationBinding?.dispose();
|
|
@@ -1291,7 +1317,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
1291
1317
|
}));
|
|
1292
1318
|
if (result.outcome === 'succeeded') {
|
|
1293
1319
|
current();
|
|
1294
|
-
if (input.includeOutput === true && result.quiescent
|
|
1320
|
+
if (input.includeOutput === true && result.quiescent) {
|
|
1295
1321
|
const text = result.output;
|
|
1296
1322
|
this.goalWakeResults.set(input, () => {
|
|
1297
1323
|
// Re-read the exact owner, binding generation and Session at enqueue,
|
|
@@ -1299,8 +1325,6 @@ export class AssistantDeliveryService extends Service {
|
|
|
1299
1325
|
const target = current();
|
|
1300
1326
|
if (target.conversation.channel === 'web') {
|
|
1301
1327
|
const owner = this.nativeWebOwner;
|
|
1302
|
-
// This finalizes the existing, flushed native Session reply. It does
|
|
1303
|
-
// not create a second send or claim that the reply was read.
|
|
1304
1328
|
if (!this.nativeWebBound || owner === undefined)
|
|
1305
1329
|
return denied();
|
|
1306
1330
|
try {
|
|
@@ -1309,8 +1333,15 @@ export class AssistantDeliveryService extends Service {
|
|
|
1309
1333
|
catch {
|
|
1310
1334
|
return denied();
|
|
1311
1335
|
}
|
|
1336
|
+
if (typeof text !== 'string' || text.trim().length === 0) {
|
|
1337
|
+
return this.publishScheduledGoalCompletionNotice(input, current, denied);
|
|
1338
|
+
}
|
|
1339
|
+
// This finalizes the existing, flushed native Session reply. It does
|
|
1340
|
+
// not create a second send or claim that the reply was read.
|
|
1312
1341
|
return Object.freeze({ kind: 'native-session', sessionId: target.sessionId });
|
|
1313
1342
|
}
|
|
1343
|
+
if (typeof text !== 'string' || text.trim().length === 0)
|
|
1344
|
+
return denied();
|
|
1314
1345
|
return this.publishScheduledGoalResult(input, text, current, denied);
|
|
1315
1346
|
});
|
|
1316
1347
|
}
|
|
@@ -1326,6 +1357,52 @@ export class AssistantDeliveryService extends Service {
|
|
|
1326
1357
|
return publish();
|
|
1327
1358
|
}
|
|
1328
1359
|
publishScheduledGoalResult(input, text, current, denied) {
|
|
1360
|
+
const { locator, proof, binding: finalBinding } = this.resolveScheduledGoalOutcome(input, current, denied);
|
|
1361
|
+
const decision = this.policy.authorize({
|
|
1362
|
+
subject: { kind: 'background', id: 'assistant-goals-wake/v1',
|
|
1363
|
+
workspace: finalBinding.workspace, principal: locator.principalId },
|
|
1364
|
+
action: 'send', resource: { kind: 'message', id: finalBinding.id }, context: { initiator: 'background' },
|
|
1365
|
+
}, { idempotencyKey: `message-send:goal-outcome:${proof.proofDigest}:${finalBinding.id}` });
|
|
1366
|
+
if (decision.effect !== 'allow')
|
|
1367
|
+
throw policyDenied(decision);
|
|
1368
|
+
const rendered = `${text}${goalOutcomeFeedbackFooter}`;
|
|
1369
|
+
if (Buffer.byteLength(rendered, 'utf8') > this.config.maxTextBytes) {
|
|
1370
|
+
throw new AssistantDeliveryError('runtime-conflict', 'scheduled goal result exceeds the delivery text limit');
|
|
1371
|
+
}
|
|
1372
|
+
const situation = goalDefinitionSituation(proof.goal.definitionDigest);
|
|
1373
|
+
return this.deliveryStore.enqueueGoalOutcomeTarget({
|
|
1374
|
+
locator, proof,
|
|
1375
|
+
intent: {
|
|
1376
|
+
idempotencyKey: `goal-outcome:${proof.proofDigest}:${finalBinding.id}`,
|
|
1377
|
+
bindingId: finalBinding.id,
|
|
1378
|
+
target: { conversation: finalBinding.conversation, principal: finalBinding.principal },
|
|
1379
|
+
text: rendered, format: 'markdown',
|
|
1380
|
+
metadata: Object.freeze({
|
|
1381
|
+
'dsh.learning.schemaVersion': '4', 'dsh.learning.kind': 'goal-outcome',
|
|
1382
|
+
'dsh.learning.goalId': locator.goalId, 'dsh.learning.assessmentId': locator.assessmentId,
|
|
1383
|
+
'dsh.learning.runId': proof.runId, 'dsh.learning.situation': situation,
|
|
1384
|
+
'dsh.learning.occurredAt': String(proof.receipt.completedAt),
|
|
1385
|
+
'dsh.learning.objectiveStatus': proof.receipt.objectiveStatus,
|
|
1386
|
+
'dsh.learning.proofDigest': proof.proofDigest,
|
|
1387
|
+
}),
|
|
1388
|
+
},
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
/** A textless terminal Web wake may notify its original owner, never impersonate a reply. */
|
|
1392
|
+
publishScheduledGoalCompletionNotice(input, current, denied) {
|
|
1393
|
+
const { locator, proof, binding } = this.resolveScheduledGoalOutcome(input, current, denied);
|
|
1394
|
+
if (proof.goal.phase !== 'complete' || proof.receipt.objectiveStatus !== 'achieved')
|
|
1395
|
+
return denied();
|
|
1396
|
+
const idempotencyKey = `goal-wake-completion-notice:${proof.proofDigest}:${binding.id}`;
|
|
1397
|
+
return this.enqueueOwnerNotification({
|
|
1398
|
+
sourceId: 'assistant-goals-wake/v1', ownerRouteId: locator.ownerRouteId,
|
|
1399
|
+
scope: { principalId: locator.principalId, principalRecordId: locator.principalRecordId,
|
|
1400
|
+
principalVersion: locator.principalVersion, workspace: locator.workspace, preset: locator.preset },
|
|
1401
|
+
sessionId: locator.sessionId, idempotencyKey, text: goalWakeCompletionNotice,
|
|
1402
|
+
expiresAt: Math.min(input.deadlineAt, proof.receipt.validUntil),
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
resolveScheduledGoalOutcome(input, current, denied) {
|
|
1329
1406
|
current();
|
|
1330
1407
|
let resolved;
|
|
1331
1408
|
try {
|
|
@@ -1380,35 +1457,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
1380
1457
|
|| route.binding.generation !== finalBinding.generation
|
|
1381
1458
|
|| route.binding.sessionId !== finalBinding.sessionId)
|
|
1382
1459
|
return denied();
|
|
1383
|
-
|
|
1384
|
-
subject: { kind: 'background', id: 'assistant-goals-wake/v1',
|
|
1385
|
-
workspace: finalBinding.workspace, principal: locator.principalId },
|
|
1386
|
-
action: 'send', resource: { kind: 'message', id: finalBinding.id }, context: { initiator: 'background' },
|
|
1387
|
-
}, { idempotencyKey: `message-send:goal-outcome:${proof.proofDigest}:${finalBinding.id}` });
|
|
1388
|
-
if (decision.effect !== 'allow')
|
|
1389
|
-
throw policyDenied(decision);
|
|
1390
|
-
const rendered = `${text}${goalOutcomeFeedbackFooter}`;
|
|
1391
|
-
if (Buffer.byteLength(rendered, 'utf8') > this.config.maxTextBytes) {
|
|
1392
|
-
throw new AssistantDeliveryError('runtime-conflict', 'scheduled goal result exceeds the delivery text limit');
|
|
1393
|
-
}
|
|
1394
|
-
const situation = `goal:${locator.goalId}:definition:${proof.goal.definitionVersion}`;
|
|
1395
|
-
return this.deliveryStore.enqueueGoalOutcomeTarget({
|
|
1396
|
-
locator, proof,
|
|
1397
|
-
intent: {
|
|
1398
|
-
idempotencyKey: `goal-outcome:${proof.proofDigest}:${finalBinding.id}`,
|
|
1399
|
-
bindingId: finalBinding.id,
|
|
1400
|
-
target: { conversation: finalBinding.conversation, principal: finalBinding.principal },
|
|
1401
|
-
text: rendered, format: 'markdown',
|
|
1402
|
-
metadata: Object.freeze({
|
|
1403
|
-
'dsh.learning.schemaVersion': '3', 'dsh.learning.kind': 'goal-outcome',
|
|
1404
|
-
'dsh.learning.goalId': locator.goalId, 'dsh.learning.assessmentId': locator.assessmentId,
|
|
1405
|
-
'dsh.learning.runId': proof.runId, 'dsh.learning.situation': situation,
|
|
1406
|
-
'dsh.learning.occurredAt': String(proof.receipt.completedAt),
|
|
1407
|
-
'dsh.learning.objectiveStatus': proof.receipt.objectiveStatus,
|
|
1408
|
-
'dsh.learning.proofDigest': proof.proofDigest,
|
|
1409
|
-
}),
|
|
1410
|
-
},
|
|
1411
|
-
});
|
|
1460
|
+
return Object.freeze({ locator, proof, binding: finalBinding });
|
|
1412
1461
|
}
|
|
1413
1462
|
/** Private verifier producer generation; invalidated with this service. */
|
|
1414
1463
|
trustedAcceptanceProducerGeneration = () => {
|
|
@@ -1451,6 +1500,91 @@ export class AssistantDeliveryService extends Service {
|
|
|
1451
1500
|
this.acceptanceSink = undefined;
|
|
1452
1501
|
};
|
|
1453
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
|
+
}
|
|
1454
1588
|
prepareForegroundTaskAcceptance(binding, envelope) {
|
|
1455
1589
|
const sink = this.acceptanceSink;
|
|
1456
1590
|
if (sink === undefined) {
|
|
@@ -1955,6 +2089,106 @@ export class AssistantDeliveryService extends Service {
|
|
|
1955
2089
|
}
|
|
1956
2090
|
return route;
|
|
1957
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
|
+
}
|
|
1958
2192
|
/**
|
|
1959
2193
|
* Host-only, content-free route minting seam for Preference promotion.
|
|
1960
2194
|
* No binding id is accepted from the caller; Delivery resolves the unique
|
|
@@ -2611,12 +2845,11 @@ export class AssistantDeliveryService extends Service {
|
|
|
2611
2845
|
return 'invalid-target';
|
|
2612
2846
|
return await this.dispatchGoalOutcomeFeedback(binding, inbox, target, parsed, goalOutcomeTarget);
|
|
2613
2847
|
}
|
|
2614
|
-
// Ordinary Agent replies have no learning metadata.
|
|
2615
|
-
//
|
|
2616
|
-
// rebuilds
|
|
2617
|
-
// selects a closed deterministic-deidentification template
|
|
2618
|
-
//
|
|
2619
|
-
// 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.
|
|
2620
2853
|
if (metadata?.['dsh.learning.kind'] !== 'automation-run') {
|
|
2621
2854
|
const sourceEventId = target.intent.replyToEventId;
|
|
2622
2855
|
if (typeof sourceEventId !== 'string')
|
|
@@ -2631,7 +2864,17 @@ export class AssistantDeliveryService extends Service {
|
|
|
2631
2864
|
owner: lineage,
|
|
2632
2865
|
bindingId: binding.id,
|
|
2633
2866
|
});
|
|
2634
|
-
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;
|
|
2635
2878
|
const projectForegroundOwner = async (inspectOnly) => {
|
|
2636
2879
|
const sink = this.evaluationSink;
|
|
2637
2880
|
if (sink === undefined || lineage === undefined)
|
|
@@ -2683,7 +2926,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
2683
2926
|
if (canonicalForeground && this.evaluationSink === undefined)
|
|
2684
2927
|
return 'unavailable';
|
|
2685
2928
|
try {
|
|
2686
|
-
// Evaluation commits first for
|
|
2929
|
+
// Evaluation commits first for recorded foreground tasks. If the sink
|
|
2687
2930
|
// is unavailable, never acknowledge a Delivery-only success; a replay
|
|
2688
2931
|
// can safely repeat the opaque command and then repair local workflow
|
|
2689
2932
|
// evidence after the canonical owner revision is durable.
|
|
@@ -2831,8 +3074,8 @@ export class AssistantDeliveryService extends Service {
|
|
|
2831
3074
|
const metadata = target.intent.metadata;
|
|
2832
3075
|
const locator = durable.locator;
|
|
2833
3076
|
const immutableProof = durable.proof;
|
|
2834
|
-
const situation =
|
|
2835
|
-
if (metadata?.['dsh.learning.schemaVersion'] !== '
|
|
3077
|
+
const situation = goalDefinitionSituation(immutableProof.goal.definitionDigest);
|
|
3078
|
+
if (metadata?.['dsh.learning.schemaVersion'] !== '4'
|
|
2836
3079
|
|| metadata['dsh.learning.kind'] !== 'goal-outcome'
|
|
2837
3080
|
|| metadata['dsh.learning.goalId'] !== locator.goalId
|
|
2838
3081
|
|| metadata['dsh.learning.assessmentId'] !== locator.assessmentId
|
|
@@ -3119,38 +3362,14 @@ export class AssistantDeliveryService extends Service {
|
|
|
3119
3362
|
return reviewer === 'auto-review' ? next() : 'unavailable';
|
|
3120
3363
|
if (initialRoute.state !== 'bound')
|
|
3121
3364
|
return 'unavailable';
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
}
|
|
3131
|
-
if (authority === undefined || authority.binding.id !== initialRoute.binding.id)
|
|
3132
|
-
return 'unavailable';
|
|
3133
|
-
const sessions = ctx.get('sessions');
|
|
3134
|
-
if (sessions === undefined)
|
|
3135
|
-
return 'unavailable';
|
|
3136
|
-
try {
|
|
3137
|
-
if (!await sessions.flush(request.agent.session))
|
|
3138
|
-
return 'unavailable';
|
|
3139
|
-
}
|
|
3140
|
-
catch {
|
|
3141
|
-
return 'unavailable';
|
|
3142
|
-
}
|
|
3143
|
-
if (signalAborted(request.signal))
|
|
3144
|
-
return 'cancelled';
|
|
3145
|
-
let persisted;
|
|
3146
|
-
try {
|
|
3147
|
-
persisted = this.resolveToolApprovalAuthority(ctx, request);
|
|
3148
|
-
}
|
|
3149
|
-
catch {
|
|
3150
|
-
return 'unavailable';
|
|
3151
|
-
}
|
|
3152
|
-
if (!sameToolApprovalAuthority(authority, persisted))
|
|
3153
|
-
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'.
|
|
3154
3373
|
const operationId = `tool-approval:${randomUUID()}`;
|
|
3155
3374
|
const expiresAt = Date.now() + this.config.toolApprovalTtlMs;
|
|
3156
3375
|
const controller = new AbortController();
|
|
@@ -3160,7 +3379,11 @@ export class AssistantDeliveryService extends Service {
|
|
|
3160
3379
|
controller.abort(request.signal?.reason);
|
|
3161
3380
|
};
|
|
3162
3381
|
request.signal?.addEventListener('abort', onRequestAbort, { once: true });
|
|
3163
|
-
|
|
3382
|
+
if (signalAborted(request.signal)) {
|
|
3383
|
+
request.signal?.removeEventListener('abort', onRequestAbort);
|
|
3384
|
+
return 'cancelled';
|
|
3385
|
+
}
|
|
3386
|
+
const deadlineAbort = new Promise(resolve => {
|
|
3164
3387
|
controller.signal.addEventListener('abort', () => {
|
|
3165
3388
|
resolve(requestCancelled ? 'cancelled' : 'unavailable');
|
|
3166
3389
|
}, { once: true });
|
|
@@ -3170,26 +3393,69 @@ export class AssistantDeliveryService extends Service {
|
|
|
3170
3393
|
}, this.config.toolApprovalTtlMs);
|
|
3171
3394
|
timeout.unref?.();
|
|
3172
3395
|
this.toolApprovalControllers.add(controller);
|
|
3173
|
-
const adapterRequest = Object.freeze({
|
|
3174
|
-
operationId,
|
|
3175
|
-
bindingId: authority.binding.id,
|
|
3176
|
-
target: Object.freeze({
|
|
3177
|
-
conversation: Object.freeze({ ...authority.binding.conversation }),
|
|
3178
|
-
principal: Object.freeze({ ...authority.binding.principal }),
|
|
3179
|
-
}),
|
|
3180
|
-
expiresAt,
|
|
3181
|
-
actionHash: authority.actionHash,
|
|
3182
|
-
toolName: request.toolName,
|
|
3183
|
-
callId: authority.callId,
|
|
3184
|
-
...(request.reason === undefined ? {} : { reason: request.reason }),
|
|
3185
|
-
arguments: authority.arguments,
|
|
3186
|
-
});
|
|
3187
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;
|
|
3188
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
|
+
});
|
|
3189
3455
|
const answer = Promise.resolve()
|
|
3190
|
-
.then(() =>
|
|
3456
|
+
.then(() => granted.adapter.requestToolApproval(adapterRequest, controller.signal))
|
|
3191
3457
|
.then(value => value, () => 'unavailable');
|
|
3192
|
-
outcome = await Promise.race([answer,
|
|
3458
|
+
outcome = await Promise.race([answer, deadlineAbort]);
|
|
3193
3459
|
}
|
|
3194
3460
|
finally {
|
|
3195
3461
|
clearTimeout(timeout);
|
|
@@ -3207,7 +3473,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
3207
3473
|
catch {
|
|
3208
3474
|
return 'unavailable';
|
|
3209
3475
|
}
|
|
3210
|
-
if (current === undefined || !sameToolApprovalAuthority(authority, current))
|
|
3476
|
+
if (authority === undefined || current === undefined || !sameToolApprovalAuthority(authority, current))
|
|
3211
3477
|
return 'unavailable';
|
|
3212
3478
|
if (outcome === 'allowed-once') {
|
|
3213
3479
|
try {
|
|
@@ -3631,6 +3897,123 @@ export class AssistantDeliveryService extends Service {
|
|
|
3631
3897
|
generation: resolved.snapshot.generation,
|
|
3632
3898
|
});
|
|
3633
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
|
+
}
|
|
3634
4017
|
/**
|
|
3635
4018
|
* Enqueue against a stable Host-owned route rather than a session binding.
|
|
3636
4019
|
* Policy is evaluated against the exact `route:<authorityId>` resource. The
|
|
@@ -3770,6 +4153,11 @@ export class AssistantDeliveryService extends Service {
|
|
|
3770
4153
|
const prepared = this.prepareAgentReply(agent, input);
|
|
3771
4154
|
return this.deliveryStore.enqueue(prepared.intent);
|
|
3772
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
|
+
}
|
|
3773
4161
|
async replyCompletedPreferenceTurn(agent, expectedBinding, envelope, input) {
|
|
3774
4162
|
const prepared = this.prepareAgentReply(agent, {
|
|
3775
4163
|
idempotencyKey: `inbound:${envelope.eventId}:reply`,
|
|
@@ -3794,6 +4182,7 @@ export class AssistantDeliveryService extends Service {
|
|
|
3794
4182
|
return projected ? 'recorded' : 'unavailable';
|
|
3795
4183
|
}
|
|
3796
4184
|
prepareAgentReply(agent, input) {
|
|
4185
|
+
assertReplyReplayAllowed(agent, input);
|
|
3797
4186
|
this.assertActive();
|
|
3798
4187
|
const binding = agent === undefined ? undefined : this.deliveryStore.getBindingBySession(String(agent.session.id));
|
|
3799
4188
|
if (agent === undefined || binding === undefined || binding.status !== 'active'
|