@alan512/experienceengine 0.2.0 → 0.3.0

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.
Files changed (182) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.mcp.json +5 -6
  3. package/README.md +65 -54
  4. package/README.zh-CN.md +64 -53
  5. package/dist/adapters/codex/action-registry.d.ts +23 -1
  6. package/dist/adapters/codex/action-registry.js +73 -0
  7. package/dist/adapters/codex/action-registry.js.map +1 -1
  8. package/dist/adapters/codex/behavior-loop.d.ts +80 -0
  9. package/dist/adapters/codex/behavior-loop.js +189 -0
  10. package/dist/adapters/codex/behavior-loop.js.map +1 -0
  11. package/dist/adapters/codex/mcp-server.d.ts +27 -72
  12. package/dist/adapters/codex/mcp-server.js +36 -160
  13. package/dist/adapters/codex/mcp-server.js.map +1 -1
  14. package/dist/cli/commands/claude-hook.d.ts +4 -0
  15. package/dist/cli/commands/claude-hook.js +105 -21
  16. package/dist/cli/commands/claude-hook.js.map +1 -1
  17. package/dist/cli/commands/codex-hook.d.ts +22 -0
  18. package/dist/cli/commands/codex-hook.js +298 -0
  19. package/dist/cli/commands/codex-hook.js.map +1 -0
  20. package/dist/cli/commands/config.js +9 -1
  21. package/dist/cli/commands/config.js.map +1 -1
  22. package/dist/cli/commands/doctor.js +77 -2
  23. package/dist/cli/commands/doctor.js.map +1 -1
  24. package/dist/cli/commands/inspect.d.ts +1 -1
  25. package/dist/cli/commands/inspect.js +329 -4
  26. package/dist/cli/commands/inspect.js.map +1 -1
  27. package/dist/cli/commands/install.js +2 -0
  28. package/dist/cli/commands/install.js.map +1 -1
  29. package/dist/cli/commands/maintenance.js +4 -0
  30. package/dist/cli/commands/maintenance.js.map +1 -1
  31. package/dist/cli/commands/repair.js +33 -3
  32. package/dist/cli/commands/repair.js.map +1 -1
  33. package/dist/cli/commands/status.js +33 -0
  34. package/dist/cli/commands/status.js.map +1 -1
  35. package/dist/cli/dispatch.js +8 -3
  36. package/dist/cli/dispatch.js.map +1 -1
  37. package/dist/cli/index.js +0 -0
  38. package/dist/config/config-schema.d.ts +8 -0
  39. package/dist/config/config-schema.js +6 -0
  40. package/dist/config/config-schema.js.map +1 -1
  41. package/dist/config/default-config.js +1 -0
  42. package/dist/config/default-config.js.map +1 -1
  43. package/dist/config/load-config.js +3 -0
  44. package/dist/config/load-config.js.map +1 -1
  45. package/dist/controller/candidate-retriever.d.ts +5 -1
  46. package/dist/controller/candidate-retriever.js +237 -13
  47. package/dist/controller/candidate-retriever.js.map +1 -1
  48. package/dist/controller/injection-renderer.d.ts +2 -2
  49. package/dist/controller/injection-renderer.js +22 -3
  50. package/dist/controller/injection-renderer.js.map +1 -1
  51. package/dist/controller/injection-scorecard.js +3 -0
  52. package/dist/controller/injection-scorecard.js.map +1 -1
  53. package/dist/controller/intervention-controller.d.ts +2 -2
  54. package/dist/controller/intervention-controller.js +185 -25
  55. package/dist/controller/intervention-controller.js.map +1 -1
  56. package/dist/controller/model-reranker-mode.d.ts +4 -0
  57. package/dist/controller/model-reranker-mode.js +14 -0
  58. package/dist/controller/model-reranker-mode.js.map +1 -0
  59. package/dist/controller/model-reranker.d.ts +0 -1
  60. package/dist/controller/model-reranker.js +1 -13
  61. package/dist/controller/model-reranker.js.map +1 -1
  62. package/dist/controller/policy-enricher.d.ts +2 -1
  63. package/dist/controller/policy-enricher.js +71 -11
  64. package/dist/controller/policy-enricher.js.map +1 -1
  65. package/dist/controller/trigger-evaluator.d.ts +2 -1
  66. package/dist/controller/trigger-evaluator.js +24 -0
  67. package/dist/controller/trigger-evaluator.js.map +1 -1
  68. package/dist/evaluation/openclaw-scenarios.js +12 -5
  69. package/dist/evaluation/openclaw-scenarios.js.map +1 -1
  70. package/dist/experience-management/repo-policy.d.ts +53 -0
  71. package/dist/experience-management/repo-policy.js +175 -0
  72. package/dist/experience-management/repo-policy.js.map +1 -0
  73. package/dist/hybrid/capsule-builder.js +2 -0
  74. package/dist/hybrid/capsule-builder.js.map +1 -1
  75. package/dist/input/scope-resolver.js +16 -4
  76. package/dist/input/scope-resolver.js.map +1 -1
  77. package/dist/install/claude-cli.js +38 -21
  78. package/dist/install/claude-cli.js.map +1 -1
  79. package/dist/install/claude-code-doctor.js +8 -3
  80. package/dist/install/claude-code-doctor.js.map +1 -1
  81. package/dist/install/claude-code-installer.js +5 -2
  82. package/dist/install/claude-code-installer.js.map +1 -1
  83. package/dist/install/claude-runtime-target.d.ts +5 -0
  84. package/dist/install/claude-runtime-target.js +32 -2
  85. package/dist/install/claude-runtime-target.js.map +1 -1
  86. package/dist/install/codex-cli.d.ts +7 -0
  87. package/dist/install/codex-cli.js +43 -20
  88. package/dist/install/codex-cli.js.map +1 -1
  89. package/dist/install/codex-hooks.d.ts +42 -0
  90. package/dist/install/codex-hooks.js +258 -0
  91. package/dist/install/codex-hooks.js.map +1 -0
  92. package/dist/install/codex-installer.d.ts +28 -3
  93. package/dist/install/codex-installer.js +120 -9
  94. package/dist/install/codex-installer.js.map +1 -1
  95. package/dist/install/codex-runtime-target.d.ts +20 -0
  96. package/dist/install/codex-runtime-target.js +85 -17
  97. package/dist/install/codex-runtime-target.js.map +1 -1
  98. package/dist/install/openclaw-cli.d.ts +1 -0
  99. package/dist/install/openclaw-cli.js +52 -3
  100. package/dist/install/openclaw-cli.js.map +1 -1
  101. package/dist/install/openclaw-installer.d.ts +5 -0
  102. package/dist/install/openclaw-installer.js +24 -5
  103. package/dist/install/openclaw-installer.js.map +1 -1
  104. package/dist/interaction/repo-summary.d.ts +17 -0
  105. package/dist/interaction/repo-summary.js +33 -15
  106. package/dist/interaction/repo-summary.js.map +1 -1
  107. package/dist/interaction/retrieval-policy-inspection.d.ts +19 -0
  108. package/dist/interaction/retrieval-policy-inspection.js +33 -0
  109. package/dist/interaction/retrieval-policy-inspection.js.map +1 -0
  110. package/dist/interaction/service.d.ts +24 -1
  111. package/dist/interaction/service.js +186 -23
  112. package/dist/interaction/service.js.map +1 -1
  113. package/dist/maintenance/claude-validate-print.d.ts +8 -1
  114. package/dist/maintenance/claude-validate-print.js +52 -2
  115. package/dist/maintenance/claude-validate-print.js.map +1 -1
  116. package/dist/maintenance/experience-export-drafts.d.ts +56 -0
  117. package/dist/maintenance/experience-export-drafts.js +217 -0
  118. package/dist/maintenance/experience-export-drafts.js.map +1 -0
  119. package/dist/maintenance/experience-hygiene.d.ts +38 -0
  120. package/dist/maintenance/experience-hygiene.js +266 -0
  121. package/dist/maintenance/experience-hygiene.js.map +1 -0
  122. package/dist/maintenance/operator-review-flow.d.ts +81 -0
  123. package/dist/maintenance/operator-review-flow.js +172 -0
  124. package/dist/maintenance/operator-review-flow.js.map +1 -0
  125. package/dist/plugin/openclaw-plugin.d.ts +5 -0
  126. package/dist/plugin/runtime-helpers.js +43 -1
  127. package/dist/plugin/runtime-helpers.js.map +1 -1
  128. package/dist/runtime/prompt-service.d.ts +51 -0
  129. package/dist/runtime/prompt-service.js +209 -0
  130. package/dist/runtime/prompt-service.js.map +1 -0
  131. package/dist/runtime/service.d.ts +8 -2
  132. package/dist/runtime/service.js +234 -44
  133. package/dist/runtime/service.js.map +1 -1
  134. package/dist/store/sqlite/db.js +15 -0
  135. package/dist/store/sqlite/db.js.map +1 -1
  136. package/dist/store/sqlite/repositories/attribution-record-repo.d.ts +15 -0
  137. package/dist/store/sqlite/repositories/attribution-record-repo.js +111 -0
  138. package/dist/store/sqlite/repositories/attribution-record-repo.js.map +1 -0
  139. package/dist/store/sqlite/repositories/episode-repo.d.ts +14 -0
  140. package/dist/store/sqlite/repositories/episode-repo.js +84 -0
  141. package/dist/store/sqlite/repositories/episode-repo.js.map +1 -0
  142. package/dist/store/sqlite/repositories/injection-repo.d.ts +2 -0
  143. package/dist/store/sqlite/repositories/injection-repo.js +32 -5
  144. package/dist/store/sqlite/repositories/injection-repo.js.map +1 -1
  145. package/dist/store/sqlite/repositories/input-record-repo.d.ts +1 -0
  146. package/dist/store/sqlite/repositories/input-record-repo.js +23 -10
  147. package/dist/store/sqlite/repositories/input-record-repo.js.map +1 -1
  148. package/dist/store/sqlite/repositories/node-repo.d.ts +2 -0
  149. package/dist/store/sqlite/repositories/node-repo.js +19 -0
  150. package/dist/store/sqlite/repositories/node-repo.js.map +1 -1
  151. package/dist/store/sqlite/repositories/outcome-record-repo.d.ts +1 -0
  152. package/dist/store/sqlite/repositories/outcome-record-repo.js +11 -2
  153. package/dist/store/sqlite/repositories/outcome-record-repo.js.map +1 -1
  154. package/dist/store/sqlite/repositories/repo-policy-repo.d.ts +11 -0
  155. package/dist/store/sqlite/repositories/repo-policy-repo.js +87 -0
  156. package/dist/store/sqlite/repositories/repo-policy-repo.js.map +1 -0
  157. package/dist/store/sqlite/repositories/review-event-repo.d.ts +1 -0
  158. package/dist/store/sqlite/repositories/review-event-repo.js +11 -2
  159. package/dist/store/sqlite/repositories/review-event-repo.js.map +1 -1
  160. package/dist/store/sqlite/repositories/task-run-repo.d.ts +1 -0
  161. package/dist/store/sqlite/repositories/task-run-repo.js +11 -2
  162. package/dist/store/sqlite/repositories/task-run-repo.js.map +1 -1
  163. package/dist/store/sqlite/schema.sql +43 -0
  164. package/dist/store/vector/api-embedding-provider.js +14 -3
  165. package/dist/store/vector/api-embedding-provider.js.map +1 -1
  166. package/dist/store/vector/embeddings.js +7 -1
  167. package/dist/store/vector/embeddings.js.map +1 -1
  168. package/dist/types/domain.d.ts +105 -3
  169. package/dist/utils/text.js +5 -1
  170. package/dist/utils/text.js.map +1 -1
  171. package/docs/assets/readme/inspect-last-example.svg +37 -0
  172. package/docs/releases/v0.2.1.md +30 -0
  173. package/docs/releases/v0.3.0.md +17 -0
  174. package/docs/user-guide.md +91 -5
  175. package/openclaw.plugin.json +1 -1
  176. package/package.json +16 -14
  177. package/plugins/claude-code-experienceengine/.claude-plugin/plugin.json +1 -1
  178. package/plugins/claude-code-experienceengine/.mcp.json +3 -9
  179. package/plugins/claude-code-experienceengine/scripts/install-deps.sh +1 -1
  180. package/plugins/claude-code-experienceengine/scripts/mcp-server.sh +43 -0
  181. package/scripts/claude-plugin/claude-hook.sh +6 -1
  182. package/scripts/claude-plugin/mcp-server.sh +45 -0
