claude-smart 0.2.49 → 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.
- package/.claude-plugin/marketplace.json +2 -2
- package/README.md +10 -43
- package/bin/claude-smart.js +105 -0
- package/package.json +3 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.coverage +0 -0
- package/plugin/README.md +4 -3
- package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +4 -2
- package/plugin/dashboard/app/dashboard/page.tsx +6 -1
- package/plugin/dashboard/app/preferences/[id]/page.tsx +18 -6
- package/plugin/dashboard/app/preferences/page.tsx +32 -35
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +16 -1
- package/plugin/dashboard/app/sessions/page.tsx +2 -0
- package/plugin/dashboard/app/skills/page.tsx +65 -50
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +17 -8
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +1 -6
- package/plugin/dashboard/components/common/host-badge.tsx +118 -0
- package/plugin/dashboard/components/common/learning-application-badge.tsx +34 -0
- package/plugin/dashboard/components/common/learnings-badge.tsx +1 -1
- package/plugin/dashboard/components/common/page-header.tsx +3 -3
- package/plugin/dashboard/lib/config-file.ts +5 -1
- package/plugin/dashboard/lib/host-attribution.ts +62 -0
- package/plugin/dashboard/lib/session-reader.ts +40 -2
- package/plugin/dashboard/lib/types.ts +7 -1
- package/plugin/pyproject.toml +1 -1
- package/plugin/scripts/_lib.sh +197 -1
- package/plugin/scripts/backend-python-runner.py +46 -0
- package/plugin/scripts/backend-service.sh +757 -119
- package/plugin/scripts/codex-hook.js +63 -225
- package/plugin/scripts/dashboard-open.sh +6 -4
- package/plugin/scripts/dashboard-service.sh +117 -136
- package/plugin/scripts/hook_entry.sh +3 -0
- package/plugin/scripts/smart-install.sh +15 -1
- package/plugin/src/claude_smart/cli.py +14 -0
- package/plugin/src/claude_smart/context_inject.py +3 -0
- package/plugin/src/claude_smart/env_config.py +4 -1
- package/plugin/src/claude_smart/events/post_tool.py +2 -1
- package/plugin/src/claude_smart/events/session_end.py +2 -1
- package/plugin/src/claude_smart/events/stop.py +3 -0
- package/plugin/src/claude_smart/events/user_prompt.py +2 -1
- package/plugin/src/claude_smart/internal_call.py +5 -2
- package/plugin/src/claude_smart/optimizer_assistant.py +59 -13
- package/plugin/src/claude_smart/publish.py +59 -7
- package/plugin/src/claude_smart/reflexio_adapter.py +137 -14
- package/plugin/src/claude_smart/runtime.py +15 -6
- package/plugin/src/claude_smart/state.py +211 -52
- package/plugin/uv.lock +1 -1
- package/plugin/vendor/reflexio/.env.example +13 -0
- package/plugin/vendor/reflexio/reflexio/README.md +7 -3
- package/plugin/vendor/reflexio/reflexio/__init__.py +12 -0
- package/plugin/vendor/reflexio/reflexio/client/client.py +126 -3
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +10 -3
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +28 -0
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +41 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +177 -25
- package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +7 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +2 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +63 -4
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +8 -1
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/README.md +22 -4
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +18 -8
- package/plugin/vendor/reflexio/reflexio/server/__main__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/api.py +66 -3
- package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +263 -3
- package/plugin/vendor/reflexio/reflexio/server/callback_executor.py +164 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +81 -81
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +28 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +62 -5
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -52
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +28 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +9 -1
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedder_warmup.py +329 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +85 -10
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +20 -5
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +77 -9
- package/plugin/vendor/reflexio/reflexio/server/org_fanout.py +184 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.1.0.prompt.md +32 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.4.0.prompt.md +63 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v2.0.0.prompt.md +30 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_impact/v1.0.0.prompt.md +51 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_relevance/v1.0.0.prompt.md +39 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.1.0.prompt.md +43 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/config.py +3 -3
- package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +122 -28
- package/plugin/vendor/reflexio/reflexio/server/routes/system.py +22 -3
- package/plugin/vendor/reflexio/reflexio/server/scheduling.py +64 -3
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +6 -4
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +41 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/retrieved_learning_evaluator.py +554 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +35 -1
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/runner.py +253 -101
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/scheduler.py +5 -7
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/service.py +27 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +11 -5
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +6 -2
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +255 -74
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +72 -0
- package/plugin/vendor/reflexio/reflexio/server/services/deferred_learning_plan.py +270 -0
- package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/worker.py +85 -16
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/hero_state.py +2 -11
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/rule_attribution.py +6 -2
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +17 -13
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +18 -3
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +2 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +66 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +510 -63
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +179 -99
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/vector_backfill_sweep.py +139 -0
- package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +66 -27
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_trigger.py +177 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +68 -2
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +360 -49
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/extractor.py +27 -30
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +8 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +137 -69
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +20 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +13 -6
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +40 -25
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/consolidator.py +12 -39
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +30 -35
- package/plugin/vendor/reflexio/reflexio/server/services/profile/service.py +122 -44
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/service.py +457 -107
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/session_dedup.py +127 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/temporal.py +104 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/dispatcher.py +139 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +16 -6
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/worker.py +137 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +12 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/lifecycle_filters.py +54 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +41 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +40 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +50 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +11 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +28 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_learning_jobs.py +36 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +16 -5
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +104 -42
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +7 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +43 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +430 -5
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +45 -7
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +146 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +42 -7
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +9 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_learning_jobs.py +50 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +3 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/evaluation_state_keys.py +78 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +171 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +52 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +59 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_profile_store.py +52 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/retrieved_learning_state.py +226 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +5 -6
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +209 -29
- package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +3 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +33 -0
- package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
"plugins": [
|
|
10
10
|
{
|
|
11
11
|
"name": "claude-smart",
|
|
12
|
-
"version": "0.2.49",
|
|
13
12
|
"source": "./plugin",
|
|
14
|
-
"description": "Turns corrections into Preferences, Project-specific skills, and Shared skills — uses Claude Code, Codex, or OpenCode as the LLM backend (no external API key required)"
|
|
13
|
+
"description": "Turns corrections into Preferences, Project-specific skills, and Shared skills — uses Claude Code, Codex, or OpenCode as the LLM backend (no external API key required)",
|
|
14
|
+
"version": "0.2.50"
|
|
15
15
|
}
|
|
16
16
|
]
|
|
17
17
|
}
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License">
|
|
14
14
|
</a>
|
|
15
15
|
<a href="plugin/pyproject.toml">
|
|
16
|
-
<img src="https://img.shields.io/badge/version-0.2.
|
|
16
|
+
<img src="https://img.shields.io/badge/version-0.2.50-green.svg" alt="Version">
|
|
17
17
|
</a>
|
|
18
18
|
<a href="plugin/pyproject.toml">
|
|
19
19
|
<img src="https://img.shields.io/badge/python-%3E%3D3.12-brightgreen.svg" alt="Python">
|
|
@@ -58,6 +58,8 @@
|
|
|
58
58
|
|
|
59
59
|
## Quick Start
|
|
60
60
|
|
|
61
|
+
Requires Node.js 20+ (for `npx`) and the host CLI you're installing into (`claude`, `codex`, or `opencode`) on `PATH`.
|
|
62
|
+
|
|
61
63
|
### Claude Code
|
|
62
64
|
|
|
63
65
|
```bash
|
|
@@ -66,27 +68,6 @@ npx claude-smart install
|
|
|
66
68
|
|
|
67
69
|
Then restart Claude Code.
|
|
68
70
|
|
|
69
|
-
Requires Node.js (for `npx`) to already exist.
|
|
70
|
-
|
|
71
|
-
Alternatively, install via Claude Code's plugin marketplace:
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
claude plugin marketplace add ReflexioAI/claude-smart
|
|
75
|
-
claude plugin install claude-smart@reflexioai
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
To uninstall:
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
npx claude-smart uninstall
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Or, if installed via the plugin marketplace:
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
claude plugin uninstall claude-smart@reflexioai
|
|
88
|
-
```
|
|
89
|
-
|
|
90
71
|
### Codex
|
|
91
72
|
|
|
92
73
|
```bash
|
|
@@ -95,39 +76,25 @@ npx claude-smart install --host codex
|
|
|
95
76
|
|
|
96
77
|
Then fully quit and reopen Codex so hooks reload.
|
|
97
78
|
|
|
98
|
-
Requires the `codex` CLI on `PATH` and Node.js (for `npx`).
|
|
99
|
-
|
|
100
|
-
To uninstall:
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
npx claude-smart uninstall --host codex
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Restart Codex after uninstalling. The uninstaller stops local claude-smart services and removes plugin/cache/config state; learned data under `~/.reflexio/` and `~/.claude-smart/` is preserved and shared with Claude Code, so you can switch between hosts without losing skills or preferences.
|
|
107
|
-
|
|
108
79
|
### OpenCode
|
|
109
80
|
|
|
110
81
|
```bash
|
|
111
82
|
npx claude-smart install --host opencode
|
|
112
83
|
```
|
|
113
84
|
|
|
114
|
-
Then restart OpenCode in your project so it loads the plugin from `opencode.json
|
|
115
|
-
|
|
116
|
-
The installer prepares claude-smart's runtime dependencies, but expects the OpenCode CLI to already be installed. It resolves and records the OpenCode executable in `~/.claude-smart/.env` for later backend restarts; set `CLAUDE_SMART_OPENCODE_PATH` before install if `opencode` is not on `PATH`.
|
|
117
|
-
|
|
118
|
-
OpenCode support is new and uses OpenCode's plugin loader to inject relevant learned context before each model request. Learning extraction runs `opencode run --pure` from an isolated temp project, so it uses OpenCode's default model unless you set `CLAUDE_SMART_OPENCODE_MODEL=provider/model`. Set that env var if your normal project config pins a different provider or model.
|
|
119
|
-
|
|
120
|
-
On native Windows, install Git for Windows and make `bash.exe` available on `PATH` before starting OpenCode, or run OpenCode from WSL. The installer also checks the Windows local embedding runtime and writes actionable setup failures to `~/.claude-smart/install-failed`.
|
|
85
|
+
Then restart OpenCode in your project so it loads the plugin from `opencode.json`. Add `--global` to install for all OpenCode projects on this machine instead.
|
|
121
86
|
|
|
122
|
-
|
|
87
|
+
### Uninstall
|
|
123
88
|
|
|
124
89
|
```bash
|
|
125
|
-
npx claude-smart uninstall
|
|
90
|
+
npx claude-smart uninstall # Claude Code
|
|
91
|
+
npx claude-smart uninstall --host codex # Codex
|
|
92
|
+
npx claude-smart uninstall --host opencode # OpenCode
|
|
126
93
|
```
|
|
127
94
|
|
|
128
|
-
Restart
|
|
95
|
+
Restart the host afterward. Learned data under `~/.reflexio/` and `~/.claude-smart/` is preserved and shared across hosts, so you can uninstall or switch hosts without losing skills or preferences.
|
|
129
96
|
|
|
130
|
-
|
|
97
|
+
For per-host details — what the installers and uninstallers touch, OpenCode config resolution and model/env overrides, and Windows notes — see [Host install notes](./DEVELOPER.md#host-install-notes) in DEVELOPER.md. Developing the plugin itself? See [Developing locally](./DEVELOPER.md#developing-locally).
|
|
131
98
|
|
|
132
99
|
> **Not supported:** Claude Code Cowork, claude.ai/code web, or remote Codex environments without local plugin hooks — they run outside your local machine, so the local backend/dashboard and `~/.reflexio/` aren't reachable.
|
|
133
100
|
|
package/bin/claude-smart.js
CHANGED
|
@@ -816,6 +816,66 @@ function fileSha256(path) {
|
|
|
816
816
|
return crypto.createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
817
817
|
}
|
|
818
818
|
|
|
819
|
+
function claudeCodeCacheRoot() {
|
|
820
|
+
return join(homedir(), ".claude", "plugins", "cache", CODEX_MARKETPLACE_NAME, "claude-smart");
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Version of the plugin this npm package ships. The Claude Code cache is keyed
|
|
824
|
+
// by this version, and each version vendors a different Reflexio — so the
|
|
825
|
+
// vendor payload may only ever be compared against the cache dir for *this*
|
|
826
|
+
// version. Comparing against some other version's cache reports a spurious
|
|
827
|
+
// mismatch (its vendor legitimately differs) and would force a pointless
|
|
828
|
+
// reinstall.
|
|
829
|
+
function claudeCodePackageVersion(sourceRoot = join(PACKAGE_ROOT, "plugin")) {
|
|
830
|
+
try {
|
|
831
|
+
const manifest = JSON.parse(
|
|
832
|
+
readFileSync(join(sourceRoot, ".claude-plugin", "plugin.json"), "utf8"),
|
|
833
|
+
);
|
|
834
|
+
if (typeof manifest.version === "string" && manifest.version) return manifest.version;
|
|
835
|
+
} catch {
|
|
836
|
+
// Fall through to pyproject.toml.
|
|
837
|
+
}
|
|
838
|
+
try {
|
|
839
|
+
const match = readFileSync(join(sourceRoot, "pyproject.toml"), "utf8").match(
|
|
840
|
+
/^version\s*=\s*"([^"]+)"/m,
|
|
841
|
+
);
|
|
842
|
+
if (match) return match[1];
|
|
843
|
+
} catch {
|
|
844
|
+
// No readable version.
|
|
845
|
+
}
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function claudeCodeVendorPayloadMismatch(
|
|
850
|
+
pluginRoot,
|
|
851
|
+
sourceRoot = join(PACKAGE_ROOT, "plugin"),
|
|
852
|
+
) {
|
|
853
|
+
// A sentinel pair, not a full tree hash: it catches the failure this exists
|
|
854
|
+
// for (a cache with no vendor bundle at all, or one from a different build),
|
|
855
|
+
// not a vendor tree that is individually corrupt past these two files.
|
|
856
|
+
const vendorFiles = [
|
|
857
|
+
"vendor/reflexio/pyproject.toml",
|
|
858
|
+
"vendor/reflexio/reflexio/__init__.py",
|
|
859
|
+
];
|
|
860
|
+
// A git checkout gitignores the generated vendor bundle, so a source tree
|
|
861
|
+
// without one is a dev install, not a broken cache. Nothing to compare.
|
|
862
|
+
const sourceHasVendor = vendorFiles.some((rel) =>
|
|
863
|
+
existsSync(join(sourceRoot, rel)),
|
|
864
|
+
);
|
|
865
|
+
if (!sourceHasVendor) return null;
|
|
866
|
+
|
|
867
|
+
for (const rel of vendorFiles) {
|
|
868
|
+
const source = join(sourceRoot, rel);
|
|
869
|
+
const cached = join(pluginRoot, rel);
|
|
870
|
+
if (!existsSync(source)) {
|
|
871
|
+
throw new Error(`installed claude-smart package is missing ${rel}`);
|
|
872
|
+
}
|
|
873
|
+
if (!existsSync(cached)) return rel;
|
|
874
|
+
if (fileSha256(source) !== fileSha256(cached)) return rel;
|
|
875
|
+
}
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
|
|
819
879
|
function verifyOpenCodePluginPackage(packageRoot) {
|
|
820
880
|
const sourceScript = join(PACKAGE_ROOT, "plugin", "scripts", "smart-install.sh");
|
|
821
881
|
const copiedScript = join(packageRoot, "plugin", "scripts", "smart-install.sh");
|
|
@@ -951,6 +1011,48 @@ async function bootstrapClaudeCodeInstall() {
|
|
|
951
1011
|
return pluginRoot;
|
|
952
1012
|
}
|
|
953
1013
|
|
|
1014
|
+
async function refreshClaudeCodeCacheIfVendorMissing() {
|
|
1015
|
+
const version = claudeCodePackageVersion();
|
|
1016
|
+
if (!version) return null;
|
|
1017
|
+
// Only ever inspect and repair the cache dir for the version we are
|
|
1018
|
+
// installing. Other version dirs coexist in the cache and are not ours to
|
|
1019
|
+
// judge — `findClaudeCodePluginRoot()` returns the highest one, which is a
|
|
1020
|
+
// different plugin build whenever this package is a downgrade.
|
|
1021
|
+
const pluginRoot = join(claudeCodeCacheRoot(), version);
|
|
1022
|
+
if (!existsSync(pluginRoot)) return null;
|
|
1023
|
+
|
|
1024
|
+
const mismatch = claudeCodeVendorPayloadMismatch(pluginRoot);
|
|
1025
|
+
if (!mismatch) return pluginRoot;
|
|
1026
|
+
|
|
1027
|
+
process.stderr.write(
|
|
1028
|
+
`warning: cached claude-smart plugin is missing or has stale ${mismatch}; reinstalling from the bundled npm package.\n`,
|
|
1029
|
+
);
|
|
1030
|
+
let code = await runClaude(["plugin", "uninstall", PLUGIN_SPEC], {
|
|
1031
|
+
spinnerLabel: "Removing stale claude-smart cache…",
|
|
1032
|
+
});
|
|
1033
|
+
if (code !== 0) {
|
|
1034
|
+
throw new Error(`could not remove stale ${PLUGIN_SPEC} cache (exit ${code})`);
|
|
1035
|
+
}
|
|
1036
|
+
code = await runClaude(["plugin", "install", PLUGIN_SPEC], {
|
|
1037
|
+
spinnerLabel: "Refreshing claude-smart cache…",
|
|
1038
|
+
});
|
|
1039
|
+
if (code !== 0) {
|
|
1040
|
+
throw new Error(`could not reinstall ${PLUGIN_SPEC} from the bundled package (exit ${code})`);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
if (!existsSync(pluginRoot)) {
|
|
1044
|
+
throw new Error(`Claude Code did not recreate the claude-smart plugin cache at ${pluginRoot}`);
|
|
1045
|
+
}
|
|
1046
|
+
const remainingMismatch = claudeCodeVendorPayloadMismatch(pluginRoot);
|
|
1047
|
+
if (remainingMismatch) {
|
|
1048
|
+
throw new Error(
|
|
1049
|
+
`refreshed claude-smart cache still does not match the bundled npm package: ${remainingMismatch}`,
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
process.stdout.write("Refreshed stale claude-smart plugin cache from the bundled npm package.\n");
|
|
1053
|
+
return pluginRoot;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
954
1056
|
function isWindows() {
|
|
955
1057
|
return currentPlatform() === "win32";
|
|
956
1058
|
}
|
|
@@ -2181,6 +2283,7 @@ async function runInstall(args, options = {}) {
|
|
|
2181
2283
|
}
|
|
2182
2284
|
|
|
2183
2285
|
try {
|
|
2286
|
+
await refreshClaudeCodeCacheIfVendorMissing();
|
|
2184
2287
|
const pluginRoot = await bootstrapClaudeCodeInstall();
|
|
2185
2288
|
restorePublishHooksFromSource(pluginRoot);
|
|
2186
2289
|
if (readOnly) {
|
|
@@ -2492,6 +2595,8 @@ if (require.main === module) {
|
|
|
2492
2595
|
module.exports = {
|
|
2493
2596
|
assertSupportedRuntimePlatform,
|
|
2494
2597
|
bootstrapPluginRuntime,
|
|
2598
|
+
claudeCodePackageVersion,
|
|
2599
|
+
claudeCodeVendorPayloadMismatch,
|
|
2495
2600
|
codexMarketplacePluginRoot,
|
|
2496
2601
|
copyCodexMarketplace,
|
|
2497
2602
|
ensurePrivateNode,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-smart",
|
|
3
|
-
"version": "0.2.
|
|
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"
|
package/plugin/.coverage
ADDED
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ??
|
|
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
|
|
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:
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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={
|
|
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">
|