@tekmidian/pai 0.35.2 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/{auto-route-DVM3U2ZY.mjs → auto-route-Byf8ENXj.mjs} +4 -4
  2. package/dist/{auto-route-DVM3U2ZY.mjs.map → auto-route-Byf8ENXj.mjs.map} +1 -1
  3. package/dist/{checkpoint-block-D3rm4dAJ.mjs → checkpoint-block-DKYxCkBL.mjs} +7 -6
  4. package/dist/checkpoint-block-DKYxCkBL.mjs.map +1 -0
  5. package/dist/cli/index.mjs +25 -17
  6. package/dist/cli/index.mjs.map +1 -1
  7. package/dist/cli/probe2.mjs +2 -0
  8. package/dist/cli/program.d.mts.map +1 -1
  9. package/dist/cli/program.mjs +16 -267
  10. package/dist/{clusters-CzGxefB7.mjs → clusters-wZgTCYCB.mjs} +2 -2
  11. package/dist/{clusters-CzGxefB7.mjs.map → clusters-wZgTCYCB.mjs.map} +1 -1
  12. package/dist/{config-BSkVcvfq.mjs → config-B64vFg14.mjs} +3 -12
  13. package/dist/{config-BSkVcvfq.mjs.map → config-B64vFg14.mjs.map} +1 -1
  14. package/dist/config-C_ErGddD.mjs +3 -0
  15. package/dist/daemon/index.mjs +19 -19
  16. package/dist/{daemon-B8L2f5vy.mjs → daemon--N2JFnUs.mjs} +33 -38
  17. package/dist/daemon--N2JFnUs.mjs.map +1 -0
  18. package/dist/daemon-DJEFqV84.mjs +20 -0
  19. package/dist/daemon-mcp/index.mjs +2 -2
  20. package/dist/{db-BtuN768f.mjs → db-Ca5qfsMC.mjs} +2 -4
  21. package/dist/{db-BtuN768f.mjs.map → db-Ca5qfsMC.mjs.map} +1 -1
  22. package/dist/db-O-cyAPfS.mjs +3 -0
  23. package/dist/db-XEwJbuGO.mjs +3 -0
  24. package/dist/{db-CYmBWcjh.mjs → db-a1ixZQjr.mjs} +2 -4
  25. package/dist/{db-CYmBWcjh.mjs.map → db-a1ixZQjr.mjs.map} +1 -1
  26. package/dist/{detect-Bf2z-oKB.mjs → detect-CdaA48EI.mjs} +1 -1
  27. package/dist/{detect-Bf2z-oKB.mjs.map → detect-CdaA48EI.mjs.map} +1 -1
  28. package/dist/{detector-BU-bsDXs.mjs → detector--Gg5JRN5.mjs} +3 -5
  29. package/dist/{detector-BU-bsDXs.mjs.map → detector--Gg5JRN5.mjs.map} +1 -1
  30. package/dist/detector-DGAk1iBR.mjs +5 -0
  31. package/dist/embeddings-CEBGrzwu.mjs +3 -0
  32. package/dist/{embeddings-Bn86ssxR.mjs → embeddings-DOLZnT1X.mjs} +2 -12
  33. package/dist/{embeddings-Bn86ssxR.mjs.map → embeddings-DOLZnT1X.mjs.map} +1 -1
  34. package/dist/{factory-vPTPoBR7.mjs → factory-Bsp7xOpO.mjs} +9 -12
  35. package/dist/{factory-vPTPoBR7.mjs.map → factory-Bsp7xOpO.mjs.map} +1 -1
  36. package/dist/factory-CrokPMk2.mjs +3 -0
  37. package/dist/{helpers-crDEr6S2.mjs → helpers-IjZkXBhj.mjs} +1 -1
  38. package/dist/{helpers-crDEr6S2.mjs.map → helpers-IjZkXBhj.mjs.map} +1 -1
  39. package/dist/hooks/context-compression-hook.mjs +209 -70
  40. package/dist/hooks/context-compression-hook.mjs.map +4 -4
  41. package/dist/index.mjs +10 -10
  42. package/dist/{indexer-backend-Bg7VDpGt.mjs → indexer-backend-nQZuEx6N.mjs} +3 -3
  43. package/dist/{indexer-backend-Bg7VDpGt.mjs.map → indexer-backend-nQZuEx6N.mjs.map} +1 -1
  44. package/dist/{ipc-client-aVKVERjJ.mjs → ipc-client-BmypMNYk.mjs} +13 -7
  45. package/dist/ipc-client-BmypMNYk.mjs.map +1 -0
  46. package/dist/{kg-entity-r8duqhi9.mjs → kg-entity-DbOMPdF9.mjs} +1 -1
  47. package/dist/{kg-entity-r8duqhi9.mjs.map → kg-entity-DbOMPdF9.mjs.map} +1 -1
  48. package/dist/{latent-ideas-BL9m2HF9.mjs → latent-ideas-Bn6A5-5P.mjs} +4 -4
  49. package/dist/{latent-ideas-BL9m2HF9.mjs.map → latent-ideas-Bn6A5-5P.mjs.map} +1 -1
  50. package/dist/{link-boost-QFLrJwD6.mjs → link-boost-fYjUnxCN.mjs} +1 -1
  51. package/dist/{link-boost-QFLrJwD6.mjs.map → link-boost-fYjUnxCN.mjs.map} +1 -1
  52. package/dist/{main-resolver-CNSqU8wo.mjs → main-resolver-BAbhKpeX.mjs} +12 -14
  53. package/dist/main-resolver-BAbhKpeX.mjs.map +1 -0
  54. package/dist/main-resolver-Dxh444GO.mjs +4 -0
  55. package/dist/{migrate-fLD6rAdO.mjs → migrate-Cjzeefn9.mjs} +2 -2
  56. package/dist/{migrate-fLD6rAdO.mjs.map → migrate-Cjzeefn9.mjs.map} +1 -1
  57. package/dist/{neighborhood-BX89_nty.mjs → neighborhood-DpaEM991.mjs} +2 -2
  58. package/dist/{neighborhood-BX89_nty.mjs.map → neighborhood-DpaEM991.mjs.map} +1 -1
  59. package/dist/{note-context-d1wT_-GA.mjs → note-context-DrcY4cWm.mjs} +1 -1
  60. package/dist/{note-context-d1wT_-GA.mjs.map → note-context-DrcY4cWm.mjs.map} +1 -1
  61. package/dist/{pai-marker-B20KqhA8.mjs → pai-marker-CHtbJMwJ.mjs} +1 -1
  62. package/dist/{pai-marker-B20KqhA8.mjs.map → pai-marker-CHtbJMwJ.mjs.map} +1 -1
  63. package/dist/{postgres-CI3FjGq0.mjs → postgres-BVme6qX0.mjs} +2 -2
  64. package/dist/{postgres-CI3FjGq0.mjs.map → postgres-BVme6qX0.mjs.map} +1 -1
  65. package/dist/{pick-DatgS3Nk.mjs → program-BnMNFb4O.mjs} +1171 -223
  66. package/dist/program-BnMNFb4O.mjs.map +1 -0
  67. package/dist/query-feedback-BBMBp96K.mjs +3 -0
  68. package/dist/{query-feedback-D4U56Hz6.mjs → query-feedback-C1T6kS18.mjs} +2 -4
  69. package/dist/{query-feedback-D4U56Hz6.mjs.map → query-feedback-C1T6kS18.mjs.map} +1 -1
  70. package/dist/reranker-CwTCNsgA.mjs +3 -0
  71. package/dist/{reranker-CMNZcfVx.mjs → reranker-xPm04PXx.mjs} +2 -8
  72. package/dist/{reranker-CMNZcfVx.mjs.map → reranker-xPm04PXx.mjs.map} +1 -1
  73. package/dist/router-BMkOb62X.mjs +3 -0
  74. package/dist/{router-i9S19Usg.mjs → router-CsDm7HvK.mjs} +2 -4
  75. package/dist/{router-i9S19Usg.mjs.map → router-CsDm7HvK.mjs.map} +1 -1
  76. package/dist/{runtime-paths-B0P1TvUr.mjs → runtime-paths-rni52zHX.mjs} +1 -1
  77. package/dist/{runtime-paths-B0P1TvUr.mjs.map → runtime-paths-rni52zHX.mjs.map} +1 -1
  78. package/dist/search-CfPpJAWQ.mjs +4 -0
  79. package/dist/{search-C32zQ0V0.mjs → search-Rpk1cSBC.mjs} +4 -15
  80. package/dist/{search-C32zQ0V0.mjs.map → search-Rpk1cSBC.mjs.map} +1 -1
  81. package/dist/{sources-BDwN0B8i.mjs → sources-D8ZdNfvK.mjs} +2 -2
  82. package/dist/{sources-BDwN0B8i.mjs.map → sources-D8ZdNfvK.mjs.map} +1 -1
  83. package/dist/{sqlite-C6FHnMkn.mjs → sqlite-D1IaR8Am.mjs} +3 -3
  84. package/dist/{sqlite-C6FHnMkn.mjs.map → sqlite-D1IaR8Am.mjs.map} +1 -1
  85. package/dist/state-WaXhLr6R.mjs +70 -0
  86. package/dist/{state-DTvy-jRB.mjs.map → state-WaXhLr6R.mjs.map} +1 -1
  87. package/dist/state-qtmrBWCm.mjs +3 -0
  88. package/dist/{stop-words-BaMEGVeY.mjs → stop-words-Hfu8u22w.mjs} +1 -1
  89. package/dist/{stop-words-BaMEGVeY.mjs.map → stop-words-Hfu8u22w.mjs.map} +1 -1
  90. package/dist/{sync--BoxBBok.mjs → sync-BWbe8JTg.mjs} +3 -3
  91. package/dist/{sync--BoxBBok.mjs.map → sync-BWbe8JTg.mjs.map} +1 -1
  92. package/dist/{themes-BObEGMWn.mjs → themes-XPkj_bfP.mjs} +3 -3
  93. package/dist/{themes-BObEGMWn.mjs.map → themes-XPkj_bfP.mjs.map} +1 -1
  94. package/dist/tools-DEt6YPfc.mjs +5 -0
  95. package/dist/{tools-C1lCHerL.mjs → tools-ceiy7ANX.mjs} +28 -65
  96. package/dist/tools-ceiy7ANX.mjs.map +1 -0
  97. package/dist/{trace-h23JCcFD.mjs → trace-DfyGmMG_.mjs} +1 -1
  98. package/dist/{trace-h23JCcFD.mjs.map → trace-DfyGmMG_.mjs.map} +1 -1
  99. package/dist/{utils-BAxjW3j8.mjs → utils-9Err2RBW.mjs} +2 -22
  100. package/dist/{utils-BAxjW3j8.mjs.map → utils-9Err2RBW.mjs.map} +1 -1
  101. package/dist/utils-DhMex3Ox.mjs +3 -0
  102. package/dist/{vault-indexer-CUF9edbW.mjs → vault-indexer-CFvlPUMB.mjs} +2 -2
  103. package/dist/{vault-indexer-CUF9edbW.mjs.map → vault-indexer-CFvlPUMB.mjs.map} +1 -1
  104. package/dist/{work-queue-worker-BcDGAcF3.mjs → work-queue-worker-228XjABm.mjs} +234 -14
  105. package/dist/work-queue-worker-228XjABm.mjs.map +1 -0
  106. package/dist/work-queue-worker-LRA9Fj9z.mjs +11 -0
  107. package/dist/{zettelkasten-W-h8G2is.mjs → zettelkasten-CvjmMghT.mjs} +4 -4
  108. package/dist/{zettelkasten-W-h8G2is.mjs.map → zettelkasten-CvjmMghT.mjs.map} +1 -1
  109. package/package.json +1 -1
  110. package/src/hooks/ts/lib/context-fill.test.ts +515 -0
  111. package/src/hooks/ts/lib/context-fill.ts +585 -0
  112. package/src/hooks/ts/lib/context-handover-cache.ts +46 -0
  113. package/src/hooks/ts/lib/transcript-text.test.ts +125 -0
  114. package/src/hooks/ts/lib/transcript-text.ts +71 -0
  115. package/src/hooks/ts/pre-compact/context-compression-hook.ts +112 -30
  116. package/statusline-command.sh +16 -0
  117. package/dist/checkpoint-block-D3rm4dAJ.mjs.map +0 -1
  118. package/dist/daemon-B8L2f5vy.mjs.map +0 -1
  119. package/dist/ipc-client-aVKVERjJ.mjs.map +0 -1
  120. package/dist/main-resolver-CNSqU8wo.mjs.map +0 -1
  121. package/dist/pick-DatgS3Nk.mjs.map +0 -1
  122. package/dist/rolldown-runtime-95iHPtFO.mjs +0 -18
  123. package/dist/state-DTvy-jRB.mjs +0 -102
  124. package/dist/tools-C1lCHerL.mjs.map +0 -1
  125. package/dist/work-queue-worker-BcDGAcF3.mjs.map +0 -1
  126. /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 existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
