claude-smart 0.2.48 → 0.2.50

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 (193) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/README.md +11 -40
  3. package/bin/claude-smart.js +393 -55
  4. package/package.json +3 -2
  5. package/plugin/.claude-plugin/plugin.json +1 -1
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/.coverage +0 -0
  8. package/plugin/README.md +4 -3
  9. package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +4 -2
  10. package/plugin/dashboard/app/dashboard/page.tsx +6 -1
  11. package/plugin/dashboard/app/preferences/[id]/page.tsx +18 -6
  12. package/plugin/dashboard/app/preferences/page.tsx +32 -35
  13. package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +16 -1
  14. package/plugin/dashboard/app/sessions/page.tsx +2 -0
  15. package/plugin/dashboard/app/skills/page.tsx +65 -50
  16. package/plugin/dashboard/app/skills/project/[id]/page.tsx +17 -8
  17. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +1 -6
  18. package/plugin/dashboard/components/common/host-badge.tsx +118 -0
  19. package/plugin/dashboard/components/common/learning-application-badge.tsx +34 -0
  20. package/plugin/dashboard/components/common/learnings-badge.tsx +1 -1
  21. package/plugin/dashboard/components/common/page-header.tsx +3 -3
  22. package/plugin/dashboard/lib/config-file.ts +5 -1
  23. package/plugin/dashboard/lib/host-attribution.ts +62 -0
  24. package/plugin/dashboard/lib/session-reader.ts +40 -2
  25. package/plugin/dashboard/lib/types.ts +7 -1
  26. package/plugin/opencode/dist/server.mjs +60 -2
  27. package/plugin/opencode/server.mts +64 -2
  28. package/plugin/pyproject.toml +2 -2
  29. package/plugin/scripts/_lib.sh +255 -7
  30. package/plugin/scripts/backend-python-runner.py +46 -0
  31. package/plugin/scripts/backend-service.sh +766 -123
  32. package/plugin/scripts/codex-hook.js +79 -229
  33. package/plugin/scripts/dashboard-open.sh +6 -4
  34. package/plugin/scripts/dashboard-service.sh +118 -137
  35. package/plugin/scripts/ensure-plugin-root.sh +106 -8
  36. package/plugin/scripts/hook_entry.sh +3 -0
  37. package/plugin/scripts/opencode-claude-compat.js +8 -1
  38. package/plugin/scripts/smart-install.sh +116 -21
  39. package/plugin/src/README.md +1 -1
  40. package/plugin/src/claude_smart/cli.py +93 -29
  41. package/plugin/src/claude_smart/context_inject.py +3 -0
  42. package/plugin/src/claude_smart/env_config.py +56 -11
  43. package/plugin/src/claude_smart/events/post_tool.py +2 -1
  44. package/plugin/src/claude_smart/events/session_end.py +2 -1
  45. package/plugin/src/claude_smart/events/stop.py +3 -0
  46. package/plugin/src/claude_smart/events/user_prompt.py +2 -1
  47. package/plugin/src/claude_smart/internal_call.py +5 -2
  48. package/plugin/src/claude_smart/optimizer_assistant.py +59 -13
  49. package/plugin/src/claude_smart/publish.py +59 -7
  50. package/plugin/src/claude_smart/reflexio_adapter.py +137 -14
  51. package/plugin/src/claude_smart/runtime.py +15 -6
  52. package/plugin/src/claude_smart/state.py +211 -52
  53. package/plugin/uv.lock +5 -5
  54. package/plugin/vendor/reflexio/.env.example +13 -0
  55. package/plugin/vendor/reflexio/reflexio/README.md +7 -3
  56. package/plugin/vendor/reflexio/reflexio/__init__.py +12 -0
  57. package/plugin/vendor/reflexio/reflexio/cli/README.md +3 -0
  58. package/plugin/vendor/reflexio/reflexio/client/client.py +166 -9
  59. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +10 -3
  60. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +28 -0
  61. package/plugin/vendor/reflexio/reflexio/lib/_search.py +41 -0
  62. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +195 -25
  63. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +7 -1
  64. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +2 -1
  65. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +63 -4
  66. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +1 -0
  67. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +8 -1
  68. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +1 -1
  69. package/plugin/vendor/reflexio/reflexio/server/README.md +22 -4
  70. package/plugin/vendor/reflexio/reflexio/server/__init__.py +18 -8
  71. package/plugin/vendor/reflexio/reflexio/server/__main__.py +6 -0
  72. package/plugin/vendor/reflexio/reflexio/server/api.py +79 -5
  73. package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +263 -3
  74. package/plugin/vendor/reflexio/reflexio/server/callback_executor.py +164 -0
  75. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +81 -81
  76. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +28 -0
  77. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +63 -6
  78. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -52
  79. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +1 -0
  80. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +28 -0
  81. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +9 -1
  82. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedder_warmup.py +329 -0
  83. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +85 -10
  84. package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +199 -2
  85. package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +77 -9
  86. package/plugin/vendor/reflexio/reflexio/server/org_fanout.py +184 -0
  87. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +1 -1
  88. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.1.0.prompt.md +32 -0
  89. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +1 -1
  90. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.4.0.prompt.md +63 -0
  91. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +1 -1
  92. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v2.0.0.prompt.md +30 -0
  93. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_impact/v1.0.0.prompt.md +51 -0
  94. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_relevance/v1.0.0.prompt.md +39 -0
  95. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +1 -1
  96. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.1.0.prompt.md +43 -0
  97. package/plugin/vendor/reflexio/reflexio/server/routes/config.py +3 -3
  98. package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +122 -28
  99. package/plugin/vendor/reflexio/reflexio/server/routes/interactions.py +63 -2
  100. package/plugin/vendor/reflexio/reflexio/server/routes/system.py +22 -3
  101. package/plugin/vendor/reflexio/reflexio/server/scheduling.py +64 -3
  102. package/plugin/vendor/reflexio/reflexio/server/services/README.md +6 -4
  103. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +3 -2
  104. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +41 -0
  105. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/retrieved_learning_evaluator.py +554 -0
  106. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +35 -1
  107. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/runner.py +253 -101
  108. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/scheduler.py +5 -7
  109. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/service.py +27 -0
  110. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +11 -5
  111. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +4 -4
  112. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +6 -2
  113. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +255 -74
  114. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +72 -0
  115. package/plugin/vendor/reflexio/reflexio/server/services/deferred_learning_plan.py +270 -0
  116. package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/__init__.py +13 -0
  117. package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/scheduler.py +142 -0
  118. package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/worker.py +262 -0
  119. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/hero_state.py +2 -11
  120. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/rule_attribution.py +6 -2
  121. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +17 -13
  122. package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +18 -3
  123. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +12 -1
  124. package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +1 -1
  125. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +2 -1
  126. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +1 -3
  127. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +66 -0
  128. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +640 -80
  129. package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +2 -0
  130. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +179 -99
  131. package/plugin/vendor/reflexio/reflexio/server/services/lineage/vector_backfill_sweep.py +139 -0
  132. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +66 -27
  133. package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_trigger.py +177 -0
  134. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +68 -2
  135. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +360 -49
  136. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/extractor.py +27 -30
  137. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +8 -1
  138. package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +137 -69
  139. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +20 -1
  140. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +13 -6
  141. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +40 -25
  142. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/consolidator.py +12 -39
  143. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +30 -35
  144. package/plugin/vendor/reflexio/reflexio/server/services/profile/service.py +166 -66
  145. package/plugin/vendor/reflexio/reflexio/server/services/reflection/service.py +457 -107
  146. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/session_dedup.py +127 -0
  147. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/temporal.py +104 -0
  148. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/dispatcher.py +139 -0
  149. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +16 -6
  150. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/worker.py +137 -0
  151. package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +12 -0
  152. package/plugin/vendor/reflexio/reflexio/server/services/storage/lifecycle_filters.py +54 -0
  153. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +41 -3
  154. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +40 -2
  155. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +2 -0
  156. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +190 -2
  157. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +11 -4
  158. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +28 -1
  159. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_learning_jobs.py +414 -0
  160. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +11 -5
  161. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +8 -3
  162. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +4 -0
  163. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +16 -5
  164. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_fts_vec.py +86 -2
  165. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +104 -42
  166. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +7 -1
  167. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +59 -7
  168. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +430 -5
  169. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +8 -3
  170. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +57 -12
  171. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +197 -12
  172. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +70 -11
  173. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +17 -0
  174. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_commit_scope.py +9 -0
  175. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +9 -2
  176. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_learning_jobs.py +269 -0
  177. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +7 -0
  178. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +3 -1
  179. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +4 -0
  180. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +12 -1
  181. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/evaluation_state_keys.py +78 -0
  182. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +38 -0
  183. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +171 -0
  184. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +52 -1
  185. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +82 -1
  186. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_profile_store.py +74 -2
  187. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/retrieved_learning_state.py +226 -0
  188. package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +5 -6
  189. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +209 -29
  190. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +3 -0
  191. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -0
  192. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +33 -0
  193. package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.48",
