feat(plugin): knowledge auto-inject (Path A) — title-first per-turn awareness
CI & Build / Python lint (push) Successful in 2s
CI & Build / integration (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 21s
CI & Build / Python tests (push) Successful in 43s
CI & Build / Build & push image (push) Successful in 55s
CI & Build / Python lint (push) Successful in 2s
CI & Build / integration (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 21s
CI & Build / Python tests (push) Successful in 43s
CI & Build / Build & push image (push) Successful in 55s
New UserPromptSubmit hook (scribe_autoinject.sh) + GET /api/plugin/retrieve that surface the TITLES (never bodies) of the few notes clearing four anti-bloat gates: a per-user confidence threshold (stricter than pull search), a margin gate, per-session dedup (exclude_ids), and a top-k ceiling. Each retrieval is logged to retrieval_logs as source=auto_inject so the threshold can be tuned from data. Per-user config (enable / threshold / top-k) is DB-backed via /api/settings with a Settings UI card; defaults enabled, threshold 0.55, top-k 3 (conservative — tune once auto_inject telemetry accrues). Scribe: project 2, milestone 93, task 1033. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xz4j1H7pjYSjKsEpgcNH5E
This commit is contained in:
@@ -17,6 +17,13 @@ const timezoneSaved = ref(false);
|
||||
const trashRetentionDays = ref("90");
|
||||
const savingRetention = ref(false);
|
||||
const retentionSaved = ref(false);
|
||||
// Knowledge auto-inject (per-user). Defaults mirror the backend
|
||||
// (services/plugin_context: enabled, threshold 0.55, top-k 3).
|
||||
const kbInjectEnabled = ref(true);
|
||||
const kbInjectThreshold = ref("0.55");
|
||||
const kbInjectTopK = ref("3");
|
||||
const savingKbInject = ref(false);
|
||||
const kbInjectSaved = ref(false);
|
||||
|
||||
// think_enabled setting removed 2026-05-23. The chat+curator architecture
|
||||
// has tools=[] on the chat model; think on a no-tools conversational pass
|
||||
@@ -56,6 +63,28 @@ async function saveRetention() {
|
||||
savingRetention.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveKbInject() {
|
||||
const t = Math.min(1, Math.max(0, Number(kbInjectThreshold.value) || 0));
|
||||
const k = Math.min(10, Math.max(1, Math.floor(Number(kbInjectTopK.value) || 1)));
|
||||
kbInjectThreshold.value = String(t);
|
||||
kbInjectTopK.value = String(k);
|
||||
savingKbInject.value = true;
|
||||
kbInjectSaved.value = false;
|
||||
try {
|
||||
await apiPut('/api/settings', {
|
||||
kb_autoinject_enabled: kbInjectEnabled.value ? 'true' : 'false',
|
||||
kb_autoinject_threshold: String(t),
|
||||
kb_autoinject_top_k: String(k),
|
||||
});
|
||||
kbInjectSaved.value = true;
|
||||
setTimeout(() => (kbInjectSaved.value = false), 2000);
|
||||
} catch {
|
||||
toastStore.show('Failed to save auto-inject settings', 'error');
|
||||
} finally {
|
||||
savingKbInject.value = false;
|
||||
}
|
||||
}
|
||||
const newEmail = ref("");
|
||||
const emailPassword = ref("");
|
||||
const changingEmail = ref(false);
|
||||
@@ -435,6 +464,13 @@ onMounted(async () => {
|
||||
const allSettings = await apiGet<Record<string, string>>("/api/settings");
|
||||
userTimezone.value = allSettings.user_timezone ?? "";
|
||||
trashRetentionDays.value = allSettings.trash_retention_days ?? "90";
|
||||
kbInjectEnabled.value = allSettings.kb_autoinject_enabled !== "false";
|
||||
if (allSettings.kb_autoinject_threshold !== undefined) {
|
||||
kbInjectThreshold.value = allSettings.kb_autoinject_threshold;
|
||||
}
|
||||
if (allSettings.kb_autoinject_top_k !== undefined) {
|
||||
kbInjectTopK.value = allSettings.kb_autoinject_top_k;
|
||||
}
|
||||
if (allSettings.notify_task_reminders !== undefined) {
|
||||
notifyTaskReminders.value = allSettings.notify_task_reminders !== "false";
|
||||
}
|
||||
@@ -1165,6 +1201,58 @@ function formatUserDate(iso: string): string {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="settings-section full-width">
|
||||
<h2>Knowledge auto-inject</h2>
|
||||
<p class="section-desc">
|
||||
When enabled, the Scribe plugin quietly surfaces the titles of your most
|
||||
relevant notes on each prompt — never their full text — so Claude can pull
|
||||
one in with <code>get_note(id)</code> only when it helps. Titles only, each
|
||||
note at most once per session, and nothing is shown unless it clears the
|
||||
confidence bar below.
|
||||
</p>
|
||||
<div class="checkbox-field">
|
||||
<label>
|
||||
<input type="checkbox" v-model="kbInjectEnabled" />
|
||||
Surface relevant note titles each prompt
|
||||
</label>
|
||||
<p class="field-hint">Off = notes reach context only when Claude searches for them.</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="kb-inject-threshold">Confidence threshold (0–1)</label>
|
||||
<input
|
||||
id="kb-inject-threshold"
|
||||
v-model="kbInjectThreshold"
|
||||
type="number"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.05"
|
||||
class="input"
|
||||
style="max-width: 8rem"
|
||||
/>
|
||||
<p class="field-hint">Minimum similarity to surface a note. Higher = stricter (fewer, more certain). Deliberately above the 0.45 used for searches you trigger yourself.</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="kb-inject-topk">Max notes per prompt</label>
|
||||
<input
|
||||
id="kb-inject-topk"
|
||||
v-model="kbInjectTopK"
|
||||
type="number"
|
||||
min="1"
|
||||
max="10"
|
||||
step="1"
|
||||
class="input"
|
||||
style="max-width: 8rem"
|
||||
/>
|
||||
<p class="field-hint">Ceiling on titles surfaced at once (1–10).</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="btn-save" @click="saveKbInject" :disabled="savingKbInject">
|
||||
{{ savingKbInject ? 'Saving…' : 'Save' }}
|
||||
</button>
|
||||
<span v-if="kbInjectSaved" class="saved-msg">Saved!</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ── Account ── -->
|
||||
|
||||
Reference in New Issue
Block a user