@@ -0,0 +1,19 @@
1
+ import type { InjectionScorecard, PolicyEnrichmentComponent, RetrievalPolicyStageDiagnostic } from "../types/domain.js";
2
+ export type RetrievalPolicyStageInspection = {
3
+ stage: RetrievalPolicyStageDiagnostic["stage"];
4
+ acceptedCount?: number;
5
+ rejectedCount?: number;
6
+ passedCount?: number;
7
+ reasonCodes: string[];
8
+ };
9
+ export type RetrievalPolicyComponentInspection = PolicyEnrichmentComponent;
10
+ export type RetrievalPolicyInspectionSummary = {
11
+ stages: RetrievalPolicyStageInspection[];
12
+ semanticMode?: "skipped" | "rerank" | "backfill";
13
+ topPolicyComponents: RetrievalPolicyComponentInspection[];
14
+ rejectedCandidates: Array<{
15
+ id: string;
16
+ reasonCodes: string[];
17
+ }>;
18
+ };
19
+ export declare const buildRetrievalPolicyInspectionSummary: (scorecard?: InjectionScorecard) => RetrievalPolicyInspectionSummary | undefined;
@@ -0,0 +1,33 @@
1
+ const SEMANTIC_STAGE = "semantic_rerank_backfill";
2
+ const inferSemanticMode = (stage) => {
3
+ const modeReason = stage?.reasonCodes.find((reason) => reason.startsWith("semantic_mode:"));
4
+ const mode = modeReason?.slice("semantic_mode:".length);
5
+ return mode === "skipped" || mode === "rerank" || mode === "backfill" ? mode : undefined;
6
+ };
7
+ export const buildRetrievalPolicyInspectionSummary = (scorecard) => {
8
+ const stages = scorecard?.retrievalPolicyDiagnostics?.stages ?? [];
9
+ const topCandidate = scorecard?.topCandidates?.[0];
10
+ const topPolicyComponents = [...(topCandidate?.policyComponents ?? [])]
11
+ .sort((left, right) => Math.abs(right.value) - Math.abs(left.value))
12
+ .slice(0, 5);
13
+ const rejectedCandidates = scorecard?.rejectedCandidates?.slice(0, 5).map((candidate) => ({
14
+ id: candidate.id,
15
+ reasonCodes: candidate.reasonCodes
16
+ })) ?? [];
17
+ if (!stages.length && !topPolicyComponents.length && !rejectedCandidates.length) {
18
+ return undefined;
19
+ }
20
+ return {
21
+ stages: stages.map((stage) => ({
22
+ stage: stage.stage,
23
+ acceptedCount: stage.acceptedCount,
24
+ rejectedCount: stage.rejectedCount,
25
+ passedCount: stage.passedCount,
26
+ reasonCodes: stage.reasonCodes
27
+ })),
28
+ semanticMode: inferSemanticMode(stages.find((stage) => stage.stage === SEMANTIC_STAGE)),
29
+ topPolicyComponents,
30
+ rejectedCandidates
31
+ };
32
+ };
33
+ //# sourceMappingURL=retrieval-policy-inspection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retrieval-policy-inspection.js","sourceRoot":"","sources":["../../src/interaction/retrieval-policy-inspection.ts"],"names":[],"mappings":"AA0BA,MAAM,cAAc,GAAG,0BAA0B,CAAC;AAElD,MAAM,iBAAiB,GAAG,CACxB,KAAiD,EACC,EAAE;IACpD,MAAM,UAAU,GAAG,KAAK,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC5F,MAAM,IAAI,GAAG,UAAU,EAAE,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACxD,OAAO,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3F,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,qCAAqC,GAAG,CACnD,SAA8B,EACgB,EAAE;IAChD,MAAM,MAAM,GAAG,SAAS,EAAE,0BAA0B,EAAE,MAAM,IAAI,EAAE,CAAC;IACnE,MAAM,YAAY,GAAG,SAAS,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC;IACnD,MAAM,mBAAmB,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,gBAAgB,IAAI,EAAE,CAAC,CAAC;SACpE,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;SACnE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACf,MAAM,kBAAkB,GACtB,SAAS,EAAE,kBAAkB,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QAC7D,EAAE,EAAE,SAAS,CAAC,EAAE;QAChB,WAAW,EAAE,SAAS,CAAC,WAAW;KACnC,CAAC,CAAC,IAAI,EAAE,CAAC;IAEZ,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,mBAAmB,CAAC,MAAM,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,CAAC;QAChF,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAC7B,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,WAAW,EAAE,KAAK,CAAC,WAAW;SAC/B,CAAC,CAAC;QACH,YAAY,EAAE,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,cAAc,CAAC,CAAC;QACvF,mBAAmB;QACnB,kBAAkB;KACnB,CAAC;AACJ,CAAC,CAAC"}
@@ -1,8 +1,12 @@
1
1
  import type { ExperienceEngineConfig } from "../config/config-schema.js";
