@openacp/cli 0.6.5 → 0.6.6

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.
@@ -773,6 +773,7 @@ var Session = class extends TypedEmitter {
773
773
  permissionGate = new PermissionGate();
774
774
  queue;
775
775
  speechService;
776
+ pendingContext = null;
776
777
  constructor(opts) {
777
778
  super();
778
779
  this.id = opts.id || nanoid(12);
@@ -832,6 +833,10 @@ var Session = class extends TypedEmitter {
832
833
  get promptRunning() {
833
834
  return this.queue.isProcessing;
834
835
  }
836
+ // --- Context Injection ---
837
+ setContext(markdown) {
838
+ this.pendingContext = markdown;
839
+ }
835
840
  // --- Voice Mode ---
836
841
  setVoiceMode(mode) {
837
842
  this.voiceMode = mode;
@@ -851,6 +856,17 @@ var Session = class extends TypedEmitter {
851
856
  }
852
857
  const promptStart = Date.now();
853
858
  this.log.debug("Prompt execution started");
859
+ if (this.pendingContext) {
860
+ text = `[CONVERSATION HISTORY - This is context from previous sessions, not current conversation]
861
+
862
+ ${this.pendingContext}
863
+
864
+ [END CONVERSATION HISTORY]
865
+
866
+ ${text}`;
867
+ this.pendingContext = null;
868
+ this.log.debug("Context injected into prompt");
869
+ }
854
870
  const processed = await this.maybeTranscribeAudio(text, attachments);
855
871
  const ttsActive = this.voiceMode !== "off" && !!this.speechService?.isTTSAvailable();
856
872
  if (ttsActive) {
@@ -1341,12 +1357,12 @@ var SessionBridge = class {
1341
1357
  break;
1342
1358
  case "image_content": {
1343
1359
  if (this.deps.fileService) {
1344
- const fs8 = this.deps.fileService;
1360
+ const fs9 = this.deps.fileService;
1345
1361
  const sid = this.session.id;
1346
1362
  const { data, mimeType } = event;
1347
1363
  const buffer = Buffer.from(data, "base64");
1348
1364
  const ext = FileService.extensionFromMime(mimeType);
1349
- fs8.saveFile(sid, `agent-image${ext}`, buffer, mimeType).then((att) => {
1365
+ fs9.saveFile(sid, `agent-image${ext}`, buffer, mimeType).then((att) => {
1350
1366
  this.adapter.sendMessage(sid, {
1351
1367
  type: "attachment",
1352
1368
  text: "",
@@ -1358,12 +1374,12 @@ var SessionBridge = class {
1358
1374
  }
1359
1375
  case "audio_content": {
1360
1376
  if (this.deps.fileService) {
1361
- const fs8 = this.deps.fileService;
1377
+ const fs9 = this.deps.fileService;
1362
1378
  const sid = this.session.id;
1363
1379
  const { data, mimeType } = event;
1364
1380
  const buffer = Buffer.from(data, "base64");
1365
1381
  const ext = FileService.extensionFromMime(mimeType);
1366
- fs8.saveFile(sid, `agent-audio${ext}`, buffer, mimeType).then((att) => {
1382
+ fs9.saveFile(sid, `agent-audio${ext}`, buffer, mimeType).then((att) => {
1367
1383
  this.adapter.sendMessage(sid, {
1368
1384
  type: "attachment",
1369
1385
  text: "",
@@ -2129,13 +2145,791 @@ var EdgeTTS = class {
2129
2145
  }
2130
2146
  };
2131
2147
 
2148
+ // src/core/context/context-manager.ts
2149
+ import * as os from "os";
2150
+ import * as path5 from "path";
2151
+
2152
+ // src/core/context/context-cache.ts
2153
+ import * as fs5 from "fs";
2154
+ import * as path4 from "path";
2155
+ import * as crypto from "crypto";
2156
+ var DEFAULT_TTL_MS = 60 * 60 * 1e3;
2157
+ var ContextCache = class {
2158
+ constructor(cacheDir, ttlMs = DEFAULT_TTL_MS) {
2159
+ this.cacheDir = cacheDir;
2160
+ this.ttlMs = ttlMs;
2161
+ fs5.mkdirSync(cacheDir, { recursive: true });
2162
+ }
2163
+ keyHash(repoPath, queryKey) {
2164
+ return crypto.createHash("sha256").update(`${repoPath}:${queryKey}`).digest("hex").slice(0, 16);
2165
+ }
2166
+ filePath(repoPath, queryKey) {
2167
+ return path4.join(this.cacheDir, `${this.keyHash(repoPath, queryKey)}.json`);
2168
+ }
2169
+ get(repoPath, queryKey) {
2170
+ const fp = this.filePath(repoPath, queryKey);
2171
+ try {
2172
+ const stat = fs5.statSync(fp);
2173
+ if (Date.now() - stat.mtimeMs > this.ttlMs) {
2174
+ fs5.unlinkSync(fp);
2175
+ return null;
2176
+ }
2177
+ return JSON.parse(fs5.readFileSync(fp, "utf-8"));
2178
+ } catch {
2179
+ return null;
2180
+ }
2181
+ }
2182
+ set(repoPath, queryKey, result) {
2183
+ fs5.writeFileSync(this.filePath(repoPath, queryKey), JSON.stringify(result));
2184
+ }
2185
+ };
2186
+
2187
+ // src/core/context/context-manager.ts
2188
+ var ContextManager = class {
2189
+ providers = [];
2190
+ cache;
2191
+ constructor() {
2192
+ this.cache = new ContextCache(path5.join(os.homedir(), ".openacp", "cache", "entire"));
2193
+ }
2194
+ register(provider) {
2195
+ this.providers.push(provider);
2196
+ }
2197
+ async getProvider(repoPath) {
2198
+ for (const provider of this.providers) {
2199
+ if (await provider.isAvailable(repoPath)) return provider;
2200
+ }
2201
+ return null;
2202
+ }
2203
+ async listSessions(query) {
2204
+ const provider = await this.getProvider(query.repoPath);
2205
+ if (!provider) return null;
2206
+ return provider.listSessions(query);
2207
+ }
2208
+ async buildContext(query, options) {
2209
+ const queryKey = `${query.type}:${query.value}:${options?.limit ?? ""}:${options?.maxTokens ?? ""}`;
2210
+ const cached = this.cache.get(query.repoPath, queryKey);
2211
+ if (cached) return cached;
2212
+ const provider = await this.getProvider(query.repoPath);
2213
+ if (!provider) return null;
2214
+ const result = await provider.buildContext(query, options);
2215
+ if (result) this.cache.set(query.repoPath, queryKey, result);
2216
+ return result;
2217
+ }
2218
+ };
2219
+
2220
+ // src/core/context/context-provider.ts
2221
+ var DEFAULT_MAX_TOKENS = 3e4;
2222
+ var TOKENS_PER_TURN_ESTIMATE = 400;
2223
+
2224
+ // src/core/context/entire/checkpoint-reader.ts
2225
+ import { execFileSync as execFileSync2 } from "child_process";
2226
+ var ENTIRE_BRANCH = "origin/entire/checkpoints/v1";
2227
+ var CHECKPOINT_ID_RE = /^[0-9a-f]{12}$/;
2228
+ var SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
2229
+ var CheckpointReader = class _CheckpointReader {
2230
+ constructor(repoPath) {
2231
+ this.repoPath = repoPath;
2232
+ }
2233
+ // ─── Git execution ───────────────────────────────────────────────────────────
2234
+ /**
2235
+ * Run a git command in the repo directory.
2236
+ * Returns trimmed stdout on success, empty string on failure.
2237
+ */
2238
+ git(...args) {
2239
+ try {
2240
+ return execFileSync2("git", ["-C", this.repoPath, ...args], {
2241
+ encoding: "utf-8"
2242
+ }).trim();
2243
+ } catch {
2244
+ return "";
2245
+ }
2246
+ }
2247
+ // ─── Static helpers ──────────────────────────────────────────────────────────
2248
+ /**
2249
+ * Convert a 12-char checkpoint ID to its shard path: "f634acf05138" → "f6/34acf05138"
2250
+ */
2251
+ static shardPath(cpId) {
2252
+ return `${cpId.slice(0, 2)}/${cpId.slice(2)}`;
2253
+ }
2254
+ /**
2255
+ * Returns true when value looks like a 12-char lowercase hex checkpoint ID.
2256
+ */
2257
+ static isCheckpointId(value) {
2258
+ return CHECKPOINT_ID_RE.test(value);
2259
+ }
2260
+ /**
2261
+ * Returns true when value looks like a UUID (session ID).
2262
+ */
2263
+ static isSessionId(value) {
2264
+ return SESSION_ID_RE.test(value);
2265
+ }
2266
+ /**
2267
+ * Parse checkpoint-level metadata JSON. Returns null on error.
2268
+ */
2269
+ static parseCheckpointMeta(json) {
2270
+ try {
2271
+ const parsed = JSON.parse(json);
2272
+ if (!parsed || typeof parsed !== "object") return null;
2273
+ if (!Array.isArray(parsed.sessions)) return null;
2274
+ return parsed;
2275
+ } catch {
2276
+ return null;
2277
+ }
2278
+ }
2279
+ /**
2280
+ * Extract Entire-Checkpoint trailer IDs from `git log --format="%H|%(trailers:...)"` output.
2281
+ * Each line is: `<hash>|<trailer_value_or_empty>`. Returns only non-empty trailer values.
2282
+ * Uses the last pipe on each line to locate the trailer value, to be robust against
2283
+ * subject lines that contain pipes.
2284
+ */
2285
+ static parseCheckpointTrailers(output) {
2286
+ const ids = [];
2287
+ for (const line of output.split("\n")) {
2288
+ const pipe = line.lastIndexOf("|");
2289
+ if (pipe === -1) continue;
2290
+ const trailerId = line.slice(pipe + 1).trim();
2291
+ if (trailerId) ids.push(trailerId);
2292
+ }
2293
+ return ids;
2294
+ }
2295
+ // ─── Branch check ────────────────────────────────────────────────────────────
2296
+ async hasEntireBranch() {
2297
+ const out = this.git("branch", "-r");
2298
+ return out.includes("entire/checkpoints/v1");
2299
+ }
2300
+ // ─── Core session fetching ───────────────────────────────────────────────────
2301
+ listAllCheckpointIds() {
2302
+ const out = this.git(
2303
+ "ls-tree",
2304
+ "-r",
2305
+ ENTIRE_BRANCH,
2306
+ "--name-only"
2307
+ );
2308
+ if (!out) return [];
2309
+ const ids = /* @__PURE__ */ new Set();
2310
+ for (const file of out.split("\n")) {
2311
+ const parts = file.split("/");
2312
+ if (parts.length === 3 && parts[2] === "metadata.json") {
2313
+ ids.add(parts[0] + parts[1]);
2314
+ }
2315
+ }
2316
+ return [...ids];
2317
+ }
2318
+ fetchCheckpointMeta(cpId) {
2319
+ const shard = _CheckpointReader.shardPath(cpId);
2320
+ const raw = this.git("show", `${ENTIRE_BRANCH}:${shard}/metadata.json`);
2321
+ if (!raw) return null;
2322
+ return _CheckpointReader.parseCheckpointMeta(raw);
2323
+ }
2324
+ fetchSessionMeta(metaPath) {
2325
+ const normalized = metaPath.startsWith("/") ? metaPath.slice(1) : metaPath;
2326
+ const raw = this.git("show", `${ENTIRE_BRANCH}:${normalized}`);
2327
+ if (!raw) return {};
2328
+ try {
2329
+ return JSON.parse(raw);
2330
+ } catch {
2331
+ return {};
2332
+ }
2333
+ }
2334
+ /**
2335
+ * Build SessionInfo[] from a single checkpoint's metadata.
2336
+ */
2337
+ buildSessionsForCheckpoint(cpId, cpMeta) {
2338
+ const sessions = [];
2339
+ for (let idx = 0; idx < cpMeta.sessions.length; idx++) {
2340
+ const sess = cpMeta.sessions[idx];
2341
+ const transcriptPath = (sess.transcript ?? "").replace(/^\//, "");
2342
+ const metaPath = sess.metadata ?? "";
2343
+ const smeta = this.fetchSessionMeta(metaPath);
2344
+ const createdAt = smeta.created_at ?? "";
2345
+ sessions.push({
2346
+ checkpointId: cpId,
2347
+ sessionIndex: String(idx),
2348
+ transcriptPath,
2349
+ createdAt,
2350
+ endedAt: createdAt,
2351
+ // will be filled from JSONL by conversation builder
2352
+ branch: smeta.branch ?? cpMeta.branch ?? "",
2353
+ agent: smeta.agent ?? "",
2354
+ turnCount: smeta.session_metrics?.turn_count ?? 0,
2355
+ filesTouched: smeta.files_touched ?? cpMeta.files_touched ?? [],
2356
+ sessionId: smeta.session_id ?? ""
2357
+ });
2358
+ }
2359
+ return sessions;
2360
+ }
2361
+ getSessionsForCheckpoint(cpId) {
2362
+ const meta = this.fetchCheckpointMeta(cpId);
2363
+ if (!meta) return [];
2364
+ return this.buildSessionsForCheckpoint(cpId, meta);
2365
+ }
2366
+ // ─── Public resolvers ────────────────────────────────────────────────────────
2367
+ /**
2368
+ * All sessions recorded on a given branch, sorted by createdAt ascending.
2369
+ */
2370
+ async resolveByBranch(branchName) {
2371
+ const cpIds = this.listAllCheckpointIds();
2372
+ const sessions = [];
2373
+ for (const cpId of cpIds) {
2374
+ const meta = this.fetchCheckpointMeta(cpId);
2375
+ if (!meta) continue;
2376
+ if (meta.branch !== branchName) continue;
2377
+ sessions.push(...this.buildSessionsForCheckpoint(cpId, meta));
2378
+ }
2379
+ sessions.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
2380
+ return sessions;
2381
+ }
2382
+ /**
2383
+ * Sessions linked to a specific commit via the Entire-Checkpoint git trailer.
2384
+ */
2385
+ async resolveByCommit(commitHash) {
2386
+ const fullHash = this.git("rev-parse", commitHash);
2387
+ if (!fullHash) return [];
2388
+ const cpId = this.git(
2389
+ "log",
2390
+ "-1",
2391
+ "--format=%(trailers:key=Entire-Checkpoint,valueonly)",
2392
+ fullHash
2393
+ );
2394
+ if (!cpId) return [];
2395
+ return this.getSessionsForCheckpoint(cpId.trim());
2396
+ }
2397
+ /**
2398
+ * All sessions from a merged PR (by number or GitHub URL).
2399
+ */
2400
+ async resolveByPr(prInput) {
2401
+ let prNumber;
2402
+ if (/^\d+$/.test(prInput)) {
2403
+ prNumber = prInput;
2404
+ } else {
2405
+ const m = /\/pull\/(\d+)/.exec(prInput);
2406
+ if (!m) return [];
2407
+ prNumber = m[1];
2408
+ }
2409
+ const mergeOut = this.git(
2410
+ "log",
2411
+ "--all",
2412
+ "--oneline",
2413
+ "--grep",
2414
+ `Merge pull request #${prNumber}`
2415
+ );
2416
+ if (!mergeOut) return [];
2417
+ const mergeCommit = mergeOut.split("\n")[0].split(" ")[0];
2418
+ const logOut = this.git(
2419
+ "log",
2420
+ "--format=%H|%(trailers:key=Entire-Checkpoint,valueonly)",
2421
+ `${mergeCommit}^2`,
2422
+ "--not",
2423
+ `${mergeCommit}^1`
2424
+ );
2425
+ if (!logOut) return [];
2426
+ const cpIds = _CheckpointReader.parseCheckpointTrailers(logOut);
2427
+ const sessions = [];
2428
+ for (const cpId of cpIds) {
2429
+ sessions.push(...this.getSessionsForCheckpoint(cpId));
2430
+ }
2431
+ sessions.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
2432
+ return sessions;
2433
+ }
2434
+ /**
2435
+ * Sessions matching a specific checkpoint ID.
2436
+ */
2437
+ async resolveByCheckpoint(checkpointId) {
2438
+ return this.getSessionsForCheckpoint(checkpointId);
2439
+ }
2440
+ /**
2441
+ * Find a session by its UUID.
2442
+ */
2443
+ async resolveBySessionId(sessionId) {
2444
+ const cpIds = this.listAllCheckpointIds();
2445
+ for (const cpId of cpIds) {
2446
+ const sessions = this.getSessionsForCheckpoint(cpId);
2447
+ const match = sessions.find((s) => s.sessionId === sessionId);
2448
+ if (match) return [match];
2449
+ }
2450
+ return [];
2451
+ }
2452
+ /**
2453
+ * Latest N sessions across all checkpoints, sorted by createdAt descending.
2454
+ */
2455
+ async resolveLatest(count) {
2456
+ const cpIds = this.listAllCheckpointIds();
2457
+ const all = [];
2458
+ for (const cpId of cpIds) {
2459
+ all.push(...this.getSessionsForCheckpoint(cpId));
2460
+ }
2461
+ all.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
2462
+ return all.slice(0, count);
2463
+ }
2464
+ /**
2465
+ * Read the full JSONL transcript content from the entire branch.
2466
+ */
2467
+ getTranscript(transcriptPath) {
2468
+ const normalized = transcriptPath.startsWith("/") ? transcriptPath.slice(1) : transcriptPath;
2469
+ return this.git("show", `${ENTIRE_BRANCH}:${normalized}`);
2470
+ }
2471
+ };
2472
+
2473
+ // src/core/context/entire/message-cleaner.ts
2474
+ var SYSTEM_TAG_PATTERNS = [
2475
+ /<system-reminder>[\s\S]*?<\/system-reminder>/g,
2476
+ /<local-command-caveat>[\s\S]*?<\/local-command-caveat>/g,
2477
+ /<local-command-stdout>[\s\S]*?<\/local-command-stdout>/g,
2478
+ /<command-name>[\s\S]*?<\/command-name>/g,
2479
+ /<command-message>[\s\S]*?<\/command-message>/g,
2480
+ /<user-prompt-submit-hook>[\s\S]*?<\/user-prompt-submit-hook>/g,
2481
+ /<ide_selection>[\s\S]*?<\/ide_selection>/g,
2482
+ /<ide_context>[\s\S]*?<\/ide_context>/g,
2483
+ /<ide_opened_file>[\s\S]*?<\/ide_opened_file>/g,
2484
+ /<cursor_context>[\s\S]*?<\/cursor_context>/g,
2485
+ /<attached_files>[\s\S]*?<\/attached_files>/g,
2486
+ /<repo_context>[\s\S]*?<\/repo_context>/g,
2487
+ /<task-notification>[\s\S]*?<\/task-notification>/g
2488
+ ];
2489
+ var COMMAND_ARGS_RE = /<command-args>([\s\S]*?)<\/command-args>/;
2490
+ function cleanSystemTags(text) {
2491
+ const argsMatch = COMMAND_ARGS_RE.exec(text);
2492
+ const userArgs = argsMatch?.[1]?.trim() ?? "";
2493
+ text = text.replace(/<command-args>[\s\S]*?<\/command-args>/g, "");
2494
+ for (const pat of SYSTEM_TAG_PATTERNS) {
2495
+ text = text.replace(new RegExp(pat.source, pat.flags), "");
2496
+ }
2497
+ text = text.trim();
2498
+ if (!text && userArgs) return userArgs;
2499
+ if (text && userArgs && text !== userArgs) return `${text}
2500
+ ${userArgs}`;
2501
+ return text || userArgs;
2502
+ }
2503
+ var SKILL_INDICATORS = [
2504
+ "Base directory for this skill:",
2505
+ "<HARD-GATE>",
2506
+ "## Checklist",
2507
+ "## Process Flow",
2508
+ "## Key Principles",
2509
+ "digraph brainstorming",
2510
+ "You MUST create a task for each"
2511
+ ];
2512
+ function isSkillPrompt(text) {
2513
+ for (const indicator of SKILL_INDICATORS) {
2514
+ if (text.includes(indicator)) return true;
2515
+ }
2516
+ if (text.length > 2e3) {
2517
+ const headerCount = (text.match(/## /g) || []).length;
2518
+ if (headerCount >= 3) return true;
2519
+ }
2520
+ return false;
2521
+ }
2522
+ function isNoiseMessage(text) {
2523
+ const cleaned = cleanSystemTags(text);
2524
+ if (!cleaned) return true;
2525
+ if (/^(ready|ready\.)$/i.test(cleaned)) return true;
2526
+ if (cleaned.includes("Tell your human partner that this command is deprecated")) return true;
2527
+ if (cleaned.startsWith("Read the output file to retrieve the result:")) return true;
2528
+ if (/^(opus|sonnet|haiku|claude)(\[.*\])?$/i.test(cleaned)) return true;
2529
+ return false;
2530
+ }
2531
+
2532
+ // src/core/context/entire/conversation-builder.ts
2533
+ function selectMode(totalTurns) {
2534
+ if (totalTurns <= 10) return "full";
2535
+ if (totalTurns <= 25) return "balanced";
2536
+ return "compact";
2537
+ }
2538
+ function estimateTokens(text) {
2539
+ return Math.floor(text.length / 4);
2540
+ }
2541
+ function shortenPath(fp) {
2542
+ const parts = fp.split("/");
2543
+ if (parts.length >= 2) return parts.slice(-2).join("/");
2544
+ return fp;
2545
+ }
2546
+ function countLines(s) {
2547
+ const trimmed = s.trim();
2548
+ if (!trimmed) return 0;
2549
+ return trimmed.split("\n").length;
2550
+ }
2551
+ function extractText(content) {
2552
+ if (typeof content === "string") return content;
2553
+ if (Array.isArray(content)) {
2554
+ return content.filter((b) => typeof b === "object" && b !== null && b.type === "text").map((b) => b.text).join("\n");
2555
+ }
2556
+ return "";
2557
+ }
2558
+ function extractContentBlocks(content) {
2559
+ if (typeof content === "string") return [{ type: "text", text: content }];
2560
+ if (Array.isArray(content)) {
2561
+ return content.filter((b) => typeof b === "object" && b !== null);
2562
+ }
2563
+ return [];
2564
+ }
2565
+ function isToolResultOnly(content) {
2566
+ if (typeof content === "string") return false;
2567
+ if (!Array.isArray(content)) return true;
2568
+ for (const block of content) {
2569
+ if (typeof block === "object" && block !== null) {
2570
+ const b = block;
2571
+ if (b.type === "text" && typeof b.text === "string" && b.text.trim()) return false;
2572
+ if (b.type === "image") return false;
2573
+ }
2574
+ }
2575
+ return true;
2576
+ }
2577
+ function hasImage(content) {
2578
+ if (!Array.isArray(content)) return false;
2579
+ return content.some((b) => typeof b === "object" && b !== null && b.type === "image");
2580
+ }
2581
+ function formatEditFull(filePath, oldStr, newStr) {
2582
+ const lines = [];
2583
+ lines.push(`\u270F\uFE0F \`${filePath}\``);
2584
+ lines.push("```diff");
2585
+ for (const line of oldStr.split("\n")) lines.push(`- ${line}`);
2586
+ for (const line of newStr.split("\n")) lines.push(`+ ${line}`);
2587
+ lines.push("```");
2588
+ return lines.join("\n");
2589
+ }
2590
+ function formatEditBalanced(filePath, oldStr, newStr, maxDiffLines = 12) {
2591
+ const oldLines = oldStr.split("\n");
2592
+ const newLines = newStr.split("\n");
2593
+ const total = oldLines.length + newLines.length;
2594
+ const lines = [];
2595
+ lines.push(`\u270F\uFE0F \`${filePath}\``);
2596
+ lines.push("```diff");
2597
+ if (total <= maxDiffLines) {
2598
+ for (const line of oldLines) lines.push(`- ${line}`);
2599
+ for (const line of newLines) lines.push(`+ ${line}`);
2600
+ } else {
2601
+ const half = Math.floor(maxDiffLines / 2);
2602
+ for (const line of oldLines.slice(0, half)) lines.push(`- ${line}`);
2603
+ if (oldLines.length > half) lines.push(` ... (-${oldLines.length} lines total)`);
2604
+ for (const line of newLines.slice(0, half)) lines.push(`+ ${line}`);
2605
+ if (newLines.length > half) lines.push(` ... (+${newLines.length} lines total)`);
2606
+ }
2607
+ lines.push("```");
2608
+ return lines.join("\n");
2609
+ }
2610
+ function formatEditCompact(filePath, oldStr, newStr) {
2611
+ const oldLines = countLines(oldStr);
2612
+ const newLines = countLines(newStr);
2613
+ let firstNew = "";
2614
+ for (const line of newStr.split("\n")) {
2615
+ const stripped = line.trim();
2616
+ if (stripped && !stripped.startsWith("//") && !stripped.startsWith("*")) {
2617
+ firstNew = stripped.slice(0, 80);
2618
+ break;
2619
+ }
2620
+ }
2621
+ if (firstNew) {
2622
+ return `\u270F\uFE0F \`${filePath}\` (-${oldLines}/+${newLines} lines): \`${firstNew}\``;
2623
+ }
2624
+ return `\u270F\uFE0F \`${filePath}\` (-${oldLines}/+${newLines} lines)`;
2625
+ }
2626
+ function formatWriteFull(filePath, content) {
2627
+ const lines = [];
2628
+ lines.push(`\u{1F4DD} \`${filePath}\``);
2629
+ lines.push("```");
2630
+ lines.push(content);
2631
+ lines.push("```");
2632
+ return lines.join("\n");
2633
+ }
2634
+ function formatWriteBalanced(filePath, content, maxLines = 15) {
2635
+ const contentLines = content.split("\n");
2636
+ const lines = [];
2637
+ lines.push(`\u{1F4DD} \`${filePath}\` (${contentLines.length} lines)`);
2638
+ lines.push("```");
2639
+ for (const line of contentLines.slice(0, maxLines)) lines.push(line);
2640
+ if (contentLines.length > maxLines) lines.push(`... (${contentLines.length - maxLines} more lines)`);
2641
+ lines.push("```");
2642
+ return lines.join("\n");
2643
+ }
2644
+ function formatWriteCompact(filePath, content) {
2645
+ const numLines = countLines(content);
2646
+ return `\u{1F4DD} \`${filePath}\` (${numLines} lines written)`;
2647
+ }
2648
+ function parseJsonlToTurns(jsonl) {
2649
+ const events = [];
2650
+ for (const rawLine of jsonl.split("\n")) {
2651
+ const line = rawLine.trim();
2652
+ if (!line) continue;
2653
+ try {
2654
+ events.push(JSON.parse(line));
2655
+ } catch {
2656
+ }
2657
+ }
2658
+ let branch = "unknown";
2659
+ for (const e of events) {
2660
+ if (e.gitBranch) {
2661
+ branch = e.gitBranch;
2662
+ break;
2663
+ }
2664
+ }
2665
+ const convEvents = events.filter((e) => e.type === "user" || e.type === "assistant");
2666
+ const turns = [];
2667
+ let currentTurn = null;
2668
+ for (const e of convEvents) {
2669
+ const etype = e.type;
2670
+ const content = e.message?.content ?? [];
2671
+ const ts = e.timestamp ?? "";
2672
+ if (etype === "user") {
2673
+ if (isToolResultOnly(content)) continue;
2674
+ const text = extractText(content);
2675
+ if (isSkillPrompt(text)) continue;
2676
+ if (isNoiseMessage(text)) continue;
2677
+ const cleaned = cleanSystemTags(text);
2678
+ if (!cleaned) continue;
2679
+ if (currentTurn) turns.push(currentTurn);
2680
+ const imgSuffix = hasImage(content) ? " [image]" : "";
2681
+ currentTurn = {
2682
+ userText: cleaned + imgSuffix,
2683
+ userTimestamp: ts,
2684
+ assistantParts: []
2685
+ };
2686
+ } else if (etype === "assistant" && currentTurn) {
2687
+ const blocks = extractContentBlocks(content);
2688
+ let pendingText = null;
2689
+ for (const block of blocks) {
2690
+ const btype = block.type;
2691
+ if (btype === "text") {
2692
+ const text = typeof block.text === "string" ? block.text.trim() : "";
2693
+ if (text) pendingText = text;
2694
+ } else if (btype === "tool_use") {
2695
+ const name = typeof block.name === "string" ? block.name : "";
2696
+ const inp = typeof block.input === "object" && block.input !== null ? block.input : {};
2697
+ if (name === "Edit") {
2698
+ if (pendingText) {
2699
+ currentTurn.assistantParts.push({ type: "text", content: pendingText });
2700
+ pendingText = null;
2701
+ }
2702
+ currentTurn.assistantParts.push({
2703
+ type: "edit",
2704
+ file: shortenPath(inp.file_path ?? ""),
2705
+ old: inp.old_string ?? "",
2706
+ new: inp.new_string ?? ""
2707
+ });
2708
+ } else if (name === "Write") {
2709
+ if (pendingText) {
2710
+ currentTurn.assistantParts.push({ type: "text", content: pendingText });
2711
+ pendingText = null;
2712
+ }
2713
+ currentTurn.assistantParts.push({
2714
+ type: "write",
2715
+ file: shortenPath(inp.file_path ?? ""),
2716
+ fileContent: inp.content ?? ""
2717
+ });
2718
+ }
2719
+ }
2720
+ }
2721
+ if (pendingText) {
2722
+ currentTurn.assistantParts.push({ type: "text", content: pendingText });
2723
+ }
2724
+ }
2725
+ }
2726
+ if (currentTurn) turns.push(currentTurn);
2727
+ const firstTimestamp = turns[0]?.userTimestamp ?? "";
2728
+ const lastTimestamp = turns[turns.length - 1]?.userTimestamp ?? "";
2729
+ return { turns, branch, firstTimestamp, lastTimestamp };
2730
+ }
2731
+ function buildSessionMarkdown(turns, mode) {
2732
+ const out = [];
2733
+ for (let i = 0; i < turns.length; i++) {
2734
+ const turn = turns[i];
2735
+ const userText = turn.userText.trim();
2736
+ if (!userText) continue;
2737
+ out.push(`**User [${i + 1}]:**`);
2738
+ out.push(userText);
2739
+ out.push("");
2740
+ let hasContent = false;
2741
+ for (const part of turn.assistantParts) {
2742
+ if (part.type === "text") {
2743
+ if (!hasContent) {
2744
+ out.push("**Assistant:**");
2745
+ hasContent = true;
2746
+ }
2747
+ out.push(part.content ?? "");
2748
+ out.push("");
2749
+ } else if (part.type === "edit") {
2750
+ if (!hasContent) {
2751
+ out.push("**Assistant:**");
2752
+ hasContent = true;
2753
+ }
2754
+ const file = part.file ?? "";
2755
+ const oldStr = part.old ?? "";
2756
+ const newStr = part.new ?? "";
2757
+ if (mode === "full") {
2758
+ out.push(formatEditFull(file, oldStr, newStr));
2759
+ } else if (mode === "balanced") {
2760
+ out.push(formatEditBalanced(file, oldStr, newStr));
2761
+ } else {
2762
+ out.push(formatEditCompact(file, oldStr, newStr));
2763
+ }
2764
+ out.push("");
2765
+ } else if (part.type === "write") {
2766
+ if (!hasContent) {
2767
+ out.push("**Assistant:**");
2768
+ hasContent = true;
2769
+ }
2770
+ const file = part.file ?? "";
2771
+ const content = part.fileContent ?? "";
2772
+ if (mode === "full") {
2773
+ out.push(formatWriteFull(file, content));
2774
+ } else if (mode === "balanced") {
2775
+ out.push(formatWriteBalanced(file, content));
2776
+ } else {
2777
+ out.push(formatWriteCompact(file, content));
2778
+ }
2779
+ out.push("");
2780
+ }
2781
+ }
2782
+ out.push("---");
2783
+ out.push("");
2784
+ }
2785
+ return out.join("\n");
2786
+ }
2787
+ var DISCLAIMER = `> **Note:** This conversation history may contain outdated information. File contents, code, and project state may have changed since these sessions were recorded. Use this as context only \u2014 always verify against current files before acting.`;
2788
+ function mergeSessionsMarkdown(sessions, mode, title) {
2789
+ const sorted = [...sessions].sort((a, b) => a.startTime.localeCompare(b.startTime));
2790
+ const totalTurns = sorted.reduce((sum, s) => sum + s.turns, 0);
2791
+ const overallStart = sorted[0]?.startTime.slice(0, 16) ?? "?";
2792
+ const overallEnd = sorted[sorted.length - 1]?.endTime.slice(0, 16) ?? "?";
2793
+ const out = [];
2794
+ out.push(`# Conversation History from ${title}`);
2795
+ out.push(`${sorted.length} sessions | ${totalTurns} turns | ${overallStart} \u2192 ${overallEnd} | mode: ${mode}`);
2796
+ out.push("");
2797
+ for (let i = 0; i < sorted.length; i++) {
2798
+ const s = sorted[i];
2799
+ const start = s.startTime.slice(0, 16);
2800
+ const end = s.endTime.slice(0, 16);
2801
+ out.push(`## Session Conversation History ${i + 1} \u2014 ${start} \u2192 ${end} (${s.agent}, ${s.turns} turns, branch: ${s.branch})`);
2802
+ out.push("");
2803
+ out.push(s.markdown);
2804
+ }
2805
+ out.push(DISCLAIMER);
2806
+ out.push("");
2807
+ return out.join("\n");
2808
+ }
2809
+
2810
+ // src/core/context/entire/entire-provider.ts
2811
+ var EntireProvider = class {
2812
+ name = "entire";
2813
+ async isAvailable(repoPath) {
2814
+ return new CheckpointReader(repoPath).hasEntireBranch();
2815
+ }
2816
+ async listSessions(query) {
2817
+ const reader = new CheckpointReader(query.repoPath);
2818
+ const sessions = await this.resolveSessions(reader, query);
2819
+ const estimatedTokens = sessions.reduce((sum, s) => sum + s.turnCount * TOKENS_PER_TURN_ESTIMATE, 0);
2820
+ return { sessions, estimatedTokens };
2821
+ }
2822
+ async buildContext(query, options) {
2823
+ const maxTokens = options?.maxTokens ?? DEFAULT_MAX_TOKENS;
2824
+ const reader = new CheckpointReader(query.repoPath);
2825
+ let sessions = await this.resolveSessions(reader, query);
2826
+ if (options?.limit && sessions.length > options.limit) {
2827
+ sessions = sessions.slice(-options.limit);
2828
+ }
2829
+ if (sessions.length === 0) {
2830
+ return { markdown: "", tokenEstimate: 0, sessionCount: 0, totalTurns: 0, mode: "full", truncated: false, timeRange: { start: "", end: "" } };
2831
+ }
2832
+ const parsedSessions = [];
2833
+ for (const sess of sessions) {
2834
+ const jsonl = reader.getTranscript(sess.transcriptPath);
2835
+ if (jsonl) parsedSessions.push({ session: sess, jsonl });
2836
+ }
2837
+ if (parsedSessions.length === 0) {
2838
+ return { markdown: "", tokenEstimate: 0, sessionCount: 0, totalTurns: 0, mode: "full", truncated: false, timeRange: { start: "", end: "" } };
2839
+ }
2840
+ const totalTurns = parsedSessions.reduce((sum, ps) => {
2841
+ const parsed = parseJsonlToTurns(ps.jsonl);
2842
+ return sum + parsed.turns.length;
2843
+ }, 0);
2844
+ let mode = selectMode(totalTurns);
2845
+ const title = this.buildTitle(query);
2846
+ let sessionMarkdowns = this.buildSessionMarkdowns(parsedSessions, mode);
2847
+ let merged = mergeSessionsMarkdown(sessionMarkdowns, mode, title);
2848
+ let tokens = estimateTokens(merged);
2849
+ if (tokens > maxTokens && mode !== "compact") {
2850
+ mode = "compact";
2851
+ sessionMarkdowns = this.buildSessionMarkdowns(parsedSessions, "compact");
2852
+ merged = mergeSessionsMarkdown(sessionMarkdowns, "compact", title);
2853
+ tokens = estimateTokens(merged);
2854
+ }
2855
+ let truncated = false;
2856
+ while (tokens > maxTokens && sessionMarkdowns.length > 1) {
2857
+ sessionMarkdowns = sessionMarkdowns.slice(1);
2858
+ truncated = true;
2859
+ merged = mergeSessionsMarkdown(sessionMarkdowns, mode, title);
2860
+ tokens = estimateTokens(merged);
2861
+ }
2862
+ const allTimes = sessionMarkdowns.flatMap((s) => [s.startTime, s.endTime]).filter(Boolean).sort();
2863
+ const finalTurns = sessionMarkdowns.reduce((sum, s) => sum + s.turns, 0);
2864
+ return {
2865
+ markdown: merged,
2866
+ tokenEstimate: tokens,
2867
+ sessionCount: sessionMarkdowns.length,
2868
+ totalTurns: finalTurns,
2869
+ mode,
2870
+ truncated,
2871
+ timeRange: { start: allTimes[0] ?? "", end: allTimes[allTimes.length - 1] ?? "" }
2872
+ };
2873
+ }
2874
+ buildSessionMarkdowns(parsedSessions, mode) {
2875
+ return parsedSessions.map((ps) => {
2876
+ const parsed = parseJsonlToTurns(ps.jsonl);
2877
+ return {
2878
+ markdown: buildSessionMarkdown(parsed.turns, mode),
2879
+ startTime: parsed.firstTimestamp,
2880
+ endTime: parsed.lastTimestamp,
2881
+ agent: ps.session.agent,
2882
+ turns: parsed.turns.length,
2883
+ branch: ps.session.branch,
2884
+ files: ps.session.filesTouched.map((f) => f.split("/").pop() ?? f)
2885
+ };
2886
+ });
2887
+ }
2888
+ async resolveSessions(reader, query) {
2889
+ switch (query.type) {
2890
+ case "branch":
2891
+ return reader.resolveByBranch(query.value);
2892
+ case "commit":
2893
+ return reader.resolveByCommit(query.value);
2894
+ case "pr":
2895
+ return reader.resolveByPr(query.value);
2896
+ case "checkpoint":
2897
+ return reader.resolveByCheckpoint(query.value);
2898
+ case "session":
2899
+ return reader.resolveBySessionId(query.value);
2900
+ case "latest":
2901
+ return reader.resolveLatest(parseInt(query.value) || 5);
2902
+ default:
2903
+ return [];
2904
+ }
2905
+ }
2906
+ buildTitle(query) {
2907
+ switch (query.type) {
2908
+ case "pr":
2909
+ return `PR #${query.value.replace(/.*\/pull\//, "")}`;
2910
+ case "branch":
2911
+ return `branch \`${query.value}\``;
2912
+ case "commit":
2913
+ return `commit \`${query.value.slice(0, 8)}\``;
2914
+ case "checkpoint":
2915
+ return `checkpoint \`${query.value}\``;
2916
+ case "session":
2917
+ return `session \`${query.value.slice(0, 8)}...\``;
2918
+ case "latest":
2919
+ return `latest ${query.value} sessions`;
2920
+ default:
2921
+ return "unknown";
2922
+ }
2923
+ }
2924
+ };
2925
+
2132
2926
  // src/core/core.ts
2133
- import path5 from "path";
2134
- import os from "os";
2927
+ import path7 from "path";
2928
+ import os2 from "os";
2135
2929
 
2136
2930
  // src/core/session-store.ts
2137
- import fs5 from "fs";
2138
- import path4 from "path";
2931
+ import fs6 from "fs";
2932
+ import path6 from "path";
2139
2933
  var log6 = createChildLogger({ module: "session-store" });
2140
2934
  var DEBOUNCE_MS2 = 2e3;
2141
2935
  var JsonFileSessionStore = class {
@@ -2197,9 +2991,9 @@ var JsonFileSessionStore = class {
2197
2991
  version: 1,
2198
2992
  sessions: Object.fromEntries(this.records)
2199
2993
  };
2200
- const dir = path4.dirname(this.filePath);
2201
- if (!fs5.existsSync(dir)) fs5.mkdirSync(dir, { recursive: true });
2202
- fs5.writeFileSync(this.filePath, JSON.stringify(data, null, 2));
2994
+ const dir = path6.dirname(this.filePath);
2995
+ if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
2996
+ fs6.writeFileSync(this.filePath, JSON.stringify(data, null, 2));
2203
2997
  }
2204
2998
  destroy() {
2205
2999
  if (this.debounceTimer) clearTimeout(this.debounceTimer);
@@ -2212,10 +3006,10 @@ var JsonFileSessionStore = class {
2212
3006
  }
2213
3007
  }
2214
3008
  load() {
2215
- if (!fs5.existsSync(this.filePath)) return;
3009
+ if (!fs6.existsSync(this.filePath)) return;
2216
3010
  try {
2217
3011
  const raw = JSON.parse(
2218
- fs5.readFileSync(this.filePath, "utf-8")
3012
+ fs6.readFileSync(this.filePath, "utf-8")
2219
3013
  );
2220
3014
  if (raw.version !== 1) {
2221
3015
  log6.warn(
@@ -2279,13 +3073,14 @@ var OpenACPCore = class {
2279
3073
  sessionFactory;
2280
3074
  usageStore = null;
2281
3075
  usageBudget = null;
3076
+ contextManager;
2282
3077
  constructor(configManager) {
2283
3078
  this.configManager = configManager;
2284
3079
  const config = configManager.get();
2285
3080
  this.agentCatalog = new AgentCatalog();
2286
3081
  this.agentCatalog.load();
2287
3082
  this.agentManager = new AgentManager(this.agentCatalog);
2288
- const storePath = path5.join(os.homedir(), ".openacp", "sessions.json");
3083
+ const storePath = path7.join(os2.homedir(), ".openacp", "sessions.json");
2289
3084
  this.sessionStore = new JsonFileSessionStore(
2290
3085
  storePath,
2291
3086
  config.sessionStore.ttlDays
@@ -2295,15 +3090,17 @@ var OpenACPCore = class {
2295
3090
  this.notificationManager = new NotificationManager(this.adapters);
2296
3091
  const usageConfig = config.usage;
2297
3092
  if (usageConfig.enabled) {
2298
- const usagePath = path5.join(os.homedir(), ".openacp", "usage.json");
3093
+ const usagePath = path7.join(os2.homedir(), ".openacp", "usage.json");
2299
3094
  this.usageStore = new UsageStore(usagePath, usageConfig.retentionDays);
2300
3095
  this.usageBudget = new UsageBudget(this.usageStore, usageConfig);
2301
3096
  }
2302
3097
  this.messageTransformer = new MessageTransformer();
2303
3098
  this.eventBus = new EventBus();
2304
3099
  this.sessionManager.setEventBus(this.eventBus);
3100
+ this.contextManager = new ContextManager();
3101
+ this.contextManager.register(new EntireProvider());
2305
3102
  this.fileService = new FileService(
2306
- path5.join(os.homedir(), ".openacp", "files")
3103
+ path7.join(os2.homedir(), ".openacp", "files")
2307
3104
  );
2308
3105
  const speechConfig = config.speech ?? {
2309
3106
  stt: { provider: null, providers: {} },
@@ -2654,6 +3451,27 @@ var OpenACPCore = class {
2654
3451
  record.workingDir
2655
3452
  );
2656
3453
  }
3454
+ async createSessionWithContext(params) {
3455
+ let contextResult = null;
3456
+ try {
3457
+ contextResult = await this.contextManager.buildContext(
3458
+ params.contextQuery,
3459
+ params.contextOptions
3460
+ );
3461
+ } catch (err) {
3462
+ log7.warn({ err }, "Context building failed, proceeding without context");
3463
+ }
3464
+ const session = await this.createSession({
3465
+ channelId: params.channelId,
3466
+ agentName: params.agentName,
3467
+ workingDirectory: params.workingDirectory,
3468
+ createThread: params.createThread
3469
+ });
3470
+ if (contextResult) {
3471
+ session.setContext(contextResult.markdown);
3472
+ }
3473
+ return { session, contextResult };
3474
+ }
2657
3475
  // --- Lazy Resume ---
2658
3476
  /**
2659
3477
  * Get active session by thread, or attempt lazy resume from store.
@@ -2849,8 +3667,8 @@ data: ${JSON.stringify(data)}
2849
3667
  };
2850
3668
 
2851
3669
  // src/core/static-server.ts
2852
- import * as fs6 from "fs";
2853
- import * as path6 from "path";
3670
+ import * as fs7 from "fs";
3671
+ import * as path8 from "path";
2854
3672
  import { fileURLToPath } from "url";
2855
3673
  var MIME_TYPES = {
2856
3674
  ".html": "text/html; charset=utf-8",
@@ -2870,16 +3688,16 @@ var StaticServer = class {
2870
3688
  this.uiDir = uiDir;
2871
3689
  if (!this.uiDir) {
2872
3690
  const __filename = fileURLToPath(import.meta.url);
2873
- const candidate = path6.resolve(path6.dirname(__filename), "../../ui/dist");
2874
- if (fs6.existsSync(path6.join(candidate, "index.html"))) {
3691
+ const candidate = path8.resolve(path8.dirname(__filename), "../../ui/dist");
3692
+ if (fs7.existsSync(path8.join(candidate, "index.html"))) {
2875
3693
  this.uiDir = candidate;
2876
3694
  }
2877
3695
  if (!this.uiDir) {
2878
- const publishCandidate = path6.resolve(
2879
- path6.dirname(__filename),
3696
+ const publishCandidate = path8.resolve(
3697
+ path8.dirname(__filename),
2880
3698
  "../ui"
2881
3699
  );
2882
- if (fs6.existsSync(path6.join(publishCandidate, "index.html"))) {
3700
+ if (fs7.existsSync(path8.join(publishCandidate, "index.html"))) {
2883
3701
  this.uiDir = publishCandidate;
2884
3702
  }
2885
3703
  }
@@ -2891,12 +3709,12 @@ var StaticServer = class {
2891
3709
  serve(req, res) {
2892
3710
  if (!this.uiDir) return false;
2893
3711
  const urlPath = (req.url || "/").split("?")[0];
2894
- const safePath = path6.normalize(urlPath);
2895
- const filePath = path6.join(this.uiDir, safePath);
2896
- if (!filePath.startsWith(this.uiDir + path6.sep) && filePath !== this.uiDir)
3712
+ const safePath = path8.normalize(urlPath);
3713
+ const filePath = path8.join(this.uiDir, safePath);
3714
+ if (!filePath.startsWith(this.uiDir + path8.sep) && filePath !== this.uiDir)
2897
3715
  return false;
2898
- if (fs6.existsSync(filePath) && fs6.statSync(filePath).isFile()) {
2899
- const ext = path6.extname(filePath);
3716
+ if (fs7.existsSync(filePath) && fs7.statSync(filePath).isFile()) {
3717
+ const ext = path8.extname(filePath);
2900
3718
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
2901
3719
  const isHashed = /\.[a-zA-Z0-9]{8,}\.(js|css)$/.test(filePath);
2902
3720
  const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "no-cache";
@@ -2904,16 +3722,16 @@ var StaticServer = class {
2904
3722
  "Content-Type": contentType,
2905
3723
  "Cache-Control": cacheControl
2906
3724
  });
2907
- fs6.createReadStream(filePath).pipe(res);
3725
+ fs7.createReadStream(filePath).pipe(res);
2908
3726
  return true;
2909
3727
  }
2910
- const indexPath = path6.join(this.uiDir, "index.html");
2911
- if (fs6.existsSync(indexPath)) {
3728
+ const indexPath = path8.join(this.uiDir, "index.html");
3729
+ if (fs7.existsSync(indexPath)) {
2912
3730
  res.writeHead(200, {
2913
3731
  "Content-Type": "text/html; charset=utf-8",
2914
3732
  "Cache-Control": "no-cache"
2915
3733
  });
2916
- fs6.createReadStream(indexPath).pipe(res);
3734
+ fs7.createReadStream(indexPath).pipe(res);
2917
3735
  return true;
2918
3736
  }
2919
3737
  return false;
@@ -2922,29 +3740,29 @@ var StaticServer = class {
2922
3740
 
2923
3741
  // src/core/api/index.ts
2924
3742
  import * as http from "http";
2925
- import * as fs7 from "fs";
2926
- import * as path7 from "path";
2927
- import * as os2 from "os";
2928
- import * as crypto from "crypto";
3743
+ import * as fs8 from "fs";
3744
+ import * as path9 from "path";
3745
+ import * as os3 from "os";
3746
+ import * as crypto2 from "crypto";
2929
3747
  import { fileURLToPath as fileURLToPath2 } from "url";
2930
3748
 
2931
3749
  // src/core/api/router.ts
2932
3750
  var Router = class {
2933
3751
  routes = [];
2934
- get(path8, handler) {
2935
- this.add("GET", path8, handler);
3752
+ get(path10, handler) {
3753
+ this.add("GET", path10, handler);
2936
3754
  }
2937
- post(path8, handler) {
2938
- this.add("POST", path8, handler);
3755
+ post(path10, handler) {
3756
+ this.add("POST", path10, handler);
2939
3757
  }
2940
- put(path8, handler) {
2941
- this.add("PUT", path8, handler);
3758
+ put(path10, handler) {
3759
+ this.add("PUT", path10, handler);
2942
3760
  }
2943
- patch(path8, handler) {
2944
- this.add("PATCH", path8, handler);
3761
+ patch(path10, handler) {
3762
+ this.add("PATCH", path10, handler);
2945
3763
  }
2946
- delete(path8, handler) {
2947
- this.add("DELETE", path8, handler);
3764
+ delete(path10, handler) {
3765
+ this.add("DELETE", path10, handler);
2948
3766
  }
2949
3767
  match(method, url) {
2950
3768
  const pathname = url.split("?")[0];
@@ -2960,9 +3778,9 @@ var Router = class {
2960
3778
  }
2961
3779
  return null;
2962
3780
  }
2963
- add(method, path8, handler) {
3781
+ add(method, path10, handler) {
2964
3782
  const keys = [];
2965
- const pattern = path8.replace(/:(\w+)/g, (_, key) => {
3783
+ const pattern = path10.replace(/:(\w+)/g, (_, key) => {
2966
3784
  keys.push(key);
2967
3785
  return "([^/]+)";
2968
3786
  });
@@ -3596,17 +4414,17 @@ function registerNotifyRoutes(router, deps) {
3596
4414
 
3597
4415
  // src/core/api/index.ts
3598
4416
  var log9 = createChildLogger({ module: "api-server" });
3599
- var DEFAULT_PORT_FILE = path7.join(os2.homedir(), ".openacp", "api.port");
4417
+ var DEFAULT_PORT_FILE = path9.join(os3.homedir(), ".openacp", "api.port");
3600
4418
  var cachedVersion;
3601
4419
  function getVersion() {
3602
4420
  if (cachedVersion) return cachedVersion;
3603
4421
  try {
3604
4422
  const __filename = fileURLToPath2(import.meta.url);
3605
- const pkgPath = path7.resolve(
3606
- path7.dirname(__filename),
4423
+ const pkgPath = path9.resolve(
4424
+ path9.dirname(__filename),
3607
4425
  "../../../package.json"
3608
4426
  );
3609
- const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
4427
+ const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
3610
4428
  cachedVersion = pkg.version ?? "0.0.0-dev";
3611
4429
  } catch {
3612
4430
  cachedVersion = "0.0.0-dev";
@@ -3619,7 +4437,7 @@ var ApiServer = class {
3619
4437
  this.config = config;
3620
4438
  this.topicManager = topicManager;
3621
4439
  this.portFilePath = portFilePath ?? DEFAULT_PORT_FILE;
3622
- this.secretFilePath = secretFilePath ?? path7.join(os2.homedir(), ".openacp", "api-secret");
4440
+ this.secretFilePath = secretFilePath ?? path9.join(os3.homedir(), ".openacp", "api-secret");
3623
4441
  this.staticServer = new StaticServer(uiDir);
3624
4442
  this.sseManager = new SSEManager(
3625
4443
  core.eventBus,
@@ -3713,24 +4531,24 @@ var ApiServer = class {
3713
4531
  return this.secret;
3714
4532
  }
3715
4533
  writePortFile() {
3716
- const dir = path7.dirname(this.portFilePath);
3717
- fs7.mkdirSync(dir, { recursive: true });
3718
- fs7.writeFileSync(this.portFilePath, String(this.actualPort));
4534
+ const dir = path9.dirname(this.portFilePath);
4535
+ fs8.mkdirSync(dir, { recursive: true });
4536
+ fs8.writeFileSync(this.portFilePath, String(this.actualPort));
3719
4537
  }
3720
4538
  removePortFile() {
3721
4539
  try {
3722
- fs7.unlinkSync(this.portFilePath);
4540
+ fs8.unlinkSync(this.portFilePath);
3723
4541
  } catch {
3724
4542
  }
3725
4543
  }
3726
4544
  loadOrCreateSecret() {
3727
- const dir = path7.dirname(this.secretFilePath);
3728
- fs7.mkdirSync(dir, { recursive: true });
4545
+ const dir = path9.dirname(this.secretFilePath);
4546
+ fs8.mkdirSync(dir, { recursive: true });
3729
4547
  try {
3730
- this.secret = fs7.readFileSync(this.secretFilePath, "utf-8").trim();
4548
+ this.secret = fs8.readFileSync(this.secretFilePath, "utf-8").trim();
3731
4549
  if (this.secret) {
3732
4550
  try {
3733
- const stat = fs7.statSync(this.secretFilePath);
4551
+ const stat = fs8.statSync(this.secretFilePath);
3734
4552
  const mode = stat.mode & 511;
3735
4553
  if (mode & 63) {
3736
4554
  log9.warn(
@@ -3745,14 +4563,14 @@ var ApiServer = class {
3745
4563
  }
3746
4564
  } catch {
3747
4565
  }
3748
- this.secret = crypto.randomBytes(32).toString("hex");
3749
- fs7.writeFileSync(this.secretFilePath, this.secret, { mode: 384 });
4566
+ this.secret = crypto2.randomBytes(32).toString("hex");
4567
+ fs8.writeFileSync(this.secretFilePath, this.secret, { mode: 384 });
3750
4568
  }
3751
4569
  authenticate(req, allowQueryParam = false) {
3752
4570
  const authHeader = req.headers.authorization;
3753
4571
  if (authHeader?.startsWith("Bearer ")) {
3754
4572
  const token = authHeader.slice(7);
3755
- if (token.length === this.secret.length && crypto.timingSafeEqual(
4573
+ if (token.length === this.secret.length && crypto2.timingSafeEqual(
3756
4574
  Buffer.from(token, "utf-8"),
3757
4575
  Buffer.from(this.secret, "utf-8")
3758
4576
  )) {
@@ -3762,7 +4580,7 @@ var ApiServer = class {
3762
4580
  if (allowQueryParam) {
3763
4581
  const parsedUrl = new URL(req.url || "", "http://localhost");
3764
4582
  const qToken = parsedUrl.searchParams.get("token");
3765
- if (qToken && qToken.length === this.secret.length && crypto.timingSafeEqual(
4583
+ if (qToken && qToken.length === this.secret.length && crypto2.timingSafeEqual(
3766
4584
  Buffer.from(qToken, "utf-8"),
3767
4585
  Buffer.from(this.secret, "utf-8")
3768
4586
  )) {
@@ -3940,10 +4758,14 @@ export {
3940
4758
  EventBus,
3941
4759
  SpeechService,
3942
4760
  GroqSTT,
4761
+ ContextManager,
4762
+ DEFAULT_MAX_TOKENS,
4763
+ CheckpointReader,
4764
+ EntireProvider,
3943
4765
  OpenACPCore,
3944
4766
  SSEManager,
3945
4767
  StaticServer,
3946
4768
  ApiServer,
3947
4769
  TopicManager
3948
4770
  };
3949
- //# sourceMappingURL=chunk-FEWSQT3U.js.map
4771
+ //# sourceMappingURL=chunk-ZMVVW3BK.js.map