@tekmidian/pai 0.35.2 → 0.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{auto-route-DVM3U2ZY.mjs → auto-route-Byf8ENXj.mjs} +4 -4
- package/dist/{auto-route-DVM3U2ZY.mjs.map → auto-route-Byf8ENXj.mjs.map} +1 -1
- package/dist/cli/index.mjs +25 -17
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/probe2.mjs +2 -0
- package/dist/cli/program.d.mts.map +1 -1
- package/dist/cli/program.mjs +16 -267
- package/dist/{clusters-CzGxefB7.mjs → clusters-wZgTCYCB.mjs} +2 -2
- package/dist/{clusters-CzGxefB7.mjs.map → clusters-wZgTCYCB.mjs.map} +1 -1
- package/dist/{config-BSkVcvfq.mjs → config-B64vFg14.mjs} +3 -12
- package/dist/{config-BSkVcvfq.mjs.map → config-B64vFg14.mjs.map} +1 -1
- package/dist/config-C_ErGddD.mjs +3 -0
- package/dist/{checkpoint-block-D3rm4dAJ.mjs → context-handover-cache-PtNvj_8D.mjs} +40 -8
- package/dist/context-handover-cache-PtNvj_8D.mjs.map +1 -0
- package/dist/daemon/index.mjs +19 -19
- package/dist/{daemon-B8L2f5vy.mjs → daemon-BZ93KRBo.mjs} +33 -38
- package/dist/daemon-BZ93KRBo.mjs.map +1 -0
- package/dist/daemon-DRdoA489.mjs +20 -0
- package/dist/daemon-mcp/index.mjs +2 -2
- package/dist/{db-BtuN768f.mjs → db-Ca5qfsMC.mjs} +2 -4
- package/dist/{db-BtuN768f.mjs.map → db-Ca5qfsMC.mjs.map} +1 -1
- package/dist/db-O-cyAPfS.mjs +3 -0
- package/dist/db-XEwJbuGO.mjs +3 -0
- package/dist/{db-CYmBWcjh.mjs → db-a1ixZQjr.mjs} +2 -4
- package/dist/{db-CYmBWcjh.mjs.map → db-a1ixZQjr.mjs.map} +1 -1
- package/dist/{detect-Bf2z-oKB.mjs → detect-CdaA48EI.mjs} +1 -1
- package/dist/{detect-Bf2z-oKB.mjs.map → detect-CdaA48EI.mjs.map} +1 -1
- package/dist/{detector-BU-bsDXs.mjs → detector--Gg5JRN5.mjs} +3 -5
- package/dist/{detector-BU-bsDXs.mjs.map → detector--Gg5JRN5.mjs.map} +1 -1
- package/dist/detector-DGAk1iBR.mjs +5 -0
- package/dist/embeddings-CEBGrzwu.mjs +3 -0
- package/dist/{embeddings-Bn86ssxR.mjs → embeddings-DOLZnT1X.mjs} +2 -12
- package/dist/{embeddings-Bn86ssxR.mjs.map → embeddings-DOLZnT1X.mjs.map} +1 -1
- package/dist/{factory-vPTPoBR7.mjs → factory-Bsp7xOpO.mjs} +9 -12
- package/dist/{factory-vPTPoBR7.mjs.map → factory-Bsp7xOpO.mjs.map} +1 -1
- package/dist/factory-CrokPMk2.mjs +3 -0
- package/dist/{helpers-crDEr6S2.mjs → helpers-IjZkXBhj.mjs} +1 -1
- package/dist/{helpers-crDEr6S2.mjs.map → helpers-IjZkXBhj.mjs.map} +1 -1
- package/dist/hooks/context-compression-hook.mjs +217 -70
- package/dist/hooks/context-compression-hook.mjs.map +4 -4
- package/dist/index.mjs +10 -10
- package/dist/{indexer-backend-Bg7VDpGt.mjs → indexer-backend-nQZuEx6N.mjs} +3 -3
- package/dist/{indexer-backend-Bg7VDpGt.mjs.map → indexer-backend-nQZuEx6N.mjs.map} +1 -1
- package/dist/{ipc-client-aVKVERjJ.mjs → ipc-client-BmypMNYk.mjs} +13 -7
- package/dist/ipc-client-BmypMNYk.mjs.map +1 -0
- package/dist/{kg-entity-r8duqhi9.mjs → kg-entity-DbOMPdF9.mjs} +1 -1
- package/dist/{kg-entity-r8duqhi9.mjs.map → kg-entity-DbOMPdF9.mjs.map} +1 -1
- package/dist/{latent-ideas-BL9m2HF9.mjs → latent-ideas-Bn6A5-5P.mjs} +4 -4
- package/dist/{latent-ideas-BL9m2HF9.mjs.map → latent-ideas-Bn6A5-5P.mjs.map} +1 -1
- package/dist/{link-boost-QFLrJwD6.mjs → link-boost-fYjUnxCN.mjs} +1 -1
- package/dist/{link-boost-QFLrJwD6.mjs.map → link-boost-fYjUnxCN.mjs.map} +1 -1
- package/dist/{main-resolver-CNSqU8wo.mjs → main-resolver-BAbhKpeX.mjs} +12 -14
- package/dist/main-resolver-BAbhKpeX.mjs.map +1 -0
- package/dist/main-resolver-Dxh444GO.mjs +4 -0
- package/dist/{migrate-fLD6rAdO.mjs → migrate-Cjzeefn9.mjs} +2 -2
- package/dist/{migrate-fLD6rAdO.mjs.map → migrate-Cjzeefn9.mjs.map} +1 -1
- package/dist/{neighborhood-BX89_nty.mjs → neighborhood-DpaEM991.mjs} +2 -2
- package/dist/{neighborhood-BX89_nty.mjs.map → neighborhood-DpaEM991.mjs.map} +1 -1
- package/dist/{note-context-d1wT_-GA.mjs → note-context-DrcY4cWm.mjs} +1 -1
- package/dist/{note-context-d1wT_-GA.mjs.map → note-context-DrcY4cWm.mjs.map} +1 -1
- package/dist/{pai-marker-B20KqhA8.mjs → pai-marker-CHtbJMwJ.mjs} +1 -1
- package/dist/{pai-marker-B20KqhA8.mjs.map → pai-marker-CHtbJMwJ.mjs.map} +1 -1
- package/dist/{postgres-CI3FjGq0.mjs → postgres-BVme6qX0.mjs} +2 -2
- package/dist/{postgres-CI3FjGq0.mjs.map → postgres-BVme6qX0.mjs.map} +1 -1
- package/dist/{pick-DatgS3Nk.mjs → program-C-fUghPv.mjs} +1298 -223
- package/dist/program-C-fUghPv.mjs.map +1 -0
- package/dist/query-feedback-BBMBp96K.mjs +3 -0
- package/dist/{query-feedback-D4U56Hz6.mjs → query-feedback-C1T6kS18.mjs} +2 -4
- package/dist/{query-feedback-D4U56Hz6.mjs.map → query-feedback-C1T6kS18.mjs.map} +1 -1
- package/dist/reranker-CwTCNsgA.mjs +3 -0
- package/dist/{reranker-CMNZcfVx.mjs → reranker-xPm04PXx.mjs} +2 -8
- package/dist/{reranker-CMNZcfVx.mjs.map → reranker-xPm04PXx.mjs.map} +1 -1
- package/dist/router-BMkOb62X.mjs +3 -0
- package/dist/{router-i9S19Usg.mjs → router-CsDm7HvK.mjs} +2 -4
- package/dist/{router-i9S19Usg.mjs.map → router-CsDm7HvK.mjs.map} +1 -1
- package/dist/{runtime-paths-B0P1TvUr.mjs → runtime-paths-rni52zHX.mjs} +1 -1
- package/dist/{runtime-paths-B0P1TvUr.mjs.map → runtime-paths-rni52zHX.mjs.map} +1 -1
- package/dist/search-CfPpJAWQ.mjs +4 -0
- package/dist/{search-C32zQ0V0.mjs → search-Rpk1cSBC.mjs} +4 -15
- package/dist/{search-C32zQ0V0.mjs.map → search-Rpk1cSBC.mjs.map} +1 -1
- package/dist/{sources-BDwN0B8i.mjs → sources-D8ZdNfvK.mjs} +2 -2
- package/dist/{sources-BDwN0B8i.mjs.map → sources-D8ZdNfvK.mjs.map} +1 -1
- package/dist/{sqlite-C6FHnMkn.mjs → sqlite-D1IaR8Am.mjs} +3 -3
- package/dist/{sqlite-C6FHnMkn.mjs.map → sqlite-D1IaR8Am.mjs.map} +1 -1
- package/dist/state-WaXhLr6R.mjs +70 -0
- package/dist/{state-DTvy-jRB.mjs.map → state-WaXhLr6R.mjs.map} +1 -1
- package/dist/state-qtmrBWCm.mjs +3 -0
- package/dist/{stop-words-BaMEGVeY.mjs → stop-words-Hfu8u22w.mjs} +1 -1
- package/dist/{stop-words-BaMEGVeY.mjs.map → stop-words-Hfu8u22w.mjs.map} +1 -1
- package/dist/{sync--BoxBBok.mjs → sync-BWbe8JTg.mjs} +3 -3
- package/dist/{sync--BoxBBok.mjs.map → sync-BWbe8JTg.mjs.map} +1 -1
- package/dist/{themes-BObEGMWn.mjs → themes-XPkj_bfP.mjs} +3 -3
- package/dist/{themes-BObEGMWn.mjs.map → themes-XPkj_bfP.mjs.map} +1 -1
- package/dist/tools-DEt6YPfc.mjs +5 -0
- package/dist/{tools-C1lCHerL.mjs → tools-ceiy7ANX.mjs} +28 -65
- package/dist/tools-ceiy7ANX.mjs.map +1 -0
- package/dist/{trace-h23JCcFD.mjs → trace-DfyGmMG_.mjs} +1 -1
- package/dist/{trace-h23JCcFD.mjs.map → trace-DfyGmMG_.mjs.map} +1 -1
- package/dist/{utils-BAxjW3j8.mjs → utils-9Err2RBW.mjs} +2 -22
- package/dist/{utils-BAxjW3j8.mjs.map → utils-9Err2RBW.mjs.map} +1 -1
- package/dist/utils-DhMex3Ox.mjs +3 -0
- package/dist/{vault-indexer-CUF9edbW.mjs → vault-indexer-CFvlPUMB.mjs} +2 -2
- package/dist/{vault-indexer-CUF9edbW.mjs.map → vault-indexer-CFvlPUMB.mjs.map} +1 -1
- package/dist/{work-queue-worker-BcDGAcF3.mjs → work-queue-worker-B8W8_3Rn.mjs} +201 -13
- package/dist/work-queue-worker-B8W8_3Rn.mjs.map +1 -0
- package/dist/work-queue-worker-HN2Ufg-L.mjs +11 -0
- package/dist/{zettelkasten-W-h8G2is.mjs → zettelkasten-CvjmMghT.mjs} +4 -4
- package/dist/{zettelkasten-W-h8G2is.mjs.map → zettelkasten-CvjmMghT.mjs.map} +1 -1
- package/package.json +1 -1
- package/src/hooks/ts/lib/context-fill.test.ts +628 -0
- package/src/hooks/ts/lib/context-fill.ts +668 -0
- package/src/hooks/ts/lib/context-handover-cache.ts +46 -0
- package/src/hooks/ts/lib/transcript-text.test.ts +125 -0
- package/src/hooks/ts/lib/transcript-text.ts +71 -0
- package/src/hooks/ts/pre-compact/context-compression-hook.ts +131 -30
- package/statusline-command.sh +16 -0
- package/dist/checkpoint-block-D3rm4dAJ.mjs.map +0 -1
- package/dist/daemon-B8L2f5vy.mjs.map +0 -1
- package/dist/ipc-client-aVKVERjJ.mjs.map +0 -1
- package/dist/main-resolver-CNSqU8wo.mjs.map +0 -1
- package/dist/pick-DatgS3Nk.mjs.map +0 -1
- package/dist/rolldown-runtime-95iHPtFO.mjs +0 -18
- package/dist/state-DTvy-jRB.mjs +0 -102
- package/dist/tools-C1lCHerL.mjs.map +0 -1
- package/dist/work-queue-worker-BcDGAcF3.mjs.map +0 -1
- /package/dist/{indexer-AEcT8wHf.mjs → indexer-D7MvSQPY.mjs} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/hooks/ts/pre-compact/context-compression-hook.ts
|
|
4
|
-
import { existsSync as
|
|
5
|
-
import { basename as basename4, dirname, join as
|
|
6
|
-
import { tmpdir } from "os";
|
|
4
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
5
|
+
import { basename as basename4, dirname, join as join9 } from "path";
|
|
6
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
7
7
|
import { connect } from "net";
|
|
8
8
|
import { randomUUID } from "crypto";
|
|
9
9
|
|
|
@@ -391,42 +391,14 @@ function renameSessionNote(notePath, meaningfulName) {
|
|
|
391
391
|
}
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
-
// src/hooks/ts/lib/project-utils/tokens.ts
|
|
395
|
-
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
396
|
-
function calculateSessionTokens(jsonlPath) {
|
|
397
|
-
if (!existsSync5(jsonlPath)) return 0;
|
|
398
|
-
try {
|
|
399
|
-
const content = readFileSync4(jsonlPath, "utf-8");
|
|
400
|
-
const lines = content.trim().split("\n");
|
|
401
|
-
let totalTokens = 0;
|
|
402
|
-
for (const line of lines) {
|
|
403
|
-
try {
|
|
404
|
-
const entry = JSON.parse(line);
|
|
405
|
-
if (entry.message?.usage) {
|
|
406
|
-
const usage = entry.message.usage;
|
|
407
|
-
totalTokens += usage.input_tokens || 0;
|
|
408
|
-
totalTokens += usage.output_tokens || 0;
|
|
409
|
-
totalTokens += usage.cache_creation_input_tokens || 0;
|
|
410
|
-
totalTokens += usage.cache_read_input_tokens || 0;
|
|
411
|
-
}
|
|
412
|
-
} catch {
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
return totalTokens;
|
|
416
|
-
} catch (error) {
|
|
417
|
-
console.error(`Error calculating tokens: ${error}`);
|
|
418
|
-
return 0;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
394
|
// src/hooks/ts/lib/project-utils/todo.ts
|
|
423
|
-
import { existsSync as
|
|
395
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
424
396
|
import { join as join6, basename as basename3 } from "path";
|
|
425
397
|
|
|
426
398
|
// src/session/checkpoint-block.ts
|
|
427
399
|
import {
|
|
428
|
-
existsSync as
|
|
429
|
-
readFileSync as
|
|
400
|
+
existsSync as existsSync5,
|
|
401
|
+
readFileSync as readFileSync4,
|
|
430
402
|
writeFileSync as writeFileSync2,
|
|
431
403
|
readdirSync as readdirSync3,
|
|
432
404
|
renameSync as renameSync2,
|
|
@@ -450,9 +422,9 @@ var CONTINUE_HEADING = "## Continue";
|
|
|
450
422
|
function findProjectTodo(rootPath) {
|
|
451
423
|
for (const rel of TODO_LOCATIONS) {
|
|
452
424
|
const full = join5(rootPath, rel);
|
|
453
|
-
if (
|
|
425
|
+
if (existsSync5(full)) {
|
|
454
426
|
try {
|
|
455
|
-
return { path: full, content:
|
|
427
|
+
return { path: full, content: readFileSync4(full, "utf8") };
|
|
456
428
|
} catch {
|
|
457
429
|
}
|
|
458
430
|
}
|
|
@@ -465,7 +437,7 @@ function resolveTodoTarget(rootPath, opts = {}) {
|
|
|
465
437
|
const notesDir = join5(rootPath, "Notes");
|
|
466
438
|
if (opts.create !== false) {
|
|
467
439
|
try {
|
|
468
|
-
if (!
|
|
440
|
+
if (!existsSync5(notesDir)) mkdirSync3(notesDir, { recursive: true });
|
|
469
441
|
} catch {
|
|
470
442
|
return null;
|
|
471
443
|
}
|
|
@@ -762,7 +734,7 @@ function applyContinue(opts) {
|
|
|
762
734
|
renameSync2(tmpPath, target.path);
|
|
763
735
|
} catch (err) {
|
|
764
736
|
try {
|
|
765
|
-
if (
|
|
737
|
+
if (existsSync5(tmpPath)) renameSync2(tmpPath, `${tmpPath}.dead`);
|
|
766
738
|
} catch {
|
|
767
739
|
}
|
|
768
740
|
return {
|
|
@@ -778,9 +750,9 @@ function applyContinue(opts) {
|
|
|
778
750
|
// src/hooks/ts/lib/project-utils/todo.ts
|
|
779
751
|
function ensureTodoMd(cwd) {
|
|
780
752
|
const todoPath = findTodoPath(cwd);
|
|
781
|
-
if (!
|
|
753
|
+
if (!existsSync6(todoPath)) {
|
|
782
754
|
const parentDir = join6(todoPath, "..");
|
|
783
|
-
if (!
|
|
755
|
+
if (!existsSync6(parentDir)) mkdirSync4(parentDir, { recursive: true });
|
|
784
756
|
const content = `# TODO
|
|
785
757
|
|
|
786
758
|
## Current Session
|
|
@@ -830,7 +802,7 @@ function updateTodoContinue(cwd, noteFilename, state, tokenDisplay, sessionId) {
|
|
|
830
802
|
try {
|
|
831
803
|
const todoPath = result.path;
|
|
832
804
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
833
|
-
let content =
|
|
805
|
+
let content = readFileSync5(todoPath, "utf-8");
|
|
834
806
|
content = content.replace(/(\n---\s*)*(\n\*Last updated:.*\*\s*)+$/g, "");
|
|
835
807
|
content = content.trimEnd() + `
|
|
836
808
|
|
|
@@ -846,24 +818,154 @@ function updateTodoContinue(cwd, noteFilename, state, tokenDisplay, sessionId) {
|
|
|
846
818
|
);
|
|
847
819
|
}
|
|
848
820
|
|
|
849
|
-
// src/hooks/ts/
|
|
850
|
-
|
|
851
|
-
|
|
821
|
+
// src/hooks/ts/lib/context-fill.ts
|
|
822
|
+
import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync6 } from "node:fs";
|
|
823
|
+
import { homedir as homedir3, tmpdir } from "node:os";
|
|
824
|
+
import { join as join7 } from "node:path";
|
|
825
|
+
var DEFAULT_CONTEXT_WINDOW = 2e5;
|
|
826
|
+
var TRANSCRIPT_TAIL_LINES = 40;
|
|
827
|
+
var STATUSLINE_STALE_MS = 5 * 60 * 1e3;
|
|
828
|
+
function unknownReading(windowSize) {
|
|
829
|
+
return { status: "unknown", usedTokens: null, windowSize, fraction: null, source: "unknown" };
|
|
830
|
+
}
|
|
831
|
+
function statuslineStateFilePath(sessionId) {
|
|
832
|
+
return join7(tmpdir(), `pai-context-${sessionId}.json`);
|
|
833
|
+
}
|
|
834
|
+
function readStatuslineFill(sessionId, now = Date.now()) {
|
|
835
|
+
if (!sessionId) return null;
|
|
836
|
+
const path = statuslineStateFilePath(sessionId);
|
|
837
|
+
if (!existsSync7(path)) return null;
|
|
838
|
+
let raw;
|
|
839
|
+
try {
|
|
840
|
+
raw = JSON.parse(readFileSync6(path, "utf-8"));
|
|
841
|
+
} catch {
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
if (typeof raw.timestamp !== "number" || now - raw.timestamp > STATUSLINE_STALE_MS) {
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
if (typeof raw.used_percentage !== "number" || typeof raw.context_window_size !== "number") {
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
850
|
+
const windowSize = raw.context_window_size;
|
|
851
|
+
const usedTokens = Math.round(raw.used_percentage / 100 * windowSize);
|
|
852
|
+
return {
|
|
853
|
+
status: "ok",
|
|
854
|
+
usedTokens,
|
|
855
|
+
windowSize,
|
|
856
|
+
fraction: raw.used_percentage / 100,
|
|
857
|
+
source: "statusline"
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
function usageTotal(usage) {
|
|
861
|
+
return (usage.input_tokens || 0) + (usage.cache_read_input_tokens || 0) + (usage.cache_creation_input_tokens || 0);
|
|
862
|
+
}
|
|
863
|
+
function contextFillFromTranscript(transcriptPath, windowSize = DEFAULT_CONTEXT_WINDOW) {
|
|
864
|
+
if (!transcriptPath || !existsSync7(transcriptPath)) return unknownReading(windowSize);
|
|
865
|
+
let raw;
|
|
866
|
+
try {
|
|
867
|
+
raw = readFileSync6(transcriptPath, "utf-8");
|
|
868
|
+
} catch {
|
|
869
|
+
return unknownReading(windowSize);
|
|
870
|
+
}
|
|
871
|
+
const lines = raw.trim().split("\n").filter((l) => l.trim());
|
|
872
|
+
const tail = lines.slice(-TRANSCRIPT_TAIL_LINES);
|
|
873
|
+
for (let i = tail.length - 1; i >= 0; i--) {
|
|
874
|
+
let entry;
|
|
875
|
+
try {
|
|
876
|
+
entry = JSON.parse(tail[i]);
|
|
877
|
+
} catch {
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
const usage = entry?.message?.usage;
|
|
881
|
+
if (usage && typeof usage === "object") {
|
|
882
|
+
const usedTokens = usageTotal(usage);
|
|
883
|
+
return {
|
|
884
|
+
status: "ok",
|
|
885
|
+
usedTokens,
|
|
886
|
+
windowSize,
|
|
887
|
+
fraction: usedTokens / windowSize,
|
|
888
|
+
source: "transcript"
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return unknownReading(windowSize);
|
|
893
|
+
}
|
|
894
|
+
function getContextFill(input, now = Date.now()) {
|
|
895
|
+
const windowSize = input.windowSize ?? DEFAULT_CONTEXT_WINDOW;
|
|
896
|
+
if (input.sessionId) {
|
|
897
|
+
const fromStatusline = readStatuslineFill(input.sessionId, now);
|
|
898
|
+
if (fromStatusline) return fromStatusline;
|
|
899
|
+
}
|
|
900
|
+
if (input.transcriptPath) {
|
|
901
|
+
return contextFillFromTranscript(input.transcriptPath, windowSize);
|
|
902
|
+
}
|
|
903
|
+
return unknownReading(windowSize);
|
|
904
|
+
}
|
|
905
|
+
function formatContextFill(reading) {
|
|
906
|
+
if (reading.status !== "ok" || reading.usedTokens === null) {
|
|
907
|
+
return { text: "unknown", flagged: false };
|
|
908
|
+
}
|
|
909
|
+
let used = reading.usedTokens;
|
|
910
|
+
let flagged = false;
|
|
911
|
+
if (used > reading.windowSize) {
|
|
912
|
+
flagged = true;
|
|
913
|
+
used = reading.windowSize;
|
|
914
|
+
}
|
|
915
|
+
const text = used > 1e3 ? `${Math.round(used / 1e3)}k` : String(used);
|
|
916
|
+
return { text: flagged ? `${text}+ (clamped \u2014 reading exceeded window)` : text, flagged };
|
|
917
|
+
}
|
|
918
|
+
var CLAUDE_PROJECTS_DIR = join7(homedir3(), ".claude", "projects");
|
|
919
|
+
|
|
920
|
+
// src/hooks/ts/lib/transcript-text.ts
|
|
852
921
|
function contentToText(content) {
|
|
853
922
|
if (typeof content === "string") return content;
|
|
854
923
|
if (Array.isArray(content)) {
|
|
855
924
|
return content.map((c) => {
|
|
856
925
|
if (typeof c === "string") return c;
|
|
857
|
-
if (c
|
|
858
|
-
|
|
926
|
+
if (c && typeof c === "object" && c.type === "text" && typeof c.text === "string") {
|
|
927
|
+
return c.text;
|
|
928
|
+
}
|
|
859
929
|
return "";
|
|
860
930
|
}).join(" ").trim();
|
|
861
931
|
}
|
|
862
932
|
return "";
|
|
863
933
|
}
|
|
934
|
+
function isNoiseFilePath(filePath, scratchpadDir) {
|
|
935
|
+
if (scratchpadDir && filePath.startsWith(scratchpadDir)) return true;
|
|
936
|
+
const segments = filePath.split("/");
|
|
937
|
+
return segments.includes("tmp") || segments.includes("jobs") || segments.includes("scratchpad");
|
|
938
|
+
}
|
|
939
|
+
function preferCwdFiles(files, cwd) {
|
|
940
|
+
if (!cwd) return files;
|
|
941
|
+
const inCwd = files.filter((f) => f.startsWith(cwd));
|
|
942
|
+
const outCwd = files.filter((f) => !f.startsWith(cwd));
|
|
943
|
+
return [...inCwd, ...outCwd];
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// src/hooks/ts/lib/context-handover-cache.ts
|
|
947
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
|
|
948
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
949
|
+
import { join as join8 } from "node:path";
|
|
950
|
+
function contextHandoverCachePath(sessionId) {
|
|
951
|
+
return join8(tmpdir2(), `pai-context-handover-${sessionId}.json`);
|
|
952
|
+
}
|
|
953
|
+
function readContextHandoverCache(sessionId) {
|
|
954
|
+
const path = contextHandoverCachePath(sessionId);
|
|
955
|
+
if (!existsSync8(path)) return null;
|
|
956
|
+
try {
|
|
957
|
+
return JSON.parse(readFileSync7(path, "utf-8"));
|
|
958
|
+
} catch {
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// src/hooks/ts/pre-compact/context-compression-hook.ts
|
|
964
|
+
var DAEMON_SOCKET = process.env.PAI_SOCKET ?? "/tmp/pai.sock";
|
|
965
|
+
var DAEMON_TIMEOUT_MS = 3e3;
|
|
864
966
|
function getTranscriptStats(transcriptPath) {
|
|
865
967
|
try {
|
|
866
|
-
const content =
|
|
968
|
+
const content = readFileSync8(transcriptPath, "utf-8");
|
|
867
969
|
const lines = content.trim().split("\n");
|
|
868
970
|
let userMessages = 0;
|
|
869
971
|
let assistantMessages = 0;
|
|
@@ -882,17 +984,18 @@ function getTranscriptStats(transcriptPath) {
|
|
|
882
984
|
return { messageCount: 0, isLarge: false };
|
|
883
985
|
}
|
|
884
986
|
}
|
|
885
|
-
function parseTranscript(transcriptPath) {
|
|
987
|
+
function parseTranscript(transcriptPath, scratchpadDir) {
|
|
886
988
|
const data = {
|
|
887
989
|
userMessages: [],
|
|
888
990
|
summaries: [],
|
|
889
991
|
captures: [],
|
|
890
992
|
lastCompleted: "",
|
|
891
993
|
filesModified: [],
|
|
994
|
+
filesExcluded: 0,
|
|
892
995
|
workItems: []
|
|
893
996
|
};
|
|
894
997
|
try {
|
|
895
|
-
const raw =
|
|
998
|
+
const raw = readFileSync8(transcriptPath, "utf-8");
|
|
896
999
|
const lines = raw.trim().split("\n");
|
|
897
1000
|
const seenSummaries = /* @__PURE__ */ new Set();
|
|
898
1001
|
for (const line of lines) {
|
|
@@ -944,8 +1047,11 @@ function parseTranscript(transcriptPath) {
|
|
|
944
1047
|
if (block.type === "tool_use") {
|
|
945
1048
|
const tool = block.name;
|
|
946
1049
|
if ((tool === "Edit" || tool === "Write") && block.input?.file_path) {
|
|
947
|
-
|
|
948
|
-
|
|
1050
|
+
const fp = block.input.file_path;
|
|
1051
|
+
if (isNoiseFilePath(fp, scratchpadDir)) {
|
|
1052
|
+
data.filesExcluded++;
|
|
1053
|
+
} else if (!data.filesModified.includes(fp)) {
|
|
1054
|
+
data.filesModified.push(fp);
|
|
949
1055
|
}
|
|
950
1056
|
}
|
|
951
1057
|
}
|
|
@@ -978,10 +1084,15 @@ function formatSessionState(data, cwd) {
|
|
|
978
1084
|
parts.push("\nCaptured context:");
|
|
979
1085
|
for (const c of recentCaptures) parts.push(`- ${c.slice(0, 150)}`);
|
|
980
1086
|
}
|
|
981
|
-
const files = data.filesModified.slice(-10);
|
|
1087
|
+
const files = preferCwdFiles(data.filesModified, cwd).slice(-10);
|
|
982
1088
|
if (files.length > 0) {
|
|
983
1089
|
parts.push("\nFiles modified this session:");
|
|
984
1090
|
for (const f of files) parts.push(`- ${f}`);
|
|
1091
|
+
} else if (data.filesExcluded > 0) {
|
|
1092
|
+
parts.push(
|
|
1093
|
+
`
|
|
1094
|
+
Files modified this session: none in the working directory (excluded ${data.filesExcluded} noise path(s) under tmp/jobs/scratchpad)`
|
|
1095
|
+
);
|
|
985
1096
|
}
|
|
986
1097
|
if (data.lastCompleted) {
|
|
987
1098
|
parts.push(`
|
|
@@ -1028,21 +1139,32 @@ function deriveTitle(data) {
|
|
|
1028
1139
|
var CUMULATIVE_STATE_FILE = ".compact-state.json";
|
|
1029
1140
|
function loadCumulativeState(notesDir) {
|
|
1030
1141
|
try {
|
|
1031
|
-
const filePath =
|
|
1032
|
-
if (!
|
|
1033
|
-
const raw = JSON.parse(
|
|
1142
|
+
const filePath = join9(notesDir, CUMULATIVE_STATE_FILE);
|
|
1143
|
+
if (!existsSync9(filePath)) return null;
|
|
1144
|
+
const raw = JSON.parse(readFileSync8(filePath, "utf-8"));
|
|
1034
1145
|
return {
|
|
1035
1146
|
userMessages: raw.userMessages || [],
|
|
1036
1147
|
summaries: raw.summaries || [],
|
|
1037
1148
|
captures: raw.captures || [],
|
|
1038
1149
|
lastCompleted: raw.lastCompleted || "",
|
|
1039
1150
|
filesModified: raw.filesModified || [],
|
|
1151
|
+
filesExcluded: raw.filesExcluded || 0,
|
|
1040
1152
|
workItems: raw.workItems || []
|
|
1041
1153
|
};
|
|
1042
1154
|
} catch {
|
|
1043
1155
|
return null;
|
|
1044
1156
|
}
|
|
1045
1157
|
}
|
|
1158
|
+
function readPreviousCompactionTimestamp(notesDir) {
|
|
1159
|
+
try {
|
|
1160
|
+
const filePath = join9(notesDir, CUMULATIVE_STATE_FILE);
|
|
1161
|
+
if (!existsSync9(filePath)) return null;
|
|
1162
|
+
const raw = JSON.parse(readFileSync8(filePath, "utf-8"));
|
|
1163
|
+
return typeof raw.lastUpdated === "string" ? raw.lastUpdated : null;
|
|
1164
|
+
} catch {
|
|
1165
|
+
return null;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1046
1168
|
function mergeTranscriptData(accumulated, current) {
|
|
1047
1169
|
if (!accumulated) return current;
|
|
1048
1170
|
const mergeArrays = (a, b) => {
|
|
@@ -1057,13 +1179,14 @@ function mergeTranscriptData(accumulated, current) {
|
|
|
1057
1179
|
captures: mergeArrays(accumulated.captures, current.captures),
|
|
1058
1180
|
lastCompleted: current.lastCompleted || accumulated.lastCompleted,
|
|
1059
1181
|
filesModified: mergeArrays(accumulated.filesModified, current.filesModified),
|
|
1182
|
+
filesExcluded: accumulated.filesExcluded + current.filesExcluded,
|
|
1060
1183
|
workItems: [...accumulated.workItems, ...newWorkItems]
|
|
1061
1184
|
};
|
|
1062
1185
|
}
|
|
1063
1186
|
function saveCumulativeState(notesDir, data, notePath) {
|
|
1064
1187
|
try {
|
|
1065
|
-
const filePath =
|
|
1066
|
-
|
|
1188
|
+
const filePath = join9(notesDir, CUMULATIVE_STATE_FILE);
|
|
1189
|
+
writeFileSync5(filePath, JSON.stringify({
|
|
1067
1190
|
...data,
|
|
1068
1191
|
notePath,
|
|
1069
1192
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1198,19 +1321,24 @@ async function main() {
|
|
|
1198
1321
|
} catch {
|
|
1199
1322
|
}
|
|
1200
1323
|
const compactType = hookInput?.compact_type || hookInput?.trigger || "auto";
|
|
1201
|
-
let
|
|
1324
|
+
let ntfyTokenDisplay = null;
|
|
1202
1325
|
if (hookInput?.transcript_path) {
|
|
1203
1326
|
const stats = getTranscriptStats(hookInput.transcript_path);
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1327
|
+
const fill = getContextFill({
|
|
1328
|
+
sessionId: hookInput.session_id,
|
|
1329
|
+
transcriptPath: hookInput.transcript_path
|
|
1330
|
+
});
|
|
1331
|
+
const tokenDisplay = formatContextFill(fill).text;
|
|
1332
|
+
ntfyTokenDisplay = tokenDisplay;
|
|
1333
|
+
const data = parseTranscript(hookInput.transcript_path, hookInput.scratchpad_dir);
|
|
1207
1334
|
let notesInfo;
|
|
1208
1335
|
try {
|
|
1209
|
-
notesInfo = hookInput.cwd ? findNotesDir(hookInput.cwd) : { path:
|
|
1336
|
+
notesInfo = hookInput.cwd ? findNotesDir(hookInput.cwd) : { path: join9(dirname(hookInput.transcript_path), "Notes"), isLocal: false };
|
|
1210
1337
|
} catch {
|
|
1211
|
-
notesInfo = { path:
|
|
1338
|
+
notesInfo = { path: join9(dirname(hookInput.transcript_path), "Notes"), isLocal: false };
|
|
1212
1339
|
}
|
|
1213
1340
|
const accumulated = loadCumulativeState(notesInfo.path);
|
|
1341
|
+
const previousCompactionAt = readPreviousCompactionTimestamp(notesInfo.path);
|
|
1214
1342
|
const merged = mergeTranscriptData(accumulated, data);
|
|
1215
1343
|
const state = formatSessionState(merged, hookInput.cwd);
|
|
1216
1344
|
if (accumulated) {
|
|
@@ -1224,10 +1352,10 @@ async function main() {
|
|
|
1224
1352
|
notePath = createSessionNote(notesInfo.path, "Untitled Session");
|
|
1225
1353
|
} else {
|
|
1226
1354
|
try {
|
|
1227
|
-
let noteContent =
|
|
1355
|
+
let noteContent = readFileSync8(notePath, "utf-8");
|
|
1228
1356
|
if (noteContent.includes("**Status:** Completed")) {
|
|
1229
1357
|
noteContent = noteContent.replace("**Status:** Completed", "**Status:** In Progress");
|
|
1230
|
-
|
|
1358
|
+
writeFileSync5(notePath, noteContent);
|
|
1231
1359
|
console.error(`Reopened completed note for continued session: ${basename4(notePath)}`);
|
|
1232
1360
|
}
|
|
1233
1361
|
} catch {
|
|
@@ -1246,12 +1374,12 @@ ${state}` : `Context compression triggered at ~${tokenDisplay} tokens with ${sta
|
|
|
1246
1374
|
const newPath = renameSessionNote(notePath, title);
|
|
1247
1375
|
if (newPath !== notePath) {
|
|
1248
1376
|
try {
|
|
1249
|
-
let noteContent =
|
|
1377
|
+
let noteContent = readFileSync8(newPath, "utf-8");
|
|
1250
1378
|
noteContent = noteContent.replace(
|
|
1251
1379
|
/^(# Session \d+:)\s*.*$/m,
|
|
1252
1380
|
`$1 ${title}`
|
|
1253
1381
|
);
|
|
1254
|
-
|
|
1382
|
+
writeFileSync5(newPath, noteContent);
|
|
1255
1383
|
console.error(`Updated note H1 to match rename`);
|
|
1256
1384
|
} catch {
|
|
1257
1385
|
}
|
|
@@ -1278,12 +1406,31 @@ ${state}` : `Context compression triggered at ~${tokenDisplay} tokens with ${sta
|
|
|
1278
1406
|
SESSION NOTE: ${notePath}
|
|
1279
1407
|
If this note still has a generic title (e.g. "New Session", "Context Compression"),
|
|
1280
1408
|
rename it based on actual work done and add a rich summary.` : "";
|
|
1409
|
+
let sourceLabel;
|
|
1410
|
+
let handoverBlock;
|
|
1411
|
+
try {
|
|
1412
|
+
const cachedHandover = readContextHandoverCache(hookInput.session_id);
|
|
1413
|
+
const handoverIsFresh = cachedHandover !== null && (!previousCompactionAt || new Date(cachedHandover.generatedAt) > new Date(previousCompactionAt));
|
|
1414
|
+
sourceLabel = handoverIsFresh ? `model-written handover (${cachedHandover.model}, generated ${cachedHandover.generatedAt}, threshold=${cachedHandover.threshold}) + mechanical scrape` : "mechanical scrape only (no fresh model-written handover was available)";
|
|
1415
|
+
handoverBlock = handoverIsFresh ? [
|
|
1416
|
+
"",
|
|
1417
|
+
"--- MODEL-WRITTEN HANDOVER (decisions, reasoning, open threads \u2014 not in the scrape above) ---",
|
|
1418
|
+
cachedHandover.summary,
|
|
1419
|
+
"--- end model-written handover ---"
|
|
1420
|
+
].join("\n") : "";
|
|
1421
|
+
} catch (err) {
|
|
1422
|
+
console.error(`Failed to resolve handover cache \u2014 falling back to scrape-only: ${err}`);
|
|
1423
|
+
sourceLabel = `mechanical scrape only (error resolving handover cache: ${err})`;
|
|
1424
|
+
handoverBlock = "";
|
|
1425
|
+
}
|
|
1281
1426
|
const injection = [
|
|
1282
1427
|
"<system-reminder>",
|
|
1283
1428
|
`SESSION STATE RECOVERED AFTER COMPACTION (${compactType}, ~${tokenDisplay} tokens)`,
|
|
1429
|
+
`HANDOVER SOURCE: ${sourceLabel}`,
|
|
1284
1430
|
"",
|
|
1285
1431
|
stateText,
|
|
1286
1432
|
noteInfo,
|
|
1433
|
+
handoverBlock,
|
|
1287
1434
|
"",
|
|
1288
1435
|
"IMPORTANT: This session state was captured before context compaction.",
|
|
1289
1436
|
"Use it to maintain continuity. Continue the conversation from where",
|
|
@@ -1292,8 +1439,8 @@ rename it based on actual work done and add a rich summary.` : "";
|
|
|
1292
1439
|
"</system-reminder>"
|
|
1293
1440
|
].join("\n");
|
|
1294
1441
|
try {
|
|
1295
|
-
const stateFile =
|
|
1296
|
-
|
|
1442
|
+
const stateFile = join9(tmpdir3(), `pai-compact-state-${hookInput.session_id}.txt`);
|
|
1443
|
+
writeFileSync5(stateFile, injection, "utf-8");
|
|
1297
1444
|
console.error(`Session state saved to ${stateFile} (${injection.length} chars)`);
|
|
1298
1445
|
} catch (err) {
|
|
1299
1446
|
console.error(`Failed to save state file: ${err}`);
|
|
@@ -1315,7 +1462,7 @@ rename it based on actual work done and add a rich summary.` : "";
|
|
|
1315
1462
|
await enqueueRegistryScan();
|
|
1316
1463
|
} catch {
|
|
1317
1464
|
}
|
|
1318
|
-
const ntfyMessage =
|
|
1465
|
+
const ntfyMessage = ntfyTokenDisplay && ntfyTokenDisplay !== "unknown" ? `Auto-pause: ~${ntfyTokenDisplay} tokens` : "Context compressing";
|
|
1319
1466
|
await sendNtfyNotification(ntfyMessage);
|
|
1320
1467
|
process.exit(0);
|
|
1321
1468
|
}
|