2
2
  import { type BenchmarkSummary } from "../evaluation/benchmark-summary.js";
3
+ import { type HygieneReviewFilters, type HygieneReviewReport } from "../maintenance/experience-hygiene.js";
4
+ import { type ExperienceExportDraftFilters, type ExperienceExportDraftReport } from "../maintenance/experience-export-drafts.js";
5
+ import { type OperatorReviewReport } from "../maintenance/operator-review-flow.js";
3
6
  import { type HybridRouteDecision } from "../hybrid/router.js";
4
- import type { CandidateLifecycleState, DistillationSource, DistillationJobState, EvaluationMode, ExperienceInputRecord, FeedbackAttributionReason, InjectionEvent, InjectionScorecard, ExperienceNode, ExperienceNodeType, ExperienceState, TaskRun } from "../types/domain.js";
7
+ import type { CandidateLifecycleState, DistillationSource, DistillationJobState, EvaluationMode, ExperienceInputRecord, FeedbackAttributionReason, AttributionRecord, EpisodeProjection, InjectionEvent, InjectionScorecard, ExperienceNode, ExperienceNodeType, ExperienceState, TaskRun } from "../types/domain.js";
5
8
  import { type ExperienceRepoSummary } from "./repo-summary.js";
9
+ import { type RetrievalPolicyInspectionSummary } from "./retrieval-policy-inspection.js";
6
10
  export type ExperienceNodeSummary = {
7
11
  id: string;
8
12
  type: ExperienceNode["node_type"];
@@ -53,6 +57,7 @@ export type ExperienceTimelineEntry = {
53
57
  };
54
58
  export type ExperienceLastInspection = {
55
59
  sessionId?: string;
60
+ episodeId?: string;
56
61
  scopeId: string;
57
62
  taskType: ExperienceInputRecord["task_type"];
58
63
  intervention: "inject" | "skip" | "shadow" | "holdout";
@@ -60,6 +65,8 @@ export type ExperienceLastInspection = {
60
65
  delivered?: boolean;
61
66
  autoFeedback: "helped" | "harmed" | "none";
62
67
  autoFeedbackReason?: InjectionEvent["attribution_reason"];
68
+ attributionRecords: AttributionRecord[];
69
+ episodeProjection?: EpisodeProjection;
63
70
  outcome: ExperienceInputRecord["outcome_signal"];
64
71
  injectedNodes: ExperienceNodeSummary[];
65
72
  hints: string[];
@@ -68,6 +75,7 @@ export type ExperienceLastInspection = {
68
75
  decisionExplanation?: string;
69
76
  trustSummary?: string;
70
77
  retrievalNotes: string[];
78
+ retrievalPolicySummary?: RetrievalPolicyInspectionSummary;
71
79
  timeline: ExperienceTimelineEntry[];
72
80
  learningStatus?: TaskRun["learning_status"];
73
81
  learningReason?: string;
@@ -171,6 +179,9 @@ export declare class ExperienceInteractionService {
171
179
  private readonly hybridWorkerClient;
172
180
  private readonly inputRepo;
173
181
  private readonly injectionRepo;
182
+ private readonly attributionRecordRepo;
183
+ private readonly repoPolicyRepo;
184
+ private readonly episodeRepo;
174
185
  private readonly nodeRepo;
175
186
  private readonly candidateRepo;
176
187
  private readonly jobRepo;
@@ -197,8 +208,20 @@ export declare class ExperienceInteractionService {
197
208
  listNodesByState(state: ExperienceState): ExperienceNodeSummary[];
198
209
  listNodesByType(nodeType: ExperienceNodeType): ExperienceNodeSummary[];
199
210
  inspectLearningSummary(): ExperienceLearningSummary;
211
+ inspectHygiene(cwd?: string, filters?: Omit<HygieneReviewFilters, "scopeId"> & {
212
+ scopeId?: string;
213
+ }): HygieneReviewReport;
214
+ inspectExportDrafts(cwd?: string, filters?: Omit<ExperienceExportDraftFilters, "scopeId"> & {
215
+ scopeId?: string;
216
+ }): ExperienceExportDraftReport;
217
+ inspectReview(cwd?: string, filters?: {
218
+ scopeId?: string;
219
+ limit?: number;
220
+ }): OperatorReviewReport;
200
221
  private buildLearningSummary;
201
222
  inspectRepoSummary(cwd?: string): ExperienceRepoSummary;
223
+ inspectRepoPolicy(cwd?: string): import("../experience-management/repo-policy.js").RepoPolicyInspection;
224
+ restoreRepoPolicy(cwd?: string): import("../types/domain.js").RepoPolicy;
202
225
  inspectFirstValueReadiness(cwd?: string): ExperienceFirstValueReadiness;
203
226
  inspectDecisionHealth(cwd?: string, limit?: number): ExperienceDecisionHealth;
204
227
  feedbackLast(feedback: FeedbackValue, cwd?: string): FeedbackResult;
@@ -3,6 +3,10 @@ import { buildBenchmarkSummary } from "../evaluation/benchmark-summary.js";
3
3
  import { buildExplainDecisionCapsule } from "../hybrid/capsule-builder.js";
4
4
  import { resolveHybridExplainProviderEndpoint } from "../hybrid/explain-provider-client.js";
5
5
  import { resolveHybridRolloutState } from "../hybrid/rollout.js";
6
+ import { buildDefaultRepoPolicy, inspectRepoPolicyEvidence } from "../experience-management/repo-policy.js";
7
+ import { buildHygieneReviewReport } from "../maintenance/experience-hygiene.js";
8
+ import { buildExperienceExportDraftReport } from "../maintenance/experience-export-drafts.js";
9
+ import { buildOperatorReviewFlow } from "../maintenance/operator-review-flow.js";
6
10
  import { selectHybridRoute } from "../hybrid/router.js";
7
11
  import { HybridWorkerClient } from "../hybrid/worker-client.js";
8
12
  import { resolveScope } from "../input/scope-resolver.js";
@@ -12,6 +16,9 @@ import { DistillationJobRepository } from "../store/sqlite/repositories/distilla
12
16
  import { HybridInvocationTraceRepository } from "../store/sqlite/repositories/hybrid-invocation-trace-repo.js";
13
17
  import { InputRecordRepository } from "../store/sqlite/repositories/input-record-repo.js";
14
18
  import { InjectionRepository } from "../store/sqlite/repositories/injection-repo.js";
19
+ import { AttributionRecordRepository } from "../store/sqlite/repositories/attribution-record-repo.js";
20
+ import { RepoPolicyRepository } from "../store/sqlite/repositories/repo-policy-repo.js";
21
+ import { EpisodeRepository } from "../store/sqlite/repositories/episode-repo.js";
15
22
  import { NodeRepository } from "../store/sqlite/repositories/node-repo.js";
16
23
  import { OutcomeRecordRepository } from "../store/sqlite/repositories/outcome-record-repo.js";
17
24
  import { ReviewEventRepository } from "../store/sqlite/repositories/review-event-repo.js";
@@ -20,8 +27,9 @@ import { TaskRunRepository } from "../store/sqlite/repositories/task-run-repo.js
20
27
  import { applyGovernedNodeFeedback, deriveNodeOriginProfileForNode } from "../experience-management/node-lifecycle-governance.js";
21
28
  import { deriveGovernanceSignals, isPotentialMisfire } from "../experience-management/governance-observability.js";
22
29
  import { nowIso } from "../utils/clock.js";
23
- import { createId } from "../utils/ids.js";
30
+ import { createId, stableId } from "../utils/ids.js";
24
31
  import { buildRepoSummary } from "./repo-summary.js";
32
+ import { buildRetrievalPolicyInspectionSummary } from "./retrieval-policy-inspection.js";
25
33
  const normalizeHybridExplainPrompt = (value) => value
26
34
  .toLowerCase()
27
35
  .replace(/\s+/g, " ")
@@ -89,14 +97,41 @@ export const deriveStructuredSilenceReason = (input) => {
89
97
  }
90
98
  return "unknown";
91
99
  };
92
- const toReviewEvent = (nodeId, eventType, source, taskRunId) => ({
100
+ const toReviewEvent = (nodeId, eventType, source, taskRunId, episodeId) => ({
93
101
  id: createId("review"),
102
+ episode_id: episodeId,
94
103
  node_id: nodeId,
95
104
  task_run_id: taskRunId,
96
105
  event_type: eventType,
97
106
  source,
98
107
  created_at: nowIso()
99
108
  });
109
+ const toManualOverrideAttributionRecord = (input) => {
110
+ const timestamp = nowIso();
111
+ return {
112
+ id: stableId("attr", `${input.injectionEvent?.injection_id ?? "manual"}:${input.nodeId}:manual_override:${input.feedback}:${timestamp}`),
113
+ injection_id: input.injectionEvent?.injection_id,
114
+ node_id: input.nodeId,
115
+ episode_id: input.episodeId ?? input.injectionEvent?.episode_id,
116
+ intervention_strength: input.injectionEvent?.scorecard?.interventionStrength,
117
+ injection_mode: input.injectionEvent?.mode,
118
+ delivery_mode: input.injectionEvent?.delivery_mode,
119
+ delivered: Boolean(input.injectionEvent?.delivered),
120
+ outcome: input.injectionEvent?.was_successful === true
121
+ ? "success"
122
+ : input.injectionEvent?.was_successful === false
123
+ ? "failure"
124
+ : "unknown",
125
+ attribution_verdict: input.feedback === "helped" ? "strong_helped" : "strong_harmed",
126
+ confidence: "high",
127
+ evidence_refs: input.evidenceRefs,
128
+ user_override: input.feedback,
129
+ source: "manual_override",
130
+ attribution_reason: "manual_override",
131
+ created_at: timestamp,
132
+ resolved_at: timestamp
133
+ };
134
+ };
100
135
  const deriveNodeRisk = (node) => {
101
136
  if (node.state === "candidate") {
102
137
  return "high";
@@ -353,6 +388,9 @@ export class ExperienceInteractionService {
353
388
  hybridWorkerClient;
354
389
  inputRepo;
355
390
  injectionRepo;
391
+ attributionRecordRepo;
392
+ repoPolicyRepo;
393
+ episodeRepo;
356
394
  nodeRepo;
357
395
  candidateRepo;
358
396
  jobRepo;
@@ -367,6 +405,9 @@ export class ExperienceInteractionService {
367
405
  bootstrapDatabase(db);
368
406
  this.inputRepo = new InputRecordRepository(db);
369
407
  this.injectionRepo = new InjectionRepository(db);
408
+ this.attributionRecordRepo = new AttributionRecordRepository(db);
409
+ this.repoPolicyRepo = new RepoPolicyRepository(db);
410
+ this.episodeRepo = new EpisodeRepository(db);
370
411
  this.nodeRepo = new NodeRepository(db);
371
412
  this.candidateRepo = new CandidateRepository(db);
372
413
  this.jobRepo = new DistillationJobRepository(db);
@@ -458,15 +499,19 @@ export class ExperienceInteractionService {
458
499
  if (!record) {
459
500
  return undefined;
460
501
  }
461
- const injectionEvent = record.session_id
502
+ const episodeProjection = record.episode_id ? this.episodeRepo.getByEpisodeId(record.episode_id) : undefined;
503
+ const injectionEvent = episodeProjection?.injection_events[0] ?? (record.session_id
462
504
  ? this.injectionRepo.getLatestBySessionId(record.session_id)
463
505
  : record.injected_node_ids.length
464
506
  ? this.injectionRepo.getLatest()
465
- : undefined;
507
+ : undefined);
466
508
  const selectedNodeIds = injectionEvent?.injected_node_ids?.length
467
509
  ? injectionEvent.injected_node_ids
468
510
  : record.injected_node_ids;
469
511
  const injectedNodes = this.nodeRepo.listByIds(selectedNodeIds);
512
+ const attributionRecords = episodeProjection?.attribution_records ?? (injectionEvent
513
+ ? this.attributionRecordRepo.listByInjectionId(injectionEvent.injection_id)
514
+ : selectedNodeIds.flatMap((nodeId) => this.attributionRecordRepo.listByNodeId(nodeId)));
470
515
  const scorecard = injectionEvent?.scorecard ??
471
516
  (selectedNodeIds.length
472
517
  ? buildInjectionScorecard({
@@ -479,17 +524,19 @@ export class ExperienceInteractionService {
479
524
  injected_node_ids: selectedNodeIds
480
525
  }, "inject", injectedNodes, record.session_id)
481
526
  : undefined);
482
- const taskRun = record.session_id ? this.taskRunRepo.getLatestBySessionId(record.session_id) : undefined;
483
- const reviewEvents = taskRun?.id ? this.reviewEventRepo.listByTaskRunId(taskRun.id) : [];
527
+ const taskRun = episodeProjection?.task_run ?? (record.session_id ? this.taskRunRepo.getLatestBySessionId(record.session_id) : undefined);
528
+ const reviewEvents = episodeProjection?.review_events ?? (taskRun?.id ? this.reviewEventRepo.listByTaskRunId(taskRun.id) : []);
484
529
  const autoFeedback = summarizeAutomaticFeedback(reviewEvents);
485
- const intervention = selectedNodeIds.length === 0
530
+ const intervention = injectionEvent?.mode === "skip"
486
531
  ? "skip"
487
- : injectionEvent && !injectionEvent.delivered
488
- ? injectionEvent.delivery_mode === "holdout"
489
- ? "holdout"
490
- : "shadow"
491
- : "inject";
492
- const outcomeRecord = taskRun?.id ? this.outcomeRepo.listByTaskRunId(taskRun.id)[0] : undefined;
532
+ : selectedNodeIds.length === 0
533
+ ? "skip"
534
+ : injectionEvent && !injectionEvent.delivered
535
+ ? injectionEvent.delivery_mode === "holdout"
536
+ ? "holdout"
537
+ : "shadow"
538
+ : "inject";
539
+ const outcomeRecord = episodeProjection?.outcome_records[0] ?? (taskRun?.id ? this.outcomeRepo.listByTaskRunId(taskRun.id)[0] : undefined);
493
540
  const latestAutomaticFeedback = reviewEvents.find((event) => event.source === "automatic");
494
541
  const autoFeedbackReason = inferAutoFeedbackReason({
495
542
  explicitReason: injectionEvent?.attribution_reason,
@@ -500,6 +547,7 @@ export class ExperienceInteractionService {
500
547
  const decisionExplanation = buildDecisionExplanation({ intervention, scorecard });
501
548
  return {
502
549
  sessionId: record.session_id,
550
+ episodeId: record.episode_id,
503
551
  scopeId: record.scope_id,
504
552
  taskType: record.task_type,
505
553
  intervention,
@@ -507,6 +555,8 @@ export class ExperienceInteractionService {
507
555
  delivered: injectionEvent?.delivered,
508
556
  autoFeedback,
509
557
  autoFeedbackReason,
558
+ attributionRecords,
559
+ episodeProjection,
510
560
  outcome: record.outcome_signal,
511
561
  injectedNodes: injectedNodes.map(toNodeSummary),
512
562
  hints: injectedNodes.map((node) => node.compact_hint),
@@ -515,6 +565,7 @@ export class ExperienceInteractionService {
515
565
  decisionExplanation,
516
566
  trustSummary: buildTrustSummary({ scorecard, injectedNodes: injectedNodes.map(toNodeSummary) }),
517
567
  retrievalNotes: buildRetrievalNotes(scorecard),
568
+ retrievalPolicySummary: buildRetrievalPolicyInspectionSummary(scorecard),
518
569
  timeline: buildLatestTimeline({
519
570
  record,
520
571
  taskRunCreatedAt: taskRun?.created_at,
@@ -537,27 +588,32 @@ export class ExperienceInteractionService {
537
588
  if (!event) {
538
589
  return undefined;
539
590
  }
540
- const taskRun = event.session_id ? this.taskRunRepo.getLatestBySessionId(event.session_id) : undefined;
591
+ const episodeProjection = event.episode_id ? this.episodeRepo.getByEpisodeId(event.episode_id) : undefined;
592
+ const taskRun = episodeProjection?.task_run ?? (event.session_id ? this.taskRunRepo.getLatestBySessionId(event.session_id) : undefined);
541
593
  const latestRecord = event.session_id ? this.inputRepo.getLatestBySessionId(event.session_id) : undefined;
542
594
  if (latestRecord) {
543
595
  return this.inspectRecord(latestRecord);
544
596
  }
545
597
  const injectedNodes = this.nodeRepo.listByIds(event.injected_node_ids);
546
- const reviewEvents = taskRun?.id ? this.reviewEventRepo.listByTaskRunId(taskRun.id) : [];
598
+ const attributionRecords = episodeProjection?.attribution_records ?? this.attributionRecordRepo.listByInjectionId(event.injection_id);
599
+ const reviewEvents = episodeProjection?.review_events ?? (taskRun?.id ? this.reviewEventRepo.listByTaskRunId(taskRun.id) : []);
547
600
  const autoFeedback = summarizeAutomaticFeedback(reviewEvents);
548
601
  const latestAutomaticFeedback = reviewEvents.find((reviewEvent) => reviewEvent.source === "automatic");
549
- const intervention = !event.delivered
550
- ? event.delivery_mode === "holdout"
551
- ? "holdout"
552
- : "shadow"
553
- : "inject";
554
- const outcomeRecord = taskRun?.id ? this.outcomeRepo.listByTaskRunId(taskRun.id)[0] : undefined;
602
+ const intervention = event.mode === "skip"
603
+ ? "skip"
604
+ : !event.delivered
605
+ ? event.delivery_mode === "holdout"
606
+ ? "holdout"
607
+ : "shadow"
608
+ : "inject";
609
+ const outcomeRecord = episodeProjection?.outcome_records[0] ?? (taskRun?.id ? this.outcomeRepo.listByTaskRunId(taskRun.id)[0] : undefined);
555
610
  const outcome = outcomeRecord?.outcome_signal ??
556
611
  (taskRun?.final_status === "success" ? "success" : taskRun?.final_status === "failure" ? "failure" : "unknown");
557
612
  const summary = event.task_summary ?? taskRun?.task_summary ?? "Latest injection event";
558
613
  const decisionExplanation = buildDecisionExplanation({ intervention, scorecard: event.scorecard });
559
614
  return {
560
615
  sessionId: event.session_id,
616
+ episodeId: event.episode_id,
561
617
  scopeId: event.scope_id,
562
618
  taskType: event.task_type,
563
619
  intervention,
@@ -570,6 +626,8 @@ export class ExperienceInteractionService {
570
626
  intervention,
571
627
  outcome
572
628
  }),
629
+ attributionRecords,
630
+ episodeProjection,
573
631
  outcome,
574
632
  injectedNodes: injectedNodes.map(toNodeSummary),
575
633
  hints: injectedNodes.map((node) => node.compact_hint),
@@ -578,6 +636,7 @@ export class ExperienceInteractionService {
578
636
  decisionExplanation,
579
637
  trustSummary: buildTrustSummary({ scorecard: event.scorecard, injectedNodes: injectedNodes.map(toNodeSummary) }),
580
638
  retrievalNotes: buildRetrievalNotes(event.scorecard),
639
+ retrievalPolicySummary: buildRetrievalPolicyInspectionSummary(event.scorecard),
581
640
  timeline: buildLatestTimeline({
582
641
  record: {
583
642
  record_id: `injection:${event.injection_id}`,
@@ -661,6 +720,77 @@ export class ExperienceInteractionService {
661
720
  inspectLearningSummary() {
662
721
  return this.buildLearningSummary();
663
722
  }
723
+ inspectHygiene(cwd = process.cwd(), filters = {}) {
724
+ const scopeId = filters.scopeId ?? resolveScope(cwd).scope_id;
725
+ const candidateStates = ["pending", "distilled", "failed", "discarded"];
726
+ return buildHygieneReviewReport({
727
+ nodes: this.nodeRepo.listByScope(scopeId),
728
+ candidates: candidateStates.flatMap((state) => this.candidateRepo.listByLifecycleState(state)).filter((candidate) => candidate.scope_id === scopeId),
729
+ attributionRecords: this.attributionRecordRepo.listRecentByScope(scopeId, Math.max(50, filters.limit ?? 20)),
730
+ filters: {
731
+ ...filters,
732
+ scopeId
733
+ }
734
+ });
735
+ }
736
+ inspectExportDrafts(cwd = process.cwd(), filters = {}) {
737
+ const scopeId = filters.scopeId ?? resolveScope(cwd).scope_id;
738
+ const candidateStates = ["pending", "distilled", "failed", "discarded"];
739
+ const nodes = this.nodeRepo.listByScope(scopeId);
740
+ const explicitLowReadiness = Boolean(filters.nodeId || filters.risk || filters.state || filters.deliveryState);
741
+ const candidateNodes = nodes
742
+ .filter((node) => !filters.nodeId || node.id === filters.nodeId)
743
+ .filter((node) => !filters.nodeType || node.node_type === filters.nodeType)
744
+ .filter((node) => !filters.taskFamily || node.task_type === filters.taskFamily)
745
+ .filter((node) => !filters.state || node.state === filters.state)
746
+ .filter((node) => !filters.deliveryState || node.delivery_state === filters.deliveryState)
747
+ .filter((node) => explicitLowReadiness || (node.state === "active" && (!node.delivery_state || node.delivery_state === "eligible") && node.harmed_count <= node.helped_count));
748
+ const candidates = candidateStates
749
+ .flatMap((state) => this.candidateRepo.listByLifecycleState(state))
750
+ .filter((candidate) => candidate.scope_id === scopeId);
751
+ const attributionRecordsById = new Map();
752
+ for (const record of this.attributionRecordRepo.listRecentByScope(scopeId, Math.max(50, filters.limit ?? 20))) {
753
+ attributionRecordsById.set(record.id, record);
754
+ }
755
+ for (const node of candidateNodes) {
756
+ for (const record of this.attributionRecordRepo.listByNodeId(node.id)) {
757
+ attributionRecordsById.set(record.id, record);
758
+ }
759
+ }
760
+ const attributionRecords = [...attributionRecordsById.values()];
761
+ const hygiene = buildHygieneReviewReport({
762
+ nodes,
763
+ candidates,
764
+ attributionRecords,
765
+ filters: {
766
+ scopeId,
767
+ limit: Math.max(50, filters.limit ?? 20)
768
+ }
769
+ });
770
+ return buildExperienceExportDraftReport({
771
+ nodes,
772
+ candidates,
773
+ attributionRecords,
774
+ hygieneFindings: hygiene.findings,
775
+ filters: {
776
+ ...filters,
777
+ scopeId
778
+ }
779
+ });
780
+ }
781
+ inspectReview(cwd = process.cwd(), filters = {}) {
782
+ const scopeId = filters.scopeId ?? resolveScope(cwd).scope_id;
783
+ const limit = filters.limit ?? 5;
784
+ const repo = this.inspectRepoSummary(cwd);
785
+ const hygiene = this.inspectHygiene(cwd, { scopeId, limit });
786
+ const exportDrafts = this.inspectExportDrafts(cwd, { scopeId, limit });
787
+ return buildOperatorReviewFlow({
788
+ repo,
789
+ hygiene,
790
+ exportDrafts,
791
+ limit
792
+ });
793
+ }
664
794
  buildLearningSummary(scopeId) {
665
795
  const candidateStates = ["pending", "distilled", "failed", "discarded"];
666
796
  const jobStates = ["pending", "processing", "succeeded", "failed", "discarded"];
@@ -724,6 +854,7 @@ export class ExperienceInteractionService {
724
854
  const latestRecord = this.inputRepo.getLatestByScope(scope.scope_id);
725
855
  const latest = latestRecord ? this.inspectRecord(latestRecord) : undefined;
726
856
  const learning = this.buildLearningSummary(scope.scope_id);
857
+ const policyInspection = this.inspectRepoPolicy(cwd);
727
858
  return buildRepoSummary({
728
859
  scope: {
729
860
  scopeId: scope.scope_id,
@@ -731,9 +862,20 @@ export class ExperienceInteractionService {
731
862
  rootPath: scope.root_path
732
863
  },
733
864
  latest: latest && latest.scopeId === scope.scope_id ? latest : undefined,
734
- learning
865
+ learning,
866
+ policyInspection
735
867
  });
736
868
  }
869
+ inspectRepoPolicy(cwd = process.cwd()) {
870
+ const scope = resolveScope(cwd);
871
+ const policy = this.repoPolicyRepo.get(scope.scope_id) ??
872
+ buildDefaultRepoPolicy(scope.scope_id, this.config.repoExperienceMode);
873
+ return inspectRepoPolicyEvidence(policy, this.attributionRecordRepo.listRecentEligibleByScope(scope.scope_id), this.injectionRepo.listRecentResolvedByScope(scope.scope_id));
874
+ }
875
+ restoreRepoPolicy(cwd = process.cwd()) {
876
+ const scope = resolveScope(cwd);
877
+ return this.repoPolicyRepo.restore(scope.scope_id, this.config.repoExperienceMode);
878
+ }
737
879
  inspectFirstValueReadiness(cwd = process.cwd()) {
738
880
  const scope = resolveScope(cwd);
739
881
  const summary = this.buildLearningSummary(scope.scope_id);
@@ -869,9 +1011,25 @@ export class ExperienceInteractionService {
869
1011
  const taskRunId = record.session_id
870
1012
  ? this.taskRunRepo.getLatestBySessionId(record.session_id)?.id
871
1013
  : undefined;
1014
+ const episodeId = record.episode_id;
1015
+ const injectionEvent = record.session_id
1016
+ ? this.injectionRepo.getLatestBySessionId(record.session_id)
1017
+ : undefined;
1018
+ const evidenceRefs = [
1019
+ record.record_id,
1020
+ taskRunId,
1021
+ injectionEvent?.injection_id
1022
+ ].filter((value) => Boolean(value));
872
1023
  for (const node of nodes) {
873
1024
  this.nodeRepo.upsert(applyGovernedNodeFeedback(node, feedback, this.deriveOriginProfile(node)));
874
- this.reviewEventRepo.upsert(toReviewEvent(node.id, feedback === "helped" ? "mark_helped" : "mark_harmed", "user", taskRunId));
1025
+ this.reviewEventRepo.upsert(toReviewEvent(node.id, feedback === "helped" ? "mark_helped" : "mark_harmed", "user", taskRunId, episodeId));
1026
+ this.attributionRecordRepo.insert(toManualOverrideAttributionRecord({
1027
+ nodeId: node.id,
1028
+ feedback,
1029
+ injectionEvent,
1030
+ episodeId,
1031
+ evidenceRefs
1032
+ }));
875
1033
  }
876
1034
  return {
877
1035
  status: "updated",
@@ -890,6 +1048,11 @@ export class ExperienceInteractionService {
890
1048
  }
891
1049
  this.nodeRepo.upsert(applyGovernedNodeFeedback(node, feedback, this.deriveOriginProfile(node)));
892
1050
  this.reviewEventRepo.upsert(toReviewEvent(nodeId, feedback === "helped" ? "mark_helped" : "mark_harmed", "user"));
1051
+ this.attributionRecordRepo.insert(toManualOverrideAttributionRecord({
1052
+ nodeId,
1053
+ feedback,
1054
+ evidenceRefs: [`manual:${nodeId}`]
1055
+ }));
893
1056
  return {
894
1057
  status: "updated",
895
1058
  feedback,