@dsh-enhanced/assistant-evaluation 0.1.66 → 0.1.68
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 +38 -3
- package/lib/attention-owner-questions.d.ts +21 -2
- package/lib/attention-owner-questions.d.ts.map +1 -1
- package/lib/attention-owner-questions.js +57 -20
- package/lib/attention-owner-questions.js.map +1 -1
- package/lib/attention-tuning.d.ts +66 -5
- package/lib/attention-tuning.d.ts.map +1 -1
- package/lib/attention-tuning.js +165 -13
- package/lib/attention-tuning.js.map +1 -1
- package/lib/benchmark/cli.d.ts.map +1 -1
- package/lib/benchmark/cli.js +9 -12
- package/lib/benchmark/cli.js.map +1 -1
- package/lib/benchmark/native-goal-services.d.ts +4 -2
- package/lib/benchmark/native-goal-services.d.ts.map +1 -1
- package/lib/benchmark/native-goal-services.js +3 -3
- package/lib/benchmark/native-goal-services.js.map +1 -1
- package/lib/benchmark/native-skills-runtime.d.ts +4 -2
- package/lib/benchmark/native-skills-runtime.d.ts.map +1 -1
- package/lib/benchmark/native-skills-runtime.js +4 -3
- package/lib/benchmark/native-skills-runtime.js.map +1 -1
- package/lib/benchmark/native-skills.d.ts +4 -2
- package/lib/benchmark/native-skills.d.ts.map +1 -1
- package/lib/benchmark/native-skills.js +6 -6
- package/lib/benchmark/native-skills.js.map +1 -1
- package/lib/benchmark/strategy-capabilities.d.ts.map +1 -1
- package/lib/benchmark/strategy-capabilities.js +0 -5
- package/lib/benchmark/strategy-capabilities.js.map +1 -1
- package/lib/benchmark/strategy-config.d.ts +4 -2
- package/lib/benchmark/strategy-config.d.ts.map +1 -1
- package/lib/benchmark/strategy-config.js +8 -3
- package/lib/benchmark/strategy-config.js.map +1 -1
- package/lib/benchmark/strategy-corpus-v2.js +1 -1
- package/lib/benchmark/strategy-evidence.d.ts +2 -2
- package/lib/benchmark/strategy-evidence.d.ts.map +1 -1
- package/lib/benchmark/strategy-evidence.js +1 -1
- package/lib/benchmark/strategy-evidence.js.map +1 -1
- package/lib/benchmark/strategy-executor.d.ts +4 -2
- package/lib/benchmark/strategy-executor.d.ts.map +1 -1
- package/lib/benchmark/strategy-executor.js +3 -3
- package/lib/benchmark/strategy-executor.js.map +1 -1
- package/lib/benchmark/strategy-goal-runtime.d.ts +4 -2
- package/lib/benchmark/strategy-goal-runtime.d.ts.map +1 -1
- package/lib/benchmark/strategy-goal-runtime.js +4 -3
- package/lib/benchmark/strategy-goal-runtime.js.map +1 -1
- package/lib/index.d.ts +7 -5
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -2
- package/lib/index.js.map +1 -1
- package/lib/intake-attention-agreement-store.d.ts +33 -0
- package/lib/intake-attention-agreement-store.d.ts.map +1 -0
- package/lib/intake-attention-agreement-store.js +162 -0
- package/lib/intake-attention-agreement-store.js.map +1 -0
- package/lib/intake-attention-agreement.d.ts +63 -0
- package/lib/intake-attention-agreement.d.ts.map +1 -1
- package/lib/intake-attention-agreement.js +63 -0
- package/lib/intake-attention-agreement.js.map +1 -1
- package/lib/notification-outcomes.d.ts +7 -0
- package/lib/notification-outcomes.d.ts.map +1 -1
- package/lib/notification-outcomes.js +52 -1
- package/lib/notification-outcomes.js.map +1 -1
- package/lib/service.d.ts +117 -5
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +242 -6
- 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 +92 -1
- package/lib/sqlite.js.map +1 -1
- package/lib/store.d.ts +14 -0
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +42 -1
- package/lib/store.js.map +1 -1
- package/lib/types.d.ts +20 -1
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +8 -0
- package/lib/types.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +18 -23
- package/lib/benchmark/super-relay.d.ts +0 -26
- package/lib/benchmark/super-relay.d.ts.map +0 -1
- package/lib/benchmark/super-relay.js +0 -173
- package/lib/benchmark/super-relay.js.map +0 -1
package/lib/service.js
CHANGED
|
@@ -4,10 +4,11 @@ import { acceptanceDigest, goalDefinitionSituation, validateTaskAcceptanceContra
|
|
|
4
4
|
import { EvaluationStore, EvaluationStoreError, canonicalEvaluationScope, evaluationLearningProjectionDigest, } from './store.js';
|
|
5
5
|
import { registerEvaluationTools } from './tools.js';
|
|
6
6
|
import { normalizeAttentionCohort, } from './attention-tuning.js';
|
|
7
|
-
import { ATTENTION_OWNER_QUESTION_SOURCE_ID, attentionOwnerQuestionIdempotencyKey, attentionOwnerQuestionText, } from './attention-owner-questions.js';
|
|
7
|
+
import { ATTENTION_OWNER_QUESTION_SOURCE_ID, attentionOwnerQuestionIdempotencyKey, attentionOwnerQuestionText, parseAttentionOwnerQuestionChoice, ATTENTION_OWNER_QUESTION_REPLY_PROTOCOL, } from './attention-owner-questions.js';
|
|
8
8
|
import { validateWorkItemOwnerFeedbackProof } from './work-item-owner-proof.js';
|
|
9
|
-
import { computeIntakeAttentionAgreement, INTAKE_ATTENTION_AGREEMENT_SCAN_LIMIT, isIntakeProvenanceSource } from './intake-attention-agreement.js';
|
|
9
|
+
import { computeIntakeAttentionAgreement, createIntakeAttentionAgreementSnapshot, INTAKE_ATTENTION_AGREEMENT_HISTORY_PROTOCOL, INTAKE_ATTENTION_AGREEMENT_SCAN_LIMIT, isIntakeProvenanceSource, } from './intake-attention-agreement.js';
|
|
10
10
|
import { executionStatuses, objectiveStatuses, outcomeSourceKinds, TRUSTED_EVALUATION_PRODUCER_PROTOCOL, } from './types.js';
|
|
11
|
+
const PERSIST_DISABLED = Object.freeze({ recorded: false, replayed: false, reason: 'disabled' });
|
|
11
12
|
const OWNER_NOTIFICATION_WITHDRAWAL_PROTOCOL = 'assistant-delivery/owner-notification-withdrawal/v1';
|
|
12
13
|
function isOwnerQuestionDeliveryPort(value) {
|
|
13
14
|
const port = value;
|
|
@@ -15,6 +16,10 @@ function isOwnerQuestionDeliveryPort(value) {
|
|
|
15
16
|
&& typeof port.validateOwnerRoute === 'function' && typeof port.resolveOwnerRoute === 'function'
|
|
16
17
|
&& typeof port.inspectOwnerNotification === 'function' && typeof port.registerOwnerNotificationWithdrawal === 'function';
|
|
17
18
|
}
|
|
19
|
+
function isOwnerQuestionReplyPort(value) {
|
|
20
|
+
const port = value;
|
|
21
|
+
return typeof port === 'object' && port !== null && typeof port.registerOwnerNotificationReply === 'function';
|
|
22
|
+
}
|
|
18
23
|
export const ASSISTANT_EVALUATION_SKILL = `# Personal assistant self-evaluation
|
|
19
24
|
|
|
20
25
|
Use this workflow only to assess whether an already-finished task met its objective. A self-assessment is diagnostic evidence, never ground truth and never permission to change production behavior.
|
|
@@ -52,6 +57,7 @@ const configSchema = Schema.object({
|
|
|
52
57
|
notificationSyncIntervalMs: Schema.number().step(1).min(0).max(86_400_000).default(60_000),
|
|
53
58
|
attentionTuningAutoAdopt: Schema.boolean().default(false),
|
|
54
59
|
intakeAttentionAgreementEnabled: Schema.boolean().default(false),
|
|
60
|
+
intakeAttentionAgreementPersistIntervalMs: Schema.number().step(1).min(60_000).max(604_800_000),
|
|
55
61
|
attentionTuningCohorts: Schema.array(Schema.object({
|
|
56
62
|
lane: Schema.string().required(),
|
|
57
63
|
sourceId: Schema.string().required(),
|
|
@@ -248,8 +254,13 @@ export class AssistantEvaluationService extends Service {
|
|
|
248
254
|
projectionTimer;
|
|
249
255
|
notificationFacts;
|
|
250
256
|
lastNotificationSyncAt = Number.NEGATIVE_INFINITY;
|
|
257
|
+
lastIntakeAgreementPersistAt = Number.NEGATIVE_INFINITY;
|
|
258
|
+
/** Rotates across scopes when one database serves several (bounded one row per tick). */
|
|
259
|
+
intakeAgreementPersistCursor;
|
|
251
260
|
ownerQuestionWithdrawal;
|
|
252
261
|
activeOwnerQuestionWithdrawals = new WeakSet();
|
|
262
|
+
ownerQuestionReply;
|
|
263
|
+
activeOwnerQuestionReplies = new WeakSet();
|
|
253
264
|
attentionCohorts;
|
|
254
265
|
active = true;
|
|
255
266
|
constructor(ctx, input, options = {}) {
|
|
@@ -272,6 +283,15 @@ export class AssistantEvaluationService extends Service {
|
|
|
272
283
|
if (this.config.intakeAttentionAgreementEnabled && !this.config.notificationOutcomesEnabled) {
|
|
273
284
|
throw new Error('assistant-evaluation: intakeAttentionAgreementEnabled requires notificationOutcomesEnabled');
|
|
274
285
|
}
|
|
286
|
+
if (this.config.intakeAttentionAgreementPersistIntervalMs !== undefined) {
|
|
287
|
+
const value = this.config.intakeAttentionAgreementPersistIntervalMs;
|
|
288
|
+
if (!Number.isSafeInteger(value) || value < 60_000 || value > 604_800_000) {
|
|
289
|
+
throw new Error('assistant-evaluation: intakeAttentionAgreementPersistIntervalMs must be between 60000 and 604800000');
|
|
290
|
+
}
|
|
291
|
+
if (!this.config.intakeAttentionAgreementEnabled || !this.config.notificationOutcomesEnabled) {
|
|
292
|
+
throw new Error('assistant-evaluation: intakeAttentionAgreementPersistIntervalMs requires intakeAttentionAgreementEnabled and notificationOutcomesEnabled');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
275
295
|
if (this.config.attentionTuningAutoAdopt && !this.config.notificationOutcomesEnabled) {
|
|
276
296
|
throw new Error('assistant-evaluation: attentionTuningAutoAdopt requires notificationOutcomesEnabled');
|
|
277
297
|
}
|
|
@@ -401,6 +421,8 @@ export class AssistantEvaluationService extends Service {
|
|
|
401
421
|
if (this.config.attentionOwnerQuestionDeliveryEnabled) {
|
|
402
422
|
this.bindOwnerQuestionWithdrawal(currentDelivery);
|
|
403
423
|
ctx.inject(['assistantDelivery'], deliveryCtx => this.bindOwnerQuestionWithdrawal(deliveryCtx.get('assistantDelivery')));
|
|
424
|
+
this.bindOwnerQuestionReply(currentDelivery);
|
|
425
|
+
ctx.inject(['assistantDelivery'], deliveryCtx => this.bindOwnerQuestionReply(deliveryCtx.get('assistantDelivery')));
|
|
404
426
|
}
|
|
405
427
|
const currentVerifier = ctx.get('assistantVerifier');
|
|
406
428
|
if (isTrustedVerifierEvaluationProducer(currentVerifier))
|
|
@@ -437,6 +459,14 @@ export class AssistantEvaluationService extends Service {
|
|
|
437
459
|
this.reconcileAttentionOwnerQuestions();
|
|
438
460
|
}
|
|
439
461
|
catch { /* retried on a later sync */ }
|
|
462
|
+
const agreementInterval = this.config.intakeAttentionAgreementPersistIntervalMs;
|
|
463
|
+
if (agreementInterval !== undefined && now - this.lastIntakeAgreementPersistAt >= agreementInterval) {
|
|
464
|
+
this.lastIntakeAgreementPersistAt = now;
|
|
465
|
+
try {
|
|
466
|
+
this.reconcileIntakeAttentionAgreementPersistence();
|
|
467
|
+
}
|
|
468
|
+
catch { /* retried on a later sync */ }
|
|
469
|
+
}
|
|
440
470
|
}
|
|
441
471
|
void this.reconcileProjections().catch(() => { });
|
|
442
472
|
}, this.config.projectionIntervalMs);
|
|
@@ -449,6 +479,7 @@ export class AssistantEvaluationService extends Service {
|
|
|
449
479
|
this.projectionTimer = undefined;
|
|
450
480
|
this.notificationFacts = undefined;
|
|
451
481
|
this.ownerQuestionWithdrawal?.dispose();
|
|
482
|
+
this.ownerQuestionReply?.dispose();
|
|
452
483
|
disposeCurrentProjector?.();
|
|
453
484
|
this.automationBinding?.dispose();
|
|
454
485
|
this.deliveryBinding?.dispose();
|
|
@@ -630,6 +661,14 @@ export class AssistantEvaluationService extends Service {
|
|
|
630
661
|
this.assertActive();
|
|
631
662
|
return this.store.getForegroundLearningProjection(exactEvaluationHostScope(input.scope), hostIdentifier(input.inboxId, 'inboxId', 1_000));
|
|
632
663
|
}
|
|
664
|
+
/**
|
|
665
|
+
* Host-only resource metrics of one trusted outcome. Kept out of the
|
|
666
|
+
* learning-projection receipt so historical digests stay unchanged.
|
|
667
|
+
*/
|
|
668
|
+
inspectTrustedOutcomeMetrics(input) {
|
|
669
|
+
this.assertActive();
|
|
670
|
+
return this.store.inspectTrustedOutcomeMetrics(exactEvaluationHostScope(input.scope), hostIdentifier(input.outcomeId, 'outcomeId', 200));
|
|
671
|
+
}
|
|
633
672
|
/** Host-only exact current owner result for one durable work item. */
|
|
634
673
|
getTrustedWorkItemLearningProjection(input) {
|
|
635
674
|
this.assertActive();
|
|
@@ -928,11 +967,82 @@ export class AssistantEvaluationService extends Service {
|
|
|
928
967
|
throw new AssistantEvaluationError('invalid-input', 'intake attention agreement window is invalid');
|
|
929
968
|
}
|
|
930
969
|
const { scope } = canonicalEvaluationScope(input.scope);
|
|
970
|
+
return this.#intakeAttentionAgreement(scope, from, to);
|
|
971
|
+
}
|
|
972
|
+
/** Shared read model for the ad hoc inspect and the E1 persistence snapshot. */
|
|
973
|
+
#intakeAttentionAgreement(scope, from, to) {
|
|
931
974
|
const heads = this.store.notifications.workItemHeads(scope, from, to, INTAKE_ATTENTION_AGREEMENT_SCAN_LIMIT);
|
|
932
975
|
const workItems = this.ctx.get('assistantWorkItems');
|
|
933
976
|
return computeIntakeAttentionAgreement({ scope, fromNotifiedAt: from, toNotifiedAt: to, records: heads.records,
|
|
934
977
|
truncated: heads.truncated, source: isIntakeProvenanceSource(workItems) ? workItems : undefined });
|
|
935
978
|
}
|
|
979
|
+
/**
|
|
980
|
+
* WP22 E1, default off: persist at most one agreement snapshot per call,
|
|
981
|
+
* using one aligned interval bucket. The window is
|
|
982
|
+
* `[bucketEnd - defaultSummaryWindowMs, bucketEnd)`, so a retry inside the
|
|
983
|
+
* same bucket computes the same window and the store replays the existing
|
|
984
|
+
* digest; a late outcome revision changes the digest and appends one
|
|
985
|
+
* immutable revision row for the same window. When the database serves
|
|
986
|
+
* several scopes the method rotates deterministically; one call writes at
|
|
987
|
+
* most one row and one credential-free scan.
|
|
988
|
+
*
|
|
989
|
+
* It never sends, adopts, authorizes intake/auto or changes the read model.
|
|
990
|
+
* Without a WorkItems peer it writes nothing and says so; with a peer that
|
|
991
|
+
* throws per item (including WorkItems' intake ledger off) the item is
|
|
992
|
+
* counted as `unresolved` inside a normal snapshot.
|
|
993
|
+
*/
|
|
994
|
+
reconcileIntakeAttentionAgreementPersistence() {
|
|
995
|
+
this.assertActive();
|
|
996
|
+
const interval = this.config.intakeAttentionAgreementPersistIntervalMs;
|
|
997
|
+
if (interval === undefined)
|
|
998
|
+
return PERSIST_DISABLED;
|
|
999
|
+
if (!this.config.notificationOutcomesEnabled || !this.config.intakeAttentionAgreementEnabled)
|
|
1000
|
+
return PERSIST_DISABLED;
|
|
1001
|
+
const workItems = this.ctx.get('assistantWorkItems');
|
|
1002
|
+
if (!isIntakeProvenanceSource(workItems))
|
|
1003
|
+
return Object.freeze({ recorded: false, replayed: false, reason: 'work-items-unavailable' });
|
|
1004
|
+
const scopes = this.store.notifications.workItemScopes(100);
|
|
1005
|
+
if (scopes.length === 0)
|
|
1006
|
+
return Object.freeze({ recorded: false, replayed: false, reason: 'no-work-item-notifications' });
|
|
1007
|
+
const scopeKeys = scopes.map(scope => canonicalEvaluationScope(scope).scopeKey);
|
|
1008
|
+
const cursor = this.intakeAgreementPersistCursor;
|
|
1009
|
+
const next = cursor === undefined ? 0 : scopeKeys.findIndex(key => key > cursor);
|
|
1010
|
+
const index = next === -1 ? 0 : next;
|
|
1011
|
+
const scope = scopes[index];
|
|
1012
|
+
const scopeKey = scopeKeys[index];
|
|
1013
|
+
const now = this.now();
|
|
1014
|
+
const to = Math.floor(now / interval) * interval;
|
|
1015
|
+
const from = Math.max(0, to - this.config.defaultSummaryWindowMs);
|
|
1016
|
+
if (to <= from)
|
|
1017
|
+
return Object.freeze({ recorded: false, replayed: false, reason: 'outside-window' });
|
|
1018
|
+
this.intakeAgreementPersistCursor = scopeKey;
|
|
1019
|
+
const agreement = this.#intakeAttentionAgreement(scope, from, to);
|
|
1020
|
+
if (agreement.scanned === 0)
|
|
1021
|
+
return Object.freeze({ recorded: false, replayed: false, reason: 'no-work-item-notifications', scope });
|
|
1022
|
+
const snapshot = createIntakeAttentionAgreementSnapshot({ agreement, computedAt: now,
|
|
1023
|
+
windowMs: this.config.defaultSummaryWindowMs, scanLimit: INTAKE_ATTENTION_AGREEMENT_SCAN_LIMIT });
|
|
1024
|
+
const result = this.store.intakeAttentionAgreement.record(scope, snapshot);
|
|
1025
|
+
return Object.freeze({ recorded: !result.replayed, replayed: result.replayed, scope, snapshot: result.record });
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* WP22 E1 read-only history, newest first. Undefined when
|
|
1029
|
+
* `intakeAttentionAgreementEnabled` is off; persisted rows are returned only
|
|
1030
|
+
* for the exact owner scope. No snapshot is a causal claim and none may
|
|
1031
|
+
* authorize intake/auto.
|
|
1032
|
+
*/
|
|
1033
|
+
inspectIntakeAttentionAgreementHistory(input) {
|
|
1034
|
+
this.assertActive();
|
|
1035
|
+
if (!this.config.intakeAttentionAgreementEnabled)
|
|
1036
|
+
return undefined;
|
|
1037
|
+
const limit = input.limit ?? 20;
|
|
1038
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) {
|
|
1039
|
+
throw new AssistantEvaluationError('invalid-input', 'intake attention agreement history limit is invalid');
|
|
1040
|
+
}
|
|
1041
|
+
const { scope } = canonicalEvaluationScope(input.scope);
|
|
1042
|
+
const snapshots = this.store.intakeAttentionAgreement.list(scope, limit);
|
|
1043
|
+
return Object.freeze({ protocol: INTAKE_ATTENTION_AGREEMENT_HISTORY_PROTOCOL, causal: false,
|
|
1044
|
+
scope: Object.freeze({ workspace: scope.workspace, preset: scope.preset }), snapshots: Object.freeze(snapshots) });
|
|
1045
|
+
}
|
|
936
1046
|
#attentionPolicy() {
|
|
937
1047
|
return Object.freeze({
|
|
938
1048
|
minSample: this.config.attentionTuningMinSample,
|
|
@@ -942,7 +1052,14 @@ export class AssistantEvaluationService extends Service {
|
|
|
942
1052
|
});
|
|
943
1053
|
}
|
|
944
1054
|
#looseningOptions() {
|
|
945
|
-
|
|
1055
|
+
if (!this.config.attentionLooseningEnabled)
|
|
1056
|
+
return undefined;
|
|
1057
|
+
return Object.freeze({
|
|
1058
|
+
replayWindowMs: this.config.attentionLooseningReplayWindowMs,
|
|
1059
|
+
...(this.config.attentionOwnerQuestionDeliveryEnabled
|
|
1060
|
+
? { ownerQuestionReplyLoop: Object.freeze({ noticeTtlMs: this.config.attentionOwnerQuestionNoticeTtlMs }) }
|
|
1061
|
+
: {}),
|
|
1062
|
+
});
|
|
946
1063
|
}
|
|
947
1064
|
#attentionCohort(input) {
|
|
948
1065
|
let cohort;
|
|
@@ -1095,6 +1212,95 @@ export class AssistantEvaluationService extends Service {
|
|
|
1095
1212
|
this.ownerQuestionWithdrawal = binding;
|
|
1096
1213
|
return binding.dispose;
|
|
1097
1214
|
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Register (once per Delivery instance) the slice-5 reply consumer. A
|
|
1217
|
+
* Delivery without the reply port is treated as absent: slice-4 notice
|
|
1218
|
+
* delivery keeps working, only replies are not read. A registration error
|
|
1219
|
+
* only disables replies; it never blocks notice delivery.
|
|
1220
|
+
*/
|
|
1221
|
+
bindOwnerQuestionReply(source) {
|
|
1222
|
+
if (!this.active || !isOwnerQuestionReplyPort(source))
|
|
1223
|
+
return undefined;
|
|
1224
|
+
if (this.ownerQuestionReply?.source === source)
|
|
1225
|
+
return undefined;
|
|
1226
|
+
const registration = Object.freeze({
|
|
1227
|
+
protocol: ATTENTION_OWNER_QUESTION_REPLY_PROTOCOL,
|
|
1228
|
+
authority: 'assistantEvaluation',
|
|
1229
|
+
sourceId: ATTENTION_OWNER_QUESTION_SOURCE_ID,
|
|
1230
|
+
idempotencyKeyPrefix: attentionOwnerQuestionIdempotencyKey(''),
|
|
1231
|
+
consume: (input) => this.#consumeOwnerQuestionReply(input),
|
|
1232
|
+
});
|
|
1233
|
+
this.activeOwnerQuestionReplies.add(registration);
|
|
1234
|
+
let unregister;
|
|
1235
|
+
try {
|
|
1236
|
+
unregister = source.registerOwnerNotificationReply(registration)?.unregister;
|
|
1237
|
+
}
|
|
1238
|
+
catch (error) {
|
|
1239
|
+
this.activeOwnerQuestionReplies.delete(registration);
|
|
1240
|
+
this.ctx.logger.warn(`assistant-evaluation: owner-question reply registration failed; replies are not read: ${String(error)}`);
|
|
1241
|
+
return undefined;
|
|
1242
|
+
}
|
|
1243
|
+
if (typeof unregister !== 'function') {
|
|
1244
|
+
this.activeOwnerQuestionReplies.delete(registration);
|
|
1245
|
+
this.ctx.logger.warn('assistant-evaluation: owner-question reply registration returned no unregister; replies are not read');
|
|
1246
|
+
return undefined;
|
|
1247
|
+
}
|
|
1248
|
+
const release = unregister;
|
|
1249
|
+
this.ownerQuestionReply?.dispose();
|
|
1250
|
+
let live = true;
|
|
1251
|
+
const binding = Object.freeze({ source, registration, dispose: () => {
|
|
1252
|
+
if (!live)
|
|
1253
|
+
return;
|
|
1254
|
+
live = false;
|
|
1255
|
+
this.activeOwnerQuestionReplies.delete(registration);
|
|
1256
|
+
try {
|
|
1257
|
+
release();
|
|
1258
|
+
}
|
|
1259
|
+
catch { /* Delivery already gone */ }
|
|
1260
|
+
if (this.ownerQuestionReply === binding)
|
|
1261
|
+
this.ownerQuestionReply = undefined;
|
|
1262
|
+
} });
|
|
1263
|
+
this.ownerQuestionReply = binding;
|
|
1264
|
+
return binding.dispose;
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Synchronous Delivery callback for one authenticated reply to an exact
|
|
1268
|
+
* owner-question notice. Only exact whole-phrase choices are accepted; a
|
|
1269
|
+
* recognized choice is consumed even when its question is no longer open,
|
|
1270
|
+
* so a stale "authorize-once" never reaches the Agent. Storage errors are
|
|
1271
|
+
* `transient` and leave Delivery's Inbox pending for a bounded retry.
|
|
1272
|
+
*/
|
|
1273
|
+
#consumeOwnerQuestionReply(input) {
|
|
1274
|
+
if (!this.active || !this.config.attentionOwnerQuestionDeliveryEnabled)
|
|
1275
|
+
return 'ignored';
|
|
1276
|
+
if (typeof input?.idempotencyKey !== 'string' || typeof input.text !== 'string')
|
|
1277
|
+
return 'ignored';
|
|
1278
|
+
const prefix = attentionOwnerQuestionIdempotencyKey('');
|
|
1279
|
+
if (!input.idempotencyKey.startsWith(prefix))
|
|
1280
|
+
return 'ignored';
|
|
1281
|
+
const questionId = input.idempotencyKey.slice(prefix.length);
|
|
1282
|
+
const choice = parseAttentionOwnerQuestionChoice(input.text);
|
|
1283
|
+
if (choice === undefined || questionId.length === 0)
|
|
1284
|
+
return 'ignored';
|
|
1285
|
+
try {
|
|
1286
|
+
for (const cohort of this.attentionCohorts) {
|
|
1287
|
+
for (const scope of this.store.attention.scopes(cohort)) {
|
|
1288
|
+
const latest = this.store.attention.latestOwnerQuestion(scope, cohort);
|
|
1289
|
+
if (latest === undefined || latest === 'unverifiable' || latest.questionId !== questionId)
|
|
1290
|
+
continue;
|
|
1291
|
+
this.applyAttentionOwnerQuestionReply({ scope, cohort, questionId, choice });
|
|
1292
|
+
return 'applied';
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
// The notice was genuinely issued under this source and key, but the
|
|
1296
|
+
// local ledger no longer holds that exact question: consume the
|
|
1297
|
+
// recognized choice instead of sending it to the Agent.
|
|
1298
|
+
return 'applied';
|
|
1299
|
+
}
|
|
1300
|
+
catch {
|
|
1301
|
+
return 'transient';
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1098
1304
|
/**
|
|
1099
1305
|
* Send-time answer for one owner-question notice. It stays sendable only
|
|
1100
1306
|
* while its question is still the cohort's latest, open, unanswered, inside
|
|
@@ -1171,6 +1377,9 @@ export class AssistantEvaluationService extends Service {
|
|
|
1171
1377
|
continue;
|
|
1172
1378
|
}
|
|
1173
1379
|
this.bindOwnerQuestionWithdrawal(delivery);
|
|
1380
|
+
// A replaced Delivery instance needs the reply consumer bound as well;
|
|
1381
|
+
// the tick is the existing retry point, so no extra loop is added.
|
|
1382
|
+
this.bindOwnerQuestionReply(delivery);
|
|
1174
1383
|
// Only queue (or adopt) while this service's own withdrawal check is
|
|
1175
1384
|
// bound to this exact Delivery instance; otherwise a closed question
|
|
1176
1385
|
// could be sent unchecked.
|
|
@@ -1232,7 +1441,7 @@ export class AssistantEvaluationService extends Service {
|
|
|
1232
1441
|
scope: { principalId: owner.principalId, ...principal, workspace: scope.workspace, preset: scope.preset },
|
|
1233
1442
|
sessionId,
|
|
1234
1443
|
idempotencyKey,
|
|
1235
|
-
text: attentionOwnerQuestionText(question, scope),
|
|
1444
|
+
text: attentionOwnerQuestionText(question, scope, { repliesRead: true }),
|
|
1236
1445
|
expiresAt,
|
|
1237
1446
|
attention: { purpose: 'result' },
|
|
1238
1447
|
withdrawable: true,
|
|
@@ -1255,8 +1464,10 @@ export class AssistantEvaluationService extends Service {
|
|
|
1255
1464
|
}
|
|
1256
1465
|
/**
|
|
1257
1466
|
* Host-internal record-only owner reply to the cohort's latest question.
|
|
1258
|
-
* It never records a grant or changes a tuning version
|
|
1259
|
-
*
|
|
1467
|
+
* It never records a grant or changes a tuning version, and it is not the
|
|
1468
|
+
* slice-5 apply path; an owner who authorizes uses
|
|
1469
|
+
* `applyAttentionOwnerQuestionReply` (the only path the Delivery reply
|
|
1470
|
+
* consumer calls) or records a host grant through `grantAttentionLoosening`.
|
|
1260
1471
|
*/
|
|
1261
1472
|
recordAttentionOwnerQuestionReply(input) {
|
|
1262
1473
|
this.assertActive();
|
|
@@ -1272,6 +1483,23 @@ export class AssistantEvaluationService extends Service {
|
|
|
1272
1483
|
|| this.now() >= latest.question.createdAt + this.config.attentionOwnerQuestionNoticeTtlMs;
|
|
1273
1484
|
return this.store.attentionQuestions.recordReply(input.scope, cohort, latest.questionId, input.choice, latest.open, expired);
|
|
1274
1485
|
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Slice 5 evidence-backed apply path for one exact owner reply; the
|
|
1488
|
+
* Delivery consumer and Host callers use this instead of the record-only
|
|
1489
|
+
* seam. `authorize-once` records the reply and creates one bounded one-time
|
|
1490
|
+
* grant for exactly the questioned proposal (parent/candidate bounds, the
|
|
1491
|
+
* proposal's step size, one adoption); the next tuning tick still re-runs
|
|
1492
|
+
* the independent holdout replay before adopting. `decline` closes the
|
|
1493
|
+
* proposal. A proposal that already had an immutable decision returns
|
|
1494
|
+
* `proposal-terminal` and nothing adopts.
|
|
1495
|
+
*/
|
|
1496
|
+
applyAttentionOwnerQuestionReply(input) {
|
|
1497
|
+
this.assertActive();
|
|
1498
|
+
const cohort = this.#attentionCohort(input.cohort);
|
|
1499
|
+
if (!this.config.attentionOwnerQuestionDeliveryEnabled)
|
|
1500
|
+
return Object.freeze({ status: 'rejected', reason: 'delivery-disabled' });
|
|
1501
|
+
return this.store.attention.applyOwnerQuestionReply(input.scope, cohort, input.questionId, input.choice, { noticeTtlMs: this.config.attentionOwnerQuestionNoticeTtlMs });
|
|
1502
|
+
}
|
|
1275
1503
|
/** Per-question delivery decisions and recorded replies for one cohort. */
|
|
1276
1504
|
attentionOwnerQuestionStatus(input) {
|
|
1277
1505
|
this.assertActive();
|
|
@@ -1303,6 +1531,14 @@ export class AssistantEvaluationService extends Service {
|
|
|
1303
1531
|
return this.active && typeof registration === 'object' && registration !== null
|
|
1304
1532
|
&& this.activeOwnerQuestionWithdrawals.has(registration);
|
|
1305
1533
|
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Delivery's slot-anchored proof that the slice-5 reply consumer under the
|
|
1536
|
+
* `assistantEvaluation` authority is this live service's own registration.
|
|
1537
|
+
*/
|
|
1538
|
+
ownsOwnerNotificationReply(registration) {
|
|
1539
|
+
return this.active && typeof registration === 'object' && registration !== null
|
|
1540
|
+
&& this.activeOwnerQuestionReplies.has(registration);
|
|
1541
|
+
}
|
|
1306
1542
|
/** Delivery uses the same ownership boundary as Automations. */
|
|
1307
1543
|
ownsTrustedDeliveryEvaluationRegistration(registration) {
|
|
1308
1544
|
return this.active && typeof registration === 'object' && registration !== null
|