5
- import { basename as basename4, dirname, join as join7 } from "path";
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 existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
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 existsSync6,
429
- readFileSync as readFileSync5,
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 (existsSync6(full)) {
425
+ if (existsSync5(full)) {
454
426
  try {
455
- return { path: full, content: readFileSync5(full, "utf8") };
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 (!existsSync6(notesDir)) mkdirSync3(notesDir, { recursive: true });
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 (existsSync6(tmpPath)) renameSync2(tmpPath, `${tmpPath}.dead`);
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 (!existsSync7(todoPath)) {
753
+ if (!existsSync6(todoPath)) {
782
754
  const parentDir = join6(todoPath, "..");
783
- if (!existsSync7(parentDir)) mkdirSync4(parentDir, { recursive: true });
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 = readFileSync6(todoPath, "utf-8");
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/pre-compact/context-compression-hook.ts
850
- var DAEMON_SOCKET = process.env.PAI_SOCKET ?? "/tmp/pai.sock";
851
- var DAEMON_TIMEOUT_MS = 3e3;
821
+ // src/hooks/ts/lib/context-fill.ts
822
+ import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync } 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?.text) return c.text;
858
- if (c?.content) return String(c.content);
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 = readFileSync7(transcriptPath, "utf-8");
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 = readFileSync7(transcriptPath, "utf-8");
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
- if (!data.filesModified.includes(block.input.file_path)) {
948
- data.filesModified.push(block.input.file_path);
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 = join7(notesDir, CUMULATIVE_STATE_FILE);
1032
- if (!existsSync8(filePath)) return null;
1033
- const raw = JSON.parse(readFileSync7(filePath, "utf-8"));
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 = join7(notesDir, CUMULATIVE_STATE_FILE);
1066
- writeFileSync4(filePath, JSON.stringify({
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 tokenCount = 0;
1324
+ let ntfyTokenDisplay = null;
1202
1325
  if (hookInput?.transcript_path) {
1203
1326
  const stats = getTranscriptStats(hookInput.transcript_path);
1204
- tokenCount = calculateSessionTokens(hookInput.transcript_path);
1205
- const tokenDisplay = tokenCount > 1e3 ? `${Math.round(tokenCount / 1e3)}k` : String(tokenCount);
1206
- const data = parseTranscript(hookInput.transcript_path);
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: join7(dirname(hookInput.transcript_path), "Notes"), isLocal: false };
1336
+ notesInfo = hookInput.cwd ? findNotesDir(hookInput.cwd) : { path: join9(dirname(hookInput.transcript_path), "Notes"), isLocal: false };
1210
1337
  } catch {
1211
- notesInfo = { path: join7(dirname(hookInput.transcript_path), "Notes"), isLocal: false };
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 = readFileSync7(notePath, "utf-8");
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
- writeFileSync4(notePath, noteContent);
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 = readFileSync7(newPath, "utf-8");
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
- writeFileSync4(newPath, noteContent);
1382
+ writeFileSync5(newPath, noteContent);
1255
1383
  console.error(`Updated note H1 to match rename`);
1256
1384
  } catch {
1257
1385
  }
@@ -1278,12 +1406,23 @@ ${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
+ const cachedHandover = readContextHandoverCache(hookInput.session_id);
1410
+ const handoverIsFresh = cachedHandover !== null && (!previousCompactionAt || new Date(cachedHandover.generatedAt) > new Date(previousCompactionAt));
1411
+ const 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)";
1412
+ const handoverBlock = handoverIsFresh ? [
1413
+ "",
1414
+ "--- MODEL-WRITTEN HANDOVER (decisions, reasoning, open threads \u2014 not in the scrape above) ---",
1415
+ cachedHandover.summary,
1416
+ "--- end model-written handover ---"
1417
+ ].join("\n") : "";
1281
1418
  const injection = [
1282
1419
  "<system-reminder>",
1283
1420
  `SESSION STATE RECOVERED AFTER COMPACTION (${compactType}, ~${tokenDisplay} tokens)`,
1421
+ `HANDOVER SOURCE: ${sourceLabel}`,
1284
1422
  "",
1285
1423
  stateText,
1286
1424
  noteInfo,
1425
+ handoverBlock,
1287
1426
  "",
1288
1427
  "IMPORTANT: This session state was captured before context compaction.",
1289
1428
  "Use it to maintain continuity. Continue the conversation from where",
@@ -1292,8 +1431,8 @@ rename it based on actual work done and add a rich summary.` : "";
1292
1431
  "</system-reminder>"
1293
1432
  ].join("\n");
1294
1433
  try {
1295
- const stateFile = join7(tmpdir(), `pai-compact-state-${hookInput.session_id}.txt`);
1296
- writeFileSync4(stateFile, injection, "utf-8");
1434
+ const stateFile = join9(tmpdir3(), `pai-compact-state-${hookInput.session_id}.txt`);
1435
+ writeFileSync5(stateFile, injection, "utf-8");
1297
1436
  console.error(`Session state saved to ${stateFile} (${injection.length} chars)`);
1298
1437
  } catch (err) {
1299
1438
  console.error(`Failed to save state file: ${err}`);
@@ -1315,7 +1454,7 @@ rename it based on actual work done and add a rich summary.` : "";
1315
1454
  await enqueueRegistryScan();
1316
1455
  } catch {
1317
1456
  }
1318
- const ntfyMessage = tokenCount > 0 ? `Auto-pause: ~${Math.round(tokenCount / 1e3)}k tokens` : "Context compressing";
1457
+ const ntfyMessage = ntfyTokenDisplay && ntfyTokenDisplay !== "unknown" ? `Auto-pause: ~${ntfyTokenDisplay} tokens` : "Context compressing";
1319
1458
  await sendNtfyNotification(ntfyMessage);
1320
1459
  process.exit(0);
1321
1460
  }