pi-cloud 2.0.1 → 2.0.2

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 (117) hide show
  1. package/CHANGELOG.md +26 -2
  2. package/README.md +2 -2
  3. package/README.zh-CN.md +2 -2
  4. package/client/dist/.vite/manifest.json +332 -308
  5. package/client/dist/assets/{EditorPanel-5KsKffCI.css → EditorPanel-CVtykOZb.css} +1 -1
  6. package/client/dist/assets/EditorPanel-CmUoE78w.js +968 -0
  7. package/client/dist/assets/{GitHistoryView-fz83ZRs_.js → GitHistoryView-v-2M8_wB.js} +1 -1
  8. package/client/dist/assets/{MemoryCenter-Di8khKRv.js → MemoryCenter-C7FlmiQa.js} +1 -1
  9. package/client/dist/assets/{PhCaretLeft.vue-C_mgOR0C.js → PhCaretLeft.vue-mR6H4iil.js} +1 -1
  10. package/client/dist/assets/PhFolderOpen.vue-CBZCIEKc.js +1 -0
  11. package/client/dist/assets/SettingsDialog-BaIuifOT.css +1 -0
  12. package/client/dist/assets/SettingsDialog-CmkVcpN6.js +5 -0
  13. package/client/dist/assets/TaskInboxPanel-CZiNlRVB.js +1 -0
  14. package/client/dist/assets/{TaskQueuePanel-Bnvgy4cm.css → TaskInboxPanel-D-F8nXji.css} +1 -1
  15. package/client/dist/assets/{TerminalPanel-CWNxgT84.js → TerminalPanel-BKnITvmW.js} +1 -1
  16. package/client/dist/assets/{abnfDiagram-VCTEODGH-o53BjVqy.js → abnfDiagram-VCTEODGH-C0onveOK.js} +1 -1
  17. package/client/dist/assets/{arc-DsZjnKYA.js → arc-BtnkAKcM.js} +1 -1
  18. package/client/dist/assets/{architectureDiagram-5GKGNRK7-u5Bv1Ym6.js → architectureDiagram-5GKGNRK7-BWzkt3mg.js} +1 -1
  19. package/client/dist/assets/{blockDiagram-NRAW4CY4-BSDeSfLw.js → blockDiagram-NRAW4CY4-B5U3jj5J.js} +1 -1
  20. package/client/dist/assets/{c4Diagram-UCG6FXSJ-B6NfOWtd.js → c4Diagram-UCG6FXSJ-BijI8bRn.js} +1 -1
  21. package/client/dist/assets/channel-Fc9PxOLB.js +1 -0
  22. package/client/dist/assets/{chunk-2Q5K7J3B-CgX2R8z-.js → chunk-2Q5K7J3B-BEieUt6C.js} +1 -1
  23. package/client/dist/assets/{chunk-5VM5RSS4-ha_fObnM.js → chunk-5VM5RSS4-DK306biR.js} +1 -1
  24. package/client/dist/assets/{chunk-F27PBJKO-DI1DZ-wc.js → chunk-F27PBJKO-jkwA9GYW.js} +1 -1
  25. package/client/dist/assets/{chunk-G27WJ6UU-Dg1UIr6p.js → chunk-G27WJ6UU-DnVXeFY7.js} +1 -1
  26. package/client/dist/assets/{chunk-JWPE2WC7-B3XeLm0J.js → chunk-JWPE2WC7-DpOSXvW8.js} +1 -1
  27. package/client/dist/assets/{chunk-LCL6LL3I-qz7b1c_I.js → chunk-LCL6LL3I-BD7QKy4w.js} +1 -1
  28. package/client/dist/assets/{chunk-POPQ4Y6H-B5NSvr4G.js → chunk-POPQ4Y6H-BqLcddFA.js} +1 -1
  29. package/client/dist/assets/{chunk-SVP7TREG-7QwKLsdb.js → chunk-SVP7TREG-C9GhrbFg.js} +1 -1
  30. package/client/dist/assets/{chunk-XXDRQBXY-CAosos5-.js → chunk-XXDRQBXY-jQoIqp5a.js} +1 -1
  31. package/client/dist/assets/classDiagram-DTDB5LWJ-D6WMV2bo.js +1 -0
  32. package/client/dist/assets/classDiagram-v2-JRS7N3AN-D6WMV2bo.js +1 -0
  33. package/client/dist/assets/{cose-bilkent-JH36ORCC-BsDQpeGC.js → cose-bilkent-JH36ORCC-DNt10hsx.js} +1 -1
  34. package/client/dist/assets/cssMode-D5VnGCRL.js +1 -0
  35. package/client/dist/assets/{cynefin-OW5HDTMX-Drahq6GQ.js → cynefin-OW5HDTMX-wI0wNPcp.js} +1 -1
  36. package/client/dist/assets/{cynefinDiagram-5FMLGOSQ-CnkS0oyb.js → cynefinDiagram-5FMLGOSQ-DwksvA8r.js} +1 -1
  37. package/client/dist/assets/{dagre-3AP2YEHR-BKQcTp5_.js → dagre-3AP2YEHR-ZY-MhFWR.js} +1 -1
  38. package/client/dist/assets/{diagram-S7CK7UJ4-CRJVo1Nm.js → diagram-S7CK7UJ4-BKaJVqZu.js} +1 -1
  39. package/client/dist/assets/{diagram-UQ7AKVKN-DsP-WSSg.js → diagram-UQ7AKVKN-DAaE-rA1.js} +1 -1
  40. package/client/dist/assets/{diagram-VSXAHHWV-Ccd1iqfn.js → diagram-VSXAHHWV-CJJ9xyap.js} +1 -1
  41. package/client/dist/assets/{diagram-VX7I27RA-D5w0ryS3.js → diagram-VX7I27RA-4Er142Er.js} +1 -1
  42. package/client/dist/assets/{diagram-Z3DM3KII-DpUTugp8.js → diagram-Z3DM3KII-eEe26_w-.js} +1 -1
  43. package/client/dist/assets/{ebnfDiagram-PWID7BFC-Donm234O.js → ebnfDiagram-PWID7BFC-Dshy_lHs.js} +1 -1
  44. package/client/dist/assets/{erDiagram-SSCWMZ5O-CvtobvjZ.js → erDiagram-SSCWMZ5O-k7LwDRbW.js} +1 -1
  45. package/client/dist/assets/{flowDiagram-A5DVABFB-v8UIYgnu.js → flowDiagram-A5DVABFB-7_X479bX.js} +1 -1
  46. package/client/dist/assets/{freemarker2-CCBoOScX.js → freemarker2-B7Gu__z_.js} +2 -2
  47. package/client/dist/assets/{ganttDiagram-EL5Y4UJY-TCK2YgJA.js → ganttDiagram-EL5Y4UJY-5WmBzvON.js} +1 -1
  48. package/client/dist/assets/{gitGraphDiagram-WWUBYQGX-BTVfzqSz.js → gitGraphDiagram-WWUBYQGX-CGTy36Lq.js} +1 -1
  49. package/client/dist/assets/handlebars-DRBRCJNd.js +1 -0
  50. package/client/dist/assets/html-6prUe0kc.js +1 -0
  51. package/client/dist/assets/htmlMode-DWoHKeAk.js +1 -0
  52. package/client/dist/assets/index-D8pylZiM.css +1 -0
  53. package/client/dist/assets/{index-B4PeEJ0f.js → index-GTyUzvDG.js} +1 -1
  54. package/client/dist/assets/index-nLzofEMh.js +407 -0
  55. package/client/dist/assets/{infoDiagram-RXCK75RN-BkzEFABK.js → infoDiagram-RXCK75RN-DZVb1P4d.js} +1 -1
  56. package/client/dist/assets/{ishikawaDiagram-5VMMS53U-DuVtEdHP.js → ishikawaDiagram-5VMMS53U-CMgYHU70.js} +1 -1
  57. package/client/dist/assets/javascript-Celvl7cV.js +1 -0
  58. package/client/dist/assets/{journeyDiagram-EYS64GPL-Cx5cuIcS.js → journeyDiagram-EYS64GPL-BnqHXNJS.js} +1 -1
  59. package/client/dist/assets/{jsonMode-BZHZxAtU.js → jsonMode-DLC0POfi.js} +2 -2
  60. package/client/dist/assets/{kanban-definition-3QL26DDD-wXihmZ_e.js → kanban-definition-3QL26DDD-BeStEVi-.js} +1 -1
  61. package/client/dist/assets/{layout-C2KDikn-.js → layout-B8XEB-tZ.js} +1 -1
  62. package/client/dist/assets/{linear-C6ueyh3n.js → linear-C1qdgmvA.js} +1 -1
  63. package/client/dist/assets/liquid-CiU5o5FX.js +1 -0
  64. package/client/dist/assets/{lspLanguageFeatures-C5jWIiYu.js → lspLanguageFeatures-BXNRwtlE.js} +1 -1
  65. package/client/dist/assets/mdx-CfTUs1eY.js +1 -0
  66. package/client/dist/assets/{mermaid.core-CcwuCXLB.js → mermaid.core-CFpSBIjf.js} +5 -5
  67. package/client/dist/assets/{mindmap-definition-FBJOCRG2-JL1NZYBY.js → mindmap-definition-FBJOCRG2-CUNP7NW4.js} +1 -1
  68. package/client/dist/assets/{pegDiagram-XKGWAZYB-DS69_m-N.js → pegDiagram-XKGWAZYB-bRD6LI7t.js} +1 -1
  69. package/client/dist/assets/{pieDiagram-E7YTZNPT-DkXpPONo.js → pieDiagram-E7YTZNPT-CUE2D9Mz.js} +1 -1
  70. package/client/dist/assets/python-rOgKTqjK.js +1 -0
  71. package/client/dist/assets/{quadrantDiagram-AXDQQJYC-DkkBvxWI.js → quadrantDiagram-AXDQQJYC-zDDE2XzB.js} +1 -1
  72. package/client/dist/assets/{railroadDiagram-O6MQD6OU-DCC05goQ.js → railroadDiagram-O6MQD6OU-3TRS2gLn.js} +1 -1
  73. package/client/dist/assets/razor-DhEzBZvo.js +1 -0
  74. package/client/dist/assets/{requirementDiagram-EFPCY7ZU-BwD-Jp5O.js → requirementDiagram-EFPCY7ZU-Cgm0MiBG.js} +1 -1
  75. package/client/dist/assets/{sankeyDiagram-P5KCCOFB-GlrDZlAs.js → sankeyDiagram-P5KCCOFB-DzQfpaqZ.js} +1 -1
  76. package/client/dist/assets/{sequenceDiagram-WJ2MYXX4-u5NKUnHb.js → sequenceDiagram-WJ2MYXX4-D_eKGGY5.js} +1 -1
  77. package/client/dist/assets/{sizeCapture-X5ZJPWSS-DWoXbO58.js → sizeCapture-X5ZJPWSS-BVFpPdEZ.js} +1 -1
  78. package/client/dist/assets/{stateDiagram-HBIQ2CUA-Bm1dFyez.js → stateDiagram-HBIQ2CUA-Bvp5OM6x.js} +1 -1
  79. package/client/dist/assets/stateDiagram-v2-4QOOHH4V-CM3P74Kw.js +1 -0
  80. package/client/dist/assets/{swimlanes-XN3QIQJK-DUNKblS5.js → swimlanes-XN3QIQJK-B497DW3A.js} +1 -1
  81. package/client/dist/assets/swimlanesDiagram-VK2B7HYN-DAMUXB40.js +8 -0
  82. package/client/dist/assets/{timeline-definition-24CTP7MA-toWxO6Z2.js → timeline-definition-24CTP7MA-BHAeybVL.js} +1 -1
  83. package/client/dist/assets/{tsMode-DZAgDpaL.js → tsMode-BKQ7PNBK.js} +2 -2
  84. package/client/dist/assets/typescript-0T3kV3Fw.js +1 -0
  85. package/client/dist/assets/{useTerminal-BcxidJuH.js → useTerminal-D0Eg4ohV.js} +1 -1
  86. package/client/dist/assets/{vennDiagram-4TSXK5OY-BkCayCPN.js → vennDiagram-4TSXK5OY-C3aoPRLD.js} +1 -1
  87. package/client/dist/assets/{wardleyDiagram-VM6X3IG4-BqKWO2eU.js → wardleyDiagram-VM6X3IG4-D45EDW-W.js} +1 -1
  88. package/client/dist/assets/{workers-CPOEug1v.js → workers-nJulRuKZ.js} +1 -1
  89. package/client/dist/assets/xml-Dm41fNV2.js +1 -0
  90. package/client/dist/assets/{xychartDiagram-S5SC5T6Z-BIAgMyo_.js → xychartDiagram-S5SC5T6Z-CCfr24LB.js} +1 -1
  91. package/client/dist/assets/yaml-BHs6-_Vy.js +1 -0
  92. package/client/dist/index.html +2 -2
  93. package/package.json +4 -3
  94. package/server/dist/index.js +1113 -382
  95. package/client/dist/assets/EditorPanel-jZgIBqB6.js +0 -968
  96. package/client/dist/assets/SettingsDialog-BsTvb8AX.js +0 -2
  97. package/client/dist/assets/SettingsDialog-JpBlJzlR.css +0 -1
  98. package/client/dist/assets/TaskQueuePanel-Bbk8CKW5.js +0 -1
  99. package/client/dist/assets/channel-vm9b-dk7.js +0 -1
  100. package/client/dist/assets/classDiagram-DTDB5LWJ-BZ5qRTrF.js +0 -1
  101. package/client/dist/assets/classDiagram-v2-JRS7N3AN-BZ5qRTrF.js +0 -1
  102. package/client/dist/assets/cssMode-Bixb-eY-.js +0 -1
  103. package/client/dist/assets/handlebars-D_iO3oKV.js +0 -1
  104. package/client/dist/assets/html-BxskKvwE.js +0 -1
  105. package/client/dist/assets/htmlMode-BRqzPJmn.js +0 -1
  106. package/client/dist/assets/index-2q9oNVFR.js +0 -407
  107. package/client/dist/assets/index-DBGNeJhr.css +0 -1
  108. package/client/dist/assets/javascript-DLM6N2IL.js +0 -1
  109. package/client/dist/assets/liquid-CK9kFoUD.js +0 -1
  110. package/client/dist/assets/mdx-DJw_6jAn.js +0 -1
  111. package/client/dist/assets/python-Z3BmknC5.js +0 -1
  112. package/client/dist/assets/razor-CQA_N38O.js +0 -1
  113. package/client/dist/assets/stateDiagram-v2-4QOOHH4V-DjQoyKBb.js +0 -1
  114. package/client/dist/assets/swimlanesDiagram-VK2B7HYN-7MB5gr5J.js +0 -8
  115. package/client/dist/assets/typescript-BGesFGk_.js +0 -1
  116. package/client/dist/assets/xml-BLpTRmDm.js +0 -1
  117. package/client/dist/assets/yaml-PqYnl7Hj.js +0 -1
@@ -14,7 +14,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
14
14
 
15
15
  // src/routes/sessions.ts
16
16
  import * as os3 from "os";
17
- import { dirname as dirname2 } from "path";
17
+ import { dirname as dirname2, isAbsolute as isAbsolute2 } from "path";
18
18
 
19
19
  // src/services/project-mover.ts
20
20
  import { access as access2, rename, stat } from "fs/promises";
@@ -140,12 +140,12 @@ var ProjectMover = class {
140
140
  }
141
141
  return trimmed;
142
142
  }
143
- async assertDirectory(path13, message) {
143
+ async assertDirectory(path13, message2) {
144
144
  try {
145
145
  const stats = await stat(path13);
146
- if (!stats.isDirectory()) throw new Error(message);
146
+ if (!stats.isDirectory()) throw new Error(message2);
147
147
  } catch (error) {
148
- if (isMissingPathError(error)) throw new Error(message);
148
+ if (isMissingPathError(error)) throw new Error(message2);
149
149
  throw error;
150
150
  }
151
151
  }
@@ -775,10 +775,12 @@ async function sessionRoutes(app, options = {}) {
775
775
  if (!profileId) return reply.status(400).send({ error: "profileId is required" });
776
776
  const owner = { type: "profile", id: profileId };
777
777
  const idsByGroup = options.pinStore.listSessionIdsByGroup(owner);
778
+ const filesByGroup = options.pinStore.listFilePathsByGroup(profileId);
778
779
  return {
779
780
  groups: options.pinStore.listGroups(owner).map((group) => ({
780
781
  ...group,
781
- sessionIds: idsByGroup.get(group.id) || []
782
+ sessionIds: idsByGroup.get(group.id) || [],
783
+ filePaths: filesByGroup.get(group.id) || []
782
784
  }))
783
785
  };
784
786
  });
@@ -792,6 +794,26 @@ async function sessionRoutes(app, options = {}) {
792
794
  return reply.status(400).send({ error: error instanceof Error ? error.message : "Failed to create pin group" });
793
795
  }
794
796
  });
797
+ app.put("/files/pin", async (req, reply) => {
798
+ if (!options.pinStore) return reply.status(503).send({ error: "Session pins are not configured" });
799
+ const { filePath, groupId, profileId } = req.body;
800
+ if (!filePath || !isAbsolute2(filePath) || !groupId || !profileId) {
801
+ return reply.status(400).send({ error: "absolute filePath, groupId, and profileId are required" });
802
+ }
803
+ try {
804
+ options.pinStore.pinFile(profileId, filePath, groupId);
805
+ return { success: true };
806
+ } catch (error) {
807
+ return reply.status(404).send({ error: error instanceof Error ? error.message : "Pin group not found" });
808
+ }
809
+ });
810
+ app.delete("/files/pin", async (req, reply) => {
811
+ if (!options.pinStore) return reply.status(503).send({ error: "Session pins are not configured" });
812
+ const { filePath, profileId } = req.query;
813
+ if (!filePath || !profileId) return reply.status(400).send({ error: "filePath and profileId are required" });
814
+ options.pinStore.unpinFile(profileId, filePath);
815
+ return { success: true };
816
+ });
795
817
  app.put("/:id/pin", async (req, reply) => {
796
818
  if (!options.pinStore) return reply.status(503).send({ error: "Session pins are not configured" });
797
819
  const { id } = req.params;
@@ -823,9 +845,11 @@ async function sessionRoutes(app, options = {}) {
823
845
  }, worktreeMetadata)]));
824
846
  const owner = { type: "profile", id: profileId };
825
847
  const idsByGroup = options.pinStore.listSessionIdsByGroup(owner);
848
+ const filesByGroup = options.pinStore.listFilePathsByGroup(profileId);
826
849
  return {
827
850
  groups: options.pinStore.listGroups(owner).map((group) => ({
828
851
  ...group,
852
+ filePaths: filesByGroup.get(group.id) || [],
829
853
  sessions: (idsByGroup.get(group.id) || []).map((id) => sessionsById.get(id)).filter((session) => Boolean(session)).map(toSessionListItem)
830
854
  }))
831
855
  };
@@ -1054,9 +1078,9 @@ async function sessionRoutes(app, options = {}) {
1054
1078
  skippedSessionFiles: sessionResult.skipped
1055
1079
  };
1056
1080
  } catch (error) {
1057
- const message = error instanceof Error ? error.message : "Failed to move project";
1058
- const status = message.includes("already exists") || message.includes("streaming") ? 409 : 400;
1059
- return reply.status(status).send({ error: message });
1081
+ const message2 = error instanceof Error ? error.message : "Failed to move project";
1082
+ const status = message2.includes("already exists") || message2.includes("streaming") ? 409 : 400;
1083
+ return reply.status(status).send({ error: message2 });
1060
1084
  }
1061
1085
  });
1062
1086
  app.post("/relocate-project", async (req, reply) => {
@@ -1084,8 +1108,8 @@ async function sessionRoutes(app, options = {}) {
1084
1108
  sessionService.invalidateSessionListCache();
1085
1109
  return { success: true, moved: result.moved, skipped: result.skipped };
1086
1110
  } catch (error) {
1087
- const message = error instanceof Error ? error.message : "Failed to relocate project sessions";
1088
- return reply.status(message.includes("already exists") ? 409 : 400).send({ error: message });
1111
+ const message2 = error instanceof Error ? error.message : "Failed to relocate project sessions";
1112
+ return reply.status(message2.includes("already exists") ? 409 : 400).send({ error: message2 });
1089
1113
  }
1090
1114
  });
1091
1115
  app.post("/:id/relocate", async (req, reply) => {
@@ -1121,8 +1145,8 @@ async function sessionRoutes(app, options = {}) {
1121
1145
  sessionService.invalidateSessionListCache();
1122
1146
  return { success: true, path: result.destinationPath, cwd: newCwd };
1123
1147
  } catch (error) {
1124
- const message = error instanceof Error ? error.message : "Failed to move session";
1125
- return reply.status(message.includes("already exists") ? 409 : 400).send({ error: message });
1148
+ const message2 = error instanceof Error ? error.message : "Failed to move session";
1149
+ return reply.status(message2.includes("already exists") ? 409 : 400).send({ error: message2 });
1126
1150
  }
1127
1151
  });
