@loom-framework/core 0.1.0-alpha.6 → 0.1.0-alpha.61

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 (162) hide show
  1. package/dist/adapter-base.d.ts +29 -0
  2. package/dist/adapter-base.d.ts.map +1 -0
  3. package/dist/adapter-base.js +62 -0
  4. package/dist/adapter-base.js.map +1 -0
  5. package/dist/adapter-factory.d.ts +8 -0
  6. package/dist/adapter-factory.d.ts.map +1 -0
  7. package/dist/adapter-factory.js +25 -0
  8. package/dist/adapter-factory.js.map +1 -0
  9. package/dist/adapter-filesystem.d.ts +6 -11
  10. package/dist/adapter-filesystem.d.ts.map +1 -1
  11. package/dist/adapter-filesystem.js +17 -38
  12. package/dist/adapter-filesystem.js.map +1 -1
  13. package/dist/adapter-sqlite.d.ts +6 -23
  14. package/dist/adapter-sqlite.d.ts.map +1 -1
  15. package/dist/adapter-sqlite.js +45 -50
  16. package/dist/adapter-sqlite.js.map +1 -1
  17. package/dist/backend/ai/button-resolver.d.ts +18 -0
  18. package/dist/backend/ai/button-resolver.d.ts.map +1 -0
  19. package/dist/backend/ai/button-resolver.js +58 -0
  20. package/dist/backend/ai/button-resolver.js.map +1 -0
  21. package/dist/backend/ai/engine.d.ts +52 -0
  22. package/dist/backend/ai/engine.d.ts.map +1 -0
  23. package/dist/backend/ai/engine.js +189 -0
  24. package/dist/backend/ai/engine.js.map +1 -0
  25. package/dist/backend/ai/index.d.ts +11 -0
  26. package/dist/backend/ai/index.d.ts.map +1 -0
  27. package/dist/backend/ai/index.js +8 -0
  28. package/dist/backend/ai/index.js.map +1 -0
  29. package/dist/backend/ai/output-parser.d.ts +29 -0
  30. package/dist/backend/ai/output-parser.d.ts.map +1 -0
  31. package/dist/backend/ai/output-parser.js +247 -0
  32. package/dist/backend/ai/output-parser.js.map +1 -0
  33. package/dist/backend/ai/session-manager.d.ts +103 -0
  34. package/dist/backend/ai/session-manager.d.ts.map +1 -0
  35. package/dist/backend/ai/session-manager.js +298 -0
  36. package/dist/backend/ai/session-manager.js.map +1 -0
  37. package/dist/backend/index.d.ts +61 -0
  38. package/dist/backend/index.d.ts.map +1 -0
  39. package/dist/backend/index.js +160 -0
  40. package/dist/backend/index.js.map +1 -0
  41. package/dist/backend/observe/index.d.ts +6 -0
  42. package/dist/backend/observe/index.d.ts.map +1 -0
  43. package/dist/backend/observe/index.js +5 -0
  44. package/dist/backend/observe/index.js.map +1 -0
  45. package/dist/backend/observe/logger.d.ts +28 -0
  46. package/dist/backend/observe/logger.d.ts.map +1 -0
  47. package/dist/backend/observe/logger.js +80 -0
  48. package/dist/backend/observe/logger.js.map +1 -0
  49. package/dist/backend/observe/types.d.ts +26 -0
  50. package/dist/backend/observe/types.d.ts.map +1 -0
  51. package/dist/backend/observe/types.js +7 -0
  52. package/dist/backend/observe/types.js.map +1 -0
  53. package/dist/backend/routes/chat.d.ts +31 -0
  54. package/dist/backend/routes/chat.d.ts.map +1 -0
  55. package/dist/backend/routes/chat.js +426 -0
  56. package/dist/backend/routes/chat.js.map +1 -0
  57. package/dist/backend/routes/data.d.ts +13 -0
  58. package/dist/backend/routes/data.d.ts.map +1 -0
  59. package/dist/backend/routes/data.js +129 -0
  60. package/dist/backend/routes/data.js.map +1 -0
  61. package/dist/backend/routes/health.d.ts +7 -0
  62. package/dist/backend/routes/health.d.ts.map +1 -0
  63. package/dist/backend/routes/health.js +15 -0
  64. package/dist/backend/routes/health.js.map +1 -0
  65. package/dist/backend/routes/index.d.ts +9 -0
  66. package/dist/backend/routes/index.d.ts.map +1 -0
  67. package/dist/backend/routes/index.js +8 -0
  68. package/dist/backend/routes/index.js.map +1 -0
  69. package/dist/backend/routes/upload.d.ts +24 -0
  70. package/dist/backend/routes/upload.d.ts.map +1 -0
  71. package/dist/backend/routes/upload.js +67 -0
  72. package/dist/backend/routes/upload.js.map +1 -0
  73. package/dist/bin.d.ts +8 -0
  74. package/dist/bin.d.ts.map +1 -0
  75. package/dist/bin.js +12 -0
  76. package/dist/bin.js.map +1 -0
  77. package/dist/capability-generator.d.ts +16 -6
  78. package/dist/capability-generator.d.ts.map +1 -1
  79. package/dist/capability-generator.js +160 -248
  80. package/dist/capability-generator.js.map +1 -1
  81. package/dist/cli/commands/build.d.ts +11 -0
  82. package/dist/cli/commands/build.d.ts.map +1 -0
  83. package/dist/cli/commands/build.js +170 -0
  84. package/dist/cli/commands/build.js.map +1 -0
  85. package/dist/cli/commands/data.d.ts +12 -0
  86. package/dist/cli/commands/data.d.ts.map +1 -0
  87. package/dist/cli/commands/data.js +158 -0
  88. package/dist/cli/commands/data.js.map +1 -0
  89. package/dist/cli/commands/dev.d.ts +9 -0
  90. package/dist/cli/commands/dev.d.ts.map +1 -0
  91. package/dist/cli/commands/dev.js +114 -0
  92. package/dist/cli/commands/dev.js.map +1 -0
  93. package/dist/cli/commands/generate-capabilities.d.ts +8 -0
  94. package/dist/cli/commands/generate-capabilities.d.ts.map +1 -0
  95. package/dist/cli/commands/generate-capabilities.js +40 -0
  96. package/dist/cli/commands/generate-capabilities.js.map +1 -0
  97. package/dist/cli/commands/generate-cli-command.d.ts +8 -0
  98. package/dist/cli/commands/generate-cli-command.d.ts.map +1 -0
  99. package/dist/cli/commands/generate-cli-command.js +64 -0
  100. package/dist/cli/commands/generate-cli-command.js.map +1 -0
  101. package/dist/cli/commands/generate-page.d.ts +9 -0
  102. package/dist/cli/commands/generate-page.d.ts.map +1 -0
  103. package/dist/cli/commands/generate-page.js +418 -0
  104. package/dist/cli/commands/generate-page.js.map +1 -0
  105. package/dist/cli/commands/generate-skill.d.ts +8 -0
  106. package/dist/cli/commands/generate-skill.d.ts.map +1 -0
  107. package/dist/cli/commands/generate-skill.js +75 -0
  108. package/dist/cli/commands/generate-skill.js.map +1 -0
  109. package/dist/cli/commands/generate.d.ts +6 -0
  110. package/dist/cli/commands/generate.d.ts.map +1 -0
  111. package/dist/cli/commands/generate.js +17 -0
  112. package/dist/cli/commands/generate.js.map +1 -0
  113. package/dist/cli/commands/init.d.ts +8 -0
  114. package/dist/cli/commands/init.d.ts.map +1 -0
  115. package/dist/cli/commands/init.js +539 -0
  116. package/dist/cli/commands/init.js.map +1 -0
  117. package/dist/cli/commands/observe.d.ts +9 -0
  118. package/dist/cli/commands/observe.d.ts.map +1 -0
  119. package/dist/cli/commands/observe.js +142 -0
  120. package/dist/cli/commands/observe.js.map +1 -0
  121. package/dist/cli/commands/skill.d.ts +9 -0
  122. package/dist/cli/commands/skill.d.ts.map +1 -0
  123. package/dist/cli/commands/skill.js +186 -0
  124. package/dist/cli/commands/skill.js.map +1 -0
  125. package/dist/cli/helpers/duration.d.ts +5 -0
  126. package/dist/cli/helpers/duration.d.ts.map +1 -0
  127. package/dist/cli/helpers/duration.js +19 -0
  128. package/dist/cli/helpers/duration.js.map +1 -0
  129. package/dist/cli/helpers/field-template.d.ts +9 -0
  130. package/dist/cli/helpers/field-template.d.ts.map +1 -0
  131. package/dist/cli/helpers/field-template.js +92 -0
  132. package/dist/cli/helpers/field-template.js.map +1 -0
  133. package/dist/cli/helpers/naming.d.ts +12 -0
  134. package/dist/cli/helpers/naming.d.ts.map +1 -0
  135. package/dist/cli/helpers/naming.js +25 -0
  136. package/dist/cli/helpers/naming.js.map +1 -0
  137. package/dist/cli/index.d.ts +9 -0
  138. package/dist/cli/index.d.ts.map +1 -0
  139. package/dist/cli/index.js +33 -0
  140. package/dist/cli/index.js.map +1 -0
  141. package/dist/cli/utils.d.ts +10 -0
  142. package/dist/cli/utils.d.ts.map +1 -0
  143. package/dist/cli/utils.js +31 -0
  144. package/dist/cli/utils.js.map +1 -0
  145. package/dist/config.d.ts +8 -33
  146. package/dist/config.d.ts.map +1 -1
  147. package/dist/config.js +6 -7
  148. package/dist/config.js.map +1 -1
  149. package/dist/index.d.ts +6 -1
  150. package/dist/index.d.ts.map +1 -1
  151. package/dist/index.js +6 -0
  152. package/dist/index.js.map +1 -1
  153. package/dist/server-bin.d.ts +12 -0
  154. package/dist/server-bin.d.ts.map +1 -0
  155. package/dist/server-bin.js +75 -0
  156. package/dist/server-bin.js.map +1 -0
  157. package/dist/types.d.ts +28 -18
  158. package/dist/types.d.ts.map +1 -1
  159. package/package.json +17 -4
  160. package/templates/skill/SKILL.md +140 -0
  161. package/templates/skill/references/README.md +128 -0
  162. package/templates/skill/references/data-model.md +78 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,IAAI,MAAM,eAAe,CAAC;AACjC,OAAO,YAAY,MAAM,iBAAiB,CAAC;AAC3C,OAAO,IAAI,MAAM,MAAM,CAAC;AAIxB,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAetH,MAAM,OAAO,UAAU;IACb,MAAM,CAAc;IACpB,MAAM,CAAY;IAClB,OAAO,CAAe;IACtB,cAAc,CAAkB;IAChC,iBAAiB,CAAuB;IACxC,OAAO,CAA8B;IACrC,WAAW,CAAS;IACpB,OAAO,CAAoB;IAEnC,YAAY,OAA0B;QACpC,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;QACvC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU;QACd,cAAc;QACd,IAAI,CAAC,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAEjD,MAAM,YAAY,GAAG;YACnB,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM;YACrB,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY;SAC7B,CAAC;QAEF,0BAA0B;QAC1B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;YACrB,MAAM,EAAE;gBACN,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,IAAK,YAAwC,CAAC,QAAkB,IAAI,MAAM;aACvG;SACF,CAAC,CAAC;QAEH,6DAA6D;QAC7D,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,KAAmB,EAAE,QAAwB,EAAE,KAAmB,EAAE,EAAE;YAClG,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;gBACrB,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;gBACjF,OAAO;YACT,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC9B,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,IAAI,GAAG,CAAC;YAC3C,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;gBAC5B,KAAK,EAAE,UAAU,KAAK,GAAG,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO;aACpE,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,yCAAyC;QACzC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvE,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;QAEhC,uBAAuB;QACvB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,gBAAgB,CAAC;YACxD,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,IAAI,EAAE;YACxC,WAAW,EAAE,IAAI,CAAC,WAAW;SAC9B,CAAC,CAAC;QAEH,6BAA6B;QAC7B,IAAI,CAAC,cAAc,GAAG,IAAI,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC3D,MAAM,IAAI,CAAC,cAAc,CAAC,UAAU,EAAE,CAAC;QAEvC,gCAAgC;QAChC,IAAI,CAAC,iBAAiB,GAAG,IAAI,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACnE,MAAM,IAAI,CAAC,iBAAiB,CAAC,UAAU,EAAE,CAAC;QAE1C,mBAAmB;QACnB,IAAI,YAAY,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YAChC,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,CAAC;QAED,oCAAoC;QACpC,IAAI,YAAY,CAAC,SAAS,EAAE,CAAC;YAC3B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;YACzE,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,YAAY,EAAE;gBACxC,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,GAAG;gBACX,QAAQ,EAAE,KAAK;aAChB,CAAC,CAAC;QACL,CAAC;QAED,kBAAkB;QAClB,mBAAmB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAChD,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/C,oBAAoB,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QACtE,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAAE;YAC/B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,MAAM,EAAE,IAAI,CAAC,iBAAiB;YAC9B,WAAW,EAAE,IAAI,CAAC,WAAW;SAC9B,CAAC,CAAC;QAEH,6BAA6B;QAC7B,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;YACzB,IAAI,IAAI,CAAC,MAAM,YAAY,gBAAgB,EAAE,CAAC;gBAC5C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACxB,CAAC;YACD,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QAC7B,CAAC,CAAC;QAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC9B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,IAAI,IAAI,CAAC;QAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,IAAI,SAAS,CAAC;QAEnD,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,IAAI;QACR,IAAI,IAAI,CAAC,MAAM,YAAY,gBAAgB,EAAE,CAAC;YAC5C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACxB,CAAC;QACD,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IAC7B,CAAC;IAED,0CAA0C;IAC1C,UAAU;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,gCAAgC;IAChC,SAAS;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,iCAAiC;IACjC,UAAU;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;OAEG;IACK,aAAa,CAAC,MAAkB;QACtC,IAAI,MAAM,CAAC,IAAI,CAAC,cAAc,KAAK,QAAQ,EAAE,CAAC;YAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,IAAI,cAAc,CAAC,CAAC;YAC3F,OAAO,IAAI,aAAa,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,IAAI,iBAAiB,CAAC;YAC3B,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC;YAC5C,MAAM;SACP,CAAC,CAAC;IACL,CAAC;CACF;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,WAAmB;IACnD,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;IAC/C,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC;IAC1B,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACrB,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Observability module - AI interaction logging and metrics
