@geminixiang/mama 0.2.0-beta.5 → 0.2.0-beta.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.
Files changed (94) hide show
  1. package/README.md +81 -18
  2. package/dist/adapter.d.ts +3 -0
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/adapters/discord/bot.d.ts +1 -1
  6. package/dist/adapters/discord/bot.d.ts.map +1 -1
  7. package/dist/adapters/discord/bot.js +84 -17
  8. package/dist/adapters/discord/bot.js.map +1 -1
  9. package/dist/adapters/slack/bot.d.ts +12 -0
  10. package/dist/adapters/slack/bot.d.ts.map +1 -1
  11. package/dist/adapters/slack/bot.js +219 -15
  12. package/dist/adapters/slack/bot.js.map +1 -1
  13. package/dist/adapters/slack/context.d.ts.map +1 -1
  14. package/dist/adapters/slack/context.js +5 -0
  15. package/dist/adapters/slack/context.js.map +1 -1
  16. package/dist/adapters/slack/tools/attach.d.ts +1 -1
  17. package/dist/adapters/slack/tools/attach.d.ts.map +1 -1
  18. package/dist/adapters/slack/tools/attach.js.map +1 -1
  19. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  20. package/dist/adapters/telegram/bot.js +32 -35
  21. package/dist/adapters/telegram/bot.js.map +1 -1
  22. package/dist/agent.d.ts.map +1 -1
  23. package/dist/agent.js +42 -26
  24. package/dist/agent.js.map +1 -1
  25. package/dist/commands/index.d.ts.map +1 -1
  26. package/dist/commands/index.js +10 -1
  27. package/dist/commands/index.js.map +1 -1
  28. package/dist/commands/model.d.ts +14 -0
  29. package/dist/commands/model.d.ts.map +1 -0
  30. package/dist/commands/model.js +112 -0
  31. package/dist/commands/model.js.map +1 -0
  32. package/dist/commands/new.d.ts +9 -0
  33. package/dist/commands/new.d.ts.map +1 -0
  34. package/dist/commands/new.js +28 -0
  35. package/dist/commands/new.js.map +1 -0
  36. package/dist/commands/sandbox.d.ts +10 -0
  37. package/dist/commands/sandbox.d.ts.map +1 -0
  38. package/dist/commands/sandbox.js +65 -0
  39. package/dist/commands/sandbox.js.map +1 -0
  40. package/dist/commands/session-view.d.ts.map +1 -1
  41. package/dist/commands/session-view.js +29 -9
  42. package/dist/commands/session-view.js.map +1 -1
  43. package/dist/commands/types.d.ts +2 -0
  44. package/dist/commands/types.d.ts.map +1 -1
  45. package/dist/commands/types.js.map +1 -1
  46. package/dist/commands/utils.d.ts +3 -0
  47. package/dist/commands/utils.d.ts.map +1 -1
  48. package/dist/commands/utils.js +5 -0
  49. package/dist/commands/utils.js.map +1 -1
  50. package/dist/config.d.ts +13 -4
  51. package/dist/config.d.ts.map +1 -1
  52. package/dist/config.js +177 -31
  53. package/dist/config.js.map +1 -1
  54. package/dist/context.d.ts +1 -1
  55. package/dist/context.d.ts.map +1 -1
  56. package/dist/context.js +50 -35
  57. package/dist/context.js.map +1 -1
  58. package/dist/main.d.ts.map +1 -1
  59. package/dist/main.js +53 -4
  60. package/dist/main.js.map +1 -1
  61. package/dist/provisioner.d.ts +12 -0
  62. package/dist/provisioner.d.ts.map +1 -1
  63. package/dist/provisioner.js +41 -10
  64. package/dist/provisioner.js.map +1 -1
  65. package/dist/runtime/session-runtime.d.ts +1 -0
  66. package/dist/runtime/session-runtime.d.ts.map +1 -1
  67. package/dist/runtime/session-runtime.js +18 -0
  68. package/dist/runtime/session-runtime.js.map +1 -1
  69. package/dist/session-store.d.ts +1 -1
  70. package/dist/session-store.d.ts.map +1 -1
  71. package/dist/session-store.js +1 -1
  72. package/dist/session-store.js.map +1 -1
  73. package/dist/session-view/service.d.ts.map +1 -1
  74. package/dist/session-view/service.js +1 -1
  75. package/dist/session-view/service.js.map +1 -1
  76. package/dist/tools/bash.d.ts +1 -1
  77. package/dist/tools/bash.d.ts.map +1 -1
  78. package/dist/tools/bash.js.map +1 -1
  79. package/dist/tools/edit.d.ts +1 -1
  80. package/dist/tools/edit.d.ts.map +1 -1
  81. package/dist/tools/edit.js.map +1 -1
  82. package/dist/tools/event.d.ts +1 -1
  83. package/dist/tools/event.d.ts.map +1 -1
  84. package/dist/tools/event.js.map +1 -1
  85. package/dist/tools/index.d.ts +1 -1
  86. package/dist/tools/index.d.ts.map +1 -1
  87. package/dist/tools/index.js.map +1 -1
  88. package/dist/tools/read.d.ts +1 -1
  89. package/dist/tools/read.d.ts.map +1 -1
  90. package/dist/tools/read.js.map +1 -1
  91. package/dist/tools/write.d.ts +1 -1
  92. package/dist/tools/write.d.ts.map +1 -1
  93. package/dist/tools/write.js.map +1 -1
  94. package/package.json +4 -4
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "crypto";
2
2
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync } from "fs";
3
3
  import { join } from "path";
4
- import { SessionManager } from "@mariozechner/pi-coding-agent";
4
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
5
5
  import { atomicWritePrivateFile } from "./fs-atomic.js";
