@jmtrin/opencode-kevin 1.0.0 → 1.2.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.
- package/LICENSE +21 -0
- package/README.md +54 -8
- package/dist/migrations/001_initial.sql +91 -91
- package/dist/migrations/002_indexes.sql +13 -13
- package/dist/migrations/003_v02_signal.sql +57 -57
- package/dist/migrations/004_v03_knowledge.sql +138 -138
- package/dist/migrations/005_v04_signal.sql +57 -57
- package/dist/migrations/006_v05_glassbox.sql +118 -118
- package/dist/migrations/007_v06_pull.sql +144 -144
- package/dist/migrations/012_v11_drift.sql +24 -0
- package/dist/plugin/Archiver.js +2 -17
- package/dist/plugin/CausalChain.js +32 -13
- package/dist/plugin/ChatBridge.d.ts +41 -0
- package/dist/plugin/ChatBridge.js +103 -0
- package/dist/plugin/ConflictDetector.js +7 -29
- package/dist/plugin/DashboardHtml.d.ts +5 -0
- package/dist/plugin/DashboardHtml.js +180 -0
- package/dist/plugin/Feedback.js +2 -19
- package/dist/plugin/HookLiveness.d.ts +1 -0
- package/dist/plugin/HookLiveness.js +11 -27
- package/dist/plugin/InjectionLedger.js +104 -57
- package/dist/plugin/Materializer.js +1 -88
- package/dist/plugin/MemoryService.d.ts +59 -1
- package/dist/plugin/MemoryService.js +13 -106
- package/dist/plugin/Migrate.js +5 -0
- package/dist/plugin/Retrospective.js +7 -0
- package/dist/plugin/ToolCallObserver.js +18 -5
- package/dist/plugin/TuiActions.d.ts +43 -0
- package/dist/plugin/TuiActions.js +181 -0
- package/dist/plugin/TuiSnapshots.d.ts +24 -0
- package/dist/plugin/TuiSnapshots.js +158 -0
- package/dist/plugin/capabilities.d.ts +2 -0
- package/dist/plugin/capabilities.js +3 -0
- package/dist/plugin/columns.d.ts +11 -0
- package/dist/plugin/columns.js +54 -0
- package/dist/plugin/contract.d.ts +8 -0
- package/dist/plugin/contract.js +23 -5
- package/dist/plugin/index.d.ts +2 -2
- package/dist/plugin/index.js +315 -10
- package/dist/plugin/kevin_audit.d.ts +17 -1
- package/dist/plugin/kevin_audit.js +69 -1
- package/dist/plugin/kevin_forget.d.ts +33 -0
- package/dist/plugin/kevin_forget.js +260 -0
- package/dist/plugin/kevin_why.js +1 -18
- package/dist/plugin/metrics.d.ts +1 -1
- package/dist/plugin/metrics.js +8 -0
- package/dist/plugin/query-tokenizer.js +56 -8
- package/dist/plugin/time-ms.d.ts +1 -0
- package/dist/plugin/time-ms.js +16 -0
- package/dist/plugin/tui-types.d.ts +59 -0
- package/dist/plugin/tui-types.js +4 -0
- package/dist/plugin/tui.d.ts +18 -0
- package/dist/plugin/tui.js +198 -0
- package/package.json +8 -2
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const cache = new WeakMap();
|
|
2
|
+
export function hasColumn(store, table, column) {
|
|
3
|
+
const byStore = cache.get(store);
|
|
4
|
+
const key = `${table}.${column}`;
|
|
5
|
+
if (byStore?.get(key) === true)
|
|
6
|
+
return true;
|
|
7
|
+
try {
|
|
8
|
+
store.prepare(`SELECT ${column} FROM ${table} LIMIT 0`).get();
|
|
9
|
+
let m = byStore;
|
|
10
|
+
if (!m) {
|
|
11
|
+
m = new Map();
|
|
12
|
+
cache.set(store, m);
|
|
13
|
+
}
|
|
14
|
+
m.set(key, true);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// v1.1.0 (K11-011 / plan §5.5, D11-06) — named helpers delegating to the registry
|
|
22
|
+
export function hasIgnoredColumn(store) {
|
|
23
|
+
return hasColumn(store, "memories", "ignored");
|
|
24
|
+
}
|
|
25
|
+
export function hasCuratedColumn(store) {
|
|
26
|
+
return hasColumn(store, "memories", "curated");
|
|
27
|
+
}
|
|
28
|
+
export function hasTruthColumns(store) {
|
|
29
|
+
return hasColumn(store, "memories", "truth_penalty");
|
|
30
|
+
}
|
|
31
|
+
export function hasRepoIdColumn(store) {
|
|
32
|
+
return hasColumn(store, "memories", "repo_id");
|
|
33
|
+
}
|
|
34
|
+
export function hasLayerColumn(store) {
|
|
35
|
+
return hasColumn(store, "memories", "layer");
|
|
36
|
+
}
|
|
37
|
+
export function hasRecurrenceColumn(store) {
|
|
38
|
+
return hasColumn(store, "memories", "recurrence_count");
|
|
39
|
+
}
|
|
40
|
+
export function hasArchivedColumn(store) {
|
|
41
|
+
return hasColumn(store, "memories", "archived_at");
|
|
42
|
+
}
|
|
43
|
+
export function hasFeedbackColumns(store) {
|
|
44
|
+
return hasColumn(store, "memories", "feedback_positive");
|
|
45
|
+
}
|
|
46
|
+
export function hasFeedbackTable(store) {
|
|
47
|
+
try {
|
|
48
|
+
store.prepare("SELECT COUNT(*) FROM memory_feedback LIMIT 0").get();
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -28,6 +28,14 @@ export declare const CONTRACT_TOOL_ADDITIONS: readonly {
|
|
|
28
28
|
name: string;
|
|
29
29
|
since: string;
|
|
30
30
|
}[];
|
|
31
|
+
/**
|
|
32
|
+
* v1.1.0 (K11-007 / plan §5.1, D11-01) — metric keys added after the freeze,
|
|
33
|
+
* each carrying the `since` the deprecation policy requires (C-05).
|
|
34
|
+
*/
|
|
35
|
+
export declare const CONTRACT_METRIC_ADDITIONS: readonly {
|
|
36
|
+
name: string;
|
|
37
|
+
since: string;
|
|
38
|
+
}[];
|
|
31
39
|
/**
|
|
32
40
|
* v1.0.0 (K10-027 / plan §5.7) — the C-09 boundary addition. Stored is
|
|
33
41
|
* not trusted: anything reaching an artifact or a prompt is escaped at
|
package/dist/plugin/contract.js
CHANGED
|
@@ -41,8 +41,21 @@ export const CONTRACT_TOOL_NAMES = [
|
|
|
41
41
|
* added_bare.
|
|
42
42
|
*/
|
|
43
43
|
export const CONTRACT_TOOL_ADDITIONS = [
|
|
44
|
-
{ name: "kevin_contract", since: "1.0.0" },
|
|
45
44
|
{ name: "kevin_bench", since: "1.0.0" },
|
|
45
|
+
{ name: "kevin_contract", since: "1.0.0" },
|
|
46
|
+
{ name: "kevin_forget", since: "1.1.0" },
|
|
47
|
+
];
|
|
48
|
+
/**
|
|
49
|
+
* v1.1.0 (K11-007 / plan §5.1, D11-01) — metric keys added after the freeze,
|
|
50
|
+
* each carrying the `since` the deprecation policy requires (C-05).
|
|
51
|
+
*/
|
|
52
|
+
export const CONTRACT_METRIC_ADDITIONS = [
|
|
53
|
+
{ name: "bench_regression_failures", since: "1.1.0" },
|
|
54
|
+
{ name: "forget_requests_total", since: "1.1.0" },
|
|
55
|
+
{ name: "forget_tombstones_published", since: "1.1.0" },
|
|
56
|
+
// v1.2.0 (K12-001 / plan §4, D12-??) — surface metrics (no migration this release)
|
|
57
|
+
{ name: "tui_snapshots_flushed", since: "1.2.0" },
|
|
58
|
+
{ name: "tui_actions_invoked", since: "1.2.0" },
|
|
46
59
|
];
|
|
47
60
|
/**
|
|
48
61
|
* v1.0.0 (K10-027 / plan §5.7) — the C-09 boundary addition. Stored is
|
|
@@ -76,12 +89,17 @@ export function describeContract(_input) {
|
|
|
76
89
|
// Derive clause values from live source wherever possible (plan §5.1).
|
|
77
90
|
// C-03 members added after the freeze carry their `since` as objects;
|
|
78
91
|
// the original frozen set stays plain strings.
|
|
79
|
-
const
|
|
92
|
+
const toolAdditions = [...CONTRACT_TOOL_ADDITIONS].sort((a, b) => a.name.localeCompare(b.name));
|
|
80
93
|
const toolValue = {
|
|
81
|
-
tools: [[...CONTRACT_TOOL_NAMES].sort(),
|
|
94
|
+
tools: [[...CONTRACT_TOOL_NAMES].sort(), toolAdditions].flat(),
|
|
82
95
|
};
|
|
83
96
|
const settingValue = { keys: [...KEVIN_CONFIG_KEYS].sort() };
|
|
84
|
-
|
|
97
|
+
// v1.1.0 — metric keys added after freeze carry `since` (C-05)
|
|
98
|
+
const metricAdditions = [...CONTRACT_METRIC_ADDITIONS].sort((a, b) => a.name.localeCompare(b.name));
|
|
99
|
+
const baseMetricKeys = Object.keys(METRIC_KEY_LABELS)
|
|
100
|
+
.filter((k) => !metricAdditions.some((a) => a.name === k))
|
|
101
|
+
.sort();
|
|
102
|
+
const metricValue = { keys: [...baseMetricKeys, ...metricAdditions].flat() };
|
|
85
103
|
const clauses = [
|
|
86
104
|
{
|
|
87
105
|
id: "C-01",
|
|
@@ -149,7 +167,7 @@ export function describeContract(_input) {
|
|
|
149
167
|
stability: "forward-only",
|
|
150
168
|
since: "0.1.0",
|
|
151
169
|
value: {
|
|
152
|
-
schema_version: "
|
|
170
|
+
schema_version: "012",
|
|
153
171
|
migrations_forward_only: true,
|
|
154
172
|
},
|
|
155
173
|
},
|
package/dist/plugin/index.d.ts
CHANGED
|
@@ -16,10 +16,10 @@ export interface KevinPluginOptions {
|
|
|
16
16
|
* v0.4.0 (K4-021) — settings surfaced by `kevin_config` (plan §8.8).
|
|
17
17
|
* Unknown keys are rejected on `set` unless `strict: false`.
|
|
18
18
|
*/
|
|
19
|
-
export declare const KEVIN_CONFIG_KEYS: readonly ["quality_gate_enabled", "lesson_snippet_injection", "patternminer_enabled", "cross_project_enabled", "llm_reflection_enabled", "tool_calls_dedup_enabled", "deterministic_retrieval", "pre_prompt_budget_tokens", "archive_after_days", "curation_enabled", "agents_md_path", "skill_emission_enabled", "reference_emission_enabled", "injection_confidence_floor", "repo_truth_enabled", "convention_mining_enabled", "conflict_detection_enabled", "error_lesson_mode", "shared_layer_enabled", "okf_path", "share_requires_approval", "author_identity_mode", "shared_confidence_floor", "hook_liveness_enabled", "native_registration_enabled", "host_probe_history_enabled", "dead_hook_report_threshold", "perf_enabled", "perf_ring_capacity", "perf_flush_on_idle", "contract_report_enabled"];
|
|
19
|
+
export declare const KEVIN_CONFIG_KEYS: readonly ["quality_gate_enabled", "lesson_snippet_injection", "patternminer_enabled", "cross_project_enabled", "llm_reflection_enabled", "tool_calls_dedup_enabled", "deterministic_retrieval", "pre_prompt_budget_tokens", "archive_after_days", "curation_enabled", "agents_md_path", "skill_emission_enabled", "reference_emission_enabled", "injection_confidence_floor", "repo_truth_enabled", "convention_mining_enabled", "conflict_detection_enabled", "error_lesson_mode", "shared_layer_enabled", "okf_path", "share_requires_approval", "author_identity_mode", "shared_confidence_floor", "hook_liveness_enabled", "native_registration_enabled", "host_probe_history_enabled", "dead_hook_report_threshold", "perf_enabled", "perf_ring_capacity", "perf_flush_on_idle", "contract_report_enabled", "tui_snapshots_enabled"];
|
|
20
20
|
export declare const ERROR_LESSON_MODE_VALUES: readonly ["all", "triage_only"];
|
|
21
21
|
/** Plugin release version — stamped into generated files (K8-021/027). */
|
|
22
|
-
export declare const KEVIN_VERSION = "1.
|
|
22
|
+
export declare const KEVIN_VERSION = "1.2.0";
|
|
23
23
|
export interface RekeyCounts {
|
|
24
24
|
memories: number;
|
|
25
25
|
shared_entries: number;
|
package/dist/plugin/index.js
CHANGED
|
@@ -6,10 +6,12 @@ import { tool } from "@opencode-ai/plugin";
|
|
|
6
6
|
import { Archiver } from "./Archiver.js";
|
|
7
7
|
import { ArtifactWriter } from "./ArtifactWriter.js";
|
|
8
8
|
import { CausalChain } from "./CausalChain.js";
|
|
9
|
+
import { handleBridgeCommand } from "./ChatBridge.js";
|
|
9
10
|
import { ConflictDetector } from "./ConflictDetector.js";
|
|
10
11
|
import { ContextInjector } from "./ContextInjector.js";
|
|
11
12
|
import { ConventionMiner } from "./ConventionMiner.js";
|
|
12
13
|
import { Curator } from "./Curator.js";
|
|
14
|
+
import { writeDashboard } from "./DashboardHtml.js";
|
|
13
15
|
import { Feedback } from "./Feedback.js";
|
|
14
16
|
import { HookLiveness } from "./HookLiveness.js";
|
|
15
17
|
import { InjectionLedger } from "./InjectionLedger.js";
|
|
@@ -24,8 +26,11 @@ import { Retrospective } from "./Retrospective.js";
|
|
|
24
26
|
import { SharedLayer } from "./SharedLayer.js";
|
|
25
27
|
import { Store } from "./Store.js";
|
|
26
28
|
import { ToolCallObserver } from "./ToolCallObserver.js";
|
|
29
|
+
import { deleteMailbox, processActions, readMailbox, writeResults, } from "./TuiActions.js";
|
|
30
|
+
import { flushSnapshots } from "./TuiSnapshots.js";
|
|
27
31
|
import { probe } from "./capabilities.js";
|
|
28
32
|
import { computeConfidence } from "./confidence.js";
|
|
33
|
+
import { contractDigest, describeContract } from "./contract.js";
|
|
29
34
|
import { probeHost, summarize } from "./host.js";
|
|
30
35
|
import { kevinApprove } from "./kevin_approve.js";
|
|
31
36
|
import { buildAudit } from "./kevin_audit.js";
|
|
@@ -34,6 +39,7 @@ import { executeKevinConflicts } from "./kevin_conflicts.js";
|
|
|
34
39
|
import { buildKevinContract } from "./kevin_contract.js";
|
|
35
40
|
import { buildDoctor } from "./kevin_doctor.js";
|
|
36
41
|
import { buildKevinFacts } from "./kevin_facts.js";
|
|
42
|
+
import { handleForget } from "./kevin_forget.js";
|
|
37
43
|
import { handleNative } from "./kevin_native.js";
|
|
38
44
|
import { kevinPropose } from "./kevin_propose.js";
|
|
39
45
|
import { kevinPublish } from "./kevin_publish.js";
|
|
@@ -111,7 +117,13 @@ export const KEVIN_CONFIG_KEYS = [
|
|
|
111
117
|
"perf_ring_capacity",
|
|
112
118
|
"perf_flush_on_idle",
|
|
113
119
|
"contract_report_enabled",
|
|
120
|
+
// v1.2.0 (K12-001 / plan §4) — the single setting seeded by runtime
|
|
121
|
+
// (no migration this release). Omitting makes `kevin_config set`
|
|
122
|
+
// return { error: "unknown_key" } while `kevin_config list` still
|
|
123
|
+
// shows it.
|
|
124
|
+
"tui_snapshots_enabled",
|
|
114
125
|
];
|
|
126
|
+
// v1.1.0 (K11-007 / D11-08) — no new settings in 1.1.0; thresholds are constants (D11-03)
|
|
115
127
|
// v0.7.0 (K7-003 / plan §5.6, D7-12) — the explicit VALUE domain for
|
|
116
128
|
// `error_lesson_mode`. The setting is TEXT and must be compared with
|
|
117
129
|
// `=== "triage_only"`, never by truthiness; the domain here is enforced by
|
|
@@ -120,7 +132,7 @@ export const KEVIN_CONFIG_KEYS = [
|
|
|
120
132
|
// behaviour on the next reflection.
|
|
121
133
|
export const ERROR_LESSON_MODE_VALUES = ["all", "triage_only"];
|
|
122
134
|
/** Plugin release version — stamped into generated files (K8-021/027). */
|
|
123
|
-
export const KEVIN_VERSION = "1.
|
|
135
|
+
export const KEVIN_VERSION = "1.2.0";
|
|
124
136
|
function resolveMigrationsDir() {
|
|
125
137
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
126
138
|
return join(here, "..", "migrations");
|
|
@@ -256,6 +268,17 @@ export const KevinPlugin = async (input, options) => {
|
|
|
256
268
|
const store = new Store({ path: dbPath });
|
|
257
269
|
const migrationsDir = opts.migrationsDir ?? resolveMigrationsDir();
|
|
258
270
|
await new Migrate(store, migrationsDir).run();
|
|
271
|
+
// v1.2.0 (K12-001 / D12-??) — no migration this release: ensure the
|
|
272
|
+
// new setting exists with its default so `kevin_config list` shows it
|
|
273
|
+
// on a database that was already at 012.
|
|
274
|
+
try {
|
|
275
|
+
store
|
|
276
|
+
.prepare("INSERT OR IGNORE INTO kevin_settings (key, value) VALUES (?, ?)")
|
|
277
|
+
.run("tui_snapshots_enabled", "1");
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
// pre-003 DB without kevin_settings — nothing to seed
|
|
281
|
+
}
|
|
259
282
|
const metrics = new Metrics(store);
|
|
260
283
|
// v0.4.0 (K4-019): the plugin hooks expose no project field, so the
|
|
261
284
|
// project id is derived once from the plugin host's working directory
|
|
@@ -395,6 +418,10 @@ export const KevinPlugin = async (input, options) => {
|
|
|
395
418
|
mkdirSync(join(materializerRoot, "skills"), { recursive: true });
|
|
396
419
|
mkdirSync(join(materializerRoot, "refs"), { recursive: true });
|
|
397
420
|
const capabilities = probe(input);
|
|
421
|
+
// v1.2.0 (K12-012 / D12-03) — permission.ask probe (best-effort, additive).
|
|
422
|
+
// When the host exposes permission.ask, the presence is noted; absence is silent no-op.
|
|
423
|
+
// No new setting — bounded to tui_snapshots_enabled host-support check per spec.
|
|
424
|
+
void capabilities.permissionAsk;
|
|
398
425
|
const materializer = new Materializer(store, { root: materializerRoot });
|
|
399
426
|
// v0.9.0 (K9-016 / plan §5.4, D9-10) — native registration replaces
|
|
400
427
|
// file emission. When attachNative returns a registration for a
|
|
@@ -870,8 +897,9 @@ export const KevinPlugin = async (input, options) => {
|
|
|
870
897
|
// monotone across releases; K6-024 extends this
|
|
871
898
|
// block with the remaining v0.6 fields. v0.8.0
|
|
872
899
|
// (K8-025) — 18 v0.7 + kevin_project +
|
|
873
|
-
// kevin_native = 23.
|
|
874
|
-
|
|
900
|
+
// kevin_native = 23. v1.0.0 (K10-018) — +kevin_contract +kevin_bench = 25.
|
|
901
|
+
// v1.1.0 (K11-007) — +kevin_forget = 26.
|
|
902
|
+
tool_count: 26,
|
|
875
903
|
v07,
|
|
876
904
|
memories_reflector: memoriesReflector,
|
|
877
905
|
memories_agent: memoriesAgent,
|
|
@@ -1461,6 +1489,24 @@ export const KevinPlugin = async (input, options) => {
|
|
|
1461
1489
|
};
|
|
1462
1490
|
},
|
|
1463
1491
|
}),
|
|
1492
|
+
// v1.1.0 (K11-007 / plan §5.1, D11-02) — kevin_forget: closes the sharing lifecycle.
|
|
1493
|
+
kevin_forget: tool({
|
|
1494
|
+
description: "Olvida memorias y publica tombstones en la capa compartida (v1.1.0, K11-005/006 / plan §5.1): dry-run por defecto — sin confirm no muta nada y devuelve el plan (archived, tombstone planned); con confirm:true archiva localmente (status='archived') y, cuando la memoria proyecta a la capa compartida (layer='shared' o shared_entry_id), publica un tombstone via el unico write path (SharedLayer.applyExport, D8-08). Segunda invocacion identica reporta noop.",
|
|
1495
|
+
args: {
|
|
1496
|
+
ids: tool.schema.array(tool.schema.string()).min(1),
|
|
1497
|
+
confirm: tool.schema.boolean().optional(),
|
|
1498
|
+
},
|
|
1499
|
+
async execute(args) {
|
|
1500
|
+
const okfPath = join(projectRoot, memoryService.getSetting("okf_path", ".kevin/knowledge.okf"));
|
|
1501
|
+
const result = handleForget({ ids: args.ids, confirm: args.confirm }, { store, memoryService, sharedLayer, okfPath, metrics });
|
|
1502
|
+
return {
|
|
1503
|
+
title: result.dry_run
|
|
1504
|
+
? "Plan de olvido (dry-run)"
|
|
1505
|
+
: "Olvido aplicado",
|
|
1506
|
+
output: JSON.stringify(result),
|
|
1507
|
+
};
|
|
1508
|
+
},
|
|
1509
|
+
}),
|
|
1464
1510
|
kevin_approve: tool({
|
|
1465
1511
|
description: "Aprueba o rechaza una propuesta de curacion (v0.6.0). reject: marca rejected, nada toca disco. approve: aplica el diff (unico call site de ArtifactWriter.apply, D6-01), marca applied y cura las memorias contribuyentes. Solo acepta propuestas pending.",
|
|
1466
1512
|
args: {
|
|
@@ -1654,15 +1700,80 @@ export const KevinPlugin = async (input, options) => {
|
|
|
1654
1700
|
});
|
|
1655
1701
|
},
|
|
1656
1702
|
"chat.message": async (hookInput, output) => {
|
|
1703
|
+
const rawText = output.parts
|
|
1704
|
+
.map((p) => p)
|
|
1705
|
+
.filter((p) => p.type === "text")
|
|
1706
|
+
.map((p) => p.text ?? "")
|
|
1707
|
+
.join(" ");
|
|
1708
|
+
const trimmed = rawText.trim();
|
|
1709
|
+
// v1.2.0 (K12-018 / D12-09) — chat-command bridge BEFORE deriveQuery.
|
|
1710
|
+
// Valid commands are SWALLOWED (parts cleared) and never reach the model.
|
|
1711
|
+
// Must run outside perf.measure so early return actually exits the hook.
|
|
1712
|
+
if (trimmed.length > 0) {
|
|
1713
|
+
try {
|
|
1714
|
+
const bridgeDeps = {
|
|
1715
|
+
getPending: () => {
|
|
1716
|
+
try {
|
|
1717
|
+
const maybe = curator.pending;
|
|
1718
|
+
if (typeof maybe === "function") {
|
|
1719
|
+
const rows = maybe.call(curator);
|
|
1720
|
+
return rows.map((p) => ({
|
|
1721
|
+
id: p.id,
|
|
1722
|
+
proposedText: String(p.proposed_text ?? p.proposedText ?? ""),
|
|
1723
|
+
}));
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
catch { }
|
|
1727
|
+
try {
|
|
1728
|
+
const rows = store
|
|
1729
|
+
.prepare("SELECT id, proposed_text FROM curation_proposals WHERE status = 'pending'")
|
|
1730
|
+
.all();
|
|
1731
|
+
return rows.map((r) => ({
|
|
1732
|
+
id: r.id,
|
|
1733
|
+
proposedText: r.proposed_text,
|
|
1734
|
+
}));
|
|
1735
|
+
}
|
|
1736
|
+
catch {
|
|
1737
|
+
return [];
|
|
1738
|
+
}
|
|
1739
|
+
},
|
|
1740
|
+
approve: (id) => kevinApprove(store, memoryService, curator, writer, metrics, {
|
|
1741
|
+
proposalId: id,
|
|
1742
|
+
decision: "approve",
|
|
1743
|
+
}),
|
|
1744
|
+
reject: (id, note) => kevinApprove(store, memoryService, curator, writer, metrics, {
|
|
1745
|
+
proposalId: id,
|
|
1746
|
+
decision: "reject",
|
|
1747
|
+
}) && void note,
|
|
1748
|
+
acknowledge: (conflictId) => {
|
|
1749
|
+
try {
|
|
1750
|
+
const cd = conflictDetector;
|
|
1751
|
+
if (typeof cd.acknowledge === "function")
|
|
1752
|
+
cd.acknowledge(conflictId);
|
|
1753
|
+
else if (typeof cd.resolve === "function")
|
|
1754
|
+
cd.resolve(conflictId, "a");
|
|
1755
|
+
}
|
|
1756
|
+
catch { }
|
|
1757
|
+
},
|
|
1758
|
+
metrics,
|
|
1759
|
+
};
|
|
1760
|
+
const br = handleBridgeCommand(trimmed, bridgeDeps);
|
|
1761
|
+
if (br.handled) {
|
|
1762
|
+
try {
|
|
1763
|
+
output.parts = [];
|
|
1764
|
+
}
|
|
1765
|
+
catch { }
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
catch {
|
|
1770
|
+
// best-effort — bridge failure must not break chat flow
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1657
1773
|
perf.measure("chat.message", () => {
|
|
1658
|
-
|
|
1659
|
-
.map((p) => p)
|
|
1660
|
-
.filter((p) => p.type === "text")
|
|
1661
|
-
.map((p) => p.text ?? "")
|
|
1662
|
-
.join(" ");
|
|
1663
|
-
if (text.trim()) {
|
|
1774
|
+
if (trimmed.length > 0) {
|
|
1664
1775
|
const derived = injector.deriveQuery([
|
|
1665
|
-
{ role: "user", content:
|
|
1776
|
+
{ role: "user", content: rawText },
|
|
1666
1777
|
]);
|
|
1667
1778
|
lastUserQuery = derived.length > 0 ? derived : null;
|
|
1668
1779
|
if (hookInput.sessionID && lastUserQuery) {
|
|
@@ -1788,6 +1899,61 @@ export const KevinPlugin = async (input, options) => {
|
|
|
1788
1899
|
// (legacy DBs pre-005 lack the recurrence_count
|
|
1789
1900
|
// column)
|
|
1790
1901
|
.catch(() => { }));
|
|
1902
|
+
// v1.2.0 (K12-007/K12-011 / D12-05) — TUI mailbox: actions→curate ordering.
|
|
1903
|
+
// Process mailbox BEFORE curator.propose so a fresh proposal created this idle
|
|
1904
|
+
// is NOT visible to a stale token (D12-04). Best-effort, never breaks idle.
|
|
1905
|
+
try {
|
|
1906
|
+
const mb = readMailbox(materializerRoot);
|
|
1907
|
+
if (mb.actions.length) {
|
|
1908
|
+
const tuiDeps = {
|
|
1909
|
+
getPending: () => {
|
|
1910
|
+
try {
|
|
1911
|
+
const maybe = curator.pending;
|
|
1912
|
+
if (typeof maybe === "function") {
|
|
1913
|
+
const rows = maybe.call(curator);
|
|
1914
|
+
return rows.map((p) => ({
|
|
1915
|
+
id: p.id,
|
|
1916
|
+
proposedText: String(p.proposed_text ?? p.proposedText ?? ""),
|
|
1917
|
+
}));
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
catch { }
|
|
1921
|
+
try {
|
|
1922
|
+
const rows = store
|
|
1923
|
+
.prepare("SELECT id, proposed_text FROM curation_proposals WHERE status = 'pending'")
|
|
1924
|
+
.all();
|
|
1925
|
+
return rows.map((r) => ({
|
|
1926
|
+
id: r.id,
|
|
1927
|
+
proposedText: r.proposed_text,
|
|
1928
|
+
}));
|
|
1929
|
+
}
|
|
1930
|
+
catch {
|
|
1931
|
+
return [];
|
|
1932
|
+
}
|
|
1933
|
+
},
|
|
1934
|
+
approve: (id) => kevinApprove(store, memoryService, curator, writer, metrics, {
|
|
1935
|
+
proposalId: id,
|
|
1936
|
+
decision: "approve",
|
|
1937
|
+
}),
|
|
1938
|
+
reject: (id, _note) => kevinApprove(store, memoryService, curator, writer, metrics, {
|
|
1939
|
+
proposalId: id,
|
|
1940
|
+
decision: "reject",
|
|
1941
|
+
}),
|
|
1942
|
+
acknowledge: (conflictId) => {
|
|
1943
|
+
const cd = conflictDetector;
|
|
1944
|
+
if (typeof cd.acknowledge === "function")
|
|
1945
|
+
cd.acknowledge(conflictId);
|
|
1946
|
+
},
|
|
1947
|
+
metrics,
|
|
1948
|
+
};
|
|
1949
|
+
const tuiResults = processActions(mb.actions, tuiDeps);
|
|
1950
|
+
writeResults(materializerRoot, tuiResults);
|
|
1951
|
+
deleteMailbox(materializerRoot);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
catch {
|
|
1955
|
+
// best-effort
|
|
1956
|
+
}
|
|
1791
1957
|
// v0.6.0 (K6-015 / plan §8.14) — session-idle curation
|
|
1792
1958
|
// generation. Dry-run only: `propose()` calls `plan()`
|
|
1793
1959
|
// and never `apply()`, so nothing here can touch disk
|
|
@@ -1829,6 +1995,145 @@ export const KevinPlugin = async (input, options) => {
|
|
|
1829
1995
|
// best-effort, same pattern as ledger.settle — a
|
|
1830
1996
|
// sync failure must not break the idle path
|
|
1831
1997
|
}
|
|
1998
|
+
// v1.2.0 (K12-003/K12-011/K12-017 / D12-05) — snapshot flush gated by tui_snapshots_enabled.
|
|
1999
|
+
// Order: actions→curate→syncSharedLayer→snapshots (D12-05) — snapshots reflect post-action truth.
|
|
2000
|
+
try {
|
|
2001
|
+
if (memoryService.getSetting("tui_snapshots_enabled", "1") === "1") {
|
|
2002
|
+
// Proposals
|
|
2003
|
+
let proposals = [];
|
|
2004
|
+
try {
|
|
2005
|
+
const rawPending = (() => {
|
|
2006
|
+
try {
|
|
2007
|
+
const maybe = curator.pending;
|
|
2008
|
+
if (typeof maybe === "function")
|
|
2009
|
+
return maybe.call(curator);
|
|
2010
|
+
}
|
|
2011
|
+
catch { }
|
|
2012
|
+
return store
|
|
2013
|
+
.prepare("SELECT id, kind, target_path, proposed_text, diff, memory_id, created_at FROM curation_proposals WHERE status = 'pending' ORDER BY created_at")
|
|
2014
|
+
.all();
|
|
2015
|
+
})();
|
|
2016
|
+
const { proposalToken } = await import("./TuiActions.js");
|
|
2017
|
+
proposals = rawPending.map((r) => {
|
|
2018
|
+
const row = r;
|
|
2019
|
+
const id = String(row.id ?? "");
|
|
2020
|
+
const proposed = String(row.proposed_text ?? row.proposedText ?? "");
|
|
2021
|
+
return {
|
|
2022
|
+
id,
|
|
2023
|
+
kind: String(row.kind ?? "agents_md"),
|
|
2024
|
+
target_path: String(row.target_path ?? row.targetPath ?? "AGENTS.md"),
|
|
2025
|
+
diff: String(row.diff ?? ""),
|
|
2026
|
+
memory_ids: String(row.memory_id ?? row.memoryIds ?? "")
|
|
2027
|
+
.split(",")
|
|
2028
|
+
.filter((s) => s.length > 0),
|
|
2029
|
+
created_at: String(row.created_at ??
|
|
2030
|
+
row.createdAt ??
|
|
2031
|
+
new Date().toISOString()),
|
|
2032
|
+
token: proposalToken(id, proposed),
|
|
2033
|
+
};
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
catch { }
|
|
2037
|
+
// Conflicts
|
|
2038
|
+
let conflicts = [];
|
|
2039
|
+
try {
|
|
2040
|
+
const maybe = conflictDetector.openConflicts;
|
|
2041
|
+
if (typeof maybe === "function") {
|
|
2042
|
+
const raw = maybe.call(conflictDetector);
|
|
2043
|
+
conflicts = raw.map((c) => {
|
|
2044
|
+
const row = c;
|
|
2045
|
+
return {
|
|
2046
|
+
id: String(row.id ?? ""),
|
|
2047
|
+
kind: String(row.kind ?? ""),
|
|
2048
|
+
a_summary: String(row.a_summary ?? row.aSummary ?? ""),
|
|
2049
|
+
b_summary: String(row.b_summary ?? row.bSummary ?? ""),
|
|
2050
|
+
opened_at: String(row.opened_at ??
|
|
2051
|
+
row.openedAt ??
|
|
2052
|
+
new Date().toISOString()),
|
|
2053
|
+
};
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
catch {
|
|
2058
|
+
conflicts = [];
|
|
2059
|
+
}
|
|
2060
|
+
// Health
|
|
2061
|
+
let health;
|
|
2062
|
+
try {
|
|
2063
|
+
const doc = buildDoctor(store, host, memoryService);
|
|
2064
|
+
let perfRows = [];
|
|
2065
|
+
try {
|
|
2066
|
+
const stats = perf.stats?.();
|
|
2067
|
+
if (Array.isArray(stats)) {
|
|
2068
|
+
perfRows = stats.map((p) => {
|
|
2069
|
+
const row = p;
|
|
2070
|
+
const budget = row.budget;
|
|
2071
|
+
return {
|
|
2072
|
+
scope: String(row.scope ?? ""),
|
|
2073
|
+
p95: Number(row.p95 ?? 0),
|
|
2074
|
+
budget_p95: Number(budget?.p95Ms ??
|
|
2075
|
+
row.budget_p95 ??
|
|
2076
|
+
row.budgetP95 ??
|
|
2077
|
+
0),
|
|
2078
|
+
within_budget: Boolean(row.withinBudget ?? row.within_budget ?? true),
|
|
2079
|
+
};
|
|
2080
|
+
});
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
catch {
|
|
2084
|
+
perfRows = [];
|
|
2085
|
+
}
|
|
2086
|
+
let digest = "unknown";
|
|
2087
|
+
try {
|
|
2088
|
+
digest = contractDigest(describeContract());
|
|
2089
|
+
}
|
|
2090
|
+
catch { }
|
|
2091
|
+
health = {
|
|
2092
|
+
verdict: doc.verdict,
|
|
2093
|
+
reason: doc.reason,
|
|
2094
|
+
hooks: doc.hooks.map((h) => ({
|
|
2095
|
+
hook: h.hook,
|
|
2096
|
+
state: h.state,
|
|
2097
|
+
fire_count: h.fire_count,
|
|
2098
|
+
expected_count: h.expected_count,
|
|
2099
|
+
})),
|
|
2100
|
+
perf: perfRows,
|
|
2101
|
+
contract_digest: digest,
|
|
2102
|
+
counters: metrics.snapshot(),
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
catch {
|
|
2106
|
+
health = {
|
|
2107
|
+
verdict: "unknown",
|
|
2108
|
+
reason: "health unavailable",
|
|
2109
|
+
hooks: [],
|
|
2110
|
+
perf: [],
|
|
2111
|
+
contract_digest: "unknown",
|
|
2112
|
+
counters: {},
|
|
2113
|
+
};
|
|
2114
|
+
}
|
|
2115
|
+
flushSnapshots({
|
|
2116
|
+
root: materializerRoot,
|
|
2117
|
+
proposals,
|
|
2118
|
+
conflicts,
|
|
2119
|
+
health,
|
|
2120
|
+
metrics,
|
|
2121
|
+
version: KEVIN_VERSION,
|
|
2122
|
+
});
|
|
2123
|
+
try {
|
|
2124
|
+
writeDashboard(materializerRoot, {
|
|
2125
|
+
generatedAt: new Date().toISOString(),
|
|
2126
|
+
proposals,
|
|
2127
|
+
conflicts,
|
|
2128
|
+
health,
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
2131
|
+
catch { }
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
catch {
|
|
2135
|
+
// best-effort — snapshot failure must not break idle
|
|
2136
|
+
}
|
|
1832
2137
|
// v1.0.0 (K10-013 / D10-08) — the session recorded work:
|
|
1833
2138
|
// arm the deferred dispose settlement. The ISO timestamp
|
|
1834
2139
|
// lets the next session.start compare it against
|
|
@@ -153,6 +153,22 @@ export interface AuditReport {
|
|
|
153
153
|
clause_count: number;
|
|
154
154
|
deprecated_count: number;
|
|
155
155
|
};
|
|
156
|
+
/**
|
|
157
|
+
* v1.2.0 (K12-014 / D12-??) — TUI surface block. Read-only introspection:
|
|
158
|
+
* enabled_setting is the kevin_settings value, last_flush_age_s is seconds
|
|
159
|
+
* since meta.json generatedAt (null when absent/corrupt), mailbox_depth is
|
|
160
|
+
* queue length (null when file missing), last_results is the parsed
|
|
161
|
+
* results.json array (null when absent). Dashboard age and bridge counters
|
|
162
|
+
* added in K12-019.
|
|
163
|
+
*/
|
|
164
|
+
tui?: {
|
|
165
|
+
enabled_setting: string;
|
|
166
|
+
last_flush_age_s: number | null;
|
|
167
|
+
mailbox_depth: number | null;
|
|
168
|
+
last_results: unknown | null;
|
|
169
|
+
dashboard_last_write_age_s: number | null;
|
|
170
|
+
bridge_interceptions: number;
|
|
171
|
+
};
|
|
156
172
|
partial: boolean;
|
|
157
173
|
}
|
|
158
174
|
/**
|
|
@@ -210,4 +226,4 @@ export interface TruthReport {
|
|
|
210
226
|
is_truncated: false;
|
|
211
227
|
};
|
|
212
228
|
}
|
|
213
|
-
export declare function buildAudit(store: Store, metrics: Metrics, capabilities?: Capabilities, projectId?: string, repoId?: string | null): AuditReport;
|
|
229
|
+
export declare function buildAudit(store: Store, metrics: Metrics, capabilities?: Capabilities, projectId?: string, repoId?: string | null, tuiRoot?: string): AuditReport;
|