@dsh-enhanced/assistant-evaluation 0.1.65 → 0.1.67
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 +69 -1
- package/lib/attention-owner-questions.d.ts +104 -0
- package/lib/attention-owner-questions.d.ts.map +1 -0
- package/lib/attention-owner-questions.js +179 -0
- package/lib/attention-owner-questions.js.map +1 -0
- package/lib/attention-tuning.d.ts +492 -0
- package/lib/attention-tuning.d.ts.map +1 -0
- package/lib/attention-tuning.js +1044 -0
- package/lib/attention-tuning.js.map +1 -0
- package/lib/benchmark/cli.d.ts.map +1 -1
- package/lib/benchmark/cli.js +14 -11
- package/lib/benchmark/cli.js.map +1 -1
- package/lib/benchmark/corpus.d.ts +13 -2
- package/lib/benchmark/corpus.d.ts.map +1 -1
- package/lib/benchmark/corpus.js +19 -9
- package/lib/benchmark/corpus.js.map +1 -1
- package/lib/benchmark/families-corpus.d.ts +11 -0
- package/lib/benchmark/families-corpus.d.ts.map +1 -0
- package/lib/benchmark/families-corpus.js +133 -0
- package/lib/benchmark/families-corpus.js.map +1 -0
- package/lib/benchmark/holdout-isolation.d.ts +27 -0
- package/lib/benchmark/holdout-isolation.d.ts.map +1 -0
- package/lib/benchmark/holdout-isolation.js +66 -0
- package/lib/benchmark/holdout-isolation.js.map +1 -0
- package/lib/benchmark/holdout.d.ts.map +1 -1
- package/lib/benchmark/holdout.js +3 -0
- package/lib/benchmark/holdout.js.map +1 -1
- package/lib/benchmark/index.d.ts +3 -0
- package/lib/benchmark/index.d.ts.map +1 -1
- package/lib/benchmark/index.js +2 -0
- package/lib/benchmark/index.js.map +1 -1
- package/lib/benchmark/native-skills.d.ts +12 -14
- package/lib/benchmark/native-skills.d.ts.map +1 -1
- package/lib/benchmark/native-skills.js +1 -3
- package/lib/benchmark/native-skills.js.map +1 -1
- package/lib/benchmark/native.d.ts +15 -2
- package/lib/benchmark/native.d.ts.map +1 -1
- package/lib/benchmark/native.js +67 -14
- package/lib/benchmark/native.js.map +1 -1
- package/lib/benchmark/report.d.ts.map +1 -1
- package/lib/benchmark/report.js +98 -8
- package/lib/benchmark/report.js.map +1 -1
- package/lib/benchmark/runner.d.ts.map +1 -1
- package/lib/benchmark/runner.js +5 -0
- package/lib/benchmark/runner.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/types.d.ts +64 -7
- package/lib/benchmark/types.d.ts.map +1 -1
- package/lib/index.d.ts +8 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -0
- package/lib/index.js.map +1 -1
- package/lib/intake-attention-agreement.d.ts +63 -0
- package/lib/intake-attention-agreement.d.ts.map +1 -0
- package/lib/intake-attention-agreement.js +146 -0
- package/lib/intake-attention-agreement.js.map +1 -0
- package/lib/notification-outcomes.d.ts +56 -0
- package/lib/notification-outcomes.d.ts.map +1 -0
- package/lib/notification-outcomes.js +348 -0
- package/lib/notification-outcomes.js.map +1 -0
- package/lib/service.d.ts +295 -2
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +965 -5
- package/lib/service.js.map +1 -1
- package/lib/sqlite.d.ts +8 -2
- package/lib/sqlite.d.ts.map +1 -1
- package/lib/sqlite.js +406 -17
- package/lib/sqlite.js.map +1 -1
- package/lib/store.d.ts +13 -2
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +122 -24
- package/lib/store.js.map +1 -1
- package/lib/types.d.ts +247 -5
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +11 -0
- package/lib/types.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/work-item-owner-proof.d.ts +4 -0
- package/lib/work-item-owner-proof.d.ts.map +1 -0
- package/lib/work-item-owner-proof.js +53 -0
- package/lib/work-item-owner-proof.js.map +1 -0
- package/package.json +29 -38
- 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/README.md
CHANGED
|
@@ -40,6 +40,10 @@ ctx.assistantEvaluation.limits()
|
|
|
40
40
|
|
|
41
41
|
`append()` 只接受 `external` 或 `self-reported` 等低信任输入;传入 `trusted` 会直接失败。Automations 的 production terminal receipt 与 Delivery 的 exact owner feedback 通过各自 durable outbox 和 Evaluation 私有 producer registration 写入,preview、复制 registration、过期 generation 或公开调用都不能进入可信 lane。相同 `idempotencyKey` 的完全相同输入安全重放;内容变化会以 `idempotency-conflict` 失败关闭。
|
|
42
42
|
|
|
43
|
+
当前有四个 trusted producer:`assistant-automations`、`assistant-delivery`、`assistant-verifier` 与 `assistant-work-items`。`assistant-work-items` 把已结算的 work-item outcome(`observed` / `unverified` / `not-applicable`)通过同一私有 registration seam 投影为 trusted 证据;它的 `objectiveStatus`/`deliveryStatus` 恒为 `unknown`,`executionStatus` 由 evidence state 派生,因此 work-item 结算永远不能升级自身的目标判断(也没有 automation-run ref,`queryTasks()` 会把它保留为独立 outcome)。
|
|
44
|
+
|
|
45
|
+
Evaluation 保持 producer-agnostic:registration/claims 类型在 `src/types.ts` 本地定义,不 import 任何 producer 运行时;producer 侧只以 type-only import + optional peer 引用这些契约类型。新增 producer 只需要在自己的 package 里实现 generation + sink 注册。
|
|
46
|
+
|
|
43
47
|
`getTrustedOutcome()` 是供本地固定 Host runbook 做跨账本投影的精确读取 seam。它要求由
|
|
44
48
|
`canonicalEvaluationHostScope()` 产生的冻结、不可序列化 scope token;缺失、跨 scope 或非
|
|
45
49
|
`trusted` 的记录一律不返回。凭证只包含状态、来源、证据引用和 evaluator,不暴露 metrics、
|
|
@@ -73,6 +77,25 @@ projection 保存选中的 execution/objective/delivery component id,原始两
|
|
|
73
77
|
| `maxEvidenceRefs` | `32` | 每条 outcome 的证据引用数上限。 |
|
|
74
78
|
| `defaultSummaryWindowMs` | 30 天 | 未指定起点时的默认汇总窗口。 |
|
|
75
79
|
| `maxSummaryWindowMs` | 365 天 | 单次汇总允许的最大窗口。 |
|
|
80
|
+
| `notificationOutcomesEnabled` | `false` | WP22:在既有 projection tick 上同步 Delivery 通知结果事实。 |
|
|
81
|
+
| `notificationResponseWindowMs` | 1 天 | 已发出且无反应的通知超过此窗口记为 `expired-unhandled`。 |
|
|
82
|
+
| `notificationObservationHorizonMs` | 30 天 | 已知通知自通知时间起在此期间内被重查,以吸收迟到回复、更正与撤回;有效值至少为响应窗口的两倍。超出观察期的通知不再被扫描(按 `occurred_at` 范围索引)。 |
|
|
83
|
+
| `notificationSyncBatchSize` | `100` | 每次同步新摄入与重查的条数上限(最大 500)。 |
|
|
84
|
+
| `notificationSyncIntervalMs` | 60 秒 | 定时器驱动的通知同步最小间隔(projection tick 更频繁,同步按此节流;`0` 表示每个 tick)。 |
|
|
85
|
+
| `intakeAttentionAgreementEnabled` | `false` | WP22:开放只读 `inspectIntakeAttentionAgreement`(WP20 intake 判断与通知结果的吻合度);要求 `notificationOutcomesEnabled`,不改变任何发送行为。 |
|
|
86
|
+
| `attentionTuningAutoAdopt` | `false` | WP22 切片 2:按 cohort 有界自动采用收紧类注意力参数并在回归时回退;要求 `notificationOutcomesEnabled`。 |
|
|
87
|
+
| `attentionTuningCohorts` | `[{ lane: owner-notification, sourceId: assistant-proactive/v1 }]` | 可调 cohort(`lane` + `sourceId`),最多 16 个。 |
|
|
88
|
+
| `attentionTuningMinSample` | `20` | 提议与回归判断所需的最少已判定通知数(最小 5)。 |
|
|
89
|
+
| `attentionTuningNegativeRatePpm` | `600000` | 负向率(rejected + expired-unhandled)达到此值时收紧一步。 |
|
|
90
|
+
| `attentionTuningMuteRatePpm` | `900000` | 其余参数已到上界后,负向率还须达到此值才停用该 cohort;须 ≥ 收紧阈值。 |
|
|
91
|
+
| `attentionTuningRegressionMarginPpm` | `100000` | 观察期负向率高于采用依据负向率加此余量时回退。 |
|
|
92
|
+
| `attentionTuningObservationMs` | 7 天 | 采用后的观察期,持久化,重启后继续。 |
|
|
93
|
+
| `attentionTuningEvaluationWindowMs` | 7 天 | 提议所用证据的回看窗口(且不早于上一次版本变化)。 |
|
|
94
|
+
| `attentionLooseningEnabled` | `false` | WP22 切片 3:放宽请求的密封留出重放与 owner 一次性授权;要求 `attentionTuningAutoAdopt`。 |
|
|
95
|
+
| `attentionLooseningReplayWindowMs` | 30 天 | 放宽提议冻结的留出重放回看窗口。 |
|
|
96
|
+
| `attentionOwnerQuestionDeliveryEnabled` | `false` | WP22 切片 4/5:最新 owner 问题经 Delivery 至多投递一次,并注册回复消费者把 exact 回复应用到授权/提议;要求 `attentionLooseningEnabled` 与 `attentionOwnerQuestionOwner`。 |
|
|
97
|
+
| `attentionOwnerQuestionOwner` | 无 | 问题投递使用的 owner 路由 id 与外部 principal id。 |
|
|
98
|
+
| `attentionOwnerQuestionNoticeTtlMs` | 1 天 | 未投递问题的过期时间、通知有效期,也是授权回复创建的一次性授权有效期。 |
|
|
76
99
|
|
|
77
100
|
## 权限与数据边界
|
|
78
101
|
|
|
@@ -85,6 +108,48 @@ projection 保存选中的 execution/objective/delivery component id,原始两
|
|
|
85
108
|
|
|
86
109
|
scope、situation、producer/evaluator id、证据引用和指标属于本地评测元数据,可能仍有隐私含义;调用方不应把原始 prompt、secret、消息正文或绝对产物路径放入这些字段。
|
|
87
110
|
|
|
111
|
+
## 工作项报告的主人结果反馈
|
|
112
|
+
|
|
113
|
+
WorkItems 的普通结算仍只能记录 `objectiveStatus: unknown`。显式启用其独立报告反馈入口后,Delivery 对真实报告回复建立当前 owner 来源,Evaluation 再通过当前 WorkItems 代次的 opaque capability 独立读取精确报告、冻结供应和已静止且预算结算的真实 production 证明。`ready`、退出码或 provider accepted 都不自动形成目标成功;`achieved` / `not-achieved` 是主人对这条报告的判断。
|
|
114
|
+
|
|
115
|
+
该路径使用 `work-item` / 原 `itemId` 作为唯一 canonical 主体;报告 revision、digest 和原生 run 是证据,不增加学习票数,也不沿续办 stream 改评后来的子项。执行状态来自独立 proof,按持久 report revision 选择;同 revision 的矛盾 proof 保持不可学习。主人判断走既有 initial / correct / withdraw CAS 和持久 operation 幂等链。`getTrustedWorkItemLearningProjection({ scope, itemId })` 返回当前有版本与 digest 的投影,撤回返回 `retract`;`getTrustedTaskLearningProjection`、owner revision reader 和 writer fence 同样支持这个主体。模型自评不能创建该可信结果。新的结果写入还要求当前 Delivery 提供源 writer fence:最后一次 WorkItems 读取后重核当前反馈授权和冻结来源,在同一源锁内写入 owner 判断并结算来源。通知在源事务提交后发送;跨库失去回执时保留原操作身份恢复,不声称可原子回滚两个数据库。
|
|
116
|
+
|
|
117
|
+
schema 13 扩展任务投影的主体身份约束及投影 outbox 的状态约束,保留既有 outcome、owner 命令、projection、投影 outbox、scope watermark 和 feed head 的值。旧 `work-item` settlement 仍是独立 outcome audit,不追认或合并为新结果。Growth / Memory 消费者可以继续拒绝尚未支持的新主体;这一来源不自行触发模型、采用插件或授予执行预算。当前 Evolution 不支持该新主体,因此其投递 outbox 诚实保存为 `not-required`,不会伪造 applied/recorded、指数重试或消耗恢复预算;canonical feed、来源 reader 和 canonical writer fence 仍可使用。要求已经投递 Evolution 的旧 learning writer fence 则明确拒绝 `work-item` 证据。
|
|
118
|
+
|
|
119
|
+
## 通知结果账本(WP22 切片 1,只读)
|
|
120
|
+
|
|
121
|
+
schema 14 新增与任务投影分离的 `evaluation_notification_outcome_*` 表:以 Delivery Outbox id 为通知主键,每次分类变化追加不可改、不可删的 revision 并推进该 scope 独立的通知 watermark;digest 不变只刷新检查时间。分类器 `notification-outcome` v1 确定性、无模型调用,优先级:owner/工作项判断 `not-achieved`→`rejected`、`achieved`→`accepted`;回复或 `partial`→`reacted`;传输 `dead`→`failed`;已发出且超过响应窗口→`expired-unhandled`(含 `expiry: { at, responseWindowMs }`),否则 `delivered`;未发出→`pending`。撤回(`unknown`)不计入判断。响应窗口只写入 `expired-unhandled` 记录,改配置只对到期判定实际改变的通知形成新 revision。格式错误或 scope 与已记录 head 冲突的事实计入 `invalid` 并跳过,不阻塞游标。head 必须与其 revision 的 digest、结果、watermark、scope 与通知时间一致,通知 watermark 与同步游标只进不退、不可删(触发器保证)。回复条数与任何正文不入库。
|
|
122
|
+
|
|
123
|
+
启用后每个 projection tick 先从持久游标摄入新审计行,再按最久未查顺序重查观察期内的已知通知;格式错误的事实计入 `invalid` 并跳过,不阻塞游标。Delivery 缺席时为零结果。读接口:`inspectNotificationOutcome({ scope, notificationId })`(当前 + 全部 revision)、`notificationOutcomeFeed({ scope, after?, limit? })`(按 watermark 可续读的当前 head)、`notificationOutcomeSummary({ scope, fromNotifiedAt?, toNotifiedAt? })`(按 outcome 与 attention verdict 计数)。本切片不写 Evolution、不改任何参数、不触发 Growth。
|
|
124
|
+
|
|
125
|
+
## intake 与注意力结果吻合度(WP22,只读)
|
|
126
|
+
|
|
127
|
+
`intakeAttentionAgreementEnabled`(默认关,须同时开 `notificationOutcomesEnabled`)开放 Host-only `inspectIntakeAttentionAgreement({ scope, fromNotifiedAt?, toNotifiedAt? })`;关闭时返回 `undefined`、不读 WorkItems。唯一可用的连接是:WP20 intake 准入 → 工作项 → 结果通知(结果账本记录中的 `workItemId`)→ 主人结果。它在窗口内按通知时间扫描当前结果头中带 `workItemId` 的记录(至多 1000 条,超出标 `truncated`),每个工作项经 WorkItems 结构化接口 `inspectIntakeProvenance(itemId)` 只读一次,按准入时的 intake level 聚合:`agreed` = accepted + reacted,`disagreed` = rejected + expired-unhandled,`agreementPpm` = agreed / (agreed + disagreed),无已判定通知时为 `null`;delivered/pending/failed 只计入 `outcomes`。非 intake 准入的工作项(显式决策、续办)计 `notIntake`;接口抛错(含 WorkItems intake ledger 关闭)、回执畸形或无 WorkItems peer(`provenanceAvailable: false`)计 `unresolved`;通知发送方可声明任意 `workItemId`,因此 provenance 所带工作项 owner scope 与本次 scope 不同的计 `foreign`,二者都从不当作吻合。
|
|
128
|
+
|
|
129
|
+
限制:observe 模式的 `would-admit` 不创建工作项,因而不产生通知,无法在此度量,只有 auto 准入(含崩溃恢复的 `replayed`)的项可测;未被 intake 接受(`wouldAccept: false`)的事件同样没有通知,不能估计漏判。另有一条可行但不采用的连接:owner 对通知的回复本身是一条入站消息,也会被 intake 判断,可经 Delivery 回复关系连回该通知;但正是这条回复使通知变成 `reacted`,度量会自证循环,故不采用。口径按通知计(一个项的报告越多权重越大),`rejected` 来自工作项反馈的 not-achieved、反映执行结果而不只是 intake 判断,因此该值是"执行 + 注意力"的综合信号,不是 intake 准确率。结果是观察性的(`causal: false`),不写任何行、不采用参数、不发送;按 `record_json` 扫描(无 workItemId 索引,无 schema 变更)。
|
|
130
|
+
|
|
131
|
+
## 注意力参数有界收紧与回退(WP22 切片 2)
|
|
132
|
+
|
|
133
|
+
schema 15 新增 `evaluation_attention_tuning_versions`(不可改、不可删,按 scope + cohort 递增版本,记录精确父版本与父 digest、参数、依据统计与 scope watermark、变化项、来源 `auto`/`request`、sha256 digest)、`_heads`(当前生效版本、`idle`/`observing` 状态与持久观察窗口)和 `_events`(`adopted`/`rolled-back`/`settled`/`request-rejected` 只追加审计)。cohort 是 Evaluation scope 内的 Delivery `lane` + `sourceId`;只统计通知结果账本当前 head。
|
|
134
|
+
|
|
135
|
+
Delivery 事实可带 `route: { kind: 'linked', originBindingId }`(经 owner linked 别名跨渠道兜底送达的工作项报告)。归一化严格校验:只接受恰好这两个键、`kind` 为 `linked`、`originBindingId` 为非空规范字符串且不等于本通知 `bindingId`,否则计入 `invalid`;记录原样保留该字段,缺席即 origin(不写入),因此无 route 记录的 digest 与改动前一致,分类结果不受 route 影响,分类器仍为 v1。linked 记录照常进入 revision、feed 与 summary,但调优的统计、提议器、留出密封与重放、owner 结构化问题一律按 `coalesce(json_extract(record_json,'$.route.kind'),'origin')='origin'` 排除,别名信道上的反应不污染 origin cohort。依据中的 `scopeWatermark` 仍是 scope 级账本位置(含 linked),只作来源锚点。升级前写入、且已超出事实刷新窗口(`max(horizon, 2*window)`)的 linked 记录没有 route 字段,仍按 origin 统计;升级前封存、时间窗含 linked 记录的放宽提案重放时 holdout digest 不再匹配,按 `holdout-drifted` 拒绝,需重新累积样本。
|
|
136
|
+
|
|
137
|
+
参数与固定界限:`interruptionCostDelta` 0–100 步长 10、`minimumUtilityDelta` 0–100 步长 10、`quietExtensionMinutes` 0–120 步长 30(在既有静默时段两侧各延长,总长不超过 1439 分钟)、`muted`。默认全零,即不改变消费者行为。纯函数提议器确定性、只收紧:已判定数不足 `attentionTuningMinSample` 为 no-op;负向率达到阈值时按固定顺序(打扰成本→阈值→静默→停用)对第一个未到上界的参数前进一步,`muted` 另需停用阈值。
|
|
138
|
+
|
|
139
|
+
`reconcileAttentionTuning()` 在通知同步之后运行(同一节流与错误边界),每个 cohort 在一个 `BEGIN IMMEDIATE` 事务内:空闲时提议并采用一步(幂等键绑定父版本、父 digest、scope watermark 与变化),进入观察;观察中若当前版本 digest 校验失败,或观察期内样本足够且负向率高于依据率加余量,则回退到精确父版本(含版本 0 默认值);正向或持平证据从不触发回退,观察期满记为 `settled`。被回退版本的证据不复用,须重新累积样本。`currentAttentionTuning({ scope, cohort })` 返回经 digest 校验的当前版本,校验失败时沿父链回落到最近可校验版本或默认值;开关关闭时返回 `undefined`。
|
|
140
|
+
|
|
141
|
+
`requestAttentionTuning({ scope, cohort, parameters })` 是 Host 内部入口(不注册工具):任何放宽(含与收紧混合)一律拒绝为 `loosening-requires-holdout` 并记录事件;越界、无变化、多于一步、开关关闭或观察中同样被拒绝并记录。合规的单步收紧以 `request` 来源采用并进入同样的观察与回退。`attentionTuningStatus({ scope, cohort })` 只读返回当前版本、全部版本和事件。本切片不实现放宽、留出重放或 owner 结构化问题,不写 Evolution,不触发 Growth。
|
|
142
|
+
|
|
143
|
+
## owner 问题回复闭环(WP22 切片 5,opt-in)
|
|
144
|
+
|
|
145
|
+
`attentionOwnerQuestionDeliveryEnabled`(默认关,须同时开 `attentionLooseningEnabled` 并配置 `attentionOwnerQuestionOwner`)同时启用两件事:既有 tick 仍按切片 4 至多投递一次最新问题,并额外向当前 Delivery 注册 `assistantEvaluation` 槽位自证的回复消费者(协议 `assistant-delivery/owner-notification-reply/v1`,来源固定、幂等键前缀 `attention-owner-question:`)。Delivery 只把引用唯一 `accepted`/`delivered`/`read` 通知、来自当前 owner binding/principal、通过外部 `answer-attention-question` Policy 的认证文本回复交给本服务;语音、模型理解或相似措辞都不参与。关闭开关时不注册消费者,问题正文保持切片 4 的“不读取回复”文案,零行为变化。
|
|
146
|
+
|
|
147
|
+
消费者按通知正文的 `questionId` 逐个 cohort/scope 只匹配该 cohort 的**最新可校验问题**,只接受整句(去空白、ASCII 大小写不敏感)的 `authorize-once` / `decline`:`authorize-once`、`authorize once`、`allow once`、`同意一次`、`授权一次`、`批准一次`;`decline`、`拒绝`、`不同意`、`不用了`。其他文本返回 `ignored`,仍按普通消息交给 Agent;可识别但问题已关闭/过期时回复被消费(Delivery 标记 processed),不会作为授权文字进入 Agent。`applyAttentionOwnerQuestionReply` 是唯一有副作用的 Host 入口:它在一个 `BEGIN IMMEDIATE` 内重验最新问题正文 digest,记录回复,然后对 `decline` 写入该提议唯一的 `owner-declined` 终态,对 `authorize-once` 创建幂等于 `owner-question:<questionId>` 的一次性授权(下界恰为被质疑候选参数、上界恰为其父参数、`maxAdoptions: 1`、步幅等于提议步数、到期为回复时刻加通知 TTL)。`recordAttentionOwnerQuestionReply` 仍是公开的只记录入口,永不创建授权或改变版本。
|
|
148
|
+
|
|
149
|
+
开启闭环时,超出授权范围的问题不再写切片 3/4 的终态 `owner-question` 决定:提议保持未决(deferred),问题开放期间阻止新的放宽提议,但下一次既有同步 tick 仍会先独立重算留出成员、重放父/候选、核对父版本与授权,只有全部通过才采用并写入唯一 `adopted` 决定;因此 owner 回复本身不等于采用,仍受独立验收。`decline`、通知 TTL 到期或授权不再可用分别以 `owner-declined`、`owner-question-expired`、`owner-authorization-unusable` 关闭提议;没有新的定时器或循环,全部复用既有同步 tick。TTL 之前回复到达但下一次 tick 已过问题 TTL 时,以回复时刻创建的一次性授权仍可在其有效期内采用(到期字段独立于问题时间戳)。
|
|
150
|
+
|
|
151
|
+
限制:切片 4 之前已记录的实施问题已有不可改的终态决定,开启闭环后对其 `authorize-once` 回复只返回 `applied + adoption: 'proposal-terminal'`,记录回复但不创建授权、不采用;重新请求可产生新的提议。一次性授权绑定精确的父/候选参数(而非提议 id),若该提议因留出漂移被拒,未使用的授权在到期前仍可能覆盖一个父/候选完全相同的新提议——这与 owner 批准的那次变化一致,但不等同于对任意后续提议的授权。回复正文只在 Delivery 回调期间传给本服务,不入库;发给模型的普通回复路径、发送权限、Evolution/Growth 均不受影响。
|
|
152
|
+
|
|
88
153
|
## 限制
|
|
89
154
|
|
|
90
155
|
- 账本保存“谁以什么证据做了什么判断”,并不把模型自评提升为 ground truth。
|
|
@@ -106,7 +171,9 @@ scope、situation、producer/evaluator id、证据引用和指标属于本地评
|
|
|
106
171
|
|
|
107
172
|
报告保留所有计划 cell 作为已验证成功率分母,unknown 和缺测单列,缺失费用/token/返工/人工介入量不会补成零。提供配对胜负、差值和均值/中位数/P95;区间按任务聚类 bootstrap,同题重复不当作独立任务。任一比较臂有缺测或 unknown 就不报告收益差值与区间,不能把基线的未知结果当作失败来制造增益;单臂有 unknown 也不报告其成功率区间。少于两个任务不提供区间。小样本或同质任务仍不足以证明泛化收益,报告始终 `promotionAuthorized: false`。
|
|
108
173
|
|
|
109
|
-
**当前完成范围:** 冻结协议、持久账本、有界协调器、统计计算、原生 AgentLoop 执行器及 `dsh-benchmark` 命令。默认 `research-v1` 保留 8 道公开研究/注入开发题;`memory-v2` 增加 6 道公开记忆开发题。`strategy-v1` 增加 4 道公开合成 shell
|
|
174
|
+
**当前完成范围:** 冻结协议、持久账本、有界协调器、统计计算、原生 AgentLoop 执行器及 `dsh-benchmark` 命令。默认 `research-v1` 保留 8 道公开研究/注入开发题;`memory-v2` 增加 6 道公开记忆开发题。`strategy-v1` 增加 4 道公开合成 shell 开发题:整数汇总、接触区间合并、按频次/字典序词频和依赖拓扑排序。`families-v1` 增加 9 道公开合成题,覆盖代码、研究、跨日、主动性、注入、撤销、目标变化与重复操作 8 个任务族(提示层代理,纯函数判定)。它们是开发语料,不是 holdout,也没有真实模型收益证据。
|
|
175
|
+
|
|
176
|
+
原生变体可设 `review: true` 追加冻结复核指引,并用 `role: "ablation"`(persona 与唯一 candidate 相同、只关闭一个开关)做消融;报告以 candidate 为消融参照臂。报告(`distributionVersion: 1`,加法字段)另含 min/p10/p90/max、分族结果、每题跨重复成功率分布与配对指标差。`assertHoldoutIsolation()` 检查留出计划与公开开发清单不重叠,由所有执行入口共用的 `runBenchmark()` 对任何 holdout 计划强制执行(`runIndependentHoldout()` 还会在打开 authority 前提前检查);`assertCandidateGenerationInput()` 拒绝把非 development 计划用于候选生成,但本包内尚无候选生成代码调用它,需由将来的生成入口接入。二者均为清单级检查。
|
|
110
177
|
|
|
111
178
|
### 独立留出 provider Host SDK(实验性)
|
|
112
179
|
|
|
@@ -146,6 +213,7 @@ dsh-benchmark doctor
|
|
|
146
213
|
dsh-benchmark corpus
|
|
147
214
|
dsh-benchmark doctor --suite memory-v2
|
|
148
215
|
dsh-benchmark corpus --suite memory-v2
|
|
216
|
+
dsh-benchmark corpus --suite families-v1
|
|
149
217
|
dsh-benchmark corpus --suite strategy-v1
|
|
150
218
|
dsh-benchmark doctor --suite strategy-v1 --config ./docs/examples/strategy-benchmark.config.json
|
|
151
219
|
dsh-benchmark plan --config ./docs/examples/strategy-benchmark.config.json --output ./private/strategy-plan.json
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { type AttentionCohort, type AttentionOwnerQuestion } from './attention-tuning.js';
|
|
3
|
+
import type { EvaluationScope } from './types.js';
|
|
4
|
+
/** Delivery `sourceId` of owner-question notices; a distinct cohort, so they never feed attention tuning. */
|
|
5
|
+
export declare const ATTENTION_OWNER_QUESTION_SOURCE_ID: "assistant-evaluation/attention-owner-notice/v1";
|
|
6
|
+
/**
|
|
7
|
+
* Slice 5 reply-consumer protocol: Delivery hands a genuine reply to one of
|
|
8
|
+
* its accepted/delivered/read owner notices to the live service in the
|
|
9
|
+
* registration's authority slot, which must vouch for the exact registration
|
|
10
|
+
* object through `ownsOwnerNotificationReply`.
|
|
11
|
+
*/
|
|
12
|
+
export declare const ATTENTION_OWNER_QUESTION_REPLY_PROTOCOL: "assistant-delivery/owner-notification-reply/v1";
|
|
13
|
+
export declare const attentionOwnerQuestionChoices: readonly ["authorize-once", "decline"];
|
|
14
|
+
export type AttentionOwnerQuestionChoice = typeof attentionOwnerQuestionChoices[number];
|
|
15
|
+
export declare function parseAttentionOwnerQuestionChoice(input: unknown): AttentionOwnerQuestionChoice | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Terminal per-question delivery state, written at most once:
|
|
18
|
+
* `queued` (handed to Delivery, carries the Outbox id), `closed` (a newer
|
|
19
|
+
* grant or a recorded reply closed it first; never sent) or `expired` (the
|
|
20
|
+
* notice TTL elapsed before it could be queued; never sent).
|
|
21
|
+
*/
|
|
22
|
+
export type AttentionOwnerQuestionDeliveryStatus = 'queued' | 'closed' | 'expired';
|
|
23
|
+
export interface AttentionOwnerQuestionDelivery {
|
|
24
|
+
readonly questionId: string;
|
|
25
|
+
readonly status: AttentionOwnerQuestionDeliveryStatus;
|
|
26
|
+
readonly outboxId: string | null;
|
|
27
|
+
readonly idempotencyKey: string;
|
|
28
|
+
readonly recordedAt: number;
|
|
29
|
+
}
|
|
30
|
+
export interface AttentionOwnerQuestionReply {
|
|
31
|
+
readonly questionId: string;
|
|
32
|
+
readonly choice: AttentionOwnerQuestionChoice;
|
|
33
|
+
readonly repliedAt: number;
|
|
34
|
+
}
|
|
35
|
+
export type AttentionOwnerQuestionReplyResult = Readonly<{
|
|
36
|
+
status: 'recorded';
|
|
37
|
+
questionId: string;
|
|
38
|
+
choice: AttentionOwnerQuestionChoice;
|
|
39
|
+
repliedAt: number;
|
|
40
|
+
}> | Readonly<{
|
|
41
|
+
status: 'rejected';
|
|
42
|
+
reason: 'delivery-disabled' | 'unknown-question' | 'question-closed' | 'question-expired' | 'reply-conflict';
|
|
43
|
+
}>;
|
|
44
|
+
export interface AttentionOwnerQuestionStatus {
|
|
45
|
+
readonly enabled: boolean;
|
|
46
|
+
readonly deliveries: readonly AttentionOwnerQuestionDelivery[];
|
|
47
|
+
readonly replies: readonly AttentionOwnerQuestionReply[];
|
|
48
|
+
}
|
|
49
|
+
/** One tick outcome per scope/cohort whose latest question still needed a delivery decision. */
|
|
50
|
+
export type AttentionOwnerQuestionTickAction = Readonly<{
|
|
51
|
+
action: 'queued';
|
|
52
|
+
questionId: string;
|
|
53
|
+
outboxId: string;
|
|
54
|
+
}> | Readonly<{
|
|
55
|
+
action: 'closed' | 'expired';
|
|
56
|
+
questionId: string;
|
|
57
|
+
}> | Readonly<{
|
|
58
|
+
action: 'deferred';
|
|
59
|
+
questionId: string;
|
|
60
|
+
reason: 'delivery-unavailable' | 'route-mismatch' | 'enqueue-failed';
|
|
61
|
+
}> | Readonly<{
|
|
62
|
+
action: 'unverifiable';
|
|
63
|
+
}>;
|
|
64
|
+
export declare function attentionOwnerQuestionIdempotencyKey(questionId: string): string;
|
|
65
|
+
/**
|
|
66
|
+
* Deterministic plain-text notice for one owner question. It names the
|
|
67
|
+
* human-readable parameter, never an internal id, digest or estimate term,
|
|
68
|
+
* and stays well inside Delivery's `result` caps. `repliesRead` is the
|
|
69
|
+
* slice-5 opt-in: only then does the notice invite an exact reply, because
|
|
70
|
+
* only then does Delivery route a reply back to this service.
|
|
71
|
+
*/
|
|
72
|
+
export declare function attentionOwnerQuestionText(question: AttentionOwnerQuestion, scope: EvaluationScope, options?: Readonly<{
|
|
73
|
+
repliesRead?: boolean;
|
|
74
|
+
}>): string;
|
|
75
|
+
/**
|
|
76
|
+
* WP22 slice 4 delivery and reply ledger for owner questions. Both tables are
|
|
77
|
+
* append-only with one row per question, so a question is handed to Delivery
|
|
78
|
+
* at most once and a reply is recorded at most once. Nothing here creates a
|
|
79
|
+
* grant or changes a tuning version.
|
|
80
|
+
*/
|
|
81
|
+
export declare class AttentionOwnerQuestionLedger {
|
|
82
|
+
#private;
|
|
83
|
+
constructor(database: DatabaseSync, now: () => number);
|
|
84
|
+
delivery(questionId: string): AttentionOwnerQuestionDelivery | undefined;
|
|
85
|
+
reply(questionId: string): AttentionOwnerQuestionReply | undefined;
|
|
86
|
+
/** Write the single terminal delivery row; an existing row wins and is returned unchanged. */
|
|
87
|
+
recordDelivery(scopeInput: EvaluationScope, cohortInput: AttentionCohort, questionId: string, status: AttentionOwnerQuestionDeliveryStatus, outboxId: string | null): AttentionOwnerQuestionDelivery;
|
|
88
|
+
/**
|
|
89
|
+
* Record-only owner reply. The caller has already established that the
|
|
90
|
+
* question is the cohort's latest and whether it is open and expired; a new
|
|
91
|
+
* reply needs open and not expired. Replaying the same choice is idempotent
|
|
92
|
+
* and a different choice is a conflict.
|
|
93
|
+
*/
|
|
94
|
+
recordReply(scopeInput: EvaluationScope, cohortInput: AttentionCohort, questionId: string, choiceInput: unknown, open: boolean, expired: boolean): AttentionOwnerQuestionReplyResult;
|
|
95
|
+
/**
|
|
96
|
+
* Same contract as `recordReply`, but the caller owns the surrounding
|
|
97
|
+
* `BEGIN IMMEDIATE`. Slice 5 uses it inside the tuning ledger's transaction
|
|
98
|
+
* so a recorded owner reply and the grant or proposal decision it applies to
|
|
99
|
+
* commit atomically. Never call it outside a transaction.
|
|
100
|
+
*/
|
|
101
|
+
recordReplyInTransaction(scopeInput: EvaluationScope, cohortInput: AttentionCohort, questionId: string, choiceInput: unknown, open: boolean, expired: boolean): AttentionOwnerQuestionReplyResult;
|
|
102
|
+
status(scopeInput: EvaluationScope, cohortInput: AttentionCohort, enabled: boolean, limit?: number): AttentionOwnerQuestionStatus;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=attention-owner-questions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attention-owner-questions.d.ts","sourceRoot":"","sources":["../src/attention-owner-questions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { canonicalEvaluationScope, EvaluationStoreError } from './store.js';
|
|
2
|
+
import { normalizeAttentionCohort, } from './attention-tuning.js';
|
|
3
|
+
/** Delivery `sourceId` of owner-question notices; a distinct cohort, so they never feed attention tuning. */
|
|
4
|
+
export const ATTENTION_OWNER_QUESTION_SOURCE_ID = 'assistant-evaluation/attention-owner-notice/v1';
|
|
5
|
+
/**
|
|
6
|
+
* Slice 5 reply-consumer protocol: Delivery hands a genuine reply to one of
|
|
7
|
+
* its accepted/delivered/read owner notices to the live service in the
|
|
8
|
+
* registration's authority slot, which must vouch for the exact registration
|
|
9
|
+
* object through `ownsOwnerNotificationReply`.
|
|
10
|
+
*/
|
|
11
|
+
export const ATTENTION_OWNER_QUESTION_REPLY_PROTOCOL = 'assistant-delivery/owner-notification-reply/v1';
|
|
12
|
+
export const attentionOwnerQuestionChoices = ['authorize-once', 'decline'];
|
|
13
|
+
/**
|
|
14
|
+
* Exact owner reply phrases. Only the whole trimmed reply is compared, case
|
|
15
|
+
* insensitively; nothing here is a model interpretation. A reply must already
|
|
16
|
+
* target the one delivered notice, so these short phrases are unambiguous.
|
|
17
|
+
*/
|
|
18
|
+
const attentionOwnerQuestionAuthorizeReplies = [
|
|
19
|
+
'authorize-once', 'authorize once', 'allow once', '同意一次', '授权一次', '批准一次',
|
|
20
|
+
];
|
|
21
|
+
const attentionOwnerQuestionDeclineReplies = ['decline', '拒绝', '不同意', '不用了'];
|
|
22
|
+
export function parseAttentionOwnerQuestionChoice(input) {
|
|
23
|
+
if (typeof input !== 'string')
|
|
24
|
+
return undefined;
|
|
25
|
+
const text = input.trim().toLowerCase();
|
|
26
|
+
if (attentionOwnerQuestionAuthorizeReplies.includes(text))
|
|
27
|
+
return 'authorize-once';
|
|
28
|
+
if (attentionOwnerQuestionDeclineReplies.includes(text))
|
|
29
|
+
return 'decline';
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
export function attentionOwnerQuestionIdempotencyKey(questionId) {
|
|
33
|
+
return `attention-owner-question:${questionId}`;
|
|
34
|
+
}
|
|
35
|
+
const parameterLabels = Object.freeze({
|
|
36
|
+
interruptionCostDelta: 'interruption cost offset',
|
|
37
|
+
minimumUtilityDelta: 'notice threshold offset',
|
|
38
|
+
quietExtensionMinutes: 'quiet-hours extension (minutes)',
|
|
39
|
+
muted: 'mute',
|
|
40
|
+
});
|
|
41
|
+
const exceededLabels = Object.freeze({
|
|
42
|
+
'no-grant': 'no owner grant is active',
|
|
43
|
+
'grant-unverifiable': 'the stored grant could not be verified',
|
|
44
|
+
'grant-expired': 'the grant has expired',
|
|
45
|
+
'grant-exhausted': 'the grant has no adoptions left',
|
|
46
|
+
'outside-bounds': 'the change is outside the granted bounds',
|
|
47
|
+
'step-too-large': 'the change is larger than the grant allows',
|
|
48
|
+
});
|
|
49
|
+
function valueText(value) {
|
|
50
|
+
return typeof value === 'boolean' ? (value ? 'on' : 'off') : String(value);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Deterministic plain-text notice for one owner question. It names the
|
|
54
|
+
* human-readable parameter, never an internal id, digest or estimate term,
|
|
55
|
+
* and stays well inside Delivery's `result` caps. `repliesRead` is the
|
|
56
|
+
* slice-5 opt-in: only then does the notice invite an exact reply, because
|
|
57
|
+
* only then does Delivery route a reply back to this service.
|
|
58
|
+
*/
|
|
59
|
+
export function attentionOwnerQuestionText(question, scope, options = {}) {
|
|
60
|
+
const label = parameterLabels[question.change.parameter] ?? 'attention setting';
|
|
61
|
+
const checked = question.replay.shared.decided + question.replay.discriminating.decided;
|
|
62
|
+
const reasons = question.exceeded.map(reason => exceededLabels[reason] ?? 'the grant does not cover it');
|
|
63
|
+
return [
|
|
64
|
+
'Attention settings: a looser setting passed its replay check and needs your approval.',
|
|
65
|
+
`Workspace: ${scope.workspace} (${scope.preset})`,
|
|
66
|
+
`Notices: ${question.cohort.sourceId} on ${question.cohort.lane}`,
|
|
67
|
+
`Change: ${label} from ${valueText(question.change.from)} to ${valueText(question.change.to)} (${question.steps} step${question.steps === 1 ? '' : 's'}).`,
|
|
68
|
+
`Held-out notices replayed: ${checked}.`,
|
|
69
|
+
`Approval is needed because ${reasons.length === 0 ? 'no owner grant covers it' : reasons.join('; ')}.`,
|
|
70
|
+
...(options.repliesRead === true
|
|
71
|
+
? ['To allow this single change, reply exactly "authorize-once". To refuse it, reply "decline". Nothing changes until your reply is recorded.']
|
|
72
|
+
: ['To allow this single change, record an owner grant through the host configuration. Replies to this message are not read. Nothing changes until an owner grant is recorded.']),
|
|
73
|
+
].join('\n');
|
|
74
|
+
}
|
|
75
|
+
function invalid(message) {
|
|
76
|
+
throw new EvaluationStoreError('invalid-input', `attention owner question: ${message}`);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* WP22 slice 4 delivery and reply ledger for owner questions. Both tables are
|
|
80
|
+
* append-only with one row per question, so a question is handed to Delivery
|
|
81
|
+
* at most once and a reply is recorded at most once. Nothing here creates a
|
|
82
|
+
* grant or changes a tuning version.
|
|
83
|
+
*/
|
|
84
|
+
export class AttentionOwnerQuestionLedger {
|
|
85
|
+
#database;
|
|
86
|
+
#now;
|
|
87
|
+
constructor(database, now) {
|
|
88
|
+
this.#database = database;
|
|
89
|
+
this.#now = now;
|
|
90
|
+
}
|
|
91
|
+
#transaction(fn) {
|
|
92
|
+
this.#database.exec('BEGIN IMMEDIATE');
|
|
93
|
+
try {
|
|
94
|
+
const value = fn();
|
|
95
|
+
this.#database.exec('COMMIT');
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
this.#database.exec('ROLLBACK');
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
delivery(questionId) {
|
|
104
|
+
const row = this.#database.prepare(`SELECT question_id, status, outbox_id, idempotency_key, recorded_at
|
|
105
|
+
FROM evaluation_attention_owner_question_deliveries WHERE question_id=?`).get(questionId);
|
|
106
|
+
return row === undefined ? undefined : Object.freeze({ questionId: row.question_id, status: row.status, outboxId: row.outbox_id,
|
|
107
|
+
idempotencyKey: row.idempotency_key, recordedAt: row.recorded_at });
|
|
108
|
+
}
|
|
109
|
+
reply(questionId) {
|
|
110
|
+
const row = this.#database.prepare(`SELECT question_id, choice, replied_at FROM evaluation_attention_owner_question_replies
|
|
111
|
+
WHERE question_id=?`).get(questionId);
|
|
112
|
+
return row === undefined ? undefined : Object.freeze({ questionId: row.question_id, choice: row.choice, repliedAt: row.replied_at });
|
|
113
|
+
}
|
|
114
|
+
/** Write the single terminal delivery row; an existing row wins and is returned unchanged. */
|
|
115
|
+
recordDelivery(scopeInput, cohortInput, questionId, status, outboxId) {
|
|
116
|
+
const { scopeKey } = canonicalEvaluationScope(scopeInput);
|
|
117
|
+
const cohort = normalizeAttentionCohort(cohortInput);
|
|
118
|
+
if ((status === 'queued') !== (typeof outboxId === 'string'))
|
|
119
|
+
invalid('only a queued delivery carries an Outbox id');
|
|
120
|
+
return this.#transaction(() => {
|
|
121
|
+
const existing = this.delivery(questionId);
|
|
122
|
+
if (existing !== undefined)
|
|
123
|
+
return existing;
|
|
124
|
+
this.#database.prepare(`INSERT INTO evaluation_attention_owner_question_deliveries
|
|
125
|
+
(question_id, scope_key, lane, source_id, status, outbox_id, idempotency_key, recorded_at) VALUES (?,?,?,?,?,?,?,?)`)
|
|
126
|
+
.run(questionId, scopeKey, cohort.lane, cohort.sourceId, status, outboxId, attentionOwnerQuestionIdempotencyKey(questionId), this.#now());
|
|
127
|
+
return this.delivery(questionId);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Record-only owner reply. The caller has already established that the
|
|
132
|
+
* question is the cohort's latest and whether it is open and expired; a new
|
|
133
|
+
* reply needs open and not expired. Replaying the same choice is idempotent
|
|
134
|
+
* and a different choice is a conflict.
|
|
135
|
+
*/
|
|
136
|
+
recordReply(scopeInput, cohortInput, questionId, choiceInput, open, expired) {
|
|
137
|
+
return this.#transaction(() => this.recordReplyInTransaction(scopeInput, cohortInput, questionId, choiceInput, open, expired));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Same contract as `recordReply`, but the caller owns the surrounding
|
|
141
|
+
* `BEGIN IMMEDIATE`. Slice 5 uses it inside the tuning ledger's transaction
|
|
142
|
+
* so a recorded owner reply and the grant or proposal decision it applies to
|
|
143
|
+
* commit atomically. Never call it outside a transaction.
|
|
144
|
+
*/
|
|
145
|
+
recordReplyInTransaction(scopeInput, cohortInput, questionId, choiceInput, open, expired) {
|
|
146
|
+
const { scopeKey } = canonicalEvaluationScope(scopeInput);
|
|
147
|
+
const cohort = normalizeAttentionCohort(cohortInput);
|
|
148
|
+
if (typeof choiceInput !== 'string' || !attentionOwnerQuestionChoices.includes(choiceInput))
|
|
149
|
+
invalid('choice is not recognised');
|
|
150
|
+
const choice = choiceInput;
|
|
151
|
+
const existing = this.reply(questionId);
|
|
152
|
+
if (existing !== undefined) {
|
|
153
|
+
return existing.choice === choice
|
|
154
|
+
? Object.freeze({ status: 'recorded', questionId, choice, repliedAt: existing.repliedAt })
|
|
155
|
+
: Object.freeze({ status: 'rejected', reason: 'reply-conflict' });
|
|
156
|
+
}
|
|
157
|
+
if (!open)
|
|
158
|
+
return Object.freeze({ status: 'rejected', reason: 'question-closed' });
|
|
159
|
+
if (expired)
|
|
160
|
+
return Object.freeze({ status: 'rejected', reason: 'question-expired' });
|
|
161
|
+
const repliedAt = this.#now();
|
|
162
|
+
this.#database.prepare(`INSERT INTO evaluation_attention_owner_question_replies
|
|
163
|
+
(question_id, scope_key, lane, source_id, choice, replied_at) VALUES (?,?,?,?,?,?)`)
|
|
164
|
+
.run(questionId, scopeKey, cohort.lane, cohort.sourceId, choice, repliedAt);
|
|
165
|
+
return Object.freeze({ status: 'recorded', questionId, choice, repliedAt });
|
|
166
|
+
}
|
|
167
|
+
status(scopeInput, cohortInput, enabled, limit = 50) {
|
|
168
|
+
const { scopeKey } = canonicalEvaluationScope(scopeInput);
|
|
169
|
+
const cohort = normalizeAttentionCohort(cohortInput);
|
|
170
|
+
const deliveries = this.#database.prepare(`SELECT question_id FROM evaluation_attention_owner_question_deliveries
|
|
171
|
+
WHERE scope_key=? AND lane=? AND source_id=? ORDER BY recorded_at DESC, rowid DESC LIMIT ?`)
|
|
172
|
+
.all(scopeKey, cohort.lane, cohort.sourceId, limit).reverse().map(row => this.delivery(row.question_id));
|
|
173
|
+
const replies = this.#database.prepare(`SELECT question_id FROM evaluation_attention_owner_question_replies
|
|
174
|
+
WHERE scope_key=? AND lane=? AND source_id=? ORDER BY replied_at DESC, rowid DESC LIMIT ?`)
|
|
175
|
+
.all(scopeKey, cohort.lane, cohort.sourceId, limit).reverse().map(row => this.reply(row.question_id));
|
|
176
|
+
return Object.freeze({ enabled, deliveries: Object.freeze(deliveries), replies: Object.freeze(replies) });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=attention-owner-questions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attention-owner-questions.js","sourceRoot":"","sources":["../src/attention-owner-questions.ts"],"names":[],"mappings":"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"}
|