@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.
- package/dist/{adapter-YSEIZJBA.js → adapter-RKK7A5GI.js} +2 -2
- package/dist/{chunk-FW6HM4VU.js → chunk-AHPRT3RY.js} +349 -62
- package/dist/chunk-AHPRT3RY.js.map +1 -0
- package/dist/{chunk-FEWSQT3U.js → chunk-ZMVVW3BK.js} +888 -66
- package/dist/chunk-ZMVVW3BK.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +80 -1
- package/dist/index.js +6 -2
- package/dist/{main-TSZR4HPP.js → main-B5L3DD3Y.js} +4 -4
- package/package.json +1 -1
- package/dist/chunk-FEWSQT3U.js.map +0 -1
- package/dist/chunk-FW6HM4VU.js.map +0 -1
- /package/dist/{adapter-YSEIZJBA.js.map → adapter-RKK7A5GI.js.map} +0 -0
- /package/dist/{main-TSZR4HPP.js.map → main-B5L3DD3Y.js.map} +0 -0
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
2134
|
-
import
|
|
2927
|
+
import path7 from "path";
|
|
2928
|
+
import os2 from "os";
|
|
2135
2929
|
|
|
2136
2930
|
// src/core/session-store.ts
|
|
2137
|
-
import
|
|
2138
|
-
import
|
|
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 =
|
|
2201
|
-
if (!
|
|
2202
|
-
|
|
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 (!
|
|
3009
|
+
if (!fs6.existsSync(this.filePath)) return;
|
|
2216
3010
|
try {
|
|
2217
3011
|
const raw = JSON.parse(
|
|
2218
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
2853
|
-
import * as
|
|
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 =
|
|
2874
|
-
if (
|
|
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 =
|
|
2879
|
-
|
|
3696
|
+
const publishCandidate = path8.resolve(
|
|
3697
|
+
path8.dirname(__filename),
|
|
2880
3698
|
"../ui"
|
|
2881
3699
|
);
|
|
2882
|
-
if (
|
|
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 =
|
|
2895
|
-
const filePath =
|
|
2896
|
-
if (!filePath.startsWith(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 (
|
|
2899
|
-
const ext =
|
|
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
|
-
|
|
3725
|
+
fs7.createReadStream(filePath).pipe(res);
|
|
2908
3726
|
return true;
|
|
2909
3727
|
}
|
|
2910
|
-
const indexPath =
|
|
2911
|
-
if (
|
|
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
|
-
|
|
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
|
|
2926
|
-
import * as
|
|
2927
|
-
import * as
|
|
2928
|
-
import * as
|
|
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(
|
|
2935
|
-
this.add("GET",
|
|
3752
|
+
get(path10, handler) {
|
|
3753
|
+
this.add("GET", path10, handler);
|
|
2936
3754
|
}
|
|
2937
|
-
post(
|
|
2938
|
-
this.add("POST",
|
|
3755
|
+
post(path10, handler) {
|
|
3756
|
+
this.add("POST", path10, handler);
|
|
2939
3757
|
}
|
|
2940
|
-
put(
|
|
2941
|
-
this.add("PUT",
|
|
3758
|
+
put(path10, handler) {
|
|
3759
|
+
this.add("PUT", path10, handler);
|
|
2942
3760
|
}
|
|
2943
|
-
patch(
|
|
2944
|
-
this.add("PATCH",
|
|
3761
|
+
patch(path10, handler) {
|
|
3762
|
+
this.add("PATCH", path10, handler);
|
|
2945
3763
|
}
|
|
2946
|
-
delete(
|
|
2947
|
-
this.add("DELETE",
|
|
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,
|
|
3781
|
+
add(method, path10, handler) {
|
|
2964
3782
|
const keys = [];
|
|
2965
|
-
const pattern =
|
|
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 =
|
|
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 =
|
|
3606
|
-
|
|
4423
|
+
const pkgPath = path9.resolve(
|
|
4424
|
+
path9.dirname(__filename),
|
|
3607
4425
|
"../../../package.json"
|
|
3608
4426
|
);
|
|
3609
|
-
const pkg = JSON.parse(
|
|
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 ??
|
|
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 =
|
|
3717
|
-
|
|
3718
|
-
|
|
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
|
-
|
|
4540
|
+
fs8.unlinkSync(this.portFilePath);
|
|
3723
4541
|
} catch {
|
|
3724
4542
|
}
|
|
3725
4543
|
}
|
|
3726
4544
|
loadOrCreateSecret() {
|
|
3727
|
-
const dir =
|
|
3728
|
-
|
|
4545
|
+
const dir = path9.dirname(this.secretFilePath);
|
|
4546
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
3729
4547
|
try {
|
|
3730
|
-
this.secret =
|
|
4548
|
+
this.secret = fs8.readFileSync(this.secretFilePath, "utf-8").trim();
|
|
3731
4549
|
if (this.secret) {
|
|
3732
4550
|
try {
|
|
3733
|
-
const stat =
|
|
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 =
|
|
3749
|
-
|
|
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 &&
|
|
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 &&
|
|
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-
|
|
4771
|
+
//# sourceMappingURL=chunk-ZMVVW3BK.js.map
|