praana 0.5.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 +124 -0
- package/bin/praana.js +17 -0
- package/bin/pran.js +17 -0
- package/dist/app-banner.d.ts +11 -0
- package/dist/app-banner.js +161 -0
- package/dist/app-controller.d.ts +44 -0
- package/dist/app-controller.js +143 -0
- package/dist/app-identity.d.ts +18 -0
- package/dist/app-identity.js +52 -0
- package/dist/auto-compact.d.ts +16 -0
- package/dist/auto-compact.js +101 -0
- package/dist/cli-args.d.ts +14 -0
- package/dist/cli-args.js +69 -0
- package/dist/compile-classic.d.ts +21 -0
- package/dist/compile-classic.js +106 -0
- package/dist/compiler.d.ts +75 -0
- package/dist/compiler.js +406 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +433 -0
- package/dist/context-engine/activity-log.d.ts +9 -0
- package/dist/context-engine/activity-log.js +109 -0
- package/dist/context-engine/artifact-store.d.ts +32 -0
- package/dist/context-engine/artifact-store.js +272 -0
- package/dist/context-engine/bm25.d.ts +3 -0
- package/dist/context-engine/bm25.js +32 -0
- package/dist/context-engine/checkpoint.d.ts +34 -0
- package/dist/context-engine/checkpoint.js +430 -0
- package/dist/context-engine/classify.d.ts +3 -0
- package/dist/context-engine/classify.js +60 -0
- package/dist/context-engine/db.d.ts +73 -0
- package/dist/context-engine/db.js +505 -0
- package/dist/context-engine/distiller.d.ts +30 -0
- package/dist/context-engine/distiller.js +67 -0
- package/dist/context-engine/engine-compiler.d.ts +23 -0
- package/dist/context-engine/engine-compiler.js +297 -0
- package/dist/context-engine/error-tracker.d.ts +21 -0
- package/dist/context-engine/error-tracker.js +74 -0
- package/dist/context-engine/event-lineage.d.ts +26 -0
- package/dist/context-engine/event-lineage.js +120 -0
- package/dist/context-engine/extraction.d.ts +26 -0
- package/dist/context-engine/extraction.js +83 -0
- package/dist/context-engine/index.d.ts +82 -0
- package/dist/context-engine/index.js +238 -0
- package/dist/context-engine/scoring.d.ts +13 -0
- package/dist/context-engine/scoring.js +47 -0
- package/dist/context-engine/state-snapshot.d.ts +8 -0
- package/dist/context-engine/state-snapshot.js +50 -0
- package/dist/context-engine/summarize.d.ts +6 -0
- package/dist/context-engine/summarize.js +32 -0
- package/dist/context-engine/telemetry.d.ts +25 -0
- package/dist/context-engine/telemetry.js +64 -0
- package/dist/context-engine/turn-digest.d.ts +50 -0
- package/dist/context-engine/turn-digest.js +250 -0
- package/dist/context-engine/turn-ledger.d.ts +18 -0
- package/dist/context-engine/turn-ledger.js +184 -0
- package/dist/context-engine/turn-recorder.d.ts +24 -0
- package/dist/context-engine/turn-recorder.js +88 -0
- package/dist/context-engine/types.d.ts +201 -0
- package/dist/context-engine/types.js +4 -0
- package/dist/context-pressure.d.ts +19 -0
- package/dist/context-pressure.js +36 -0
- package/dist/distillers/generic.d.ts +14 -0
- package/dist/distillers/generic.js +93 -0
- package/dist/distillers/git-diff.d.ts +8 -0
- package/dist/distillers/git-diff.js +119 -0
- package/dist/distillers/index.d.ts +2 -0
- package/dist/distillers/index.js +16 -0
- package/dist/distillers/npm-test.d.ts +8 -0
- package/dist/distillers/npm-test.js +50 -0
- package/dist/distillers/rg-results.d.ts +8 -0
- package/dist/distillers/rg-results.js +28 -0
- package/dist/distillers/tsc-errors.d.ts +8 -0
- package/dist/distillers/tsc-errors.js +52 -0
- package/dist/event-log.d.ts +56 -0
- package/dist/event-log.js +214 -0
- package/dist/llm.d.ts +29 -0
- package/dist/llm.js +155 -0
- package/dist/logger.d.ts +94 -0
- package/dist/logger.js +287 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +54 -0
- package/dist/memory/confidence.d.ts +7 -0
- package/dist/memory/confidence.js +37 -0
- package/dist/memory/consolidation.d.ts +26 -0
- package/dist/memory/consolidation.js +166 -0
- package/dist/memory/db.d.ts +40 -0
- package/dist/memory/db.js +283 -0
- package/dist/memory/dedup.d.ts +6 -0
- package/dist/memory/dedup.js +50 -0
- package/dist/memory/embedder-factory.d.ts +3 -0
- package/dist/memory/embedder-factory.js +81 -0
- package/dist/memory/embeddings.d.ts +15 -0
- package/dist/memory/embeddings.js +67 -0
- package/dist/memory/index.d.ts +9 -0
- package/dist/memory/index.js +11 -0
- package/dist/memory/ollama-summarizer.d.ts +19 -0
- package/dist/memory/ollama-summarizer.js +72 -0
- package/dist/memory/openai-summarizer.d.ts +21 -0
- package/dist/memory/openai-summarizer.js +51 -0
- package/dist/memory/store.d.ts +61 -0
- package/dist/memory/store.js +502 -0
- package/dist/memory/summarizer-factory.d.ts +3 -0
- package/dist/memory/summarizer-factory.js +69 -0
- package/dist/memory/summarizer.d.ts +4 -0
- package/dist/memory/summarizer.js +112 -0
- package/dist/memory/types.d.ts +87 -0
- package/dist/memory/types.js +17 -0
- package/dist/model-context.d.ts +15 -0
- package/dist/model-context.js +212 -0
- package/dist/project-detector.d.ts +37 -0
- package/dist/project-detector.js +604 -0
- package/dist/render.d.ts +15 -0
- package/dist/render.js +46 -0
- package/dist/session.d.ts +118 -0
- package/dist/session.js +809 -0
- package/dist/skills/index.d.ts +69 -0
- package/dist/skills/index.js +885 -0
- package/dist/skills/types.d.ts +93 -0
- package/dist/skills/types.js +8 -0
- package/dist/slash-commands.d.ts +14 -0
- package/dist/slash-commands.js +301 -0
- package/dist/state-graph.d.ts +38 -0
- package/dist/state-graph.js +255 -0
- package/dist/status-bar.d.ts +54 -0
- package/dist/status-bar.js +184 -0
- package/dist/thinking-display.d.ts +21 -0
- package/dist/thinking-display.js +37 -0
- package/dist/tool-summary.d.ts +4 -0
- package/dist/tool-summary.js +67 -0
- package/dist/tools/index.d.ts +925 -0
- package/dist/tools/index.js +86 -0
- package/dist/tools/knowledge.d.ts +140 -0
- package/dist/tools/knowledge.js +260 -0
- package/dist/tools/memory.d.ts +39 -0
- package/dist/tools/memory.js +300 -0
- package/dist/tools/search-code.d.ts +134 -0
- package/dist/tools/search-code.js +390 -0
- package/dist/tools/system.d.ts +16 -0
- package/dist/tools/system.js +499 -0
- package/dist/tools/tool-def.d.ts +6 -0
- package/dist/tools/tool-def.js +3 -0
- package/dist/turn-control.d.ts +51 -0
- package/dist/turn-control.js +210 -0
- package/dist/turn.d.ts +20 -0
- package/dist/turn.js +624 -0
- package/dist/types.d.ts +233 -0
- package/dist/types.js +4 -0
- package/dist/ui/readline-ui.d.ts +2 -0
- package/dist/ui/readline-ui.js +176 -0
- package/dist/ui/tui/app.d.ts +13 -0
- package/dist/ui/tui/app.js +270 -0
- package/dist/ui/tui/busy-indicator.d.ts +2 -0
- package/dist/ui/tui/busy-indicator.js +13 -0
- package/dist/ui/tui/components/gutter-rule.d.ts +5 -0
- package/dist/ui/tui/components/gutter-rule.js +9 -0
- package/dist/ui/tui/components/inline-tool-row.d.ts +10 -0
- package/dist/ui/tui/components/inline-tool-row.js +8 -0
- package/dist/ui/tui/components/prompt-input.d.ts +20 -0
- package/dist/ui/tui/components/prompt-input.js +120 -0
- package/dist/ui/tui/components/system-line.d.ts +5 -0
- package/dist/ui/tui/components/system-line.js +6 -0
- package/dist/ui/tui/components/thinking-block.d.ts +11 -0
- package/dist/ui/tui/components/thinking-block.js +31 -0
- package/dist/ui/tui/components/toast-line.d.ts +4 -0
- package/dist/ui/tui/components/toast-line.js +8 -0
- package/dist/ui/tui/components/tool-result-line.d.ts +5 -0
- package/dist/ui/tui/components/tool-result-line.js +6 -0
- package/dist/ui/tui/components/turn-footer.d.ts +5 -0
- package/dist/ui/tui/components/turn-footer.js +7 -0
- package/dist/ui/tui/components/user-block.d.ts +6 -0
- package/dist/ui/tui/components/user-block.js +6 -0
- package/dist/ui/tui/logo-banner.d.ts +5 -0
- package/dist/ui/tui/logo-banner.js +8 -0
- package/dist/ui/tui/markdown-render.d.ts +16 -0
- package/dist/ui/tui/markdown-render.js +218 -0
- package/dist/ui/tui/palette.d.ts +12 -0
- package/dist/ui/tui/palette.js +13 -0
- package/dist/ui/tui/reasoning-summary.d.ts +12 -0
- package/dist/ui/tui/reasoning-summary.js +27 -0
- package/dist/ui/tui/reducer.d.ts +92 -0
- package/dist/ui/tui/reducer.js +260 -0
- package/dist/ui/tui/run.d.ts +3 -0
- package/dist/ui/tui/run.js +40 -0
- package/dist/ui/tui/sink.d.ts +4 -0
- package/dist/ui/tui/sink.js +89 -0
- package/dist/ui/tui/status-bar-view.d.ts +5 -0
- package/dist/ui/tui/status-bar-view.js +44 -0
- package/dist/ui/tui/terminal-height.d.ts +12 -0
- package/dist/ui/tui/terminal-height.js +20 -0
- package/dist/ui/tui/terminal-width.d.ts +2 -0
- package/dist/ui/tui/terminal-width.js +5 -0
- package/dist/ui/tui/tool-display.d.ts +23 -0
- package/dist/ui/tui/tool-display.js +217 -0
- package/dist/ui/tui/transcript-line.d.ts +12 -0
- package/dist/ui/tui/transcript-line.js +43 -0
- package/dist/ui/tui/transcript-replay.d.ts +12 -0
- package/dist/ui/tui/transcript-replay.js +117 -0
- package/dist/ui-events.d.ts +39 -0
- package/dist/ui-events.js +33 -0
- package/dist/ui.d.ts +77 -0
- package/dist/ui.js +179 -0
- package/package.json +73 -0
- package/praana.config.example.toml +231 -0
package/dist/session.js
ADDED
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
|
+
import { readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
6
|
+
import { ulid } from "ulid";
|
|
7
|
+
import { SkillRuntime, discoverSkills } from "./skills/index.js";
|
|
8
|
+
import { EventLog, writeSessionMeta, readSessionMeta } from "./event-log.js";
|
|
9
|
+
import { StateGraph } from "./state-graph.js";
|
|
10
|
+
import { loadConfig } from "./config.js";
|
|
11
|
+
import { MemoryStore, createEmbedder, createSummarizer, } from "./memory/index.js";
|
|
12
|
+
import { buildProjectContext } from "./project-detector.js";
|
|
13
|
+
import { runConsolidation } from "./memory/consolidation.js";
|
|
14
|
+
import { ContextEngine, isContextEngineEnabled, renderSessionTelemetrySummary, resolveContextDbPath, resolveContextEngineConfig, } from "./context-engine/index.js";
|
|
15
|
+
import { fetchAndCacheContextWindow, resolveContextWindowSync, } from "./model-context.js";
|
|
16
|
+
import { APP_HOME_DIR, APP_AGENT_ID, LEGACY_APP_HOME_DIR, resolveAppHomePath, resolveDefaultMemoryDbPath } from "./app-identity.js";
|
|
17
|
+
import { createSessionLogger, getAppLogger } from "./logger.js";
|
|
18
|
+
export class Session {
|
|
19
|
+
id;
|
|
20
|
+
cwd;
|
|
21
|
+
config;
|
|
22
|
+
eventLog;
|
|
23
|
+
stateGraph;
|
|
24
|
+
memoryStore = null;
|
|
25
|
+
memoryEnabled;
|
|
26
|
+
incognito = false;
|
|
27
|
+
digest = null;
|
|
28
|
+
agentsContext = null; // content from AGENTS.md / CLAUDE.md
|
|
29
|
+
projectContext = null; // stack fingerprint from package.json, README, etc.
|
|
30
|
+
skills = []; // discovered skills (re-discovered on resume; residency resets)
|
|
31
|
+
skillRuntime = null;
|
|
32
|
+
contextEngine = null;
|
|
33
|
+
debug = false;
|
|
34
|
+
ended = false;
|
|
35
|
+
startedAt;
|
|
36
|
+
turnCount = 0;
|
|
37
|
+
modelOverride = null;
|
|
38
|
+
modelContextWindow = null;
|
|
39
|
+
modelContextWindowFor = null;
|
|
40
|
+
lastCompileMetrics = null;
|
|
41
|
+
lastCompileScoreRecords = [];
|
|
42
|
+
lastPressureMode = "normal";
|
|
43
|
+
lastPressureRatio = 0;
|
|
44
|
+
sessionInputTokens = 0;
|
|
45
|
+
sessionOutputTokens = 0;
|
|
46
|
+
lastUserInput = "";
|
|
47
|
+
compactionArmed = false;
|
|
48
|
+
sessionLogger = null;
|
|
49
|
+
noticeCapture;
|
|
50
|
+
constructor(id, cwd, config, startedAt) {
|
|
51
|
+
this.id = id;
|
|
52
|
+
this.cwd = cwd;
|
|
53
|
+
this.config = config;
|
|
54
|
+
this.startedAt = startedAt;
|
|
55
|
+
const logDir = config.session.log_dir;
|
|
56
|
+
this.eventLog = new EventLog(id, logDir);
|
|
57
|
+
this.stateGraph = new StateGraph();
|
|
58
|
+
this.memoryEnabled = config.memory.enabled;
|
|
59
|
+
}
|
|
60
|
+
static createNew(id, cwd, config) {
|
|
61
|
+
const startedAt = Date.now();
|
|
62
|
+
const session = new Session(id, cwd, config, startedAt);
|
|
63
|
+
writeSessionMeta(config.session.log_dir, {
|
|
64
|
+
session_id: id,
|
|
65
|
+
started_at: startedAt,
|
|
66
|
+
cwd,
|
|
67
|
+
agent: APP_AGENT_ID,
|
|
68
|
+
});
|
|
69
|
+
return session;
|
|
70
|
+
}
|
|
71
|
+
static async create(cwd, config, opts) {
|
|
72
|
+
const cfg = config ?? loadConfig();
|
|
73
|
+
const id = ulid();
|
|
74
|
+
const session = Session.createNew(id, cwd, cfg);
|
|
75
|
+
session.incognito = opts?.incognito ?? false;
|
|
76
|
+
session.noticeCapture = opts?.captureNotice;
|
|
77
|
+
await session.initLogger();
|
|
78
|
+
session.agentsContext = loadAgentsContext(cwd);
|
|
79
|
+
if (session.agentsContext) {
|
|
80
|
+
const tokEst = Math.ceil(session.agentsContext.length / 4);
|
|
81
|
+
session.getLogger().notice(`context ${tokEst} tok`, { domain: "session" });
|
|
82
|
+
}
|
|
83
|
+
initContextEngine(session);
|
|
84
|
+
await initSkills(session, cfg, cwd);
|
|
85
|
+
applyProjectContext(session, cwd);
|
|
86
|
+
if (session.incognito) {
|
|
87
|
+
session.memoryEnabled = false;
|
|
88
|
+
session.memoryStore = null;
|
|
89
|
+
session.digest = null;
|
|
90
|
+
session.getLogger().notice("Cross-session memory persistence disabled (incognito)");
|
|
91
|
+
await session.refreshModelContextWindow().catch((err) => {
|
|
92
|
+
session.getLogger().child("session").warn("Failed to prefetch context window", {
|
|
93
|
+
cause: err,
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
return session;
|
|
97
|
+
}
|
|
98
|
+
if (session.memoryEnabled) {
|
|
99
|
+
try {
|
|
100
|
+
session.memoryStore = await session.initMemoryStore();
|
|
101
|
+
session.eventLog.append({
|
|
102
|
+
kind: "system_note",
|
|
103
|
+
actor: "kernel",
|
|
104
|
+
payload: {
|
|
105
|
+
type: "memory_lifecycle",
|
|
106
|
+
phase: "session_start_begin",
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
const d = await session.memoryStore.sessionStart({
|
|
110
|
+
agent: APP_AGENT_ID,
|
|
111
|
+
user_id: hashString(process.env.USER ?? "unknown"),
|
|
112
|
+
time: Date.now(),
|
|
113
|
+
context_id: hashString(cwd),
|
|
114
|
+
context_label: basename(cwd),
|
|
115
|
+
working_context: { repo: { root: cwd, name: basename(cwd) } },
|
|
116
|
+
recall_min_score: cfg.compiler.recall_min_score ?? 0.35,
|
|
117
|
+
});
|
|
118
|
+
session.digest = d.markdown;
|
|
119
|
+
session.eventLog.append({
|
|
120
|
+
kind: "system_note",
|
|
121
|
+
actor: "kernel",
|
|
122
|
+
payload: {
|
|
123
|
+
type: "memory_lifecycle",
|
|
124
|
+
phase: "session_start_success",
|
|
125
|
+
digestLength: session.digest.length,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
session.getLogger().child("memory").warn("Failed to initialize, continuing without memory", {
|
|
131
|
+
code: "MEMORY_INIT_FAILED",
|
|
132
|
+
cause: err,
|
|
133
|
+
});
|
|
134
|
+
session.eventLog.append({
|
|
135
|
+
kind: "system_note",
|
|
136
|
+
actor: "kernel",
|
|
137
|
+
payload: {
|
|
138
|
+
type: "memory_lifecycle",
|
|
139
|
+
phase: "session_start_failure",
|
|
140
|
+
error: err.message,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
session.memoryEnabled = false;
|
|
144
|
+
session.memoryStore = null;
|
|
145
|
+
session.digest = null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
await session.refreshModelContextWindow().catch((err) => {
|
|
149
|
+
session.getLogger().child("session").warn("Failed to prefetch context window", {
|
|
150
|
+
cause: err,
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
return session;
|
|
154
|
+
}
|
|
155
|
+
static async resume(sessionId, cwd, config, opts) {
|
|
156
|
+
const cfg = config ?? loadConfig();
|
|
157
|
+
const meta = readSessionMeta(cfg.session.log_dir, sessionId);
|
|
158
|
+
if (!meta) {
|
|
159
|
+
throw new Error(`Session ${sessionId} not found.`);
|
|
160
|
+
}
|
|
161
|
+
const session = new Session(sessionId, cwd, cfg, meta.started_at);
|
|
162
|
+
session.noticeCapture = opts?.captureNotice;
|
|
163
|
+
await session.initLogger();
|
|
164
|
+
session.agentsContext = loadAgentsContext(cwd);
|
|
165
|
+
if (session.agentsContext) {
|
|
166
|
+
const tokEst = Math.ceil(session.agentsContext.length / 4);
|
|
167
|
+
session.getLogger().notice(`context ${tokEst} tok`, { domain: "session" });
|
|
168
|
+
}
|
|
169
|
+
initContextEngine(session);
|
|
170
|
+
await initSkills(session, cfg, cwd);
|
|
171
|
+
const allEvents = session.eventLog.readAll();
|
|
172
|
+
// Replay state mutations chronologically. Reset markers intentionally clear
|
|
173
|
+
// earlier context_action events so /clear remains effective after resume.
|
|
174
|
+
for (const ev of allEvents) {
|
|
175
|
+
if (ev.kind === "context_action") {
|
|
176
|
+
session.stateGraph.replayAction(ev.payload);
|
|
177
|
+
}
|
|
178
|
+
else if (ev.kind === "system_note" &&
|
|
179
|
+
ev.payload.type === "state_reset" &&
|
|
180
|
+
ev.payload.cleared === "all") {
|
|
181
|
+
session.stateGraph.clear();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
loadProjectContextField(session, cwd);
|
|
185
|
+
// Restore model override if one was set previously.
|
|
186
|
+
for (let i = allEvents.length - 1; i >= 0; i--) {
|
|
187
|
+
const ev = allEvents[i];
|
|
188
|
+
if (ev.kind !== "system_note" || ev.payload.type !== "model_override")
|
|
189
|
+
continue;
|
|
190
|
+
const rawModel = ev.payload.model;
|
|
191
|
+
if (typeof rawModel === "string" && rawModel.trim()) {
|
|
192
|
+
session.modelOverride = rawModel.trim();
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
session.modelOverride = null;
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
if (session.memoryEnabled) {
|
|
200
|
+
try {
|
|
201
|
+
session.memoryStore = await session.initMemoryStore();
|
|
202
|
+
session.eventLog.append({
|
|
203
|
+
kind: "system_note",
|
|
204
|
+
actor: "kernel",
|
|
205
|
+
payload: {
|
|
206
|
+
type: "memory_lifecycle",
|
|
207
|
+
phase: "resume_session_start_begin",
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
// For resumed sessions, regenerate digest
|
|
211
|
+
const d = await session.memoryStore.sessionStart({
|
|
212
|
+
agent: APP_AGENT_ID,
|
|
213
|
+
user_id: hashString(process.env.USER ?? "unknown"),
|
|
214
|
+
time: Date.now(),
|
|
215
|
+
context_id: hashString(cwd),
|
|
216
|
+
context_label: basename(cwd),
|
|
217
|
+
working_context: { repo: { root: cwd, name: basename(cwd) } },
|
|
218
|
+
recall_min_score: cfg.compiler.recall_min_score ?? 0.35,
|
|
219
|
+
});
|
|
220
|
+
session.digest = d.markdown;
|
|
221
|
+
session.eventLog.append({
|
|
222
|
+
kind: "system_note",
|
|
223
|
+
actor: "kernel",
|
|
224
|
+
payload: {
|
|
225
|
+
type: "memory_lifecycle",
|
|
226
|
+
phase: "resume_session_start_success",
|
|
227
|
+
digestLength: session.digest.length,
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
session.getLogger().child("memory").warn("Failed to initialize for resumed session", {
|
|
233
|
+
code: "MEMORY_INIT_FAILED",
|
|
234
|
+
cause: err,
|
|
235
|
+
});
|
|
236
|
+
session.eventLog.append({
|
|
237
|
+
kind: "system_note",
|
|
238
|
+
actor: "kernel",
|
|
239
|
+
payload: {
|
|
240
|
+
type: "memory_lifecycle",
|
|
241
|
+
phase: "resume_session_start_failure",
|
|
242
|
+
error: err.message,
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
session.memoryEnabled = false;
|
|
246
|
+
session.memoryStore = null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
await session.refreshModelContextWindow().catch((err) => {
|
|
250
|
+
session.getLogger().child("session").warn("Failed to prefetch context window", {
|
|
251
|
+
cause: err,
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
return session;
|
|
255
|
+
}
|
|
256
|
+
isContextEngineEnabled() {
|
|
257
|
+
return isContextEngineEnabled(this.config);
|
|
258
|
+
}
|
|
259
|
+
getTurnCount() {
|
|
260
|
+
return this.turnCount;
|
|
261
|
+
}
|
|
262
|
+
incrementTurn() {
|
|
263
|
+
this.turnCount++;
|
|
264
|
+
this.stateGraph.incrementTurn();
|
|
265
|
+
}
|
|
266
|
+
clearState() {
|
|
267
|
+
this.stateGraph.clear();
|
|
268
|
+
}
|
|
269
|
+
getStartedAt() {
|
|
270
|
+
return this.startedAt;
|
|
271
|
+
}
|
|
272
|
+
getUptimeMs() {
|
|
273
|
+
return Math.max(0, Date.now() - this.startedAt);
|
|
274
|
+
}
|
|
275
|
+
getActiveModelId() {
|
|
276
|
+
return this.modelOverride ?? this.config.llm.model;
|
|
277
|
+
}
|
|
278
|
+
getLogger() {
|
|
279
|
+
if (!this.sessionLogger) {
|
|
280
|
+
return getAppLogger().child("session");
|
|
281
|
+
}
|
|
282
|
+
return this.sessionLogger;
|
|
283
|
+
}
|
|
284
|
+
async initLogger() {
|
|
285
|
+
if (this.sessionLogger)
|
|
286
|
+
return;
|
|
287
|
+
this.sessionLogger = await createSessionLogger({
|
|
288
|
+
sessionId: this.id,
|
|
289
|
+
sessionLogDir: this.config.session.log_dir,
|
|
290
|
+
debug: this.debug,
|
|
291
|
+
captureNotice: this.noticeCapture,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
getContextWindowTokens(modelId) {
|
|
295
|
+
const id = modelId ?? this.getActiveModelId();
|
|
296
|
+
if (this.modelContextWindowFor === id && this.modelContextWindow !== null) {
|
|
297
|
+
return this.modelContextWindow;
|
|
298
|
+
}
|
|
299
|
+
return resolveContextWindowSync(this.config.llm.provider, id, this.config.llm.context_window);
|
|
300
|
+
}
|
|
301
|
+
async refreshModelContextWindow(modelId) {
|
|
302
|
+
const id = modelId ?? this.getActiveModelId();
|
|
303
|
+
const window = await fetchAndCacheContextWindow(this.config.llm.provider, id, this.config.llm.context_window);
|
|
304
|
+
this.modelContextWindow = window;
|
|
305
|
+
this.modelContextWindowFor = id;
|
|
306
|
+
return window;
|
|
307
|
+
}
|
|
308
|
+
async ensureModelContextWindow(modelId) {
|
|
309
|
+
const id = modelId ?? this.getActiveModelId();
|
|
310
|
+
if (this.modelContextWindowFor === id && this.modelContextWindow !== null) {
|
|
311
|
+
return this.modelContextWindow;
|
|
312
|
+
}
|
|
313
|
+
return this.refreshModelContextWindow(id);
|
|
314
|
+
}
|
|
315
|
+
setModelOverride(model) {
|
|
316
|
+
const next = model && model.trim() ? model.trim() : null;
|
|
317
|
+
if (next === this.modelOverride)
|
|
318
|
+
return;
|
|
319
|
+
this.modelOverride = next;
|
|
320
|
+
this.modelContextWindow = null;
|
|
321
|
+
this.modelContextWindowFor = null;
|
|
322
|
+
}
|
|
323
|
+
getModelOverride() {
|
|
324
|
+
return this.modelOverride;
|
|
325
|
+
}
|
|
326
|
+
isIncognito() {
|
|
327
|
+
return this.incognito;
|
|
328
|
+
}
|
|
329
|
+
recordInputTokens(count) {
|
|
330
|
+
this.sessionInputTokens += count;
|
|
331
|
+
}
|
|
332
|
+
recordOutputTokens(count) {
|
|
333
|
+
this.sessionOutputTokens += count;
|
|
334
|
+
}
|
|
335
|
+
getInputTokens() {
|
|
336
|
+
return this.sessionInputTokens;
|
|
337
|
+
}
|
|
338
|
+
getOutputTokens() {
|
|
339
|
+
return this.sessionOutputTokens;
|
|
340
|
+
}
|
|
341
|
+
async setIncognito(enabled) {
|
|
342
|
+
this.incognito = enabled;
|
|
343
|
+
if (enabled) {
|
|
344
|
+
this.memoryEnabled = false;
|
|
345
|
+
this.memoryStore = null;
|
|
346
|
+
this.digest = null;
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (!this.config.memory.enabled)
|
|
350
|
+
return;
|
|
351
|
+
try {
|
|
352
|
+
this.memoryStore = await this.initMemoryStore();
|
|
353
|
+
const d = await this.memoryStore.sessionStart({
|
|
354
|
+
agent: APP_AGENT_ID,
|
|
355
|
+
user_id: hashString(process.env.USER ?? "unknown"),
|
|
356
|
+
time: Date.now(),
|
|
357
|
+
context_id: hashString(this.cwd),
|
|
358
|
+
context_label: basename(this.cwd),
|
|
359
|
+
working_context: { repo: { root: this.cwd, name: basename(this.cwd) } },
|
|
360
|
+
recall_min_score: this.config.compiler.recall_min_score ?? 0.35,
|
|
361
|
+
});
|
|
362
|
+
this.digest = d.markdown;
|
|
363
|
+
this.memoryEnabled = true;
|
|
364
|
+
}
|
|
365
|
+
catch (err) {
|
|
366
|
+
this.getLogger().child("memory").warn("Failed to re-enable memory", {
|
|
367
|
+
code: "MEMORY_INIT_FAILED",
|
|
368
|
+
cause: err,
|
|
369
|
+
});
|
|
370
|
+
this.memoryEnabled = false;
|
|
371
|
+
this.memoryStore = null;
|
|
372
|
+
this.digest = null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
get promptDir() {
|
|
376
|
+
return join(this.config.session.log_dir, this.id, "prompts");
|
|
377
|
+
}
|
|
378
|
+
getMemoryDbPath() {
|
|
379
|
+
const configuredPath = this.config.memory?.db_path;
|
|
380
|
+
if (configuredPath) {
|
|
381
|
+
const p = expandHome(configuredPath);
|
|
382
|
+
return p.startsWith("/") ? p : join(this.cwd, p);
|
|
383
|
+
}
|
|
384
|
+
return resolveDefaultMemoryDbPath();
|
|
385
|
+
}
|
|
386
|
+
getRepoRoot() {
|
|
387
|
+
return findGitRoot(this.cwd);
|
|
388
|
+
}
|
|
389
|
+
setLastCompileMetrics(metrics) {
|
|
390
|
+
this.lastCompileMetrics = metrics;
|
|
391
|
+
}
|
|
392
|
+
getLastCompileMetrics() {
|
|
393
|
+
return this.lastCompileMetrics;
|
|
394
|
+
}
|
|
395
|
+
setLastCompileScoreRecords(records, pressureMode, pressureRatio) {
|
|
396
|
+
this.lastCompileScoreRecords = records;
|
|
397
|
+
this.lastPressureMode = pressureMode;
|
|
398
|
+
this.lastPressureRatio = pressureRatio;
|
|
399
|
+
}
|
|
400
|
+
getLastCompileScoreRecords() {
|
|
401
|
+
return this.lastCompileScoreRecords;
|
|
402
|
+
}
|
|
403
|
+
getCompileScoreRecord(unitId) {
|
|
404
|
+
return this.lastCompileScoreRecords.find((r) => r.unitId === unitId);
|
|
405
|
+
}
|
|
406
|
+
getLastPressureMode() {
|
|
407
|
+
return this.lastPressureMode;
|
|
408
|
+
}
|
|
409
|
+
getLastPressureRatio() {
|
|
410
|
+
return this.lastPressureRatio;
|
|
411
|
+
}
|
|
412
|
+
setLastUserInput(input) {
|
|
413
|
+
this.lastUserInput = input;
|
|
414
|
+
}
|
|
415
|
+
getLastUserInput() {
|
|
416
|
+
return this.lastUserInput;
|
|
417
|
+
}
|
|
418
|
+
isCompactionArmed() {
|
|
419
|
+
return this.compactionArmed;
|
|
420
|
+
}
|
|
421
|
+
setCompactionArmed(armed) {
|
|
422
|
+
this.compactionArmed = armed;
|
|
423
|
+
}
|
|
424
|
+
getMemoryStats() {
|
|
425
|
+
const snapshot = this.stateGraph.snapshot();
|
|
426
|
+
const byKind = {};
|
|
427
|
+
let active = 0;
|
|
428
|
+
let soft = 0;
|
|
429
|
+
let hard = 0;
|
|
430
|
+
for (const obj of snapshot) {
|
|
431
|
+
byKind[obj.kind] = (byKind[obj.kind] ?? 0) + 1;
|
|
432
|
+
if (obj.tier === "active")
|
|
433
|
+
active++;
|
|
434
|
+
else if (obj.tier === "soft")
|
|
435
|
+
soft++;
|
|
436
|
+
else
|
|
437
|
+
hard++;
|
|
438
|
+
}
|
|
439
|
+
return { total: snapshot.length, active, soft, hard, byKind };
|
|
440
|
+
}
|
|
441
|
+
getPersistentMemoryEntryCount() {
|
|
442
|
+
if (!this.memoryEnabled || !this.memoryStore)
|
|
443
|
+
return null;
|
|
444
|
+
return this.memoryStore.getEntryCount();
|
|
445
|
+
}
|
|
446
|
+
getSessionSummary() {
|
|
447
|
+
const events = this.eventLog.readAll();
|
|
448
|
+
let turns = 0;
|
|
449
|
+
let memoriesStored = 0;
|
|
450
|
+
for (const ev of events) {
|
|
451
|
+
if (ev.kind === "user_message")
|
|
452
|
+
turns++;
|
|
453
|
+
if (ev.kind !== "tool_result")
|
|
454
|
+
continue;
|
|
455
|
+
if (ev.payload.tool !== "remember")
|
|
456
|
+
continue;
|
|
457
|
+
const result = ev.payload.result;
|
|
458
|
+
if (result?.ok === true)
|
|
459
|
+
memoriesStored++;
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
turns,
|
|
463
|
+
stateObjects: this.stateGraph.snapshot().length,
|
|
464
|
+
memoriesStored,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
/** Build a transcript of user/agent/tool events for the summarizer. */
|
|
468
|
+
getTranscriptEvents() {
|
|
469
|
+
const all = this.eventLog.readAll();
|
|
470
|
+
const out = [];
|
|
471
|
+
for (const ev of all) {
|
|
472
|
+
if (ev.kind === "user_message" || ev.kind === "agent_message") {
|
|
473
|
+
out.push({ type: ev.kind, timestamp: ev.timestamp, content: ev.payload.text ?? "" });
|
|
474
|
+
}
|
|
475
|
+
else if (ev.kind === "tool_call") {
|
|
476
|
+
out.push({ type: "tool_use", timestamp: ev.timestamp, tool_name: ev.payload.tool, args: ev.payload.args });
|
|
477
|
+
}
|
|
478
|
+
else if (ev.kind === "tool_result") {
|
|
479
|
+
out.push({ type: "tool_result", timestamp: ev.timestamp, tool_name: ev.payload.tool, result: ev.payload.result });
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return out;
|
|
483
|
+
}
|
|
484
|
+
async end(reason, events, opts) {
|
|
485
|
+
if (this.ended)
|
|
486
|
+
return;
|
|
487
|
+
this.ended = true;
|
|
488
|
+
if (this.memoryEnabled && this.memoryStore) {
|
|
489
|
+
const memoryTimeoutMs = opts?.memoryTimeoutMs ?? 0;
|
|
490
|
+
try {
|
|
491
|
+
this.eventLog.append({
|
|
492
|
+
kind: "system_note",
|
|
493
|
+
actor: "kernel",
|
|
494
|
+
payload: {
|
|
495
|
+
type: "memory_lifecycle",
|
|
496
|
+
phase: "session_end_begin",
|
|
497
|
+
reason,
|
|
498
|
+
},
|
|
499
|
+
});
|
|
500
|
+
const finish = this.memoryStore.sessionEnd(reason, events);
|
|
501
|
+
if (memoryTimeoutMs > 0) {
|
|
502
|
+
const completed = await waitForCompletion(finish, memoryTimeoutMs);
|
|
503
|
+
if (completed) {
|
|
504
|
+
this.eventLog.append({
|
|
505
|
+
kind: "system_note",
|
|
506
|
+
actor: "kernel",
|
|
507
|
+
payload: {
|
|
508
|
+
type: "memory_lifecycle",
|
|
509
|
+
phase: "session_end_success",
|
|
510
|
+
reason,
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
// Ensure late failures are not unhandled after we stop waiting.
|
|
516
|
+
void finish.catch((err) => {
|
|
517
|
+
this.getLogger().child("memory").warn("Background session-end task failed", {
|
|
518
|
+
cause: err,
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
this.getLogger().child("memory").warn("Session-end summarization is continuing in background");
|
|
522
|
+
this.eventLog.append({
|
|
523
|
+
kind: "system_note",
|
|
524
|
+
actor: "kernel",
|
|
525
|
+
payload: {
|
|
526
|
+
type: "memory_lifecycle",
|
|
527
|
+
phase: "session_end_background",
|
|
528
|
+
reason,
|
|
529
|
+
timeoutMs: memoryTimeoutMs,
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
await finish;
|
|
536
|
+
this.eventLog.append({
|
|
537
|
+
kind: "system_note",
|
|
538
|
+
actor: "kernel",
|
|
539
|
+
payload: {
|
|
540
|
+
type: "memory_lifecycle",
|
|
541
|
+
phase: "session_end_success",
|
|
542
|
+
reason,
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
catch (err) {
|
|
548
|
+
this.getLogger().child("memory").warn("Error during session end", {
|
|
549
|
+
cause: err,
|
|
550
|
+
});
|
|
551
|
+
this.eventLog.append({
|
|
552
|
+
kind: "system_note",
|
|
553
|
+
actor: "kernel",
|
|
554
|
+
payload: {
|
|
555
|
+
type: "memory_lifecycle",
|
|
556
|
+
phase: "session_end_failure",
|
|
557
|
+
reason,
|
|
558
|
+
error: err.message,
|
|
559
|
+
},
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
// Spawn background consolidation processor if enabled
|
|
564
|
+
if (this.memoryEnabled && this.memoryStore && this.config.consolidation?.enabled) {
|
|
565
|
+
const consolidationConfig = {
|
|
566
|
+
enabled: true,
|
|
567
|
+
promotion_threshold: this.config.consolidation.promotion_threshold ?? 3,
|
|
568
|
+
run_delay_seconds: this.config.consolidation.run_delay_seconds ?? 30,
|
|
569
|
+
};
|
|
570
|
+
const sessionId = this.id;
|
|
571
|
+
const store = this.memoryStore;
|
|
572
|
+
const transcriptEvents = events ?? this.getTranscriptEvents();
|
|
573
|
+
setTimeout(async () => {
|
|
574
|
+
try {
|
|
575
|
+
const summarizer = store.getSummarizer();
|
|
576
|
+
if (!summarizer)
|
|
577
|
+
return;
|
|
578
|
+
const result = await runConsolidation({
|
|
579
|
+
store,
|
|
580
|
+
llm: summarizer,
|
|
581
|
+
sessionId,
|
|
582
|
+
events: transcriptEvents,
|
|
583
|
+
config: consolidationConfig,
|
|
584
|
+
});
|
|
585
|
+
if (result.promotions > 0) {
|
|
586
|
+
this.getLogger().child("memory").info(`Consolidated: ${result.promotions} patterns promoted to deep memory`);
|
|
587
|
+
}
|
|
588
|
+
if (result.newEntries > 0 || result.confirmations > 0) {
|
|
589
|
+
this.getLogger().child("memory").info(`Consolidation complete: ${result.newEntries} new, ${result.confirmations} confirmed, ${result.contradictions} contradicted`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
catch (err) {
|
|
593
|
+
this.getLogger().child("memory").warn("Background consolidation failed", {
|
|
594
|
+
cause: err,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
}, consolidationConfig.run_delay_seconds * 1000);
|
|
598
|
+
}
|
|
599
|
+
if (this.contextEngine) {
|
|
600
|
+
try {
|
|
601
|
+
const engineConfig = resolveContextEngineConfig(this.config);
|
|
602
|
+
if (this.debug || engineConfig.measurement_mode) {
|
|
603
|
+
const summary = this.contextEngine.finalizeTelemetry(this.getTurnCount());
|
|
604
|
+
this.getLogger().child("context_engine").debug(renderSessionTelemetrySummary(summary));
|
|
605
|
+
}
|
|
606
|
+
this.contextEngine.runShutdownMaintenance(this.getTurnCount());
|
|
607
|
+
}
|
|
608
|
+
catch (err) {
|
|
609
|
+
this.getLogger().child("context_engine").warn("Shutdown maintenance failed", {
|
|
610
|
+
cause: err,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
finally {
|
|
614
|
+
this.contextEngine.close();
|
|
615
|
+
this.contextEngine = null;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
this.eventLog.close();
|
|
619
|
+
}
|
|
620
|
+
async initMemoryStore() {
|
|
621
|
+
const configuredPath = this.config.memory?.db_path;
|
|
622
|
+
let dbPath;
|
|
623
|
+
if (configuredPath) {
|
|
624
|
+
dbPath = expandHome(configuredPath);
|
|
625
|
+
if (!dbPath.startsWith("/"))
|
|
626
|
+
dbPath = join(this.cwd, dbPath);
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
dbPath = resolveDefaultMemoryDbPath();
|
|
630
|
+
}
|
|
631
|
+
const embedder = await createEmbedder(this.config.memory);
|
|
632
|
+
const summarizer = await createSummarizer(this.config.memory);
|
|
633
|
+
return new MemoryStore({ dbPath, embedder, summarizer });
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
// ---- Helpers ----
|
|
637
|
+
function initContextEngine(session) {
|
|
638
|
+
if (!session.isContextEngineEnabled())
|
|
639
|
+
return;
|
|
640
|
+
try {
|
|
641
|
+
const dbPath = resolveContextDbPath(session.config, session.cwd);
|
|
642
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
643
|
+
session.contextEngine = ContextEngine.open(dbPath, session.id, resolveContextEngineConfig(session.config));
|
|
644
|
+
const evicted = session.contextEngine.runStartupMaintenance(session.getTurnCount());
|
|
645
|
+
const migrated = session.contextEngine.migrateLedgerFromEvents(session.eventLog.readAll());
|
|
646
|
+
const ceLog = session.getLogger().child("context_engine");
|
|
647
|
+
if (evicted > 0) {
|
|
648
|
+
ceLog.notice(`context engine evicted ${evicted} stale artifact(s)`);
|
|
649
|
+
}
|
|
650
|
+
else if (migrated > 0) {
|
|
651
|
+
ceLog.notice(`context engine migrated ${migrated} turn(s) to ledger`);
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
ceLog.notice("context engine enabled");
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
catch (err) {
|
|
658
|
+
session.getLogger().child("context_engine").warn("Failed to initialize, continuing without context engine", {
|
|
659
|
+
cause: err,
|
|
660
|
+
});
|
|
661
|
+
session.contextEngine = null;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function hashString(s) {
|
|
665
|
+
return createHash("sha256").update(s).digest("hex").slice(0, 12);
|
|
666
|
+
}
|
|
667
|
+
function basename(p) {
|
|
668
|
+
return p.split("/").pop() ?? p;
|
|
669
|
+
}
|
|
670
|
+
function expandHome(p) {
|
|
671
|
+
return p.startsWith("~/") ? p.replace(/^~\//, `${homedir()}/`) : p;
|
|
672
|
+
}
|
|
673
|
+
async function waitForCompletion(promise, timeoutMs) {
|
|
674
|
+
const timeout = new Promise((resolve) => {
|
|
675
|
+
setTimeout(() => resolve(false), timeoutMs);
|
|
676
|
+
});
|
|
677
|
+
const done = promise.then(() => true);
|
|
678
|
+
return Promise.race([done, timeout]);
|
|
679
|
+
}
|
|
680
|
+
/** Find git root of the given directory, or return the directory itself. */
|
|
681
|
+
function findGitRoot(cwd) {
|
|
682
|
+
try {
|
|
683
|
+
return execSync("git rev-parse --show-toplevel", {
|
|
684
|
+
cwd,
|
|
685
|
+
encoding: "utf-8",
|
|
686
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
687
|
+
}).trim();
|
|
688
|
+
}
|
|
689
|
+
catch {
|
|
690
|
+
return cwd;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Load project context from AGENTS.md / CLAUDE.md files.
|
|
695
|
+
*
|
|
696
|
+
* Load order (all non-empty results are merged):
|
|
697
|
+
* 1. ~/.praana/AGENTS.md (or legacy ~/.aria/AGENTS.md) — global personal instructions
|
|
698
|
+
* 2. <git root>/AGENTS.md — project-wide context
|
|
699
|
+
* 3. <cwd>/AGENTS.md — subdirectory context (if cwd ≠ git root)
|
|
700
|
+
* 4. CLAUDE.md fallback — if no AGENTS.md found at project root
|
|
701
|
+
*
|
|
702
|
+
* Combined content is capped at ~4000 tokens (16000 chars).
|
|
703
|
+
*/
|
|
704
|
+
export function loadAgentsContext(cwd) {
|
|
705
|
+
const MAX_CHARS = 16_000; // ~4000 tokens
|
|
706
|
+
const parts = [];
|
|
707
|
+
const tryRead = (filePath, label) => {
|
|
708
|
+
if (!existsSync(filePath))
|
|
709
|
+
return;
|
|
710
|
+
try {
|
|
711
|
+
const content = readFileSync(filePath, "utf-8").trim();
|
|
712
|
+
if (content)
|
|
713
|
+
parts.push(`<!-- ${label} -->\n${content}`);
|
|
714
|
+
}
|
|
715
|
+
catch { /* unreadable, skip */ }
|
|
716
|
+
};
|
|
717
|
+
// 1. Global personal instructions
|
|
718
|
+
tryRead(resolveAppHomePath("AGENTS.md"), `~/${APP_HOME_DIR}/AGENTS.md`);
|
|
719
|
+
tryRead(expandHome(`~/${LEGACY_APP_HOME_DIR}/AGENTS.md`), `~/${LEGACY_APP_HOME_DIR}/AGENTS.md`);
|
|
720
|
+
// 2. Project root AGENTS.md
|
|
721
|
+
const gitRoot = findGitRoot(cwd);
|
|
722
|
+
tryRead(join(gitRoot, "AGENTS.md"), "AGENTS.md");
|
|
723
|
+
// 3. Subdirectory AGENTS.md (only if cwd differs from git root)
|
|
724
|
+
if (cwd !== gitRoot) {
|
|
725
|
+
tryRead(join(cwd, "AGENTS.md"), `${basename(cwd)}/AGENTS.md`);
|
|
726
|
+
}
|
|
727
|
+
// 4. CLAUDE.md fallback — only if no AGENTS.md was found at project root
|
|
728
|
+
if (!existsSync(join(gitRoot, "AGENTS.md"))) {
|
|
729
|
+
tryRead(join(gitRoot, "CLAUDE.md"), "CLAUDE.md");
|
|
730
|
+
if (cwd !== gitRoot) {
|
|
731
|
+
tryRead(join(cwd, "CLAUDE.md"), `${basename(cwd)}/CLAUDE.md`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (parts.length === 0)
|
|
735
|
+
return null;
|
|
736
|
+
const combined = parts.join("\n\n");
|
|
737
|
+
if (combined.length > MAX_CHARS) {
|
|
738
|
+
getAppLogger().child("session").warn("AGENTS.md content truncated to ~4000 tokens", {
|
|
739
|
+
details: { tokenEstimate: Math.ceil(combined.length / 4) },
|
|
740
|
+
});
|
|
741
|
+
return combined.slice(0, MAX_CHARS) + "\n\n<!-- [truncated] -->";
|
|
742
|
+
}
|
|
743
|
+
return combined;
|
|
744
|
+
}
|
|
745
|
+
export { buildProjectContext };
|
|
746
|
+
function loadProjectContextField(session, cwd) {
|
|
747
|
+
if (!session.config.project_detection?.enabled) {
|
|
748
|
+
session.projectContext = null;
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
session.projectContext = buildProjectContext(cwd, {
|
|
752
|
+
languages: session.config.project_detection.manual_languages,
|
|
753
|
+
frameworks: session.config.project_detection.manual_frameworks,
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
/** Load stack fingerprint on session start; StateGraph constraint only for engine mode. */
|
|
757
|
+
function applyProjectContext(session, cwd) {
|
|
758
|
+
loadProjectContextField(session, cwd);
|
|
759
|
+
const projectContext = session.projectContext;
|
|
760
|
+
if (!projectContext)
|
|
761
|
+
return;
|
|
762
|
+
session.eventLog.append({
|
|
763
|
+
kind: "system_note",
|
|
764
|
+
actor: "kernel",
|
|
765
|
+
payload: {
|
|
766
|
+
type: "project_context_loaded",
|
|
767
|
+
summary: projectContext.slice(0, 100),
|
|
768
|
+
},
|
|
769
|
+
});
|
|
770
|
+
session.getLogger().notice(`fingerprint ${Math.ceil(projectContext.length / 4)} tok`, {
|
|
771
|
+
domain: "session",
|
|
772
|
+
});
|
|
773
|
+
const engineActive = session.isContextEngineEnabled() && session.contextEngine !== null;
|
|
774
|
+
if (engineActive) {
|
|
775
|
+
session.stateGraph.create("constraint", { text: projectContext });
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
/** Discover skills — SkillRuntime in engine mode, metadata catalog only in classic mode. */
|
|
779
|
+
async function initSkills(session, cfg, cwd) {
|
|
780
|
+
if (!cfg.skills?.enabled)
|
|
781
|
+
return;
|
|
782
|
+
if (session.isContextEngineEnabled() && session.contextEngine) {
|
|
783
|
+
await initSkillRuntime(session, cfg, cwd);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
session.skillRuntime = null;
|
|
787
|
+
session.skills = discoverSkills(cwd, cfg.skills.max_depth);
|
|
788
|
+
if (session.skills.length > 0) {
|
|
789
|
+
session.getLogger().child("skills").notice(`${session.skills.length} skill(s) (classic catalog)`);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
/** Discover skills and attach SkillRuntime to the session (residency resets on resume). */
|
|
793
|
+
async function initSkillRuntime(session, cfg, cwd) {
|
|
794
|
+
session.skillRuntime = new SkillRuntime(cfg.skills, cwd);
|
|
795
|
+
if (!cfg.skills?.enabled)
|
|
796
|
+
return;
|
|
797
|
+
await session.skillRuntime.initialize();
|
|
798
|
+
session.skills = session.skillRuntime.getIndex().map((e) => ({
|
|
799
|
+
name: e.name,
|
|
800
|
+
description: e.description,
|
|
801
|
+
location: "",
|
|
802
|
+
directory: "",
|
|
803
|
+
body: "",
|
|
804
|
+
metadata: { name: e.name, description: e.description },
|
|
805
|
+
}));
|
|
806
|
+
if (session.skills.length > 0) {
|
|
807
|
+
session.getLogger().child("skills").notice(`${session.skills.length} skill(s)`);
|
|
808
|
+
}
|
|
809
|
+
}
|