@openacp/cli 0.6.5 → 0.6.7
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-7GY3N4ZH.js} +9 -9
- package/dist/{admin-SCP25TN2.js → admin-2HAFXQBG.js} +6 -4
- package/dist/{chunk-WVMSP4AF.js → chunk-2J2RBYWN.js} +2 -2
- package/dist/{chunk-ZKTIZME6.js → chunk-47B7GNOE.js} +2 -2
- package/dist/{chunk-XVL6AGMG.js → chunk-5OVPEDUB.js} +2 -2
- package/dist/{chunk-FW6HM4VU.js → chunk-5SXG7X5D.js} +862 -303
- package/dist/chunk-5SXG7X5D.js.map +1 -0
- package/dist/{chunk-F3AICYO4.js → chunk-JHYXKVV2.js} +19 -1
- package/dist/chunk-JHYXKVV2.js.map +1 -0
- package/dist/{chunk-FCLGYYTY.js → chunk-JUYDFUSN.js} +224 -2
- package/dist/chunk-JUYDFUSN.js.map +1 -0
- package/dist/{chunk-774Y4RAK.js → chunk-KIRH7TUJ.js} +94 -24
- package/dist/chunk-KIRH7TUJ.js.map +1 -0
- package/dist/{chunk-4GQ3I65A.js → chunk-LBIKITQT.js} +1 -2
- package/dist/{chunk-4GQ3I65A.js.map → chunk-LBIKITQT.js.map} +1 -1
- package/dist/{chunk-FEWSQT3U.js → chunk-LO4Y5WQ7.js} +1014 -83
- package/dist/chunk-LO4Y5WQ7.js.map +1 -0
- package/dist/{chunk-3IRAWHMC.js → chunk-MZXWCDBU.js} +3 -3
- package/dist/{chunk-YQRF3IOR.js → chunk-O7CPGUAI.js} +2 -2
- package/dist/{chunk-7KZI2236.js → chunk-RHE2JSYE.js} +2 -2
- package/dist/{chunk-3ZO3MHZN.js → chunk-SHHMBGB3.js} +4 -3
- package/dist/chunk-SHHMBGB3.js.map +1 -0
- package/dist/{chunk-JV6XQRAE.js → chunk-XANPHG7W.js} +2 -2
- package/dist/{chunk-PJVKOZTR.js → chunk-YEOY2PBJ.js} +2 -2
- package/dist/cli.js +21 -21
- package/dist/{config-B26J3XXN.js → config-CQAS6YHR.js} +2 -2
- package/dist/{config-editor-QTGUK3CD.js → config-editor-37BM56WF.js} +4 -4
- package/dist/{config-registry-7I6GGDOY.js → config-registry-HDXFES2D.js} +2 -2
- package/dist/{daemon-5DS5BQXJ.js → daemon-K33ZPSEZ.js} +3 -3
- package/dist/{discord-QKT3JMRW.js → discord-VOHXRTCH.js} +113 -131
- package/dist/discord-VOHXRTCH.js.map +1 -0
- package/dist/{doctor-QQ3YZEYV.js → doctor-HASEBMUD.js} +4 -4
- package/dist/doctor-W4VGLDVM.js +9 -0
- package/dist/index.d.ts +105 -10
- package/dist/index.js +15 -11
- package/dist/{main-TSZR4HPP.js → main-DUXVFTDD.js} +19 -19
- package/dist/{new-session-K6UCWYOP.js → new-session-NHK7TOEW.js} +3 -3
- package/dist/{settings-RRF77IC4.js → settings-6TF4WIGJ.js} +3 -3
- package/dist/{setup-5ZKSUR26.js → setup-RJCEB6FS.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-3ZO3MHZN.js.map +0 -1
- package/dist/chunk-774Y4RAK.js.map +0 -1
- package/dist/chunk-F3AICYO4.js.map +0 -1
- package/dist/chunk-FCLGYYTY.js.map +0 -1
- package/dist/chunk-FEWSQT3U.js.map +0 -1
- package/dist/chunk-FW6HM4VU.js.map +0 -1
- package/dist/discord-QKT3JMRW.js.map +0 -1
- package/dist/doctor-6SUCVUZB.js +0 -9
- /package/dist/{adapter-YSEIZJBA.js.map → adapter-7GY3N4ZH.js.map} +0 -0
- /package/dist/{admin-SCP25TN2.js.map → admin-2HAFXQBG.js.map} +0 -0
- /package/dist/{chunk-WVMSP4AF.js.map → chunk-2J2RBYWN.js.map} +0 -0
- /package/dist/{chunk-ZKTIZME6.js.map → chunk-47B7GNOE.js.map} +0 -0
- /package/dist/{chunk-XVL6AGMG.js.map → chunk-5OVPEDUB.js.map} +0 -0
- /package/dist/{chunk-3IRAWHMC.js.map → chunk-MZXWCDBU.js.map} +0 -0
- /package/dist/{chunk-YQRF3IOR.js.map → chunk-O7CPGUAI.js.map} +0 -0
- /package/dist/{chunk-7KZI2236.js.map → chunk-RHE2JSYE.js.map} +0 -0
- /package/dist/{chunk-JV6XQRAE.js.map → chunk-XANPHG7W.js.map} +0 -0
- /package/dist/{chunk-PJVKOZTR.js.map → chunk-YEOY2PBJ.js.map} +0 -0
- /package/dist/{config-B26J3XXN.js.map → config-CQAS6YHR.js.map} +0 -0
- /package/dist/{config-editor-QTGUK3CD.js.map → config-editor-37BM56WF.js.map} +0 -0
- /package/dist/{config-registry-7I6GGDOY.js.map → config-registry-HDXFES2D.js.map} +0 -0
- /package/dist/{daemon-5DS5BQXJ.js.map → daemon-K33ZPSEZ.js.map} +0 -0
- /package/dist/{doctor-6SUCVUZB.js.map → doctor-HASEBMUD.js.map} +0 -0
- /package/dist/{doctor-QQ3YZEYV.js.map → doctor-W4VGLDVM.js.map} +0 -0
- /package/dist/{main-TSZR4HPP.js.map → main-DUXVFTDD.js.map} +0 -0
- /package/dist/{new-session-K6UCWYOP.js.map → new-session-NHK7TOEW.js.map} +0 -0
- /package/dist/{settings-RRF77IC4.js.map → settings-6TF4WIGJ.js.map} +0 -0
- /package/dist/{setup-5ZKSUR26.js.map → setup-RJCEB6FS.js.map} +0 -0
|
@@ -769,10 +769,12 @@ var Session = class extends TypedEmitter {
|
|
|
769
769
|
voiceMode = "off";
|
|
770
770
|
dangerousMode = false;
|
|
771
771
|
archiving = false;
|
|
772
|
+
promptCount = 0;
|
|
772
773
|
log;
|
|
773
774
|
permissionGate = new PermissionGate();
|
|
774
775
|
queue;
|
|
775
776
|
speechService;
|
|
777
|
+
pendingContext = null;
|
|
776
778
|
constructor(opts) {
|
|
777
779
|
super();
|
|
778
780
|
this.id = opts.id || nanoid(12);
|
|
@@ -832,6 +834,10 @@ var Session = class extends TypedEmitter {
|
|
|
832
834
|
get promptRunning() {
|
|
833
835
|
return this.queue.isProcessing;
|
|
834
836
|
}
|
|
837
|
+
// --- Context Injection ---
|
|
838
|
+
setContext(markdown) {
|
|
839
|
+
this.pendingContext = markdown;
|
|
840
|
+
}
|
|
835
841
|
// --- Voice Mode ---
|
|
836
842
|
setVoiceMode(mode) {
|
|
837
843
|
this.voiceMode = mode;
|
|
@@ -846,11 +852,23 @@ var Session = class extends TypedEmitter {
|
|
|
846
852
|
await this.runWarmup();
|
|
847
853
|
return;
|
|
848
854
|
}
|
|
855
|
+
this.promptCount++;
|
|
849
856
|
if (this._status === "initializing") {
|
|
850
857
|
this.activate();
|
|
851
858
|
}
|
|
852
859
|
const promptStart = Date.now();
|
|
853
860
|
this.log.debug("Prompt execution started");
|
|
861
|
+
if (this.pendingContext) {
|
|
862
|
+
text = `[CONVERSATION HISTORY - This is context from previous sessions, not current conversation]
|
|
863
|
+
|
|
864
|
+
${this.pendingContext}
|
|
865
|
+
|
|
866
|
+
[END CONVERSATION HISTORY]
|
|
867
|
+
|
|
868
|
+
${text}`;
|
|
869
|
+
this.pendingContext = null;
|
|
870
|
+
this.log.debug("Context injected into prompt");
|
|
871
|
+
}
|
|
854
872
|
const processed = await this.maybeTranscribeAudio(text, attachments);
|
|
855
873
|
const ttsActive = this.voiceMode !== "off" && !!this.speechService?.isTTSAvailable();
|
|
856
874
|
if (ttsActive) {
|
|
@@ -986,6 +1004,33 @@ ${result.text}` : result.text;
|
|
|
986
1004
|
this.resume();
|
|
987
1005
|
}
|
|
988
1006
|
}
|
|
1007
|
+
async generateSummary(timeoutMs = 15e3) {
|
|
1008
|
+
let summary = "";
|
|
1009
|
+
let timer;
|
|
1010
|
+
const captureHandler = (event) => {
|
|
1011
|
+
if (event.type === "text") summary += event.content;
|
|
1012
|
+
};
|
|
1013
|
+
this.pause((event) => event !== "agent_event");
|
|
1014
|
+
this.agentInstance.on("agent_event", captureHandler);
|
|
1015
|
+
try {
|
|
1016
|
+
const promptPromise = this.agentInstance.prompt(
|
|
1017
|
+
"Summarize what you've accomplished so far in this session in 2-3 sentences. Include: key files changed, decisions made, and current status. Reply ONLY with the summary, nothing else."
|
|
1018
|
+
);
|
|
1019
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1020
|
+
timer = setTimeout(() => reject(new Error("summary timeout")), timeoutMs);
|
|
1021
|
+
});
|
|
1022
|
+
await Promise.race([promptPromise, timeoutPromise]);
|
|
1023
|
+
return summary.trim().slice(0, 500);
|
|
1024
|
+
} catch {
|
|
1025
|
+
this.log.warn("Failed to generate session summary");
|
|
1026
|
+
return "";
|
|
1027
|
+
} finally {
|
|
1028
|
+
if (timer) clearTimeout(timer);
|
|
1029
|
+
this.agentInstance.off("agent_event", captureHandler);
|
|
1030
|
+
this.clearBuffer();
|
|
1031
|
+
this.resume();
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
989
1034
|
/** Fire-and-forget warm-up: primes model cache while user types their first message */
|
|
990
1035
|
async warmup() {
|
|
991
1036
|
await this.queue.enqueue("\0__warmup__");
|
|
@@ -1322,7 +1367,8 @@ var SessionBridge = class {
|
|
|
1322
1367
|
sessionId: this.session.id,
|
|
1323
1368
|
sessionName: this.session.name,
|
|
1324
1369
|
type: "completed",
|
|
1325
|
-
summary: `Session "${this.session.name || this.session.id}" completed
|
|
1370
|
+
summary: `Session "${this.session.name || this.session.id}" completed
|
|
1371
|
+
\u23F1 ${Math.round((Date.now() - this.session.createdAt.getTime()) / 6e4)} min \xB7 \u{1F4AC} ${this.session.promptCount} prompts`
|
|
1326
1372
|
});
|
|
1327
1373
|
break;
|
|
1328
1374
|
case "error":
|
|
@@ -1341,12 +1387,12 @@ var SessionBridge = class {
|
|
|
1341
1387
|
break;
|
|
1342
1388
|
case "image_content": {
|
|
1343
1389
|
if (this.deps.fileService) {
|
|
1344
|
-
const
|
|
1390
|
+
const fs9 = this.deps.fileService;
|
|
1345
1391
|
const sid = this.session.id;
|
|
1346
1392
|
const { data, mimeType } = event;
|
|
1347
1393
|
const buffer = Buffer.from(data, "base64");
|
|
1348
1394
|
const ext = FileService.extensionFromMime(mimeType);
|
|
1349
|
-
|
|
1395
|
+
fs9.saveFile(sid, `agent-image${ext}`, buffer, mimeType).then((att) => {
|
|
1350
1396
|
this.adapter.sendMessage(sid, {
|
|
1351
1397
|
type: "attachment",
|
|
1352
1398
|
text: "",
|
|
@@ -1358,12 +1404,12 @@ var SessionBridge = class {
|
|
|
1358
1404
|
}
|
|
1359
1405
|
case "audio_content": {
|
|
1360
1406
|
if (this.deps.fileService) {
|
|
1361
|
-
const
|
|
1407
|
+
const fs9 = this.deps.fileService;
|
|
1362
1408
|
const sid = this.session.id;
|
|
1363
1409
|
const { data, mimeType } = event;
|
|
1364
1410
|
const buffer = Buffer.from(data, "base64");
|
|
1365
1411
|
const ext = FileService.extensionFromMime(mimeType);
|
|
1366
|
-
|
|
1412
|
+
fs9.saveFile(sid, `agent-audio${ext}`, buffer, mimeType).then((att) => {
|
|
1367
1413
|
this.adapter.sendMessage(sid, {
|
|
1368
1414
|
type: "attachment",
|
|
1369
1415
|
text: "",
|
|
@@ -1573,24 +1619,34 @@ var MessageTransformer = class {
|
|
|
1573
1619
|
case "thought":
|
|
1574
1620
|
return { type: "thought", text: event.content };
|
|
1575
1621
|
case "tool_call": {
|
|
1622
|
+
const meta = event.meta;
|
|
1576
1623
|
const metadata = {
|
|
1577
1624
|
id: event.id,
|
|
1578
1625
|
name: event.name,
|
|
1579
1626
|
kind: event.kind,
|
|
1580
1627
|
status: event.status,
|
|
1581
1628
|
content: event.content,
|
|
1582
|
-
locations: event.locations
|
|
1629
|
+
locations: event.locations,
|
|
1630
|
+
rawInput: event.rawInput,
|
|
1631
|
+
displaySummary: meta?.displaySummary,
|
|
1632
|
+
displayTitle: meta?.displayTitle,
|
|
1633
|
+
displayKind: meta?.displayKind
|
|
1583
1634
|
};
|
|
1584
1635
|
this.enrichWithViewerLinks(event, metadata, sessionContext);
|
|
1585
1636
|
return { type: "tool_call", text: event.name, metadata };
|
|
1586
1637
|
}
|
|
1587
1638
|
case "tool_update": {
|
|
1639
|
+
const meta = event.meta;
|
|
1588
1640
|
const metadata = {
|
|
1589
1641
|
id: event.id,
|
|
1590
1642
|
name: event.name,
|
|
1591
1643
|
kind: event.kind,
|
|
1592
1644
|
status: event.status,
|
|
1593
|
-
content: event.content
|
|
1645
|
+
content: event.content,
|
|
1646
|
+
rawInput: event.rawInput,
|
|
1647
|
+
displaySummary: meta?.displaySummary,
|
|
1648
|
+
displayTitle: meta?.displayTitle,
|
|
1649
|
+
displayKind: meta?.displayKind
|
|
1594
1650
|
};
|
|
1595
1651
|
this.enrichWithViewerLinks(event, metadata, sessionContext);
|
|
1596
1652
|
return { type: "tool_update", text: "", metadata };
|
|
@@ -2129,13 +2185,791 @@ var EdgeTTS = class {
|
|
|
2129
2185
|
}
|
|
2130
2186
|
};
|
|
2131
2187
|
|
|
2188
|
+
// src/core/context/context-manager.ts
|
|
2189
|
+
import * as os from "os";
|
|
2190
|
+
import * as path5 from "path";
|
|
2191
|
+
|
|
2192
|
+
// src/core/context/context-cache.ts
|
|
2193
|
+
import * as fs5 from "fs";
|
|
2194
|
+
import * as path4 from "path";
|
|
2195
|
+
import * as crypto from "crypto";
|
|
2196
|
+
var DEFAULT_TTL_MS = 60 * 60 * 1e3;
|
|
2197
|
+
var ContextCache = class {
|
|
2198
|
+
constructor(cacheDir, ttlMs = DEFAULT_TTL_MS) {
|
|
2199
|
+
this.cacheDir = cacheDir;
|
|
2200
|
+
this.ttlMs = ttlMs;
|
|
2201
|
+
fs5.mkdirSync(cacheDir, { recursive: true });
|
|
2202
|
+
}
|
|
2203
|
+
keyHash(repoPath, queryKey) {
|
|
2204
|
+
return crypto.createHash("sha256").update(`${repoPath}:${queryKey}`).digest("hex").slice(0, 16);
|
|
2205
|
+
}
|
|
2206
|
+
filePath(repoPath, queryKey) {
|
|
2207
|
+
return path4.join(this.cacheDir, `${this.keyHash(repoPath, queryKey)}.json`);
|
|
2208
|
+
}
|
|
2209
|
+
get(repoPath, queryKey) {
|
|
2210
|
+
const fp = this.filePath(repoPath, queryKey);
|
|
2211
|
+
try {
|
|
2212
|
+
const stat = fs5.statSync(fp);
|
|
2213
|
+
if (Date.now() - stat.mtimeMs > this.ttlMs) {
|
|
2214
|
+
fs5.unlinkSync(fp);
|
|
2215
|
+
return null;
|
|
2216
|
+
}
|
|
2217
|
+
return JSON.parse(fs5.readFileSync(fp, "utf-8"));
|
|
2218
|
+
} catch {
|
|
2219
|
+
return null;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
set(repoPath, queryKey, result) {
|
|
2223
|
+
fs5.writeFileSync(this.filePath(repoPath, queryKey), JSON.stringify(result));
|
|
2224
|
+
}
|
|
2225
|
+
};
|
|
2226
|
+
|
|
2227
|
+
// src/core/context/context-manager.ts
|
|
2228
|
+
var ContextManager = class {
|
|
2229
|
+
providers = [];
|
|
2230
|
+
cache;
|
|
2231
|
+
constructor() {
|
|
2232
|
+
this.cache = new ContextCache(path5.join(os.homedir(), ".openacp", "cache", "entire"));
|
|
2233
|
+
}
|
|
2234
|
+
register(provider) {
|
|
2235
|
+
this.providers.push(provider);
|
|
2236
|
+
}
|
|
2237
|
+
async getProvider(repoPath) {
|
|
2238
|
+
for (const provider of this.providers) {
|
|
2239
|
+
if (await provider.isAvailable(repoPath)) return provider;
|
|
2240
|
+
}
|
|
2241
|
+
return null;
|
|
2242
|
+
}
|
|
2243
|
+
async listSessions(query) {
|
|
2244
|
+
const provider = await this.getProvider(query.repoPath);
|
|
2245
|
+
if (!provider) return null;
|
|
2246
|
+
return provider.listSessions(query);
|
|
2247
|
+
}
|
|
2248
|
+
async buildContext(query, options) {
|
|
2249
|
+
const queryKey = `${query.type}:${query.value}:${options?.limit ?? ""}:${options?.maxTokens ?? ""}`;
|
|
2250
|
+
const cached = this.cache.get(query.repoPath, queryKey);
|
|
2251
|
+
if (cached) return cached;
|
|
2252
|
+
const provider = await this.getProvider(query.repoPath);
|
|
2253
|
+
if (!provider) return null;
|
|
2254
|
+
const result = await provider.buildContext(query, options);
|
|
2255
|
+
if (result) this.cache.set(query.repoPath, queryKey, result);
|
|
2256
|
+
return result;
|
|
2257
|
+
}
|
|
2258
|
+
};
|
|
2259
|
+
|
|
2260
|
+
// src/core/context/context-provider.ts
|
|
2261
|
+
var DEFAULT_MAX_TOKENS = 3e4;
|
|
2262
|
+
var TOKENS_PER_TURN_ESTIMATE = 400;
|
|
2263
|
+
|
|
2264
|
+
// src/core/context/entire/checkpoint-reader.ts
|
|
2265
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
2266
|
+
var ENTIRE_BRANCH = "origin/entire/checkpoints/v1";
|
|
2267
|
+
var CHECKPOINT_ID_RE = /^[0-9a-f]{12}$/;
|
|
2268
|
+
var SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
2269
|
+
var CheckpointReader = class _CheckpointReader {
|
|
2270
|
+
constructor(repoPath) {
|
|
2271
|
+
this.repoPath = repoPath;
|
|
2272
|
+
}
|
|
2273
|
+
// ─── Git execution ───────────────────────────────────────────────────────────
|
|
2274
|
+
/**
|
|
2275
|
+
* Run a git command in the repo directory.
|
|
2276
|
+
* Returns trimmed stdout on success, empty string on failure.
|
|
2277
|
+
*/
|
|
2278
|
+
git(...args) {
|
|
2279
|
+
try {
|
|
2280
|
+
return execFileSync2("git", ["-C", this.repoPath, ...args], {
|
|
2281
|
+
encoding: "utf-8"
|
|
2282
|
+
}).trim();
|
|
2283
|
+
} catch {
|
|
2284
|
+
return "";
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
// ─── Static helpers ──────────────────────────────────────────────────────────
|
|
2288
|
+
/**
|
|
2289
|
+
* Convert a 12-char checkpoint ID to its shard path: "f634acf05138" → "f6/34acf05138"
|
|
2290
|
+
*/
|
|
2291
|
+
static shardPath(cpId) {
|
|
2292
|
+
return `${cpId.slice(0, 2)}/${cpId.slice(2)}`;
|
|
2293
|
+
}
|
|
2294
|
+
/**
|
|
2295
|
+
* Returns true when value looks like a 12-char lowercase hex checkpoint ID.
|
|
2296
|
+
*/
|
|
2297
|
+
static isCheckpointId(value) {
|
|
2298
|
+
return CHECKPOINT_ID_RE.test(value);
|
|
2299
|
+
}
|
|
2300
|
+
/**
|
|
2301
|
+
* Returns true when value looks like a UUID (session ID).
|
|
2302
|
+
*/
|
|
2303
|
+
static isSessionId(value) {
|
|
2304
|
+
return SESSION_ID_RE.test(value);
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Parse checkpoint-level metadata JSON. Returns null on error.
|
|
2308
|
+
*/
|
|
2309
|
+
static parseCheckpointMeta(json) {
|
|
2310
|
+
try {
|
|
2311
|
+
const parsed = JSON.parse(json);
|
|
2312
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
2313
|
+
if (!Array.isArray(parsed.sessions)) return null;
|
|
2314
|
+
return parsed;
|
|
2315
|
+
} catch {
|
|
2316
|
+
return null;
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
/**
|
|
2320
|
+
* Extract Entire-Checkpoint trailer IDs from `git log --format="%H|%(trailers:...)"` output.
|
|
2321
|
+
* Each line is: `<hash>|<trailer_value_or_empty>`. Returns only non-empty trailer values.
|
|
2322
|
+
* Uses the last pipe on each line to locate the trailer value, to be robust against
|
|
2323
|
+
* subject lines that contain pipes.
|
|
2324
|
+
*/
|
|
2325
|
+
static parseCheckpointTrailers(output) {
|
|
2326
|
+
const ids = [];
|
|
2327
|
+
for (const line of output.split("\n")) {
|
|
2328
|
+
const pipe = line.lastIndexOf("|");
|
|
2329
|
+
if (pipe === -1) continue;
|
|
2330
|
+
const trailerId = line.slice(pipe + 1).trim();
|
|
2331
|
+
if (trailerId) ids.push(trailerId);
|
|
2332
|
+
}
|
|
2333
|
+
return ids;
|
|
2334
|
+
}
|
|
2335
|
+
// ─── Branch check ────────────────────────────────────────────────────────────
|
|
2336
|
+
async hasEntireBranch() {
|
|
2337
|
+
const out = this.git("branch", "-r");
|
|
2338
|
+
return out.includes("entire/checkpoints/v1");
|
|
2339
|
+
}
|
|
2340
|
+
// ─── Core session fetching ───────────────────────────────────────────────────
|
|
2341
|
+
listAllCheckpointIds() {
|
|
2342
|
+
const out = this.git(
|
|
2343
|
+
"ls-tree",
|
|
2344
|
+
"-r",
|
|
2345
|
+
ENTIRE_BRANCH,
|
|
2346
|
+
"--name-only"
|
|
2347
|
+
);
|
|
2348
|
+
if (!out) return [];
|
|
2349
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2350
|
+
for (const file of out.split("\n")) {
|
|
2351
|
+
const parts = file.split("/");
|
|
2352
|
+
if (parts.length === 3 && parts[2] === "metadata.json") {
|
|
2353
|
+
ids.add(parts[0] + parts[1]);
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
return [...ids];
|
|
2357
|
+
}
|
|
2358
|
+
fetchCheckpointMeta(cpId) {
|
|
2359
|
+
const shard = _CheckpointReader.shardPath(cpId);
|
|
2360
|
+
const raw = this.git("show", `${ENTIRE_BRANCH}:${shard}/metadata.json`);
|
|
2361
|
+
if (!raw) return null;
|
|
2362
|
+
return _CheckpointReader.parseCheckpointMeta(raw);
|
|
2363
|
+
}
|
|
2364
|
+
fetchSessionMeta(metaPath) {
|
|
2365
|
+
const normalized = metaPath.startsWith("/") ? metaPath.slice(1) : metaPath;
|
|
2366
|
+
const raw = this.git("show", `${ENTIRE_BRANCH}:${normalized}`);
|
|
2367
|
+
if (!raw) return {};
|
|
2368
|
+
try {
|
|
2369
|
+
return JSON.parse(raw);
|
|
2370
|
+
} catch {
|
|
2371
|
+
return {};
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
/**
|
|
2375
|
+
* Build SessionInfo[] from a single checkpoint's metadata.
|
|
2376
|
+
*/
|
|
2377
|
+
buildSessionsForCheckpoint(cpId, cpMeta) {
|
|
2378
|
+
const sessions = [];
|
|
2379
|
+
for (let idx = 0; idx < cpMeta.sessions.length; idx++) {
|
|
2380
|
+
const sess = cpMeta.sessions[idx];
|
|
2381
|
+
const transcriptPath = (sess.transcript ?? "").replace(/^\//, "");
|
|
2382
|
+
const metaPath = sess.metadata ?? "";
|
|
2383
|
+
const smeta = this.fetchSessionMeta(metaPath);
|
|
2384
|
+
const createdAt = smeta.created_at ?? "";
|
|
2385
|
+
sessions.push({
|
|
2386
|
+
checkpointId: cpId,
|
|
2387
|
+
sessionIndex: String(idx),
|
|
2388
|
+
transcriptPath,
|
|
2389
|
+
createdAt,
|
|
2390
|
+
endedAt: createdAt,
|
|
2391
|
+
// will be filled from JSONL by conversation builder
|
|
2392
|
+
branch: smeta.branch ?? cpMeta.branch ?? "",
|
|
2393
|
+
agent: smeta.agent ?? "",
|
|
2394
|
+
turnCount: smeta.session_metrics?.turn_count ?? 0,
|
|
2395
|
+
filesTouched: smeta.files_touched ?? cpMeta.files_touched ?? [],
|
|
2396
|
+
sessionId: smeta.session_id ?? ""
|
|
2397
|
+
});
|
|
2398
|
+
}
|
|
2399
|
+
return sessions;
|
|
2400
|
+
}
|
|
2401
|
+
getSessionsForCheckpoint(cpId) {
|
|
2402
|
+
const meta = this.fetchCheckpointMeta(cpId);
|
|
2403
|
+
if (!meta) return [];
|
|
2404
|
+
return this.buildSessionsForCheckpoint(cpId, meta);
|
|
2405
|
+
}
|
|
2406
|
+
// ─── Public resolvers ────────────────────────────────────────────────────────
|
|
2407
|
+
/**
|
|
2408
|
+
* All sessions recorded on a given branch, sorted by createdAt ascending.
|
|
2409
|
+
*/
|
|
2410
|
+
async resolveByBranch(branchName) {
|
|
2411
|
+
const cpIds = this.listAllCheckpointIds();
|
|
2412
|
+
const sessions = [];
|
|
2413
|
+
for (const cpId of cpIds) {
|
|
2414
|
+
const meta = this.fetchCheckpointMeta(cpId);
|
|
2415
|
+
if (!meta) continue;
|
|
2416
|
+
if (meta.branch !== branchName) continue;
|
|
2417
|
+
sessions.push(...this.buildSessionsForCheckpoint(cpId, meta));
|
|
2418
|
+
}
|
|
2419
|
+
sessions.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
2420
|
+
return sessions;
|
|
2421
|
+
}
|
|
2422
|
+
/**
|
|
2423
|
+
* Sessions linked to a specific commit via the Entire-Checkpoint git trailer.
|
|
2424
|
+
*/
|
|
2425
|
+
async resolveByCommit(commitHash) {
|
|
2426
|
+
const fullHash = this.git("rev-parse", commitHash);
|
|
2427
|
+
if (!fullHash) return [];
|
|
2428
|
+
const cpId = this.git(
|
|
2429
|
+
"log",
|
|
2430
|
+
"-1",
|
|
2431
|
+
"--format=%(trailers:key=Entire-Checkpoint,valueonly)",
|
|
2432
|
+
fullHash
|
|
2433
|
+
);
|
|
2434
|
+
if (!cpId) return [];
|
|
2435
|
+
return this.getSessionsForCheckpoint(cpId.trim());
|
|
2436
|
+
}
|
|
2437
|
+
/**
|
|
2438
|
+
* All sessions from a merged PR (by number or GitHub URL).
|
|
2439
|
+
*/
|
|
2440
|
+
async resolveByPr(prInput) {
|
|
2441
|
+
let prNumber;
|
|
2442
|
+
if (/^\d+$/.test(prInput)) {
|
|
2443
|
+
prNumber = prInput;
|
|
2444
|
+
} else {
|
|
2445
|
+
const m = /\/pull\/(\d+)/.exec(prInput);
|
|
2446
|
+
if (!m) return [];
|
|
2447
|
+
prNumber = m[1];
|
|
2448
|
+
}
|
|
2449
|
+
const mergeOut = this.git(
|
|
2450
|
+
"log",
|
|
2451
|
+
"--all",
|
|
2452
|
+
"--oneline",
|
|
2453
|
+
"--grep",
|
|
2454
|
+
`Merge pull request #${prNumber}`
|
|
2455
|
+
);
|
|
2456
|
+
if (!mergeOut) return [];
|
|
2457
|
+
const mergeCommit = mergeOut.split("\n")[0].split(" ")[0];
|
|
2458
|
+
const logOut = this.git(
|
|
2459
|
+
"log",
|
|
2460
|
+
"--format=%H|%(trailers:key=Entire-Checkpoint,valueonly)",
|
|
2461
|
+
`${mergeCommit}^2`,
|
|
2462
|
+
"--not",
|
|
2463
|
+
`${mergeCommit}^1`
|
|
2464
|
+
);
|
|
2465
|
+
if (!logOut) return [];
|
|
2466
|
+
const cpIds = _CheckpointReader.parseCheckpointTrailers(logOut);
|
|
2467
|
+
const sessions = [];
|
|
2468
|
+
for (const cpId of cpIds) {
|
|
2469
|
+
sessions.push(...this.getSessionsForCheckpoint(cpId));
|
|
2470
|
+
}
|
|
2471
|
+
sessions.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
2472
|
+
return sessions;
|
|
2473
|
+
}
|
|
2474
|
+
/**
|
|
2475
|
+
* Sessions matching a specific checkpoint ID.
|
|
2476
|
+
*/
|
|
2477
|
+
async resolveByCheckpoint(checkpointId) {
|
|
2478
|
+
return this.getSessionsForCheckpoint(checkpointId);
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Find a session by its UUID.
|
|
2482
|
+
*/
|
|
2483
|
+
async resolveBySessionId(sessionId) {
|
|
2484
|
+
const cpIds = this.listAllCheckpointIds();
|
|
2485
|
+
for (const cpId of cpIds) {
|
|
2486
|
+
const sessions = this.getSessionsForCheckpoint(cpId);
|
|
2487
|
+
const match = sessions.find((s) => s.sessionId === sessionId);
|
|
2488
|
+
if (match) return [match];
|
|
2489
|
+
}
|
|
2490
|
+
return [];
|
|
2491
|
+
}
|
|
2492
|
+
/**
|
|
2493
|
+
* Latest N sessions across all checkpoints, sorted by createdAt descending.
|
|
2494
|
+
*/
|
|
2495
|
+
async resolveLatest(count) {
|
|
2496
|
+
const cpIds = this.listAllCheckpointIds();
|
|
2497
|
+
const all = [];
|
|
2498
|
+
for (const cpId of cpIds) {
|
|
2499
|
+
all.push(...this.getSessionsForCheckpoint(cpId));
|
|
2500
|
+
}
|
|
2501
|
+
all.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
2502
|
+
return all.slice(0, count);
|
|
2503
|
+
}
|
|
2504
|
+
/**
|
|
2505
|
+
* Read the full JSONL transcript content from the entire branch.
|
|
2506
|
+
*/
|
|
2507
|
+
getTranscript(transcriptPath) {
|
|
2508
|
+
const normalized = transcriptPath.startsWith("/") ? transcriptPath.slice(1) : transcriptPath;
|
|
2509
|
+
return this.git("show", `${ENTIRE_BRANCH}:${normalized}`);
|
|
2510
|
+
}
|
|
2511
|
+
};
|
|
2512
|
+
|
|
2513
|
+
// src/core/context/entire/message-cleaner.ts
|
|
2514
|
+
var SYSTEM_TAG_PATTERNS = [
|
|
2515
|
+
/<system-reminder>[\s\S]*?<\/system-reminder>/g,
|
|
2516
|
+
/<local-command-caveat>[\s\S]*?<\/local-command-caveat>/g,
|
|
2517
|
+
/<local-command-stdout>[\s\S]*?<\/local-command-stdout>/g,
|
|
2518
|
+
/<command-name>[\s\S]*?<\/command-name>/g,
|
|
2519
|
+
/<command-message>[\s\S]*?<\/command-message>/g,
|
|
2520
|
+
/<user-prompt-submit-hook>[\s\S]*?<\/user-prompt-submit-hook>/g,
|
|
2521
|
+
/<ide_selection>[\s\S]*?<\/ide_selection>/g,
|
|
2522
|
+
/<ide_context>[\s\S]*?<\/ide_context>/g,
|
|
2523
|
+
/<ide_opened_file>[\s\S]*?<\/ide_opened_file>/g,
|
|
2524
|
+
/<cursor_context>[\s\S]*?<\/cursor_context>/g,
|
|
2525
|
+
/<attached_files>[\s\S]*?<\/attached_files>/g,
|
|
2526
|
+
/<repo_context>[\s\S]*?<\/repo_context>/g,
|
|
2527
|
+
/<task-notification>[\s\S]*?<\/task-notification>/g
|
|
2528
|
+
];
|
|
2529
|
+
var COMMAND_ARGS_RE = /<command-args>([\s\S]*?)<\/command-args>/;
|
|
2530
|
+
function cleanSystemTags(text) {
|
|
2531
|
+
const argsMatch = COMMAND_ARGS_RE.exec(text);
|
|
2532
|
+
const userArgs = argsMatch?.[1]?.trim() ?? "";
|
|
2533
|
+
text = text.replace(/<command-args>[\s\S]*?<\/command-args>/g, "");
|
|
2534
|
+
for (const pat of SYSTEM_TAG_PATTERNS) {
|
|
2535
|
+
text = text.replace(new RegExp(pat.source, pat.flags), "");
|
|
2536
|
+
}
|
|
2537
|
+
text = text.trim();
|
|
2538
|
+
if (!text && userArgs) return userArgs;
|
|
2539
|
+
if (text && userArgs && text !== userArgs) return `${text}
|
|
2540
|
+
${userArgs}`;
|
|
2541
|
+
return text || userArgs;
|
|
2542
|
+
}
|
|
2543
|
+
var SKILL_INDICATORS = [
|
|
2544
|
+
"Base directory for this skill:",
|
|
2545
|
+
"<HARD-GATE>",
|
|
2546
|
+
"## Checklist",
|
|
2547
|
+
"## Process Flow",
|
|
2548
|
+
"## Key Principles",
|
|
2549
|
+
"digraph brainstorming",
|
|
2550
|
+
"You MUST create a task for each"
|
|
2551
|
+
];
|
|
2552
|
+
function isSkillPrompt(text) {
|
|
2553
|
+
for (const indicator of SKILL_INDICATORS) {
|
|
2554
|
+
if (text.includes(indicator)) return true;
|
|
2555
|
+
}
|
|
2556
|
+
if (text.length > 2e3) {
|
|
2557
|
+
const headerCount = (text.match(/## /g) || []).length;
|
|
2558
|
+
if (headerCount >= 3) return true;
|
|
2559
|
+
}
|
|
2560
|
+
return false;
|
|
2561
|
+
}
|
|
2562
|
+
function isNoiseMessage(text) {
|
|
2563
|
+
const cleaned = cleanSystemTags(text);
|
|
2564
|
+
if (!cleaned) return true;
|
|
2565
|
+
if (/^(ready|ready\.)$/i.test(cleaned)) return true;
|
|
2566
|
+
if (cleaned.includes("Tell your human partner that this command is deprecated")) return true;
|
|
2567
|
+
if (cleaned.startsWith("Read the output file to retrieve the result:")) return true;
|
|
2568
|
+
if (/^(opus|sonnet|haiku|claude)(\[.*\])?$/i.test(cleaned)) return true;
|
|
2569
|
+
return false;
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
// src/core/context/entire/conversation-builder.ts
|
|
2573
|
+
function selectMode(totalTurns) {
|
|
2574
|
+
if (totalTurns <= 10) return "full";
|
|
2575
|
+
if (totalTurns <= 25) return "balanced";
|
|
2576
|
+
return "compact";
|
|
2577
|
+
}
|
|
2578
|
+
function estimateTokens(text) {
|
|
2579
|
+
return Math.floor(text.length / 4);
|
|
2580
|
+
}
|
|
2581
|
+
function shortenPath(fp) {
|
|
2582
|
+
const parts = fp.split("/");
|
|
2583
|
+
if (parts.length >= 2) return parts.slice(-2).join("/");
|
|
2584
|
+
return fp;
|
|
2585
|
+
}
|
|
2586
|
+
function countLines(s) {
|
|
2587
|
+
const trimmed = s.trim();
|
|
2588
|
+
if (!trimmed) return 0;
|
|
2589
|
+
return trimmed.split("\n").length;
|
|
2590
|
+
}
|
|
2591
|
+
function extractText(content) {
|
|
2592
|
+
if (typeof content === "string") return content;
|
|
2593
|
+
if (Array.isArray(content)) {
|
|
2594
|
+
return content.filter((b) => typeof b === "object" && b !== null && b.type === "text").map((b) => b.text).join("\n");
|
|
2595
|
+
}
|
|
2596
|
+
return "";
|
|
2597
|
+
}
|
|
2598
|
+
function extractContentBlocks(content) {
|
|
2599
|
+
if (typeof content === "string") return [{ type: "text", text: content }];
|
|
2600
|
+
if (Array.isArray(content)) {
|
|
2601
|
+
return content.filter((b) => typeof b === "object" && b !== null);
|
|
2602
|
+
}
|
|
2603
|
+
return [];
|
|
2604
|
+
}
|
|
2605
|
+
function isToolResultOnly(content) {
|
|
2606
|
+
if (typeof content === "string") return false;
|
|
2607
|
+
if (!Array.isArray(content)) return true;
|
|
2608
|
+
for (const block of content) {
|
|
2609
|
+
if (typeof block === "object" && block !== null) {
|
|
2610
|
+
const b = block;
|
|
2611
|
+
if (b.type === "text" && typeof b.text === "string" && b.text.trim()) return false;
|
|
2612
|
+
if (b.type === "image") return false;
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
return true;
|
|
2616
|
+
}
|
|
2617
|
+
function hasImage(content) {
|
|
2618
|
+
if (!Array.isArray(content)) return false;
|
|
2619
|
+
return content.some((b) => typeof b === "object" && b !== null && b.type === "image");
|
|
2620
|
+
}
|
|
2621
|
+
function formatEditFull(filePath, oldStr, newStr) {
|
|
2622
|
+
const lines = [];
|
|
2623
|
+
lines.push(`\u270F\uFE0F \`${filePath}\``);
|
|
2624
|
+
lines.push("```diff");
|
|
2625
|
+
for (const line of oldStr.split("\n")) lines.push(`- ${line}`);
|
|
2626
|
+
for (const line of newStr.split("\n")) lines.push(`+ ${line}`);
|
|
2627
|
+
lines.push("```");
|
|
2628
|
+
return lines.join("\n");
|
|
2629
|
+
}
|
|
2630
|
+
function formatEditBalanced(filePath, oldStr, newStr, maxDiffLines = 12) {
|
|
2631
|
+
const oldLines = oldStr.split("\n");
|
|
2632
|
+
const newLines = newStr.split("\n");
|
|
2633
|
+
const total = oldLines.length + newLines.length;
|
|
2634
|
+
const lines = [];
|
|
2635
|
+
lines.push(`\u270F\uFE0F \`${filePath}\``);
|
|
2636
|
+
lines.push("```diff");
|
|
2637
|
+
if (total <= maxDiffLines) {
|
|
2638
|
+
for (const line of oldLines) lines.push(`- ${line}`);
|
|
2639
|
+
for (const line of newLines) lines.push(`+ ${line}`);
|
|
2640
|
+
} else {
|
|
2641
|
+
const half = Math.floor(maxDiffLines / 2);
|
|
2642
|
+
for (const line of oldLines.slice(0, half)) lines.push(`- ${line}`);
|
|
2643
|
+
if (oldLines.length > half) lines.push(` ... (-${oldLines.length} lines total)`);
|
|
2644
|
+
for (const line of newLines.slice(0, half)) lines.push(`+ ${line}`);
|
|
2645
|
+
if (newLines.length > half) lines.push(` ... (+${newLines.length} lines total)`);
|
|
2646
|
+
}
|
|
2647
|
+
lines.push("```");
|
|
2648
|
+
return lines.join("\n");
|
|
2649
|
+
}
|
|
2650
|
+
function formatEditCompact(filePath, oldStr, newStr) {
|
|
2651
|
+
const oldLines = countLines(oldStr);
|
|
2652
|
+
const newLines = countLines(newStr);
|
|
2653
|
+
let firstNew = "";
|
|
2654
|
+
for (const line of newStr.split("\n")) {
|
|
2655
|
+
const stripped = line.trim();
|
|
2656
|
+
if (stripped && !stripped.startsWith("//") && !stripped.startsWith("*")) {
|
|
2657
|
+
firstNew = stripped.slice(0, 80);
|
|
2658
|
+
break;
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
if (firstNew) {
|
|
2662
|
+
return `\u270F\uFE0F \`${filePath}\` (-${oldLines}/+${newLines} lines): \`${firstNew}\``;
|
|
2663
|
+
}
|
|
2664
|
+
return `\u270F\uFE0F \`${filePath}\` (-${oldLines}/+${newLines} lines)`;
|
|
2665
|
+
}
|
|
2666
|
+
function formatWriteFull(filePath, content) {
|
|
2667
|
+
const lines = [];
|
|
2668
|
+
lines.push(`\u{1F4DD} \`${filePath}\``);
|
|
2669
|
+
lines.push("```");
|
|
2670
|
+
lines.push(content);
|
|
2671
|
+
lines.push("```");
|
|
2672
|
+
return lines.join("\n");
|
|
2673
|
+
}
|
|
2674
|
+
function formatWriteBalanced(filePath, content, maxLines = 15) {
|
|
2675
|
+
const contentLines = content.split("\n");
|
|
2676
|
+
const lines = [];
|
|
2677
|
+
lines.push(`\u{1F4DD} \`${filePath}\` (${contentLines.length} lines)`);
|
|
2678
|
+
lines.push("```");
|
|
2679
|
+
for (const line of contentLines.slice(0, maxLines)) lines.push(line);
|
|
2680
|
+
if (contentLines.length > maxLines) lines.push(`... (${contentLines.length - maxLines} more lines)`);
|
|
2681
|
+
lines.push("```");
|
|
2682
|
+
return lines.join("\n");
|
|
2683
|
+
}
|
|
2684
|
+
function formatWriteCompact(filePath, content) {
|
|
2685
|
+
const numLines = countLines(content);
|
|
2686
|
+
return `\u{1F4DD} \`${filePath}\` (${numLines} lines written)`;
|
|
2687
|
+
}
|
|
2688
|
+
function parseJsonlToTurns(jsonl) {
|
|
2689
|
+
const events = [];
|
|
2690
|
+
for (const rawLine of jsonl.split("\n")) {
|
|
2691
|
+
const line = rawLine.trim();
|
|
2692
|
+
if (!line) continue;
|
|
2693
|
+
try {
|
|
2694
|
+
events.push(JSON.parse(line));
|
|
2695
|
+
} catch {
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
let branch = "unknown";
|
|
2699
|
+
for (const e of events) {
|
|
2700
|
+
if (e.gitBranch) {
|
|
2701
|
+
branch = e.gitBranch;
|
|
2702
|
+
break;
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
const convEvents = events.filter((e) => e.type === "user" || e.type === "assistant");
|
|
2706
|
+
const turns = [];
|
|
2707
|
+
let currentTurn = null;
|
|
2708
|
+
for (const e of convEvents) {
|
|
2709
|
+
const etype = e.type;
|
|
2710
|
+
const content = e.message?.content ?? [];
|
|
2711
|
+
const ts = e.timestamp ?? "";
|
|
2712
|
+
if (etype === "user") {
|
|
2713
|
+
if (isToolResultOnly(content)) continue;
|
|
2714
|
+
const text = extractText(content);
|
|
2715
|
+
if (isSkillPrompt(text)) continue;
|
|
2716
|
+
if (isNoiseMessage(text)) continue;
|
|
2717
|
+
const cleaned = cleanSystemTags(text);
|
|
2718
|
+
if (!cleaned) continue;
|
|
2719
|
+
if (currentTurn) turns.push(currentTurn);
|
|
2720
|
+
const imgSuffix = hasImage(content) ? " [image]" : "";
|
|
2721
|
+
currentTurn = {
|
|
2722
|
+
userText: cleaned + imgSuffix,
|
|
2723
|
+
userTimestamp: ts,
|
|
2724
|
+
assistantParts: []
|
|
2725
|
+
};
|
|
2726
|
+
} else if (etype === "assistant" && currentTurn) {
|
|
2727
|
+
const blocks = extractContentBlocks(content);
|
|
2728
|
+
let pendingText = null;
|
|
2729
|
+
for (const block of blocks) {
|
|
2730
|
+
const btype = block.type;
|
|
2731
|
+
if (btype === "text") {
|
|
2732
|
+
const text = typeof block.text === "string" ? block.text.trim() : "";
|
|
2733
|
+
if (text) pendingText = text;
|
|
2734
|
+
} else if (btype === "tool_use") {
|
|
2735
|
+
const name = typeof block.name === "string" ? block.name : "";
|
|
2736
|
+
const inp = typeof block.input === "object" && block.input !== null ? block.input : {};
|
|
2737
|
+
if (name === "Edit") {
|
|
2738
|
+
if (pendingText) {
|
|
2739
|
+
currentTurn.assistantParts.push({ type: "text", content: pendingText });
|
|
2740
|
+
pendingText = null;
|
|
2741
|
+
}
|
|
2742
|
+
currentTurn.assistantParts.push({
|
|
2743
|
+
type: "edit",
|
|
2744
|
+
file: shortenPath(inp.file_path ?? ""),
|
|
2745
|
+
old: inp.old_string ?? "",
|
|
2746
|
+
new: inp.new_string ?? ""
|
|
2747
|
+
});
|
|
2748
|
+
} else if (name === "Write") {
|
|
2749
|
+
if (pendingText) {
|
|
2750
|
+
currentTurn.assistantParts.push({ type: "text", content: pendingText });
|
|
2751
|
+
pendingText = null;
|
|
2752
|
+
}
|
|
2753
|
+
currentTurn.assistantParts.push({
|
|
2754
|
+
type: "write",
|
|
2755
|
+
file: shortenPath(inp.file_path ?? ""),
|
|
2756
|
+
fileContent: inp.content ?? ""
|
|
2757
|
+
});
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
if (pendingText) {
|
|
2762
|
+
currentTurn.assistantParts.push({ type: "text", content: pendingText });
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
if (currentTurn) turns.push(currentTurn);
|
|
2767
|
+
const firstTimestamp = turns[0]?.userTimestamp ?? "";
|
|
2768
|
+
const lastTimestamp = turns[turns.length - 1]?.userTimestamp ?? "";
|
|
2769
|
+
return { turns, branch, firstTimestamp, lastTimestamp };
|
|
2770
|
+
}
|
|
2771
|
+
function buildSessionMarkdown(turns, mode) {
|
|
2772
|
+
const out = [];
|
|
2773
|
+
for (let i = 0; i < turns.length; i++) {
|
|
2774
|
+
const turn = turns[i];
|
|
2775
|
+
const userText = turn.userText.trim();
|
|
2776
|
+
if (!userText) continue;
|
|
2777
|
+
out.push(`**User [${i + 1}]:**`);
|
|
2778
|
+
out.push(userText);
|
|
2779
|
+
out.push("");
|
|
2780
|
+
let hasContent = false;
|
|
2781
|
+
for (const part of turn.assistantParts) {
|
|
2782
|
+
if (part.type === "text") {
|
|
2783
|
+
if (!hasContent) {
|
|
2784
|
+
out.push("**Assistant:**");
|
|
2785
|
+
hasContent = true;
|
|
2786
|
+
}
|
|
2787
|
+
out.push(part.content ?? "");
|
|
2788
|
+
out.push("");
|
|
2789
|
+
} else if (part.type === "edit") {
|
|
2790
|
+
if (!hasContent) {
|
|
2791
|
+
out.push("**Assistant:**");
|
|
2792
|
+
hasContent = true;
|
|
2793
|
+
}
|
|
2794
|
+
const file = part.file ?? "";
|
|
2795
|
+
const oldStr = part.old ?? "";
|
|
2796
|
+
const newStr = part.new ?? "";
|
|
2797
|
+
if (mode === "full") {
|
|
2798
|
+
out.push(formatEditFull(file, oldStr, newStr));
|
|
2799
|
+
} else if (mode === "balanced") {
|
|
2800
|
+
out.push(formatEditBalanced(file, oldStr, newStr));
|
|
2801
|
+
} else {
|
|
2802
|
+
out.push(formatEditCompact(file, oldStr, newStr));
|
|
2803
|
+
}
|
|
2804
|
+
out.push("");
|
|
2805
|
+
} else if (part.type === "write") {
|
|
2806
|
+
if (!hasContent) {
|
|
2807
|
+
out.push("**Assistant:**");
|
|
2808
|
+
hasContent = true;
|
|
2809
|
+
}
|
|
2810
|
+
const file = part.file ?? "";
|
|
2811
|
+
const content = part.fileContent ?? "";
|
|
2812
|
+
if (mode === "full") {
|
|
2813
|
+
out.push(formatWriteFull(file, content));
|
|
2814
|
+
} else if (mode === "balanced") {
|
|
2815
|
+
out.push(formatWriteBalanced(file, content));
|
|
2816
|
+
} else {
|
|
2817
|
+
out.push(formatWriteCompact(file, content));
|
|
2818
|
+
}
|
|
2819
|
+
out.push("");
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
out.push("---");
|
|
2823
|
+
out.push("");
|
|
2824
|
+
}
|
|
2825
|
+
return out.join("\n");
|
|
2826
|
+
}
|
|
2827
|
+
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.`;
|
|
2828
|
+
function mergeSessionsMarkdown(sessions, mode, title) {
|
|
2829
|
+
const sorted = [...sessions].sort((a, b) => a.startTime.localeCompare(b.startTime));
|
|
2830
|
+
const totalTurns = sorted.reduce((sum, s) => sum + s.turns, 0);
|
|
2831
|
+
const overallStart = sorted[0]?.startTime.slice(0, 16) ?? "?";
|
|
2832
|
+
const overallEnd = sorted[sorted.length - 1]?.endTime.slice(0, 16) ?? "?";
|
|
2833
|
+
const out = [];
|
|
2834
|
+
out.push(`# Conversation History from ${title}`);
|
|
2835
|
+
out.push(`${sorted.length} sessions | ${totalTurns} turns | ${overallStart} \u2192 ${overallEnd} | mode: ${mode}`);
|
|
2836
|
+
out.push("");
|
|
2837
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
2838
|
+
const s = sorted[i];
|
|
2839
|
+
const start = s.startTime.slice(0, 16);
|
|
2840
|
+
const end = s.endTime.slice(0, 16);
|
|
2841
|
+
out.push(`## Session Conversation History ${i + 1} \u2014 ${start} \u2192 ${end} (${s.agent}, ${s.turns} turns, branch: ${s.branch})`);
|
|
2842
|
+
out.push("");
|
|
2843
|
+
out.push(s.markdown);
|
|
2844
|
+
}
|
|
2845
|
+
out.push(DISCLAIMER);
|
|
2846
|
+
out.push("");
|
|
2847
|
+
return out.join("\n");
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
// src/core/context/entire/entire-provider.ts
|
|
2851
|
+
var EntireProvider = class {
|
|
2852
|
+
name = "entire";
|
|
2853
|
+
async isAvailable(repoPath) {
|
|
2854
|
+
return new CheckpointReader(repoPath).hasEntireBranch();
|
|
2855
|
+
}
|
|
2856
|
+
async listSessions(query) {
|
|
2857
|
+
const reader = new CheckpointReader(query.repoPath);
|
|
2858
|
+
const sessions = await this.resolveSessions(reader, query);
|
|
2859
|
+
const estimatedTokens = sessions.reduce((sum, s) => sum + s.turnCount * TOKENS_PER_TURN_ESTIMATE, 0);
|
|
2860
|
+
return { sessions, estimatedTokens };
|
|
2861
|
+
}
|
|
2862
|
+
async buildContext(query, options) {
|
|
2863
|
+
const maxTokens = options?.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
2864
|
+
const reader = new CheckpointReader(query.repoPath);
|
|
2865
|
+
let sessions = await this.resolveSessions(reader, query);
|
|
2866
|
+
if (options?.limit && sessions.length > options.limit) {
|
|
2867
|
+
sessions = sessions.slice(-options.limit);
|
|
2868
|
+
}
|
|
2869
|
+
if (sessions.length === 0) {
|
|
2870
|
+
return { markdown: "", tokenEstimate: 0, sessionCount: 0, totalTurns: 0, mode: "full", truncated: false, timeRange: { start: "", end: "" } };
|
|
2871
|
+
}
|
|
2872
|
+
const parsedSessions = [];
|
|
2873
|
+
for (const sess of sessions) {
|
|
2874
|
+
const jsonl = reader.getTranscript(sess.transcriptPath);
|
|
2875
|
+
if (jsonl) parsedSessions.push({ session: sess, jsonl });
|
|
2876
|
+
}
|
|
2877
|
+
if (parsedSessions.length === 0) {
|
|
2878
|
+
return { markdown: "", tokenEstimate: 0, sessionCount: 0, totalTurns: 0, mode: "full", truncated: false, timeRange: { start: "", end: "" } };
|
|
2879
|
+
}
|
|
2880
|
+
const totalTurns = parsedSessions.reduce((sum, ps) => {
|
|
2881
|
+
const parsed = parseJsonlToTurns(ps.jsonl);
|
|
2882
|
+
return sum + parsed.turns.length;
|
|
2883
|
+
}, 0);
|
|
2884
|
+
let mode = selectMode(totalTurns);
|
|
2885
|
+
const title = this.buildTitle(query);
|
|
2886
|
+
let sessionMarkdowns = this.buildSessionMarkdowns(parsedSessions, mode);
|
|
2887
|
+
let merged = mergeSessionsMarkdown(sessionMarkdowns, mode, title);
|
|
2888
|
+
let tokens = estimateTokens(merged);
|
|
2889
|
+
if (tokens > maxTokens && mode !== "compact") {
|
|
2890
|
+
mode = "compact";
|
|
2891
|
+
sessionMarkdowns = this.buildSessionMarkdowns(parsedSessions, "compact");
|
|
2892
|
+
merged = mergeSessionsMarkdown(sessionMarkdowns, "compact", title);
|
|
2893
|
+
tokens = estimateTokens(merged);
|
|
2894
|
+
}
|
|
2895
|
+
let truncated = false;
|
|
2896
|
+
while (tokens > maxTokens && sessionMarkdowns.length > 1) {
|
|
2897
|
+
sessionMarkdowns = sessionMarkdowns.slice(1);
|
|
2898
|
+
truncated = true;
|
|
2899
|
+
merged = mergeSessionsMarkdown(sessionMarkdowns, mode, title);
|
|
2900
|
+
tokens = estimateTokens(merged);
|
|
2901
|
+
}
|
|
2902
|
+
const allTimes = sessionMarkdowns.flatMap((s) => [s.startTime, s.endTime]).filter(Boolean).sort();
|
|
2903
|
+
const finalTurns = sessionMarkdowns.reduce((sum, s) => sum + s.turns, 0);
|
|
2904
|
+
return {
|
|
2905
|
+
markdown: merged,
|
|
2906
|
+
tokenEstimate: tokens,
|
|
2907
|
+
sessionCount: sessionMarkdowns.length,
|
|
2908
|
+
totalTurns: finalTurns,
|
|
2909
|
+
mode,
|
|
2910
|
+
truncated,
|
|
2911
|
+
timeRange: { start: allTimes[0] ?? "", end: allTimes[allTimes.length - 1] ?? "" }
|
|
2912
|
+
};
|
|
2913
|
+
}
|
|
2914
|
+
buildSessionMarkdowns(parsedSessions, mode) {
|
|
2915
|
+
return parsedSessions.map((ps) => {
|
|
2916
|
+
const parsed = parseJsonlToTurns(ps.jsonl);
|
|
2917
|
+
return {
|
|
2918
|
+
markdown: buildSessionMarkdown(parsed.turns, mode),
|
|
2919
|
+
startTime: parsed.firstTimestamp,
|
|
2920
|
+
endTime: parsed.lastTimestamp,
|
|
2921
|
+
agent: ps.session.agent,
|
|
2922
|
+
turns: parsed.turns.length,
|
|
2923
|
+
branch: ps.session.branch,
|
|
2924
|
+
files: ps.session.filesTouched.map((f) => f.split("/").pop() ?? f)
|
|
2925
|
+
};
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2928
|
+
async resolveSessions(reader, query) {
|
|
2929
|
+
switch (query.type) {
|
|
2930
|
+
case "branch":
|
|
2931
|
+
return reader.resolveByBranch(query.value);
|
|
2932
|
+
case "commit":
|
|
2933
|
+
return reader.resolveByCommit(query.value);
|
|
2934
|
+
case "pr":
|
|
2935
|
+
return reader.resolveByPr(query.value);
|
|
2936
|
+
case "checkpoint":
|
|
2937
|
+
return reader.resolveByCheckpoint(query.value);
|
|
2938
|
+
case "session":
|
|
2939
|
+
return reader.resolveBySessionId(query.value);
|
|
2940
|
+
case "latest":
|
|
2941
|
+
return reader.resolveLatest(parseInt(query.value) || 5);
|
|
2942
|
+
default:
|
|
2943
|
+
return [];
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
buildTitle(query) {
|
|
2947
|
+
switch (query.type) {
|
|
2948
|
+
case "pr":
|
|
2949
|
+
return `PR #${query.value.replace(/.*\/pull\//, "")}`;
|
|
2950
|
+
case "branch":
|
|
2951
|
+
return `branch \`${query.value}\``;
|
|
2952
|
+
case "commit":
|
|
2953
|
+
return `commit \`${query.value.slice(0, 8)}\``;
|
|
2954
|
+
case "checkpoint":
|
|
2955
|
+
return `checkpoint \`${query.value}\``;
|
|
2956
|
+
case "session":
|
|
2957
|
+
return `session \`${query.value.slice(0, 8)}...\``;
|
|
2958
|
+
case "latest":
|
|
2959
|
+
return `latest ${query.value} sessions`;
|
|
2960
|
+
default:
|
|
2961
|
+
return "unknown";
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
};
|
|
2965
|
+
|
|
2132
2966
|
// src/core/core.ts
|
|
2133
|
-
import
|
|
2134
|
-
import
|
|
2967
|
+
import path7 from "path";
|
|
2968
|
+
import os2 from "os";
|
|
2135
2969
|
|
|
2136
2970
|
// src/core/session-store.ts
|
|
2137
|
-
import
|
|
2138
|
-
import
|
|
2971
|
+
import fs6 from "fs";
|
|
2972
|
+
import path6 from "path";
|
|
2139
2973
|
var log6 = createChildLogger({ module: "session-store" });
|
|
2140
2974
|
var DEBOUNCE_MS2 = 2e3;
|
|
2141
2975
|
var JsonFileSessionStore = class {
|
|
@@ -2197,9 +3031,9 @@ var JsonFileSessionStore = class {
|
|
|
2197
3031
|
version: 1,
|
|
2198
3032
|
sessions: Object.fromEntries(this.records)
|
|
2199
3033
|
};
|
|
2200
|
-
const dir =
|
|
2201
|
-
if (!
|
|
2202
|
-
|
|
3034
|
+
const dir = path6.dirname(this.filePath);
|
|
3035
|
+
if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
|
|
3036
|
+
fs6.writeFileSync(this.filePath, JSON.stringify(data, null, 2));
|
|
2203
3037
|
}
|
|
2204
3038
|
destroy() {
|
|
2205
3039
|
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
@@ -2212,10 +3046,10 @@ var JsonFileSessionStore = class {
|
|
|
2212
3046
|
}
|
|
2213
3047
|
}
|
|
2214
3048
|
load() {
|
|
2215
|
-
if (!
|
|
3049
|
+
if (!fs6.existsSync(this.filePath)) return;
|
|
2216
3050
|
try {
|
|
2217
3051
|
const raw = JSON.parse(
|
|
2218
|
-
|
|
3052
|
+
fs6.readFileSync(this.filePath, "utf-8")
|
|
2219
3053
|
);
|
|
2220
3054
|
if (raw.version !== 1) {
|
|
2221
3055
|
log6.warn(
|
|
@@ -2279,13 +3113,14 @@ var OpenACPCore = class {
|
|
|
2279
3113
|
sessionFactory;
|
|
2280
3114
|
usageStore = null;
|
|
2281
3115
|
usageBudget = null;
|
|
3116
|
+
contextManager;
|
|
2282
3117
|
constructor(configManager) {
|
|
2283
3118
|
this.configManager = configManager;
|
|
2284
3119
|
const config = configManager.get();
|
|
2285
3120
|
this.agentCatalog = new AgentCatalog();
|
|
2286
3121
|
this.agentCatalog.load();
|
|
2287
3122
|
this.agentManager = new AgentManager(this.agentCatalog);
|
|
2288
|
-
const storePath =
|
|
3123
|
+
const storePath = path7.join(os2.homedir(), ".openacp", "sessions.json");
|
|
2289
3124
|
this.sessionStore = new JsonFileSessionStore(
|
|
2290
3125
|
storePath,
|
|
2291
3126
|
config.sessionStore.ttlDays
|
|
@@ -2295,15 +3130,17 @@ var OpenACPCore = class {
|
|
|
2295
3130
|
this.notificationManager = new NotificationManager(this.adapters);
|
|
2296
3131
|
const usageConfig = config.usage;
|
|
2297
3132
|
if (usageConfig.enabled) {
|
|
2298
|
-
const usagePath =
|
|
3133
|
+
const usagePath = path7.join(os2.homedir(), ".openacp", "usage.json");
|
|
2299
3134
|
this.usageStore = new UsageStore(usagePath, usageConfig.retentionDays);
|
|
2300
3135
|
this.usageBudget = new UsageBudget(this.usageStore, usageConfig);
|
|
2301
3136
|
}
|
|
2302
3137
|
this.messageTransformer = new MessageTransformer();
|
|
2303
3138
|
this.eventBus = new EventBus();
|
|
2304
3139
|
this.sessionManager.setEventBus(this.eventBus);
|
|
3140
|
+
this.contextManager = new ContextManager();
|
|
3141
|
+
this.contextManager.register(new EntireProvider());
|
|
2305
3142
|
this.fileService = new FileService(
|
|
2306
|
-
|
|
3143
|
+
path7.join(os2.homedir(), ".openacp", "files")
|
|
2307
3144
|
);
|
|
2308
3145
|
const speechConfig = config.speech ?? {
|
|
2309
3146
|
stt: { provider: null, providers: {} },
|
|
@@ -2398,22 +3235,82 @@ var OpenACPCore = class {
|
|
|
2398
3235
|
this.usageStore.destroy();
|
|
2399
3236
|
}
|
|
2400
3237
|
}
|
|
3238
|
+
// --- Summary ---
|
|
3239
|
+
async summarizeSession(sessionId) {
|
|
3240
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
3241
|
+
if (session && session.status === "active") {
|
|
3242
|
+
try {
|
|
3243
|
+
const summary = await session.generateSummary();
|
|
3244
|
+
if (!summary) return { ok: false, error: "Agent could not generate summary" };
|
|
3245
|
+
return { ok: true, summary };
|
|
3246
|
+
} catch (err) {
|
|
3247
|
+
return { ok: false, error: err.message };
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
const record = this.sessionManager.getSessionRecord(sessionId);
|
|
3251
|
+
if (!record?.agentSessionId) {
|
|
3252
|
+
return { ok: false, error: "Session not found or has no agent history" };
|
|
3253
|
+
}
|
|
3254
|
+
const caps = getAgentCapabilities(record.agentName);
|
|
3255
|
+
if (!caps.supportsResume) {
|
|
3256
|
+
return { ok: false, error: `Agent "${record.agentName}" does not support resume \u2014 cannot summarize ended session` };
|
|
3257
|
+
}
|
|
3258
|
+
let tempSession;
|
|
3259
|
+
try {
|
|
3260
|
+
const agentInstance = await this.agentManager.resume(
|
|
3261
|
+
record.agentName,
|
|
3262
|
+
record.workingDir,
|
|
3263
|
+
record.agentSessionId
|
|
3264
|
+
);
|
|
3265
|
+
tempSession = new Session({
|
|
3266
|
+
id: `summary-${sessionId}`,
|
|
3267
|
+
channelId: record.channelId,
|
|
3268
|
+
agentName: record.agentName,
|
|
3269
|
+
workingDirectory: record.workingDir,
|
|
3270
|
+
agentInstance
|
|
3271
|
+
});
|
|
3272
|
+
tempSession.activate();
|
|
3273
|
+
const summary = await tempSession.generateSummary();
|
|
3274
|
+
if (!summary) return { ok: false, error: "Agent could not generate summary" };
|
|
3275
|
+
return { ok: true, summary };
|
|
3276
|
+
} catch (err) {
|
|
3277
|
+
return { ok: false, error: err.message };
|
|
3278
|
+
} finally {
|
|
3279
|
+
if (tempSession) {
|
|
3280
|
+
try {
|
|
3281
|
+
await tempSession.destroy();
|
|
3282
|
+
} catch {
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
2401
3287
|
// --- Archive ---
|
|
2402
3288
|
async archiveSession(sessionId) {
|
|
2403
3289
|
const session = this.sessionManager.getSession(sessionId);
|
|
2404
|
-
|
|
2405
|
-
if (session
|
|
2406
|
-
|
|
2407
|
-
if (
|
|
2408
|
-
|
|
2409
|
-
const adapter = this.adapters.get(session.channelId);
|
|
3290
|
+
const record = this.sessionManager.getSessionRecord(sessionId);
|
|
3291
|
+
if (!session && !record) return { ok: false, error: "Session not found" };
|
|
3292
|
+
const channelId = session?.channelId ?? record?.channelId;
|
|
3293
|
+
if (!channelId) return { ok: false, error: "No channel for session" };
|
|
3294
|
+
const adapter = this.adapters.get(channelId);
|
|
2410
3295
|
if (!adapter) return { ok: false, error: "Adapter not found for session" };
|
|
2411
3296
|
try {
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
3297
|
+
if (session) {
|
|
3298
|
+
await adapter.archiveSessionTopic(session.id);
|
|
3299
|
+
} else {
|
|
3300
|
+
await adapter.deleteSessionThread(sessionId);
|
|
3301
|
+
}
|
|
3302
|
+
if (session) {
|
|
3303
|
+
try {
|
|
3304
|
+
await this.sessionManager.cancelSession(sessionId);
|
|
3305
|
+
} catch {
|
|
3306
|
+
} finally {
|
|
3307
|
+
session.archiving = false;
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
await this.sessionManager.removeRecord(sessionId);
|
|
3311
|
+
return { ok: true };
|
|
2416
3312
|
} catch (err) {
|
|
3313
|
+
if (session) session.archiving = false;
|
|
2417
3314
|
return { ok: false, error: err.message };
|
|
2418
3315
|
}
|
|
2419
3316
|
}
|
|
@@ -2654,6 +3551,27 @@ var OpenACPCore = class {
|
|
|
2654
3551
|
record.workingDir
|
|
2655
3552
|
);
|
|
2656
3553
|
}
|
|
3554
|
+
async createSessionWithContext(params) {
|
|
3555
|
+
let contextResult = null;
|
|
3556
|
+
try {
|
|
3557
|
+
contextResult = await this.contextManager.buildContext(
|
|
3558
|
+
params.contextQuery,
|
|
3559
|
+
params.contextOptions
|
|
3560
|
+
);
|
|
3561
|
+
} catch (err) {
|
|
3562
|
+
log7.warn({ err }, "Context building failed, proceeding without context");
|
|
3563
|
+
}
|
|
3564
|
+
const session = await this.createSession({
|
|
3565
|
+
channelId: params.channelId,
|
|
3566
|
+
agentName: params.agentName,
|
|
3567
|
+
workingDirectory: params.workingDirectory,
|
|
3568
|
+
createThread: params.createThread
|
|
3569
|
+
});
|
|
3570
|
+
if (contextResult) {
|
|
3571
|
+
session.setContext(contextResult.markdown);
|
|
3572
|
+
}
|
|
3573
|
+
return { session, contextResult };
|
|
3574
|
+
}
|
|
2657
3575
|
// --- Lazy Resume ---
|
|
2658
3576
|
/**
|
|
2659
3577
|
* Get active session by thread, or attempt lazy resume from store.
|
|
@@ -2849,8 +3767,8 @@ data: ${JSON.stringify(data)}
|
|
|
2849
3767
|
};
|
|
2850
3768
|
|
|
2851
3769
|
// src/core/static-server.ts
|
|
2852
|
-
import * as
|
|
2853
|
-
import * as
|
|
3770
|
+
import * as fs7 from "fs";
|
|
3771
|
+
import * as path8 from "path";
|
|
2854
3772
|
import { fileURLToPath } from "url";
|
|
2855
3773
|
var MIME_TYPES = {
|
|
2856
3774
|
".html": "text/html; charset=utf-8",
|
|
@@ -2870,16 +3788,16 @@ var StaticServer = class {
|
|
|
2870
3788
|
this.uiDir = uiDir;
|
|
2871
3789
|
if (!this.uiDir) {
|
|
2872
3790
|
const __filename = fileURLToPath(import.meta.url);
|
|
2873
|
-
const candidate =
|
|
2874
|
-
if (
|
|
3791
|
+
const candidate = path8.resolve(path8.dirname(__filename), "../../ui/dist");
|
|
3792
|
+
if (fs7.existsSync(path8.join(candidate, "index.html"))) {
|
|
2875
3793
|
this.uiDir = candidate;
|
|
2876
3794
|
}
|
|
2877
3795
|
if (!this.uiDir) {
|
|
2878
|
-
const publishCandidate =
|
|
2879
|
-
|
|
3796
|
+
const publishCandidate = path8.resolve(
|
|
3797
|
+
path8.dirname(__filename),
|
|
2880
3798
|
"../ui"
|
|
2881
3799
|
);
|
|
2882
|
-
if (
|
|
3800
|
+
if (fs7.existsSync(path8.join(publishCandidate, "index.html"))) {
|
|
2883
3801
|
this.uiDir = publishCandidate;
|
|
2884
3802
|
}
|
|
2885
3803
|
}
|
|
@@ -2891,12 +3809,12 @@ var StaticServer = class {
|
|
|
2891
3809
|
serve(req, res) {
|
|
2892
3810
|
if (!this.uiDir) return false;
|
|
2893
3811
|
const urlPath = (req.url || "/").split("?")[0];
|
|
2894
|
-
const safePath =
|
|
2895
|
-
const filePath =
|
|
2896
|
-
if (!filePath.startsWith(this.uiDir +
|
|
3812
|
+
const safePath = path8.normalize(urlPath);
|
|
3813
|
+
const filePath = path8.join(this.uiDir, safePath);
|
|
3814
|
+
if (!filePath.startsWith(this.uiDir + path8.sep) && filePath !== this.uiDir)
|
|
2897
3815
|
return false;
|
|
2898
|
-
if (
|
|
2899
|
-
const ext =
|
|
3816
|
+
if (fs7.existsSync(filePath) && fs7.statSync(filePath).isFile()) {
|
|
3817
|
+
const ext = path8.extname(filePath);
|
|
2900
3818
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
2901
3819
|
const isHashed = /\.[a-zA-Z0-9]{8,}\.(js|css)$/.test(filePath);
|
|
2902
3820
|
const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "no-cache";
|
|
@@ -2904,16 +3822,16 @@ var StaticServer = class {
|
|
|
2904
3822
|
"Content-Type": contentType,
|
|
2905
3823
|
"Cache-Control": cacheControl
|
|
2906
3824
|
});
|
|
2907
|
-
|
|
3825
|
+
fs7.createReadStream(filePath).pipe(res);
|
|
2908
3826
|
return true;
|
|
2909
3827
|
}
|
|
2910
|
-
const indexPath =
|
|
2911
|
-
if (
|
|
3828
|
+
const indexPath = path8.join(this.uiDir, "index.html");
|
|
3829
|
+
if (fs7.existsSync(indexPath)) {
|
|
2912
3830
|
res.writeHead(200, {
|
|
2913
3831
|
"Content-Type": "text/html; charset=utf-8",
|
|
2914
3832
|
"Cache-Control": "no-cache"
|
|
2915
3833
|
});
|
|
2916
|
-
|
|
3834
|
+
fs7.createReadStream(indexPath).pipe(res);
|
|
2917
3835
|
return true;
|
|
2918
3836
|
}
|
|
2919
3837
|
return false;
|
|
@@ -2922,29 +3840,29 @@ var StaticServer = class {
|
|
|
2922
3840
|
|
|
2923
3841
|
// src/core/api/index.ts
|
|
2924
3842
|
import * as http from "http";
|
|
2925
|
-
import * as
|
|
2926
|
-
import * as
|
|
2927
|
-
import * as
|
|
2928
|
-
import * as
|
|
3843
|
+
import * as fs8 from "fs";
|
|
3844
|
+
import * as path9 from "path";
|
|
3845
|
+
import * as os3 from "os";
|
|
3846
|
+
import * as crypto2 from "crypto";
|
|
2929
3847
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2930
3848
|
|
|
2931
3849
|
// src/core/api/router.ts
|
|
2932
3850
|
var Router = class {
|
|
2933
3851
|
routes = [];
|
|
2934
|
-
get(
|
|
2935
|
-
this.add("GET",
|
|
3852
|
+
get(path10, handler) {
|
|
3853
|
+
this.add("GET", path10, handler);
|
|
2936
3854
|
}
|
|
2937
|
-
post(
|
|
2938
|
-
this.add("POST",
|
|
3855
|
+
post(path10, handler) {
|
|
3856
|
+
this.add("POST", path10, handler);
|
|
2939
3857
|
}
|
|
2940
|
-
put(
|
|
2941
|
-
this.add("PUT",
|
|
3858
|
+
put(path10, handler) {
|
|
3859
|
+
this.add("PUT", path10, handler);
|
|
2942
3860
|
}
|
|
2943
|
-
patch(
|
|
2944
|
-
this.add("PATCH",
|
|
3861
|
+
patch(path10, handler) {
|
|
3862
|
+
this.add("PATCH", path10, handler);
|
|
2945
3863
|
}
|
|
2946
|
-
delete(
|
|
2947
|
-
this.add("DELETE",
|
|
3864
|
+
delete(path10, handler) {
|
|
3865
|
+
this.add("DELETE", path10, handler);
|
|
2948
3866
|
}
|
|
2949
3867
|
match(method, url) {
|
|
2950
3868
|
const pathname = url.split("?")[0];
|
|
@@ -2960,9 +3878,9 @@ var Router = class {
|
|
|
2960
3878
|
}
|
|
2961
3879
|
return null;
|
|
2962
3880
|
}
|
|
2963
|
-
add(method,
|
|
3881
|
+
add(method, path10, handler) {
|
|
2964
3882
|
const keys = [];
|
|
2965
|
-
const pattern =
|
|
3883
|
+
const pattern = path10.replace(/:(\w+)/g, (_, key) => {
|
|
2966
3884
|
keys.push(key);
|
|
2967
3885
|
return "([^/]+)";
|
|
2968
3886
|
});
|
|
@@ -3253,6 +4171,15 @@ function registerSessionRoutes(router, deps) {
|
|
|
3253
4171
|
}
|
|
3254
4172
|
});
|
|
3255
4173
|
});
|
|
4174
|
+
router.post("/api/sessions/:sessionId/summary", async (_req, res, params) => {
|
|
4175
|
+
const sessionId = decodeURIComponent(params.sessionId);
|
|
4176
|
+
const result = await deps.core.summarizeSession(sessionId);
|
|
4177
|
+
if (result.ok) {
|
|
4178
|
+
deps.sendJson(res, 200, result);
|
|
4179
|
+
} else {
|
|
4180
|
+
deps.sendJson(res, 400, result);
|
|
4181
|
+
}
|
|
4182
|
+
});
|
|
3256
4183
|
router.post("/api/sessions/:sessionId/archive", async (_req, res, params) => {
|
|
3257
4184
|
const sessionId = decodeURIComponent(params.sessionId);
|
|
3258
4185
|
const result = await deps.core.archiveSession(sessionId);
|
|
@@ -3321,7 +4248,7 @@ function redactDeep(obj) {
|
|
|
3321
4248
|
}
|
|
3322
4249
|
function registerConfigRoutes(router, deps) {
|
|
3323
4250
|
router.get("/api/config/editable", async (_req, res) => {
|
|
3324
|
-
const { getSafeFields: getSafeFields2, resolveOptions: resolveOptions2, getConfigValue: getConfigValue2 } = await import("./config-registry-
|
|
4251
|
+
const { getSafeFields: getSafeFields2, resolveOptions: resolveOptions2, getConfigValue: getConfigValue2 } = await import("./config-registry-HDXFES2D.js");
|
|
3325
4252
|
const config = deps.core.configManager.get();
|
|
3326
4253
|
const safeFields = getSafeFields2();
|
|
3327
4254
|
const fields = safeFields.map((def) => ({
|
|
@@ -3363,7 +4290,7 @@ function registerConfigRoutes(router, deps) {
|
|
|
3363
4290
|
deps.sendJson(res, 400, { error: "Invalid config path" });
|
|
3364
4291
|
return;
|
|
3365
4292
|
}
|
|
3366
|
-
const { getFieldDef: getFieldDef2 } = await import("./config-registry-
|
|
4293
|
+
const { getFieldDef: getFieldDef2 } = await import("./config-registry-HDXFES2D.js");
|
|
3367
4294
|
const fieldDef = getFieldDef2(configPath);
|
|
3368
4295
|
if (!fieldDef || fieldDef.scope !== "safe") {
|
|
3369
4296
|
deps.sendJson(res, 403, {
|
|
@@ -3388,7 +4315,7 @@ function registerConfigRoutes(router, deps) {
|
|
|
3388
4315
|
}
|
|
3389
4316
|
const lastKey = parts[parts.length - 1];
|
|
3390
4317
|
target[lastKey] = value;
|
|
3391
|
-
const { ConfigSchema } = await import("./config-
|
|
4318
|
+
const { ConfigSchema } = await import("./config-CQAS6YHR.js");
|
|
3392
4319
|
const result = ConfigSchema.safeParse(cloned);
|
|
3393
4320
|
if (!result.success) {
|
|
3394
4321
|
deps.sendJson(res, 400, {
|
|
@@ -3408,7 +4335,7 @@ function registerConfigRoutes(router, deps) {
|
|
|
3408
4335
|
}
|
|
3409
4336
|
updateTarget[lastKey] = value;
|
|
3410
4337
|
await deps.core.configManager.save(updates, configPath);
|
|
3411
|
-
const { isHotReloadable: isHotReloadable2 } = await import("./config-registry-
|
|
4338
|
+
const { isHotReloadable: isHotReloadable2 } = await import("./config-registry-HDXFES2D.js");
|
|
3412
4339
|
const needsRestart = !isHotReloadable2(configPath);
|
|
3413
4340
|
deps.sendJson(res, 200, {
|
|
3414
4341
|
ok: true,
|
|
@@ -3596,17 +4523,17 @@ function registerNotifyRoutes(router, deps) {
|
|
|
3596
4523
|
|
|
3597
4524
|
// src/core/api/index.ts
|
|
3598
4525
|
var log9 = createChildLogger({ module: "api-server" });
|
|
3599
|
-
var DEFAULT_PORT_FILE =
|
|
4526
|
+
var DEFAULT_PORT_FILE = path9.join(os3.homedir(), ".openacp", "api.port");
|
|
3600
4527
|
var cachedVersion;
|
|
3601
4528
|
function getVersion() {
|
|
3602
4529
|
if (cachedVersion) return cachedVersion;
|
|
3603
4530
|
try {
|
|
3604
4531
|
const __filename = fileURLToPath2(import.meta.url);
|
|
3605
|
-
const pkgPath =
|
|
3606
|
-
|
|
4532
|
+
const pkgPath = path9.resolve(
|
|
4533
|
+
path9.dirname(__filename),
|
|
3607
4534
|
"../../../package.json"
|
|
3608
4535
|
);
|
|
3609
|
-
const pkg = JSON.parse(
|
|
4536
|
+
const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
|
|
3610
4537
|
cachedVersion = pkg.version ?? "0.0.0-dev";
|
|
3611
4538
|
} catch {
|
|
3612
4539
|
cachedVersion = "0.0.0-dev";
|
|
@@ -3619,7 +4546,7 @@ var ApiServer = class {
|
|
|
3619
4546
|
this.config = config;
|
|
3620
4547
|
this.topicManager = topicManager;
|
|
3621
4548
|
this.portFilePath = portFilePath ?? DEFAULT_PORT_FILE;
|
|
3622
|
-
this.secretFilePath = secretFilePath ??
|
|
4549
|
+
this.secretFilePath = secretFilePath ?? path9.join(os3.homedir(), ".openacp", "api-secret");
|
|
3623
4550
|
this.staticServer = new StaticServer(uiDir);
|
|
3624
4551
|
this.sseManager = new SSEManager(
|
|
3625
4552
|
core.eventBus,
|
|
@@ -3713,24 +4640,24 @@ var ApiServer = class {
|
|
|
3713
4640
|
return this.secret;
|
|
3714
4641
|
}
|
|
3715
4642
|
writePortFile() {
|
|
3716
|
-
const dir =
|
|
3717
|
-
|
|
3718
|
-
|
|
4643
|
+
const dir = path9.dirname(this.portFilePath);
|
|
4644
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
4645
|
+
fs8.writeFileSync(this.portFilePath, String(this.actualPort));
|
|
3719
4646
|
}
|
|
3720
4647
|
removePortFile() {
|
|
3721
4648
|
try {
|
|
3722
|
-
|
|
4649
|
+
fs8.unlinkSync(this.portFilePath);
|
|
3723
4650
|
} catch {
|
|
3724
4651
|
}
|
|
3725
4652
|
}
|
|
3726
4653
|
loadOrCreateSecret() {
|
|
3727
|
-
const dir =
|
|
3728
|
-
|
|
4654
|
+
const dir = path9.dirname(this.secretFilePath);
|
|
4655
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
3729
4656
|
try {
|
|
3730
|
-
this.secret =
|
|
4657
|
+
this.secret = fs8.readFileSync(this.secretFilePath, "utf-8").trim();
|
|
3731
4658
|
if (this.secret) {
|
|
3732
4659
|
try {
|
|
3733
|
-
const stat =
|
|
4660
|
+
const stat = fs8.statSync(this.secretFilePath);
|
|
3734
4661
|
const mode = stat.mode & 511;
|
|
3735
4662
|
if (mode & 63) {
|
|
3736
4663
|
log9.warn(
|
|
@@ -3745,14 +4672,14 @@ var ApiServer = class {
|
|
|
3745
4672
|
}
|
|
3746
4673
|
} catch {
|
|
3747
4674
|
}
|
|
3748
|
-
this.secret =
|
|
3749
|
-
|
|
4675
|
+
this.secret = crypto2.randomBytes(32).toString("hex");
|
|
4676
|
+
fs8.writeFileSync(this.secretFilePath, this.secret, { mode: 384 });
|
|
3750
4677
|
}
|
|
3751
4678
|
authenticate(req, allowQueryParam = false) {
|
|
3752
4679
|
const authHeader = req.headers.authorization;
|
|
3753
4680
|
if (authHeader?.startsWith("Bearer ")) {
|
|
3754
4681
|
const token = authHeader.slice(7);
|
|
3755
|
-
if (token.length === this.secret.length &&
|
|
4682
|
+
if (token.length === this.secret.length && crypto2.timingSafeEqual(
|
|
3756
4683
|
Buffer.from(token, "utf-8"),
|
|
3757
4684
|
Buffer.from(this.secret, "utf-8")
|
|
3758
4685
|
)) {
|
|
@@ -3762,7 +4689,7 @@ var ApiServer = class {
|
|
|
3762
4689
|
if (allowQueryParam) {
|
|
3763
4690
|
const parsedUrl = new URL(req.url || "", "http://localhost");
|
|
3764
4691
|
const qToken = parsedUrl.searchParams.get("token");
|
|
3765
|
-
if (qToken && qToken.length === this.secret.length &&
|
|
4692
|
+
if (qToken && qToken.length === this.secret.length && crypto2.timingSafeEqual(
|
|
3766
4693
|
Buffer.from(qToken, "utf-8"),
|
|
3767
4694
|
Buffer.from(this.secret, "utf-8")
|
|
3768
4695
|
)) {
|
|
@@ -3940,10 +4867,14 @@ export {
|
|
|
3940
4867
|
EventBus,
|
|
3941
4868
|
SpeechService,
|
|
3942
4869
|
GroqSTT,
|
|
4870
|
+
ContextManager,
|
|
4871
|
+
DEFAULT_MAX_TOKENS,
|
|
4872
|
+
CheckpointReader,
|
|
4873
|
+
EntireProvider,
|
|
3943
4874
|
OpenACPCore,
|
|
3944
4875
|
SSEManager,
|
|
3945
4876
|
StaticServer,
|
|
3946
4877
|
ApiServer,
|
|
3947
4878
|
TopicManager
|
|
3948
4879
|
};
|
|
3949
|
-
//# sourceMappingURL=chunk-
|
|
4880
|
+
//# sourceMappingURL=chunk-LO4Y5WQ7.js.map
|