muonroi-cli 1.8.0 → 1.8.1
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 -21
- package/README.md +122 -122
- package/dist/src/agent-harness/mock-model.js +112 -4
- package/dist/src/cli/cost-forensics.js +17 -14
- package/dist/src/council/prompts.js +20 -20
- package/dist/src/flow/compaction/index.d.ts +1 -0
- package/dist/src/flow/compaction/index.js +4 -0
- package/dist/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/index.js +4 -2
- package/dist/src/lsp/manager.js +11 -3
- package/dist/src/lsp/manager.test.js +39 -0
- package/dist/src/mcp/auto-setup.js +0 -8
- package/dist/src/mcp/oauth-callback.js +2 -2
- package/dist/src/mcp/parse-headers.test.js +14 -14
- package/dist/src/mcp/self-verify-jobs.d.ts +1 -0
- package/dist/src/mcp/self-verify-runner.js +1 -1
- package/dist/src/mcp/setup-guide-text.js +74 -74
- package/dist/src/mcp/smoke.test.js +43 -43
- package/dist/src/ops/doctor.js +7 -7
- package/dist/src/orchestrator/__tests__/batch-turn-runner.test.js +1 -0
- package/dist/src/orchestrator/__tests__/message-processor.test.js +1 -0
- package/dist/src/orchestrator/__tests__/prompts.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/prompts.test.js +88 -0
- package/dist/src/orchestrator/__tests__/sub-session-delegation.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/sub-session-delegation.test.js +507 -0
- package/dist/src/orchestrator/auto-commit.js +9 -7
- package/dist/src/orchestrator/message-processor.d.ts +2 -0
- package/dist/src/orchestrator/message-processor.js +7 -0
- package/dist/src/orchestrator/orchestrator.d.ts +3 -0
- package/dist/src/orchestrator/orchestrator.js +362 -28
- package/dist/src/orchestrator/pending-calls.js +2 -1
- package/dist/src/orchestrator/preprocessor.js +3 -2
- package/dist/src/orchestrator/prompts.d.ts +8 -0
- package/dist/src/orchestrator/prompts.js +65 -18
- package/dist/src/orchestrator/sub-agent-cap.d.ts +2 -0
- package/dist/src/orchestrator/sub-agent-cap.js +12 -4
- package/dist/src/orchestrator/subagent-compactor.d.ts +7 -0
- package/dist/src/orchestrator/subagent-compactor.js +65 -14
- package/dist/src/orchestrator/subagent-compactor.spec.js +125 -9
- package/dist/src/orchestrator/tool-engine.d.ts +2 -0
- package/dist/src/orchestrator/tool-engine.js +80 -21
- package/dist/src/orchestrator/turn-runner-deps.d.ts +2 -0
- package/dist/src/pil/__tests__/llm-classify.test.js +79 -1
- package/dist/src/pil/layer1-intent.js +37 -37
- package/dist/src/pil/layer2_5-ponytail.js +8 -8
- package/dist/src/pil/llm-classify.d.ts +11 -0
- package/dist/src/pil/llm-classify.js +193 -0
- package/dist/src/product-loop/done-gate.js +3 -3
- package/dist/src/product-loop/loop-driver.js +18 -18
- package/dist/src/product-loop/progress-snapshot.js +4 -4
- package/dist/src/providers/mcp-vision-bridge.js +48 -48
- package/dist/src/reporter/index.js +1 -1
- package/dist/src/scaffold/bb-ecosystem-apply.js +47 -47
- package/dist/src/scaffold/bb-quality-gate.js +5 -5
- package/dist/src/scaffold/continuation-prompt.js +60 -60
- package/dist/src/scaffold/init-new.js +453 -453
- package/dist/src/self-qa/__tests__/scenario-planner.test.js +3 -3
- package/dist/src/self-qa/agentic-loop.js +11 -2
- package/dist/src/storage/__tests__/hierarchical-session.test.d.ts +1 -0
- package/dist/src/storage/__tests__/hierarchical-session.test.js +161 -0
- package/dist/src/storage/__tests__/migrations.test.js +2 -2
- package/dist/src/storage/__tests__/sweep-stale-pending.test.js +2 -2
- package/dist/src/storage/index.d.ts +1 -1
- package/dist/src/storage/index.js +1 -1
- package/dist/src/storage/interaction-log.js +5 -5
- package/dist/src/storage/migrations.js +135 -123
- package/dist/src/storage/session-experience-store.js +4 -4
- package/dist/src/storage/sessions.js +43 -43
- package/dist/src/storage/transcript-response-entry.test.js +18 -5
- package/dist/src/storage/transcript.d.ts +2 -0
- package/dist/src/storage/transcript.js +223 -97
- package/dist/src/storage/usage.js +34 -17
- package/dist/src/storage/workspaces.js +12 -12
- package/dist/src/tools/native-tools.js +10 -1
- package/dist/src/tools/registry.d.ts +1 -0
- package/dist/src/tools/registry.js +30 -2
- package/dist/src/types/index.d.ts +1 -1
- package/dist/src/ui/app.js +0 -0
- package/dist/src/ui/slash/council-inspect.js +4 -4
- package/dist/src/ui/slash/expand.js +14 -1
- package/dist/src/ui/use-app-logic.js +0 -0
- package/dist/src/utils/__tests__/compaction-caps.test.js +9 -9
- package/dist/src/utils/__tests__/llm-deadline.test.js +16 -4
- package/dist/src/utils/__tests__/logger.test.d.ts +1 -0
- package/dist/src/utils/__tests__/logger.test.js +115 -0
- package/dist/src/utils/__tests__/settings-agent-first.test.d.ts +1 -0
- package/dist/src/utils/__tests__/settings-agent-first.test.js +48 -0
- package/dist/src/utils/clipboard-image.js +23 -23
- package/dist/src/utils/install-manager.d.ts +1 -0
- package/dist/src/utils/install-manager.js +76 -7
- package/dist/src/utils/install-manager.test.js +43 -1
- package/dist/src/utils/logger.d.ts +28 -0
- package/dist/src/utils/logger.js +171 -0
- package/dist/src/utils/settings.d.ts +9 -1
- package/dist/src/utils/settings.js +59 -22
- package/dist/src/utils/side-question.js +2 -2
- package/dist/src/utils/skills.js +3 -3
- package/dist/src/utils/update-checker.test.js +5 -1
- package/dist/src/verify/__tests__/coverage-parsers.test.js +30 -30
- package/package.json +1 -1
|
@@ -29,9 +29,9 @@ describe("scenario-planner", () => {
|
|
|
29
29
|
expect(hits[0]?.line).toBe(3);
|
|
30
30
|
});
|
|
31
31
|
it("handles multiple hits in one file", () => {
|
|
32
|
-
const src = `
|
|
33
|
-
<Semantic id="one" role="textbox">
|
|
34
|
-
<Semantic id="two" role="button">
|
|
32
|
+
const src = `
|
|
33
|
+
<Semantic id="one" role="textbox">
|
|
34
|
+
<Semantic id="two" role="button">
|
|
35
35
|
`;
|
|
36
36
|
const hits = extractSemanticHits(src, "f.tsx");
|
|
37
37
|
expect(hits.map((h) => h.id)).toEqual(["one", "two"]);
|
|
@@ -330,8 +330,14 @@ export async function runAgenticLoop(opts) {
|
|
|
330
330
|
const events = [];
|
|
331
331
|
let lastFrame = null;
|
|
332
332
|
const entry = opts.entry ?? resolve("src/index.ts");
|
|
333
|
-
const
|
|
334
|
-
|
|
333
|
+
const args = [entry, "--agent-mode"];
|
|
334
|
+
if (opts.mockLlmDir !== "none") {
|
|
335
|
+
const mockDir = opts.mockLlmDir ?? resolve("tests/harness/fixtures/llm");
|
|
336
|
+
args.push("--mock-llm", mockDir);
|
|
337
|
+
}
|
|
338
|
+
if (opts.extraArgs) {
|
|
339
|
+
args.push(...opts.extraArgs);
|
|
340
|
+
}
|
|
335
341
|
const env = {
|
|
336
342
|
...process.env,
|
|
337
343
|
MUONROI_TEST_NO_PERSIST: "1",
|
|
@@ -438,6 +444,9 @@ export async function runAgenticLoop(opts) {
|
|
|
438
444
|
}
|
|
439
445
|
try {
|
|
440
446
|
await executeDecision(driver, decision);
|
|
447
|
+
if (decision.action === "type" || decision.action === "press") {
|
|
448
|
+
await new Promise((res) => setTimeout(res, 300));
|
|
449
|
+
}
|
|
441
450
|
}
|
|
442
451
|
catch (err) {
|
|
443
452
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
const sessionsDb = new Map();
|
|
3
|
+
const messagesDb = new Map();
|
|
4
|
+
const toolCallsDb = new Map();
|
|
5
|
+
const toolResultsDb = new Map();
|
|
6
|
+
const compactionsDb = new Map();
|
|
7
|
+
vi.mock("../db", () => {
|
|
8
|
+
return {
|
|
9
|
+
getDatabase: () => ({
|
|
10
|
+
prepare: (sql) => {
|
|
11
|
+
const lower = sql.toLowerCase();
|
|
12
|
+
return {
|
|
13
|
+
get: (param) => {
|
|
14
|
+
if (lower.includes("from sessions")) {
|
|
15
|
+
if (lower.includes("where parent_session_id =")) {
|
|
16
|
+
for (const [id, val] of sessionsDb.entries()) {
|
|
17
|
+
if (val.parent_session_id === param) {
|
|
18
|
+
return { id };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
return sessionsDb.get(param) || { parent_session_id: null };
|
|
24
|
+
}
|
|
25
|
+
if (lower.includes("from tool_calls") && lower.includes("todo_write")) {
|
|
26
|
+
const calls = toolCallsDb.get(param) || [];
|
|
27
|
+
const todo = calls.find((c) => c.tool_name === "todo_write");
|
|
28
|
+
return todo ? { args_json: todo.args_json } : undefined;
|
|
29
|
+
}
|
|
30
|
+
if (lower.includes("from compactions")) {
|
|
31
|
+
return compactionsDb.get(param);
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
},
|
|
35
|
+
all: (...params) => {
|
|
36
|
+
if (lower.includes("from messages")) {
|
|
37
|
+
return messagesDb.get(params[0]) || [];
|
|
38
|
+
}
|
|
39
|
+
if (lower.includes("from tool_results")) {
|
|
40
|
+
return toolResultsDb.get(params[0]) || [];
|
|
41
|
+
}
|
|
42
|
+
if (lower.includes("from sessions")) {
|
|
43
|
+
if (lower.includes("where parent_session_id =")) {
|
|
44
|
+
const results = [];
|
|
45
|
+
for (const [id, val] of sessionsDb.entries()) {
|
|
46
|
+
if (val.parent_session_id === params[0]) {
|
|
47
|
+
results.push({ id });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return results;
|
|
51
|
+
}
|
|
52
|
+
if (lower.includes("where id in")) {
|
|
53
|
+
return params.map((id) => ({ id }));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return [];
|
|
57
|
+
},
|
|
58
|
+
run: () => ({ changes: 0 }),
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
exec: () => undefined,
|
|
62
|
+
pragma: () => undefined,
|
|
63
|
+
transaction: (fn) => fn,
|
|
64
|
+
}),
|
|
65
|
+
withTransaction: (fn) => fn({}),
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
import { buildChatEntries, getLastTodoWriteArgs, getSessionChain } from "../transcript.js";
|
|
69
|
+
describe("Hierarchical Sessions (Silent Rotation Support)", () => {
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
sessionsDb.clear();
|
|
72
|
+
messagesDb.clear();
|
|
73
|
+
toolCallsDb.clear();
|
|
74
|
+
toolResultsDb.clear();
|
|
75
|
+
compactionsDb.clear();
|
|
76
|
+
});
|
|
77
|
+
describe("getSessionChain", () => {
|
|
78
|
+
it("returns correct chain for a single session", () => {
|
|
79
|
+
sessionsDb.set("sessionA", { parent_session_id: null });
|
|
80
|
+
expect(getSessionChain("sessionA")).toEqual(["sessionA"]);
|
|
81
|
+
});
|
|
82
|
+
it("traverses up the parent session chain recursively", () => {
|
|
83
|
+
sessionsDb.set("sessionA", { parent_session_id: null });
|
|
84
|
+
sessionsDb.set("sessionB", { parent_session_id: "sessionA" });
|
|
85
|
+
sessionsDb.set("sessionC", { parent_session_id: "sessionB" });
|
|
86
|
+
expect(getSessionChain("sessionC")).toEqual(["sessionA", "sessionB", "sessionC"]);
|
|
87
|
+
});
|
|
88
|
+
it("handles loops gracefully and does not infinite loop", () => {
|
|
89
|
+
sessionsDb.set("sessionA", { parent_session_id: "sessionB" });
|
|
90
|
+
sessionsDb.set("sessionB", { parent_session_id: "sessionA" });
|
|
91
|
+
// Should break loop and return traversed list in some order
|
|
92
|
+
const chain = getSessionChain("sessionB");
|
|
93
|
+
expect(chain.length).toBe(2);
|
|
94
|
+
expect(chain).toContain("sessionA");
|
|
95
|
+
expect(chain).toContain("sessionB");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe("getLastTodoWriteArgs", () => {
|
|
99
|
+
it("returns null if no todo_write exists in chain", () => {
|
|
100
|
+
sessionsDb.set("sessionA", { parent_session_id: null });
|
|
101
|
+
expect(getLastTodoWriteArgs("sessionA")).toBeNull();
|
|
102
|
+
});
|
|
103
|
+
it("retrieves todo_write arguments from the current session", () => {
|
|
104
|
+
sessionsDb.set("sessionA", { parent_session_id: null });
|
|
105
|
+
toolCallsDb.set("sessionA", [
|
|
106
|
+
{ id: 1, tool_call_id: "call_1", tool_name: "todo_write", args_json: '{"items":[]}' },
|
|
107
|
+
]);
|
|
108
|
+
expect(getLastTodoWriteArgs("sessionA")).toBe('{"items":[]}');
|
|
109
|
+
});
|
|
110
|
+
it("retrieves todo_write arguments recursively from parent session", () => {
|
|
111
|
+
sessionsDb.set("sessionA", { parent_session_id: null });
|
|
112
|
+
sessionsDb.set("sessionB", { parent_session_id: "sessionA" });
|
|
113
|
+
toolCallsDb.set("sessionA", [
|
|
114
|
+
{ id: 1, tool_call_id: "call_1", tool_name: "todo_write", args_json: '{"items":["task1"]}' },
|
|
115
|
+
]);
|
|
116
|
+
// sessionB has no todo_write calls
|
|
117
|
+
expect(getLastTodoWriteArgs("sessionB")).toBe('{"items":["task1"]}');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe("buildChatEntries", () => {
|
|
121
|
+
it("merges messages from parent and child session chronologically", () => {
|
|
122
|
+
sessionsDb.set("sessionA", { parent_session_id: null });
|
|
123
|
+
sessionsDb.set("sessionB", { parent_session_id: "sessionA" });
|
|
124
|
+
messagesDb.set("sessionA", [
|
|
125
|
+
{
|
|
126
|
+
seq: 1,
|
|
127
|
+
role: "user",
|
|
128
|
+
message_json: JSON.stringify({ role: "user", content: "Hello A" }),
|
|
129
|
+
created_at: "2026-06-25T12:00:00Z",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
seq: 2,
|
|
133
|
+
role: "assistant",
|
|
134
|
+
message_json: JSON.stringify({ role: "assistant", content: "Reply A" }),
|
|
135
|
+
created_at: "2026-06-25T12:01:00Z",
|
|
136
|
+
},
|
|
137
|
+
]);
|
|
138
|
+
messagesDb.set("sessionB", [
|
|
139
|
+
{
|
|
140
|
+
seq: 1,
|
|
141
|
+
role: "system",
|
|
142
|
+
// compaction summary (should be skipped since it's the first message of a child)
|
|
143
|
+
message_json: JSON.stringify({ role: "system", content: "[Context checkpoint summary]\nEarlier work" }),
|
|
144
|
+
created_at: "2026-06-25T12:02:00Z",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
seq: 2,
|
|
148
|
+
role: "user",
|
|
149
|
+
message_json: JSON.stringify({ role: "user", content: "Hello B" }),
|
|
150
|
+
created_at: "2026-06-25T12:03:00Z",
|
|
151
|
+
},
|
|
152
|
+
]);
|
|
153
|
+
const entries = buildChatEntries("sessionB");
|
|
154
|
+
expect(entries.length).toBe(3);
|
|
155
|
+
expect(entries[0]).toEqual(expect.objectContaining({ type: "user", content: "Hello A" }));
|
|
156
|
+
expect(entries[1]).toEqual(expect.objectContaining({ type: "assistant", content: "Reply A" }));
|
|
157
|
+
expect(entries[2]).toEqual(expect.objectContaining({ type: "user", content: "Hello B" }));
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
//# sourceMappingURL=hierarchical-session.test.js.map
|
|
@@ -313,7 +313,7 @@ describe("DB migrations", () => {
|
|
|
313
313
|
]);
|
|
314
314
|
raw.rows.set("usage_events", []);
|
|
315
315
|
// Insert without pil_active/enrichment_delta — should default to 0
|
|
316
|
-
db.prepare(`INSERT INTO usage_events (session_id, message_seq, source, model, input_tokens, output_tokens, total_tokens, cost_micros, created_at)
|
|
316
|
+
db.prepare(`INSERT INTO usage_events (session_id, message_seq, source, model, input_tokens, output_tokens, total_tokens, cost_micros, created_at)
|
|
317
317
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run("sess1", null, "orchestrator", "claude", 100, 50, 150, 0, new Date().toISOString());
|
|
318
318
|
const usageRows = raw.rows.get("usage_events");
|
|
319
319
|
expect(usageRows).toHaveLength(1);
|
|
@@ -346,7 +346,7 @@ describe("DB migrations", () => {
|
|
|
346
346
|
},
|
|
347
347
|
]);
|
|
348
348
|
raw.rows.set("usage_events", []);
|
|
349
|
-
db.prepare(`INSERT INTO usage_events (session_id, message_seq, source, model, input_tokens, output_tokens, total_tokens, cost_micros, created_at, pil_active, enrichment_delta)
|
|
349
|
+
db.prepare(`INSERT INTO usage_events (session_id, message_seq, source, model, input_tokens, output_tokens, total_tokens, cost_micros, created_at, pil_active, enrichment_delta)
|
|
350
350
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run("sess2", null, "orchestrator", "claude", 100, 50, 150, 0, new Date().toISOString(), 1, -120);
|
|
351
351
|
const usageRows = raw.rows.get("usage_events");
|
|
352
352
|
expect(usageRows).toHaveLength(1);
|
|
@@ -89,8 +89,8 @@ describe("sweepStalePendingRows", () => {
|
|
|
89
89
|
vi.spyOn(Date, "now").mockReturnValue(fixedNow);
|
|
90
90
|
sweepStalePendingRows(60_000);
|
|
91
91
|
const toolCallCall = preparedCalls.find((c) => c.sql.toLowerCase().includes("update tool_calls"));
|
|
92
|
-
// run args: [
|
|
93
|
-
const cutoff = toolCallCall?.runArgs[
|
|
92
|
+
// run args: [cutoff]
|
|
93
|
+
const cutoff = toolCallCall?.runArgs[0];
|
|
94
94
|
expect(cutoff).toBe("2026-05-22T09:59:00.000Z");
|
|
95
95
|
});
|
|
96
96
|
});
|
|
@@ -3,7 +3,7 @@ export { loadConfig, type MuonroiConfig } from "./config.js";
|
|
|
3
3
|
export { getDatabasePath } from "./db.js";
|
|
4
4
|
export { type InteractionEventType, logInteraction } from "./interaction-log.js";
|
|
5
5
|
export { SessionStore } from "./sessions.js";
|
|
6
|
-
export { appendCompaction, appendMessages, appendSystemMessage, buildChatEntries, getLastTodoWriteArgs, getNextMessageSequence, loadLatestCompaction, loadRawTranscript, loadTranscript, loadTranscriptState, markMessageCompleted, markMessageErrored, markToolCallErrored, persistMessageWriteAhead, persistToolCallWriteAhead, } from "./transcript.js";
|
|
6
|
+
export { appendCompaction, appendMessages, appendSystemMessage, buildChatEntries, getLastTodoWriteArgs, getNextMessageSequence, getSessionChain, loadLatestCompaction, loadRawTranscript, loadTranscript, loadTranscriptState, markMessageCompleted, markMessageErrored, markToolCallErrored, persistMessageWriteAhead, persistToolCallWriteAhead, revertLatestCompaction, } from "./transcript.js";
|
|
7
7
|
export { buildEffectiveTranscript, type LoadedTranscriptState, type PersistedCompaction } from "./transcript-view.js";
|
|
8
8
|
export { logUIInteraction, type UIInteractionSubtype } from "./ui-interaction-log.js";
|
|
9
9
|
export { getSessionTotalTokens, listSessionUsage, recordUsageEvent, type TokenUsageLike } from "./usage.js";
|
|
@@ -4,7 +4,7 @@ export { loadConfig } from "./config.js";
|
|
|
4
4
|
export { getDatabasePath } from "./db.js";
|
|
5
5
|
export { logInteraction } from "./interaction-log.js";
|
|
6
6
|
export { SessionStore } from "./sessions.js";
|
|
7
|
-
export { appendCompaction, appendMessages, appendSystemMessage, buildChatEntries, getLastTodoWriteArgs, getNextMessageSequence, loadLatestCompaction, loadRawTranscript, loadTranscript, loadTranscriptState, markMessageCompleted, markMessageErrored, markToolCallErrored, persistMessageWriteAhead, persistToolCallWriteAhead, } from "./transcript.js";
|
|
7
|
+
export { appendCompaction, appendMessages, appendSystemMessage, buildChatEntries, getLastTodoWriteArgs, getNextMessageSequence, getSessionChain, loadLatestCompaction, loadRawTranscript, loadTranscript, loadTranscriptState, markMessageCompleted, markMessageErrored, markToolCallErrored, persistMessageWriteAhead, persistToolCallWriteAhead, revertLatestCompaction, } from "./transcript.js";
|
|
8
8
|
export { buildEffectiveTranscript } from "./transcript-view.js";
|
|
9
9
|
export { logUIInteraction } from "./ui-interaction-log.js";
|
|
10
10
|
export { getSessionTotalTokens, listSessionUsage, recordUsageEvent } from "./usage.js";
|
|
@@ -55,10 +55,10 @@ export function selectEEInjectionsForRun(runId, limit = 20) {
|
|
|
55
55
|
try {
|
|
56
56
|
const db = getDatabase();
|
|
57
57
|
return db
|
|
58
|
-
.prepare(`SELECT session_id, event_subtype, duration_ms, metadata_json, created_at
|
|
59
|
-
FROM interaction_logs
|
|
60
|
-
WHERE session_id = ? AND event_type = 'ee_injection'
|
|
61
|
-
ORDER BY created_at DESC
|
|
58
|
+
.prepare(`SELECT session_id, event_subtype, duration_ms, metadata_json, created_at
|
|
59
|
+
FROM interaction_logs
|
|
60
|
+
WHERE session_id = ? AND event_type = 'ee_injection'
|
|
61
|
+
ORDER BY created_at DESC
|
|
62
62
|
LIMIT ?`)
|
|
63
63
|
.all(runId, limit);
|
|
64
64
|
}
|
|
@@ -74,7 +74,7 @@ export function logInteraction(sessionId, eventType, metadata) {
|
|
|
74
74
|
try {
|
|
75
75
|
const db = getDatabase();
|
|
76
76
|
const metadataJson = metadata?.data ? JSON.stringify(metadata.data) : null;
|
|
77
|
-
db.prepare(`INSERT INTO interaction_logs (session_id, event_type, event_subtype, model, duration_ms, input_tokens, output_tokens, metadata_json, created_at)
|
|
77
|
+
db.prepare(`INSERT INTO interaction_logs (session_id, event_type, event_subtype, model, duration_ms, input_tokens, output_tokens, metadata_json, created_at)
|
|
78
78
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(sessionId, eventType, metadata?.eventSubtype ?? null, metadata?.model ?? null, metadata?.durationMs ?? null, metadata?.inputTokens ?? null, metadata?.outputTokens ?? null, metadataJson, new Date().toISOString());
|
|
79
79
|
maybePruneOld();
|
|
80
80
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const LATEST_DB_VERSION =
|
|
1
|
+
const LATEST_DB_VERSION = 8;
|
|
2
2
|
export function applyMigrations(db) {
|
|
3
3
|
const version = Number(db.pragma("user_version", { simple: true })) || 0;
|
|
4
4
|
if (version >= LATEST_DB_VERSION)
|
|
@@ -13,9 +13,9 @@ export function applyMigrations(db) {
|
|
|
13
13
|
db.pragma("user_version = 2");
|
|
14
14
|
}
|
|
15
15
|
if (version < 3) {
|
|
16
|
-
db.exec(`
|
|
17
|
-
ALTER TABLE usage_events ADD COLUMN pil_active INTEGER NOT NULL DEFAULT 0;
|
|
18
|
-
ALTER TABLE usage_events ADD COLUMN enrichment_delta INTEGER NOT NULL DEFAULT 0;
|
|
16
|
+
db.exec(`
|
|
17
|
+
ALTER TABLE usage_events ADD COLUMN pil_active INTEGER NOT NULL DEFAULT 0;
|
|
18
|
+
ALTER TABLE usage_events ADD COLUMN enrichment_delta INTEGER NOT NULL DEFAULT 0;
|
|
19
19
|
`);
|
|
20
20
|
db.pragma("user_version = 3");
|
|
21
21
|
}
|
|
@@ -34,24 +34,24 @@ export function applyMigrations(db) {
|
|
|
34
34
|
db.pragma("user_version = 4");
|
|
35
35
|
}
|
|
36
36
|
if (version < 5) {
|
|
37
|
-
db.exec(`
|
|
38
|
-
CREATE TABLE IF NOT EXISTS interaction_logs (
|
|
39
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
40
|
-
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
41
|
-
event_type TEXT NOT NULL,
|
|
42
|
-
event_subtype TEXT,
|
|
43
|
-
model TEXT,
|
|
44
|
-
duration_ms INTEGER,
|
|
45
|
-
input_tokens INTEGER,
|
|
46
|
-
output_tokens INTEGER,
|
|
47
|
-
metadata_json TEXT,
|
|
48
|
-
created_at TEXT NOT NULL
|
|
49
|
-
) STRICT;
|
|
50
|
-
|
|
51
|
-
CREATE INDEX IF NOT EXISTS idx_interaction_logs_session
|
|
52
|
-
ON interaction_logs(session_id, created_at DESC);
|
|
53
|
-
CREATE INDEX IF NOT EXISTS idx_interaction_logs_event_type
|
|
54
|
-
ON interaction_logs(event_type, created_at DESC);
|
|
37
|
+
db.exec(`
|
|
38
|
+
CREATE TABLE IF NOT EXISTS interaction_logs (
|
|
39
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
40
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
41
|
+
event_type TEXT NOT NULL,
|
|
42
|
+
event_subtype TEXT,
|
|
43
|
+
model TEXT,
|
|
44
|
+
duration_ms INTEGER,
|
|
45
|
+
input_tokens INTEGER,
|
|
46
|
+
output_tokens INTEGER,
|
|
47
|
+
metadata_json TEXT,
|
|
48
|
+
created_at TEXT NOT NULL
|
|
49
|
+
) STRICT;
|
|
50
|
+
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_interaction_logs_session
|
|
52
|
+
ON interaction_logs(session_id, created_at DESC);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_interaction_logs_event_type
|
|
54
|
+
ON interaction_logs(event_type, created_at DESC);
|
|
55
55
|
`);
|
|
56
56
|
db.pragma("user_version = 5");
|
|
57
57
|
}
|
|
@@ -87,114 +87,126 @@ export function applyMigrations(db) {
|
|
|
87
87
|
}
|
|
88
88
|
db.pragma("user_version = 7");
|
|
89
89
|
}
|
|
90
|
+
if (version < 8) {
|
|
91
|
+
const cols = db.prepare("PRAGMA table_info(sessions)").all();
|
|
92
|
+
const colNames = new Set(cols.map((c) => c.name));
|
|
93
|
+
if (!colNames.has("parent_session_id")) {
|
|
94
|
+
db.exec("ALTER TABLE sessions ADD COLUMN parent_session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL");
|
|
95
|
+
}
|
|
96
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_sessions_parent_id ON sessions(parent_session_id)");
|
|
97
|
+
db.pragma("user_version = 8");
|
|
98
|
+
}
|
|
90
99
|
});
|
|
91
100
|
migrate();
|
|
92
101
|
}
|
|
93
102
|
function createInitialSchema(db) {
|
|
94
|
-
db.exec(`
|
|
95
|
-
CREATE TABLE IF NOT EXISTS workspaces (
|
|
96
|
-
id TEXT PRIMARY KEY,
|
|
97
|
-
scope_key TEXT NOT NULL UNIQUE,
|
|
98
|
-
canonical_path TEXT NOT NULL,
|
|
99
|
-
git_root TEXT,
|
|
100
|
-
display_name TEXT NOT NULL,
|
|
101
|
-
last_seen_at TEXT NOT NULL
|
|
102
|
-
) STRICT;
|
|
103
|
-
|
|
104
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
105
|
-
id TEXT PRIMARY KEY,
|
|
106
|
-
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
103
|
+
db.exec(`
|
|
104
|
+
CREATE TABLE IF NOT EXISTS workspaces (
|
|
105
|
+
id TEXT PRIMARY KEY,
|
|
106
|
+
scope_key TEXT NOT NULL UNIQUE,
|
|
107
|
+
canonical_path TEXT NOT NULL,
|
|
108
|
+
git_root TEXT,
|
|
109
|
+
display_name TEXT NOT NULL,
|
|
110
|
+
last_seen_at TEXT NOT NULL
|
|
111
|
+
) STRICT;
|
|
112
|
+
|
|
113
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
114
|
+
id TEXT PRIMARY KEY,
|
|
115
|
+
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
116
|
+
parent_session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
|
|
117
|
+
title TEXT,
|
|
118
|
+
model TEXT NOT NULL,
|
|
119
|
+
mode TEXT NOT NULL,
|
|
120
|
+
cwd_at_start TEXT NOT NULL,
|
|
121
|
+
cwd_last TEXT NOT NULL,
|
|
122
|
+
status TEXT NOT NULL,
|
|
123
|
+
created_at TEXT NOT NULL,
|
|
124
|
+
updated_at TEXT NOT NULL
|
|
125
|
+
) STRICT;
|
|
126
|
+
|
|
127
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
128
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
129
|
+
seq INTEGER NOT NULL,
|
|
130
|
+
role TEXT NOT NULL,
|
|
131
|
+
message_json TEXT NOT NULL,
|
|
132
|
+
created_at TEXT NOT NULL,
|
|
133
|
+
status TEXT,
|
|
134
|
+
PRIMARY KEY (session_id, seq)
|
|
135
|
+
) STRICT;
|
|
136
|
+
|
|
137
|
+
CREATE TABLE IF NOT EXISTS tool_calls (
|
|
138
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
139
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
140
|
+
message_seq INTEGER NOT NULL,
|
|
141
|
+
tool_call_id TEXT NOT NULL,
|
|
142
|
+
tool_name TEXT NOT NULL,
|
|
143
|
+
args_json TEXT NOT NULL,
|
|
144
|
+
status TEXT NOT NULL,
|
|
145
|
+
started_at TEXT NOT NULL,
|
|
146
|
+
completed_at TEXT,
|
|
147
|
+
UNIQUE(session_id, tool_call_id)
|
|
148
|
+
) STRICT;
|
|
149
|
+
|
|
150
|
+
CREATE TABLE IF NOT EXISTS tool_results (
|
|
151
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
152
|
+
tool_call_row_id INTEGER NOT NULL REFERENCES tool_calls(id) ON DELETE CASCADE,
|
|
153
|
+
output_kind TEXT NOT NULL,
|
|
154
|
+
output_json TEXT NOT NULL,
|
|
155
|
+
success INTEGER NOT NULL,
|
|
156
|
+
created_at TEXT NOT NULL
|
|
157
|
+
) STRICT;
|
|
158
|
+
|
|
159
|
+
CREATE TABLE IF NOT EXISTS usage_events (
|
|
160
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
161
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
162
|
+
message_seq INTEGER,
|
|
163
|
+
source TEXT NOT NULL,
|
|
164
|
+
model TEXT NOT NULL,
|
|
165
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
166
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
167
|
+
total_tokens INTEGER NOT NULL DEFAULT 0,
|
|
168
|
+
cost_micros INTEGER NOT NULL DEFAULT 0,
|
|
169
|
+
created_at TEXT NOT NULL,
|
|
170
|
+
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
171
|
+
cache_creation_tokens INTEGER NOT NULL DEFAULT 0
|
|
172
|
+
) STRICT;
|
|
173
|
+
|
|
174
|
+
CREATE TABLE IF NOT EXISTS compactions (
|
|
175
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
176
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
177
|
+
first_kept_seq INTEGER NOT NULL,
|
|
178
|
+
summary TEXT NOT NULL,
|
|
179
|
+
tokens_before INTEGER NOT NULL DEFAULT 0,
|
|
180
|
+
created_at TEXT NOT NULL
|
|
181
|
+
) STRICT;
|
|
182
|
+
|
|
183
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_workspace_updated
|
|
184
|
+
ON sessions(workspace_id, updated_at DESC);
|
|
185
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_parent_id
|
|
186
|
+
ON sessions(parent_session_id);
|
|
187
|
+
CREATE INDEX IF NOT EXISTS idx_messages_session_seq
|
|
188
|
+
ON messages(session_id, seq);
|
|
189
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_session_seq
|
|
190
|
+
ON tool_calls(session_id, message_seq);
|
|
191
|
+
CREATE INDEX IF NOT EXISTS idx_usage_events_session_created
|
|
192
|
+
ON usage_events(session_id, created_at DESC);
|
|
193
|
+
CREATE INDEX IF NOT EXISTS idx_compactions_session_created
|
|
194
|
+
ON compactions(session_id, created_at DESC);
|
|
183
195
|
`);
|
|
184
196
|
}
|
|
185
197
|
function createCompactionSchema(db) {
|
|
186
|
-
db.exec(`
|
|
187
|
-
CREATE TABLE IF NOT EXISTS compactions (
|
|
188
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
189
|
-
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
190
|
-
first_kept_seq INTEGER NOT NULL,
|
|
191
|
-
summary TEXT NOT NULL,
|
|
192
|
-
tokens_before INTEGER NOT NULL DEFAULT 0,
|
|
193
|
-
created_at TEXT NOT NULL
|
|
194
|
-
) STRICT;
|
|
195
|
-
|
|
196
|
-
CREATE INDEX IF NOT EXISTS idx_compactions_session_created
|
|
197
|
-
ON compactions(session_id, created_at DESC);
|
|
198
|
+
db.exec(`
|
|
199
|
+
CREATE TABLE IF NOT EXISTS compactions (
|
|
200
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
201
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
202
|
+
first_kept_seq INTEGER NOT NULL,
|
|
203
|
+
summary TEXT NOT NULL,
|
|
204
|
+
tokens_before INTEGER NOT NULL DEFAULT 0,
|
|
205
|
+
created_at TEXT NOT NULL
|
|
206
|
+
) STRICT;
|
|
207
|
+
|
|
208
|
+
CREATE INDEX IF NOT EXISTS idx_compactions_session_created
|
|
209
|
+
ON compactions(session_id, created_at DESC);
|
|
198
210
|
`);
|
|
199
211
|
}
|
|
200
212
|
//# sourceMappingURL=migrations.js.map
|
|
@@ -70,8 +70,8 @@ function parseCounts(json) {
|
|
|
70
70
|
export function selectSessionExperience(sessionId) {
|
|
71
71
|
try {
|
|
72
72
|
const row = getDatabase()
|
|
73
|
-
.prepare(`SELECT metadata_json FROM interaction_logs
|
|
74
|
-
WHERE session_id = ? AND event_type = ?
|
|
73
|
+
.prepare(`SELECT metadata_json FROM interaction_logs
|
|
74
|
+
WHERE session_id = ? AND event_type = ?
|
|
75
75
|
ORDER BY created_at DESC, id DESC LIMIT 1`)
|
|
76
76
|
.get(sessionId, EVENT_TYPE);
|
|
77
77
|
return parseCounts(row?.metadata_json ?? null);
|
|
@@ -150,8 +150,8 @@ export function computeExperienceAggregate(rows, limit = 100) {
|
|
|
150
150
|
export function aggregateSessionExperience(limit = 100) {
|
|
151
151
|
try {
|
|
152
152
|
const rows = getDatabase()
|
|
153
|
-
.prepare(`SELECT session_id, metadata_json, created_at FROM interaction_logs
|
|
154
|
-
WHERE event_type = ?
|
|
153
|
+
.prepare(`SELECT session_id, metadata_json, created_at FROM interaction_logs
|
|
154
|
+
WHERE event_type = ?
|
|
155
155
|
ORDER BY created_at DESC, id DESC`)
|
|
156
156
|
.all(EVENT_TYPE);
|
|
157
157
|
return computeExperienceAggregate(rows, limit);
|