3
+ */
4
+ export { AIInteractionLogger, parseDuration } from './logger.js';
5
+ export type { AIInteractionLog, LogFilter } from './types.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/backend/observe/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjE,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Observability module - AI interaction logging and metrics
3
+ */
4
+ export { AIInteractionLogger, parseDuration } from './logger.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/backend/observe/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * AIInteractionLogger
3
+ *
4
+ * Records each AI interaction to .loom/logs/ as JSON files.
5
+ * Each interaction is stored as a separate file: .loom/logs/{messageId}.json
6
+ */
7
+ import type { AIInteractionLog, LogFilter } from './types.js';
8
+ export declare class AIInteractionLogger {
9
+ private logsDir;
10
+ constructor(projectRoot: string);
11
+ /**
12
+ * Ensure logs directory exists
13
+ */
14
+ initialize(): Promise<void>;
15
+ /**
16
+ * Record an AI interaction log
17
+ */
18
+ log(entry: AIInteractionLog): Promise<void>;
19
+ /**
20
+ * Read all logs, optionally filtered
21
+ */
22
+ readLogs(filter?: LogFilter): Promise<AIInteractionLog[]>;
23
+ }
24
+ /**
25
+ * Parse a duration string like '1h', '30m', '7d' into milliseconds
26
+ */
27
+ export declare function parseDuration(duration: string): number;
28
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/backend/observe/logger.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE9D,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,OAAO,CAAS;gBAEZ,WAAW,EAAE,MAAM;IAI/B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC;;OAEG;IACG,GAAG,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjD;;OAEG;IACG,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAgChE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAgBtD"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * AIInteractionLogger
3
+ *
4
+ * Records each AI interaction to .loom/logs/ as JSON files.
5
+ * Each interaction is stored as a separate file: .loom/logs/{messageId}.json
6
+ */
7
+ import { promises as fs } from 'fs';
8
+ import path from 'path';
9
+ export class AIInteractionLogger {
10
+ logsDir;
11
+ constructor(projectRoot) {
12
+ this.logsDir = path.join(projectRoot, '.loom', 'logs');
13
+ }
14
+ /**
15
+ * Ensure logs directory exists
16
+ */
17
+ async initialize() {
18
+ await fs.mkdir(this.logsDir, { recursive: true });
19
+ }
20
+ /**
21
+ * Record an AI interaction log
22
+ */
23
+ async log(entry) {
24
+ const filePath = path.join(this.logsDir, `${entry.messageId}.json`);
25
+ await fs.writeFile(filePath, JSON.stringify(entry, null, 2), 'utf-8');
26
+ }
27
+ /**
28
+ * Read all logs, optionally filtered
29
+ */
30
+ async readLogs(filter) {
31
+ try {
32
+ const files = await fs.readdir(this.logsDir);
33
+ const logs = [];
34
+ for (const file of files) {
35
+ if (!file.endsWith('.json'))
36
+ continue;
37
+ try {
38
+ const content = await fs.readFile(path.join(this.logsDir, file), 'utf-8');
39
+ const entry = JSON.parse(content);
40
+ if (filter?.session && entry.sessionId !== filter.session)
41
+ continue;
42
+ if (filter?.last) {
43
+ const cutoff = Date.now() - parseDuration(filter.last);
44
+ if (new Date(entry.timestamp).getTime() < cutoff)
45
+ continue;
46
+ }
47
+ logs.push(entry);
48
+ }
49
+ catch {
50
+ // Skip malformed files
51
+ }
52
+ }
53
+ // Sort by timestamp descending (most recent first)
54
+ logs.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
55
+ return logs;
56
+ }
57
+ catch {
58
+ return [];
59
+ }
60
+ }
61
+ }
62
+ /**
63
+ * Parse a duration string like '1h', '30m', '7d' into milliseconds
64
+ */
65
+ export function parseDuration(duration) {
66
+ const match = duration.match(/^(\d+)(s|m|h|d)$/);
67
+ if (!match) {
68
+ throw new Error(`Invalid duration format: ${duration}. Use format like '1h', '30m', '7d'`);
69
+ }
70
+ const value = parseInt(match[1], 10);
71
+ const unit = match[2];
72
+ switch (unit) {
73
+ case 's': return value * 1000;
74
+ case 'm': return value * 60 * 1000;
75
+ case 'h': return value * 60 * 60 * 1000;
76
+ case 'd': return value * 24 * 60 * 60 * 1000;
77
+ default: throw new Error(`Unknown duration unit: ${unit}`);
78
+ }
79
+ }
80
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../../src/backend/observe/logger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * AI Interaction Log Types
3
+ *
4
+ * Defines the structure for observability logging of AI interactions.
5
+ */
6
+ export interface AIInteractionLog {
7
+ sessionId: string;
8
+ messageId: string;
9
+ timestamp: string;
10
+ prompt: string;
11
+ response: string;
12
+ duration: number;
13
+ usage?: {
14
+ inputTokens: number;
15
+ outputTokens: number;
16
+ };
17
+ mcpCalls: string[];
18
+ cliCalls: string[];
19
+ error?: string;
20
+ }
21
+ export interface LogFilter {
22
+ session?: string;
23
+ /** Duration string like '1h', '30m', '7d' */
24
+ last?: string;
25
+ }
26
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/backend/observe/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE;QACN,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * AI Interaction Log Types
3
+ *
4
+ * Defines the structure for observability logging of AI interactions.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/backend/observe/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Chat SSE Route - HTTP Server-Sent Events streaming for AI chat
3
+ *
4
+ * Replaces WebSocket with HTTP SSE for AI conversation streaming.
5
+ * Ant Design X (useXChat + XRequest) natively consumes this format.
6
+ *
7
+ * POST /api/v1/chat
8
+ * Body: { content, session_id, button_id?, context?, files? }
9
+ * Response: SSE stream with data lines containing JSON chunks
10
+ *
11
+ * GET /api/v1/sessions - List sessions
12
+ * POST /api/v1/sessions - Create session
13
+ * GET /api/v1/sessions/:id - Load session
14
+ * DELETE /api/v1/sessions/:id - Delete session
15
+ */
16
+ import type { FastifyInstance } from 'fastify';
17
+ import type { AIEngine, LoomConfig } from '../../index.js';
18
+ import { SessionManager } from '../ai/session-manager.js';
19
+ import { AIInteractionLogger } from '../observe/index.js';
20
+ export interface ChatRouteOptions {
21
+ engine: AIEngine;
22
+ sessionManager: SessionManager;
23
+ config: LoomConfig;
24
+ logger?: AIInteractionLogger;
25
+ projectRoot: string;
26
+ }
27
+ /**
28
+ * Register chat SSE route and session REST routes
29
+ */
30
+ export declare function registerChatRoutes(fastify: FastifyInstance, options: ChatRouteOptions): void;
31
+ //# sourceMappingURL=chat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/backend/routes/chat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAC;AAC7E,OAAO,KAAK,EAAE,QAAQ,EAAW,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAoC1D,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,QAAQ,CAAC;IACjB,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;CACrB;AA0HD;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,gBAAgB,GACxB,IAAI,CAwRN"}
@@ -0,0 +1,426 @@
1
+ /**
2
+ * Chat SSE Route - HTTP Server-Sent Events streaming for AI chat
3
+ *
4
+ * Replaces WebSocket with HTTP SSE for AI conversation streaming.
5
+ * Ant Design X (useXChat + XRequest) natively consumes this format.
6
+ *
7
+ * POST /api/v1/chat
8
+ * Body: { content, session_id, button_id?, context?, files? }
9
+ * Response: SSE stream with data lines containing JSON chunks
10
+ *
11
+ * GET /api/v1/sessions - List sessions
12
+ * POST /api/v1/sessions - Create session
13
+ * GET /api/v1/sessions/:id - Load session
14
+ * DELETE /api/v1/sessions/:id - Delete session
15
+ */
16
+ import { resolveButtonPrompt } from '../ai/button-resolver.js';
17
+ import { saveUploadedFile } from './upload.js';
18
+ // ── Shared Schemas ──
19
+ const chatBodySchema = {
20
+ type: 'object',
21
+ properties: {
22
+ content: { type: 'string' },
23
+ session_id: { type: 'string' },
24
+ button_id: { type: 'string' },
25
+ context: { type: 'object', additionalProperties: true },
26
+ files: {
27
+ type: 'array',
28
+ items: {
29
+ type: 'object',
30
+ required: ['uid', 'name', 'size', 'type', 'content'],
31
+ properties: {
32
+ uid: { type: 'string' },
33
+ name: { type: 'string' },
34
+ size: { type: 'number' },
35
+ type: { type: 'string' },
36
+ content: { type: 'string' },
37
+ },
38
+ },
39
+ },
40
+ },
41
+ };
42
+ const sessionParamsSchema = {
43
+ type: 'object',
44
+ required: ['id'],
45
+ properties: {
46
+ id: { type: 'string', minLength: 1 },
47
+ },
48
+ };
49
+ const messageParamsSchema = {
50
+ type: 'object',
51
+ required: ['id', 'messageId'],
52
+ properties: {
53
+ id: { type: 'string', minLength: 1 },
54
+ messageId: { type: 'string', minLength: 1 },
55
+ },
56
+ };
57
+ const truncateBodySchema = {
58
+ type: 'object',
59
+ required: ['fromIndex'],
60
+ properties: {
61
+ fromIndex: { type: 'integer', minimum: 0 },
62
+ },
63
+ };
64
+ function startHeartbeat(reply) {
65
+ return setInterval(() => {
66
+ try {
67
+ reply.raw.write(':\n\n'); // SSE comment line - keeps connection alive
68
+ }
69
+ catch {
70
+ // Connection already closed
71
+ }
72
+ }, 30000);
73
+ }
74
+ function startIdleTimeout(reply, onTimeout) {
75
+ return setTimeout(() => {
76
+ try {
77
+ reply.raw.end();
78
+ }
79
+ catch {
80
+ // Already closed
81
+ }
82
+ onTimeout();
83
+ }, 5 * 60 * 1000); // 5 minutes idle timeout
84
+ }
85
+ function refreshIdleTimeout(state, reply, onTimeout) {
86
+ clearTimeout(state.timeout);
87
+ state.timeout = startIdleTimeout(reply, onTimeout);
88
+ }
89
+ function cleanupSSE(state) {
90
+ clearInterval(state.heartbeat);
91
+ clearTimeout(state.timeout);
92
+ state.clientConnected = false;
93
+ }
94
+ // ── SSE Helpers ──
95
+ async function sendSSE(reply, event) {
96
+ try {
97
+ const ok = reply.raw.write(`data: ${JSON.stringify(event)}\n\n`);
98
+ if (!ok) {
99
+ // Backpressure: wait for drain or timeout after 5s
100
+ await new Promise((resolve) => {
101
+ const drainHandler = () => { reply.raw.off('drain', drainHandler); resolve(); };
102
+ reply.raw.once('drain', drainHandler);
103
+ setTimeout(() => { reply.raw.off('drain', drainHandler); resolve(); }, 5000);
104
+ });
105
+ }
106
+ return true;
107
+ }
108
+ catch {
109
+ return false; // Client disconnected
110
+ }
111
+ }
112
+ function endSSE(reply) {
113
+ try {
114
+ reply.raw.end();
115
+ }
116
+ catch {
117
+ // Already closed
118
+ }
119
+ }
120
+ // ── Route Registration ──
121
+ /**
122
+ * Register chat SSE route and session REST routes
123
+ */
124
+ export function registerChatRoutes(fastify, options) {
125
+ const { engine, sessionManager, config, logger, projectRoot } = options;
126
+ // POST /api/v1/chat — SSE streaming AI response
127
+ fastify.post('/api/v1/chat', {
128
+ schema: { body: chatBodySchema },
129
+ }, async (request, reply) => {
130
+ const body = request.body;
131
+ const { content, session_id, button_id, context, files } = body;
132
+ if (!content && !button_id) {
133
+ return reply.status(400).send({ error: 'content or button_id is required' });
134
+ }
135
+ // Auto-generate session_id if not provided
136
+ const resolvedSessionId = session_id || `session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
137
+ const isNewSession = !session_id;
138
+ // Set SSE headers
139
+ reply.raw.writeHead(200, {
140
+ 'Content-Type': 'text/event-stream',
141
+ 'Cache-Control': 'no-cache',
142
+ Connection: 'keep-alive',
143
+ 'X-Accel-Buffering': 'no', // Disable nginx buffering
144
+ });
145
+ // Start heartbeat and idle timeout
146
+ const sseState = {
147
+ heartbeat: startHeartbeat(reply),
148
+ timeout: startIdleTimeout(reply, () => { sseState.clientConnected = false; }),
149
+ clientConnected: true,
150
+ };
151
+ const messageId = generateMessageId();
152
+ const startTime = Date.now();
153
+ // Track active process for abort on disconnect
154
+ let activeProcessKey;
155
+ // Handle client disconnect - abort the engine process
156
+ reply.raw.on('close', () => {
157
+ if (activeProcessKey && engine && 'killSession' in engine) {
158
+ engine.killSession(activeProcessKey);
159
+ }
160
+ cleanupSSE(sseState);
161
+ });
162
+ // Handle file uploads
163
+ const uploadedFiles = [];
164
+ if (files && files.length > 0 && projectRoot) {
165
+ for (const file of files) {
166
+ if (file.content) {
167
+ try {
168
+ const result = await saveUploadedFile(projectRoot, resolvedSessionId, file.name, file.content);
169
+ uploadedFiles.push({ name: file.name, path: result.path });
170
+ }
171
+ catch (err) {
172
+ fastify.log.warn('Failed to save uploaded file %s: %s', file.name, err);
173
+ }
174
+ }
175
+ }
176
+ }
177
+ // Resolve prompt: AI button or direct content
178
+ let prompt;
179
+ if (button_id) {
180
+ const resolved = resolveButtonPrompt(button_id, context, config);
181
+ if (!resolved) {
182
+ await sendSSE(reply, { type: 'error', error: `AI button "${button_id}" not found`, message_id: messageId, session_id: resolvedSessionId });
183
+ endSSE(reply);
184
+ cleanupSSE(sseState);
185
+ return;
186
+ }
187
+ if (resolved.error) {
188
+ await sendSSE(reply, { type: 'error', error: resolved.error, message_id: messageId, session_id: resolvedSessionId });
189
+ endSSE(reply);
190
+ cleanupSSE(sseState);
191
+ return;
192
+ }
193
+ prompt = resolved.prompt;
194
+ }
195
+ else {
196
+ prompt = content;
197
+ }
198
+ // Get or create session
199
+ const session = await sessionManager.getOrCreate(resolvedSessionId);
200
+ activeProcessKey = resolvedSessionId;
201
+ // Add user message to session
202
+ const userMsg = {
203
+ id: messageId,
204
+ role: 'user',
205
+ content: prompt,
206
+ timestamp: new Date().toISOString(),
207
+ files: files?.map((f) => ({ uid: f.uid, name: f.name, size: f.size, type: f.type })),
208
+ };
209
+ await sessionManager.addMessages(resolvedSessionId, [userMsg]);
210
+ // Call AI engine and stream chunks
211
+ let aiContent = '';
212
+ const aiContentBlocks = [];
213
+ let claudeSessionId;
214
+ let usage;
215
+ try {
216
+ for await (const chunk of engine.call(prompt, {
217
+ sessionId: resolvedSessionId,
218
+ resumeSessionId: session.claudeSessionId,
219
+ files: uploadedFiles.length > 0 ? uploadedFiles : undefined,
220
+ })) {
221
+ // Check if client is still connected
222
+ if (!sseState.clientConnected)
223
+ break;
224
+ if (chunk.type === 'content' && chunk.content) {
225
+ aiContent += chunk.content;
226
+ // Merge into last content block or append
227
+ const last = aiContentBlocks[aiContentBlocks.length - 1];
228
+ if (last?.type === 'content') {
229
+ last.content = (last.content || '') + chunk.content;
230
+ }
231
+ else {
232
+ aiContentBlocks.push({ type: 'content', content: chunk.content });
233
+ }
234
+ const sent = await sendSSE(reply, { type: 'content', content: chunk.content, message_id: messageId, session_id: resolvedSessionId });
235
+ if (!sent)
236
+ break;
237
+ refreshIdleTimeout(sseState, reply, () => { sseState.clientConnected = false; });
238
+ }
239
+ else if (chunk.type === 'thinking' && chunk.content) {
240
+ // Merge into last thinking block or append
241
+ const last = aiContentBlocks[aiContentBlocks.length - 1];
242
+ if (last?.type === 'thinking') {
243
+ last.thinking = (last.thinking || '') + chunk.content;
244
+ }
245
+ else {
246
+ aiContentBlocks.push({ type: 'thinking', thinking: chunk.content });
247
+ }
248
+ const sent = await sendSSE(reply, { type: 'thinking', thinking: chunk.content, message_id: messageId, session_id: resolvedSessionId });
249
+ if (!sent)
250
+ break;
251
+ refreshIdleTimeout(sseState, reply, () => { sseState.clientConnected = false; });
252
+ }
253
+ else if (chunk.type === 'session_info') {
254
+ claudeSessionId = chunk.sessionId;
255
+ if (chunk.usage)
256
+ usage = chunk.usage;
257
+ }
258
+ else if (chunk.type === 'error' && chunk.error) {
259
+ await sendSSE(reply, { type: 'error', error: chunk.error, message_id: messageId, session_id: resolvedSessionId });
260
+ }
261
+ else if (chunk.type === 'tool_call') {
262
+ // Update existing tool_call block or append
263
+ const blockIdx = aiContentBlocks.findIndex(b => b.type === 'tool_call' && b.toolUseId === chunk.toolUseId);
264
+ if (blockIdx >= 0) {
265
+ const existing = aiContentBlocks[blockIdx];
266
+ const newInput = chunk.toolInput;
267
+ const shouldUpdateInput = newInput !== undefined &&
268
+ (typeof newInput !== 'object' || newInput === null || Object.keys(newInput).length > 0 || existing.toolInput === undefined);
269
+ aiContentBlocks[blockIdx] = {
270
+ ...existing,
271
+ ...(shouldUpdateInput ? { toolInput: newInput } : {}),
272
+ status: 'started',
273
+ };
274
+ }
275
+ else {
276
+ aiContentBlocks.push({
277
+ type: 'tool_call',
278
+ tool: chunk.toolName || 'unknown',
279
+ toolUseId: chunk.toolUseId || '',
280
+ status: 'started',
281
+ toolInput: chunk.toolInput,
282
+ });
283
+ }
284
+ const sent = await sendSSE(reply, {
285
+ type: 'tool_call',
286
+ toolUseId: chunk.toolUseId,
287
+ toolName: chunk.toolName,
288
+ toolInput: chunk.toolInput,
289
+ });
290
+ if (!sent)
291
+ break;
292
+ refreshIdleTimeout(sseState, reply, () => { sseState.clientConnected = false; });
293
+ }
294
+ else if (chunk.type === 'tool_result') {
295
+ // Find matching tool_call block and update
296
+ const blockIdx = aiContentBlocks.findIndex(b => b.type === 'tool_call' && b.toolUseId === chunk.toolUseId);
297
+ if (blockIdx >= 0) {
298
+ aiContentBlocks[blockIdx] = { ...aiContentBlocks[blockIdx], status: 'completed', result: chunk.toolResult };
299
+ }
300
+ const sent = await sendSSE(reply, {
301
+ type: 'tool_result',
302
+ toolUseId: chunk.toolUseId || '',
303
+ toolResult: chunk.toolResult,
304
+ });
305
+ if (!sent)
306
+ break;
307
+ refreshIdleTimeout(sseState, reply, () => { sseState.clientConnected = false; });
308
+ }
309
+ }
310
+ }
311
+ catch (error) {
312
+ fastify.log.error('AI engine exception: %s', error);
313
+ recordLog(logger, resolvedSessionId, messageId, prompt, '', startTime, [], [], error instanceof Error ? error.message : String(error), fastify);
314
+ await sendSSE(reply, { type: 'error', error: `Processing error: ${error}`, message_id: messageId, session_id: resolvedSessionId });
315
+ endSSE(reply);
316
+ cleanupSSE(sseState);
317
+ return;
318
+ }
319
+ // Send completion
320
+ if (sseState.clientConnected) {
321
+ const completePayload = {
322
+ type: 'done',
323
+ content: aiContent || '(empty response)',
324
+ message_id: messageId,
325
+ assistant_message_id: `resp_${messageId}`,
326
+ session_id: resolvedSessionId,
327
+ is_new_session: isNewSession || undefined,
328
+ };
329
+ if (claudeSessionId)
330
+ completePayload.claude_session_id = claudeSessionId;
331
+ if (usage)
332
+ completePayload.usage = usage;
333
+ await sendSSE(reply, completePayload);
334
+ // Persist assistant message (with contentBlocks)
335
+ const assistantMsg = {
336
+ id: `resp_${messageId}`,
337
+ role: 'assistant',
338
+ content: aiContent || '(empty response)',
339
+ timestamp: new Date().toISOString(),
340
+ ...(aiContentBlocks.length > 0 ? { contentBlocks: aiContentBlocks } : {}),
341
+ };
342
+ await sessionManager.addMessages(resolvedSessionId, [assistantMsg], claudeSessionId, usage);
343
+ recordLog(logger, resolvedSessionId, messageId, prompt, aiContent || '(empty response)', startTime, [], [], undefined, fastify, usage);
344
+ }
345
+ endSSE(reply);
346
+ cleanupSSE(sseState);
347
+ });
348
+ // GET /api/v1/sessions — List sessions
349
+ fastify.get('/api/v1/sessions', async (_request, reply) => {
350
+ const sessions = await sessionManager.listSessions();
351
+ return reply.send(sessions);
352
+ });
353
+ // POST /api/v1/sessions — Create session
354
+ fastify.post('/api/v1/sessions', {
355
+ schema: { body: { type: 'object', properties: { id: { type: 'string' } } } },
356
+ }, async (request, reply) => {
357
+ const body = request.body;
358
+ const session = await sessionManager.createSession(body?.id);
359
+ return reply.code(201).send(session);
360
+ });
361
+ // GET /api/v1/sessions/:id — Load session
362
+ fastify.get('/api/v1/sessions/:id', {
363
+ schema: { params: sessionParamsSchema },
364
+ }, async (request, reply) => {
365
+ const { id } = request.params;
366
+ const session = await sessionManager.readSession(id);
367
+ if (!session)
368
+ return reply.status(404).send({ error: 'Session not found' });
369
+ return reply.send(session);
370
+ });
371
+ // DELETE /api/v1/sessions/:id — Delete session
372
+ fastify.delete('/api/v1/sessions/:id', {
373
+ schema: { params: sessionParamsSchema },
374
+ }, async (request, reply) => {
375
+ const { id } = request.params;
376
+ const success = await sessionManager.deleteSession(id);
377
+ if (!success)
378
+ return reply.status(404).send({ error: 'Session not found' });
379
+ return reply.send({ success: true });
380
+ });
381
+ // DELETE /api/v1/sessions/:id/messages/:messageId — Delete a single message
382
+ fastify.delete('/api/v1/sessions/:id/messages/:messageId', {
383
+ schema: { params: messageParamsSchema },
384
+ }, async (request, reply) => {
385
+ const { id, messageId } = request.params;
386
+ const session = await sessionManager.deleteMessage(id, messageId);
387
+ if (!session)
388
+ return reply.status(404).send({ error: 'Session or message not found' });
389
+ return reply.send(session);
390
+ });
391
+ // POST /api/v1/sessions/:id/truncate — Truncate messages from a given index
392
+ fastify.post('/api/v1/sessions/:id/truncate', {
393
+ schema: { params: sessionParamsSchema, body: truncateBodySchema },
394
+ }, async (request, reply) => {
395
+ const { id } = request.params;
396
+ const body = request.body;
397
+ const session = await sessionManager.truncateMessagesFromIndex(id, body.fromIndex);
398
+ if (!session)
399
+ return reply.status(404).send({ error: 'Session not found or invalid index' });
400
+ return reply.send(session);
401
+ });
402
+ }
403
+ // ── Helpers ──
404
+ function generateMessageId() {
405
+ return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
406
+ }
407
+ function recordLog(logger, sessionId, messageId, prompt, response, startTime, mcpCalls, cliCalls, error, fastify, usage) {
408
+ if (!logger)
409
+ return;
410
+ const logEntry = {
411
+ sessionId,
412
+ messageId,
413
+ timestamp: new Date().toISOString(),
414
+ prompt,
415
+ response,
416
+ duration: Date.now() - startTime,
417
+ usage: usage ? { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens } : undefined,
418
+ mcpCalls,
419
+ cliCalls,
420
+ error,
421
+ };
422
+ logger.log(logEntry).catch((err) => {
423
+ fastify.log.warn('Failed to write interaction log: %s', err);
424
+ });
425
+ }
426
+ //# sourceMappingURL=chat.js.map