6
6
  export class ThreadRootNotFoundError extends Error {
7
7
  constructor(sessionFile) {
@@ -1 +1 @@
1
- {"version":3,"file":"session-store.js","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAExD,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAChD,YAAY,WAAmB;QAC7B,KAAK,CAAC,oDAAoD,WAAW,EAAE,CAAC,CAAC;QACzE,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAgCD;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB;IACrD,OAAO,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAkB;IACnD,MAAM,QAAQ,GAAG,wBAAwB,CAAC,UAAU,CAAC,CAAC;IACtD,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,OAAO,oBAAoB,CAAC,UAAU,CAAC,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,UAAkB,EAAE,GAAW;IACvE,MAAM,YAAY,GAAG,qBAAqB,CAAC,UAAU,CAAC,CAAC;IACvD,IAAI,YAAY;QAAE,OAAO,YAAY,CAAC;IACtC,OAAO,wBAAwB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;AACnD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAAmB;IACpD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,WAAW,CAAC;IACzD,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC;AAC7D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB;IACrD,OAAO,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAC,CAAC,CAAC,UAAU,CAAC;AAC9E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB;IACrD,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,GAAG,SAAS,IAAI,IAAI,QAAQ,CAAC;IAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC5C,sBAAsB,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACrC,sBAAsB,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC9D,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,UAAkB,EAAE,GAAW;IACtE,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACjE,MAAM,SAAS,GAAG,UAAU,EAAE,CAAC;IAC/B,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,GAAG,SAAS,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;IACpF,kBAAkB,CAAC,WAAW,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;IAChD,iBAAiB,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAC3C,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,WAAmB,EACnB,UAAkB,EAClB,GAAW;IAEX,IAAI,mCAAmC,CAAC,WAAW,CAAC,EAAE,CAAC;QACrD,MAAM,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,kBAAkB,GAAG,cAE1B,CAAC;IACF,OAAO,IAAI,kBAAkB,CAAC,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;AACpE,CAAC;AAED,SAAS,iBAAiB,CAAC,UAAkB,EAAE,eAAuB;IACpE,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAC;IACnD,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,sBAAsB,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,QAAQ,CAAC,CAAC;AAChE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,8BAA8B,CAAC,WAAmB,EAAE,GAAW;IAC7E,kBAAkB,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IACrC,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,SAAS,kBAAkB,CAAC,WAAmB,EAAE,GAAW,EAAE,SAAS,GAAG,UAAU,EAAE;IACpF,MAAM,UAAU,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IAC3C,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG;QACb,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,CAAC;QACV,EAAE,EAAE,SAAS;QACb,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,GAAG;KACJ,CAAC;IACF,sBAAsB,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACrE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB,EAAE,UAAkB;IACzE,OAAO,IAAI,CAAC,oBAAoB,CAAC,UAAU,CAAC,EAAE,GAAG,oBAAoB,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;AAC7F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CACxC,OAA0C;IAE1C,MAAM,EAAE,eAAe,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IAChD,MAAM,UAAU,GAAG,oBAAoB,CAAC,eAAe,CAAC,CAAC;IAEzD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,OAAO;YACL,UAAU;YACV,WAAW,EAAE,yBAAyB,CAAC,UAAU,EAAE,eAAe,CAAC;YACnE,iBAAiB,EAAE,IAAI;SACxB,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,oBAAoB,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;IACrE,OAAO;QACL,UAAU;QACV,WAAW,EACT,uBAAuB,CAAC,UAAU,CAAC;YACnC,8BAA8B,CAAC,UAAU,EAAE,eAAe,CAAC;QAC7D,iBAAiB,EAAE,IAAI;KACxB,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,WAAmB;IAC3C,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,OAAO;gBAAE,SAAS;YACvB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAsB,CAAC;YACvD,OAAO,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC;QAClC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,mCAAmC,CAAC,WAAmB;IAC9D,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,KAAK,CAAC;IAE3C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC;aAC/C,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;aAC1B,MAAM,CAAC,OAAO,CAAC;aACf,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAsB,CAAC,CAAC;QAExD,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,SAAS,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,WAAmB;IACrC,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC,EAAE,WAAW,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,4BAA4B,CACnC,iBAAyB,EACzB,WAA8B;IAE9B,MAAM,UAAU,GAAG,8BAA8B,CAAC,WAAW,CAAC,CAAC;IAC/D,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAE7B,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,UAAU,EAA+B,CAAC;IACjG,MAAM,UAAU,GAAG,oBAAoB,CAAC,OAAO,EAAE,UAAU,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IACnF,IAAI,UAAU,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnC,MAAM,qBAAqB,GAAG,OAAO,CAAC,SAAS,CAC7C,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,UAAU,IAAI,kBAAkB,CAAC,KAAK,CAAC,CAClE,CAAC;IACF,MAAM,QAAQ,GAAG,qBAAqB,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,qBAAqB,CAAC;IACvF,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,oBAAoB,CAC3B,OAAkC,EAClC,UAAkB,EAClB,QAAiB;IAEjB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC;YAAE,SAAS;QAEzC,MAAM,cAAc,GAAG,2BAA2B,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1E,IAAI,cAAc,KAAK,UAAU;YAAE,SAAS;QAE5C,MAAM,gBAAgB,GAAG,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC;QAClD,IACE,QAAQ,KAAK,SAAS;YACtB,OAAO,gBAAgB,KAAK,QAAQ;YACpC,gBAAgB,GAAG,QAAQ,EAC3B,CAAC;YACD,SAAS;QACX,CAAC;QAED,OAAO,CAAC,CAAC;IACX,CAAC;IAED,OAAO,CAAC,CAAC,CAAC;AACZ,CAAC;AAED,SAAS,kBAAkB,CAAC,KAA8B;IACxD,OAAO,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,KAAK,MAAM,CAAC;AACpE,CAAC;AAED,SAAS,cAAc,CAAC,KAA8B;IACpD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC;IACvC,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC;IAEvC,OAAO,OAAO;SACX,MAAM,CAAC,CAAC,IAAI,EAA4C,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC;SAChF,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;SAC9B,IAAI,CAAC,MAAM,CAAC,CAAC;AAClB,CAAC;AAED,SAAS,8BAA8B,CAAC,WAA8B;IACpE,MAAM,SAAS,GAAG,WAAW,CAAC,QAAQ,IAAI,WAAW,CAAC,IAAI,IAAI,SAAS,CAAC;IACxE,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;IACtC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,OAAO,2BAA2B,CAAC,IAAI,SAAS,MAAM,IAAI,EAAE,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,yBAAyB,CAAC,IAAY;IAC7C,OAAO,IAAI,CAAC,OAAO,CAAC,8DAA8D,EAAE,EAAE,CAAC,CAAC;AAC1F,CAAC;AAED,SAAS,2BAA2B,CAAC,IAAY;IAC/C,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,CACnC,iIAAiI,EACjI,EAAE,CACH,CAAC;IACF,OAAO,yBAAyB,CAAC,gBAAgB,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5D,CAAC;AAED,SAAS,qBAAqB,CAAC,UAAkB;IAC/C,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAChD,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,QAAQ,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3D,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,OAAO,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,UAAkB;IACzD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,UAAU,CAAC,CAAC;IACnD,IAAI,QAAQ,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IACpF,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,WAAmB;IACzD,OAAO,UAAU,CAAC,WAAW,CAAC,IAAI,gBAAgB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC;AACvF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,UAAkB;IAC1D,OAAO,wBAAwB,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CACnC,iBAAyB,EACzB,iBAAyB,EACzB,GAAW;IAEX,MAAM,UAAU,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IACjD,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC;IAC3E,MAAM,UAAU,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IAC3C,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,+BAA+B,iBAAiB,EAAE,CAAC,CAAC;IACtE,CAAC;IACD,MAAM,CAAC,iBAAiB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,UAAU,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC;IAC1C,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,sCAAsC,CACpD,iBAAyB,EACzB,GAAW,EACX,WAA8B,EAC9B,aAAsB;IAEtB,MAAM,UAAU,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IACjD,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,CAAC,iBAAiB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAE3C,MAAM,MAAM,GAAG;QACb,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,CAAC;QACV,EAAE,EAAE,UAAU,EAAE;QAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,GAAG;QACH,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5C,CAAC;IACF,MAAM,QAAQ,GAAG,8BAA8B,CAAC,WAAW,CAAC,CAAC;IAC7D,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,sBAAsB,CAAC,iBAAiB,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzE,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IAED,MAAM,SAAS,GAAG;QAChB,IAAI,EAAE,SAAS;QACf,EAAE,EAAE,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;QAC5B,QAAQ,EAAE,IAAI;QACd,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,OAAO,EAAE;YACP,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;YAC3C,GAAG,CAAC,WAAW,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACnF;KACF,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrF,sBAAsB,CAAC,iBAAiB,EAAE,GAAG,OAAO,IAAI,CAAC,CAAC;IAC1D,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,oCAAoC,CAClD,iBAAyB,EACzB,iBAAyB,EACzB,GAAW,EACX,WAA8B;IAE9B,MAAM,eAAe,GAAG,4BAA4B,CAAC,iBAAiB,EAAE,WAAW,CAAC,CAAC;IACrF,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,MAAM,IAAI,uBAAuB,CAAC,iBAAiB,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,UAAU,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IACjD,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,CAAC,iBAAiB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAE3C,MAAM,MAAM,GAAG;QACb,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,CAAC;QACV,EAAE,EAAE,UAAU,EAAE;QAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,GAAG;QACH,aAAa,EAAE,iBAAiB;KACjC,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,GAAG,eAAe,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9F,sBAAsB,CAAC,iBAAiB,EAAE,GAAG,OAAO,IAAI,CAAC,CAAC;IAC1D,OAAO,iBAAiB,CAAC;AAC3B,CAAC","sourcesContent":["import { randomUUID } from \"crypto\";\nimport { existsSync, mkdirSync, readFileSync, renameSync, rmSync } from \"fs\";\nimport { join } from \"path\";\nimport { SessionManager } from \"@mariozechner/pi-coding-agent\";\nimport { atomicWritePrivateFile } from \"./fs-atomic.js\";\n\nexport class ThreadRootNotFoundError extends Error {\n constructor(sessionFile: string) {\n super(`Thread root message not found in source session: ${sessionFile}`);\n this.name = \"ThreadRootNotFoundError\";\n }\n}\n\nexport interface ThreadRootMessage {\n text?: string;\n userName?: string;\n user?: string;\n loggedAt?: number;\n}\n\nexport interface ResolvedSessionScope {\n sessionDir: string;\n contextFile: string;\n threadRootMessage: ThreadRootMessage | null;\n}\n\nexport interface ResolveGenericSessionScopeOptions {\n conversationDir: string;\n sessionKey: string;\n}\n\ninterface SessionMessageEntryLike {\n type: string;\n id: string;\n parentId: string | null;\n timestamp: string;\n message?: {\n role?: string;\n timestamp?: number;\n content?: Array<{ type?: string; text?: string }> | string;\n };\n}\n\n/**\n * Returns the shared session directory for a conversation.\n * Channel sessions use a current pointer within this directory.\n * Thread sessions are stored as fixed files within the same directory.\n */\nexport function getChannelSessionDir(channelDir: string): string {\n return join(channelDir, \"sessions\");\n}\n\n/**\n * Resolves the current active session file for a session directory.\n * Reads the \"current\" pointer file; creates a new session if none exists\n * or the pointed-to file is missing.\n */\nexport function resolveSessionFile(sessionDir: string): string {\n const existing = tryResolveCurrentSession(sessionDir);\n if (existing) return existing;\n return createNewSessionFile(sessionDir);\n}\n\n/**\n * Resolve the current active session file for a session directory.\n * Creates a fully initialized persistent session with the provided cwd when none exists.\n */\nexport function resolveManagedSessionFile(sessionDir: string, cwd: string): string {\n const existingPath = getCurrentSessionPath(sessionDir);\n if (existingPath) return existingPath;\n return createManagedSessionFile(sessionDir, cwd);\n}\n\n/**\n * Extracts the short UUID from a session file path.\n * e.g. \"2026-04-05T00-00_7b54cf90.jsonl\" → \"7b54cf90\"\n */\nexport function extractSessionUuid(sessionFile: string): string {\n const base = sessionFile.split(\"/\").pop() ?? sessionFile;\n return base.replace(\".jsonl\", \"\").split(\"_\").pop() ?? base;\n}\n\n/**\n * Extracts the thread/suffix part of a session key.\n * \"channelId:threadId\" → \"threadId\", \"channelId\" → \"channelId\"\n */\nexport function extractSessionSuffix(sessionKey: string): string {\n return sessionKey.includes(\":\") ? sessionKey.split(\":\").pop()! : sessionKey;\n}\n\n/**\n * Creates an empty timestamped file and updates the \"current\" pointer.\n * Used only by tests for placeholder-file scenarios.\n *\n * Order matters: write the session file first, then atomic-rename the pointer\n * last so a crash mid-create never leaves \"current\" pointing at a missing file.\n */\nexport function createNewSessionFile(sessionDir: string): string {\n mkdirSync(sessionDir, { recursive: true });\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const uuid = randomUUID().slice(0, 8);\n const filename = `${timestamp}_${uuid}.jsonl`;\n const filePath = join(sessionDir, filename);\n atomicWritePrivateFile(filePath, \"\");\n atomicWritePrivateFile(join(sessionDir, \"current\"), filename);\n return filePath;\n}\n\n/**\n * Creates a new persistent session file with a proper SessionManager header and cwd.\n * Also updates the \"current\" pointer. Header is written before the pointer flips so a\n * partial create cannot leave \"current\" pointing at a missing file.\n */\nexport function createManagedSessionFile(sessionDir: string, cwd: string): string {\n mkdirSync(sessionDir, { recursive: true });\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const sessionId = randomUUID();\n const sessionFile = join(sessionDir, `${timestamp}_${sessionId.slice(0, 8)}.jsonl`);\n writeSessionHeader(sessionFile, cwd, sessionId);\n setCurrentPointer(sessionDir, sessionFile);\n return sessionFile;\n}\n\n/**\n * Open a session file with an explicit cwd, even if the file does not exist yet.\n * This avoids SessionManager.open() falling back to process.cwd() for fresh sessions.\n */\nexport function openManagedSession(\n sessionFile: string,\n sessionDir: string,\n cwd: string,\n): SessionManager {\n if (shouldRecreatePreinitializedSession(sessionFile)) {\n rmSync(sessionFile, { force: true });\n }\n\n const SessionManagerCtor = SessionManager as unknown as {\n new (cwd: string, sessionDir: string, sessionFile: string, persist: boolean): SessionManager;\n };\n return new SessionManagerCtor(cwd, sessionDir, sessionFile, true);\n}\n\nfunction setCurrentPointer(sessionDir: string, sessionFilePath: string): void {\n const filename = sessionFilePath.split(\"/\").pop()!;\n mkdirSync(sessionDir, { recursive: true });\n atomicWritePrivateFile(join(sessionDir, \"current\"), filename);\n}\n\n/**\n * Creates or overwrites a fixed-path session file with a valid session header.\n */\nexport function createManagedSessionFileAtPath(sessionFile: string, cwd: string): string {\n writeSessionHeader(sessionFile, cwd);\n return sessionFile;\n}\n\nfunction writeSessionHeader(sessionFile: string, cwd: string, sessionId = randomUUID()): void {\n const sessionDir = getFileDir(sessionFile);\n mkdirSync(sessionDir, { recursive: true });\n const header = {\n type: \"session\",\n version: 3,\n id: sessionId,\n timestamp: new Date().toISOString(),\n cwd,\n };\n atomicWritePrivateFile(sessionFile, `${JSON.stringify(header)}\\n`);\n}\n\n/**\n * Returns the fixed session file path for a Slack thread.\n */\nexport function getThreadSessionFile(channelDir: string, sessionKey: string): string {\n return join(getChannelSessionDir(channelDir), `${extractSessionSuffix(sessionKey)}.jsonl`);\n}\n\n/**\n * Resolve the default session scope for platforms without Slack-style branch forking.\n * Top-level/private sessions use the conversation's current pointer. Threaded or\n * per-message sessions use a fixed file derived from the session key suffix.\n */\nexport function resolveGenericSessionScope(\n options: ResolveGenericSessionScopeOptions,\n): ResolvedSessionScope {\n const { conversationDir, sessionKey } = options;\n const sessionDir = getChannelSessionDir(conversationDir);\n\n if (!sessionKey.includes(\":\")) {\n return {\n sessionDir,\n contextFile: resolveManagedSessionFile(sessionDir, conversationDir),\n threadRootMessage: null,\n };\n }\n\n const threadFile = getThreadSessionFile(conversationDir, sessionKey);\n return {\n sessionDir,\n contextFile:\n tryResolveThreadSession(threadFile) ??\n createManagedSessionFileAtPath(threadFile, conversationDir),\n threadRootMessage: null,\n };\n}\n\nfunction hasSessionHeader(sessionFile: string): boolean {\n try {\n const lines = readFileSync(sessionFile, \"utf-8\").split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n const entry = JSON.parse(trimmed) as { type?: string };\n return entry.type === \"session\";\n }\n } catch {\n return false;\n }\n return false;\n}\n\nfunction shouldRecreatePreinitializedSession(sessionFile: string): boolean {\n if (!existsSync(sessionFile)) return false;\n\n try {\n const entries = readFileSync(sessionFile, \"utf-8\")\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter(Boolean)\n .map((line) => JSON.parse(line) as { type?: string });\n\n return entries.length === 1 && entries[0]?.type === \"session\";\n } catch {\n return false;\n }\n}\n\nfunction getFileDir(sessionFile: string): string {\n return sessionFile.substring(0, sessionFile.lastIndexOf(\"/\"));\n}\n\nfunction resolveThreadSnapshotEntries(\n sourceSessionFile: string,\n rootMessage: ThreadRootMessage,\n): SessionMessageEntryLike[] | null {\n const targetText = buildComparableRootMessageText(rootMessage);\n if (!targetText) return null;\n\n const entries = SessionManager.open(sourceSessionFile).getEntries() as SessionMessageEntryLike[];\n const matchIndex = findRootMessageIndex(entries, targetText, rootMessage.loggedAt);\n if (matchIndex === -1) return null;\n\n const nextTopLevelUserIndex = entries.findIndex(\n (entry, index) => index > matchIndex && isUserMessageEntry(entry),\n );\n const endIndex = nextTopLevelUserIndex === -1 ? entries.length : nextTopLevelUserIndex;\n return entries.slice(0, endIndex);\n}\n\nfunction findRootMessageIndex(\n entries: SessionMessageEntryLike[],\n targetText: string,\n loggedAt?: number,\n): number {\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n if (!isUserMessageEntry(entry)) continue;\n\n const comparableText = normalizeComparableUserText(getMessageText(entry));\n if (comparableText !== targetText) continue;\n\n const messageTimestamp = entry.message?.timestamp;\n if (\n loggedAt !== undefined &&\n typeof messageTimestamp === \"number\" &&\n messageTimestamp < loggedAt\n ) {\n continue;\n }\n\n return i;\n }\n\n return -1;\n}\n\nfunction isUserMessageEntry(entry: SessionMessageEntryLike): boolean {\n return entry.type === \"message\" && entry.message?.role === \"user\";\n}\n\nfunction getMessageText(entry: SessionMessageEntryLike): string {\n const content = entry.message?.content;\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n return content\n .filter((part): part is { type?: string; text?: string } => part.type === \"text\")\n .map((part) => part.text ?? \"\")\n .join(\"\\n\\n\");\n}\n\nfunction buildComparableRootMessageText(rootMessage: ThreadRootMessage): string | null {\n const userLabel = rootMessage.userName || rootMessage.user || \"unknown\";\n const text = rootMessage.text?.trim();\n if (!text) return null;\n return normalizeComparableUserText(`[${userLabel}]: ${text}`);\n}\n\nfunction stripSlackAttachmentBlock(text: string): string {\n return text.replace(/\\n*<slack_attachments>\\n[\\s\\S]*?\\n<\\/slack_attachments>\\s*$/g, \"\");\n}\n\nfunction normalizeComparableUserText(text: string): string {\n const withoutTimestamp = text.replace(\n /^\\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\\]\\s+(?=\\[[^\\]]+\\](?:\\s+\\[in-thread:[^\\]]+\\])?:\\s)/,\n \"\",\n );\n return stripSlackAttachmentBlock(withoutTimestamp).trim();\n}\n\nfunction getCurrentSessionPath(sessionDir: string): string | null {\n const pointerFile = join(sessionDir, \"current\");\n if (!existsSync(pointerFile)) return null;\n const filename = readFileSync(pointerFile, \"utf-8\").trim();\n if (!filename) return null;\n return join(sessionDir, filename);\n}\n\n/**\n * Try to resolve an existing current session file.\n * Returns null if no current pointer exists or the pointed file has no valid session header.\n */\nexport function tryResolveCurrentSession(sessionDir: string): string | null {\n const fullPath = getCurrentSessionPath(sessionDir);\n if (fullPath && existsSync(fullPath) && hasSessionHeader(fullPath)) return fullPath;\n return null;\n}\n\n/**\n * Try to resolve an existing thread session file.\n * Returns the file path if found, or null if no valid thread session exists yet.\n */\nexport function tryResolveThreadSession(sessionFile: string): string | null {\n return existsSync(sessionFile) && hasSessionHeader(sessionFile) ? sessionFile : null;\n}\n\n/**\n * Resolve the channel's current session file path (for fork source).\n * Returns null if no channel session exists.\n */\nexport function resolveChannelSessionFile(channelDir: string): string | null {\n return tryResolveCurrentSession(getChannelSessionDir(channelDir));\n}\n\n/**\n * Fork a channel session into a fixed thread-session path.\n * The resulting file keeps forkFrom's distinct session/header metadata.\n */\nexport function forkThreadSessionFile(\n sourceSessionFile: string,\n targetSessionFile: string,\n cwd: string,\n): string {\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n const forked = SessionManager.forkFrom(sourceSessionFile, cwd, sessionDir);\n const forkedFile = forked.getSessionFile();\n if (!forkedFile) {\n throw new Error(`Failed to fork session from ${sourceSessionFile}`);\n }\n rmSync(targetSessionFile, { force: true });\n renameSync(forkedFile, targetSessionFile);\n return targetSessionFile;\n}\n\nexport function createThreadSessionFileFromRootMessage(\n targetSessionFile: string,\n cwd: string,\n rootMessage: ThreadRootMessage,\n parentSession?: string,\n): string {\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n rmSync(targetSessionFile, { force: true });\n\n const header = {\n type: \"session\",\n version: 3,\n id: randomUUID(),\n timestamp: new Date().toISOString(),\n cwd,\n ...(parentSession ? { parentSession } : {}),\n };\n const rootText = buildComparableRootMessageText(rootMessage);\n if (!rootText) {\n atomicWritePrivateFile(targetSessionFile, `${JSON.stringify(header)}\\n`);\n return targetSessionFile;\n }\n\n const rootEntry = {\n type: \"message\",\n id: randomUUID().slice(0, 8),\n parentId: null,\n timestamp: new Date().toISOString(),\n message: {\n role: \"user\",\n content: [{ type: \"text\", text: rootText }],\n ...(rootMessage.loggedAt !== undefined ? { timestamp: rootMessage.loggedAt } : {}),\n },\n };\n const content = [header, rootEntry].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(targetSessionFile, `${content}\\n`);\n return targetSessionFile;\n}\n\nexport function forkThreadSessionFileFromRootMessage(\n sourceSessionFile: string,\n targetSessionFile: string,\n cwd: string,\n rootMessage: ThreadRootMessage,\n): string {\n const snapshotEntries = resolveThreadSnapshotEntries(sourceSessionFile, rootMessage);\n if (!snapshotEntries) {\n throw new ThreadRootNotFoundError(sourceSessionFile);\n }\n\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n rmSync(targetSessionFile, { force: true });\n\n const header = {\n type: \"session\",\n version: 3,\n id: randomUUID(),\n timestamp: new Date().toISOString(),\n cwd,\n parentSession: sourceSessionFile,\n };\n const content = [header, ...snapshotEntries].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(targetSessionFile, `${content}\\n`);\n return targetSessionFile;\n}\n"]}
1
+ {"version":3,"file":"session-store.js","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAExD,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAChD,YAAY,WAAmB;QAC7B,KAAK,CAAC,oDAAoD,WAAW,EAAE,CAAC,CAAC;QACzE,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAgCD;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB;IACrD,OAAO,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAkB;IACnD,MAAM,QAAQ,GAAG,wBAAwB,CAAC,UAAU,CAAC,CAAC;IACtD,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,OAAO,oBAAoB,CAAC,UAAU,CAAC,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,UAAkB,EAAE,GAAW;IACvE,MAAM,YAAY,GAAG,qBAAqB,CAAC,UAAU,CAAC,CAAC;IACvD,IAAI,YAAY;QAAE,OAAO,YAAY,CAAC;IACtC,OAAO,wBAAwB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;AACnD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAAmB;IACpD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,WAAW,CAAC;IACzD,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC;AAC7D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB;IACrD,OAAO,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAC,CAAC,CAAC,UAAU,CAAC;AAC9E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB;IACrD,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,GAAG,SAAS,IAAI,IAAI,QAAQ,CAAC;IAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC5C,sBAAsB,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACrC,sBAAsB,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC9D,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,UAAkB,EAAE,GAAW;IACtE,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACjE,MAAM,SAAS,GAAG,UAAU,EAAE,CAAC;IAC/B,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,GAAG,SAAS,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;IACpF,kBAAkB,CAAC,WAAW,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;IAChD,iBAAiB,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAC3C,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,WAAmB,EACnB,UAAkB,EAClB,GAAW;IAEX,IAAI,mCAAmC,CAAC,WAAW,CAAC,EAAE,CAAC;QACrD,MAAM,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,kBAAkB,GAAG,cAE1B,CAAC;IACF,OAAO,IAAI,kBAAkB,CAAC,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;AACpE,CAAC;AAED,SAAS,iBAAiB,CAAC,UAAkB,EAAE,eAAuB;IACpE,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAC;IACnD,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,sBAAsB,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,QAAQ,CAAC,CAAC;AAChE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,8BAA8B,CAAC,WAAmB,EAAE,GAAW;IAC7E,kBAAkB,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;IACrC,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,SAAS,kBAAkB,CAAC,WAAmB,EAAE,GAAW,EAAE,SAAS,GAAG,UAAU,EAAE;IACpF,MAAM,UAAU,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IAC3C,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG;QACb,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,CAAC;QACV,EAAE,EAAE,SAAS;QACb,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,GAAG;KACJ,CAAC;IACF,sBAAsB,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACrE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB,EAAE,UAAkB;IACzE,OAAO,IAAI,CAAC,oBAAoB,CAAC,UAAU,CAAC,EAAE,GAAG,oBAAoB,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;AAC7F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CACxC,OAA0C;IAE1C,MAAM,EAAE,eAAe,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IAChD,MAAM,UAAU,GAAG,oBAAoB,CAAC,eAAe,CAAC,CAAC;IAEzD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,OAAO;YACL,UAAU;YACV,WAAW,EAAE,yBAAyB,CAAC,UAAU,EAAE,eAAe,CAAC;YACnE,iBAAiB,EAAE,IAAI;SACxB,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,oBAAoB,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;IACrE,OAAO;QACL,UAAU;QACV,WAAW,EACT,uBAAuB,CAAC,UAAU,CAAC;YACnC,8BAA8B,CAAC,UAAU,EAAE,eAAe,CAAC;QAC7D,iBAAiB,EAAE,IAAI;KACxB,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,WAAmB;IAC3C,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,OAAO;gBAAE,SAAS;YACvB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAsB,CAAC;YACvD,OAAO,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC;QAClC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,mCAAmC,CAAC,WAAmB;IAC9D,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,KAAK,CAAC;IAE3C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC;aAC/C,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;aAC1B,MAAM,CAAC,OAAO,CAAC;aACf,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAsB,CAAC,CAAC;QAExD,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,SAAS,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,WAAmB;IACrC,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC,EAAE,WAAW,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,4BAA4B,CACnC,iBAAyB,EACzB,WAA8B;IAE9B,MAAM,UAAU,GAAG,8BAA8B,CAAC,WAAW,CAAC,CAAC;IAC/D,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAE7B,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,UAAU,EAA+B,CAAC;IACjG,MAAM,UAAU,GAAG,oBAAoB,CAAC,OAAO,EAAE,UAAU,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IACnF,IAAI,UAAU,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnC,MAAM,qBAAqB,GAAG,OAAO,CAAC,SAAS,CAC7C,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,UAAU,IAAI,kBAAkB,CAAC,KAAK,CAAC,CAClE,CAAC;IACF,MAAM,QAAQ,GAAG,qBAAqB,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,qBAAqB,CAAC;IACvF,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,oBAAoB,CAC3B,OAAkC,EAClC,UAAkB,EAClB,QAAiB;IAEjB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC;YAAE,SAAS;QAEzC,MAAM,cAAc,GAAG,2BAA2B,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1E,IAAI,cAAc,KAAK,UAAU;YAAE,SAAS;QAE5C,MAAM,gBAAgB,GAAG,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC;QAClD,IACE,QAAQ,KAAK,SAAS;YACtB,OAAO,gBAAgB,KAAK,QAAQ;YACpC,gBAAgB,GAAG,QAAQ,EAC3B,CAAC;YACD,SAAS;QACX,CAAC;QAED,OAAO,CAAC,CAAC;IACX,CAAC;IAED,OAAO,CAAC,CAAC,CAAC;AACZ,CAAC;AAED,SAAS,kBAAkB,CAAC,KAA8B;IACxD,OAAO,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,KAAK,MAAM,CAAC;AACpE,CAAC;AAED,SAAS,cAAc,CAAC,KAA8B;IACpD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC;IACvC,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC;IAEvC,OAAO,OAAO;SACX,MAAM,CAAC,CAAC,IAAI,EAA4C,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC;SAChF,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;SAC9B,IAAI,CAAC,MAAM,CAAC,CAAC;AAClB,CAAC;AAED,SAAS,8BAA8B,CAAC,WAA8B;IACpE,MAAM,SAAS,GAAG,WAAW,CAAC,QAAQ,IAAI,WAAW,CAAC,IAAI,IAAI,SAAS,CAAC;IACxE,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;IACtC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,OAAO,2BAA2B,CAAC,IAAI,SAAS,MAAM,IAAI,EAAE,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,yBAAyB,CAAC,IAAY;IAC7C,OAAO,IAAI,CAAC,OAAO,CAAC,8DAA8D,EAAE,EAAE,CAAC,CAAC;AAC1F,CAAC;AAED,SAAS,2BAA2B,CAAC,IAAY;IAC/C,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,CACnC,iIAAiI,EACjI,EAAE,CACH,CAAC;IACF,OAAO,yBAAyB,CAAC,gBAAgB,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5D,CAAC;AAED,SAAS,qBAAqB,CAAC,UAAkB;IAC/C,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAChD,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,QAAQ,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3D,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,OAAO,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,UAAkB;IACzD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,UAAU,CAAC,CAAC;IACnD,IAAI,QAAQ,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IACpF,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,WAAmB;IACzD,OAAO,UAAU,CAAC,WAAW,CAAC,IAAI,gBAAgB,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC;AACvF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,UAAkB;IAC1D,OAAO,wBAAwB,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CACnC,iBAAyB,EACzB,iBAAyB,EACzB,GAAW;IAEX,MAAM,UAAU,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IACjD,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC;IAC3E,MAAM,UAAU,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;IAC3C,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,+BAA+B,iBAAiB,EAAE,CAAC,CAAC;IACtE,CAAC;IACD,MAAM,CAAC,iBAAiB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,UAAU,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC;IAC1C,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,sCAAsC,CACpD,iBAAyB,EACzB,GAAW,EACX,WAA8B,EAC9B,aAAsB;IAEtB,MAAM,UAAU,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IACjD,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,CAAC,iBAAiB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAE3C,MAAM,MAAM,GAAG;QACb,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,CAAC;QACV,EAAE,EAAE,UAAU,EAAE;QAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,GAAG;QACH,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5C,CAAC;IACF,MAAM,QAAQ,GAAG,8BAA8B,CAAC,WAAW,CAAC,CAAC;IAC7D,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,sBAAsB,CAAC,iBAAiB,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzE,OAAO,iBAAiB,CAAC;IAC3B,CAAC;IAED,MAAM,SAAS,GAAG;QAChB,IAAI,EAAE,SAAS;QACf,EAAE,EAAE,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;QAC5B,QAAQ,EAAE,IAAI;QACd,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,OAAO,EAAE;YACP,IAAI,EAAE,MAAM;YACZ,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;YAC3C,GAAG,CAAC,WAAW,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACnF;KACF,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrF,sBAAsB,CAAC,iBAAiB,EAAE,GAAG,OAAO,IAAI,CAAC,CAAC;IAC1D,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,oCAAoC,CAClD,iBAAyB,EACzB,iBAAyB,EACzB,GAAW,EACX,WAA8B;IAE9B,MAAM,eAAe,GAAG,4BAA4B,CAAC,iBAAiB,EAAE,WAAW,CAAC,CAAC;IACrF,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,MAAM,IAAI,uBAAuB,CAAC,iBAAiB,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,UAAU,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IACjD,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,CAAC,iBAAiB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAE3C,MAAM,MAAM,GAAG;QACb,IAAI,EAAE,SAAS;QACf,OAAO,EAAE,CAAC;QACV,EAAE,EAAE,UAAU,EAAE;QAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,GAAG;QACH,aAAa,EAAE,iBAAiB;KACjC,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,GAAG,eAAe,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9F,sBAAsB,CAAC,iBAAiB,EAAE,GAAG,OAAO,IAAI,CAAC,CAAC;IAC1D,OAAO,iBAAiB,CAAC;AAC3B,CAAC","sourcesContent":["import { randomUUID } from \"crypto\";\nimport { existsSync, mkdirSync, readFileSync, renameSync, rmSync } from \"fs\";\nimport { join } from \"path\";\nimport { SessionManager } from \"@earendil-works/pi-coding-agent\";\nimport { atomicWritePrivateFile } from \"./fs-atomic.js\";\n\nexport class ThreadRootNotFoundError extends Error {\n constructor(sessionFile: string) {\n super(`Thread root message not found in source session: ${sessionFile}`);\n this.name = \"ThreadRootNotFoundError\";\n }\n}\n\nexport interface ThreadRootMessage {\n text?: string;\n userName?: string;\n user?: string;\n loggedAt?: number;\n}\n\nexport interface ResolvedSessionScope {\n sessionDir: string;\n contextFile: string;\n threadRootMessage: ThreadRootMessage | null;\n}\n\nexport interface ResolveGenericSessionScopeOptions {\n conversationDir: string;\n sessionKey: string;\n}\n\ninterface SessionMessageEntryLike {\n type: string;\n id: string;\n parentId: string | null;\n timestamp: string;\n message?: {\n role?: string;\n timestamp?: number;\n content?: Array<{ type?: string; text?: string }> | string;\n };\n}\n\n/**\n * Returns the shared session directory for a conversation.\n * Channel sessions use a current pointer within this directory.\n * Thread sessions are stored as fixed files within the same directory.\n */\nexport function getChannelSessionDir(channelDir: string): string {\n return join(channelDir, \"sessions\");\n}\n\n/**\n * Resolves the current active session file for a session directory.\n * Reads the \"current\" pointer file; creates a new session if none exists\n * or the pointed-to file is missing.\n */\nexport function resolveSessionFile(sessionDir: string): string {\n const existing = tryResolveCurrentSession(sessionDir);\n if (existing) return existing;\n return createNewSessionFile(sessionDir);\n}\n\n/**\n * Resolve the current active session file for a session directory.\n * Creates a fully initialized persistent session with the provided cwd when none exists.\n */\nexport function resolveManagedSessionFile(sessionDir: string, cwd: string): string {\n const existingPath = getCurrentSessionPath(sessionDir);\n if (existingPath) return existingPath;\n return createManagedSessionFile(sessionDir, cwd);\n}\n\n/**\n * Extracts the short UUID from a session file path.\n * e.g. \"2026-04-05T00-00_7b54cf90.jsonl\" → \"7b54cf90\"\n */\nexport function extractSessionUuid(sessionFile: string): string {\n const base = sessionFile.split(\"/\").pop() ?? sessionFile;\n return base.replace(\".jsonl\", \"\").split(\"_\").pop() ?? base;\n}\n\n/**\n * Extracts the thread/suffix part of a session key.\n * \"channelId:threadId\" → \"threadId\", \"channelId\" → \"channelId\"\n */\nexport function extractSessionSuffix(sessionKey: string): string {\n return sessionKey.includes(\":\") ? sessionKey.split(\":\").pop()! : sessionKey;\n}\n\n/**\n * Creates an empty timestamped file and updates the \"current\" pointer.\n * Used only by tests for placeholder-file scenarios.\n *\n * Order matters: write the session file first, then atomic-rename the pointer\n * last so a crash mid-create never leaves \"current\" pointing at a missing file.\n */\nexport function createNewSessionFile(sessionDir: string): string {\n mkdirSync(sessionDir, { recursive: true });\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const uuid = randomUUID().slice(0, 8);\n const filename = `${timestamp}_${uuid}.jsonl`;\n const filePath = join(sessionDir, filename);\n atomicWritePrivateFile(filePath, \"\");\n atomicWritePrivateFile(join(sessionDir, \"current\"), filename);\n return filePath;\n}\n\n/**\n * Creates a new persistent session file with a proper SessionManager header and cwd.\n * Also updates the \"current\" pointer. Header is written before the pointer flips so a\n * partial create cannot leave \"current\" pointing at a missing file.\n */\nexport function createManagedSessionFile(sessionDir: string, cwd: string): string {\n mkdirSync(sessionDir, { recursive: true });\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const sessionId = randomUUID();\n const sessionFile = join(sessionDir, `${timestamp}_${sessionId.slice(0, 8)}.jsonl`);\n writeSessionHeader(sessionFile, cwd, sessionId);\n setCurrentPointer(sessionDir, sessionFile);\n return sessionFile;\n}\n\n/**\n * Open a session file with an explicit cwd, even if the file does not exist yet.\n * This avoids SessionManager.open() falling back to process.cwd() for fresh sessions.\n */\nexport function openManagedSession(\n sessionFile: string,\n sessionDir: string,\n cwd: string,\n): SessionManager {\n if (shouldRecreatePreinitializedSession(sessionFile)) {\n rmSync(sessionFile, { force: true });\n }\n\n const SessionManagerCtor = SessionManager as unknown as {\n new (cwd: string, sessionDir: string, sessionFile: string, persist: boolean): SessionManager;\n };\n return new SessionManagerCtor(cwd, sessionDir, sessionFile, true);\n}\n\nfunction setCurrentPointer(sessionDir: string, sessionFilePath: string): void {\n const filename = sessionFilePath.split(\"/\").pop()!;\n mkdirSync(sessionDir, { recursive: true });\n atomicWritePrivateFile(join(sessionDir, \"current\"), filename);\n}\n\n/**\n * Creates or overwrites a fixed-path session file with a valid session header.\n */\nexport function createManagedSessionFileAtPath(sessionFile: string, cwd: string): string {\n writeSessionHeader(sessionFile, cwd);\n return sessionFile;\n}\n\nfunction writeSessionHeader(sessionFile: string, cwd: string, sessionId = randomUUID()): void {\n const sessionDir = getFileDir(sessionFile);\n mkdirSync(sessionDir, { recursive: true });\n const header = {\n type: \"session\",\n version: 3,\n id: sessionId,\n timestamp: new Date().toISOString(),\n cwd,\n };\n atomicWritePrivateFile(sessionFile, `${JSON.stringify(header)}\\n`);\n}\n\n/**\n * Returns the fixed session file path for a Slack thread.\n */\nexport function getThreadSessionFile(channelDir: string, sessionKey: string): string {\n return join(getChannelSessionDir(channelDir), `${extractSessionSuffix(sessionKey)}.jsonl`);\n}\n\n/**\n * Resolve the default session scope for platforms without Slack-style branch forking.\n * Top-level/private sessions use the conversation's current pointer. Threaded or\n * per-message sessions use a fixed file derived from the session key suffix.\n */\nexport function resolveGenericSessionScope(\n options: ResolveGenericSessionScopeOptions,\n): ResolvedSessionScope {\n const { conversationDir, sessionKey } = options;\n const sessionDir = getChannelSessionDir(conversationDir);\n\n if (!sessionKey.includes(\":\")) {\n return {\n sessionDir,\n contextFile: resolveManagedSessionFile(sessionDir, conversationDir),\n threadRootMessage: null,\n };\n }\n\n const threadFile = getThreadSessionFile(conversationDir, sessionKey);\n return {\n sessionDir,\n contextFile:\n tryResolveThreadSession(threadFile) ??\n createManagedSessionFileAtPath(threadFile, conversationDir),\n threadRootMessage: null,\n };\n}\n\nfunction hasSessionHeader(sessionFile: string): boolean {\n try {\n const lines = readFileSync(sessionFile, \"utf-8\").split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n const entry = JSON.parse(trimmed) as { type?: string };\n return entry.type === \"session\";\n }\n } catch {\n return false;\n }\n return false;\n}\n\nfunction shouldRecreatePreinitializedSession(sessionFile: string): boolean {\n if (!existsSync(sessionFile)) return false;\n\n try {\n const entries = readFileSync(sessionFile, \"utf-8\")\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter(Boolean)\n .map((line) => JSON.parse(line) as { type?: string });\n\n return entries.length === 1 && entries[0]?.type === \"session\";\n } catch {\n return false;\n }\n}\n\nfunction getFileDir(sessionFile: string): string {\n return sessionFile.substring(0, sessionFile.lastIndexOf(\"/\"));\n}\n\nfunction resolveThreadSnapshotEntries(\n sourceSessionFile: string,\n rootMessage: ThreadRootMessage,\n): SessionMessageEntryLike[] | null {\n const targetText = buildComparableRootMessageText(rootMessage);\n if (!targetText) return null;\n\n const entries = SessionManager.open(sourceSessionFile).getEntries() as SessionMessageEntryLike[];\n const matchIndex = findRootMessageIndex(entries, targetText, rootMessage.loggedAt);\n if (matchIndex === -1) return null;\n\n const nextTopLevelUserIndex = entries.findIndex(\n (entry, index) => index > matchIndex && isUserMessageEntry(entry),\n );\n const endIndex = nextTopLevelUserIndex === -1 ? entries.length : nextTopLevelUserIndex;\n return entries.slice(0, endIndex);\n}\n\nfunction findRootMessageIndex(\n entries: SessionMessageEntryLike[],\n targetText: string,\n loggedAt?: number,\n): number {\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n if (!isUserMessageEntry(entry)) continue;\n\n const comparableText = normalizeComparableUserText(getMessageText(entry));\n if (comparableText !== targetText) continue;\n\n const messageTimestamp = entry.message?.timestamp;\n if (\n loggedAt !== undefined &&\n typeof messageTimestamp === \"number\" &&\n messageTimestamp < loggedAt\n ) {\n continue;\n }\n\n return i;\n }\n\n return -1;\n}\n\nfunction isUserMessageEntry(entry: SessionMessageEntryLike): boolean {\n return entry.type === \"message\" && entry.message?.role === \"user\";\n}\n\nfunction getMessageText(entry: SessionMessageEntryLike): string {\n const content = entry.message?.content;\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n return content\n .filter((part): part is { type?: string; text?: string } => part.type === \"text\")\n .map((part) => part.text ?? \"\")\n .join(\"\\n\\n\");\n}\n\nfunction buildComparableRootMessageText(rootMessage: ThreadRootMessage): string | null {\n const userLabel = rootMessage.userName || rootMessage.user || \"unknown\";\n const text = rootMessage.text?.trim();\n if (!text) return null;\n return normalizeComparableUserText(`[${userLabel}]: ${text}`);\n}\n\nfunction stripSlackAttachmentBlock(text: string): string {\n return text.replace(/\\n*<slack_attachments>\\n[\\s\\S]*?\\n<\\/slack_attachments>\\s*$/g, \"\");\n}\n\nfunction normalizeComparableUserText(text: string): string {\n const withoutTimestamp = text.replace(\n /^\\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\\]\\s+(?=\\[[^\\]]+\\](?:\\s+\\[in-thread:[^\\]]+\\])?:\\s)/,\n \"\",\n );\n return stripSlackAttachmentBlock(withoutTimestamp).trim();\n}\n\nfunction getCurrentSessionPath(sessionDir: string): string | null {\n const pointerFile = join(sessionDir, \"current\");\n if (!existsSync(pointerFile)) return null;\n const filename = readFileSync(pointerFile, \"utf-8\").trim();\n if (!filename) return null;\n return join(sessionDir, filename);\n}\n\n/**\n * Try to resolve an existing current session file.\n * Returns null if no current pointer exists or the pointed file has no valid session header.\n */\nexport function tryResolveCurrentSession(sessionDir: string): string | null {\n const fullPath = getCurrentSessionPath(sessionDir);\n if (fullPath && existsSync(fullPath) && hasSessionHeader(fullPath)) return fullPath;\n return null;\n}\n\n/**\n * Try to resolve an existing thread session file.\n * Returns the file path if found, or null if no valid thread session exists yet.\n */\nexport function tryResolveThreadSession(sessionFile: string): string | null {\n return existsSync(sessionFile) && hasSessionHeader(sessionFile) ? sessionFile : null;\n}\n\n/**\n * Resolve the channel's current session file path (for fork source).\n * Returns null if no channel session exists.\n */\nexport function resolveChannelSessionFile(channelDir: string): string | null {\n return tryResolveCurrentSession(getChannelSessionDir(channelDir));\n}\n\n/**\n * Fork a channel session into a fixed thread-session path.\n * The resulting file keeps forkFrom's distinct session/header metadata.\n */\nexport function forkThreadSessionFile(\n sourceSessionFile: string,\n targetSessionFile: string,\n cwd: string,\n): string {\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n const forked = SessionManager.forkFrom(sourceSessionFile, cwd, sessionDir);\n const forkedFile = forked.getSessionFile();\n if (!forkedFile) {\n throw new Error(`Failed to fork session from ${sourceSessionFile}`);\n }\n rmSync(targetSessionFile, { force: true });\n renameSync(forkedFile, targetSessionFile);\n return targetSessionFile;\n}\n\nexport function createThreadSessionFileFromRootMessage(\n targetSessionFile: string,\n cwd: string,\n rootMessage: ThreadRootMessage,\n parentSession?: string,\n): string {\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n rmSync(targetSessionFile, { force: true });\n\n const header = {\n type: \"session\",\n version: 3,\n id: randomUUID(),\n timestamp: new Date().toISOString(),\n cwd,\n ...(parentSession ? { parentSession } : {}),\n };\n const rootText = buildComparableRootMessageText(rootMessage);\n if (!rootText) {\n atomicWritePrivateFile(targetSessionFile, `${JSON.stringify(header)}\\n`);\n return targetSessionFile;\n }\n\n const rootEntry = {\n type: \"message\",\n id: randomUUID().slice(0, 8),\n parentId: null,\n timestamp: new Date().toISOString(),\n message: {\n role: \"user\",\n content: [{ type: \"text\", text: rootText }],\n ...(rootMessage.loggedAt !== undefined ? { timestamp: rootMessage.loggedAt } : {}),\n },\n };\n const content = [header, rootEntry].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(targetSessionFile, `${content}\\n`);\n return targetSessionFile;\n}\n\nexport function forkThreadSessionFileFromRootMessage(\n sourceSessionFile: string,\n targetSessionFile: string,\n cwd: string,\n rootMessage: ThreadRootMessage,\n): string {\n const snapshotEntries = resolveThreadSnapshotEntries(sourceSessionFile, rootMessage);\n if (!snapshotEntries) {\n throw new ThreadRootNotFoundError(sourceSessionFile);\n }\n\n const sessionDir = getFileDir(targetSessionFile);\n mkdirSync(sessionDir, { recursive: true });\n rmSync(targetSessionFile, { force: true });\n\n const header = {\n type: \"session\",\n version: 3,\n id: randomUUID(),\n timestamp: new Date().toISOString(),\n cwd,\n parentSession: sourceSessionFile,\n };\n const content = [header, ...snapshotEntries].map((entry) => JSON.stringify(entry)).join(\"\\n\");\n atomicWritePrivateFile(targetSessionFile, `${content}\\n`);\n return targetSessionFile;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/session-view/service.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,KAAK,GAAG,OAAO,CAAC;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,mBAAmB,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,KAAK,EAAE,mBAAmB,EAAE,CAAC;CAC9B;AAED,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GACjB,MAAM,GAAG,IAAI,CAMf;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,CAkD1E;AAED,wBAAgB,2BAA2B,CACzC,eAAe,EAAE,MAAM,EACvB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,GAChC,MAAM,GAAG,IAAI,CA2Bf","sourcesContent":["import { basename, dirname, join, resolve } from \"path\";\nimport { existsSync, readdirSync } from \"fs\";\nimport {\n SessionManager,\n type BranchSummaryEntry,\n type CompactionEntry,\n type SessionEntry,\n type SessionMessageEntry,\n} from \"@mariozechner/pi-coding-agent\";\nimport {\n getThreadSessionFile,\n resolveChannelSessionFile,\n tryResolveThreadSession,\n} from \"../session-store.js\";\nimport * as log from \"../log.js\";\n\nexport interface SessionViewItem {\n kind: \"user\" | \"assistant\" | \"tool\" | \"system\";\n title: string;\n body?: string;\n meta?: string;\n tone?: \"default\" | \"ok\" | \"err\" | \"muted\";\n entryId?: string;\n forks?: SessionViewRelation[];\n}\n\nexport interface SessionViewRelation {\n kind: \"parent\" | \"fork\";\n fileName: string;\n sessionId: string;\n title: string;\n updatedAt: string;\n entryCount: number;\n summary?: string;\n anchorEntryId?: string;\n}\n\nexport interface SessionViewModel {\n sessionId: string;\n fileName: string;\n title: string;\n createdAt: string;\n updatedAt: string;\n entryCount: number;\n items: SessionViewItem[];\n parent?: SessionViewRelation;\n forks: SessionViewRelation[];\n}\n\nexport function resolveExistingSessionFile(\n workingDir: string,\n conversationId: string,\n sessionKey: string,\n): string | null {\n const conversationDir = join(workingDir, conversationId);\n if (sessionKey.includes(\":\")) {\n return tryResolveThreadSession(getThreadSessionFile(conversationDir, sessionKey));\n }\n return resolveChannelSessionFile(conversationDir);\n}\n\nexport function loadSessionViewModel(sessionFile: string): SessionViewModel {\n const resolvedFile = resolve(sessionFile);\n const sm = SessionManager.open(resolvedFile);\n const header = sm.getHeader();\n if (!header) throw new Error(`No valid session found: ${sessionFile}`);\n\n const entries = sm.getEntries();\n const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;\n const title = sm.getSessionName() || `Session ${header.id.slice(0, 8)}`;\n\n const parent = header.parentSession\n ? buildSessionRelation(resolve(header.parentSession), \"parent\")\n : undefined;\n const forks = listRelatedSessionFiles(resolvedFile)\n .filter((candidate) => candidate !== resolvedFile)\n .map((candidate) => buildSessionRelation(candidate, \"fork\", resolvedFile))\n .filter((relation): relation is SessionViewRelation => relation !== null)\n .sort((a, b) => (a.updatedAt < b.updatedAt ? -1 : a.updatedAt > b.updatedAt ? 1 : 0));\n\n const forksByEntryId = new Map<string, SessionViewRelation[]>();\n for (const fork of forks) {\n if (!fork.anchorEntryId) continue;\n const bucket = forksByEntryId.get(fork.anchorEntryId) ?? [];\n bucket.push(fork);\n forksByEntryId.set(fork.anchorEntryId, bucket);\n }\n\n const items = entries.flatMap((entry) => {\n const item = mapEntryToItem(entry);\n if (!item) return [];\n if (item.entryId) {\n const anchoredForks = forksByEntryId.get(item.entryId);\n if (anchoredForks) {\n item.forks = anchoredForks;\n }\n }\n return [item];\n });\n\n return {\n sessionId: header.id,\n fileName: basename(resolvedFile),\n title,\n createdAt: header.timestamp,\n updatedAt,\n entryCount: entries.length,\n items,\n parent: parent ?? undefined,\n forks,\n };\n}\n\nexport function resolveRequestedSessionFile(\n baseSessionFile: string,\n requestedFileName?: string | null,\n): string | null {\n const resolvedBase = resolve(baseSessionFile);\n if (!requestedFileName) return resolvedBase;\n\n const trimmed = requestedFileName.trim();\n if (!trimmed) return resolvedBase;\n\n const fileName = basename(trimmed);\n if (fileName !== trimmed || !fileName.endsWith(\".jsonl\")) return null;\n\n const candidate = join(dirname(resolvedBase), fileName);\n if (!existsSync(candidate)) return null;\n\n let sm: SessionManager;\n try {\n sm = SessionManager.open(candidate);\n } catch (err) {\n throw new Error(\n `Session file is corrupted: ${candidate}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n if (!sm.getHeader()) {\n throw new Error(`Session file is missing a valid header: ${candidate}`);\n }\n return candidate;\n}\n\nfunction listRelatedSessionFiles(sessionFile: string): string[] {\n const dir = dirname(sessionFile);\n if (!existsSync(dir)) return [];\n\n return readdirSync(dir)\n .filter((name) => name.endsWith(\".jsonl\"))\n .map((fileName) => join(dir, fileName));\n}\n\nfunction buildSessionRelation(\n sessionFile: string,\n kind: \"parent\" | \"fork\",\n expectedParent?: string,\n): SessionViewRelation | null {\n let sm: SessionManager;\n try {\n sm = SessionManager.open(sessionFile);\n } catch (err) {\n log.logWarning(\n `Skipping corrupted session file while building ${kind} relation: ${sessionFile}`,\n err instanceof Error ? err.message : String(err),\n );\n return null;\n }\n const header = sm.getHeader();\n if (!header) {\n log.logWarning(\n `Skipping session file with missing header while building ${kind} relation: ${sessionFile}`,\n );\n return null;\n }\n if (kind === \"fork\" && resolve(header.parentSession ?? \"\") !== expectedParent) {\n return null;\n }\n\n const entries = sm.getEntries();\n const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;\n const anchorEntryId =\n kind === \"fork\" && expectedParent\n ? findForkAnchorEntryId(SessionManager.open(expectedParent).getEntries(), entries)\n : undefined;\n return {\n kind,\n fileName: basename(sessionFile),\n sessionId: header.id,\n title: sm.getSessionName() || `Session ${header.id.slice(0, 8)}`,\n updatedAt,\n entryCount: entries.length,\n summary: extractSessionSummary(entries),\n anchorEntryId,\n };\n}\n\nfunction findForkAnchorEntryId(\n parentEntries: SessionEntry[],\n childEntries: SessionEntry[],\n): string | undefined {\n let sharedCount = 0;\n while (\n sharedCount < parentEntries.length &&\n sharedCount < childEntries.length &&\n parentEntries[sharedCount]?.id === childEntries[sharedCount]?.id\n ) {\n sharedCount += 1;\n }\n\n if (sharedCount > 0) {\n return parentEntries[sharedCount - 1]?.id;\n }\n\n const childRoot = findComparableUserMessage(childEntries);\n if (!childRoot) return undefined;\n\n return findParentAnchorByRootMessage(parentEntries, childRoot);\n}\n\nfunction findParentAnchorByRootMessage(\n parentEntries: SessionEntry[],\n childRoot: ComparableUserMessage,\n): string | undefined {\n let textMatchId: string | undefined;\n\n for (const entry of parentEntries) {\n const comparable = getComparableUserMessage(entry);\n if (!comparable) continue;\n if (comparable.normalizedText !== childRoot.normalizedText) continue;\n if (\n childRoot.messageTimestamp !== undefined &&\n comparable.messageTimestamp !== undefined &&\n comparable.messageTimestamp === childRoot.messageTimestamp\n ) {\n return entry.id;\n }\n textMatchId ??= entry.id;\n }\n\n return textMatchId;\n}\n\ninterface ComparableUserMessage {\n normalizedText: string;\n messageTimestamp?: number;\n}\n\nfunction findComparableUserMessage(entries: SessionEntry[]): ComparableUserMessage | null {\n for (const entry of entries) {\n const comparable = getComparableUserMessage(entry);\n if (comparable) return comparable;\n }\n return null;\n}\n\nfunction getComparableUserMessage(entry: SessionEntry): ComparableUserMessage | null {\n if (entry.type !== \"message\" || entry.message.role !== \"user\") return null;\n\n const body = contentToText(entry.message.content);\n const normalizedText = normalizeComparableUserText(body);\n if (!normalizedText) return null;\n\n const messageTimestamp =\n typeof entry.message.timestamp === \"number\" ? entry.message.timestamp : undefined;\n return { normalizedText, messageTimestamp };\n}\n\nfunction normalizeComparableUserText(text: string): string {\n const withoutTimestamp = text.replace(\n /^\\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\\]\\s+(?=\\[[^\\]]+\\](?:\\s+\\[in-thread:[^\\]]+\\])?:\\s)/,\n \"\",\n );\n return stripSlackAttachmentBlock(withoutTimestamp).trim();\n}\n\nfunction stripSlackAttachmentBlock(text: string): string {\n return text.replace(/\\n*<slack_attachments>\\n[\\s\\S]*?\\n<\\/slack_attachments>\\s*$/g, \"\");\n}\n\nfunction extractSessionSummary(entries: SessionEntry[]): string | undefined {\n for (const entry of entries) {\n if (entry.type !== \"message\") continue;\n const item = mapEntryToItem(entry);\n if (!item?.body) continue;\n return collapseSummary(item.body);\n }\n return undefined;\n}\n\nfunction collapseSummary(text: string): string {\n const singleLine = text.replace(/\\s+/g, \" \").trim();\n return singleLine.length > 96 ? `${singleLine.slice(0, 93)}…` : singleLine;\n}\n\nfunction mapEntryToItem(entry: SessionEntry): SessionViewItem | null {\n switch (entry.type) {\n case \"message\":\n return mapMessageEntry(entry);\n case \"model_change\":\n return {\n kind: \"system\",\n title: \"Model changed\",\n body: `${entry.provider} / ${entry.modelId}`,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"thinking_level_change\":\n return {\n kind: \"system\",\n title: \"Thinking level changed\",\n body: entry.thinkingLevel,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"compaction\":\n return mapCompactionEntry(entry);\n case \"branch_summary\":\n return mapBranchSummaryEntry(entry);\n case \"custom_message\":\n return {\n kind: \"system\",\n title: `Custom message · ${entry.customType}`,\n body: contentToText(entry.content),\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"custom\":\n return {\n kind: \"system\",\n title: `Custom data · ${entry.customType}`,\n body: entry.data === undefined ? \"(no data)\" : JSON.stringify(entry.data, null, 2),\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"label\":\n return {\n kind: \"system\",\n title: \"Label updated\",\n body: entry.label || \"(cleared)\",\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"session_info\":\n return entry.name\n ? {\n kind: \"system\",\n title: \"Session renamed\",\n body: entry.name,\n meta: entry.timestamp,\n tone: \"muted\",\n }\n : null;\n default:\n return null;\n }\n}\n\nfunction mapMessageEntry(entry: SessionMessageEntry): SessionViewItem {\n const message = entry.message as unknown as Record<string, unknown> & {\n role?: string;\n content?: unknown;\n provider?: string;\n model?: string;\n toolName?: string;\n isError?: boolean;\n command?: string;\n output?: string;\n exitCode?: number;\n cancelled?: boolean;\n truncated?: boolean;\n stopReason?: string;\n customType?: string;\n summary?: string;\n };\n\n switch (message.role) {\n case \"user\":\n return {\n kind: \"user\",\n title: \"User\",\n body: contentToText(message.content),\n meta: entry.timestamp,\n entryId: entry.id,\n };\n case \"assistant\": {\n const assistantBody = assistantContentToText(message.content);\n const metaParts = [message.provider, message.model, message.stopReason].filter(Boolean);\n return {\n kind: \"assistant\",\n title: \"Assistant\",\n body: assistantBody,\n meta:\n metaParts.length > 0 ? `${entry.timestamp} · ${metaParts.join(\" · \")}` : entry.timestamp,\n entryId: entry.id,\n };\n }\n case \"toolResult\":\n return {\n kind: \"tool\",\n title: `Tool result · ${String(message.toolName ?? \"unknown\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: message.isError ? \"err\" : \"ok\",\n entryId: entry.id,\n };\n case \"bashExecution\": {\n const command = String(message.command ?? \"\").trim();\n const output = String(message.output ?? \"\").trim();\n const details = [\n typeof message.exitCode === \"number\" ? `[exitCode] ${message.exitCode}` : \"\",\n message.cancelled ? `[cancelled] true` : \"\",\n message.truncated ? `[truncated] true` : \"\",\n ].filter(Boolean);\n const body = [command ? `$ ${command}` : \"\", output, ...details].filter(Boolean).join(\"\\n\\n\");\n return {\n kind: \"tool\",\n title: \"Bash execution\",\n body: body || \"(no output)\",\n meta: entry.timestamp,\n entryId: entry.id,\n };\n }\n case \"custom\":\n return {\n kind: \"system\",\n title: `Custom message · ${String(message.customType ?? \"custom\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n case \"branchSummary\":\n return {\n kind: \"system\",\n title: \"Branch summary\",\n body: String(message.summary ?? \"\"),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n case \"compactionSummary\":\n return {\n kind: \"system\",\n title: \"Compaction summary\",\n body: String(message.summary ?? \"\"),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n default:\n return {\n kind: \"system\",\n title: `Message · ${String(message.role ?? \"unknown\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n }\n}\n\nfunction mapCompactionEntry(entry: CompactionEntry): SessionViewItem {\n return {\n kind: \"system\",\n title: \"Context compacted\",\n body: entry.summary,\n meta: `${entry.timestamp} · ${entry.tokensBefore} tokens before compaction`,\n tone: \"muted\",\n };\n}\n\nfunction mapBranchSummaryEntry(entry: BranchSummaryEntry): SessionViewItem {\n return {\n kind: \"system\",\n title: \"Branch summary\",\n body: entry.summary,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n}\n\nfunction assistantContentToText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n const lines: string[] = [];\n\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const value = block as Record<string, unknown>;\n if (value.type === \"text\" && typeof value.text === \"string\") {\n lines.push(value.text);\n continue;\n }\n if (value.type === \"thinking\" && typeof value.thinking === \"string\") {\n lines.push(`[thinking]\\n${value.thinking}`);\n continue;\n }\n if (value.type === \"toolCall\") {\n const name = typeof value.name === \"string\" ? value.name : \"tool\";\n const args = value.arguments === undefined ? \"\" : JSON.stringify(value.arguments, null, 2);\n lines.push([`[toolCall] ${name}`, args].filter(Boolean).join(\"\\n\"));\n continue;\n }\n if (value.type === \"image\") {\n lines.push(`[image ${String(value.mimeType ?? \"unknown\")}]`);\n }\n }\n\n return lines.join(\"\\n\\n\");\n}\n\nfunction contentToText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n const lines: string[] = [];\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const value = block as Record<string, unknown>;\n if (value.type === \"text\" && typeof value.text === \"string\") {\n lines.push(value.text);\n continue;\n }\n if (value.type === \"thinking\" && typeof value.thinking === \"string\") {\n lines.push(`[thinking]\\n${value.thinking}`);\n continue;\n }\n if (value.type === \"toolCall\") {\n const name = typeof value.name === \"string\" ? value.name : \"tool\";\n const args = value.arguments === undefined ? \"\" : JSON.stringify(value.arguments, null, 2);\n lines.push([`[toolCall] ${name}`, args].filter(Boolean).join(\"\\n\"));\n continue;\n }\n if (value.type === \"image\") {\n lines.push(`[image ${String(value.mimeType ?? \"unknown\")}]`);\n }\n }\n\n return lines.join(\"\\n\\n\");\n}\n"]}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/session-view/service.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,KAAK,GAAG,OAAO,CAAC;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,mBAAmB,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,KAAK,EAAE,mBAAmB,EAAE,CAAC;CAC9B;AAED,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GACjB,MAAM,GAAG,IAAI,CAMf;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,CAkD1E;AAED,wBAAgB,2BAA2B,CACzC,eAAe,EAAE,MAAM,EACvB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,GAChC,MAAM,GAAG,IAAI,CA2Bf","sourcesContent":["import { basename, dirname, join, resolve } from \"path\";\nimport { existsSync, readdirSync } from \"fs\";\nimport {\n SessionManager,\n type BranchSummaryEntry,\n type CompactionEntry,\n type SessionEntry,\n type SessionMessageEntry,\n} from \"@earendil-works/pi-coding-agent\";\nimport {\n getThreadSessionFile,\n resolveChannelSessionFile,\n tryResolveThreadSession,\n} from \"../session-store.js\";\nimport * as log from \"../log.js\";\n\nexport interface SessionViewItem {\n kind: \"user\" | \"assistant\" | \"tool\" | \"system\";\n title: string;\n body?: string;\n meta?: string;\n tone?: \"default\" | \"ok\" | \"err\" | \"muted\";\n entryId?: string;\n forks?: SessionViewRelation[];\n}\n\nexport interface SessionViewRelation {\n kind: \"parent\" | \"fork\";\n fileName: string;\n sessionId: string;\n title: string;\n updatedAt: string;\n entryCount: number;\n summary?: string;\n anchorEntryId?: string;\n}\n\nexport interface SessionViewModel {\n sessionId: string;\n fileName: string;\n title: string;\n createdAt: string;\n updatedAt: string;\n entryCount: number;\n items: SessionViewItem[];\n parent?: SessionViewRelation;\n forks: SessionViewRelation[];\n}\n\nexport function resolveExistingSessionFile(\n workingDir: string,\n conversationId: string,\n sessionKey: string,\n): string | null {\n const conversationDir = join(workingDir, conversationId);\n if (sessionKey.includes(\":\")) {\n return tryResolveThreadSession(getThreadSessionFile(conversationDir, sessionKey));\n }\n return resolveChannelSessionFile(conversationDir);\n}\n\nexport function loadSessionViewModel(sessionFile: string): SessionViewModel {\n const resolvedFile = resolve(sessionFile);\n const sm = SessionManager.open(resolvedFile);\n const header = sm.getHeader();\n if (!header) throw new Error(`No valid session found: ${sessionFile}`);\n\n const entries = sm.getEntries();\n const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;\n const title = sm.getSessionName() || `Session ${header.id.slice(0, 8)}`;\n\n const parent = header.parentSession\n ? buildSessionRelation(resolve(header.parentSession), \"parent\")\n : undefined;\n const forks = listRelatedSessionFiles(resolvedFile)\n .filter((candidate) => candidate !== resolvedFile)\n .map((candidate) => buildSessionRelation(candidate, \"fork\", resolvedFile))\n .filter((relation): relation is SessionViewRelation => relation !== null)\n .sort((a, b) => (a.updatedAt < b.updatedAt ? -1 : a.updatedAt > b.updatedAt ? 1 : 0));\n\n const forksByEntryId = new Map<string, SessionViewRelation[]>();\n for (const fork of forks) {\n if (!fork.anchorEntryId) continue;\n const bucket = forksByEntryId.get(fork.anchorEntryId) ?? [];\n bucket.push(fork);\n forksByEntryId.set(fork.anchorEntryId, bucket);\n }\n\n const items = entries.flatMap((entry) => {\n const item = mapEntryToItem(entry);\n if (!item) return [];\n if (item.entryId) {\n const anchoredForks = forksByEntryId.get(item.entryId);\n if (anchoredForks) {\n item.forks = anchoredForks;\n }\n }\n return [item];\n });\n\n return {\n sessionId: header.id,\n fileName: basename(resolvedFile),\n title,\n createdAt: header.timestamp,\n updatedAt,\n entryCount: entries.length,\n items,\n parent: parent ?? undefined,\n forks,\n };\n}\n\nexport function resolveRequestedSessionFile(\n baseSessionFile: string,\n requestedFileName?: string | null,\n): string | null {\n const resolvedBase = resolve(baseSessionFile);\n if (!requestedFileName) return resolvedBase;\n\n const trimmed = requestedFileName.trim();\n if (!trimmed) return resolvedBase;\n\n const fileName = basename(trimmed);\n if (fileName !== trimmed || !fileName.endsWith(\".jsonl\")) return null;\n\n const candidate = join(dirname(resolvedBase), fileName);\n if (!existsSync(candidate)) return null;\n\n let sm: SessionManager;\n try {\n sm = SessionManager.open(candidate);\n } catch (err) {\n throw new Error(\n `Session file is corrupted: ${candidate}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n if (!sm.getHeader()) {\n throw new Error(`Session file is missing a valid header: ${candidate}`);\n }\n return candidate;\n}\n\nfunction listRelatedSessionFiles(sessionFile: string): string[] {\n const dir = dirname(sessionFile);\n if (!existsSync(dir)) return [];\n\n return readdirSync(dir)\n .filter((name) => name.endsWith(\".jsonl\"))\n .map((fileName) => join(dir, fileName));\n}\n\nfunction buildSessionRelation(\n sessionFile: string,\n kind: \"parent\" | \"fork\",\n expectedParent?: string,\n): SessionViewRelation | null {\n let sm: SessionManager;\n try {\n sm = SessionManager.open(sessionFile);\n } catch (err) {\n log.logWarning(\n `Skipping corrupted session file while building ${kind} relation: ${sessionFile}`,\n err instanceof Error ? err.message : String(err),\n );\n return null;\n }\n const header = sm.getHeader();\n if (!header) {\n log.logWarning(\n `Skipping session file with missing header while building ${kind} relation: ${sessionFile}`,\n );\n return null;\n }\n if (kind === \"fork\" && resolve(header.parentSession ?? \"\") !== expectedParent) {\n return null;\n }\n\n const entries = sm.getEntries();\n const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;\n const anchorEntryId =\n kind === \"fork\" && expectedParent\n ? findForkAnchorEntryId(SessionManager.open(expectedParent).getEntries(), entries)\n : undefined;\n return {\n kind,\n fileName: basename(sessionFile),\n sessionId: header.id,\n title: sm.getSessionName() || `Session ${header.id.slice(0, 8)}`,\n updatedAt,\n entryCount: entries.length,\n summary: extractSessionSummary(entries),\n anchorEntryId,\n };\n}\n\nfunction findForkAnchorEntryId(\n parentEntries: SessionEntry[],\n childEntries: SessionEntry[],\n): string | undefined {\n let sharedCount = 0;\n while (\n sharedCount < parentEntries.length &&\n sharedCount < childEntries.length &&\n parentEntries[sharedCount]?.id === childEntries[sharedCount]?.id\n ) {\n sharedCount += 1;\n }\n\n if (sharedCount > 0) {\n return parentEntries[sharedCount - 1]?.id;\n }\n\n const childRoot = findComparableUserMessage(childEntries);\n if (!childRoot) return undefined;\n\n return findParentAnchorByRootMessage(parentEntries, childRoot);\n}\n\nfunction findParentAnchorByRootMessage(\n parentEntries: SessionEntry[],\n childRoot: ComparableUserMessage,\n): string | undefined {\n let textMatchId: string | undefined;\n\n for (const entry of parentEntries) {\n const comparable = getComparableUserMessage(entry);\n if (!comparable) continue;\n if (comparable.normalizedText !== childRoot.normalizedText) continue;\n if (\n childRoot.messageTimestamp !== undefined &&\n comparable.messageTimestamp !== undefined &&\n comparable.messageTimestamp === childRoot.messageTimestamp\n ) {\n return entry.id;\n }\n textMatchId ??= entry.id;\n }\n\n return textMatchId;\n}\n\ninterface ComparableUserMessage {\n normalizedText: string;\n messageTimestamp?: number;\n}\n\nfunction findComparableUserMessage(entries: SessionEntry[]): ComparableUserMessage | null {\n for (const entry of entries) {\n const comparable = getComparableUserMessage(entry);\n if (comparable) return comparable;\n }\n return null;\n}\n\nfunction getComparableUserMessage(entry: SessionEntry): ComparableUserMessage | null {\n if (entry.type !== \"message\" || entry.message.role !== \"user\") return null;\n\n const body = contentToText(entry.message.content);\n const normalizedText = normalizeComparableUserText(body);\n if (!normalizedText) return null;\n\n const messageTimestamp =\n typeof entry.message.timestamp === \"number\" ? entry.message.timestamp : undefined;\n return { normalizedText, messageTimestamp };\n}\n\nfunction normalizeComparableUserText(text: string): string {\n const withoutTimestamp = text.replace(\n /^\\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\\]\\s+(?=\\[[^\\]]+\\](?:\\s+\\[in-thread:[^\\]]+\\])?:\\s)/,\n \"\",\n );\n return stripSlackAttachmentBlock(withoutTimestamp).trim();\n}\n\nfunction stripSlackAttachmentBlock(text: string): string {\n return text.replace(/\\n*<slack_attachments>\\n[\\s\\S]*?\\n<\\/slack_attachments>\\s*$/g, \"\");\n}\n\nfunction extractSessionSummary(entries: SessionEntry[]): string | undefined {\n for (const entry of entries) {\n if (entry.type !== \"message\") continue;\n const item = mapEntryToItem(entry);\n if (!item?.body) continue;\n return collapseSummary(item.body);\n }\n return undefined;\n}\n\nfunction collapseSummary(text: string): string {\n const singleLine = text.replace(/\\s+/g, \" \").trim();\n return singleLine.length > 96 ? `${singleLine.slice(0, 93)}…` : singleLine;\n}\n\nfunction mapEntryToItem(entry: SessionEntry): SessionViewItem | null {\n switch (entry.type) {\n case \"message\":\n return mapMessageEntry(entry);\n case \"model_change\":\n return {\n kind: \"system\",\n title: \"Model changed\",\n body: `${entry.provider} / ${entry.modelId}`,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"thinking_level_change\":\n return {\n kind: \"system\",\n title: \"Thinking level changed\",\n body: entry.thinkingLevel,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"compaction\":\n return mapCompactionEntry(entry);\n case \"branch_summary\":\n return mapBranchSummaryEntry(entry);\n case \"custom_message\":\n return {\n kind: \"system\",\n title: `Custom message · ${entry.customType}`,\n body: contentToText(entry.content),\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"custom\":\n return {\n kind: \"system\",\n title: `Custom data · ${entry.customType}`,\n body: entry.data === undefined ? \"(no data)\" : JSON.stringify(entry.data, null, 2),\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"label\":\n return {\n kind: \"system\",\n title: \"Label updated\",\n body: entry.label || \"(cleared)\",\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"session_info\":\n return entry.name\n ? {\n kind: \"system\",\n title: \"Session renamed\",\n body: entry.name,\n meta: entry.timestamp,\n tone: \"muted\",\n }\n : null;\n default:\n return null;\n }\n}\n\nfunction mapMessageEntry(entry: SessionMessageEntry): SessionViewItem {\n const message = entry.message as unknown as Record<string, unknown> & {\n role?: string;\n content?: unknown;\n provider?: string;\n model?: string;\n toolName?: string;\n isError?: boolean;\n command?: string;\n output?: string;\n exitCode?: number;\n cancelled?: boolean;\n truncated?: boolean;\n stopReason?: string;\n customType?: string;\n summary?: string;\n };\n\n switch (message.role) {\n case \"user\":\n return {\n kind: \"user\",\n title: \"User\",\n body: contentToText(message.content),\n meta: entry.timestamp,\n entryId: entry.id,\n };\n case \"assistant\": {\n const assistantBody = assistantContentToText(message.content);\n const metaParts = [message.provider, message.model, message.stopReason].filter(Boolean);\n return {\n kind: \"assistant\",\n title: \"Assistant\",\n body: assistantBody,\n meta:\n metaParts.length > 0 ? `${entry.timestamp} · ${metaParts.join(\" · \")}` : entry.timestamp,\n entryId: entry.id,\n };\n }\n case \"toolResult\":\n return {\n kind: \"tool\",\n title: `Tool result · ${String(message.toolName ?? \"unknown\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: message.isError ? \"err\" : \"ok\",\n entryId: entry.id,\n };\n case \"bashExecution\": {\n const command = String(message.command ?? \"\").trim();\n const output = String(message.output ?? \"\").trim();\n const details = [\n typeof message.exitCode === \"number\" ? `[exitCode] ${message.exitCode}` : \"\",\n message.cancelled ? `[cancelled] true` : \"\",\n message.truncated ? `[truncated] true` : \"\",\n ].filter(Boolean);\n const body = [command ? `$ ${command}` : \"\", output, ...details].filter(Boolean).join(\"\\n\\n\");\n return {\n kind: \"tool\",\n title: \"Bash execution\",\n body: body || \"(no output)\",\n meta: entry.timestamp,\n entryId: entry.id,\n };\n }\n case \"custom\":\n return {\n kind: \"system\",\n title: `Custom message · ${String(message.customType ?? \"custom\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n case \"branchSummary\":\n return {\n kind: \"system\",\n title: \"Branch summary\",\n body: String(message.summary ?? \"\"),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n case \"compactionSummary\":\n return {\n kind: \"system\",\n title: \"Compaction summary\",\n body: String(message.summary ?? \"\"),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n default:\n return {\n kind: \"system\",\n title: `Message · ${String(message.role ?? \"unknown\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n }\n}\n\nfunction mapCompactionEntry(entry: CompactionEntry): SessionViewItem {\n return {\n kind: \"system\",\n title: \"Context compacted\",\n body: entry.summary,\n meta: `${entry.timestamp} · ${entry.tokensBefore} tokens before compaction`,\n tone: \"muted\",\n };\n}\n\nfunction mapBranchSummaryEntry(entry: BranchSummaryEntry): SessionViewItem {\n return {\n kind: \"system\",\n title: \"Branch summary\",\n body: entry.summary,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n}\n\nfunction assistantContentToText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n const lines: string[] = [];\n\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const value = block as Record<string, unknown>;\n if (value.type === \"text\" && typeof value.text === \"string\") {\n lines.push(value.text);\n continue;\n }\n if (value.type === \"thinking\" && typeof value.thinking === \"string\") {\n lines.push(`[thinking]\\n${value.thinking}`);\n continue;\n }\n if (value.type === \"toolCall\") {\n const name = typeof value.name === \"string\" ? value.name : \"tool\";\n const args = value.arguments === undefined ? \"\" : JSON.stringify(value.arguments, null, 2);\n lines.push([`[toolCall] ${name}`, args].filter(Boolean).join(\"\\n\"));\n continue;\n }\n if (value.type === \"image\") {\n lines.push(`[image ${String(value.mimeType ?? \"unknown\")}]`);\n }\n }\n\n return lines.join(\"\\n\\n\");\n}\n\nfunction contentToText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n const lines: string[] = [];\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const value = block as Record<string, unknown>;\n if (value.type === \"text\" && typeof value.text === \"string\") {\n lines.push(value.text);\n continue;\n }\n if (value.type === \"thinking\" && typeof value.thinking === \"string\") {\n lines.push(`[thinking]\\n${value.thinking}`);\n continue;\n }\n if (value.type === \"toolCall\") {\n const name = typeof value.name === \"string\" ? value.name : \"tool\";\n const args = value.arguments === undefined ? \"\" : JSON.stringify(value.arguments, null, 2);\n lines.push([`[toolCall] ${name}`, args].filter(Boolean).join(\"\\n\"));\n continue;\n }\n if (value.type === \"image\") {\n lines.push(`[image ${String(value.mimeType ?? \"unknown\")}]`);\n }\n }\n\n return lines.join(\"\\n\\n\");\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { basename, dirname, join, resolve } from "path";
2
2
  import { existsSync, readdirSync } from "fs";
3
- import { SessionManager, } from "@mariozechner/pi-coding-agent";
3
+ import { SessionManager, } from "@earendil-works/pi-coding-agent";
4
4
  import { getThreadSessionFile, resolveChannelSessionFile, tryResolveThreadSession, } from "../session-store.js";
5
5
  import * as log from "../log.js";
6
6
  export function resolveExistingSessionFile(workingDir, conversationId, sessionKey) {
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/session-view/service.ts"],"names":[],"mappings":"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","sourcesContent":["import { basename, dirname, join, resolve } from \"path\";\nimport { existsSync, readdirSync } from \"fs\";\nimport {\n SessionManager,\n type BranchSummaryEntry,\n type CompactionEntry,\n type SessionEntry,\n type SessionMessageEntry,\n} from \"@mariozechner/pi-coding-agent\";\nimport {\n getThreadSessionFile,\n resolveChannelSessionFile,\n tryResolveThreadSession,\n} from \"../session-store.js\";\nimport * as log from \"../log.js\";\n\nexport interface SessionViewItem {\n kind: \"user\" | \"assistant\" | \"tool\" | \"system\";\n title: string;\n body?: string;\n meta?: string;\n tone?: \"default\" | \"ok\" | \"err\" | \"muted\";\n entryId?: string;\n forks?: SessionViewRelation[];\n}\n\nexport interface SessionViewRelation {\n kind: \"parent\" | \"fork\";\n fileName: string;\n sessionId: string;\n title: string;\n updatedAt: string;\n entryCount: number;\n summary?: string;\n anchorEntryId?: string;\n}\n\nexport interface SessionViewModel {\n sessionId: string;\n fileName: string;\n title: string;\n createdAt: string;\n updatedAt: string;\n entryCount: number;\n items: SessionViewItem[];\n parent?: SessionViewRelation;\n forks: SessionViewRelation[];\n}\n\nexport function resolveExistingSessionFile(\n workingDir: string,\n conversationId: string,\n sessionKey: string,\n): string | null {\n const conversationDir = join(workingDir, conversationId);\n if (sessionKey.includes(\":\")) {\n return tryResolveThreadSession(getThreadSessionFile(conversationDir, sessionKey));\n }\n return resolveChannelSessionFile(conversationDir);\n}\n\nexport function loadSessionViewModel(sessionFile: string): SessionViewModel {\n const resolvedFile = resolve(sessionFile);\n const sm = SessionManager.open(resolvedFile);\n const header = sm.getHeader();\n if (!header) throw new Error(`No valid session found: ${sessionFile}`);\n\n const entries = sm.getEntries();\n const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;\n const title = sm.getSessionName() || `Session ${header.id.slice(0, 8)}`;\n\n const parent = header.parentSession\n ? buildSessionRelation(resolve(header.parentSession), \"parent\")\n : undefined;\n const forks = listRelatedSessionFiles(resolvedFile)\n .filter((candidate) => candidate !== resolvedFile)\n .map((candidate) => buildSessionRelation(candidate, \"fork\", resolvedFile))\n .filter((relation): relation is SessionViewRelation => relation !== null)\n .sort((a, b) => (a.updatedAt < b.updatedAt ? -1 : a.updatedAt > b.updatedAt ? 1 : 0));\n\n const forksByEntryId = new Map<string, SessionViewRelation[]>();\n for (const fork of forks) {\n if (!fork.anchorEntryId) continue;\n const bucket = forksByEntryId.get(fork.anchorEntryId) ?? [];\n bucket.push(fork);\n forksByEntryId.set(fork.anchorEntryId, bucket);\n }\n\n const items = entries.flatMap((entry) => {\n const item = mapEntryToItem(entry);\n if (!item) return [];\n if (item.entryId) {\n const anchoredForks = forksByEntryId.get(item.entryId);\n if (anchoredForks) {\n item.forks = anchoredForks;\n }\n }\n return [item];\n });\n\n return {\n sessionId: header.id,\n fileName: basename(resolvedFile),\n title,\n createdAt: header.timestamp,\n updatedAt,\n entryCount: entries.length,\n items,\n parent: parent ?? undefined,\n forks,\n };\n}\n\nexport function resolveRequestedSessionFile(\n baseSessionFile: string,\n requestedFileName?: string | null,\n): string | null {\n const resolvedBase = resolve(baseSessionFile);\n if (!requestedFileName) return resolvedBase;\n\n const trimmed = requestedFileName.trim();\n if (!trimmed) return resolvedBase;\n\n const fileName = basename(trimmed);\n if (fileName !== trimmed || !fileName.endsWith(\".jsonl\")) return null;\n\n const candidate = join(dirname(resolvedBase), fileName);\n if (!existsSync(candidate)) return null;\n\n let sm: SessionManager;\n try {\n sm = SessionManager.open(candidate);\n } catch (err) {\n throw new Error(\n `Session file is corrupted: ${candidate}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n if (!sm.getHeader()) {\n throw new Error(`Session file is missing a valid header: ${candidate}`);\n }\n return candidate;\n}\n\nfunction listRelatedSessionFiles(sessionFile: string): string[] {\n const dir = dirname(sessionFile);\n if (!existsSync(dir)) return [];\n\n return readdirSync(dir)\n .filter((name) => name.endsWith(\".jsonl\"))\n .map((fileName) => join(dir, fileName));\n}\n\nfunction buildSessionRelation(\n sessionFile: string,\n kind: \"parent\" | \"fork\",\n expectedParent?: string,\n): SessionViewRelation | null {\n let sm: SessionManager;\n try {\n sm = SessionManager.open(sessionFile);\n } catch (err) {\n log.logWarning(\n `Skipping corrupted session file while building ${kind} relation: ${sessionFile}`,\n err instanceof Error ? err.message : String(err),\n );\n return null;\n }\n const header = sm.getHeader();\n if (!header) {\n log.logWarning(\n `Skipping session file with missing header while building ${kind} relation: ${sessionFile}`,\n );\n return null;\n }\n if (kind === \"fork\" && resolve(header.parentSession ?? \"\") !== expectedParent) {\n return null;\n }\n\n const entries = sm.getEntries();\n const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;\n const anchorEntryId =\n kind === \"fork\" && expectedParent\n ? findForkAnchorEntryId(SessionManager.open(expectedParent).getEntries(), entries)\n : undefined;\n return {\n kind,\n fileName: basename(sessionFile),\n sessionId: header.id,\n title: sm.getSessionName() || `Session ${header.id.slice(0, 8)}`,\n updatedAt,\n entryCount: entries.length,\n summary: extractSessionSummary(entries),\n anchorEntryId,\n };\n}\n\nfunction findForkAnchorEntryId(\n parentEntries: SessionEntry[],\n childEntries: SessionEntry[],\n): string | undefined {\n let sharedCount = 0;\n while (\n sharedCount < parentEntries.length &&\n sharedCount < childEntries.length &&\n parentEntries[sharedCount]?.id === childEntries[sharedCount]?.id\n ) {\n sharedCount += 1;\n }\n\n if (sharedCount > 0) {\n return parentEntries[sharedCount - 1]?.id;\n }\n\n const childRoot = findComparableUserMessage(childEntries);\n if (!childRoot) return undefined;\n\n return findParentAnchorByRootMessage(parentEntries, childRoot);\n}\n\nfunction findParentAnchorByRootMessage(\n parentEntries: SessionEntry[],\n childRoot: ComparableUserMessage,\n): string | undefined {\n let textMatchId: string | undefined;\n\n for (const entry of parentEntries) {\n const comparable = getComparableUserMessage(entry);\n if (!comparable) continue;\n if (comparable.normalizedText !== childRoot.normalizedText) continue;\n if (\n childRoot.messageTimestamp !== undefined &&\n comparable.messageTimestamp !== undefined &&\n comparable.messageTimestamp === childRoot.messageTimestamp\n ) {\n return entry.id;\n }\n textMatchId ??= entry.id;\n }\n\n return textMatchId;\n}\n\ninterface ComparableUserMessage {\n normalizedText: string;\n messageTimestamp?: number;\n}\n\nfunction findComparableUserMessage(entries: SessionEntry[]): ComparableUserMessage | null {\n for (const entry of entries) {\n const comparable = getComparableUserMessage(entry);\n if (comparable) return comparable;\n }\n return null;\n}\n\nfunction getComparableUserMessage(entry: SessionEntry): ComparableUserMessage | null {\n if (entry.type !== \"message\" || entry.message.role !== \"user\") return null;\n\n const body = contentToText(entry.message.content);\n const normalizedText = normalizeComparableUserText(body);\n if (!normalizedText) return null;\n\n const messageTimestamp =\n typeof entry.message.timestamp === \"number\" ? entry.message.timestamp : undefined;\n return { normalizedText, messageTimestamp };\n}\n\nfunction normalizeComparableUserText(text: string): string {\n const withoutTimestamp = text.replace(\n /^\\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\\]\\s+(?=\\[[^\\]]+\\](?:\\s+\\[in-thread:[^\\]]+\\])?:\\s)/,\n \"\",\n );\n return stripSlackAttachmentBlock(withoutTimestamp).trim();\n}\n\nfunction stripSlackAttachmentBlock(text: string): string {\n return text.replace(/\\n*<slack_attachments>\\n[\\s\\S]*?\\n<\\/slack_attachments>\\s*$/g, \"\");\n}\n\nfunction extractSessionSummary(entries: SessionEntry[]): string | undefined {\n for (const entry of entries) {\n if (entry.type !== \"message\") continue;\n const item = mapEntryToItem(entry);\n if (!item?.body) continue;\n return collapseSummary(item.body);\n }\n return undefined;\n}\n\nfunction collapseSummary(text: string): string {\n const singleLine = text.replace(/\\s+/g, \" \").trim();\n return singleLine.length > 96 ? `${singleLine.slice(0, 93)}…` : singleLine;\n}\n\nfunction mapEntryToItem(entry: SessionEntry): SessionViewItem | null {\n switch (entry.type) {\n case \"message\":\n return mapMessageEntry(entry);\n case \"model_change\":\n return {\n kind: \"system\",\n title: \"Model changed\",\n body: `${entry.provider} / ${entry.modelId}`,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"thinking_level_change\":\n return {\n kind: \"system\",\n title: \"Thinking level changed\",\n body: entry.thinkingLevel,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"compaction\":\n return mapCompactionEntry(entry);\n case \"branch_summary\":\n return mapBranchSummaryEntry(entry);\n case \"custom_message\":\n return {\n kind: \"system\",\n title: `Custom message · ${entry.customType}`,\n body: contentToText(entry.content),\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"custom\":\n return {\n kind: \"system\",\n title: `Custom data · ${entry.customType}`,\n body: entry.data === undefined ? \"(no data)\" : JSON.stringify(entry.data, null, 2),\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"label\":\n return {\n kind: \"system\",\n title: \"Label updated\",\n body: entry.label || \"(cleared)\",\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"session_info\":\n return entry.name\n ? {\n kind: \"system\",\n title: \"Session renamed\",\n body: entry.name,\n meta: entry.timestamp,\n tone: \"muted\",\n }\n : null;\n default:\n return null;\n }\n}\n\nfunction mapMessageEntry(entry: SessionMessageEntry): SessionViewItem {\n const message = entry.message as unknown as Record<string, unknown> & {\n role?: string;\n content?: unknown;\n provider?: string;\n model?: string;\n toolName?: string;\n isError?: boolean;\n command?: string;\n output?: string;\n exitCode?: number;\n cancelled?: boolean;\n truncated?: boolean;\n stopReason?: string;\n customType?: string;\n summary?: string;\n };\n\n switch (message.role) {\n case \"user\":\n return {\n kind: \"user\",\n title: \"User\",\n body: contentToText(message.content),\n meta: entry.timestamp,\n entryId: entry.id,\n };\n case \"assistant\": {\n const assistantBody = assistantContentToText(message.content);\n const metaParts = [message.provider, message.model, message.stopReason].filter(Boolean);\n return {\n kind: \"assistant\",\n title: \"Assistant\",\n body: assistantBody,\n meta:\n metaParts.length > 0 ? `${entry.timestamp} · ${metaParts.join(\" · \")}` : entry.timestamp,\n entryId: entry.id,\n };\n }\n case \"toolResult\":\n return {\n kind: \"tool\",\n title: `Tool result · ${String(message.toolName ?? \"unknown\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: message.isError ? \"err\" : \"ok\",\n entryId: entry.id,\n };\n case \"bashExecution\": {\n const command = String(message.command ?? \"\").trim();\n const output = String(message.output ?? \"\").trim();\n const details = [\n typeof message.exitCode === \"number\" ? `[exitCode] ${message.exitCode}` : \"\",\n message.cancelled ? `[cancelled] true` : \"\",\n message.truncated ? `[truncated] true` : \"\",\n ].filter(Boolean);\n const body = [command ? `$ ${command}` : \"\", output, ...details].filter(Boolean).join(\"\\n\\n\");\n return {\n kind: \"tool\",\n title: \"Bash execution\",\n body: body || \"(no output)\",\n meta: entry.timestamp,\n entryId: entry.id,\n };\n }\n case \"custom\":\n return {\n kind: \"system\",\n title: `Custom message · ${String(message.customType ?? \"custom\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n case \"branchSummary\":\n return {\n kind: \"system\",\n title: \"Branch summary\",\n body: String(message.summary ?? \"\"),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n case \"compactionSummary\":\n return {\n kind: \"system\",\n title: \"Compaction summary\",\n body: String(message.summary ?? \"\"),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n default:\n return {\n kind: \"system\",\n title: `Message · ${String(message.role ?? \"unknown\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n }\n}\n\nfunction mapCompactionEntry(entry: CompactionEntry): SessionViewItem {\n return {\n kind: \"system\",\n title: \"Context compacted\",\n body: entry.summary,\n meta: `${entry.timestamp} · ${entry.tokensBefore} tokens before compaction`,\n tone: \"muted\",\n };\n}\n\nfunction mapBranchSummaryEntry(entry: BranchSummaryEntry): SessionViewItem {\n return {\n kind: \"system\",\n title: \"Branch summary\",\n body: entry.summary,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n}\n\nfunction assistantContentToText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n const lines: string[] = [];\n\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const value = block as Record<string, unknown>;\n if (value.type === \"text\" && typeof value.text === \"string\") {\n lines.push(value.text);\n continue;\n }\n if (value.type === \"thinking\" && typeof value.thinking === \"string\") {\n lines.push(`[thinking]\\n${value.thinking}`);\n continue;\n }\n if (value.type === \"toolCall\") {\n const name = typeof value.name === \"string\" ? value.name : \"tool\";\n const args = value.arguments === undefined ? \"\" : JSON.stringify(value.arguments, null, 2);\n lines.push([`[toolCall] ${name}`, args].filter(Boolean).join(\"\\n\"));\n continue;\n }\n if (value.type === \"image\") {\n lines.push(`[image ${String(value.mimeType ?? \"unknown\")}]`);\n }\n }\n\n return lines.join(\"\\n\\n\");\n}\n\nfunction contentToText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n const lines: string[] = [];\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const value = block as Record<string, unknown>;\n if (value.type === \"text\" && typeof value.text === \"string\") {\n lines.push(value.text);\n continue;\n }\n if (value.type === \"thinking\" && typeof value.thinking === \"string\") {\n lines.push(`[thinking]\\n${value.thinking}`);\n continue;\n }\n if (value.type === \"toolCall\") {\n const name = typeof value.name === \"string\" ? value.name : \"tool\";\n const args = value.arguments === undefined ? \"\" : JSON.stringify(value.arguments, null, 2);\n lines.push([`[toolCall] ${name}`, args].filter(Boolean).join(\"\\n\"));\n continue;\n }\n if (value.type === \"image\") {\n lines.push(`[image ${String(value.mimeType ?? \"unknown\")}]`);\n }\n }\n\n return lines.join(\"\\n\\n\");\n}\n"]}
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/session-view/service.ts"],"names":[],"mappings":"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","sourcesContent":["import { basename, dirname, join, resolve } from \"path\";\nimport { existsSync, readdirSync } from \"fs\";\nimport {\n SessionManager,\n type BranchSummaryEntry,\n type CompactionEntry,\n type SessionEntry,\n type SessionMessageEntry,\n} from \"@earendil-works/pi-coding-agent\";\nimport {\n getThreadSessionFile,\n resolveChannelSessionFile,\n tryResolveThreadSession,\n} from \"../session-store.js\";\nimport * as log from \"../log.js\";\n\nexport interface SessionViewItem {\n kind: \"user\" | \"assistant\" | \"tool\" | \"system\";\n title: string;\n body?: string;\n meta?: string;\n tone?: \"default\" | \"ok\" | \"err\" | \"muted\";\n entryId?: string;\n forks?: SessionViewRelation[];\n}\n\nexport interface SessionViewRelation {\n kind: \"parent\" | \"fork\";\n fileName: string;\n sessionId: string;\n title: string;\n updatedAt: string;\n entryCount: number;\n summary?: string;\n anchorEntryId?: string;\n}\n\nexport interface SessionViewModel {\n sessionId: string;\n fileName: string;\n title: string;\n createdAt: string;\n updatedAt: string;\n entryCount: number;\n items: SessionViewItem[];\n parent?: SessionViewRelation;\n forks: SessionViewRelation[];\n}\n\nexport function resolveExistingSessionFile(\n workingDir: string,\n conversationId: string,\n sessionKey: string,\n): string | null {\n const conversationDir = join(workingDir, conversationId);\n if (sessionKey.includes(\":\")) {\n return tryResolveThreadSession(getThreadSessionFile(conversationDir, sessionKey));\n }\n return resolveChannelSessionFile(conversationDir);\n}\n\nexport function loadSessionViewModel(sessionFile: string): SessionViewModel {\n const resolvedFile = resolve(sessionFile);\n const sm = SessionManager.open(resolvedFile);\n const header = sm.getHeader();\n if (!header) throw new Error(`No valid session found: ${sessionFile}`);\n\n const entries = sm.getEntries();\n const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;\n const title = sm.getSessionName() || `Session ${header.id.slice(0, 8)}`;\n\n const parent = header.parentSession\n ? buildSessionRelation(resolve(header.parentSession), \"parent\")\n : undefined;\n const forks = listRelatedSessionFiles(resolvedFile)\n .filter((candidate) => candidate !== resolvedFile)\n .map((candidate) => buildSessionRelation(candidate, \"fork\", resolvedFile))\n .filter((relation): relation is SessionViewRelation => relation !== null)\n .sort((a, b) => (a.updatedAt < b.updatedAt ? -1 : a.updatedAt > b.updatedAt ? 1 : 0));\n\n const forksByEntryId = new Map<string, SessionViewRelation[]>();\n for (const fork of forks) {\n if (!fork.anchorEntryId) continue;\n const bucket = forksByEntryId.get(fork.anchorEntryId) ?? [];\n bucket.push(fork);\n forksByEntryId.set(fork.anchorEntryId, bucket);\n }\n\n const items = entries.flatMap((entry) => {\n const item = mapEntryToItem(entry);\n if (!item) return [];\n if (item.entryId) {\n const anchoredForks = forksByEntryId.get(item.entryId);\n if (anchoredForks) {\n item.forks = anchoredForks;\n }\n }\n return [item];\n });\n\n return {\n sessionId: header.id,\n fileName: basename(resolvedFile),\n title,\n createdAt: header.timestamp,\n updatedAt,\n entryCount: entries.length,\n items,\n parent: parent ?? undefined,\n forks,\n };\n}\n\nexport function resolveRequestedSessionFile(\n baseSessionFile: string,\n requestedFileName?: string | null,\n): string | null {\n const resolvedBase = resolve(baseSessionFile);\n if (!requestedFileName) return resolvedBase;\n\n const trimmed = requestedFileName.trim();\n if (!trimmed) return resolvedBase;\n\n const fileName = basename(trimmed);\n if (fileName !== trimmed || !fileName.endsWith(\".jsonl\")) return null;\n\n const candidate = join(dirname(resolvedBase), fileName);\n if (!existsSync(candidate)) return null;\n\n let sm: SessionManager;\n try {\n sm = SessionManager.open(candidate);\n } catch (err) {\n throw new Error(\n `Session file is corrupted: ${candidate}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n if (!sm.getHeader()) {\n throw new Error(`Session file is missing a valid header: ${candidate}`);\n }\n return candidate;\n}\n\nfunction listRelatedSessionFiles(sessionFile: string): string[] {\n const dir = dirname(sessionFile);\n if (!existsSync(dir)) return [];\n\n return readdirSync(dir)\n .filter((name) => name.endsWith(\".jsonl\"))\n .map((fileName) => join(dir, fileName));\n}\n\nfunction buildSessionRelation(\n sessionFile: string,\n kind: \"parent\" | \"fork\",\n expectedParent?: string,\n): SessionViewRelation | null {\n let sm: SessionManager;\n try {\n sm = SessionManager.open(sessionFile);\n } catch (err) {\n log.logWarning(\n `Skipping corrupted session file while building ${kind} relation: ${sessionFile}`,\n err instanceof Error ? err.message : String(err),\n );\n return null;\n }\n const header = sm.getHeader();\n if (!header) {\n log.logWarning(\n `Skipping session file with missing header while building ${kind} relation: ${sessionFile}`,\n );\n return null;\n }\n if (kind === \"fork\" && resolve(header.parentSession ?? \"\") !== expectedParent) {\n return null;\n }\n\n const entries = sm.getEntries();\n const updatedAt = entries.at(-1)?.timestamp ?? header.timestamp;\n const anchorEntryId =\n kind === \"fork\" && expectedParent\n ? findForkAnchorEntryId(SessionManager.open(expectedParent).getEntries(), entries)\n : undefined;\n return {\n kind,\n fileName: basename(sessionFile),\n sessionId: header.id,\n title: sm.getSessionName() || `Session ${header.id.slice(0, 8)}`,\n updatedAt,\n entryCount: entries.length,\n summary: extractSessionSummary(entries),\n anchorEntryId,\n };\n}\n\nfunction findForkAnchorEntryId(\n parentEntries: SessionEntry[],\n childEntries: SessionEntry[],\n): string | undefined {\n let sharedCount = 0;\n while (\n sharedCount < parentEntries.length &&\n sharedCount < childEntries.length &&\n parentEntries[sharedCount]?.id === childEntries[sharedCount]?.id\n ) {\n sharedCount += 1;\n }\n\n if (sharedCount > 0) {\n return parentEntries[sharedCount - 1]?.id;\n }\n\n const childRoot = findComparableUserMessage(childEntries);\n if (!childRoot) return undefined;\n\n return findParentAnchorByRootMessage(parentEntries, childRoot);\n}\n\nfunction findParentAnchorByRootMessage(\n parentEntries: SessionEntry[],\n childRoot: ComparableUserMessage,\n): string | undefined {\n let textMatchId: string | undefined;\n\n for (const entry of parentEntries) {\n const comparable = getComparableUserMessage(entry);\n if (!comparable) continue;\n if (comparable.normalizedText !== childRoot.normalizedText) continue;\n if (\n childRoot.messageTimestamp !== undefined &&\n comparable.messageTimestamp !== undefined &&\n comparable.messageTimestamp === childRoot.messageTimestamp\n ) {\n return entry.id;\n }\n textMatchId ??= entry.id;\n }\n\n return textMatchId;\n}\n\ninterface ComparableUserMessage {\n normalizedText: string;\n messageTimestamp?: number;\n}\n\nfunction findComparableUserMessage(entries: SessionEntry[]): ComparableUserMessage | null {\n for (const entry of entries) {\n const comparable = getComparableUserMessage(entry);\n if (comparable) return comparable;\n }\n return null;\n}\n\nfunction getComparableUserMessage(entry: SessionEntry): ComparableUserMessage | null {\n if (entry.type !== \"message\" || entry.message.role !== \"user\") return null;\n\n const body = contentToText(entry.message.content);\n const normalizedText = normalizeComparableUserText(body);\n if (!normalizedText) return null;\n\n const messageTimestamp =\n typeof entry.message.timestamp === \"number\" ? entry.message.timestamp : undefined;\n return { normalizedText, messageTimestamp };\n}\n\nfunction normalizeComparableUserText(text: string): string {\n const withoutTimestamp = text.replace(\n /^\\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{2}:[0-9]{2}\\]\\s+(?=\\[[^\\]]+\\](?:\\s+\\[in-thread:[^\\]]+\\])?:\\s)/,\n \"\",\n );\n return stripSlackAttachmentBlock(withoutTimestamp).trim();\n}\n\nfunction stripSlackAttachmentBlock(text: string): string {\n return text.replace(/\\n*<slack_attachments>\\n[\\s\\S]*?\\n<\\/slack_attachments>\\s*$/g, \"\");\n}\n\nfunction extractSessionSummary(entries: SessionEntry[]): string | undefined {\n for (const entry of entries) {\n if (entry.type !== \"message\") continue;\n const item = mapEntryToItem(entry);\n if (!item?.body) continue;\n return collapseSummary(item.body);\n }\n return undefined;\n}\n\nfunction collapseSummary(text: string): string {\n const singleLine = text.replace(/\\s+/g, \" \").trim();\n return singleLine.length > 96 ? `${singleLine.slice(0, 93)}…` : singleLine;\n}\n\nfunction mapEntryToItem(entry: SessionEntry): SessionViewItem | null {\n switch (entry.type) {\n case \"message\":\n return mapMessageEntry(entry);\n case \"model_change\":\n return {\n kind: \"system\",\n title: \"Model changed\",\n body: `${entry.provider} / ${entry.modelId}`,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"thinking_level_change\":\n return {\n kind: \"system\",\n title: \"Thinking level changed\",\n body: entry.thinkingLevel,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"compaction\":\n return mapCompactionEntry(entry);\n case \"branch_summary\":\n return mapBranchSummaryEntry(entry);\n case \"custom_message\":\n return {\n kind: \"system\",\n title: `Custom message · ${entry.customType}`,\n body: contentToText(entry.content),\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"custom\":\n return {\n kind: \"system\",\n title: `Custom data · ${entry.customType}`,\n body: entry.data === undefined ? \"(no data)\" : JSON.stringify(entry.data, null, 2),\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"label\":\n return {\n kind: \"system\",\n title: \"Label updated\",\n body: entry.label || \"(cleared)\",\n meta: entry.timestamp,\n tone: \"muted\",\n };\n case \"session_info\":\n return entry.name\n ? {\n kind: \"system\",\n title: \"Session renamed\",\n body: entry.name,\n meta: entry.timestamp,\n tone: \"muted\",\n }\n : null;\n default:\n return null;\n }\n}\n\nfunction mapMessageEntry(entry: SessionMessageEntry): SessionViewItem {\n const message = entry.message as unknown as Record<string, unknown> & {\n role?: string;\n content?: unknown;\n provider?: string;\n model?: string;\n toolName?: string;\n isError?: boolean;\n command?: string;\n output?: string;\n exitCode?: number;\n cancelled?: boolean;\n truncated?: boolean;\n stopReason?: string;\n customType?: string;\n summary?: string;\n };\n\n switch (message.role) {\n case \"user\":\n return {\n kind: \"user\",\n title: \"User\",\n body: contentToText(message.content),\n meta: entry.timestamp,\n entryId: entry.id,\n };\n case \"assistant\": {\n const assistantBody = assistantContentToText(message.content);\n const metaParts = [message.provider, message.model, message.stopReason].filter(Boolean);\n return {\n kind: \"assistant\",\n title: \"Assistant\",\n body: assistantBody,\n meta:\n metaParts.length > 0 ? `${entry.timestamp} · ${metaParts.join(\" · \")}` : entry.timestamp,\n entryId: entry.id,\n };\n }\n case \"toolResult\":\n return {\n kind: \"tool\",\n title: `Tool result · ${String(message.toolName ?? \"unknown\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: message.isError ? \"err\" : \"ok\",\n entryId: entry.id,\n };\n case \"bashExecution\": {\n const command = String(message.command ?? \"\").trim();\n const output = String(message.output ?? \"\").trim();\n const details = [\n typeof message.exitCode === \"number\" ? `[exitCode] ${message.exitCode}` : \"\",\n message.cancelled ? `[cancelled] true` : \"\",\n message.truncated ? `[truncated] true` : \"\",\n ].filter(Boolean);\n const body = [command ? `$ ${command}` : \"\", output, ...details].filter(Boolean).join(\"\\n\\n\");\n return {\n kind: \"tool\",\n title: \"Bash execution\",\n body: body || \"(no output)\",\n meta: entry.timestamp,\n entryId: entry.id,\n };\n }\n case \"custom\":\n return {\n kind: \"system\",\n title: `Custom message · ${String(message.customType ?? \"custom\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n case \"branchSummary\":\n return {\n kind: \"system\",\n title: \"Branch summary\",\n body: String(message.summary ?? \"\"),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n case \"compactionSummary\":\n return {\n kind: \"system\",\n title: \"Compaction summary\",\n body: String(message.summary ?? \"\"),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n default:\n return {\n kind: \"system\",\n title: `Message · ${String(message.role ?? \"unknown\")}`,\n body: contentToText(message.content),\n meta: entry.timestamp,\n tone: \"muted\",\n entryId: entry.id,\n };\n }\n}\n\nfunction mapCompactionEntry(entry: CompactionEntry): SessionViewItem {\n return {\n kind: \"system\",\n title: \"Context compacted\",\n body: entry.summary,\n meta: `${entry.timestamp} · ${entry.tokensBefore} tokens before compaction`,\n tone: \"muted\",\n };\n}\n\nfunction mapBranchSummaryEntry(entry: BranchSummaryEntry): SessionViewItem {\n return {\n kind: \"system\",\n title: \"Branch summary\",\n body: entry.summary,\n meta: entry.timestamp,\n tone: \"muted\",\n };\n}\n\nfunction assistantContentToText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n const lines: string[] = [];\n\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const value = block as Record<string, unknown>;\n if (value.type === \"text\" && typeof value.text === \"string\") {\n lines.push(value.text);\n continue;\n }\n if (value.type === \"thinking\" && typeof value.thinking === \"string\") {\n lines.push(`[thinking]\\n${value.thinking}`);\n continue;\n }\n if (value.type === \"toolCall\") {\n const name = typeof value.name === \"string\" ? value.name : \"tool\";\n const args = value.arguments === undefined ? \"\" : JSON.stringify(value.arguments, null, 2);\n lines.push([`[toolCall] ${name}`, args].filter(Boolean).join(\"\\n\"));\n continue;\n }\n if (value.type === \"image\") {\n lines.push(`[image ${String(value.mimeType ?? \"unknown\")}]`);\n }\n }\n\n return lines.join(\"\\n\\n\");\n}\n\nfunction contentToText(content: unknown): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return \"\";\n\n const lines: string[] = [];\n for (const block of content) {\n if (!block || typeof block !== \"object\") continue;\n const value = block as Record<string, unknown>;\n if (value.type === \"text\" && typeof value.text === \"string\") {\n lines.push(value.text);\n continue;\n }\n if (value.type === \"thinking\" && typeof value.thinking === \"string\") {\n lines.push(`[thinking]\\n${value.thinking}`);\n continue;\n }\n if (value.type === \"toolCall\") {\n const name = typeof value.name === \"string\" ? value.name : \"tool\";\n const args = value.arguments === undefined ? \"\" : JSON.stringify(value.arguments, null, 2);\n lines.push([`[toolCall] ${name}`, args].filter(Boolean).join(\"\\n\"));\n continue;\n }\n if (value.type === \"image\") {\n lines.push(`[image ${String(value.mimeType ?? \"unknown\")}]`);\n }\n }\n\n return lines.join(\"\\n\\n\");\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { AgentTool } from "@mariozechner/pi-agent-core";
1
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
2
2
  import type { Executor } from "../sandbox.js";
3
3
  declare const bashSchema: import("@sinclair/typebox").TObject<{
4
4
  label: import("@sinclair/typebox").TString;
@@ -1 +1 @@
1
- {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAE7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAiB9C,QAAA,MAAM,UAAU;;;;EAQd,CAAC;AAOH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAsE/E","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { Executor } from \"../sandbox.js\";\nimport {\n DEFAULT_MAX_BYTES,\n DEFAULT_MAX_LINES,\n formatSize,\n type TruncationResult,\n truncateTail,\n} from \"./truncate.js\";\n\n/**\n * Generate a unique temp file path for bash output\n */\nfunction getTempFilePath(): string {\n const id = randomBytes(8).toString(\"hex\");\n return join(tmpdir(), `mama-bash-${id}.log`);\n}\n\nconst bashSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of what this command does (shown to user)\",\n }),\n command: Type.String({ description: \"Bash command to execute\" }),\n timeout: Type.Optional(\n Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" }),\n ),\n});\n\ninterface BashToolDetails {\n truncation?: TruncationResult;\n fullOutputPath?: string;\n}\n\nexport function createBashTool(executor: Executor): AgentTool<typeof bashSchema> {\n return {\n name: \"bash\",\n label: \"bash\",\n description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n parameters: bashSchema,\n execute: async (\n _toolCallId: string,\n { command, timeout }: { label: string; command: string; timeout?: number },\n signal?: AbortSignal,\n ) => {\n // Track output for potential temp file writing\n let tempFilePath: string | undefined;\n let tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\n const result = await executor.exec(command, { timeout, signal });\n let output = \"\";\n if (result.stdout) output += result.stdout;\n if (result.stderr) {\n if (output) output += \"\\n\";\n output += result.stderr;\n }\n\n const totalBytes = Buffer.byteLength(output, \"utf-8\");\n\n // Write to temp file if output exceeds limit\n if (totalBytes > DEFAULT_MAX_BYTES) {\n tempFilePath = getTempFilePath();\n tempFileStream = createWriteStream(tempFilePath);\n tempFileStream.write(output);\n tempFileStream.end();\n }\n\n // Apply tail truncation\n const truncation = truncateTail(output);\n let outputText = truncation.content || \"(no output)\";\n\n // Build details with truncation info\n let details: BashToolDetails | undefined;\n\n if (truncation.truncated) {\n details = {\n truncation,\n fullOutputPath: tempFilePath,\n };\n\n // Build actionable notice\n const startLine = truncation.totalLines - truncation.outputLines + 1;\n const endLine = truncation.totalLines;\n\n if (truncation.lastLinePartial) {\n // Edge case: last line alone > 50KB\n const lastLineSize = formatSize(\n Buffer.byteLength(output.split(\"\\n\").pop() || \"\", \"utf-8\"),\n );\n outputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\n } else if (truncation.truncatedBy === \"lines\") {\n outputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\n } else {\n outputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\n }\n }\n\n if (result.code !== 0) {\n throw new Error(`${outputText}\\n\\nCommand exited with code ${result.code}`.trim());\n }\n\n return { content: [{ type: \"text\", text: outputText }], details };\n },\n };\n}\n"]}
1
+ {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAE/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAiB9C,QAAA,MAAM,UAAU;;;;EAQd,CAAC;AAOH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAsE/E","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { Executor } from \"../sandbox.js\";\nimport {\n DEFAULT_MAX_BYTES,\n DEFAULT_MAX_LINES,\n formatSize,\n type TruncationResult,\n truncateTail,\n} from \"./truncate.js\";\n\n/**\n * Generate a unique temp file path for bash output\n */\nfunction getTempFilePath(): string {\n const id = randomBytes(8).toString(\"hex\");\n return join(tmpdir(), `mama-bash-${id}.log`);\n}\n\nconst bashSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of what this command does (shown to user)\",\n }),\n command: Type.String({ description: \"Bash command to execute\" }),\n timeout: Type.Optional(\n Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" }),\n ),\n});\n\ninterface BashToolDetails {\n truncation?: TruncationResult;\n fullOutputPath?: string;\n}\n\nexport function createBashTool(executor: Executor): AgentTool<typeof bashSchema> {\n return {\n name: \"bash\",\n label: \"bash\",\n description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n parameters: bashSchema,\n execute: async (\n _toolCallId: string,\n { command, timeout }: { label: string; command: string; timeout?: number },\n signal?: AbortSignal,\n ) => {\n // Track output for potential temp file writing\n let tempFilePath: string | undefined;\n let tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\n const result = await executor.exec(command, { timeout, signal });\n let output = \"\";\n if (result.stdout) output += result.stdout;\n if (result.stderr) {\n if (output) output += \"\\n\";\n output += result.stderr;\n }\n\n const totalBytes = Buffer.byteLength(output, \"utf-8\");\n\n // Write to temp file if output exceeds limit\n if (totalBytes > DEFAULT_MAX_BYTES) {\n tempFilePath = getTempFilePath();\n tempFileStream = createWriteStream(tempFilePath);\n tempFileStream.write(output);\n tempFileStream.end();\n }\n\n // Apply tail truncation\n const truncation = truncateTail(output);\n let outputText = truncation.content || \"(no output)\";\n\n // Build details with truncation info\n let details: BashToolDetails | undefined;\n\n if (truncation.truncated) {\n details = {\n truncation,\n fullOutputPath: tempFilePath,\n };\n\n // Build actionable notice\n const startLine = truncation.totalLines - truncation.outputLines + 1;\n const endLine = truncation.totalLines;\n\n if (truncation.lastLinePartial) {\n // Edge case: last line alone > 50KB\n const lastLineSize = formatSize(\n Buffer.byteLength(output.split(\"\\n\").pop() || \"\", \"utf-8\"),\n );\n outputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\n } else if (truncation.truncatedBy === \"lines\") {\n outputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\n } else {\n outputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\n }\n }\n\n if (result.code !== 0) {\n throw new Error(`${outputText}\\n\\nCommand exited with code ${result.code}`.trim());\n }\n\n return { content: [{ type: \"text\", text: outputText }], details };\n },\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"bash.js","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { Executor } from \"../sandbox.js\";\nimport {\n DEFAULT_MAX_BYTES,\n DEFAULT_MAX_LINES,\n formatSize,\n type TruncationResult,\n truncateTail,\n} from \"./truncate.js\";\n\n/**\n * Generate a unique temp file path for bash output\n */\nfunction getTempFilePath(): string {\n const id = randomBytes(8).toString(\"hex\");\n return join(tmpdir(), `mama-bash-${id}.log`);\n}\n\nconst bashSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of what this command does (shown to user)\",\n }),\n command: Type.String({ description: \"Bash command to execute\" }),\n timeout: Type.Optional(\n Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" }),\n ),\n});\n\ninterface BashToolDetails {\n truncation?: TruncationResult;\n fullOutputPath?: string;\n}\n\nexport function createBashTool(executor: Executor): AgentTool<typeof bashSchema> {\n return {\n name: \"bash\",\n label: \"bash\",\n description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n parameters: bashSchema,\n execute: async (\n _toolCallId: string,\n { command, timeout }: { label: string; command: string; timeout?: number },\n signal?: AbortSignal,\n ) => {\n // Track output for potential temp file writing\n let tempFilePath: string | undefined;\n let tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\n const result = await executor.exec(command, { timeout, signal });\n let output = \"\";\n if (result.stdout) output += result.stdout;\n if (result.stderr) {\n if (output) output += \"\\n\";\n output += result.stderr;\n }\n\n const totalBytes = Buffer.byteLength(output, \"utf-8\");\n\n // Write to temp file if output exceeds limit\n if (totalBytes > DEFAULT_MAX_BYTES) {\n tempFilePath = getTempFilePath();\n tempFileStream = createWriteStream(tempFilePath);\n tempFileStream.write(output);\n tempFileStream.end();\n }\n\n // Apply tail truncation\n const truncation = truncateTail(output);\n let outputText = truncation.content || \"(no output)\";\n\n // Build details with truncation info\n let details: BashToolDetails | undefined;\n\n if (truncation.truncated) {\n details = {\n truncation,\n fullOutputPath: tempFilePath,\n };\n\n // Build actionable notice\n const startLine = truncation.totalLines - truncation.outputLines + 1;\n const endLine = truncation.totalLines;\n\n if (truncation.lastLinePartial) {\n // Edge case: last line alone > 50KB\n const lastLineSize = formatSize(\n Buffer.byteLength(output.split(\"\\n\").pop() || \"\", \"utf-8\"),\n );\n outputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\n } else if (truncation.truncatedBy === \"lines\") {\n outputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\n } else {\n outputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\n }\n }\n\n if (result.code !== 0) {\n throw new Error(`${outputText}\\n\\nCommand exited with code ${result.code}`.trim());\n }\n\n return { content: [{ type: \"text\", text: outputText }], details };\n },\n };\n}\n"]}
1
+ {"version":3,"file":"bash.js","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { Executor } from \"../sandbox.js\";\nimport {\n DEFAULT_MAX_BYTES,\n DEFAULT_MAX_LINES,\n formatSize,\n type TruncationResult,\n truncateTail,\n} from \"./truncate.js\";\n\n/**\n * Generate a unique temp file path for bash output\n */\nfunction getTempFilePath(): string {\n const id = randomBytes(8).toString(\"hex\");\n return join(tmpdir(), `mama-bash-${id}.log`);\n}\n\nconst bashSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of what this command does (shown to user)\",\n }),\n command: Type.String({ description: \"Bash command to execute\" }),\n timeout: Type.Optional(\n Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" }),\n ),\n});\n\ninterface BashToolDetails {\n truncation?: TruncationResult;\n fullOutputPath?: string;\n}\n\nexport function createBashTool(executor: Executor): AgentTool<typeof bashSchema> {\n return {\n name: \"bash\",\n label: \"bash\",\n description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n parameters: bashSchema,\n execute: async (\n _toolCallId: string,\n { command, timeout }: { label: string; command: string; timeout?: number },\n signal?: AbortSignal,\n ) => {\n // Track output for potential temp file writing\n let tempFilePath: string | undefined;\n let tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\n const result = await executor.exec(command, { timeout, signal });\n let output = \"\";\n if (result.stdout) output += result.stdout;\n if (result.stderr) {\n if (output) output += \"\\n\";\n output += result.stderr;\n }\n\n const totalBytes = Buffer.byteLength(output, \"utf-8\");\n\n // Write to temp file if output exceeds limit\n if (totalBytes > DEFAULT_MAX_BYTES) {\n tempFilePath = getTempFilePath();\n tempFileStream = createWriteStream(tempFilePath);\n tempFileStream.write(output);\n tempFileStream.end();\n }\n\n // Apply tail truncation\n const truncation = truncateTail(output);\n let outputText = truncation.content || \"(no output)\";\n\n // Build details with truncation info\n let details: BashToolDetails | undefined;\n\n if (truncation.truncated) {\n details = {\n truncation,\n fullOutputPath: tempFilePath,\n };\n\n // Build actionable notice\n const startLine = truncation.totalLines - truncation.outputLines + 1;\n const endLine = truncation.totalLines;\n\n if (truncation.lastLinePartial) {\n // Edge case: last line alone > 50KB\n const lastLineSize = formatSize(\n Buffer.byteLength(output.split(\"\\n\").pop() || \"\", \"utf-8\"),\n );\n outputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\n } else if (truncation.truncatedBy === \"lines\") {\n outputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\n } else {\n outputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\n }\n }\n\n if (result.code !== 0) {\n throw new Error(`${outputText}\\n\\nCommand exited with code ${result.code}`.trim());\n }\n\n return { content: [{ type: \"text\", text: outputText }], details };\n },\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { AgentTool } from "@mariozechner/pi-agent-core";
1
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
2
2
  import type { Executor } from "../sandbox.js";
3
3
  declare const editSchema: import("@sinclair/typebox").TObject<{
4
4
  label: import("@sinclair/typebox").TString;
@@ -1 +1 @@
1
- {"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAG7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAqF9C,QAAA,MAAM,UAAU;;;;;EAOd,CAAC;AAEH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAqE/E","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as Diff from \"diff\";\nimport type { Executor } from \"../sandbox.js\";\n\n/**\n * Generate a unified diff string with line numbers and context\n */\nfunction generateDiffString(oldContent: string, newContent: string, contextLines = 4): string {\n const parts = Diff.diffLines(oldContent, newContent);\n const output: string[] = [];\n\n const oldLines = oldContent.split(\"\\n\");\n const newLines = newContent.split(\"\\n\");\n const maxLineNum = Math.max(oldLines.length, newLines.length);\n const lineNumWidth = String(maxLineNum).length;\n\n let oldLineNum = 1;\n let newLineNum = 1;\n let lastWasChange = false;\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n const raw = part.value.split(\"\\n\");\n if (raw[raw.length - 1] === \"\") {\n raw.pop();\n }\n\n if (part.added || part.removed) {\n for (const line of raw) {\n if (part.added) {\n const lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n output.push(`+${lineNum} ${line}`);\n newLineNum++;\n } else {\n const lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n output.push(`-${lineNum} ${line}`);\n oldLineNum++;\n }\n }\n lastWasChange = true;\n } else {\n const nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n if (lastWasChange || nextPartIsChange) {\n let linesToShow = raw;\n let skipStart = 0;\n let skipEnd = 0;\n\n if (!lastWasChange) {\n skipStart = Math.max(0, raw.length - contextLines);\n linesToShow = raw.slice(skipStart);\n }\n\n if (!nextPartIsChange && linesToShow.length > contextLines) {\n skipEnd = linesToShow.length - contextLines;\n linesToShow = linesToShow.slice(0, contextLines);\n }\n\n if (skipStart > 0) {\n output.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n }\n\n for (const line of linesToShow) {\n const lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n output.push(` ${lineNum} ${line}`);\n oldLineNum++;\n newLineNum++;\n }\n\n if (skipEnd > 0) {\n output.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n }\n\n oldLineNum += skipStart + skipEnd;\n newLineNum += skipStart + skipEnd;\n } else {\n oldLineNum += raw.length;\n newLineNum += raw.length;\n }\n\n lastWasChange = false;\n }\n }\n\n return output.join(\"\\n\");\n}\n\nconst editSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of the edit you're making (shown to user)\",\n }),\n path: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n oldText: Type.String({ description: \"Exact text to find and replace (must match exactly)\" }),\n newText: Type.String({ description: \"New text to replace the old text with\" }),\n});\n\nexport function createEditTool(executor: Executor): AgentTool<typeof editSchema> {\n return {\n name: \"edit\",\n label: \"edit\",\n description:\n \"Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits.\",\n parameters: editSchema,\n execute: async (\n _toolCallId: string,\n { path, oldText, newText }: { label: string; path: string; oldText: string; newText: string },\n signal?: AbortSignal,\n ) => {\n // Read the file\n const readResult = await executor.exec(`cat ${shellEscape(path)}`, { signal });\n if (readResult.code !== 0) {\n throw new Error(readResult.stderr || `File not found: ${path}`);\n }\n\n const content = readResult.stdout;\n\n // Check if old text exists\n if (!content.includes(oldText)) {\n throw new Error(\n `Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,\n );\n }\n\n // Count occurrences\n const occurrences = content.split(oldText).length - 1;\n\n if (occurrences > 1) {\n throw new Error(\n `Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\n );\n }\n\n // Perform replacement\n const index = content.indexOf(oldText);\n const newContent =\n content.substring(0, index) + newText + content.substring(index + oldText.length);\n\n if (content === newContent) {\n throw new Error(\n `No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,\n );\n }\n\n // Write the file back\n const writeResult = await executor.exec(\n `printf '%s' ${shellEscape(newContent)} > ${shellEscape(path)}`,\n {\n signal,\n },\n );\n if (writeResult.code !== 0) {\n throw new Error(writeResult.stderr || `Failed to write file: ${path}`);\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `Successfully replaced text in ${path}. Changed ${oldText.length} characters to ${newText.length} characters.`,\n },\n ],\n details: { diff: generateDiffString(content, newContent) },\n };\n },\n };\n}\n\nfunction shellEscape(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
1
+ {"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAG/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAqF9C,QAAA,MAAM,UAAU;;;;;EAOd,CAAC;AAEH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAqE/E","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as Diff from \"diff\";\nimport type { Executor } from \"../sandbox.js\";\n\n/**\n * Generate a unified diff string with line numbers and context\n */\nfunction generateDiffString(oldContent: string, newContent: string, contextLines = 4): string {\n const parts = Diff.diffLines(oldContent, newContent);\n const output: string[] = [];\n\n const oldLines = oldContent.split(\"\\n\");\n const newLines = newContent.split(\"\\n\");\n const maxLineNum = Math.max(oldLines.length, newLines.length);\n const lineNumWidth = String(maxLineNum).length;\n\n let oldLineNum = 1;\n let newLineNum = 1;\n let lastWasChange = false;\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n const raw = part.value.split(\"\\n\");\n if (raw[raw.length - 1] === \"\") {\n raw.pop();\n }\n\n if (part.added || part.removed) {\n for (const line of raw) {\n if (part.added) {\n const lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n output.push(`+${lineNum} ${line}`);\n newLineNum++;\n } else {\n const lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n output.push(`-${lineNum} ${line}`);\n oldLineNum++;\n }\n }\n lastWasChange = true;\n } else {\n const nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n if (lastWasChange || nextPartIsChange) {\n let linesToShow = raw;\n let skipStart = 0;\n let skipEnd = 0;\n\n if (!lastWasChange) {\n skipStart = Math.max(0, raw.length - contextLines);\n linesToShow = raw.slice(skipStart);\n }\n\n if (!nextPartIsChange && linesToShow.length > contextLines) {\n skipEnd = linesToShow.length - contextLines;\n linesToShow = linesToShow.slice(0, contextLines);\n }\n\n if (skipStart > 0) {\n output.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n }\n\n for (const line of linesToShow) {\n const lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n output.push(` ${lineNum} ${line}`);\n oldLineNum++;\n newLineNum++;\n }\n\n if (skipEnd > 0) {\n output.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n }\n\n oldLineNum += skipStart + skipEnd;\n newLineNum += skipStart + skipEnd;\n } else {\n oldLineNum += raw.length;\n newLineNum += raw.length;\n }\n\n lastWasChange = false;\n }\n }\n\n return output.join(\"\\n\");\n}\n\nconst editSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of the edit you're making (shown to user)\",\n }),\n path: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n oldText: Type.String({ description: \"Exact text to find and replace (must match exactly)\" }),\n newText: Type.String({ description: \"New text to replace the old text with\" }),\n});\n\nexport function createEditTool(executor: Executor): AgentTool<typeof editSchema> {\n return {\n name: \"edit\",\n label: \"edit\",\n description:\n \"Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits.\",\n parameters: editSchema,\n execute: async (\n _toolCallId: string,\n { path, oldText, newText }: { label: string; path: string; oldText: string; newText: string },\n signal?: AbortSignal,\n ) => {\n // Read the file\n const readResult = await executor.exec(`cat ${shellEscape(path)}`, { signal });\n if (readResult.code !== 0) {\n throw new Error(readResult.stderr || `File not found: ${path}`);\n }\n\n const content = readResult.stdout;\n\n // Check if old text exists\n if (!content.includes(oldText)) {\n throw new Error(\n `Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,\n );\n }\n\n // Count occurrences\n const occurrences = content.split(oldText).length - 1;\n\n if (occurrences > 1) {\n throw new Error(\n `Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\n );\n }\n\n // Perform replacement\n const index = content.indexOf(oldText);\n const newContent =\n content.substring(0, index) + newText + content.substring(index + oldText.length);\n\n if (content === newContent) {\n throw new Error(\n `No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,\n );\n }\n\n // Write the file back\n const writeResult = await executor.exec(\n `printf '%s' ${shellEscape(newContent)} > ${shellEscape(path)}`,\n {\n signal,\n },\n );\n if (writeResult.code !== 0) {\n throw new Error(writeResult.stderr || `Failed to write file: ${path}`);\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `Successfully replaced text in ${path}. Changed ${oldText.length} characters to ${newText.length} characters.`,\n },\n ],\n details: { diff: generateDiffString(content, newContent) },\n };\n },\n };\n}\n\nfunction shellEscape(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"edit.js","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as Diff from \"diff\";\nimport type { Executor } from \"../sandbox.js\";\n\n/**\n * Generate a unified diff string with line numbers and context\n */\nfunction generateDiffString(oldContent: string, newContent: string, contextLines = 4): string {\n const parts = Diff.diffLines(oldContent, newContent);\n const output: string[] = [];\n\n const oldLines = oldContent.split(\"\\n\");\n const newLines = newContent.split(\"\\n\");\n const maxLineNum = Math.max(oldLines.length, newLines.length);\n const lineNumWidth = String(maxLineNum).length;\n\n let oldLineNum = 1;\n let newLineNum = 1;\n let lastWasChange = false;\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n const raw = part.value.split(\"\\n\");\n if (raw[raw.length - 1] === \"\") {\n raw.pop();\n }\n\n if (part.added || part.removed) {\n for (const line of raw) {\n if (part.added) {\n const lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n output.push(`+${lineNum} ${line}`);\n newLineNum++;\n } else {\n const lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n output.push(`-${lineNum} ${line}`);\n oldLineNum++;\n }\n }\n lastWasChange = true;\n } else {\n const nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n if (lastWasChange || nextPartIsChange) {\n let linesToShow = raw;\n let skipStart = 0;\n let skipEnd = 0;\n\n if (!lastWasChange) {\n skipStart = Math.max(0, raw.length - contextLines);\n linesToShow = raw.slice(skipStart);\n }\n\n if (!nextPartIsChange && linesToShow.length > contextLines) {\n skipEnd = linesToShow.length - contextLines;\n linesToShow = linesToShow.slice(0, contextLines);\n }\n\n if (skipStart > 0) {\n output.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n }\n\n for (const line of linesToShow) {\n const lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n output.push(` ${lineNum} ${line}`);\n oldLineNum++;\n newLineNum++;\n }\n\n if (skipEnd > 0) {\n output.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n }\n\n oldLineNum += skipStart + skipEnd;\n newLineNum += skipStart + skipEnd;\n } else {\n oldLineNum += raw.length;\n newLineNum += raw.length;\n }\n\n lastWasChange = false;\n }\n }\n\n return output.join(\"\\n\");\n}\n\nconst editSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of the edit you're making (shown to user)\",\n }),\n path: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n oldText: Type.String({ description: \"Exact text to find and replace (must match exactly)\" }),\n newText: Type.String({ description: \"New text to replace the old text with\" }),\n});\n\nexport function createEditTool(executor: Executor): AgentTool<typeof editSchema> {\n return {\n name: \"edit\",\n label: \"edit\",\n description:\n \"Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits.\",\n parameters: editSchema,\n execute: async (\n _toolCallId: string,\n { path, oldText, newText }: { label: string; path: string; oldText: string; newText: string },\n signal?: AbortSignal,\n ) => {\n // Read the file\n const readResult = await executor.exec(`cat ${shellEscape(path)}`, { signal });\n if (readResult.code !== 0) {\n throw new Error(readResult.stderr || `File not found: ${path}`);\n }\n\n const content = readResult.stdout;\n\n // Check if old text exists\n if (!content.includes(oldText)) {\n throw new Error(\n `Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,\n );\n }\n\n // Count occurrences\n const occurrences = content.split(oldText).length - 1;\n\n if (occurrences > 1) {\n throw new Error(\n `Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\n );\n }\n\n // Perform replacement\n const index = content.indexOf(oldText);\n const newContent =\n content.substring(0, index) + newText + content.substring(index + oldText.length);\n\n if (content === newContent) {\n throw new Error(\n `No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,\n );\n }\n\n // Write the file back\n const writeResult = await executor.exec(\n `printf '%s' ${shellEscape(newContent)} > ${shellEscape(path)}`,\n {\n signal,\n },\n );\n if (writeResult.code !== 0) {\n throw new Error(writeResult.stderr || `Failed to write file: ${path}`);\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `Successfully replaced text in ${path}. Changed ${oldText.length} characters to ${newText.length} characters.`,\n },\n ],\n details: { diff: generateDiffString(content, newContent) },\n };\n },\n };\n}\n\nfunction shellEscape(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
1
+ {"version":3,"file":"edit.js","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as Diff from \"diff\";\nimport type { Executor } from \"../sandbox.js\";\n\n/**\n * Generate a unified diff string with line numbers and context\n */\nfunction generateDiffString(oldContent: string, newContent: string, contextLines = 4): string {\n const parts = Diff.diffLines(oldContent, newContent);\n const output: string[] = [];\n\n const oldLines = oldContent.split(\"\\n\");\n const newLines = newContent.split(\"\\n\");\n const maxLineNum = Math.max(oldLines.length, newLines.length);\n const lineNumWidth = String(maxLineNum).length;\n\n let oldLineNum = 1;\n let newLineNum = 1;\n let lastWasChange = false;\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n const raw = part.value.split(\"\\n\");\n if (raw[raw.length - 1] === \"\") {\n raw.pop();\n }\n\n if (part.added || part.removed) {\n for (const line of raw) {\n if (part.added) {\n const lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n output.push(`+${lineNum} ${line}`);\n newLineNum++;\n } else {\n const lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n output.push(`-${lineNum} ${line}`);\n oldLineNum++;\n }\n }\n lastWasChange = true;\n } else {\n const nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n if (lastWasChange || nextPartIsChange) {\n let linesToShow = raw;\n let skipStart = 0;\n let skipEnd = 0;\n\n if (!lastWasChange) {\n skipStart = Math.max(0, raw.length - contextLines);\n linesToShow = raw.slice(skipStart);\n }\n\n if (!nextPartIsChange && linesToShow.length > contextLines) {\n skipEnd = linesToShow.length - contextLines;\n linesToShow = linesToShow.slice(0, contextLines);\n }\n\n if (skipStart > 0) {\n output.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n }\n\n for (const line of linesToShow) {\n const lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n output.push(` ${lineNum} ${line}`);\n oldLineNum++;\n newLineNum++;\n }\n\n if (skipEnd > 0) {\n output.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n }\n\n oldLineNum += skipStart + skipEnd;\n newLineNum += skipStart + skipEnd;\n } else {\n oldLineNum += raw.length;\n newLineNum += raw.length;\n }\n\n lastWasChange = false;\n }\n }\n\n return output.join(\"\\n\");\n}\n\nconst editSchema = Type.Object({\n label: Type.String({\n description: \"Brief description of the edit you're making (shown to user)\",\n }),\n path: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n oldText: Type.String({ description: \"Exact text to find and replace (must match exactly)\" }),\n newText: Type.String({ description: \"New text to replace the old text with\" }),\n});\n\nexport function createEditTool(executor: Executor): AgentTool<typeof editSchema> {\n return {\n name: \"edit\",\n label: \"edit\",\n description:\n \"Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits.\",\n parameters: editSchema,\n execute: async (\n _toolCallId: string,\n { path, oldText, newText }: { label: string; path: string; oldText: string; newText: string },\n signal?: AbortSignal,\n ) => {\n // Read the file\n const readResult = await executor.exec(`cat ${shellEscape(path)}`, { signal });\n if (readResult.code !== 0) {\n throw new Error(readResult.stderr || `File not found: ${path}`);\n }\n\n const content = readResult.stdout;\n\n // Check if old text exists\n if (!content.includes(oldText)) {\n throw new Error(\n `Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,\n );\n }\n\n // Count occurrences\n const occurrences = content.split(oldText).length - 1;\n\n if (occurrences > 1) {\n throw new Error(\n `Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\n );\n }\n\n // Perform replacement\n const index = content.indexOf(oldText);\n const newContent =\n content.substring(0, index) + newText + content.substring(index + oldText.length);\n\n if (content === newContent) {\n throw new Error(\n `No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,\n );\n }\n\n // Write the file back\n const writeResult = await executor.exec(\n `printf '%s' ${shellEscape(newContent)} > ${shellEscape(path)}`,\n {\n signal,\n },\n );\n if (writeResult.code !== 0) {\n throw new Error(writeResult.stderr || `Failed to write file: ${path}`);\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `Successfully replaced text in ${path}. Changed ${oldText.length} characters to ${newText.length} characters.`,\n },\n ],\n details: { diff: generateDiffString(content, newContent) },\n };\n },\n };\n}\n\nfunction shellEscape(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { AgentTool } from "@mariozechner/pi-agent-core";
1
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
2
2
  declare const eventSchema: import("@sinclair/typebox").TObject<{
3
3
  label: import("@sinclair/typebox").TString;
4
4
  type: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"immediate">, import("@sinclair/typebox").TLiteral<"one-shot">, import("@sinclair/typebox").TLiteral<"periodic">]>;