1128
1152
  app.post("/:id/resume", async (req) => {
@@ -1179,8 +1203,8 @@ async function sessionRoutes(app, options = {}) {
1179
1203
  try {
1180
1204
  return await sessionService.updateSessionSkillPolicy(clientId, id, mode, skills || []);
1181
1205
  } catch (error) {
1182
- const message = error instanceof Error ? error.message : "Failed to update session skills";
1183
- return reply.status(message.includes("streaming") ? 409 : 404).send({ error: message });
1206
+ const message2 = error instanceof Error ? error.message : "Failed to update session skills";
1207
+ return reply.status(message2.includes("streaming") ? 409 : 404).send({ error: message2 });
1184
1208
  }
1185
1209
  });
1186
1210
  app.post("/:id/model", async (req, reply) => {
@@ -1192,8 +1216,8 @@ async function sessionRoutes(app, options = {}) {
1192
1216
  try {
1193
1217
  return await sessionService.setSessionModel(clientId, id, provider, modelId);
1194
1218
  } catch (error) {
1195
- const message = error instanceof Error ? error.message : "Failed to change model";
1196
- return reply.status(message.includes("streaming") ? 409 : 400).send({ error: message });
1219
+ const message2 = error instanceof Error ? error.message : "Failed to change model";
1220
+ return reply.status(message2.includes("streaming") ? 409 : 400).send({ error: message2 });
1197
1221
  }
1198
1222
  });
1199
1223
  app.post("/:id/thinking-level", async (req, reply) => {
@@ -1205,8 +1229,8 @@ async function sessionRoutes(app, options = {}) {
1205
1229
  try {
1206
1230
  return await sessionService.setSessionThinkingLevel(clientId, id, level);
1207
1231
  } catch (error) {
1208
- const message = error instanceof Error ? error.message : "Failed to change thinking level";
1209
- return reply.status(message.includes("streaming") ? 409 : 400).send({ error: message });
1232
+ const message2 = error instanceof Error ? error.message : "Failed to change thinking level";
1233
+ return reply.status(message2.includes("streaming") ? 409 : 400).send({ error: message2 });
1210
1234
  }
1211
1235
  });
1212
1236
  app.get("/:id/summary", async (req, reply) => {
@@ -1330,6 +1354,7 @@ async function sessionRoutes(app, options = {}) {
1330
1354
  model: foundSession.model,
1331
1355
  thinkingLevel: foundSession.thinkingLevel,
1332
1356
  isStreaming: foundSession.isStreaming,
1357
+ streamingStartedAt: sessionService.getSessionStreamingStartedAt(foundSession.sessionId),
1333
1358
  activity: options.activityStore?.listForSession(foundSession.sessionId) || []
1334
1359
  }, worktreeMetadata);
1335
1360
  }
@@ -1406,8 +1431,8 @@ async function sessionRoutes(app, options = {}) {
1406
1431
  const result = await sessionService.renameSession(clientId, id, name);
1407
1432
  return result;
1408
1433
  } catch (error) {
1409
- const message = error instanceof Error ? error.message : "Failed to rename session";
1410
- return reply.status(message.includes("not found") ? 404 : 400).send({ error: message });
1434
+ const message2 = error instanceof Error ? error.message : "Failed to rename session";
1435
+ return reply.status(message2.includes("not found") ? 404 : 400).send({ error: message2 });
1411
1436
  }
1412
1437
  });
1413
1438
  app.delete("/:id", async (req, reply) => {
@@ -1440,8 +1465,8 @@ var MEMORY_CATEGORIES = ["rule", "preference", "decision", "fact", "pitfall"];
1440
1465
  var MEMORY_STATUSES = ["active", "pending", "archived"];
1441
1466
  var PINNED_APPLICABILITY_MODES = ["always", "matched"];
1442
1467
  var MemoryRouteError = class extends Error {
1443
- constructor(statusCode, message) {
1444
- super(message);
1468
+ constructor(statusCode, message2) {
1469
+ super(message2);
1445
1470
  this.statusCode = statusCode;
1446
1471
  }
1447
1472
  statusCode;
@@ -1561,7 +1586,10 @@ async function memoryRoutes(app) {
1561
1586
  app.post("/extractions/:id/clear", async (req, reply) => {
1562
1587
  try {
1563
1588
  const body = requestBody(req.body);
1564
- const context = await resolveContext(app, body);
1589
+ const context = await resolveContext(app, {
1590
+ clientId: body.clientId,
1591
+ projectPath: body.projectPath
1592
+ });
1565
1593
  const { id } = req.params;
1566
1594
  requireAccessibleRun(app, context, id);
1567
1595
  app.memoryRuntime.store.clearFailedRun(id);
@@ -1767,13 +1795,13 @@ function clampNumber(value, min, max, fallback) {
1767
1795
  return Math.min(max, Math.max(min, Math.trunc(parsed)));
1768
1796
  }
1769
1797
  function sendError(reply, error) {
1770
- const message = error instanceof Error ? error.message : "Memory request failed";
1771
- if (error instanceof MemoryRouteError) return reply.status(error.statusCode).send({ error: message });
1772
- if (/not found/i.test(message)) return reply.status(404).send({ error: message });
1773
- if (/modified|already exists|not pending|not failed|not completed|cannot be requeued|superseded/i.test(message)) {
1774
- return reply.status(409).send({ error: message });
1798
+ const message2 = error instanceof Error ? error.message : "Memory request failed";
1799
+ if (error instanceof MemoryRouteError) return reply.status(error.statusCode).send({ error: message2 });
1800
+ if (/not found/i.test(message2)) return reply.status(404).send({ error: message2 });
1801
+ if (/modified|already exists|not pending|not failed|not completed|cannot be requeued|superseded/i.test(message2)) {
1802
+ return reply.status(409).send({ error: message2 });
1775
1803
  }
1776
- return reply.status(400).send({ error: message });
1804
+ return reply.status(400).send({ error: message2 });
1777
1805
  }
1778
1806
 
1779
1807
  // src/routes/files.ts
@@ -2186,9 +2214,9 @@ async function fileRoutes(app) {
2186
2214
  mtime: stats.mtimeMs
2187
2215
  };
2188
2216
  } catch (error) {
2189
- const message = error instanceof Error ? error.message : "Unknown archive error";
2217
+ const message2 = error instanceof Error ? error.message : "Unknown archive error";
2190
2218
  return reply.code(415).send({
2191
- error: `Archive preview failed: ${message}`,
2219
+ error: `Archive preview failed: ${message2}`,
2192
2220
  kind: "binary",
2193
2221
  path: resolvedPath,
2194
2222
  mtime: stats.mtimeMs
@@ -2929,12 +2957,12 @@ async function synthesizeSpeech(text, options = {}) {
2929
2957
  continue;
2930
2958
  }
2931
2959
  if (!response.ok) {
2932
- const message = await providerError(response);
2960
+ const message2 = await providerError(response);
2933
2961
  if (isRetryableStatus2(response.status) && attempt < MAX_SYNTHESIS_ATTEMPTS) {
2934
2962
  await wait2(retryDelayMs * attempt);
2935
2963
  continue;
2936
2964
  }
2937
- throw new Error(message);
2965
+ throw new Error(message2);
2938
2966
  }
2939
2967
  const audio = Buffer.from(await response.arrayBuffer());
2940
2968
  if (audio.length === 0) throw new Error("The text-to-speech provider returned an empty response.");
@@ -3202,8 +3230,8 @@ function parseBranchName(name, files) {
3202
3230
  if (!files.length) throw new Error("No git changes to suggest a branch name from");
3203
3231
  return sanitizeBranchName(proposeCommitMessage(files));
3204
3232
  }
3205
- function textFromAssistantMessage(message) {
3206
- return message.content.filter((item) => item.type === "text").map((item) => item.text).join("\n").trim();
3233
+ function textFromAssistantMessage(message2) {
3234
+ return message2.content.filter((item) => item.type === "text").map((item) => item.text).join("\n").trim();
3207
3235
  }
3208
3236
  function joinGitOutput(...parts) {
3209
3237
  return parts.map((part) => part.replace(/^(?:\r?\n)+|(?:\r?\n)+$/g, "")).filter((part) => part.trim()).join("\n\n");
@@ -3365,9 +3393,9 @@ async function generateCommitMessageWithAi(sessionService, clientId, cwd, status
3365
3393
  if (response.stopReason === "error") {
3366
3394
  throw new Error(response.errorMessage || "AI commit message generation failed");
3367
3395
  }
3368
- const message = textFromAssistantMessage(response).replace(/^```(?:text)?/i, "").replace(/```$/i, "").trim().replace(/^['"]|['"]$/g, "").trim();
3369
- if (!message) throw new Error("AI did not return a commit message");
3370
- return message;
3396
+ const message2 = textFromAssistantMessage(response).replace(/^```(?:text)?/i, "").replace(/```$/i, "").trim().replace(/^['"]|['"]$/g, "").trim();
3397
+ if (!message2) throw new Error("AI did not return a commit message");
3398
+ return message2;
3371
3399
  }
3372
3400
  function parseChangedRanges(diff) {
3373
3401
  const changes = {};
@@ -3442,7 +3470,7 @@ async function gitRoutes(app, options = {}) {
3442
3470
  }) || { global: {}, project: {}, effective: DEFAULT_COMMIT_MESSAGE_PROMPTS };
3443
3471
  });
3444
3472
  app.get("/status", async (req, reply) => {
3445
- const { cwd, message, stagedOnly } = req.query;
3473
+ const { cwd, message: message2, stagedOnly } = req.query;
3446
3474
  const resolvedCwd = await resolveGitCwd(cwd);
3447
3475
  try {
3448
3476
  if (!await isGitRepository(resolvedCwd)) {
@@ -3457,7 +3485,7 @@ async function gitRoutes(app, options = {}) {
3457
3485
  cwd: resolvedCwd,
3458
3486
  isRepository: true,
3459
3487
  files,
3460
- message: message?.trim() || (files.length ? proposeCommitMessage(files) : ""),
3488
+ message: message2?.trim() || (files.length ? proposeCommitMessage(files) : ""),
3461
3489
  output
3462
3490
  };
3463
3491
  } catch (error) {
@@ -3494,8 +3522,8 @@ async function gitRoutes(app, options = {}) {
3494
3522
  commits: commits.slice(0, GIT_HISTORY_PAGE_SIZE)
3495
3523
  };
3496
3524
  } catch (error) {
3497
- const message = error instanceof Error ? error.message : "Failed to load git history";
3498
- return reply.status(400).send({ error: message });
3525
+ const message2 = error instanceof Error ? error.message : "Failed to load git history";
3526
+ return reply.status(400).send({ error: message2 });
3499
3527
  }
3500
3528
  });
3501
3529
  app.get("/branches", async (req, reply) => {
@@ -3556,8 +3584,8 @@ async function gitRoutes(app, options = {}) {
3556
3584
  return reply.status(400).send({ error: "No git changes to generate a commit message from" });
3557
3585
  }
3558
3586
  const prompts = options.commitMessagePrompts?.get(resolvedCwd).effective || DEFAULT_COMMIT_MESSAGE_PROMPTS;
3559
- const message = await generateCommitMessageWithAi(app.services.sessions, clientId, resolvedCwd, status, diff, prompts);
3560
- return { cwd: resolvedCwd, message, files };
3587
+ const message2 = await generateCommitMessageWithAi(app.services.sessions, clientId, resolvedCwd, status, diff, prompts);
3588
+ return { cwd: resolvedCwd, message: message2, files };
3561
3589
  } catch (error) {
3562
3590
  const errorMessage2 = error instanceof Error ? error.message : "Failed to generate commit message";
3563
3591
  return reply.status(400).send({ error: errorMessage2 });
@@ -3636,8 +3664,8 @@ async function gitRoutes(app, options = {}) {
3636
3664
  app.post("/commit", async (req, reply) => {
3637
3665
  const body = req.body || {};
3638
3666
  const resolvedCwd = await resolveGitCwd(body.cwd);
3639
- const message = body.message?.trim();
3640
- if (!message) {
3667
+ const message2 = body.message?.trim();
3668
+ if (!message2) {
3641
3669
  return reply.status(400).send({ error: "Commit message is required" });
3642
3670
  }
3643
3671
  try {
@@ -3648,12 +3676,12 @@ async function gitRoutes(app, options = {}) {
3648
3676
  if (!files.length) {
3649
3677
  return reply.status(400).send({ error: "No changes to commit" });
3650
3678
  }
3651
- const output = await runGit(resolvedCwd, ["commit", "-m", message]);
3679
+ const output = await runGit(resolvedCwd, ["commit", "-m", message2]);
3652
3680
  const commit = (await runGit(resolvedCwd, ["rev-parse", "HEAD"])).trim();
3653
- recordCommitActivity(options, { sessionId: body.sessionId, cwd: resolvedCwd, message, commit, files, mode: "commit" });
3681
+ recordCommitActivity(options, { sessionId: body.sessionId, cwd: resolvedCwd, message: message2, commit, files, mode: "commit" });
3654
3682
  return {
3655
3683
  cwd: resolvedCwd,
3656
- message,
3684
+ message: message2,
3657
3685
  files,
3658
3686
  commit,
3659
3687
  output
@@ -3664,7 +3692,7 @@ async function gitRoutes(app, options = {}) {
3664
3692
  }
3665
3693
  });
3666
3694
  app.get("/amend-status", async (req, reply) => {
3667
- const { cwd, message } = req.query;
3695
+ const { cwd, message: message2 } = req.query;
3668
3696
  const resolvedCwd = await resolveGitCwd(cwd);
3669
3697
  try {
3670
3698
  await ensureHasCommit(resolvedCwd);
@@ -3675,7 +3703,7 @@ async function gitRoutes(app, options = {}) {
3675
3703
  return {
3676
3704
  cwd: resolvedCwd,
3677
3705
  files: parseStatusFiles(status),
3678
- message: message?.trim() || previousMessage.trim()
3706
+ message: message2?.trim() || previousMessage.trim()
3679
3707
  };
3680
3708
  } catch (error) {
3681
3709
  const errorMessage2 = error instanceof Error ? error.message : "Failed to prepare git amend";
@@ -3685,8 +3713,8 @@ async function gitRoutes(app, options = {}) {
3685
3713
  app.post("/amend", async (req, reply) => {
3686
3714
  const body = req.body || {};
3687
3715
  const resolvedCwd = await resolveGitCwd(body.cwd);
3688
- const message = body.message?.trim();
3689
- if (!message) {
3716
+ const message2 = body.message?.trim();
3717
+ if (!message2) {
3690
3718
  return reply.status(400).send({ error: "Commit message is required" });
3691
3719
  }
3692
3720
  try {
@@ -3694,12 +3722,12 @@ async function gitRoutes(app, options = {}) {
3694
3722
  await runGit(resolvedCwd, ["add", "-A"]);
3695
3723
  const status = await runGit(resolvedCwd, ["status", "--porcelain"]);
3696
3724
  const files = parseStatusFiles(status);
3697
- const output = await runGit(resolvedCwd, ["commit", "--amend", "--allow-empty", "-m", message]);
3725
+ const output = await runGit(resolvedCwd, ["commit", "--amend", "--allow-empty", "-m", message2]);
3698
3726
  const commit = (await runGit(resolvedCwd, ["rev-parse", "HEAD"])).trim();
3699
- recordCommitActivity(options, { sessionId: body.sessionId, cwd: resolvedCwd, message, commit, files, mode: "amend" });
3727
+ recordCommitActivity(options, { sessionId: body.sessionId, cwd: resolvedCwd, message: message2, commit, files, mode: "amend" });
3700
3728
  return {
3701
3729
  cwd: resolvedCwd,
3702
- message,
3730
+ message: message2,
3703
3731
  files,
3704
3732
  commit,
3705
3733
  output
@@ -3719,8 +3747,8 @@ async function gitRoutes(app, options = {}) {
3719
3747
  changes: parseChangedRanges(diff)
3720
3748
  };
3721
3749
  } catch (error) {
3722
- const message = error instanceof Error ? error.message : "Failed to run git diff";
3723
- return reply.status(400).send({ error: message });
3750
+ const message2 = error instanceof Error ? error.message : "Failed to run git diff";
3751
+ return reply.status(400).send({ error: message2 });
3724
3752
  }
3725
3753
  });
3726
3754
  app.get("/diff", async (req, reply) => {
@@ -3731,8 +3759,8 @@ async function gitRoutes(app, options = {}) {
3731
3759
  if (commit) {
3732
3760
  const diff2 = await runGit(resolvedCwd, ["show", "--format=", "--patch", commit]);
3733
3761
  const remainingBytes2 = MAX_SLASH_COMMAND_OUTPUT_BYTES - Buffer.byteLength(diff2);
3734
- const stat10 = remainingBytes2 > 0 ? await runGit(resolvedCwd, ["show", "--format=", "--stat", commit], remainingBytes2) : "";
3735
- return { cwd: resolvedCwd, scope: `commit-${commit}`, commit, stat: stat10, diff: diff2 };
3762
+ const stat11 = remainingBytes2 > 0 ? await runGit(resolvedCwd, ["show", "--format=", "--stat", commit], remainingBytes2) : "";
3763
+ return { cwd: resolvedCwd, scope: `commit-${commit}`, commit, stat: stat11, diff: diff2 };
3736
3764
  }
3737
3765
  const scope = parseDiffScope(rawScope);
3738
3766
  const pathArgs = path13 ? ["--", path13] : [];
@@ -3741,11 +3769,11 @@ async function gitRoutes(app, options = {}) {
3741
3769
  diff = await appendUntrackedDiff(resolvedCwd, diff, [], path13, MAX_SLASH_COMMAND_OUTPUT_BYTES);
3742
3770
  }
3743
3771
  const remainingBytes = MAX_SLASH_COMMAND_OUTPUT_BYTES - Buffer.byteLength(diff);
3744
- let stat9 = remainingBytes > 0 ? await getDiff(resolvedCwd, ["--stat", ...pathArgs], scope, remainingBytes) : "";
3772
+ let stat10 = remainingBytes > 0 ? await getDiff(resolvedCwd, ["--stat", ...pathArgs], scope, remainingBytes) : "";
3745
3773
  if (includeUntracked === "true" && scope !== "staged" && remainingBytes > 0) {
3746
- stat9 = await appendUntrackedDiff(resolvedCwd, stat9, ["--stat"], path13, remainingBytes);
3774
+ stat10 = await appendUntrackedDiff(resolvedCwd, stat10, ["--stat"], path13, remainingBytes);
3747
3775
  }
3748
- return { cwd: resolvedCwd, scope, path: path13, stat: stat9, diff };
3776
+ return { cwd: resolvedCwd, scope, path: path13, stat: stat10, diff };
3749
3777
  } catch (error) {
3750
3778
  if (error instanceof OversizedGitOutputError) {
3751
3779
  return {
@@ -3756,8 +3784,8 @@ async function gitRoutes(app, options = {}) {
3756
3784
  message: error.message
3757
3785
  };
3758
3786
  }
3759
- const message = error instanceof Error ? error.message : "Failed to run git diff";
3760
- return reply.status(400).send({ error: message });
3787
+ const message2 = error instanceof Error ? error.message : "Failed to run git diff";
3788
+ return reply.status(400).send({ error: message2 });
3761
3789
  }
3762
3790
  });
3763
3791
  }
@@ -3935,10 +3963,10 @@ var ProjectTaskStore = class {
3935
3963
  if (!task) throw new ProjectTaskNotFoundError("Task not found");
3936
3964
  return task;
3937
3965
  }
3938
- assertChanged(id, changes, message) {
3966
+ assertChanged(id, changes, message2) {
3939
3967
  if (changes) return;
3940
3968
  if (!this.get(id)) throw new ProjectTaskNotFoundError("Task not found");
3941
- throw new ProjectTaskConflictError(message);
3969
+ throw new ProjectTaskConflictError(message2);
3942
3970
  }
3943
3971
  };
3944
3972
  function isProjectTaskStatus(value) {
@@ -4108,8 +4136,8 @@ async function taskRoutes(app, options) {
4108
4136
  }
4109
4137
  });
4110
4138
  }
4111
- function textFromAssistantMessage2(message) {
4112
- return message.content.filter((item) => item.type === "text").map((item) => item.text).join("\n").trim();
4139
+ function textFromAssistantMessage2(message2) {
4140
+ return message2.content.filter((item) => item.type === "text").map((item) => item.text).join("\n").trim();
4113
4141
  }
4114
4142
  function taskPolishPrompt(title, prompt) {
4115
4143
  return `Polish this project task so it is ready for an AI coding agent to execute.
@@ -4202,11 +4230,11 @@ function optionalString3(value, field) {
4202
4230
  return value.trim() || null;
4203
4231
  }
4204
4232
  function sendTaskError(reply, error, validationStatus = 409) {
4205
- const message = error instanceof Error ? error.message : "Task request failed";
4206
- if (error instanceof ProjectTaskNotFoundError) return reply.status(404).send({ error: message });
4207
- if (error instanceof ProjectTaskConflictError) return reply.status(409).send({ error: message });
4208
- if (error instanceof ProjectTaskValidationError) return reply.status(validationStatus).send({ error: message });
4209
- return reply.status(500).send({ error: message });
4233
+ const message2 = error instanceof Error ? error.message : "Task request failed";
4234
+ if (error instanceof ProjectTaskNotFoundError) return reply.status(404).send({ error: message2 });
4235
+ if (error instanceof ProjectTaskConflictError) return reply.status(409).send({ error: message2 });
4236
+ if (error instanceof ProjectTaskValidationError) return reply.status(validationStatus).send({ error: message2 });
4237
+ return reply.status(500).send({ error: message2 });
4210
4238
  }
4211
4239
 
4212
4240
  // src/routes/feishu-gateway.ts
@@ -4222,8 +4250,8 @@ async function feishuGatewayRoutes(app, options) {
4222
4250
  function sendFeishuGatewayError(reply, error) {
4223
4251
  const maybeStatusCode = error?.statusCode;
4224
4252
  const statusCode = typeof maybeStatusCode === "number" ? maybeStatusCode : 500;
4225
- const message = error instanceof Error ? error.message : "Feishu gateway request failed";
4226
- return reply.status(statusCode).send({ error: message });
4253
+ const message2 = error instanceof Error ? error.message : "Feishu gateway request failed";
4254
+ return reply.status(statusCode).send({ error: message2 });
4227
4255
  }
4228
4256
 
4229
4257
  // src/routes/gateways.ts
@@ -4258,8 +4286,8 @@ async function gatewayRoutes(app, options) {
4258
4286
  });
4259
4287
  }
4260
4288
  function sendGatewayError(reply, error) {
4261
- const message = error instanceof Error ? error.message : "Gateway settings request failed";
4262
- return reply.status(400).send({ error: message });
4289
+ const message2 = error instanceof Error ? error.message : "Gateway settings request failed";
4290
+ return reply.status(400).send({ error: message2 });
4263
4291
  }
4264
4292
 
4265
4293
  // src/routes/wecom-gateway.ts
@@ -4317,8 +4345,8 @@ async function wecomGatewayRoutes(app, options) {
4317
4345
  }
4318
4346
  function sendWecomError(reply, error) {
4319
4347
  const statusCode = Number(error?.statusCode) || 400;
4320
- const message = error instanceof Error ? error.message : "WeCom gateway request failed";
4321
- return reply.status(statusCode).send({ error: message });
4348
+ const message2 = error instanceof Error ? error.message : "WeCom gateway request failed";
4349
+ return reply.status(statusCode).send({ error: message2 });
4322
4350
  }
4323
4351
 
4324
4352
  // src/routes/git-hosting.ts
@@ -4360,15 +4388,15 @@ var GiteaSettingsStore = class {
4360
4388
  return this.get();
4361
4389
  }
4362
4390
  clear() {
4363
- this.db.prepare("DELETE FROM security_settings WHERE key IN (?, ?)").run(SERVER_URL_KEY, TOKEN_KEY);
4391
+ this.db.prepare("DELETE FROM application_settings WHERE key IN (?, ?)").run(SERVER_URL_KEY, TOKEN_KEY);
4364
4392
  }
4365
4393
  value(key) {
4366
- const row = this.db.prepare("SELECT value FROM security_settings WHERE key = ?").get(key);
4394
+ const row = this.db.prepare("SELECT value FROM application_settings WHERE key = ?").get(key);
4367
4395
  return row?.value;
4368
4396
  }
4369
4397
  set(key, value) {
4370
4398
  this.db.prepare(`
4371
- INSERT INTO security_settings (key, value, updated_at)
4399
+ INSERT INTO application_settings (key, value, updated_at)
4372
4400
  VALUES (?, ?, ?)
4373
4401
  ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
4374
4402
  `).run(key, value, (/* @__PURE__ */ new Date()).toISOString());
@@ -4423,7 +4451,7 @@ var GithubSettingsStore = class {
4423
4451
  const value = proxyUrl.trim();
4424
4452
  if (value) new URL(value);
4425
4453
  if (value) this.set(PROXY_URL_KEY, value);
4426
- else this.db.prepare("DELETE FROM security_settings WHERE key = ?").run(PROXY_URL_KEY);
4454
+ else this.db.prepare("DELETE FROM application_settings WHERE key = ?").run(PROXY_URL_KEY);
4427
4455
  return this.get();
4428
4456
  }
4429
4457
  proxyEnv() {
@@ -4431,15 +4459,15 @@ var GithubSettingsStore = class {
4431
4459
  return proxyUrl ? githubProxyEnvFromUrl(proxyUrl) : {};
4432
4460
  }
4433
4461
  clear() {
4434
- this.db.prepare("DELETE FROM security_settings WHERE key IN (?, ?)").run(SERVER_URL_KEY2, TOKEN_KEY2);
4462
+ this.db.prepare("DELETE FROM application_settings WHERE key IN (?, ?)").run(SERVER_URL_KEY2, TOKEN_KEY2);
4435
4463
  }
4436
4464
  value(key) {
4437
- const row = this.db.prepare("SELECT value FROM security_settings WHERE key = ?").get(key);
4465
+ const row = this.db.prepare("SELECT value FROM application_settings WHERE key = ?").get(key);
4438
4466
  return row?.value;
4439
4467
  }
4440
4468
  set(key, value) {
4441
4469
  this.db.prepare(`
4442
- INSERT INTO security_settings (key, value, updated_at)
4470
+ INSERT INTO application_settings (key, value, updated_at)
4443
4471
  VALUES (?, ?, ?)
4444
4472
  ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
4445
4473
  `).run(key, value, (/* @__PURE__ */ new Date()).toISOString());
@@ -4462,8 +4490,8 @@ var maxDiffBuffer = 10 * 1024 * 1024;
4462
4490
  var githubProxyCheckArgs = ["-fsSL", "--connect-timeout", "5", "--max-time", "10", "https://www.google.com/generate_204"];
4463
4491
  var githubProxyCountryArgs = ["-fsSL", "--connect-timeout", "5", "--max-time", "10", "https://ipinfo.io/country"];
4464
4492
  var proxyEnvKeys = ["ALL_PROXY", "HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "all_proxy", "http_proxy", "https_proxy", "no_proxy"];
4465
- function textFromAssistantMessage3(message) {
4466
- return message.content.filter((item) => item.type === "text").map((item) => item.text).join("\n").trim();
4493
+ function textFromAssistantMessage3(message2) {
4494
+ return message2.content.filter((item) => item.type === "text").map((item) => item.text).join("\n").trim();
4467
4495
  }
4468
4496
  async function runGit2(cwd, args) {
4469
4497
  const { stdout } = await execFileAsync2("git", args, { cwd, maxBuffer: maxDiffBuffer });
@@ -4793,8 +4821,8 @@ async function gitHostingRoutes(app, options) {
4793
4821
  }
4794
4822
  function sendError2(reply, error, status = 500) {
4795
4823
  const maybeStatus = error?.statusCode;
4796
- const errorStatus2 = typeof maybeStatus === "number" ? maybeStatus : status;
4797
- return reply.status(errorStatus2).send({ error: error instanceof Error ? error.message : "Git integration request failed" });
4824
+ const errorStatus3 = typeof maybeStatus === "number" ? maybeStatus : status;
4825
+ return reply.status(errorStatus3).send({ error: error instanceof Error ? error.message : "Git integration request failed" });
4798
4826
  }
4799
4827
 
4800
4828
  // src/routes/review-sources.ts
@@ -4945,6 +4973,72 @@ async function reviewSourceRoutes(app, options) {
4945
4973
  });
4946
4974
  }
4947
4975
 
4976
+ // src/routes/model-window-kickoffs.ts
4977
+ import { stat as stat4 } from "node:fs/promises";
4978
+ var modelWindowKickoffRoutes = async (app, options) => {
4979
+ app.get("/", async () => ({ kickoffs: options.store.list() }));
4980
+ app.post("/", async (req, reply) => {
4981
+ try {
4982
+ const input = await validateInput(options.sessions, req.body);
4983
+ return reply.status(201).send({ kickoff: options.store.create(input) });
4984
+ } catch (error) {
4985
+ return reply.status(errorStatus(error, 400)).send({ error: message(error) });
4986
+ }
4987
+ });
4988
+ app.put("/:id", async (req, reply) => {
4989
+ try {
4990
+ const input = await validateInput(options.sessions, req.body);
4991
+ return { kickoff: options.store.update(req.params.id, input) };
4992
+ } catch (error) {
4993
+ const fallback = message(error).includes("not found") ? 404 : 400;
4994
+ return reply.status(errorStatus(error, fallback)).send({ error: message(error) });
4995
+ }
4996
+ });
4997
+ app.delete("/:id", async (req, reply) => {
4998
+ try {
4999
+ options.store.delete(req.params.id);
5000
+ return reply.status(204).send();
5001
+ } catch (error) {
5002
+ return reply.status(404).send({ error: message(error) });
5003
+ }
5004
+ });
5005
+ app.get("/notification-channels", async () => ({ channels: options.notifications.list() }));
5006
+ app.post("/notification-channels/wecom", async (req, reply) => {
5007
+ try {
5008
+ return { channel: options.notifications.saveWecom(req.body) };
5009
+ } catch (error) {
5010
+ return reply.status(400).send({ error: message(error) });
5011
+ }
5012
+ });
5013
+ app.delete("/notification-channels/:id", async (req, reply) => {
5014
+ try {
5015
+ options.notifications.delete(req.params.id);
5016
+ return reply.status(204).send();
5017
+ } catch (error) {
5018
+ return reply.status(404).send({ error: message(error) });
5019
+ }
5020
+ });
5021
+ };
5022
+ async function validateInput(sessions, input) {
5023
+ if (!input?.profileId || !input.provider || !input.modelId) throw new Error("Profile, provider, and model are required");
5024
+ const models = await sessions.listAgentProfileModels(input.profileId);
5025
+ if (!models.some((model) => model.provider === input.provider && model.id === input.modelId)) {
5026
+ throw new Error(`Unknown model: ${input.provider}/${input.modelId}`);
5027
+ }
5028
+ const projectPath = await resolveAllowedExistingPath(input.projectPath || "~");
5029
+ if (!(await stat4(projectPath)).isDirectory()) throw new Error("Project path must be a directory");
5030
+ return { ...input, projectPath };
5031
+ }
5032
+ function errorStatus(error, fallback) {
5033
+ return typeof error === "object" && error && "statusCode" in error && typeof error.statusCode === "number" ? error.statusCode : fallback;
5034
+ }
5035
+ function message(error) {
5036
+ return error instanceof Error ? error.message : String(error);
5037
+ }
5038
+
5039
+ // src/routes/auth.ts
5040
+ import rateLimit from "@fastify/rate-limit";
5041
+
4948
5042
  // src/auth/request.ts
4949
5043
  function getRequestContext(req, trustProxy) {
4950
5044
  const forwardedFor = req.headers["x-forwarded-for"];
@@ -5092,17 +5186,70 @@ function mapRow2(row) {
5092
5186
  };
5093
5187
  }
5094
5188
 
5189
+ // src/services/user-prompt-store.ts
5190
+ import { randomUUID as randomUUID3 } from "crypto";
5191
+ var UserPromptStore = class {
5192
+ constructor(db) {
5193
+ this.db = db;
5194
+ }
5195
+ db;
5196
+ list() {
5197
+ return this.db.prepare(`
5198
+ SELECT id, name, content, created_at, updated_at
5199
+ FROM user_prompts
5200
+ ORDER BY lower(name), id
5201
+ `).all().map(mapRow3);
5202
+ }
5203
+ create(input) {
5204
+ const id = randomUUID3();
5205
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5206
+ this.db.prepare(`
5207
+ INSERT INTO user_prompts (id, name, content, created_at, updated_at)
5208
+ VALUES (?, ?, ?, ?, ?)
5209
+ `).run(id, input.name.trim(), input.content.trim(), now, now);
5210
+ return this.get(id);
5211
+ }
5212
+ update(id, input) {
5213
+ const result = this.db.prepare(`
5214
+ UPDATE user_prompts SET name = ?, content = ?, updated_at = ?
5215
+ WHERE id = ?
5216
+ `).run(input.name.trim(), input.content.trim(), (/* @__PURE__ */ new Date()).toISOString(), id);
5217
+ if (!result.changes) throw new Error(`User prompt not found: ${id}`);
5218
+ return this.get(id);
5219
+ }
5220
+ delete(id) {
5221
+ this.db.prepare("DELETE FROM user_prompts WHERE id = ?").run(id);
5222
+ }
5223
+ get(id) {
5224
+ const row = this.db.prepare(`
5225
+ SELECT id, name, content, created_at, updated_at
5226
+ FROM user_prompts WHERE id = ?
5227
+ `).get(id);
5228
+ return row ? mapRow3(row) : null;
5229
+ }
5230
+ };
5231
+ function mapRow3(row) {
5232
+ return {
5233
+ id: row.id,
5234
+ name: row.name,
5235
+ content: row.content,
5236
+ createdAt: row.created_at,
5237
+ updatedAt: row.updated_at
5238
+ };
5239
+ }
5240
+
5095
5241
  // src/routes/auth.ts
5096
5242
  async function authRoutes(app, options) {
5097
5243
  const { config, sessions, audit, totp, rateLimiter, db } = options;
5098
5244
  const presetStore = new SkillPresetStore(db);
5245
+ const userPromptStore = new UserPromptStore(db);
5099
5246
  function getPreferenceValue(key) {
5100
- const row = db.prepare("SELECT value FROM security_settings WHERE key = ?").get(key);
5247
+ const row = db.prepare("SELECT value FROM application_settings WHERE key = ?").get(key);
5101
5248
  return row?.value;
5102
5249
  }
5103
5250
  function setPreferenceValue(key, value) {
5104
5251
  db.prepare(`
5105
- INSERT INTO security_settings (key, value, updated_at)
5252
+ INSERT INTO application_settings (key, value, updated_at)
5106
5253
  VALUES (?, ?, ?)
5107
5254
  ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
5108
5255
  `).run(key, value, (/* @__PURE__ */ new Date()).toISOString());
@@ -5176,6 +5323,13 @@ async function authRoutes(app, options) {
5176
5323
  }
5177
5324
  return { name, mode, skills };
5178
5325
  }
5326
+ function parseUserPromptBody(body) {
5327
+ const name = typeof body.name === "string" ? body.name.trim() : "";
5328
+ const content = typeof body.content === "string" ? body.content.trim() : "";
5329
+ if (!name) throw new Error("name is required");
5330
+ if (!content) throw new Error("content is required");
5331
+ return { name, content };
5332
+ }
5179
5333
  function isUniqueConstraintError(error) {
5180
5334
  return error instanceof Error && /unique/i.test(error.message);
5181
5335
  }
@@ -5185,6 +5339,12 @@ async function authRoutes(app, options) {
5185
5339
  }
5186
5340
  return reply.status(400).send({ error: error instanceof Error ? error.message : "Invalid preset payload" });
5187
5341
  }
5342
+ function sendUserPromptError(reply, error) {
5343
+ if (isUniqueConstraintError(error)) {
5344
+ return reply.status(409).send({ error: "A prompt with that name already exists" });
5345
+ }
5346
+ return reply.status(400).send({ error: error instanceof Error ? error.message : "Invalid prompt payload" });
5347
+ }
5188
5348
  app.get("/me", async (req, reply) => {
5189
5349
  const session = renewSessionFromRequest(req, reply, config, sessions);
5190
5350
  return {
@@ -5367,6 +5527,47 @@ async function authRoutes(app, options) {
5367
5527
  presetStore.delete(req.params.id, session.username);
5368
5528
  return { success: true };
5369
5529
  });
5530
+ await app.register(async (userPromptRoutes) => {
5531
+ await userPromptRoutes.register(rateLimit, {
5532
+ max: 100,
5533
+ timeWindow: "1 minute",
5534
+ keyGenerator: (req) => getRequestContext(req, config.trustProxy).ip
5535
+ });
5536
+ userPromptRoutes.get("/user-prompts", async (req, reply) => {
5537
+ const session = requireAuth(req, reply);
5538
+ if (!session) return;
5539
+ return { prompts: userPromptStore.list() };
5540
+ });
5541
+ userPromptRoutes.post("/user-prompts", async (req, reply) => {
5542
+ const session = requireAuth(req, reply);
5543
+ if (!session) return;
5544
+ try {
5545
+ return { prompt: userPromptStore.create(parseUserPromptBody(req.body || {})) };
5546
+ } catch (error) {
5547
+ return sendUserPromptError(reply, error);
5548
+ }
5549
+ });
5550
+ userPromptRoutes.patch("/user-prompts/:id", async (req, reply) => {
5551
+ const session = requireAuth(req, reply);
5552
+ if (!session) return;
5553
+ try {
5554
+ return {
5555
+ prompt: userPromptStore.update(
5556
+ req.params.id,
5557
+ parseUserPromptBody(req.body || {})
5558
+ )
5559
+ };
5560
+ } catch (error) {
5561
+ return sendUserPromptError(reply, error);
5562
+ }
5563
+ });
5564
+ userPromptRoutes.delete("/user-prompts/:id", async (req, reply) => {
5565
+ const session = requireAuth(req, reply);
5566
+ if (!session) return;
5567
+ userPromptStore.delete(req.params.id);
5568
+ return { success: true };
5569
+ });
5570
+ });
5370
5571
  app.post("/2fa/setup", async (req, reply) => {
5371
5572
  const session = requireAuth(req, reply);
5372
5573
  if (!session) return;
@@ -5450,8 +5651,8 @@ function isAllowedRequestOrigin(req) {
5450
5651
  var IMAGE_MIME_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
5451
5652
  var MAX_IMAGE_COUNT = 4;
5452
5653
  var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
5453
- function invalidImage(message = "The image could not be read. Try selecting it again.") {
5454
- return { ok: false, code: "image_malformed", message };
5654
+ function invalidImage(message2 = "The image could not be read. Try selecting it again.") {
5655
+ return { ok: false, code: "image_malformed", message: message2 };
5455
5656
  }
5456
5657
  function sniffImageMimeType(bytes) {
5457
5658
  if (bytes.subarray(0, 3).equals(Buffer.from([255, 216, 255]))) return "image/jpeg";
@@ -5542,10 +5743,10 @@ function extensionForMimeType(mimeType) {
5542
5743
  }
5543
5744
 
5544
5745
  // src/ws/safe-send.ts
5545
- function sendJson(socket, message) {
5746
+ function sendJson(socket, message2) {
5546
5747
  if (socket.readyState !== socket.OPEN) return;
5547
5748
  try {
5548
- socket.send(JSON.stringify(message));
5749
+ socket.send(JSON.stringify(message2));
5549
5750
  } catch {
5550
5751
  }
5551
5752
  }
@@ -5618,6 +5819,7 @@ function formatCompactResult(result, status) {
5618
5819
  }
5619
5820
  async function chatWebSocket(app) {
5620
5821
  const sessionService = app.services.sessions;
5822
+ const clients = /* @__PURE__ */ new Set();
5621
5823
  app.get("/ws/chat", { websocket: true }, (socket, req) => {
5622
5824
  const auth = app.authServices;
5623
5825
  if (!isAllowedRequestOrigin(req)) {
@@ -5645,7 +5847,17 @@ async function chatWebSocket(app) {
5645
5847
  return;
5646
5848
  }
5647
5849
  const safeClientId = clientId;
5850
+ const connectedClient = { socket, watchedSessionId: void 0 };
5851
+ clients.add(connectedClient);
5648
5852
  console.log(`Client connected: ${safeClientId}`);
5853
+ sessionService.cancelCleanup(safeClientId);
5854
+ function broadcastToSession(sessionId, message2) {
5855
+ for (const client of clients) {
5856
+ if (client === connectedClient || client.watchedSessionId === sessionId) {
5857
+ sendJson(client.socket, message2);
5858
+ }
5859
+ }
5860
+ }
5649
5861
  function sendPromptPreflight(sessionId, requestId, accepted, error, imagePaths) {
5650
5862
  sendJson(socket, {
5651
5863
  type: "prompt_preflight",
@@ -5709,10 +5921,24 @@ async function chatWebSocket(app) {
5709
5921
  }
5710
5922
  socket.on("message", async (rawData) => {
5711
5923
  try {
5712
- const message = JSON.parse(rawData.toString());
5713
- switch (message.type) {
5924
+ const message2 = JSON.parse(rawData.toString());
5925
+ switch (message2.type) {
5926
+ case "watch": {
5927
+ connectedClient.watchedSessionId = message2.payload?.sessionId;
5928
+ const targetSessionId = connectedClient.watchedSessionId;
5929
+ if (targetSessionId) {
5930
+ const isStreaming = sessionService.isSessionStreaming(targetSessionId);
5931
+ sendJson(socket, {
5932
+ type: "streaming_state",
5933
+ sessionId: targetSessionId,
5934
+ isStreaming,
5935
+ ...isStreaming ? { streamingStartedAt: sessionService.getSessionStreamingStartedAt(targetSessionId) } : {}
5936
+ });
5937
+ }
5938
+ break;
5939
+ }
5714
5940
  case "prompt": {
5715
- const session2 = await ensureTargetSession(message.payload?.sessionId);
5941
+ const session2 = await ensureTargetSession(message2.payload?.sessionId);
5716
5942
  if (!session2) {
5717
5943
  sendJson(socket, {
5718
5944
  type: "error",
@@ -5720,14 +5946,16 @@ async function chatWebSocket(app) {
5720
5946
  });
5721
5947
  return;
5722
5948
  }
5723
- const imageResult = validateImages(message.payload?.images, session2.model);
5949
+ const imageResult = validateImages(message2.payload?.images, session2.model);
5724
5950
  if (!imageResult.ok) {
5725
- sendPromptPreflight(session2.sessionId, message.payload?.requestId, false, imageResult);
5951
+ sendPromptPreflight(session2.sessionId, message2.payload?.requestId, false, imageResult);
5726
5952
  return;
5727
5953
  }
5728
5954
  sessionService.cancelCleanup(safeClientId);
5729
5955
  const sessionId = session2.sessionId;
5730
- const compactInstructions = parseCompactCommand(message.payload?.text || "");
5956
+ connectedClient.watchedSessionId = sessionId;
5957
+ const streamingStartedAt = sessionService.markSessionStreamingStarted(sessionId);
5958
+ const compactInstructions = parseCompactCommand(message2.payload?.text || "");
5731
5959
  req.log?.info({
5732
5960
  clientId: safeClientId,
5733
5961
  sessionId,
@@ -5751,7 +5979,7 @@ async function chatWebSocket(app) {
5751
5979
  if (recent.every((t) => t.name === recent[0].name && t.input === recent[0].input)) {
5752
5980
  loopDetected = true;
5753
5981
  console.warn(`[loop-detected] tool="${name}" sessionId=${sessionId}`);
5754
- sendJson(socket, {
5982
+ broadcastToSession(sessionId, {
5755
5983
  type: "loop_detected",
5756
5984
  sessionId,
5757
5985
  message: `Loop detected: tool "${name}" called ${MAX_REPEATED_TOOL_CALLS} times with identical input. Stopping automatically.`,
@@ -5766,10 +5994,11 @@ async function chatWebSocket(app) {
5766
5994
  }
5767
5995
  }
5768
5996
  }
5769
- sendJson(socket, {
5997
+ broadcastToSession(sessionId, {
5770
5998
  type: "event",
5771
5999
  sessionId,
5772
- event
6000
+ event,
6001
+ streamingStartedAt
5773
6002
  });
5774
6003
  });
5775
6004
  try {
@@ -5783,9 +6012,9 @@ async function chatWebSocket(app) {
5783
6012
  result
5784
6013
  });
5785
6014
  } else {
5786
- const requestId = message.payload?.requestId;
6015
+ const requestId = message2.payload?.requestId;
5787
6016
  const preparedPrompt = await promptWithUploadedImagePaths(
5788
- message.payload?.text || "",
6017
+ message2.payload?.text || "",
5789
6018
  session2,
5790
6019
  imageResult
5791
6020
  );
@@ -5803,24 +6032,23 @@ async function chatWebSocket(app) {
5803
6032
  });
5804
6033
  } finally {
5805
6034
  unsubscribe();
5806
- if (!loopDetected) {
5807
- sendJson(socket, { type: "status", sessionId, status: "idle" });
5808
- }
6035
+ sessionService.markSessionStreamingFinished(sessionId);
6036
+ broadcastToSession(sessionId, { type: "status", sessionId, status: "idle" });
5809
6037
  }
5810
6038
  break;
5811
6039
  }
5812
6040
  case "steer":
5813
6041
  case "followUp": {
5814
- const session2 = await ensureTargetSession(message.payload?.sessionId);
6042
+ const session2 = await ensureTargetSession(message2.payload?.sessionId);
5815
6043
  if (!session2) break;
5816
- const imageResult = validateImages(message.payload?.images, session2.model);
6044
+ const imageResult = validateImages(message2.payload?.images, session2.model);
5817
6045
  if (!imageResult.ok) {
5818
- sendPromptPreflight(session2.sessionId, message.payload?.requestId, false, imageResult);
6046
+ sendPromptPreflight(session2.sessionId, message2.payload?.requestId, false, imageResult);
5819
6047
  break;
5820
6048
  }
5821
- if (!message.payload?.text && imageResult.images.length === 0) break;
5822
- const requestId = message.payload?.requestId;
5823
- const streamingBehavior = message.type === "steer" ? "steer" : "followUp";
6049
+ if (!message2.payload?.text && imageResult.images.length === 0) break;
6050
+ const requestId = message2.payload?.requestId;
6051
+ const streamingBehavior = message2.type === "steer" ? "steer" : "followUp";
5824
6052
  req.log?.info({
5825
6053
  clientId: safeClientId,
5826
6054
  sessionId: session2.sessionId,
@@ -5830,7 +6058,7 @@ async function chatWebSocket(app) {
5830
6058
  }, "[ai-chat] request");
5831
6059
  await sessionService.runForegroundWithClientProfileProxy(safeClientId, async () => {
5832
6060
  const preparedPrompt = await promptWithUploadedImagePaths(
5833
- message.payload?.text || "",
6061
+ message2.payload?.text || "",
5834
6062
  session2,
5835
6063
  imageResult
5836
6064
  );
@@ -5843,7 +6071,7 @@ async function chatWebSocket(app) {
5843
6071
  break;
5844
6072
  }
5845
6073
  case "abort": {
5846
- const session2 = await ensureTargetSession(message.payload?.sessionId);
6074
+ const session2 = await ensureTargetSession(message2.payload?.sessionId);
5847
6075
  if (session2) {
5848
6076
  const aborted = await abortSessionBestEffort(session2);
5849
6077
  if (!aborted) {
@@ -5854,7 +6082,8 @@ async function chatWebSocket(app) {
5854
6082
  message: "Request did not stop cleanly; the stuck runtime was discarded."
5855
6083
  });
5856
6084
  }
5857
- sendJson(socket, { type: "status", sessionId: session2.sessionId, status: "idle" });
6085
+ sessionService.markSessionStreamingFinished(session2.sessionId);
6086
+ broadcastToSession(session2.sessionId, { type: "status", sessionId: session2.sessionId, status: "idle" });
5858
6087
  }
5859
6088
  break;
5860
6089
  }
@@ -5868,17 +6097,10 @@ async function chatWebSocket(app) {
5868
6097
  }
5869
6098
  });
5870
6099
  socket.on("close", () => {
6100
+ clients.delete(connectedClient);
5871
6101
  console.log(`Client disconnected: ${safeClientId}`);
5872
6102
  unsubscribeMemoryUpdates();
5873
6103
  unsubscribeMemoryRecall();
5874
- const activeSession = sessionService.getSession(safeClientId);
5875
- if (activeSession?.isStreaming) {
5876
- abortSessionBestEffort(activeSession).then((aborted) => {
5877
- if (!aborted) sessionService.forceDisposeBySessionId(activeSession.sessionId);
5878
- }).catch((error) => {
5879
- console.warn("Failed to stop streaming session on disconnect:", error);
5880
- });
5881
- }
5882
6104
  sessionService.scheduleCleanup(safeClientId);
5883
6105
  });
5884
6106
  });
@@ -5946,16 +6168,16 @@ async function terminalWebSocket(app) {
5946
6168
  }));
5947
6169
  socket.on("message", (rawData) => {
5948
6170
  try {
5949
- const message = JSON.parse(rawData.toString());
5950
- switch (message.type) {
6171
+ const message2 = JSON.parse(rawData.toString());
6172
+ switch (message2.type) {
5951
6173
  case "input":
5952
- if (message.terminalId === terminal.id && typeof message.data === "string") {
5953
- terminalManager.writeTo(terminal.id, message.data);
6174
+ if (message2.terminalId === terminal.id && typeof message2.data === "string") {
6175
+ terminalManager.writeTo(terminal.id, message2.data);
5954
6176
  }
5955
6177
  break;
5956
6178
  case "resize":
5957
- if (message.terminalId === terminal.id && Number.isFinite(message.cols) && Number.isFinite(message.rows)) {
5958
- terminalManager.resize(terminal.id, message.cols, message.rows);
6179
+ if (message2.terminalId === terminal.id && Number.isFinite(message2.cols) && Number.isFinite(message2.rows)) {
6180
+ terminalManager.resize(terminal.id, message2.cols, message2.rows);
5959
6181
  }
5960
6182
  break;
5961
6183
  }
@@ -6534,12 +6756,117 @@ var wecomGatewaySchemaMigration = {
6534
6756
  }
6535
6757
  };
6536
6758
 
6759
+ // src/db/migrations/005-model-window-kickoff-schema.ts
6760
+ var modelWindowKickoffSchemaMigration = {
6761
+ version: 5,
6762
+ name: "model-window-kickoff-schema",
6763
+ up(db) {
6764
+ db.exec(`
6765
+ CREATE TABLE IF NOT EXISTS notification_channels (
6766
+ id TEXT PRIMARY KEY,
6767
+ type TEXT NOT NULL,
6768
+ name TEXT NOT NULL,
6769
+ config_json TEXT NOT NULL,
6770
+ created_at TEXT NOT NULL,
6771
+ updated_at TEXT NOT NULL
6772
+ );
6773
+
6774
+ CREATE TABLE IF NOT EXISTS model_window_kickoffs (
6775
+ id TEXT PRIMARY KEY,
6776
+ enabled INTEGER NOT NULL DEFAULT 1,
6777
+ profile_id TEXT NOT NULL,
6778
+ provider TEXT NOT NULL,
6779
+ model_id TEXT NOT NULL,
6780
+ prompt TEXT NOT NULL,
6781
+ window_duration_minutes INTEGER NOT NULL,
6782
+ safety_buffer_seconds INTEGER NOT NULL,
6783
+ notification_channel_id TEXT REFERENCES notification_channels(id) ON DELETE SET NULL,
6784
+ next_run_at TEXT NOT NULL,
6785
+ last_attempt_at TEXT,
6786
+ last_success_at TEXT,
6787
+ last_error TEXT,
6788
+ created_at TEXT NOT NULL,
6789
+ updated_at TEXT NOT NULL
6790
+ );
6791
+
6792
+ CREATE INDEX IF NOT EXISTS model_window_kickoffs_due_idx
6793
+ ON model_window_kickoffs(enabled, next_run_at);
6794
+ `);
6795
+ }
6796
+ };
6797
+
6798
+ // src/db/migrations/006-model-window-kickoff-project-path.ts
6799
+ var modelWindowKickoffProjectPathMigration = {
6800
+ version: 6,
6801
+ name: "model-window-kickoff-project-path",
6802
+ up(db) {
6803
+ db.exec(`
6804
+ ALTER TABLE model_window_kickoffs
6805
+ ADD COLUMN project_path TEXT NOT NULL DEFAULT '~';
6806
+ `);
6807
+ }
6808
+ };
6809
+
6810
+ // src/db/migrations/007-user-prompts-schema.ts
6811
+ var userPromptsSchemaMigration = {
6812
+ version: 7,
6813
+ name: "user-prompts-schema",
6814
+ up(db) {
6815
+ db.exec(`
6816
+ CREATE TABLE user_prompts (
6817
+ id TEXT PRIMARY KEY,
6818
+ name TEXT NOT NULL UNIQUE,
6819
+ content TEXT NOT NULL,
6820
+ created_at TEXT NOT NULL,
6821
+ updated_at TEXT NOT NULL
6822
+ )
6823
+ `);
6824
+ }
6825
+ };
6826
+
6827
+ // src/db/migrations/008-application-settings-table.ts
6828
+ var applicationSettingsTableMigration = {
6829
+ version: 8,
6830
+ name: "application-settings-table",
6831
+ up(db) {
6832
+ db.exec("ALTER TABLE security_settings RENAME TO application_settings");
6833
+ }
6834
+ };
6835
+
6836
+ // src/db/migrations/009-pinned-files-schema.ts
6837
+ var pinnedFilesSchemaMigration = {
6838
+ version: 9,
6839
+ name: "pinned-files-schema",
6840
+ up(db) {
6841
+ db.exec(`
6842
+ CREATE TABLE pinned_files (
6843
+ owner_type TEXT NOT NULL CHECK (owner_type = 'profile'),
6844
+ owner_id TEXT NOT NULL,
6845
+ file_path TEXT NOT NULL,
6846
+ group_id TEXT NOT NULL,
6847
+ created_at TEXT NOT NULL,
6848
+ PRIMARY KEY (owner_type, owner_id, file_path),
6849
+ FOREIGN KEY (owner_type, owner_id, group_id)
6850
+ REFERENCES session_pin_groups(owner_type, owner_id, id) ON DELETE CASCADE
6851
+ );
6852
+
6853
+ CREATE INDEX pinned_files_group_idx
6854
+ ON pinned_files(owner_type, owner_id, group_id, created_at);
6855
+ `);
6856
+ }
6857
+ };
6858
+
6537
6859
  // src/db/migrations/index.ts
6538
6860
  var migrations = [
6539
6861
  applicationSchemaMigration,
6540
6862
  gatewaySchemaMigration,
6541
6863
  projectHistorySchemaMigration,
6542
- wecomGatewaySchemaMigration
6864
+ wecomGatewaySchemaMigration,
6865
+ modelWindowKickoffSchemaMigration,
6866
+ modelWindowKickoffProjectPathMigration,
6867
+ userPromptsSchemaMigration,
6868
+ applicationSettingsTableMigration,
6869
+ pinnedFilesSchemaMigration
6543
6870
  ];
6544
6871
  function runDatabaseMigrations(db) {
6545
6872
  db.exec(`
@@ -6787,18 +7114,18 @@ var TotpService = class {
6787
7114
  this.deleteValue("totp.secret");
6788
7115
  }
6789
7116
  getValue(key) {
6790
- const row = this.db.prepare("SELECT value FROM security_settings WHERE key = ?").get(key);
7117
+ const row = this.db.prepare("SELECT value FROM application_settings WHERE key = ?").get(key);
6791
7118
  return row?.value;
6792
7119
  }
6793
7120
  setValue(key, value) {
6794
7121
  this.db.prepare(`
6795
- INSERT INTO security_settings (key, value, updated_at)
7122
+ INSERT INTO application_settings (key, value, updated_at)
6796
7123
  VALUES (?, ?, ?)
6797
7124
  ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
6798
7125
  `).run(key, value, (/* @__PURE__ */ new Date()).toISOString());
6799
7126
  }
6800
7127
  deleteValue(key) {
6801
- this.db.prepare("DELETE FROM security_settings WHERE key = ?").run(key);
7128
+ this.db.prepare("DELETE FROM application_settings WHERE key = ?").run(key);
6802
7129
  }
6803
7130
  };
6804
7131
 
@@ -7126,6 +7453,7 @@ var PiSessionService = class {
7126
7453
  clientSessions = /* @__PURE__ */ new Map();
7127
7454
  currentClientSession = /* @__PURE__ */ new Map();
7128
7455
  cleanupTimers = /* @__PURE__ */ new Map();
7456
+ streamingStartedAt = /* @__PURE__ */ new Map();
7129
7457
  clientProfiles = /* @__PURE__ */ new Map();
7130
7458
  sessionListCache = /* @__PURE__ */ new Map();
7131
7459
  userMessageCountCache = /* @__PURE__ */ new Map();
@@ -7873,7 +8201,7 @@ var PiSessionService = class {
7873
8201
  path: sessionPath || cwd,
7874
8202
  created: (/* @__PURE__ */ new Date()).toISOString(),
7875
8203
  modified: (/* @__PURE__ */ new Date()).toISOString(),
7876
- messageCount: activeSession.messages.filter((message) => message.role === "user").length,
8204
+ messageCount: activeSession.messages.filter((message2) => message2.role === "user").length,
7877
8205
  cwd,
7878
8206
  firstMessage: typeof firstUserMessage === "string" ? firstUserMessage : void 0,
7879
8207
  isStreaming: activeSession.isStreaming
@@ -8292,12 +8620,32 @@ var PiSessionService = class {
8292
8620
  isSessionStreaming(sessionId) {
8293
8621
  return this.getSessionBySessionId(sessionId)?.isStreaming === true;
8294
8622
  }
8623
+ markSessionStreamingStarted(sessionId) {
8624
+ const startedAt = this.streamingStartedAt.get(sessionId) ?? Date.now();
8625
+ this.streamingStartedAt.set(sessionId, startedAt);
8626
+ return startedAt;
8627
+ }
8628
+ getSessionStreamingStartedAt(sessionId) {
8629
+ const startedAt = this.streamingStartedAt.get(sessionId);
8630
+ if (startedAt !== void 0 || !this.isSessionStreaming(sessionId)) return startedAt;
8631
+ return this.markSessionStreamingStarted(sessionId);
8632
+ }
8633
+ markSessionStreamingFinished(sessionId) {
8634
+ this.streamingStartedAt.delete(sessionId);
8635
+ }
8295
8636
  isCwdStreaming(cwd) {
8296
8637
  return Array.from(this.sessions.values()).some((session) => session.sessionManager.getCwd() === cwd && session.isStreaming === true);
8297
8638
  }
8298
8639
  scheduleCleanup(clientId, delayMs = 5 * 60 * 1e3) {
8299
8640
  this.cancelCleanup(clientId);
8300
8641
  const timer = setTimeout(() => {
8642
+ this.cleanupTimers.delete(clientId);
8643
+ const sessionIds = this.clientSessions.get(clientId) || /* @__PURE__ */ new Set();
8644
+ const isStreaming = Array.from(sessionIds).some((sessionId) => this.sessions.get(sessionId)?.isStreaming);
8645
+ if (isStreaming) {
8646
+ this.scheduleCleanup(clientId, delayMs);
8647
+ return;
8648
+ }
8301
8649
  this.disposeSession(clientId);
8302
8650
  }, delayMs);
8303
8651
  this.cleanupTimers.set(clientId, timer);
@@ -8317,6 +8665,7 @@ var PiSessionService = class {
8317
8665
  if (!session) continue;
8318
8666
  session.dispose();
8319
8667
  this.sessions.delete(sessionId);
8668
+ this.streamingStartedAt.delete(sessionId);
8320
8669
  }
8321
8670
  this.clientSessions.delete(clientId);
8322
8671
  this.currentClientSession.delete(clientId);
@@ -8342,6 +8691,7 @@ var PiSessionService = class {
8342
8691
  session.dispose();
8343
8692
  this.sessions.delete(sessionId);
8344
8693
  }
8694
+ this.streamingStartedAt.delete(sessionId);
8345
8695
  for (const [clientId, sessionIds] of this.clientSessions) {
8346
8696
  sessionIds.delete(sessionId);
8347
8697
  if (this.currentClientSession.get(clientId) === sessionId) {
@@ -8562,7 +8912,7 @@ var PiSessionService = class {
8562
8912
  return (await this.readSessionDirHeader(sessionDir))?.cwd;
8563
8913
  }
8564
8914
  getFirstUserMessageContent(messages) {
8565
- const firstUserMessage = messages.find((message) => Boolean(message) && typeof message === "object" && message.role === "user");
8915
+ const firstUserMessage = messages.find((message2) => Boolean(message2) && typeof message2 === "object" && message2.role === "user");
8566
8916
  return typeof firstUserMessage?.content === "string" ? firstUserMessage.content : void 0;
8567
8917
  }
8568
8918
  async readSessionDirHeader(sessionDir) {
@@ -8678,7 +9028,7 @@ var SessionActivityStore = class {
8678
9028
  WHERE session_id = ?
8679
9029
  ORDER BY created_at ASC, id ASC
8680
9030
  `).all(sessionId);
8681
- return rows.map(mapRow3);
9031
+ return rows.map(mapRow4);
8682
9032
  }
8683
9033
  updatePrStatus(id, status, checkedAt = this.now()) {
8684
9034
  const row = this.db.prepare("SELECT session_id, data_json FROM session_builtin_events WHERE id = ? AND kind = 'pr_created'").get(id);
@@ -8700,7 +9050,7 @@ var SessionActivityStore = class {
8700
9050
  ORDER BY created_at DESC, id DESC
8701
9051
  `).all(...sessionIds);
8702
9052
  for (const row of rows) {
8703
- if (!latest.has(row.session_id)) latest.set(row.session_id, mapRow3(row));
9053
+ if (!latest.has(row.session_id)) latest.set(row.session_id, mapRow4(row));
8704
9054
  }
8705
9055
  return latest;
8706
9056
  }
@@ -8713,7 +9063,7 @@ var SessionActivityStore = class {
8713
9063
  return this.listForSession(sessionId).find((record) => record.id === Number(result.lastInsertRowid));
8714
9064
  }
8715
9065
  };
8716
- function mapRow3(row) {
9066
+ function mapRow4(row) {
8717
9067
  return {
8718
9068
  id: row.id,
8719
9069
  sessionId: row.session_id,
@@ -8732,7 +9082,7 @@ function parseJson(value) {
8732
9082
  }
8733
9083
 
8734
9084
  // src/services/session-pin-store.ts
8735
- import { randomUUID as randomUUID3 } from "crypto";
9085
+ import { randomUUID as randomUUID4 } from "crypto";
8736
9086
  var DEFAULT_PIN_GROUP_ID = "default";
8737
9087
  var SessionPinStore = class {
8738
9088
  constructor(db) {
@@ -8753,7 +9103,7 @@ var SessionPinStore = class {
8753
9103
  this.ensureDefaultGroup(owner);
8754
9104
  const trimmed = name.trim();
8755
9105
  if (!trimmed) throw new Error("Group name is required");
8756
- const id = randomUUID3();
9106
+ const id = randomUUID4();
8757
9107
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
8758
9108
  this.db.prepare(`
8759
9109
  INSERT INTO session_pin_groups (owner_type, owner_id, id, name, is_default, created_at)
@@ -8779,6 +9129,40 @@ var SessionPinStore = class {
8779
9129
  DELETE FROM session_pins WHERE owner_type = ? AND owner_id = ? AND session_id = ?
8780
9130
  `).run(owner.type, owner.id, sessionId);
8781
9131
  }
9132
+ pinFile(profileId, filePath, groupId) {
9133
+ const owner = { type: "profile", id: profileId };
9134
+ this.ensureDefaultGroup(owner);
9135
+ if (!this.db.prepare(`
9136
+ SELECT 1 FROM session_pin_groups WHERE owner_type = 'profile' AND owner_id = ? AND id = ?
9137
+ `).get(profileId, groupId)) {
9138
+ throw new Error("Pin group not found");
9139
+ }
9140
+ this.db.prepare(`
9141
+ INSERT INTO pinned_files (owner_type, owner_id, file_path, group_id, created_at)
9142
+ VALUES ('profile', ?, ?, ?, ?)
9143
+ ON CONFLICT(owner_type, owner_id, file_path) DO UPDATE SET group_id = excluded.group_id
9144
+ `).run(profileId, filePath, groupId, (/* @__PURE__ */ new Date()).toISOString());
9145
+ }
9146
+ unpinFile(profileId, filePath) {
9147
+ this.db.prepare(`
9148
+ DELETE FROM pinned_files WHERE owner_type = 'profile' AND owner_id = ? AND file_path = ?
9149
+ `).run(profileId, filePath);
9150
+ }
9151
+ listFilePathsByGroup(profileId) {
9152
+ const rows = this.db.prepare(`
9153
+ SELECT group_id, file_path
9154
+ FROM pinned_files
9155
+ WHERE owner_type = 'profile' AND owner_id = ?
9156
+ ORDER BY created_at, file_path
9157
+ `).all(profileId);
9158
+ const result = /* @__PURE__ */ new Map();
9159
+ for (const row of rows) {
9160
+ const paths = result.get(row.group_id) || [];
9161
+ paths.push(row.file_path);
9162
+ result.set(row.group_id, paths);
9163
+ }
9164
+ return result;
9165
+ }
8782
9166
  listSessionIdsByGroup(owner) {
8783
9167
  const rows = this.db.prepare(`
8784
9168
  SELECT group_id, session_id
@@ -8833,7 +9217,7 @@ var SkillPolicyStore = class {
8833
9217
  }
8834
9218
  get(sessionId) {
8835
9219
  const row = this.db.prepare("SELECT * FROM session_skill_policies WHERE session_id = ?").get(sessionId);
8836
- return row ? mapRow4(row) : null;
9220
+ return row ? mapRow5(row) : null;
8837
9221
  }
8838
9222
  delete(sessionId) {
8839
9223
  this.db.prepare("DELETE FROM session_skill_policies WHERE session_id = ?").run(sessionId);
@@ -8842,7 +9226,7 @@ var SkillPolicyStore = class {
8842
9226
  function normalizeSkills2(skills) {
8843
9227
  return Array.from(new Set(skills.map((skill) => skill.trim()).filter(Boolean)));
8844
9228
  }
8845
- function mapRow4(row) {
9229
+ function mapRow5(row) {
8846
9230
  return {
8847
9231
  sessionId: row.session_id,
8848
9232
  username: row.username,
@@ -9025,16 +9409,16 @@ function buildExtractionSource(branchEntries, startingLeafId, endingLeafId) {
9025
9409
  const evidence = [];
9026
9410
  for (const entry of branchEntries.slice(startIndex + 1, endIndex + 1)) {
9027
9411
  if (entry.type !== "message") continue;
9028
- const message = entry.message;
9029
- if (message.role === "user") {
9030
- for (const text of textBlocks(message.content)) {
9412
+ const message2 = entry.message;
9413
+ if (message2.role === "user") {
9414
+ for (const text of textBlocks(message2.content)) {
9031
9415
  lines.push(`[User] ${text}`);
9032
9416
  evidence.push({ role: "user", text });
9033
9417
  }
9034
9418
  continue;
9035
9419
  }
9036
- if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
9037
- for (const block of message.content) {
9420
+ if (message2.role !== "assistant" || !Array.isArray(message2.content)) continue;
9421
+ for (const block of message2.content) {
9038
9422
  if (block.type === "text" && block.text.trim()) {
9039
9423
  const text = block.text.trim();
9040
9424
  lines.push(`[Assistant] ${text}`);
@@ -9474,15 +9858,15 @@ function buildExtractionSource2(branchEntries, startingLeafId, endingLeafId) {
9474
9858
  };
9475
9859
  for (const entry of branchEntries.slice(startIndex + 1, endIndex + 1)) {
9476
9860
  if (entry.type !== "message") continue;
9477
- const message = entry.message;
9478
- if (message.role === "user") {
9479
- for (const text2 of textBlocks2(message.content)) {
9861
+ const message2 = entry.message;
9862
+ if (message2.role === "user") {
9863
+ for (const text2 of textBlocks2(message2.content)) {
9480
9864
  if (!isRawNoise(text2)) append({ role: "user", text: text2, ...textMetadata(text2) });
9481
9865
  }
9482
9866
  continue;
9483
9867
  }
9484
- if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
9485
- for (const block of message.content) {
9868
+ if (message2.role !== "assistant" || !Array.isArray(message2.content)) continue;
9869
+ for (const block of message2.content) {
9486
9870
  if (block.type === "text" && isConfirmedAssistantEvidence(block.text)) {
9487
9871
  append({ role: "assistant", text: block.text, ...textMetadata(block.text) });
9488
9872
  continue;
@@ -9984,8 +10368,8 @@ function buildExtractionQueries(source) {
9984
10368
  // src/memory/extractor.ts
9985
10369
  var EXTRACTION_TIMEOUT_MS = 45e3;
9986
10370
  var RetryableExtractionError = class extends Error {
9987
- constructor(kind, message) {
9988
- super(message);
10371
+ constructor(kind, message2) {
10372
+ super(message2);
9989
10373
  this.kind = kind;
9990
10374
  this.name = "RetryableExtractionError";
9991
10375
  }
@@ -10349,21 +10733,21 @@ function isUsage(value) {
10349
10733
  function isAbort(error, signal) {
10350
10734
  return signal.aborted || error instanceof Error && error.name === "AbortError";
10351
10735
  }
10352
- function providerResponseError(message) {
10353
- return new Error(message?.trim() || "Memory extraction provider failed");
10736
+ function providerResponseError(message2) {
10737
+ return new Error(message2?.trim() || "Memory extraction provider failed");
10354
10738
  }
10355
10739
  function isUnsupportedSchemaError(error) {
10356
- const status = errorStatus(error);
10357
- const message = error instanceof Error ? error.message : String(error);
10358
- return (status === 400 || status === 422 || status === void 0) && /(?:response[_ .-]?format|output[_ .-]?config|text\.format|json[_ .-]?schema|structured output|responseJsonSchema|unknown (?:field|parameter)).*(?:unsupported|unknown|invalid|not (?:allowed|supported))|(?:unsupported|unknown|invalid|not (?:allowed|supported)).*(?:response[_ .-]?format|output[_ .-]?config|text\.format|json[_ .-]?schema|structured output|responseJsonSchema)/i.test(message);
10740
+ const status = errorStatus2(error);
10741
+ const message2 = error instanceof Error ? error.message : String(error);
10742
+ return (status === 400 || status === 422 || status === void 0) && /(?:response[_ .-]?format|output[_ .-]?config|text\.format|json[_ .-]?schema|structured output|responseJsonSchema|unknown (?:field|parameter)).*(?:unsupported|unknown|invalid|not (?:allowed|supported))|(?:unsupported|unknown|invalid|not (?:allowed|supported)).*(?:response[_ .-]?format|output[_ .-]?config|text\.format|json[_ .-]?schema|structured output|responseJsonSchema)/i.test(message2);
10359
10743
  }
10360
10744
  function isRetryableTransportError(error) {
10361
- const status = errorStatus(error);
10745
+ const status = errorStatus2(error);
10362
10746
  if (status !== void 0) return status === 408 || status === 409 || status === 425 || status === 429 || status >= 500;
10363
10747
  if (!(error instanceof Error)) return false;
10364
10748
  return /(?:timeout|timed out|network|connection|socket|fetch failed|temporarily unavailable|rate limit|overloaded)/i.test(error.message);
10365
10749
  }
10366
- function errorStatus(error) {
10750
+ function errorStatus2(error) {
10367
10751
  if (!error || typeof error !== "object") return void 0;
10368
10752
  const candidate = error;
10369
10753
  const status = candidate.status ?? candidate.statusCode ?? candidate.response?.status;
@@ -10997,15 +11381,15 @@ function errorName2(error) {
10997
11381
  }
10998
11382
 
10999
11383
  // src/memory/store.ts
11000
- import { randomUUID as randomUUID4 } from "node:crypto";
11384
+ import { randomUUID as randomUUID5 } from "node:crypto";
11001
11385
  var MemoryStore = class {
11002
11386
  constructor(db) {
11003
11387
  this.db = db;
11004
11388
  }
11005
11389
  db;
11006
11390
  isAutoExtractionEnabled() {
11007
- const row = this.db.prepare("SELECT value FROM security_settings WHERE key = ?").get("memory.autoExtract");
11008
- return row?.value !== "false";
11391
+ const row = this.db.prepare("SELECT value FROM application_settings WHERE key = ?").get("memory.autoExtract");
11392
+ return row?.value === "true";
11009
11393
  }
11010
11394
  getOrCreateProject(profileId, canonicalPath) {
11011
11395
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -11013,7 +11397,7 @@ var MemoryStore = class {
11013
11397
  INSERT INTO memory_projects (id, profile_id, canonical_path, created_at, updated_at)
11014
11398
  VALUES (?, ?, ?, ?, ?)
11015
11399
  ON CONFLICT(profile_id, canonical_path) DO NOTHING
11016
- `).run(randomUUID4(), profileId, canonicalPath, now, now);
11400
+ `).run(randomUUID5(), profileId, canonicalPath, now, now);
11017
11401
  return this.getProjectByPath(profileId, canonicalPath);
11018
11402
  }
11019
11403
  getProjectById(id) {
@@ -11071,7 +11455,7 @@ var MemoryStore = class {
11071
11455
  const evidence = input.evidence ? assertMemoryContent(input.evidence) : null;
11072
11456
  const tags = normalizeMemoryTags(input.tags);
11073
11457
  const now = (/* @__PURE__ */ new Date()).toISOString();
11074
- const id = randomUUID4();
11458
+ const id = randomUUID5();
11075
11459
  try {
11076
11460
  this.db.prepare(`
11077
11461
  INSERT INTO memories (
@@ -11375,7 +11759,7 @@ var MemoryStore = class {
11375
11759
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'queued', ?, ?, 0, ?)
11376
11760
  ON CONFLICT(source_session_id, ending_leaf_id, source_kind) DO NOTHING
11377
11761
  `).run(
11378
- randomUUID4(),
11762
+ randomUUID5(),
11379
11763
  input.profileId,
11380
11764
  input.projectId,
11381
11765
  input.sourceSessionId,
@@ -11886,7 +12270,7 @@ function createMemoryRuntime(options) {
11886
12270
  // src/services/worktree-manager.ts
11887
12271
  import { execFile as execFileCallback } from "child_process";
11888
12272
  import { copyFile, mkdir as mkdir5, readdir as readdir3 } from "fs/promises";
11889
- import { basename as basename5, dirname as dirname8, isAbsolute as isAbsolute3, join as join13 } from "path";
12273
+ import { basename as basename5, dirname as dirname8, isAbsolute as isAbsolute4, join as join13 } from "path";
11890
12274
  import { promisify as promisify4 } from "util";
11891
12275
  var defaultExecFile = promisify4(execFileCallback);
11892
12276
  var WorktreeManager = class {
@@ -12008,7 +12392,7 @@ var WorktreeManager = class {
12008
12392
  }
12009
12393
  }
12010
12394
  isRootFileName(name) {
12011
- return Boolean(name.trim()) && !isAbsolute3(name) && !name.includes("/") && !name.includes("\\") && basename5(name) === name;
12395
+ return Boolean(name.trim()) && !isAbsolute4(name) && !name.includes("/") && !name.includes("\\") && basename5(name) === name;
12012
12396
  }
12013
12397
  };
12014
12398
 
@@ -12381,8 +12765,8 @@ function matchesNoProxy(host, entry) {
12381
12765
 
12382
12766
  // src/services/github-client.ts
12383
12767
  var GithubRequestError = class extends Error {
12384
- constructor(message, statusCode) {
12385
- super(message);
12768
+ constructor(message2, statusCode) {
12769
+ super(message2);
12386
12770
  this.statusCode = statusCode;
12387
12771
  this.name = "GithubRequestError";
12388
12772
  }
@@ -12481,8 +12865,8 @@ function execFileCapture(file, args, options) {
12481
12865
  function parseResponse2(text, ok, status) {
12482
12866
  const data = parseJsonObject3(text);
12483
12867
  if (!ok) {
12484
- const message = typeof data.message === "string" ? data.message : `GitHub request failed (${status})`;
12485
- throw new GithubRequestError(message, status);
12868
+ const message2 = typeof data.message === "string" ? data.message : `GitHub request failed (${status})`;
12869
+ throw new GithubRequestError(message2, status);
12486
12870
  }
12487
12871
  return data;
12488
12872
  }
@@ -12556,18 +12940,18 @@ var GatewaySettingsStore = class {
12556
12940
  else this.delete(key);
12557
12941
  }
12558
12942
  value(key) {
12559
- const row = this.db.prepare("SELECT value FROM security_settings WHERE key = ?").get(key);
12943
+ const row = this.db.prepare("SELECT value FROM application_settings WHERE key = ?").get(key);
12560
12944
  return row?.value || "";
12561
12945
  }
12562
12946
  set(key, value) {
12563
12947
  this.db.prepare(`
12564
- INSERT INTO security_settings (key, value, updated_at)
12948
+ INSERT INTO application_settings (key, value, updated_at)
12565
12949
  VALUES (?, ?, ?)
12566
12950
  ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
12567
12951
  `).run(key, value, (/* @__PURE__ */ new Date()).toISOString());
12568
12952
  }
12569
12953
  delete(key) {
12570
- this.db.prepare("DELETE FROM security_settings WHERE key = ?").run(key);
12954
+ this.db.prepare("DELETE FROM application_settings WHERE key = ?").run(key);
12571
12955
  }
12572
12956
  };
12573
12957
  function normalizeCwdList(value) {
@@ -12689,8 +13073,8 @@ function toSessionOptions(task, cwd) {
12689
13073
 
12690
13074
  // src/services/repository-cloner.ts
12691
13075
  import { EventEmitter as EventEmitter2 } from "node:events";
12692
- import { randomUUID as randomUUID5 } from "node:crypto";
12693
- import { rm as rm4, stat as stat5 } from "node:fs/promises";
13076
+ import { randomUUID as randomUUID6 } from "node:crypto";
13077
+ import { rm as rm4, stat as stat6 } from "node:fs/promises";
12694
13078
  import { join as join14 } from "node:path";
12695
13079
  var GIT_PROGRESS_PATTERNS = [
12696
13080
  { pattern: /^Receiving objects:\s+(\d+)%/, status: "Receiving objects\u2026" },
@@ -12710,7 +13094,7 @@ var RepositoryCloner = class {
12710
13094
  if (!destinationPath) throw new Error("Destination path is required");
12711
13095
  const pathExists = this.options.pathExists || defaultPathExists;
12712
13096
  if (await pathExists(destinationPath)) return { status: "destination_exists", existingPath: destinationPath };
12713
- const job = new CloneJob(`clone_${randomUUID5()}`, remoteUrl, destinationPath, input.shallow === true);
13097
+ const job = new CloneJob(`clone_${randomUUID6()}`, remoteUrl, destinationPath, input.shallow === true);
12714
13098
  this.jobs.set(job.id, job);
12715
13099
  await this.spawnClone(job, remoteUrl, false, input.clientId);
12716
13100
  return { status: "started", jobId: job.id };
@@ -12869,7 +13253,7 @@ function withToken(remoteUrl, username, token) {
12869
13253
  return parsed.toString();
12870
13254
  }
12871
13255
  async function defaultPathExists(path13) {
12872
- return stat5(path13).then(() => true, () => false);
13256
+ return stat6(path13).then(() => true, () => false);
12873
13257
  }
12874
13258
  async function defaultRemovePath(path13) {
12875
13259
  await rm4(path13, { recursive: true, force: true });
@@ -12877,8 +13261,8 @@ async function defaultRemovePath(path13) {
12877
13261
 
12878
13262
  // src/services/feishu-gateway.ts
12879
13263
  import { createDecipheriv, createHash as createHash7 } from "node:crypto";
12880
- import { stat as stat6 } from "node:fs/promises";
12881
- import { isAbsolute as isAbsolute4 } from "node:path";
13264
+ import { stat as stat7 } from "node:fs/promises";
13265
+ import { isAbsolute as isAbsolute5 } from "node:path";
12882
13266
 
12883
13267
  // src/services/gateway-command-aliases.ts
12884
13268
  var DEFAULT_COMMAND_PREFIX = "\u5C0F\u52A9\u7406";
@@ -12964,12 +13348,12 @@ var FeishuGatewayService = class {
12964
13348
  if (payload.type === "url_verification") {
12965
13349
  return { challenge: stringValue(payload.challenge) };
12966
13350
  }
12967
- const message = this.parseMessage(payload);
12968
- if (!message) return { ok: true, ignored: true };
12969
- if (this.markDuplicate(message.eventId) || this.markDuplicate(message.messageId)) {
13351
+ const message2 = this.parseMessage(payload);
13352
+ if (!message2) return { ok: true, ignored: true };
13353
+ if (this.markDuplicate(message2.eventId) || this.markDuplicate(message2.messageId)) {
12970
13354
  return { ok: true, duplicate: true };
12971
13355
  }
12972
- this.enqueue(message, config);
13356
+ this.enqueue(message2, config);
12973
13357
  return { ok: true };
12974
13358
  }
12975
13359
  loadConfig() {
@@ -13041,22 +13425,22 @@ var FeishuGatewayService = class {
13041
13425
  const event = asRecord(payload.event);
13042
13426
  const eventType = stringValue(header.event_type);
13043
13427
  if (eventType && eventType !== "im.message.receive_v1") return null;
13044
- const message = asRecord(event.message);
13045
- const messageType = stringValue(message.message_type);
13428
+ const message2 = asRecord(event.message);
13429
+ const messageType = stringValue(message2.message_type);
13046
13430
  if (messageType && messageType !== "text") {
13047
- logUnsupportedFeishuMessage(message, messageType);
13431
+ logUnsupportedFeishuMessage(message2, messageType);
13048
13432
  return null;
13049
13433
  }
13050
- const text = parseFeishuTextContent(stringValue(message.content)).trim();
13051
- const chatId = stringValue(message.chat_id);
13052
- const messageId = stringValue(message.message_id);
13434
+ const text = parseFeishuTextContent(stringValue(message2.content)).trim();
13435
+ const chatId = stringValue(message2.chat_id);
13436
+ const messageId = stringValue(message2.message_id);
13053
13437
  if (!text || !chatId || !messageId) return null;
13054
13438
  return {
13055
13439
  eventId: stringValue(header.event_id) || messageId,
13056
13440
  messageId,
13057
13441
  chatId,
13058
- chatType: stringValue(message.chat_type) || "p2p",
13059
- threadId: stringValue(message.thread_id) || stringValue(message.parent_id) || stringValue(message.root_id),
13442
+ chatType: stringValue(message2.chat_type) || "p2p",
13443
+ threadId: stringValue(message2.thread_id) || stringValue(message2.parent_id) || stringValue(message2.root_id),
13060
13444
  text
13061
13445
  };
13062
13446
  }
@@ -13070,24 +13454,24 @@ var FeishuGatewayService = class {
13070
13454
  this.seen.set(id, now + DEDUP_TTL_MS);
13071
13455
  return false;
13072
13456
  }
13073
- enqueue(message, config) {
13074
- const clientId = this.clientIdForMessage(message);
13457
+ enqueue(message2, config) {
13458
+ const clientId = this.clientIdForMessage(message2);
13075
13459
  const previous = this.queues.get(clientId) || Promise.resolve();
13076
- const next = previous.catch(() => void 0).then(() => this.processMessage(clientId, message, config)).catch((error) => console.error("[feishu-gateway] message handling failed:", error)).finally(() => {
13460
+ const next = previous.catch(() => void 0).then(() => this.processMessage(clientId, message2, config)).catch((error) => console.error("[feishu-gateway] message handling failed:", error)).finally(() => {
13077
13461
  if (this.queues.get(clientId) === next) this.queues.delete(clientId);
13078
13462
  });
13079
13463
  this.queues.set(clientId, next);
13080
13464
  }
13081
- clientIdForMessage(message) {
13082
- const chatType = message.chatType === "p2p" ? "dm" : message.chatType || "chat";
13083
- const thread = message.threadId ? `:${message.threadId}` : "";
13084
- return `feishu:${chatType}:${message.chatId}${thread}`;
13465
+ clientIdForMessage(message2) {
13466
+ const chatType = message2.chatType === "p2p" ? "dm" : message2.chatType || "chat";
13467
+ const thread = message2.threadId ? `:${message2.threadId}` : "";
13468
+ return `feishu:${chatType}:${message2.chatId}${thread}`;
13085
13469
  }
13086
- async processMessage(clientId, message, config) {
13470
+ async processMessage(clientId, message2, config) {
13087
13471
  const effectiveConfig = this.resolveChatConfig(clientId, config);
13088
- if (await this.handleCommand(clientId, message, config, effectiveConfig)) return;
13472
+ if (await this.handleCommand(clientId, message2, config, effectiveConfig)) return;
13089
13473
  if (!effectiveConfig.defaultCwd) {
13090
- await this.sendReply(config, message.messageId, GATEWAY_FOLDERS_REQUIRED_MESSAGE);
13474
+ await this.sendReply(config, message2.messageId, GATEWAY_FOLDERS_REQUIRED_MESSAGE);
13091
13475
  return;
13092
13476
  }
13093
13477
  const session = await this.ensureSession(clientId, effectiveConfig);
@@ -13101,13 +13485,13 @@ var FeishuGatewayService = class {
13101
13485
  });
13102
13486
  try {
13103
13487
  await this.sessionService.runForegroundWithClientProfileProxy(clientId, async () => {
13104
- await session.prompt(message.text);
13488
+ await session.prompt(message2.text);
13105
13489
  });
13106
13490
  } finally {
13107
13491
  unsubscribe();
13108
13492
  }
13109
13493
  const response = chunks.join("").trim() || extractLastAssistantText(session.messages) || "(Pi finished without a text response.)";
13110
- await this.sendReply(config, message.messageId, response);
13494
+ await this.sendReply(config, message2.messageId, response);
13111
13495
  }
13112
13496
  async ensureSession(clientId, config) {
13113
13497
  await this.sessionService.setClientAgentProfile(clientId, config.agentProfile || "default");
@@ -13122,8 +13506,8 @@ var FeishuGatewayService = class {
13122
13506
  this.saveSessionMapping(clientId, result.session.sessionId);
13123
13507
  return result.session;
13124
13508
  }
13125
- async handleCommand(clientId, message, baseConfig, effectiveConfig) {
13126
- const commandText = normalizeGatewayCommandText(message.text);
13509
+ async handleCommand(clientId, message2, baseConfig, effectiveConfig) {
13510
+ const commandText = normalizeGatewayCommandText(message2.text);
13127
13511
  const [command = "", ...args] = commandText.split(/\s+/);
13128
13512
  const normalized = command.toLowerCase();
13129
13513
  if (!normalized.startsWith("/")) return false;
@@ -13132,66 +13516,66 @@ var FeishuGatewayService = class {
13132
13516
  this.resetSession(clientId);
13133
13517
  await this.sendReply(
13134
13518
  baseConfig,
13135
- message.messageId,
13519
+ message2.messageId,
13136
13520
  await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Reset this Feishu chat. A fresh Pi session will start with your next message.")
13137
13521
  );
13138
13522
  return true;
13139
13523
  }
13140
13524
  if (normalized === "/help") {
13141
- await this.sendReply(baseConfig, message.messageId, this.formatHelp());
13525
+ await this.sendReply(baseConfig, message2.messageId, this.formatHelp());
13142
13526
  return true;
13143
13527
  }
13144
13528
  if (normalized === "/status") {
13145
- await this.sendReply(baseConfig, message.messageId, await this.formatStatus(clientId, effectiveConfig));
13529
+ await this.sendReply(baseConfig, message2.messageId, await this.formatStatus(clientId, effectiveConfig));
13146
13530
  return true;
13147
13531
  }
13148
13532
  if (normalized === "/profiles") {
13149
- await this.sendReply(baseConfig, message.messageId, await this.formatProfiles(effectiveConfig.agentProfile || "default"));
13533
+ await this.sendReply(baseConfig, message2.messageId, await this.formatProfiles(effectiveConfig.agentProfile || "default"));
13150
13534
  return true;
13151
13535
  }
13152
13536
  if (normalized === "/profile" || normalized === "/use-profile" || normalized === "/set-profile") {
13153
13537
  await this.setChatProfile(clientId, args[0]);
13154
13538
  this.resetSession(clientId);
13155
- await this.sendReply(baseConfig, message.messageId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Profile updated."));
13539
+ await this.sendReply(baseConfig, message2.messageId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Profile updated."));
13156
13540
  return true;
13157
13541
  }
13158
13542
  if (normalized === "/cwds" || normalized === "/pwds") {
13159
- await this.sendReply(baseConfig, message.messageId, this.formatCwdChoices(baseConfig));
13543
+ await this.sendReply(baseConfig, message2.messageId, this.formatCwdChoices(baseConfig));
13160
13544
  return true;
13161
13545
  }
13162
13546
  if (normalized === "/cwd") {
13163
13547
  if (args.length === 0) {
13164
- await this.sendReply(baseConfig, message.messageId, effectiveConfig.defaultCwd || NO_GATEWAY_FOLDERS_MESSAGE);
13548
+ await this.sendReply(baseConfig, message2.messageId, effectiveConfig.defaultCwd || NO_GATEWAY_FOLDERS_MESSAGE);
13165
13549
  return true;
13166
13550
  }
13167
13551
  await this.setChatCwd(clientId, args[0], baseConfig);
13168
13552
  this.resetSession(clientId);
13169
- await this.sendReply(baseConfig, message.messageId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Working directory updated."));
13553
+ await this.sendReply(baseConfig, message2.messageId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Working directory updated."));
13170
13554
  return true;
13171
13555
  }
13172
13556
  if (normalized === "/skillsets") {
13173
- await this.sendReply(baseConfig, message.messageId, this.formatSkillsets(effectiveConfig.skillPresetId));
13557
+ await this.sendReply(baseConfig, message2.messageId, this.formatSkillsets(effectiveConfig.skillPresetId));
13174
13558
  return true;
13175
13559
  }
13176
13560
  if (normalized === "/skillset") {
13177
13561
  await this.setChatSkillset(clientId, args.join(" "));
13178
13562
  this.resetSession(clientId);
13179
- await this.sendReply(baseConfig, message.messageId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Skillset updated."));
13563
+ await this.sendReply(baseConfig, message2.messageId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Skillset updated."));
13180
13564
  return true;
13181
13565
  }
13182
13566
  if (normalized === "/clear-config") {
13183
13567
  this.clearChatConfig(clientId);
13184
13568
  this.resetSession(clientId);
13185
- await this.sendReply(baseConfig, message.messageId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Feishu chat config cleared."));
13569
+ await this.sendReply(baseConfig, message2.messageId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Feishu chat config cleared."));
13186
13570
  return true;
13187
13571
  }
13188
13572
  } catch (error) {
13189
- await this.sendReply(baseConfig, message.messageId, `Command failed: ${error instanceof Error ? error.message : "unknown error"}
13573
+ await this.sendReply(baseConfig, message2.messageId, `Command failed: ${error instanceof Error ? error.message : "unknown error"}
13190
13574
 
13191
13575
  ${this.formatHelp()}`);
13192
13576
  return true;
13193
13577
  }
13194
- await this.sendReply(baseConfig, message.messageId, `Unknown command: ${command}
13578
+ await this.sendReply(baseConfig, message2.messageId, `Unknown command: ${command}
13195
13579
 
13196
13580
  ${this.formatHelp()}`);
13197
13581
  return true;
@@ -13266,8 +13650,8 @@ ${this.formatHelp()}`);
13266
13650
  const slot = Number(slotText.trim());
13267
13651
  if (!Number.isInteger(slot) || slot < 1 || slot > config.cwdChoices.length) throw new Error(`Usage: /cwd <1-${config.cwdChoices.length}>`);
13268
13652
  const target = config.cwdChoices[slot - 1];
13269
- if (!isAbsolute4(target)) throw new Error(`Configured CWD ${slot} must be an absolute path`);
13270
- const info = await stat6(target);
13653
+ if (!isAbsolute5(target)) throw new Error(`Configured CWD ${slot} must be an absolute path`);
13654
+ const info = await stat7(target);
13271
13655
  if (!info.isDirectory()) throw new Error(`Configured CWD ${slot} must be an existing directory`);
13272
13656
  this.saveChatConfig(clientId, { defaultCwd: target });
13273
13657
  }
@@ -13428,13 +13812,13 @@ function parseFeishuTextContent(content) {
13428
13812
  return content;
13429
13813
  }
13430
13814
  }
13431
- function logUnsupportedFeishuMessage(message, messageType) {
13432
- const content = parseJsonRecord(stringValue(message.content));
13815
+ function logUnsupportedFeishuMessage(message2, messageType) {
13816
+ const content = parseJsonRecord(stringValue(message2.content));
13433
13817
  console.info("[feishu-gateway] unsupported inbound message", {
13434
13818
  messageType,
13435
- messageId: stringValue(message.message_id) || void 0,
13436
- chatType: stringValue(message.chat_type) || void 0,
13437
- messageKeys: Object.keys(message).sort(),
13819
+ messageId: stringValue(message2.message_id) || void 0,
13820
+ chatType: stringValue(message2.chat_type) || void 0,
13821
+ messageKeys: Object.keys(message2).sort(),
13438
13822
  contentKeys: Object.keys(content).sort(),
13439
13823
  contentStringFields: Object.fromEntries(
13440
13824
  Object.entries(content).filter(([, value]) => typeof value === "string").map(([key, value]) => [key, value.length])
@@ -13458,9 +13842,9 @@ function truncateFeishuText(text) {
13458
13842
  function extractLastAssistantText(messages) {
13459
13843
  if (!Array.isArray(messages)) return "";
13460
13844
  for (let index = messages.length - 1; index >= 0; index -= 1) {
13461
- const message = messages[index];
13462
- if (message?.role !== "assistant") continue;
13463
- const content = message.content;
13845
+ const message2 = messages[index];
13846
+ if (message2?.role !== "assistant") continue;
13847
+ const content = message2.content;
13464
13848
  if (typeof content === "string") return content.trim();
13465
13849
  if (Array.isArray(content)) {
13466
13850
  const text = content.map((part) => typeof part === "string" ? part : part?.text || part?.content || "").join("").trim();
@@ -13471,9 +13855,9 @@ function extractLastAssistantText(messages) {
13471
13855
  }
13472
13856
 
13473
13857
  // src/services/weixin-gateway.ts
13474
- import { randomUUID as randomUUID6 } from "node:crypto";
13475
- import { stat as stat7 } from "node:fs/promises";
13476
- import { isAbsolute as isAbsolute5 } from "node:path";
13858
+ import { randomUUID as randomUUID7 } from "node:crypto";
13859
+ import { stat as stat8 } from "node:fs/promises";
13860
+ import { isAbsolute as isAbsolute6 } from "node:path";
13477
13861
  import QRCode from "qrcode";
13478
13862
  import { Agent, fetch as undiciFetch2 } from "undici";
13479
13863
 
@@ -13734,11 +14118,11 @@ var WeixinGatewayService = class {
13734
14118
  }
13735
14119
  const messages = Array.isArray(response.msgs) ? response.msgs : [];
13736
14120
  for (const raw of messages) {
13737
- const message = await this.parseMessage(asRecord2(raw), config);
13738
- if (!message) continue;
13739
- if (this.markDuplicate(message.messageId)) continue;
13740
- if (message.contextToken) this.saveContextToken(config.accountId, message.senderId, message.contextToken);
13741
- this.enqueue(message, config);
14121
+ const message2 = await this.parseMessage(asRecord2(raw), config);
14122
+ if (!message2) continue;
14123
+ if (this.markDuplicate(message2.messageId)) continue;
14124
+ if (message2.contextToken) this.saveContextToken(config.accountId, message2.senderId, message2.contextToken);
14125
+ this.enqueue(message2, config);
13742
14126
  }
13743
14127
  } catch (error) {
13744
14128
  if (!this.running) break;
@@ -13750,17 +14134,17 @@ var WeixinGatewayService = class {
13750
14134
  }
13751
14135
  console.info("[weixin-gateway] stopped");
13752
14136
  }
13753
- async parseMessage(message, config) {
13754
- const senderId = stringValue2(message.from_user_id);
14137
+ async parseMessage(message2, config) {
14138
+ const senderId = stringValue2(message2.from_user_id);
13755
14139
  if (!senderId || senderId === config.accountId) return null;
13756
- const { chatType, chatId } = this.guessChat(message, config.accountId);
14140
+ const { chatType, chatId } = this.guessChat(message2, config.accountId);
13757
14141
  if (chatType === "group") return null;
13758
14142
  if (!this.isDmAllowed(senderId, config)) return null;
13759
- const items = Array.isArray(message.item_list) ? message.item_list.map(asRecord2) : [];
14143
+ const items = Array.isArray(message2.item_list) ? message2.item_list.map(asRecord2) : [];
13760
14144
  const text = extractText2(items).trim();
13761
14145
  const imageResult = await this.extractImages(config, items);
13762
- const messageId = stringValue2(message.message_id) || randomUUID6();
13763
- if (!text && imageResult.images.length === 0 && !imageResult.error) this.logUnsupportedInboundMessage(message, items, "no text or supported image item");
14146
+ const messageId = stringValue2(message2.message_id) || randomUUID7();
14147
+ if (!text && imageResult.images.length === 0 && !imageResult.error) this.logUnsupportedInboundMessage(message2, items, "no text or supported image item");
13764
14148
  if (!text && imageResult.images.length === 0 && !imageResult.error || !chatId) return null;
13765
14149
  return {
13766
14150
  messageId,
@@ -13770,15 +14154,15 @@ var WeixinGatewayService = class {
13770
14154
  text,
13771
14155
  images: imageResult.images,
13772
14156
  imageError: imageResult.error,
13773
- contextToken: stringValue2(message.context_token) || void 0
14157
+ contextToken: stringValue2(message2.context_token) || void 0
13774
14158
  };
13775
14159
  }
13776
- guessChat(message, accountId) {
13777
- const roomId = stringValue2(message.room_id) || stringValue2(message.chat_room_id);
13778
- const toUserId = stringValue2(message.to_user_id);
13779
- const isGroup = Boolean(roomId) || Boolean(toUserId && accountId && toUserId !== accountId && Number(message.msg_type) === 1);
13780
- if (isGroup) return { chatType: "group", chatId: roomId || toUserId || stringValue2(message.from_user_id) };
13781
- return { chatType: "dm", chatId: stringValue2(message.from_user_id) };
14160
+ guessChat(message2, accountId) {
14161
+ const roomId = stringValue2(message2.room_id) || stringValue2(message2.chat_room_id);
14162
+ const toUserId = stringValue2(message2.to_user_id);
14163
+ const isGroup = Boolean(roomId) || Boolean(toUserId && accountId && toUserId !== accountId && Number(message2.msg_type) === 1);
14164
+ if (isGroup) return { chatType: "group", chatId: roomId || toUserId || stringValue2(message2.from_user_id) };
14165
+ return { chatType: "dm", chatId: stringValue2(message2.from_user_id) };
13782
14166
  }
13783
14167
  isDmAllowed(senderId, config) {
13784
14168
  if (config.dmPolicy === "disabled") return false;
@@ -13786,13 +14170,13 @@ var WeixinGatewayService = class {
13786
14170
  if (config.dmPolicy === "open") return parseBoolean3(process.env.PI_CLOUD_WECHAT_ALLOW_ALL_USERS || process.env.GATEWAY_ALLOW_ALL_USERS, false);
13787
14171
  return true;
13788
14172
  }
13789
- logUnsupportedInboundMessage(message, items, reason) {
14173
+ logUnsupportedInboundMessage(message2, items, reason) {
13790
14174
  console.info("[weixin-gateway] unsupported inbound message", JSON.stringify({
13791
14175
  reason,
13792
- messageId: message.message_id,
13793
- msgType: message.msg_type,
13794
- messageType: message.message_type,
13795
- messageKeys: Object.keys(message).sort(),
14176
+ messageId: message2.message_id,
14177
+ msgType: message2.msg_type,
14178
+ messageType: message2.message_type,
14179
+ messageKeys: Object.keys(message2).sort(),
13796
14180
  itemSummaries: items.map(summarizeWeixinItem)
13797
14181
  }, null, 2));
13798
14182
  }
@@ -13880,36 +14264,36 @@ var WeixinGatewayService = class {
13880
14264
  this.seen.set(id, now + DEDUP_TTL_MS2);
13881
14265
  return false;
13882
14266
  }
13883
- enqueue(message, config) {
13884
- const clientId = this.clientIdForMessage(message);
14267
+ enqueue(message2, config) {
14268
+ const clientId = this.clientIdForMessage(message2);
13885
14269
  const previous = this.queues.get(clientId) || Promise.resolve();
13886
- const next = previous.catch(() => void 0).then(() => this.processMessage(clientId, message, config)).catch((error) => console.error("[weixin-gateway] message handling failed:", error)).finally(() => {
14270
+ const next = previous.catch(() => void 0).then(() => this.processMessage(clientId, message2, config)).catch((error) => console.error("[weixin-gateway] message handling failed:", error)).finally(() => {
13887
14271
  if (this.queues.get(clientId) === next) this.queues.delete(clientId);
13888
14272
  });
13889
14273
  this.queues.set(clientId, next);
13890
14274
  }
13891
- clientIdForMessage(message) {
13892
- return `weixin:${message.chatType}:${message.chatId}`;
14275
+ clientIdForMessage(message2) {
14276
+ return `weixin:${message2.chatType}:${message2.chatId}`;
13893
14277
  }
13894
- async processMessage(clientId, message, config) {
14278
+ async processMessage(clientId, message2, config) {
13895
14279
  const effectiveConfig = this.resolveChatConfig(clientId, config);
13896
- if (await this.handleCommand(clientId, message, config, effectiveConfig)) return;
14280
+ if (await this.handleCommand(clientId, message2, config, effectiveConfig)) return;
13897
14281
  if (!effectiveConfig.defaultCwd) {
13898
- await this.sendReply(config, message.chatId, GATEWAY_FOLDERS_REQUIRED_MESSAGE2);
14282
+ await this.sendReply(config, message2.chatId, GATEWAY_FOLDERS_REQUIRED_MESSAGE2);
13899
14283
  return;
13900
14284
  }
13901
- if (message.imageError && message.images.length === 0) {
13902
- await this.sendReply(config, message.chatId, message.imageError);
14285
+ if (message2.imageError && message2.images.length === 0) {
14286
+ await this.sendReply(config, message2.chatId, message2.imageError);
13903
14287
  return;
13904
14288
  }
13905
14289
  const session = await this.ensureSession(clientId, effectiveConfig);
13906
- const imageResult = validateImages(message.images, session.model);
14290
+ const imageResult = validateImages(message2.images, session.model);
13907
14291
  if (!imageResult.ok) {
13908
- await this.sendReply(config, message.chatId, imageResult.message);
14292
+ await this.sendReply(config, message2.chatId, imageResult.message);
13909
14293
  return;
13910
14294
  }
13911
- await this.saveInboundImages(effectiveConfig.defaultCwd, message.messageId, imageResult.images);
13912
- const promptText = message.text || (imageResult.images.length ? "Please analyze the attached image." : "");
14295
+ await this.saveInboundImages(effectiveConfig.defaultCwd, message2.messageId, imageResult.images);
14296
+ const promptText = message2.text || (imageResult.images.length ? "Please analyze the attached image." : "");
13913
14297
  const chunks = [];
13914
14298
  const unsubscribe = session.subscribe((event) => {
13915
14299
  if (event?.type !== "message_update") return;
@@ -13918,7 +14302,7 @@ var WeixinGatewayService = class {
13918
14302
  chunks.push(update.delta);
13919
14303
  }
13920
14304
  });
13921
- const stopTypingIndicator = this.startTypingIndicator(config, message.chatId);
14305
+ const stopTypingIndicator = this.startTypingIndicator(config, message2.chatId);
13922
14306
  try {
13923
14307
  await this.sessionService.runForegroundWithClientProfileProxy(clientId, async () => {
13924
14308
  await session.prompt(promptText, imageResult.images.length ? { images: imageResult.images } : void 0);
@@ -13928,7 +14312,7 @@ var WeixinGatewayService = class {
13928
14312
  unsubscribe();
13929
14313
  }
13930
14314
  const response = chunks.join("").trim() || extractLastAssistantText2(session.messages) || "(Pi finished without a text response.)";
13931
- await this.sendReply(config, message.chatId, response);
14315
+ await this.sendReply(config, message2.chatId, response);
13932
14316
  }
13933
14317
  async saveInboundImages(projectRoot, messageId, images) {
13934
14318
  if (!projectRoot || images.length === 0) return;
@@ -13959,8 +14343,8 @@ var WeixinGatewayService = class {
13959
14343
  this.saveSessionMapping(clientId, result.session.sessionId);
13960
14344
  return result.session;
13961
14345
  }
13962
- async handleCommand(clientId, message, baseConfig, effectiveConfig) {
13963
- const commandText = normalizeGatewayCommandText(message.text);
14346
+ async handleCommand(clientId, message2, baseConfig, effectiveConfig) {
14347
+ const commandText = normalizeGatewayCommandText(message2.text);
13964
14348
  const [command = "", ...args] = commandText.split(/\s+/);
13965
14349
  const normalized = command.toLowerCase();
13966
14350
  if (!normalized.startsWith("/")) return false;
@@ -13969,66 +14353,66 @@ var WeixinGatewayService = class {
13969
14353
  this.resetSession(clientId);
13970
14354
  await this.sendReply(
13971
14355
  baseConfig,
13972
- message.chatId,
14356
+ message2.chatId,
13973
14357
  await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Reset this WeChat chat. A fresh Pi session will start with your next message.")
13974
14358
  );
13975
14359
  return true;
13976
14360
  }
13977
14361
  if (normalized === "/help") {
13978
- await this.sendReply(baseConfig, message.chatId, this.formatHelp());
14362
+ await this.sendReply(baseConfig, message2.chatId, this.formatHelp());
13979
14363
  return true;
13980
14364
  }
13981
14365
  if (normalized === "/status") {
13982
- await this.sendReply(baseConfig, message.chatId, await this.formatStatus(clientId, effectiveConfig));
14366
+ await this.sendReply(baseConfig, message2.chatId, await this.formatStatus(clientId, effectiveConfig));
13983
14367
  return true;
13984
14368
  }
13985
14369
  if (normalized === "/profiles") {
13986
- await this.sendReply(baseConfig, message.chatId, await this.formatProfiles(effectiveConfig.agentProfile || "default"));
14370
+ await this.sendReply(baseConfig, message2.chatId, await this.formatProfiles(effectiveConfig.agentProfile || "default"));
13987
14371
  return true;
13988
14372
  }
13989
14373
  if (normalized === "/profile" || normalized === "/use-profile" || normalized === "/set-profile") {
13990
14374
  await this.setChatProfile(clientId, args[0]);
13991
14375
  this.resetSession(clientId);
13992
- await this.sendReply(baseConfig, message.chatId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Profile updated."));
14376
+ await this.sendReply(baseConfig, message2.chatId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Profile updated."));
13993
14377
  return true;
13994
14378
  }
13995
14379
  if (normalized === "/cwds" || normalized === "/pwds") {
13996
- await this.sendReply(baseConfig, message.chatId, this.formatCwdChoices(baseConfig));
14380
+ await this.sendReply(baseConfig, message2.chatId, this.formatCwdChoices(baseConfig));
13997
14381
  return true;
13998
14382
  }
13999
14383
  if (normalized === "/cwd") {
14000
14384
  if (args.length === 0) {
14001
- await this.sendReply(baseConfig, message.chatId, effectiveConfig.defaultCwd || NO_GATEWAY_FOLDERS_MESSAGE2);
14385
+ await this.sendReply(baseConfig, message2.chatId, effectiveConfig.defaultCwd || NO_GATEWAY_FOLDERS_MESSAGE2);
14002
14386
  return true;
14003
14387
  }
14004
14388
  await this.setChatCwd(clientId, args[0], baseConfig);
14005
14389
  this.resetSession(clientId);
14006
- await this.sendReply(baseConfig, message.chatId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Working directory updated."));
14390
+ await this.sendReply(baseConfig, message2.chatId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Working directory updated."));
14007
14391
  return true;
14008
14392
  }
14009
14393
  if (normalized === "/skillsets") {
14010
- await this.sendReply(baseConfig, message.chatId, this.formatSkillsets(effectiveConfig.skillPresetId));
14394
+ await this.sendReply(baseConfig, message2.chatId, this.formatSkillsets(effectiveConfig.skillPresetId));
14011
14395
  return true;
14012
14396
  }
14013
14397
  if (normalized === "/skillset") {
14014
14398
  await this.setChatSkillset(clientId, args.join(" "));
14015
14399
  this.resetSession(clientId);
14016
- await this.sendReply(baseConfig, message.chatId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Skillset updated."));
14400
+ await this.sendReply(baseConfig, message2.chatId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Skillset updated."));
14017
14401
  return true;
14018
14402
  }
14019
14403
  if (normalized === "/clear-config") {
14020
14404
  this.clearChatConfig(clientId);
14021
14405
  this.resetSession(clientId);
14022
- await this.sendReply(baseConfig, message.chatId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "WeChat chat config cleared."));
14406
+ await this.sendReply(baseConfig, message2.chatId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "WeChat chat config cleared."));
14023
14407
  return true;
14024
14408
  }
14025
14409
  } catch (error) {
14026
- await this.sendReply(baseConfig, message.chatId, `Command failed: ${error instanceof Error ? error.message : "unknown error"}
14410
+ await this.sendReply(baseConfig, message2.chatId, `Command failed: ${error instanceof Error ? error.message : "unknown error"}
14027
14411
 
14028
14412
  ${this.formatHelp()}`);
14029
14413
  return true;
14030
14414
  }
14031
- await this.sendReply(baseConfig, message.chatId, `Unknown command: ${command}
14415
+ await this.sendReply(baseConfig, message2.chatId, `Unknown command: ${command}
14032
14416
 
14033
14417
  ${this.formatHelp()}`);
14034
14418
  return true;
@@ -14103,8 +14487,8 @@ ${this.formatHelp()}`);
14103
14487
  const slot = Number(slotText.trim());
14104
14488
  if (!Number.isInteger(slot) || slot < 1 || slot > config.cwdChoices.length) throw new Error(`Usage: /cwd <1-${config.cwdChoices.length}>`);
14105
14489
  const target = config.cwdChoices[slot - 1];
14106
- if (!isAbsolute5(target)) throw new Error(`Configured CWD ${slot} must be an absolute path`);
14107
- const info = await stat7(target);
14490
+ if (!isAbsolute6(target)) throw new Error(`Configured CWD ${slot} must be an absolute path`);
14491
+ const info = await stat8(target);
14108
14492
  if (!info.isDirectory()) throw new Error(`Configured CWD ${slot} must be an existing directory`);
14109
14493
  this.saveChatConfig(clientId, { defaultCwd: target });
14110
14494
  }
@@ -14275,7 +14659,7 @@ ${this.formatHelp()}`);
14275
14659
  msg: {
14276
14660
  from_user_id: "",
14277
14661
  to_user_id: chatId,
14278
- client_id: randomUUID6(),
14662
+ client_id: randomUUID7(),
14279
14663
  message_type: MSG_TYPE_BOT,
14280
14664
  ...msg,
14281
14665
  ...withContextToken(this.getContextToken(config.accountId, chatId))
@@ -14520,11 +14904,11 @@ function safeId(value) {
14520
14904
  }
14521
14905
  function extractLastAssistantText2(messages) {
14522
14906
  for (let index = messages.length - 1; index >= 0; index -= 1) {
14523
- const message = messages[index];
14524
- if (message?.role !== "assistant") continue;
14525
- if (typeof message.content === "string") return message.content.trim();
14526
- if (Array.isArray(message.content)) {
14527
- const text = message.content.map((part) => typeof part === "string" ? part : part?.text || part?.content || "").join("").trim();
14907
+ const message2 = messages[index];
14908
+ if (message2?.role !== "assistant") continue;
14909
+ if (typeof message2.content === "string") return message2.content.trim();
14910
+ if (Array.isArray(message2.content)) {
14911
+ const text = message2.content.map((part) => typeof part === "string" ? part : part?.text || part?.content || "").join("").trim();
14528
14912
  if (text) return text;
14529
14913
  }
14530
14914
  }
@@ -14533,8 +14917,8 @@ function extractLastAssistantText2(messages) {
14533
14917
 
14534
14918
  // src/services/wecom-gateway.ts
14535
14919
  import { randomBytes as randomBytes3 } from "node:crypto";
14536
- import { stat as stat8 } from "node:fs/promises";
14537
- import { isAbsolute as isAbsolute6 } from "node:path";
14920
+ import { stat as stat9 } from "node:fs/promises";
14921
+ import { isAbsolute as isAbsolute7 } from "node:path";
14538
14922
 
14539
14923
  // src/services/wecom-crypto.ts
14540
14924
  import { createDecipheriv as createDecipheriv3, createHash as createHash8, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
@@ -14682,24 +15066,24 @@ var WecomGatewayService = class {
14682
15066
  const encrypted = xmlValue(body, "Encrypt");
14683
15067
  if (!encrypted) throw gatewayError("Invalid WeCom callback envelope", 400);
14684
15068
  this.verifySignature(query, encrypted, config);
14685
- const message = parseWecomMessage(decryptWecomPayload(encrypted, config.encodingAesKey, config.corpId));
15069
+ const message2 = parseWecomMessage(decryptWecomPayload(encrypted, config.encodingAesKey, config.corpId));
14686
15070
  this.markCallbackVerified();
14687
- if (!message || message.agentId !== config.agentId) return "success";
14688
- if (config.allowedUsers.length && !config.allowedUsers.includes(message.userId)) return "success";
14689
- if (this.markDuplicate(message.messageId)) return "success";
14690
- if (!["text", "image", "voice"].includes(message.messageType)) {
14691
- void this.sendReply(config, message.userId, "This WeCom gateway supports text, image, and voice messages only.").catch((error) => {
15071
+ if (!message2 || message2.agentId !== config.agentId) return "success";
15072
+ if (config.allowedUsers.length && !config.allowedUsers.includes(message2.userId)) return "success";
15073
+ if (this.markDuplicate(message2.messageId)) return "success";
15074
+ if (!["text", "image", "voice"].includes(message2.messageType)) {
15075
+ void this.sendReply(config, message2.userId, "This WeCom gateway supports text, image, and voice messages only.").catch((error) => {
14692
15076
  console.warn("[wecom-gateway] unsupported-message reply failed:", error instanceof Error ? error.message : error);
14693
15077
  });
14694
15078
  return "success";
14695
15079
  }
14696
15080
  this.enqueue({
14697
- messageId: message.messageId,
14698
- userId: message.userId,
14699
- text: message.text,
14700
- messageType: message.messageType,
14701
- mediaId: message.mediaId,
14702
- picUrl: message.picUrl
15081
+ messageId: message2.messageId,
15082
+ userId: message2.userId,
15083
+ text: message2.text,
15084
+ messageType: message2.messageType,
15085
+ mediaId: message2.mediaId,
15086
+ picUrl: message2.picUrl
14703
15087
  }, config);
14704
15088
  return "success";
14705
15089
  }
@@ -14786,57 +15170,57 @@ var WecomGatewayService = class {
14786
15170
  this.seen.set(id, now + DEDUP_TTL_MS3);
14787
15171
  return false;
14788
15172
  }
14789
- enqueue(message, config) {
14790
- const clientId = `wecom:dm:${message.userId}`;
15173
+ enqueue(message2, config) {
15174
+ const clientId = `wecom:dm:${message2.userId}`;
14791
15175
  const previous = this.queues.get(clientId) || Promise.resolve();
14792
- const next = previous.catch(() => void 0).then(() => this.processMessage(clientId, message, config)).catch((error) => console.error("[wecom-gateway] message handling failed:", error)).finally(() => {
15176
+ const next = previous.catch(() => void 0).then(() => this.processMessage(clientId, message2, config)).catch((error) => console.error("[wecom-gateway] message handling failed:", error)).finally(() => {
14793
15177
  if (this.queues.get(clientId) === next) this.queues.delete(clientId);
14794
15178
  });
14795
15179
  this.queues.set(clientId, next);
14796
15180
  }
14797
- async processMessage(clientId, message, config) {
15181
+ async processMessage(clientId, message2, config) {
14798
15182
  const effectiveConfig = this.resolveChatConfig(clientId, config);
14799
- if (await this.handleCommand(clientId, message, config, effectiveConfig)) return;
15183
+ if (await this.handleCommand(clientId, message2, config, effectiveConfig)) return;
14800
15184
  if (!effectiveConfig.defaultCwd) {
14801
- await this.sendReply(config, message.userId, "No gateway folders configured. Add at least one allowed folder in Web UI Settings > Gateway.");
15185
+ await this.sendReply(config, message2.userId, "No gateway folders configured. Add at least one allowed folder in Web UI Settings > Gateway.");
14802
15186
  return;
14803
15187
  }
14804
- let promptText = message.text;
14805
- if (message.messageType === "voice") {
14806
- if (!message.mediaId) {
14807
- await this.sendReply(config, message.userId, "The WeCom voice message did not include downloadable media. Try sending it again.");
15188
+ let promptText = message2.text;
15189
+ if (message2.messageType === "voice") {
15190
+ if (!message2.mediaId) {
15191
+ await this.sendReply(config, message2.userId, "The WeCom voice message did not include downloadable media. Try sending it again.");
14808
15192
  return;
14809
15193
  }
14810
15194
  try {
14811
- promptText = (await transcribeAudio(await this.downloadVoice(config, message.mediaId), "audio/amr")).trim();
15195
+ promptText = (await transcribeAudio(await this.downloadVoice(config, message2.mediaId), "audio/amr")).trim();
14812
15196
  } catch (error) {
14813
15197
  console.warn("[wecom-gateway] failed to transcribe inbound voice:", error instanceof Error ? error.message : error);
14814
- await this.sendReply(config, message.userId, "The voice message could not be transcribed. Check the speech-to-text configuration and try again.");
15198
+ await this.sendReply(config, message2.userId, "The voice message could not be transcribed. Check the speech-to-text configuration and try again.");
14815
15199
  return;
14816
15200
  }
14817
15201
  if (!promptText) {
14818
- await this.sendReply(config, message.userId, "The voice message did not contain recognizable speech. Try sending it again.");
15202
+ await this.sendReply(config, message2.userId, "The voice message did not contain recognizable speech. Try sending it again.");
14819
15203
  return;
14820
15204
  }
14821
15205
  }
14822
15206
  const session = await this.ensureSession(clientId, effectiveConfig);
14823
15207
  let images = [];
14824
- if (message.messageType === "image") {
14825
- if (!message.mediaId) {
14826
- await this.sendReply(config, message.userId, "The WeCom image message did not include downloadable media. Try sending it again.");
15208
+ if (message2.messageType === "image") {
15209
+ if (!message2.mediaId) {
15210
+ await this.sendReply(config, message2.userId, "The WeCom image message did not include downloadable media. Try sending it again.");
14827
15211
  return;
14828
15212
  }
14829
15213
  try {
14830
- images = [await this.downloadImage(config, message.mediaId)];
15214
+ images = [await this.downloadImage(config, message2.mediaId)];
14831
15215
  } catch (error) {
14832
15216
  console.warn("[wecom-gateway] failed to download inbound image:", error instanceof Error ? error.message : error);
14833
- await this.sendReply(config, message.userId, "The image could not be downloaded from WeCom. Try sending it again.");
15217
+ await this.sendReply(config, message2.userId, "The image could not be downloaded from WeCom. Try sending it again.");
14834
15218
  return;
14835
15219
  }
14836
15220
  }
14837
15221
  const imageResult = validateImages(images, session.model);
14838
15222
  if (!imageResult.ok) {
14839
- await this.sendReply(config, message.userId, imageResult.message);
15223
+ await this.sendReply(config, message2.userId, imageResult.message);
14840
15224
  return;
14841
15225
  }
14842
15226
  promptText ||= imageResult.images.length ? "Please analyze the attached image." : "";
@@ -14851,7 +15235,7 @@ var WecomGatewayService = class {
14851
15235
  unsubscribe();
14852
15236
  }
14853
15237
  const response = chunks.join("").trim() || extractLastAssistantText3(session.messages) || "(Pi finished without a text response.)";
14854
- await this.sendReply(config, message.userId, response);
15238
+ await this.sendReply(config, message2.userId, response);
14855
15239
  }
14856
15240
  async ensureSession(clientId, config) {
14857
15241
  await this.sessionService.setClientAgentProfile(clientId, config.agentProfile || "default");
@@ -14880,28 +15264,28 @@ var WecomGatewayService = class {
14880
15264
  `).run(clientId, clientId, result.session.sessionId, now, now);
14881
15265
  return result.session;
14882
15266
  }
14883
- async handleCommand(clientId, message, baseConfig, effectiveConfig) {
14884
- const commandText = normalizeGatewayCommandText(message.text);
15267
+ async handleCommand(clientId, message2, baseConfig, effectiveConfig) {
15268
+ const commandText = normalizeGatewayCommandText(message2.text);
14885
15269
  const [command = "", ...args] = commandText.split(/\s+/);
14886
15270
  const normalized = command.toLowerCase();
14887
15271
  if (!normalized.startsWith("/")) return false;
14888
15272
  try {
14889
15273
  if (normalized === "/new" || normalized === "/reset") {
14890
15274
  this.resetSession(clientId);
14891
- await this.sendReply(baseConfig, message.userId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Reset this WeCom chat. A fresh Pi session will start with your next message."));
15275
+ await this.sendReply(baseConfig, message2.userId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Reset this WeCom chat. A fresh Pi session will start with your next message."));
14892
15276
  return true;
14893
15277
  }
14894
15278
  if (normalized === "/help") {
14895
- await this.sendReply(baseConfig, message.userId, this.formatHelp());
15279
+ await this.sendReply(baseConfig, message2.userId, this.formatHelp());
14896
15280
  return true;
14897
15281
  }
14898
15282
  if (normalized === "/status") {
14899
- await this.sendReply(baseConfig, message.userId, await this.formatStatus(clientId, effectiveConfig));
15283
+ await this.sendReply(baseConfig, message2.userId, await this.formatStatus(clientId, effectiveConfig));
14900
15284
  return true;
14901
15285
  }
14902
15286
  if (normalized === "/profiles") {
14903
15287
  const profiles = await this.sessionService.listAgentProfiles();
14904
- await this.sendReply(baseConfig, message.userId, ["Available profiles:", ...profiles.map((profile) => `${profile.id === (effectiveConfig.agentProfile || "default") ? "*" : " "} ${profile.id}`)].join("\n"));
15288
+ await this.sendReply(baseConfig, message2.userId, ["Available profiles:", ...profiles.map((profile) => `${profile.id === (effectiveConfig.agentProfile || "default") ? "*" : " "} ${profile.id}`)].join("\n"));
14905
15289
  return true;
14906
15290
  }
14907
15291
  if (normalized === "/profile" || normalized === "/use-profile" || normalized === "/set-profile") {
@@ -14911,25 +15295,25 @@ var WecomGatewayService = class {
14911
15295
  if (!profiles.some((item) => item.id === profile)) throw new Error(`Unknown profile: ${profile}`);
14912
15296
  this.saveChatConfig(clientId, { agentProfile: profile });
14913
15297
  this.resetSession(clientId);
14914
- await this.sendReply(baseConfig, message.userId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Profile updated."));
15298
+ await this.sendReply(baseConfig, message2.userId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Profile updated."));
14915
15299
  return true;
14916
15300
  }
14917
15301
  if (normalized === "/cwds" || normalized === "/pwds") {
14918
- await this.sendReply(baseConfig, message.userId, baseConfig.cwdChoices.length ? baseConfig.cwdChoices.map((cwd, index) => `${index + 1}: ${cwd}`).join("\n") : "No gateway folders configured.");
15302
+ await this.sendReply(baseConfig, message2.userId, baseConfig.cwdChoices.length ? baseConfig.cwdChoices.map((cwd, index) => `${index + 1}: ${cwd}`).join("\n") : "No gateway folders configured.");
14919
15303
  return true;
14920
15304
  }
14921
15305
  if (normalized === "/cwd") {
14922
15306
  const slot = Number(args[0]);
14923
15307
  if (!Number.isInteger(slot) || slot < 1 || slot > baseConfig.cwdChoices.length) throw new Error(`Usage: /cwd <1-${baseConfig.cwdChoices.length}>`);
14924
15308
  const target = baseConfig.cwdChoices[slot - 1];
14925
- if (!isAbsolute6(target) || !(await stat8(target)).isDirectory()) throw new Error(`Configured CWD ${slot} must be an existing absolute directory`);
15309
+ if (!isAbsolute7(target) || !(await stat9(target)).isDirectory()) throw new Error(`Configured CWD ${slot} must be an existing absolute directory`);
14926
15310
  this.saveChatConfig(clientId, { defaultCwd: target });
14927
15311
  this.resetSession(clientId);
14928
- await this.sendReply(baseConfig, message.userId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Working directory updated."));
15312
+ await this.sendReply(baseConfig, message2.userId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Working directory updated."));
14929
15313
  return true;
14930
15314
  }
14931
15315
  if (normalized === "/skillsets") {
14932
- await this.sendReply(baseConfig, message.userId, this.formatSkillsets(effectiveConfig.skillPresetId));
15316
+ await this.sendReply(baseConfig, message2.userId, this.formatSkillsets(effectiveConfig.skillPresetId));
14933
15317
  return true;
14934
15318
  }
14935
15319
  if (normalized === "/skillset") {
@@ -14937,22 +15321,22 @@ var WecomGatewayService = class {
14937
15321
  if (!selected) throw new Error("Usage: /skillset <all|preset-name>");
14938
15322
  this.saveChatConfig(clientId, this.resolveSkillsetConfig(selected, true));
14939
15323
  this.resetSession(clientId);
14940
- await this.sendReply(baseConfig, message.userId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Skillset updated."));
15324
+ await this.sendReply(baseConfig, message2.userId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "Skillset updated."));
14941
15325
  return true;
14942
15326
  }
14943
15327
  if (normalized === "/clear-config") {
14944
15328
  this.db.prepare("DELETE FROM wecom_gateway_configs WHERE client_id = ?").run(clientId);
14945
15329
  this.resetSession(clientId);
14946
- await this.sendReply(baseConfig, message.userId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "WeCom chat config cleared."));
15330
+ await this.sendReply(baseConfig, message2.userId, await this.formatStatus(clientId, this.resolveChatConfig(clientId, baseConfig), "WeCom chat config cleared."));
14947
15331
  return true;
14948
15332
  }
14949
15333
  } catch (error) {
14950
- await this.sendReply(baseConfig, message.userId, `Command failed: ${error instanceof Error ? error.message : "unknown error"}
15334
+ await this.sendReply(baseConfig, message2.userId, `Command failed: ${error instanceof Error ? error.message : "unknown error"}
14951
15335
 
14952
15336
  ${this.formatHelp()}`);
14953
15337
  return true;
14954
15338
  }
14955
- await this.sendReply(baseConfig, message.userId, `Unknown command: ${command}
15339
+ await this.sendReply(baseConfig, message2.userId, `Unknown command: ${command}
14956
15340
 
14957
15341
  ${this.formatHelp()}`);
14958
15342
  return true;
@@ -15181,8 +15565,8 @@ function normalizeList(value) {
15181
15565
  function stringValue3(value) {
15182
15566
  return typeof value === "string" ? value : value === void 0 || value === null ? "" : String(value);
15183
15567
  }
15184
- function gatewayError(message, statusCode) {
15185
- const error = new Error(message);
15568
+ function gatewayError(message2, statusCode) {
15569
+ const error = new Error(message2);
15186
15570
  error.statusCode = statusCode;
15187
15571
  return error;
15188
15572
  }
@@ -15198,11 +15582,11 @@ async function parseWecomResponse(response) {
15198
15582
  }
15199
15583
  function extractLastAssistantText3(messages) {
15200
15584
  for (let index = messages.length - 1; index >= 0; index -= 1) {
15201
- const message = messages[index];
15202
- if (message?.role !== "assistant") continue;
15203
- if (typeof message.content === "string" && message.content.trim()) return message.content.trim();
15204
- if (!Array.isArray(message.content)) continue;
15205
- const text = message.content.filter((part) => part?.type === "text" && typeof part.text === "string").map((part) => part.text).join("").trim();
15585
+ const message2 = messages[index];
15586
+ if (message2?.role !== "assistant") continue;
15587
+ if (typeof message2.content === "string" && message2.content.trim()) return message2.content.trim();
15588
+ if (!Array.isArray(message2.content)) continue;
15589
+ const text = message2.content.filter((part) => part?.type === "text" && typeof part.text === "string").map((part) => part.text).join("").trim();
15206
15590
  if (text) return text;
15207
15591
  }
15208
15592
  return void 0;
@@ -15255,7 +15639,7 @@ var ClaudeCodeReviewSourceAdapter = class {
15255
15639
  if (!normalized) return this.list(options);
15256
15640
  const matches = this.readSessions().filter(({ item, filePath }) => {
15257
15641
  if (item.name?.toLowerCase().includes(normalized) || item.firstMessage?.toLowerCase().includes(normalized)) return true;
15258
- return this.readTranscript(filePath).messages.some((message) => this.messageText(message.content).toLowerCase().includes(normalized));
15642
+ return this.readTranscript(filePath).messages.some((message2) => this.messageText(message2.content).toLowerCase().includes(normalized));
15259
15643
  }).map((session) => session.item).sort((a, b) => b.modified.localeCompare(a.modified));
15260
15644
  return this.applyListOptions(matches, options);
15261
15645
  }
@@ -15276,14 +15660,14 @@ var ClaudeCodeReviewSourceAdapter = class {
15276
15660
  try {
15277
15661
  const records = this.readRecords(filePath);
15278
15662
  if (!records.length) return [];
15279
- const stat9 = fs5.statSync(filePath);
15663
+ const stat10 = fs5.statSync(filePath);
15280
15664
  const sessionRecord = records.find((record) => record.sessionId || record.session_id);
15281
15665
  const sessionId = sessionRecord?.sessionId || sessionRecord?.session_id || path7.basename(filePath, ".jsonl");
15282
15666
  const cwd = records.find((record) => record.cwd)?.cwd;
15283
15667
  const timestamps = records.map((record) => this.timestamp(record.timestamp)).filter((value) => value !== void 0);
15284
15668
  const messages = this.recordsToMessages(records);
15285
15669
  const title = [...records].reverse().find((record) => typeof record.aiTitle === "string")?.aiTitle;
15286
- const firstUserMessage = messages.find((message) => message.role === "user");
15670
+ const firstUserMessage = messages.find((message2) => message2.role === "user");
15287
15671
  return [{
15288
15672
  filePath,
15289
15673
  item: {
@@ -15292,8 +15676,8 @@ var ClaudeCodeReviewSourceAdapter = class {
15292
15676
  name: title,
15293
15677
  path: filePath,
15294
15678
  cwd,
15295
- created: new Date(timestamps.length ? Math.min(...timestamps) : stat9.birthtimeMs).toISOString(),
15296
- modified: new Date(timestamps.length ? Math.max(...timestamps) : stat9.mtimeMs).toISOString(),
15679
+ created: new Date(timestamps.length ? Math.min(...timestamps) : stat10.birthtimeMs).toISOString(),
15680
+ modified: new Date(timestamps.length ? Math.max(...timestamps) : stat10.mtimeMs).toISOString(),
15297
15681
  messageCount: messages.length,
15298
15682
  firstMessage: firstUserMessage ? this.messageText(firstUserMessage.content) : void 0
15299
15683
  }
@@ -15418,7 +15802,7 @@ var CodexReviewSourceAdapter = class {
15418
15802
  if (!normalized) return this.list(options);
15419
15803
  const matches = this.readSessions().filter(({ item, filePath }) => {
15420
15804
  if (item.name?.toLowerCase().includes(normalized) || item.firstMessage?.toLowerCase().includes(normalized)) return true;
15421
- return this.readTranscript(filePath).messages.some((message) => this.messageText(message.content).toLowerCase().includes(normalized));
15805
+ return this.readTranscript(filePath).messages.some((message2) => this.messageText(message2.content).toLowerCase().includes(normalized));
15422
15806
  }).map((session) => session.item).sort((a, b) => b.modified.localeCompare(a.modified));
15423
15807
  return this.applyListOptions(matches, options);
15424
15808
  }
@@ -15439,11 +15823,11 @@ var CodexReviewSourceAdapter = class {
15439
15823
  try {
15440
15824
  const records = this.readRecords(filePath);
15441
15825
  if (!records.length) return [];
15442
- const stat9 = fs6.statSync(filePath);
15826
+ const stat10 = fs6.statSync(filePath);
15443
15827
  const metadata = records.find((record) => record.type === "session_meta")?.payload;
15444
15828
  const messages = this.recordsToMessages(records);
15445
15829
  const timestamps = records.map((record) => this.timestamp(record.timestamp)).filter((value) => value !== void 0);
15446
- const firstUserMessage = messages.find((message) => message.role === "user" && !message.detailOnly);
15830
+ const firstUserMessage = messages.find((message2) => message2.role === "user" && !message2.detailOnly);
15447
15831
  return [{
15448
15832
  filePath,
15449
15833
  item: {
@@ -15451,8 +15835,8 @@ var CodexReviewSourceAdapter = class {
15451
15835
  sourceId: "",
15452
15836
  path: filePath,
15453
15837
  cwd: this.stringValue(metadata?.cwd),
15454
- created: new Date(timestamps.length ? Math.min(...timestamps) : stat9.birthtimeMs).toISOString(),
15455
- modified: new Date(timestamps.length ? Math.max(...timestamps) : stat9.mtimeMs).toISOString(),
15838
+ created: new Date(timestamps.length ? Math.min(...timestamps) : stat10.birthtimeMs).toISOString(),
15839
+ modified: new Date(timestamps.length ? Math.max(...timestamps) : stat10.mtimeMs).toISOString(),
15456
15840
  messageCount: messages.length,
15457
15841
  firstMessage: firstUserMessage ? this.messageText(firstUserMessage.content) : void 0
15458
15842
  }
@@ -15719,15 +16103,15 @@ var DevinReviewSourceAdapter = class {
15719
16103
  }
15720
16104
  extractMessages(raw) {
15721
16105
  if (Array.isArray(raw.messages) && raw.messages.length > 0) {
15722
- return raw.messages.map((message) => ({
15723
- role: String(message.role || "unknown"),
15724
- content: message.content,
15725
- timestamp: typeof message.timestamp === "number" ? message.timestamp : void 0,
15726
- detailOnly: this.isDetailOnlyMessage(message.content)
16106
+ return raw.messages.map((message2) => ({
16107
+ role: String(message2.role || "unknown"),
16108
+ content: message2.content,
16109
+ timestamp: typeof message2.timestamp === "number" ? message2.timestamp : void 0,
16110
+ detailOnly: this.isDetailOnlyMessage(message2.content)
15727
16111
  }));
15728
16112
  }
15729
16113
  const steps = Array.isArray(raw.steps) ? raw.steps : [];
15730
- return steps.map((step) => this.devinStepToMessage(step)).filter((message) => message.content !== "");
16114
+ return steps.map((step) => this.devinStepToMessage(step)).filter((message2) => message2.content !== "");
15731
16115
  }
15732
16116
  devinStepToMessage(step) {
15733
16117
  const source = String(step.source || "unknown");
@@ -15769,16 +16153,16 @@ ${observation}
15769
16153
  const text = typeof content === "string" ? content.trimStart() : "";
15770
16154
  return text.startsWith("You are Devin, an interactive command line") || text.startsWith("You are powered by ") || /^Available subagent profiles for the\s+`?run_subagent`?\s+tool\./.test(text) || text.startsWith("The following skills can be invoked using the `skill` tool.");
15771
16155
  }
15772
- messageToText(message) {
15773
- if (!message) return void 0;
15774
- if (typeof message.content === "string") return message.content;
15775
- if (Array.isArray(message.content)) {
15776
- return message.content.map((item) => typeof item === "string" ? item : item?.text).filter(Boolean).join("\n");
16156
+ messageToText(message2) {
16157
+ if (!message2) return void 0;
16158
+ if (typeof message2.content === "string") return message2.content;
16159
+ if (Array.isArray(message2.content)) {
16160
+ return message2.content.map((item) => typeof item === "string" ? item : item?.text).filter(Boolean).join("\n");
15777
16161
  }
15778
16162
  return void 0;
15779
16163
  }
15780
16164
  transcriptToText(transcript) {
15781
- return transcript.messages.map((message) => this.messageToText(message) || "").join("\n");
16165
+ return transcript.messages.map((message2) => this.messageToText(message2) || "").join("\n");
15782
16166
  }
15783
16167
  applyListOptions(items, options) {
15784
16168
  let result = items;
@@ -15988,6 +16372,336 @@ var ReviewSourceService = class {
15988
16372
  }
15989
16373
  };
15990
16374
 
16375
+ // src/services/model-window-kickoff-store.ts
16376
+ import { randomUUID as randomUUID8 } from "node:crypto";
16377
+ var DEFAULT_KICKOFF_PROMPT = "Ping";
16378
+ var ModelWindowKickoffStore = class {
16379
+ constructor(db) {
16380
+ this.db = db;
16381
+ }
16382
+ db;
16383
+ list() {
16384
+ return this.db.prepare("SELECT * FROM model_window_kickoffs ORDER BY created_at").all().map(toRecord);
16385
+ }
16386
+ get(id) {
16387
+ const row = this.db.prepare("SELECT * FROM model_window_kickoffs WHERE id = ?").get(id);
16388
+ return row && toRecord(row);
16389
+ }
16390
+ listDue(now) {
16391
+ return this.db.prepare(`
16392
+ SELECT * FROM model_window_kickoffs WHERE enabled = 1 AND next_run_at <= ? ORDER BY next_run_at
16393
+ `).all(now).map(toRecord);
16394
+ }
16395
+ create(input, now = (/* @__PURE__ */ new Date()).toISOString()) {
16396
+ const value = validateInput2(input, now);
16397
+ const id = randomUUID8();
16398
+ this.db.prepare(`
16399
+ INSERT INTO model_window_kickoffs (
16400
+ id, enabled, profile_id, provider, model_id, project_path, prompt, window_duration_minutes,
16401
+ safety_buffer_seconds, notification_channel_id, next_run_at, created_at, updated_at
16402
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
16403
+ `).run(
16404
+ id,
16405
+ value.enabled ? 1 : 0,
16406
+ value.profileId,
16407
+ value.provider,
16408
+ value.modelId,
16409
+ value.projectPath,
16410
+ value.prompt,
16411
+ value.windowDurationMinutes,
16412
+ value.safetyBufferSeconds,
16413
+ value.notificationChannelId,
16414
+ value.nextRunAt,
16415
+ now,
16416
+ now
16417
+ );
16418
+ return this.get(id);
16419
+ }
16420
+ update(id, input, now = (/* @__PURE__ */ new Date()).toISOString()) {
16421
+ if (!this.get(id)) throw new Error("Model window kickoff not found");
16422
+ const value = validateInput2(input, now);
16423
+ this.db.prepare(`
16424
+ UPDATE model_window_kickoffs SET enabled = ?, profile_id = ?, provider = ?, model_id = ?, project_path = ?, prompt = ?,
16425
+ window_duration_minutes = ?, safety_buffer_seconds = ?, notification_channel_id = ?, next_run_at = ?, updated_at = ?
16426
+ WHERE id = ?
16427
+ `).run(
16428
+ value.enabled ? 1 : 0,
16429
+ value.profileId,
16430
+ value.provider,
16431
+ value.modelId,
16432
+ value.projectPath,
16433
+ value.prompt,
16434
+ value.windowDurationMinutes,
16435
+ value.safetyBufferSeconds,
16436
+ value.notificationChannelId,
16437
+ value.nextRunAt,
16438
+ now,
16439
+ id
16440
+ );
16441
+ return this.get(id);
16442
+ }
16443
+ delete(id) {
16444
+ if (this.db.prepare("DELETE FROM model_window_kickoffs WHERE id = ?").run(id).changes !== 1) {
16445
+ throw new Error("Model window kickoff not found");
16446
+ }
16447
+ }
16448
+ recordAttempt(id, at) {
16449
+ this.db.prepare("UPDATE model_window_kickoffs SET last_attempt_at = ?, updated_at = ? WHERE id = ?").run(at, at, id);
16450
+ }
16451
+ recordSuccess(id, at, nextRunAt) {
16452
+ this.db.prepare(`UPDATE model_window_kickoffs SET last_success_at = ?, next_run_at = ?, last_error = NULL, updated_at = ? WHERE id = ?`).run(at, nextRunAt, at, id);
16453
+ }
16454
+ recordFailure(id, at, error, nextRunAt) {
16455
+ this.db.prepare(`UPDATE model_window_kickoffs SET last_error = ?, next_run_at = ?, updated_at = ? WHERE id = ?`).run(error.slice(0, 2e3), nextRunAt, at, id);
16456
+ }
16457
+ };
16458
+ function validateInput2(input, now) {
16459
+ const profileId = input.profileId?.trim();
16460
+ const provider = input.provider?.trim();
16461
+ const modelId = input.modelId?.trim();
16462
+ const projectPath = input.projectPath?.trim() || "~";
16463
+ const prompt = (input.prompt ?? DEFAULT_KICKOFF_PROMPT).trim();
16464
+ const windowDurationMinutes = input.windowDurationMinutes ?? 300;
16465
+ const safetyBufferSeconds = input.safetyBufferSeconds ?? 60;
16466
+ const nextRunAt = input.nextRunAt ?? now;
16467
+ if (!profileId || !provider || !modelId || !prompt) {
16468
+ throw new Error("Profile, provider, model, and prompt are required");
16469
+ }
16470
+ if (!Number.isInteger(windowDurationMinutes) || windowDurationMinutes < 1 || windowDurationMinutes > 43200) {
16471
+ throw new Error("Window duration must be between 1 and 43200 minutes");
16472
+ }
16473
+ if (!Number.isInteger(safetyBufferSeconds) || safetyBufferSeconds < 0 || safetyBufferSeconds > 86400) {
16474
+ throw new Error("Safety buffer must be between 0 and 86400 seconds");
16475
+ }
16476
+ if (!Number.isFinite(Date.parse(nextRunAt))) {
16477
+ throw new Error("Next run time is invalid");
16478
+ }
16479
+ return {
16480
+ enabled: input.enabled ?? true,
16481
+ profileId,
16482
+ provider,
16483
+ modelId,
16484
+ projectPath,
16485
+ prompt,
16486
+ windowDurationMinutes,
16487
+ safetyBufferSeconds,
16488
+ notificationChannelId: input.notificationChannelId || null,
16489
+ nextRunAt: new Date(nextRunAt).toISOString()
16490
+ };
16491
+ }
16492
+ function toRecord(row) {
16493
+ return {
16494
+ id: row.id,
16495
+ enabled: Boolean(row.enabled),
16496
+ profileId: row.profile_id,
16497
+ provider: row.provider,
16498
+ modelId: row.model_id,
16499
+ projectPath: row.project_path,
16500
+ prompt: row.prompt,
16501
+ windowDurationMinutes: row.window_duration_minutes,
16502
+ safetyBufferSeconds: row.safety_buffer_seconds,
16503
+ notificationChannelId: row.notification_channel_id,
16504
+ nextRunAt: row.next_run_at,
16505
+ lastAttemptAt: row.last_attempt_at,
16506
+ lastSuccessAt: row.last_success_at,
16507
+ lastError: row.last_error,
16508
+ createdAt: row.created_at,
16509
+ updatedAt: row.updated_at
16510
+ };
16511
+ }
16512
+
16513
+ // src/services/model-window-kickoff-scheduler.ts
16514
+ var POLL_INTERVAL_MS = 1 * 6e4;
16515
+ var FAILURE_RETRY_MS = 60 * 6e4;
16516
+ var ModelWindowKickoffScheduler = class {
16517
+ constructor(dependencies) {
16518
+ this.dependencies = dependencies;
16519
+ this.now = dependencies.now ?? (() => /* @__PURE__ */ new Date());
16520
+ }
16521
+ dependencies;
16522
+ timer;
16523
+ running = /* @__PURE__ */ new Set();
16524
+ now;
16525
+ start() {
16526
+ if (this.timer) return;
16527
+ void this.tick();
16528
+ this.timer = setInterval(() => void this.tick(), this.dependencies.pollIntervalMs ?? POLL_INTERVAL_MS);
16529
+ this.timer.unref();
16530
+ }
16531
+ async stop() {
16532
+ if (this.timer) clearInterval(this.timer);
16533
+ this.timer = void 0;
16534
+ while (this.running.size) await new Promise((resolve10) => setTimeout(resolve10, 10));
16535
+ }
16536
+ async tick() {
16537
+ const due = this.dependencies.store.listDue(this.now().toISOString());
16538
+ await Promise.allSettled(due.map((kickoff) => this.run(kickoff)));
16539
+ }
16540
+ async run(kickoff) {
16541
+ if (this.running.has(kickoff.id)) return;
16542
+ this.running.add(kickoff.id);
16543
+ const attemptedAt = this.now();
16544
+ this.dependencies.store.recordAttempt(kickoff.id, attemptedAt.toISOString());
16545
+ try {
16546
+ const profile = await this.dependencies.resolveProfile(kickoff.profileId);
16547
+ if (!profile) throw new Error("Agent profile not found");
16548
+ const response = this.dependencies.probe ? await this.dependencies.probe(kickoff, profile) : await probeModel(kickoff, this.dependencies.sessions);
16549
+ const successfulAt = this.now();
16550
+ const next = new Date(successfulAt.getTime() + kickoff.windowDurationMinutes * 6e4 + kickoff.safetyBufferSeconds * 1e3);
16551
+ this.dependencies.store.recordSuccess(kickoff.id, successfulAt.toISOString(), next.toISOString());
16552
+ if (kickoff.notificationChannelId) {
16553
+ try {
16554
+ await this.dependencies.notifications.send(
16555
+ kickoff.notificationChannelId,
16556
+ formatSuccessNotification(kickoff, profile, response, attemptedAt, next)
16557
+ );
16558
+ } catch (error) {
16559
+ this.dependencies.log?.error(error, "Model window kickoff notification failed");
16560
+ }
16561
+ }
16562
+ } catch (error) {
16563
+ const failedAt = this.now();
16564
+ const failure = error instanceof Error ? error.message : String(error);
16565
+ const retryAt = new Date(failedAt.getTime() + FAILURE_RETRY_MS);
16566
+ this.dependencies.store.recordFailure(kickoff.id, failedAt.toISOString(), failure, retryAt.toISOString());
16567
+ if (kickoff.notificationChannelId) {
16568
+ try {
16569
+ await this.dependencies.notifications.send(
16570
+ kickoff.notificationChannelId,
16571
+ formatFailureNotification(kickoff, failure, attemptedAt, retryAt)
16572
+ );
16573
+ } catch (notificationError) {
16574
+ this.dependencies.log?.error(notificationError, "Model window kickoff failure notification failed");
16575
+ }
16576
+ }
16577
+ } finally {
16578
+ this.running.delete(kickoff.id);
16579
+ }
16580
+ }
16581
+ };
16582
+ function formatSuccessNotification(kickoff, profile, response, startedAt, next) {
16583
+ return [
16584
+ "\u2705Model window started",
16585
+ markdownField("Profile", profile.label || profile.id),
16586
+ markdownField("Model", `${kickoff.provider}/${kickoff.modelId}`),
16587
+ ...response ? [markdownField("Response", response)] : [],
16588
+ markdownField("Started time", formatLocalDateTime(startedAt), "warning"),
16589
+ markdownField("Next kickoff", formatLocalDateTime(next))
16590
+ ].join("\n");
16591
+ }
16592
+ function formatFailureNotification(kickoff, failure, startedAt, retryAt) {
16593
+ return [
16594
+ "\u274CModel window kickoff failed",
16595
+ markdownField("Profile", kickoff.profileId),
16596
+ markdownField("Model", `${kickoff.provider}/${kickoff.modelId}`),
16597
+ markdownField("Error", failure, "warning"),
16598
+ markdownField("Started time", formatLocalDateTime(startedAt), "warning"),
16599
+ markdownField("Retry", formatLocalDateTime(retryAt))
16600
+ ].join("\n");
16601
+ }
16602
+ function markdownField(label, value, color = "comment") {
16603
+ return `>${label}: <font color="${color}">${value}</font>`;
16604
+ }
16605
+ function formatLocalDateTime(value) {
16606
+ const pad = (part) => String(part).padStart(2, "0");
16607
+ return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())} ${pad(value.getHours())}:${pad(value.getMinutes())}:${pad(value.getSeconds())}`;
16608
+ }
16609
+ async function probeModel(kickoff, sessions) {
16610
+ const clientId = `model-window-kickoff:${kickoff.id}`;
16611
+ const sessionName = `Model window kickoff: ${kickoff.provider}/${kickoff.modelId} (${kickoff.id.slice(0, 8)})`;
16612
+ await sessions.setClientAgentProfile(clientId, kickoff.profileId);
16613
+ const persisted = (await sessions.listSessions(clientId, kickoff.projectPath)).find((item) => item.name === sessionName);
16614
+ let activeSession;
16615
+ if (persisted) {
16616
+ activeSession = await sessions.resumeSession(clientId, persisted.path);
16617
+ } else {
16618
+ activeSession = (await sessions.createSession(clientId, {
16619
+ cwd: kickoff.projectPath,
16620
+ agentProfileId: kickoff.profileId,
16621
+ modelProvider: kickoff.provider,
16622
+ modelId: kickoff.modelId,
16623
+ memoryEnabled: false
16624
+ })).session;
16625
+ await sessions.renameSession(clientId, activeSession.sessionId, sessionName);
16626
+ }
16627
+ await sessions.setSessionModel(clientId, activeSession.sessionId, kickoff.provider, kickoff.modelId);
16628
+ await sessions.runForegroundWithClientProfileProxy(clientId, () => activeSession.prompt(kickoff.prompt));
16629
+ const response = [...activeSession.messages].reverse().find((message2) => message2.role === "assistant" && "stopReason" in message2);
16630
+ if (!response || !("stopReason" in response)) throw new Error("Model kickoff finished without an assistant response");
16631
+ if (response.stopReason === "error" || response.stopReason === "aborted") {
16632
+ throw new Error(response.errorMessage || `Model kickoff ${response.stopReason}`);
16633
+ }
16634
+ return extractAssistantText(response.content);
16635
+ }
16636
+ function extractAssistantText(content) {
16637
+ if (typeof content === "string") return content.trim();
16638
+ if (!Array.isArray(content)) return "";
16639
+ return content.filter((part) => typeof part === "object" && part !== null && "type" in part && part.type === "text" && "text" in part && typeof part.text === "string").map((part) => part.text).join("").trim();
16640
+ }
16641
+
16642
+ // src/services/notification-channel.ts
16643
+ import { randomUUID as randomUUID9 } from "node:crypto";
16644
+ var NotificationChannelService = class {
16645
+ constructor(db) {
16646
+ this.db = db;
16647
+ }
16648
+ db;
16649
+ list() {
16650
+ return this.db.prepare("SELECT id, type, name, config_json FROM notification_channels ORDER BY created_at").all().map(publicChannel);
16651
+ }
16652
+ saveWecom(input) {
16653
+ const existing = input.id ? this.getRow(input.id) : void 0;
16654
+ const botKey = input.botKey?.trim() || readBotKey(existing);
16655
+ if (!botKey) throw new Error("WeCom bot key is required");
16656
+ const id = existing?.id ?? randomUUID9();
16657
+ const now = (/* @__PURE__ */ new Date()).toISOString();
16658
+ const name = input.name?.trim() || existing?.name || "WeCom";
16659
+ this.db.prepare(`
16660
+ INSERT INTO notification_channels (id, type, name, config_json, created_at, updated_at)
16661
+ VALUES (?, 'wecom', ?, ?, ?, ?)
16662
+ ON CONFLICT(id) DO UPDATE SET name = excluded.name, config_json = excluded.config_json, updated_at = excluded.updated_at
16663
+ `).run(id, name, JSON.stringify({ botKey }), now, now);
16664
+ return publicChannel(this.getRow(id));
16665
+ }
16666
+ delete(id) {
16667
+ if (this.db.prepare("DELETE FROM notification_channels WHERE id = ?").run(id).changes !== 1) {
16668
+ throw new Error("Notification channel not found");
16669
+ }
16670
+ }
16671
+ async send(id, message2) {
16672
+ const row = this.getRow(id);
16673
+ if (!row) throw new Error("Notification channel not found");
16674
+ if (row.type !== "wecom") throw new Error(`Unsupported notification channel: ${row.type}`);
16675
+ const botKey = readBotKey(row);
16676
+ if (!botKey) throw new Error("WeCom bot key is not configured");
16677
+ const response = await fetch(`https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${encodeURIComponent(botKey)}`, {
16678
+ method: "POST",
16679
+ headers: { "content-type": "application/json" },
16680
+ body: JSON.stringify({ msgtype: "markdown", markdown: { content: message2 } }),
16681
+ signal: AbortSignal.timeout(1e4)
16682
+ });
16683
+ if (!response.ok) throw new Error(`WeCom returned HTTP ${response.status}`);
16684
+ const result = await response.json();
16685
+ if (result.errcode !== 0) throw new Error(`WeCom notification failed: ${result.errmsg || result.errcode}`);
16686
+ }
16687
+ getRow(id) {
16688
+ return this.db.prepare("SELECT id, type, name, config_json FROM notification_channels WHERE id = ?").get(id);
16689
+ }
16690
+ };
16691
+ function readBotKey(row) {
16692
+ if (!row) {
16693
+ return "";
16694
+ }
16695
+ try {
16696
+ return JSON.parse(row.config_json).botKey || "";
16697
+ } catch {
16698
+ return "";
16699
+ }
16700
+ }
16701
+ function publicChannel(row) {
16702
+ return { id: row.id, type: "wecom", name: row.name, configured: Boolean(readBotKey(row)) };
16703
+ }
16704
+
15991
16705
  // src/index.ts
15992
16706
  var __filename = fileURLToPath2(import.meta.url);
15993
16707
  var __dirname = path12.dirname(__filename);
@@ -16076,8 +16790,8 @@ function isSpaRoute(req) {
16076
16790
  if (path12.extname(url)) return false;
16077
16791
  return true;
16078
16792
  }
16079
- function getSecuritySetting(db, key) {
16080
- const row = db.prepare("SELECT value FROM security_settings WHERE key = ?").get(key);
16793
+ function getApplicationSetting(db, key) {
16794
+ const row = db.prepare("SELECT value FROM application_settings WHERE key = ?").get(key);
16081
16795
  return row?.value || "";
16082
16796
  }
16083
16797
  function formatServerTimestamp() {
@@ -16135,6 +16849,16 @@ async function buildApp() {
16135
16849
  const skillPresetStore = new SkillPresetStore(db);
16136
16850
  const reviewSourceStore = new ReviewSourceStore(db);
16137
16851
  const reviewSourceService = new ReviewSourceService(reviewSourceStore);
16852
+ const modelWindowKickoffStore = new ModelWindowKickoffStore(db);
16853
+ const notificationChannels = new NotificationChannelService(db);
16854
+ const modelWindowKickoffScheduler = new ModelWindowKickoffScheduler({
16855
+ store: modelWindowKickoffStore,
16856
+ notifications: notificationChannels,
16857
+ sessions: piSessionService,
16858
+ resolveProfile: async (profileId) => (await piSessionService.listAgentProfiles()).find((profile) => profile.id === profileId),
16859
+ log: app.log
16860
+ });
16861
+ modelWindowKickoffScheduler.start();
16138
16862
  const sessionActivityStore = new SessionActivityStore(db);
16139
16863
  const sessionPinStore = new SessionPinStore(db);
16140
16864
  projectTaskStore.restoreAllStarting();
@@ -16153,7 +16877,7 @@ async function buildApp() {
16153
16877
  ...await piSessionService.getClientAgentProxyEnvForRoutes(clientId || "default"),
16154
16878
  ...githubSettings.proxyEnv()
16155
16879
  }),
16156
- gitCloneParentPath: () => getSecuritySetting(db, "ui.gitCloneParentPath") || path12.join(os9.homedir(), "git", "github")
16880
+ gitCloneParentPath: () => getApplicationSetting(db, "ui.gitCloneParentPath") || path12.join(os9.homedir(), "git", "github")
16157
16881
  });
16158
16882
  const gitHosting = new GitHostingService();
16159
16883
  const worktreeManager = new WorktreeManager();
@@ -16178,6 +16902,7 @@ async function buildApp() {
16178
16902
  worktreeMetadata: worktreeMetadataStore
16179
16903
  });
16180
16904
  app.addHook("onClose", async () => {
16905
+ await modelWindowKickoffScheduler.stop();
16181
16906
  await weixinGateway.stop();
16182
16907
  terminalManager.disposeAll();
16183
16908
  piSessionService.disposeAll();
@@ -16266,6 +16991,12 @@ async function buildApp() {
16266
16991
  await app.register(changelogRoutes, { prefix: "/api/changelog" });
16267
16992
  await app.register(speechRoutes, { prefix: "/api/speech" });
16268
16993
  await app.register(reviewSourceRoutes, { prefix: "/api/review-sources", reviewSourceService, pinStore: sessionPinStore });
16994
+ await app.register(modelWindowKickoffRoutes, {
16995
+ prefix: "/api/model-window-kickoffs",
16996
+ store: modelWindowKickoffStore,
16997
+ notifications: notificationChannels,
16998
+ sessions: piSessionService
16999
+ });
16269
17000
  await app.register(chatWebSocket);
16270
17001
  await app.register(terminalWebSocket);
16271
17002
  app.get("/api/health", async () => {