3
+ "version": "0.2.50",
4
4
  "description": "Self-improving Claude Code, Codex, and OpenCode plugin that turns corrections into Preferences, Project-specific skills, and Shared skills",
5
5
  "keywords": [
6
6
  "claude",
@@ -40,7 +40,8 @@
40
40
  },
41
41
  "scripts": {
42
42
  "build:opencode": "tsc -p plugin/opencode/tsconfig.json",
43
- "prepack": "npm run build:opencode"
43
+ "prepack": "npm run gen:marketplace && npm run build:opencode",
44
+ "gen:marketplace": "node scripts/generate-marketplace.js"
44
45
  },
45
46
  "bin": {
46
47
  "claude-smart": "bin/claude-smart.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.48",
3
+ "version": "0.2.50",
4
4
  "description": "Self-improving Claude Code, Codex, and OpenCode plugin that turns corrections into Preferences, Project-specific skills, and Shared skills",
5
5
  "author": {
6
6
  "name": "Yi Lu"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.48",
3
+ "version": "0.2.50",
4
4
  "description": "Self-improving coding assistant plugin — learns from corrections across sessions via reflexio",
5
5
  "author": {
6
6
  "name": "Yi Lu"
Binary file
package/plugin/README.md CHANGED
@@ -9,13 +9,14 @@ This directory is the Claude Code/Codex/OpenCode plugin payload shipped through
9
9
  ### Claude Code
10
10
 
11
11
  ```bash
12
- claude plugin marketplace add ReflexioAI/claude-smart
13
- claude plugin install claude-smart@reflexioai
12
+ npx claude-smart install
14
13
  ```
15
14
 
16
15
  The Setup hook bootstraps `uv`, Python 3.12, and a private Node.js/npm runtime
17
16
  under `~/.claude-smart/` when they are missing. If Node.js is already installed,
18
- `npx claude-smart install` is equivalent.
17
+ the npm installer also supplies the generated Reflexio runtime bundle that is
18
+ not committed to the GitHub repository. Installing this repository directly as
19
+ a Claude Code marketplace is therefore unsupported.
19
20
 
20
21
  Add `--read-only` to either install command to skip hooks that publish
21
22
  interactions for learning.
@@ -4,7 +4,9 @@ import { readConfig } from "@/lib/config-file";
4
4
 
5
5
  export const dynamic = "force-dynamic";
6
6
 
7
- const DEFAULT_URL = "http://localhost:8071";
7
+ function defaultUrl(): string {
8
+ return `http://localhost:${process.env.BACKEND_PORT || "8071"}`;
9
+ }
8
10
 
9
11
  async function reflexioConfig(): Promise<{ base: string; apiKey: string }> {
10
12
  const config = await readConfig();
@@ -13,7 +15,7 @@ async function reflexioConfig(): Promise<{ base: string; apiKey: string }> {
13
15
  const fromConfig = originOnly(config.REFLEXIO_URL ?? "");
14
16
  const configuredBase = fromEnv ?? fromConfig;
15
17
  return {
16
- base: configuredBase ?? DEFAULT_URL,
18
+ base: configuredBase ?? defaultUrl(),
17
19
  apiKey: configuredBase ? apiKey : "",
18
20
  };
19
21
  }
@@ -11,6 +11,7 @@ import {
11
11
  Users,
12
12
  } from "lucide-react";
13
13
  import { LearningsBadge } from "@/components/common/learnings-badge";
14
+ import { HostBadge } from "@/components/common/host-badge";
14
15
  import { PageHeader } from "@/components/common/page-header";
15
16
  import { StatCard } from "@/components/common/stat-card";
16
17
  import { EmptyState } from "@/components/common/empty-state";
@@ -217,7 +218,11 @@ export default function DashboardPage() {
217
218
  <code className="font-mono text-xs truncate">
218
219
  {truncateId(s.session_id, 10, 6)}
219
220
  </code>
220
- <LearningsBadge count={s.learning_interaction_count} />
221
+ <LearningsBadge
222
+ count={s.learning_interaction_count}
223
+ size="sm"
224
+ />
225
+ <HostBadge host={s.host} size="sm" />
221
226
  </div>
222
227
  <div className="flex w-full items-center justify-end gap-4 text-xs text-muted-foreground sm:w-auto sm:shrink-0">
223
228
  <span>{s.turn_count} turns</span>
@@ -21,6 +21,7 @@ import {
21
21
  FolderGit2,
22
22
  } from "lucide-react";
23
23
  import { PageHeader } from "@/components/common/page-header";
24
+ import { LearningHostProvenance } from "@/components/common/host-badge";
24
25
  import { EmptyState } from "@/components/common/empty-state";
25
26
  import { DeleteLearningDangerZone } from "@/components/common/delete-learning-danger-zone";
26
27
  import { Button } from "@/components/ui/button";
@@ -29,6 +30,7 @@ import { Badge } from "@/components/ui/badge";
29
30
  import { Separator } from "@/components/ui/separator";
30
31
  import { reflexio } from "@/lib/reflexio-client";
31
32
  import { formatTimestamp, truncateId } from "@/lib/format";
33
+ import { useRequestHostAttribution } from "@/lib/host-attribution";
32
34
  import { cn } from "@/lib/utils";
33
35
  import { statusLabel as status } from "@/lib/status";
34
36
  import type { UserProfile } from "@/lib/types";
@@ -49,6 +51,7 @@ export default function PreferenceDetailPage({
49
51
  const [deleting, setDeleting] = useState(false);
50
52
  const [editing, setEditing] = useState(false);
51
53
  const [content, setContent] = useState("");
54
+ const attribution = useRequestHostAttribution();
52
55
 
53
56
  useEffect(() => {
54
57
  let cancelled = false;
@@ -205,11 +208,6 @@ export default function PreferenceDetailPage({
205
208
  <FolderGit2 className="h-3 w-3" />
206
209
  {truncateId(profile.user_id, 32, 8)}
207
210
  </Badge>
208
- {profile.source && (
209
- <Badge variant="secondary" className="font-mono text-[10px]">
210
- {profile.source}
211
- </Badge>
212
- )}
213
211
  {dirty && (
214
212
  <Badge variant="destructive" className="gap-1.5">
215
213
  unsaved changes
@@ -343,6 +341,20 @@ export default function PreferenceDetailPage({
343
341
  display={truncateId(profile.generated_from_request_id, 8, 4)}
344
342
  />
345
343
  )}
344
+ {attribution && profile.generated_from_request_id && (
345
+ <Meta
346
+ label="Origin"
347
+ value={
348
+ <LearningHostProvenance
349
+ attribution={attribution}
350
+ requestId={profile.generated_from_request_id}
351
+ />
352
+ }
353
+ />
354
+ )}
355
+ {profile.source && (
356
+ <Meta label="Integration" value={profile.source} mono />
357
+ )}
346
358
  </dl>
347
359
  </div>
348
360
  </aside>
@@ -428,7 +440,7 @@ function Meta({
428
440
  }: {
429
441
  icon?: React.ComponentType<{ className?: string }>;
430
442
  label: string;
431
- value: string;
443
+ value: React.ReactNode;
432
444
  mono?: boolean;
433
445
  }) {
434
446
  return (
@@ -2,8 +2,10 @@
2
2
 
3
3
  import { useEffect, useMemo, useState } from "react";
4
4
  import Link from "next/link";
5
- import { Users, ChevronRight } from "lucide-react";
5
+ import { AlertTriangle, Users, ChevronRight } from "lucide-react";
6
6
  import { PageHeader } from "@/components/common/page-header";
7
+ import { HostBadge } from "@/components/common/host-badge";
8
+ import { LearningApplicationBadge } from "@/components/common/learning-application-badge";
7
9
  import { EmptyState } from "@/components/common/empty-state";
8
10
  import { DeleteAllButton } from "@/components/common/delete-all-button";
9
11
  import { Badge } from "@/components/ui/badge";
@@ -17,6 +19,7 @@ import {
17
19
  } from "@/components/ui/select";
18
20
  import { reflexio } from "@/lib/reflexio-client";
19
21
  import { formatRelative, truncateId } from "@/lib/format";
22
+ import { useRequestHostAttribution } from "@/lib/host-attribution";
20
23
  import type { PlaybookApplicationStat, UserProfile } from "@/lib/types";
21
24
 
22
25
  type PreferenceSort = "newest" | "applied";
@@ -33,6 +36,7 @@ export default function PreferencesPage() {
33
36
  const [error, setError] = useState<string | null>(null);
34
37
  const [sortBy, setSortBy] = useState<PreferenceSort>("newest");
35
38
  const [filter, setFilter] = useState("");
39
+ const attribution = useRequestHostAttribution();
36
40
 
37
41
  useEffect(() => {
38
42
  let cancelled = false;
@@ -141,6 +145,13 @@ export default function PreferencesPage() {
141
145
  {error}. Is reflexio running on the configured backend URL?
142
146
  </div>
143
147
  )}
148
+ {attribution?.unavailable && !error && (
149
+ <div className="mb-4 flex items-center gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-800 dark:text-amber-200">
150
+ <AlertTriangle className="h-4 w-4 shrink-0" />
151
+ Preference source details are temporarily unavailable. Preferences
152
+ remain available.
153
+ </div>
154
+ )}
144
155
 
145
156
  {profiles === null && !error ? (
146
157
  <div className="text-sm text-muted-foreground">Loading…</div>
@@ -162,6 +173,7 @@ export default function PreferencesPage() {
162
173
  >
163
174
  <header className="mb-2 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
164
175
  <div className="flex min-w-0 flex-wrap items-center gap-2">
176
+ <LearningApplicationBadge stat={stat} />
165
177
  <Badge variant="outline" className="h-5 font-mono text-[10px]">
166
178
  {truncateId(p.user_id, 32, 8)}
167
179
  </Badge>
@@ -170,7 +182,6 @@ export default function PreferencesPage() {
170
182
  {p.status}
171
183
  </Badge>
172
184
  )}
173
- <PreferenceApplicationStatBadge stat={stat} />
174
185
  </div>
175
186
  <div className="flex items-center gap-1.5 shrink-0">
176
187
  <span className="text-[11px] text-muted-foreground">
@@ -182,11 +193,25 @@ export default function PreferencesPage() {
182
193
  <p className="max-w-5xl text-sm leading-relaxed line-clamp-3">
183
194
  {p.content}
184
195
  </p>
185
- {p.source && (
186
- <p className="text-[11px] text-muted-foreground mt-2 font-mono">
187
- source: {p.source}
188
- </p>
189
- )}
196
+ <div className="mt-2 flex flex-wrap items-center gap-3">
197
+ {attribution &&
198
+ !attribution.unavailable &&
199
+ p.generated_from_request_id && (
200
+ <HostBadge
201
+ host={
202
+ attribution.hosts.get(
203
+ p.generated_from_request_id,
204
+ ) ?? null
205
+ }
206
+ display="provenance"
207
+ />
208
+ )}
209
+ {p.source && (
210
+ <span className="text-[11px] text-muted-foreground font-mono">
211
+ Integration: {p.source}
212
+ </span>
213
+ )}
214
+ </div>
190
215
  </Link>
191
216
  );
192
217
  })}
@@ -196,31 +221,3 @@ export default function PreferencesPage() {
196
221
  </div>
197
222
  );
198
223
  }
199
-
200
- function PreferenceApplicationStatBadge({
201
- stat,
202
- }: {
203
- stat: PlaybookApplicationStat | undefined;
204
- }) {
205
- if (!stat || stat.applied_count === 0) {
206
- return (
207
- <Badge
208
- variant="outline"
209
- className="h-5 text-[10px] text-muted-foreground"
210
- title="No citations recorded yet for this preference. It will count once an assistant reply cites it."
211
- >
212
- Never applied
213
- </Badge>
214
- );
215
- }
216
- const last = formatRelative(stat.last_applied_at);
217
- return (
218
- <Badge
219
- variant="secondary"
220
- className="h-5 text-[10px]"
221
- title={`Last applied ${last}`}
222
- >
223
- Applied {stat.applied_count}×{stat.last_applied_at ? ` · ${last}` : ""}
224
- </Badge>
225
- );
226
- }
@@ -16,6 +16,8 @@ import {
16
16
  Sparkles,
17
17
  } from "lucide-react";
18
18
  import { PageHeader } from "@/components/common/page-header";
19
+ import { HostBadge } from "@/components/common/host-badge";
20
+ import { LearningsBadge } from "@/components/common/learnings-badge";
19
21
  import { EmptyState } from "@/components/common/empty-state";
20
22
  import { Badge } from "@/components/ui/badge";
21
23
  import { Button } from "@/components/ui/button";
@@ -75,7 +77,20 @@ export default function InteractionDetailPage({
75
77
  <div className="flex-1 overflow-auto">
76
78
  <PageHeader
77
79
  title="Session transcript"
78
- description={sessionId}
80
+ description={
81
+ <span className="flex flex-wrap items-center gap-2">
82
+ <span>{sessionId}</span>
83
+ {detail && (
84
+ <>
85
+ <LearningsBadge
86
+ count={detail.learning_interaction_count}
87
+ size="sm"
88
+ />
89
+ <HostBadge host={detail.host} size="sm" />
90
+ </>
91
+ )}
92
+ </span>
93
+ }
79
94
  actions={
80
95
  <div className="flex items-center gap-2">
81
96
  <Link href="/sessions">
@@ -7,6 +7,7 @@ import { PageHeader } from "@/components/common/page-header";
7
7
  import { EmptyState } from "@/components/common/empty-state";
8
8
  import { DeleteAllButton } from "@/components/common/delete-all-button";
9
9
  import { LearningsBadge } from "@/components/common/learnings-badge";
10
+ import { HostBadge } from "@/components/common/host-badge";
10
11
  import { Input } from "@/components/ui/input";
11
12
  import { dayBucket, formatRelative, truncateId } from "@/lib/format";
12
13
  import type { SessionSummary } from "@/lib/types";
@@ -153,6 +154,7 @@ export default function SessionsPage() {
153
154
  count={s.learning_interaction_count}
154
155
  size="sm"
155
156
  />
157
+ <HostBadge host={s.host} size="sm" />
156
158
  </div>
157
159
  <div className="flex items-center gap-3 text-[11px] text-muted-foreground mt-0.5">
158
160
  <code className="font-mono">
@@ -2,8 +2,15 @@
2
2
 
3
3
  import { useEffect, useMemo, useState } from "react";
4
4
  import Link from "next/link";
5
- import { BookOpen, ChevronRight, Layers3 } from "lucide-react";
5
+ import {
6
+ AlertTriangle,
7
+ BookOpen,
8
+ ChevronRight,
9
+ Layers3,
10
+ } from "lucide-react";
6
11
  import { PageHeader } from "@/components/common/page-header";
12
+ import { HostBadge } from "@/components/common/host-badge";
13
+ import { LearningApplicationBadge } from "@/components/common/learning-application-badge";
7
14
  import { EmptyState } from "@/components/common/empty-state";
8
15
  import { DeleteAllButton } from "@/components/common/delete-all-button";
9
16
  import { PageTabs } from "@/components/common/page-tabs";
@@ -18,6 +25,7 @@ import {
18
25
  } from "@/components/ui/select";
19
26
  import { reflexio } from "@/lib/reflexio-client";
20
27
  import { formatRelative } from "@/lib/format";
28
+ import { useRequestHostAttribution } from "@/lib/host-attribution";
21
29
  import { cn } from "@/lib/utils";
22
30
  import {
23
31
  agentPlaybookStatusLabel,
@@ -27,6 +35,7 @@ import {
27
35
  } from "@/lib/status";
28
36
  import type {
29
37
  AgentPlaybook,
38
+ Host,
30
39
  PlaybookApplicationStat,
31
40
  UserPlaybook,
32
41
  } from "@/lib/types";
@@ -58,24 +67,29 @@ const SHARED_STATUS_META: Record<
58
67
  interface SkillCard {
59
68
  kind: SkillKind;
60
69
  id: number;
61
- agentVersion: string;
62
70
  createdAt: number;
63
71
  content: string;
64
72
  trigger: string | null;
65
73
  rationale: string | null;
66
74
  status: SkillStatus;
75
+ scopeId: string;
76
+ host: Host | null;
67
77
  }
68
78
 
69
- function projectSkill(p: UserPlaybook): SkillCard {
79
+ function projectSkill(
80
+ p: UserPlaybook,
81
+ requestHosts?: ReadonlyMap<string, Host | null>,
82
+ ): SkillCard {
70
83
  return {
71
84
  kind: "project",
72
85
  id: p.user_playbook_id,
73
- agentVersion: p.agent_version || "default",
74
86
  createdAt: p.created_at,
75
87
  content: p.content,
76
88
  trigger: p.trigger,
77
89
  rationale: p.rationale,
78
90
  status: statusLabel(p),
91
+ scopeId: p.user_id || "unknown",
92
+ host: requestHosts?.get(p.request_id) ?? null,
79
93
  };
80
94
  }
81
95
 
@@ -83,12 +97,13 @@ function sharedSkill(p: AgentPlaybook): SkillCard {
83
97
  return {
84
98
  kind: "shared",
85
99
  id: p.agent_playbook_id,
86
- agentVersion: p.agent_version || "default",
87
100
  createdAt: p.created_at,
88
101
  content: p.content,
89
102
  trigger: p.trigger,
90
103
  rationale: p.rationale,
91
104
  status: agentPlaybookStatusLabel(p),
105
+ scopeId: p.agent_version || "default",
106
+ host: null,
92
107
  };
93
108
  }
94
109
 
@@ -106,10 +121,11 @@ export default function SkillsPage() {
106
121
  );
107
122
  const [error, setError] = useState<string | null>(null);
108
123
  const [activeKind, setActiveKind] = useState<SkillKind>("project");
109
- const [agentVersion, setAgentVersion] = useState<string>("__all__");
124
+ const [scope, setScope] = useState<string>("__all__");
110
125
  const [statusFilter, setStatusFilter] = useState<string>("CURRENT");
111
126
  const [sortBy, setSortBy] = useState<SkillSort>("newest");
112
127
  const [search, setSearch] = useState("");
128
+ const attribution = useRequestHostAttribution();
113
129
 
114
130
  useEffect(() => {
115
131
  let cancelled = false;
@@ -158,24 +174,26 @@ export default function SkillsPage() {
158
174
 
159
175
  const activeSkills = useMemo(() => {
160
176
  return activeKind === "project"
161
- ? (projectSkills ?? []).map(projectSkill)
177
+ ? (projectSkills ?? []).map((playbook) =>
178
+ projectSkill(playbook, attribution?.hosts),
179
+ )
162
180
  : (sharedSkills ?? []).map(sharedSkill);
163
- }, [activeKind, projectSkills, sharedSkills]);
181
+ }, [activeKind, attribution, projectSkills, sharedSkills]);
164
182
 
165
- const projects = useMemo(() => {
183
+ const scopes = useMemo(() => {
166
184
  const set = new Set<string>();
167
- for (const p of activeSkills) set.add(p.agentVersion);
185
+ for (const p of activeSkills) set.add(p.scopeId);
168
186
  return Array.from(set).sort();
169
187
  }, [activeSkills]);
170
188
 
171
189
  const filtered = useMemo(() => {
172
190
  const matches = activeSkills.filter((p) => {
173
- if (agentVersion !== "__all__" && p.agentVersion !== agentVersion)
191
+ if (scope !== "__all__" && p.scopeId !== scope)
174
192
  return false;
175
193
  if (statusFilter !== "__all__" && p.status !== statusFilter) return false;
176
194
  if (search) {
177
195
  const s = search.toLowerCase();
178
- const hay = `${p.content} ${p.trigger ?? ""} ${p.rationale ?? ""}`.toLowerCase();
196
+ const hay = `${p.scopeId} ${p.content} ${p.trigger ?? ""} ${p.rationale ?? ""}`.toLowerCase();
179
197
  if (!hay.includes(s)) return false;
180
198
  }
181
199
  return true;
@@ -193,7 +211,7 @@ export default function SkillsPage() {
193
211
  }
194
212
  return b.createdAt - a.createdAt;
195
213
  });
196
- }, [activeSkills, agentVersion, search, sortBy, statsByRule, statusFilter]);
214
+ }, [activeSkills, scope, search, sortBy, statsByRule, statusFilter]);
197
215
 
198
216
  const projectCount = projectSkills?.length ?? 0;
199
217
  const sharedCount = sharedSkills?.length ?? 0;
@@ -201,10 +219,12 @@ export default function SkillsPage() {
201
219
  const activeCount = activeKind === "project" ? projectCount : sharedCount;
202
220
  const loading = projectSkills === null || sharedSkills === null;
203
221
  const hasNoSharedSkills = activeKind === "shared" && sharedCount === 0;
222
+ const allScopesLabel =
223
+ activeKind === "project" ? "All user scopes" : "All agents";
204
224
 
205
225
  const switchKind = (kind: SkillKind) => {
206
226
  setActiveKind(kind);
207
- setAgentVersion("__all__");
227
+ setScope("__all__");
208
228
  setStatusFilter(kind === "project" ? "CURRENT" : "__all__");
209
229
  };
210
230
 
@@ -216,15 +236,17 @@ export default function SkillsPage() {
216
236
  actions={
217
237
  <div className="flex max-w-full flex-wrap items-center justify-end gap-2">
218
238
  <Select
219
- value={agentVersion}
220
- onValueChange={(v) => setAgentVersion(v ?? "__all__")}
239
+ value={scope}
240
+ onValueChange={(v) => setScope(v ?? "__all__")}
221
241
  >
222
242
  <SelectTrigger size="sm" className="w-40 text-xs bg-background/80">
223
- <SelectValue placeholder="Project" />
243
+ <SelectValue>
244
+ {scope === "__all__" ? allScopesLabel : scope}
245
+ </SelectValue>
224
246
  </SelectTrigger>
225
247
  <SelectContent>
226
- <SelectItem value="__all__">All projects</SelectItem>
227
- {projects.map((p) => (
248
+ <SelectItem value="__all__">{allScopesLabel}</SelectItem>
249
+ {scopes.map((p) => (
228
250
  <SelectItem key={p} value={p}>
229
251
  {p}
230
252
  </SelectItem>
@@ -337,6 +359,13 @@ export default function SkillsPage() {
337
359
  {error}. Is reflexio running on the configured backend URL?
338
360
  </div>
339
361
  )}
362
+ {activeKind === "project" && attribution?.unavailable && !error && (
363
+ <div className="mb-4 flex items-center gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-800 dark:text-amber-200">
364
+ <AlertTriangle className="h-4 w-4 shrink-0" />
365
+ Skill source details are temporarily unavailable. Skills remain
366
+ available.
367
+ </div>
368
+ )}
340
369
 
341
370
  {loading && !error ? (
342
371
  <div className="text-sm text-muted-foreground">Loading...</div>
@@ -366,17 +395,20 @@ export default function SkillsPage() {
366
395
  >
367
396
  <header className="mb-2 flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
368
397
  <div className="flex min-w-0 flex-wrap items-center gap-2">
369
- <Badge
370
- variant="outline"
371
- className="h-5 max-w-56 truncate font-mono text-[10px]"
372
- >
373
- {p.agentVersion}
374
- </Badge>
398
+ <LearningApplicationBadge stat={stat} />
375
399
  <StatusBadge kind={p.kind} status={p.status} />
376
400
  <Badge variant="secondary" className="h-5 text-[10px]">
377
401
  {p.kind === "project" ? "project-specific" : "shared"}
378
402
  </Badge>
379
- <ApplicationStatBadge stat={stat} />
403
+ {p.kind === "shared" && (
404
+ <Badge
405
+ variant="outline"
406
+ className="h-5 max-w-56 truncate font-mono text-[10px]"
407
+ title="Shared-skill applicability scope; not the source host."
408
+ >
409
+ Agent scope: {p.scopeId}
410
+ </Badge>
411
+ )}
380
412
  </div>
381
413
  <div className="flex shrink-0 items-center gap-1.5 pt-0.5">
382
414
  <span className="text-[11px] text-muted-foreground">
@@ -406,6 +438,13 @@ export default function SkillsPage() {
406
438
  {p.rationale}
407
439
  </p>
408
440
  )}
441
+ {p.kind === "project" &&
442
+ attribution &&
443
+ !attribution.unavailable && (
444
+ <div className="mt-2">
445
+ <HostBadge host={p.host} display="provenance" />
446
+ </div>
447
+ )}
409
448
  </Link>
410
449
  );
411
450
  })}
@@ -416,30 +455,6 @@ export default function SkillsPage() {
416
455
  );
417
456
  }
418
457
 
419
- function ApplicationStatBadge({ stat }: { stat: PlaybookApplicationStat | undefined }) {
420
- if (!stat || stat.applied_count === 0) {
421
- return (
422
- <Badge
423
- variant="outline"
424
- className="h-5 text-[10px] text-muted-foreground"
425
- title="No citations recorded yet for this rule. It will count once an assistant reply cites it."
426
- >
427
- Never applied
428
- </Badge>
429
- );
430
- }
431
- const last = formatRelative(stat.last_applied_at);
432
- return (
433
- <Badge
434
- variant="secondary"
435
- className="h-5 text-[10px]"
436
- title={`Last applied ${last}`}
437
- >
438
- Applied {stat.applied_count}×{stat.last_applied_at ? ` · ${last}` : ""}
439
- </Badge>
440
- );
441
- }
442
-
443
458
  function statusFilterLabel(kind: SkillKind, status: string): string {
444
459
  if (status === "__all__") return "All";
445
460
  if (kind === "shared") {
@@ -13,11 +13,11 @@ import {
13
13
  Check,
14
14
  BookMarked,
15
15
  Hash,
16
- FolderGit2,
17
16
  Clock,
18
17
  FileText,
19
18
  } from "lucide-react";
20
19
  import { PageHeader } from "@/components/common/page-header";
20
+ import { LearningHostProvenance } from "@/components/common/host-badge";
21
21
  import { EmptyState } from "@/components/common/empty-state";
22
22
  import { DeleteLearningDangerZone } from "@/components/common/delete-learning-danger-zone";
23
23
  import { Button } from "@/components/ui/button";
@@ -26,6 +26,7 @@ import { Badge } from "@/components/ui/badge";
26
26
  import { Separator } from "@/components/ui/separator";
27
27
  import { reflexio } from "@/lib/reflexio-client";
28
28
  import { formatTimestamp, truncateId } from "@/lib/format";
29
+ import { useRequestHostAttribution } from "@/lib/host-attribution";
29
30
  import { cn } from "@/lib/utils";
30
31
  import { statusLabel } from "@/lib/status";
31
32
  import type { StatusLabel } from "@/lib/status";
@@ -66,6 +67,7 @@ export default function ProjectSkillDetailPage({
66
67
  trigger: "",
67
68
  rationale: "",
68
69
  });
70
+ const attribution = useRequestHostAttribution();
69
71
 
70
72
  useEffect(() => {
71
73
  let cancelled = false;
@@ -229,10 +231,6 @@ export default function ProjectSkillDetailPage({
229
231
 
230
232
  {playbook && (
231
233
  <div className="flex items-center gap-2 flex-wrap">
232
- <Badge variant="outline" className="gap-1.5">
233
- <FolderGit2 className="h-3 w-3" />
234
- {playbook.agent_version || "default"}
235
- </Badge>
236
234
  <StatusBadge status={status!} />
237
235
  {displayName(playbook.playbook_name) && (
238
236
  <Badge variant="secondary" className="font-mono text-[10px]">
@@ -332,7 +330,7 @@ export default function ProjectSkillDetailPage({
332
330
  value={formatTimestamp(playbook.created_at)}
333
331
  />
334
332
  <Meta
335
- label="Project"
333
+ label="Agent version"
336
334
  value={playbook.agent_version || "default"}
337
335
  mono
338
336
  />
@@ -350,8 +348,19 @@ export default function ProjectSkillDetailPage({
350
348
  display={truncateId(playbook.request_id, 8, 4)}
351
349
  />
352
350
  )}
351
+ {attribution && playbook.request_id && (
352
+ <Meta
353
+ label="Origin"
354
+ value={
355
+ <LearningHostProvenance
356
+ attribution={attribution}
357
+ requestId={playbook.request_id}
358
+ />
359
+ }
360
+ />
361
+ )}
353
362
  {playbook.source && (
354
- <Meta label="Source" value={playbook.source} mono />
363
+ <Meta label="Integration" value={playbook.source} mono />
355
364
  )}
356
365
  </dl>
357
366
  </div>
@@ -499,7 +508,7 @@ function Meta({
499
508
  }: {
500
509
  icon?: React.ComponentType<{ className?: string }>;
501
510
  label: string;
502
- value: string;
511
+ value: React.ReactNode;
503
512
  mono?: boolean;
504
513
  }) {
505
514
  return (