@tom2012/cc-web 2026.6.17-b → 2026.9.6-c

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 (239) hide show
  1. package/CHANGELOG.json +96 -21
  2. package/backend/dist/adapters/claude-adapter.d.ts +4 -0
  3. package/backend/dist/adapters/claude-adapter.d.ts.map +1 -1
  4. package/backend/dist/adapters/claude-adapter.js +18 -2
  5. package/backend/dist/adapters/claude-adapter.js.map +1 -1
  6. package/backend/dist/adapters/types.d.ts +8 -0
  7. package/backend/dist/adapters/types.d.ts.map +1 -1
  8. package/backend/dist/auth.d.ts.map +0 -0
  9. package/backend/dist/config.d.ts +0 -0
  10. package/backend/dist/config.js +0 -0
  11. package/backend/dist/config.js.map +0 -0
  12. package/backend/dist/hooks-manager.d.ts +0 -1
  13. package/backend/dist/hooks-manager.d.ts.map +1 -1
  14. package/backend/dist/hooks-manager.js +19 -19
  15. package/backend/dist/hooks-manager.js.map +1 -1
  16. package/backend/dist/index.d.ts.map +1 -1
  17. package/backend/dist/index.js +76 -1
  18. package/backend/dist/index.js.map +1 -1
  19. package/backend/dist/logger.d.ts.map +1 -1
  20. package/backend/dist/logger.js +3 -0
  21. package/backend/dist/logger.js.map +1 -1
  22. package/backend/dist/routes/auth.d.ts +0 -0
  23. package/backend/dist/routes/auth.d.ts.map +0 -0
  24. package/backend/dist/routes/auth.js.map +0 -0
  25. package/backend/dist/routes/projects.d.ts.map +0 -0
  26. package/backend/dist/routes/projects.js.map +0 -0
  27. package/backend/dist/session-manager.d.ts +0 -0
  28. package/backend/dist/session-manager.d.ts.map +0 -0
  29. package/backend/dist/session-manager.js +0 -0
  30. package/backend/dist/session-manager.js.map +0 -0
  31. package/backend/dist/terminal-manager.d.ts +10 -1
  32. package/backend/dist/terminal-manager.d.ts.map +1 -1
  33. package/backend/dist/terminal-manager.js +19 -2
  34. package/backend/dist/terminal-manager.js.map +1 -1
  35. package/backend/dist/types.d.ts +0 -0
  36. package/backend/dist/types.d.ts.map +0 -0
  37. package/backend/dist/types.js +0 -0
  38. package/backend/dist/types.js.map +0 -0
  39. package/frontend/dist/assets/{ChatOverlay-Dp1BzsYb.js → ChatOverlay-CymdKeQK.js} +1 -1
  40. package/frontend/dist/assets/{ClaudeMemPage-CpzMjJnb.js → ClaudeMemPage-BZ4UtUoz.js} +1 -1
  41. package/frontend/dist/assets/{GraphPreview-DDY1yVOX.js → GraphPreview-21EwV_e_.js} +1 -1
  42. package/frontend/dist/assets/{MobilePage-BnnE820D.js → MobilePage-99vv9cWh.js} +3 -3
  43. package/frontend/dist/assets/{NotebookPage-BOiYcs-l.js → NotebookPage-B-mcSRJm.js} +1 -1
  44. package/frontend/dist/assets/{OfficePreview-C5N2gr8-.js → OfficePreview-C3rHS2Te.js} +2 -2
  45. package/frontend/dist/assets/{PdfPreview-COG-9AQa.js → PdfPreview-B-toyaoA.js} +1 -1
  46. package/frontend/dist/assets/{ProjectPage-ARPH_J5o.js → ProjectPage-CShqyrUl.js} +4 -4
  47. package/frontend/dist/assets/{ProjectSettingsPage-CMWcT-5r.js → ProjectSettingsPage-FxkEGuUP.js} +1 -1
  48. package/frontend/dist/assets/{SettingsPage-BMrIDtg8.js → SettingsPage-B-bqNp6b.js} +1 -1
  49. package/frontend/dist/assets/{SkillHubPage-Bq93A7Rt.js → SkillHubPage-DN79j9Is.js} +1 -1
  50. package/frontend/dist/assets/{check-circle-2-BGT1eH1f.js → check-circle-2-DRT7EX5I.js} +1 -1
  51. package/frontend/dist/assets/{chevron-down-Bbg11n9i.js → chevron-down-DdtdiM4b.js} +1 -1
  52. package/frontend/dist/assets/{chevron-left-BmStQriC.js → chevron-left-Chv-EE6G.js} +1 -1
  53. package/frontend/dist/assets/{file-text-D9WjmbhK.js → file-text-D3ZDULSr.js} +1 -1
  54. package/frontend/dist/assets/{index-EFhMcMWk.js → index-BIGQ6n-i.js} +2 -2
  55. package/frontend/dist/assets/index-BpdaJxoa.css +1 -0
  56. package/frontend/dist/assets/{index-BRqvDxO6.js → index-CH1oLpmL.js} +1 -1
  57. package/frontend/dist/assets/{index-Bhp9CVTp.js → index-Dbm49BgL.js} +1 -1
  58. package/frontend/dist/assets/{index-COZnAgez.js → index-xJR85RJZ.js} +1 -1
  59. package/frontend/dist/assets/{jszip.min-Dyo__hKc.js → jszip.min-CMcZF4_M.js} +1 -1
  60. package/frontend/dist/assets/{save-CAjdgnp7.js → save-Dhw-I0uF.js} +1 -1
  61. package/frontend/dist/assets/{search-DmWhhICR.js → search-zoOED2R0.js} +1 -1
  62. package/frontend/dist/assets/{select-DAfsBGCS.js → select-B7xEE81_.js} +1 -1
  63. package/frontend/dist/assets/{tabs-zra46FZv.js → tabs-BCHX4LWy.js} +1 -1
  64. package/frontend/dist/assets/{textarea-BG4Tvy0e.js → textarea-CGPmR1Ae.js} +1 -1
  65. package/frontend/dist/index.html +2 -2
  66. package/package.json +36 -10
  67. package/backend/dist/backup/config.d.ts +0 -10
  68. package/backend/dist/backup/config.d.ts.map +0 -1
  69. package/backend/dist/backup/config.js +0 -128
  70. package/backend/dist/backup/config.js.map +0 -1
  71. package/backend/dist/backup/engine.d.ts +0 -22
  72. package/backend/dist/backup/engine.d.ts.map +0 -1
  73. package/backend/dist/backup/engine.js +0 -298
  74. package/backend/dist/backup/engine.js.map +0 -1
  75. package/backend/dist/backup/providers/dropbox.d.ts +0 -25
  76. package/backend/dist/backup/providers/dropbox.d.ts.map +0 -1
  77. package/backend/dist/backup/providers/dropbox.js +0 -320
  78. package/backend/dist/backup/providers/dropbox.js.map +0 -1
  79. package/backend/dist/backup/providers/google-drive.d.ts +0 -29
  80. package/backend/dist/backup/providers/google-drive.d.ts.map +0 -1
  81. package/backend/dist/backup/providers/google-drive.js +0 -303
  82. package/backend/dist/backup/providers/google-drive.js.map +0 -1
  83. package/backend/dist/backup/providers/index.d.ts +0 -3
  84. package/backend/dist/backup/providers/index.d.ts.map +0 -1
  85. package/backend/dist/backup/providers/index.js +0 -19
  86. package/backend/dist/backup/providers/index.js.map +0 -1
  87. package/backend/dist/backup/providers/onedrive.d.ts +0 -29
  88. package/backend/dist/backup/providers/onedrive.d.ts.map +0 -1
  89. package/backend/dist/backup/providers/onedrive.js +0 -317
  90. package/backend/dist/backup/providers/onedrive.js.map +0 -1
  91. package/backend/dist/backup/scheduler.d.ts +0 -6
  92. package/backend/dist/backup/scheduler.d.ts.map +0 -1
  93. package/backend/dist/backup/scheduler.js +0 -55
  94. package/backend/dist/backup/scheduler.js.map +0 -1
  95. package/backend/dist/backup/types.d.ts +0 -73
  96. package/backend/dist/backup/types.d.ts.map +0 -1
  97. package/backend/dist/backup/types.js +0 -4
  98. package/backend/dist/backup/types.js.map +0 -1
  99. package/backend/dist/chat-process-manager.d.ts +0 -45
  100. package/backend/dist/chat-process-manager.d.ts.map +0 -1
  101. package/backend/dist/chat-process-manager.js +0 -258
  102. package/backend/dist/chat-process-manager.js.map +0 -1
  103. package/backend/dist/flows/runner.d.ts +0 -37
  104. package/backend/dist/flows/runner.d.ts.map +0 -1
  105. package/backend/dist/flows/runner.js +0 -660
  106. package/backend/dist/flows/runner.js.map +0 -1
  107. package/backend/dist/flows/store.d.ts +0 -40
  108. package/backend/dist/flows/store.d.ts.map +0 -1
  109. package/backend/dist/flows/store.js +0 -284
  110. package/backend/dist/flows/store.js.map +0 -1
  111. package/backend/dist/flows/types.d.ts +0 -176
  112. package/backend/dist/flows/types.d.ts.map +0 -1
  113. package/backend/dist/flows/types.js +0 -25
  114. package/backend/dist/flows/types.js.map +0 -1
  115. package/backend/dist/information/condenser.d.ts +0 -12
  116. package/backend/dist/information/condenser.d.ts.map +0 -1
  117. package/backend/dist/information/condenser.js +0 -568
  118. package/backend/dist/information/condenser.js.map +0 -1
  119. package/backend/dist/information/conversation-sync.d.ts +0 -22
  120. package/backend/dist/information/conversation-sync.d.ts.map +0 -1
  121. package/backend/dist/information/conversation-sync.js +0 -500
  122. package/backend/dist/information/conversation-sync.js.map +0 -1
  123. package/backend/dist/information/types.d.ts +0 -36
  124. package/backend/dist/information/types.d.ts.map +0 -1
  125. package/backend/dist/information/types.js +0 -4
  126. package/backend/dist/information/types.js.map +0 -1
  127. package/backend/dist/memory-pool/buoyancy.d.ts +0 -2
  128. package/backend/dist/memory-pool/buoyancy.d.ts.map +0 -1
  129. package/backend/dist/memory-pool/buoyancy.js +0 -12
  130. package/backend/dist/memory-pool/buoyancy.js.map +0 -1
  131. package/backend/dist/memory-pool/global-pool-manager.d.ts +0 -12
  132. package/backend/dist/memory-pool/global-pool-manager.d.ts.map +0 -1
  133. package/backend/dist/memory-pool/global-pool-manager.js +0 -295
  134. package/backend/dist/memory-pool/global-pool-manager.js.map +0 -1
  135. package/backend/dist/memory-pool/pool-lock.d.ts +0 -2
  136. package/backend/dist/memory-pool/pool-lock.d.ts.map +0 -1
  137. package/backend/dist/memory-pool/pool-lock.js +0 -31
  138. package/backend/dist/memory-pool/pool-lock.js.map +0 -1
  139. package/backend/dist/memory-pool/pool-manager.d.ts +0 -57
  140. package/backend/dist/memory-pool/pool-manager.d.ts.map +0 -1
  141. package/backend/dist/memory-pool/pool-manager.js +0 -322
  142. package/backend/dist/memory-pool/pool-manager.js.map +0 -1
  143. package/backend/dist/memory-pool/templates.d.ts +0 -5
  144. package/backend/dist/memory-pool/templates.d.ts.map +0 -1
  145. package/backend/dist/memory-pool/templates.js +0 -234
  146. package/backend/dist/memory-pool/templates.js.map +0 -1
  147. package/backend/dist/memory-pool/types.d.ts +0 -61
  148. package/backend/dist/memory-pool/types.d.ts.map +0 -1
  149. package/backend/dist/memory-pool/types.js +0 -4
  150. package/backend/dist/memory-pool/types.js.map +0 -1
  151. package/backend/dist/plan-control/checker.d.ts +0 -3
  152. package/backend/dist/plan-control/checker.d.ts.map +0 -1
  153. package/backend/dist/plan-control/checker.js +0 -103
  154. package/backend/dist/plan-control/checker.js.map +0 -1
  155. package/backend/dist/plan-control/executor.d.ts +0 -93
  156. package/backend/dist/plan-control/executor.d.ts.map +0 -1
  157. package/backend/dist/plan-control/executor.js +0 -955
  158. package/backend/dist/plan-control/executor.js.map +0 -1
  159. package/backend/dist/plan-control/parser.d.ts +0 -12
  160. package/backend/dist/plan-control/parser.d.ts.map +0 -1
  161. package/backend/dist/plan-control/parser.js +0 -216
  162. package/backend/dist/plan-control/parser.js.map +0 -1
  163. package/backend/dist/plan-control/templates.d.ts +0 -4
  164. package/backend/dist/plan-control/templates.d.ts.map +0 -1
  165. package/backend/dist/plan-control/templates.js +0 -271
  166. package/backend/dist/plan-control/templates.js.map +0 -1
  167. package/backend/dist/plan-control/types.d.ts +0 -102
  168. package/backend/dist/plan-control/types.d.ts.map +0 -1
  169. package/backend/dist/plan-control/types.js +0 -12
  170. package/backend/dist/plan-control/types.js.map +0 -1
  171. package/backend/dist/routes/backup.d.ts +0 -4
  172. package/backend/dist/routes/backup.d.ts.map +0 -1
  173. package/backend/dist/routes/backup.js +0 -274
  174. package/backend/dist/routes/backup.js.map +0 -1
  175. package/backend/dist/routes/flows.d.ts +0 -5
  176. package/backend/dist/routes/flows.d.ts.map +0 -1
  177. package/backend/dist/routes/flows.js +0 -457
  178. package/backend/dist/routes/flows.js.map +0 -1
  179. package/backend/dist/routes/global-flows.d.ts +0 -3
  180. package/backend/dist/routes/global-flows.d.ts.map +0 -1
  181. package/backend/dist/routes/global-flows.js +0 -90
  182. package/backend/dist/routes/global-flows.js.map +0 -1
  183. package/backend/dist/routes/information.d.ts +0 -3
  184. package/backend/dist/routes/information.d.ts.map +0 -1
  185. package/backend/dist/routes/information.js +0 -159
  186. package/backend/dist/routes/information.js.map +0 -1
  187. package/backend/dist/routes/memory-pool.d.ts +0 -3
  188. package/backend/dist/routes/memory-pool.d.ts.map +0 -1
  189. package/backend/dist/routes/memory-pool.js +0 -837
  190. package/backend/dist/routes/memory-pool.js.map +0 -1
  191. package/backend/dist/routes/plan-control.d.ts +0 -15
  192. package/backend/dist/routes/plan-control.d.ts.map +0 -1
  193. package/backend/dist/routes/plan-control.js +0 -337
  194. package/backend/dist/routes/plan-control.js.map +0 -1
  195. package/backend/dist/routes/share.d.ts +0 -3
  196. package/backend/dist/routes/share.d.ts.map +0 -1
  197. package/backend/dist/routes/share.js +0 -167
  198. package/backend/dist/routes/share.js.map +0 -1
  199. package/backend/dist/routes/sounds.d.ts +0 -3
  200. package/backend/dist/routes/sounds.d.ts.map +0 -1
  201. package/backend/dist/routes/sounds.js +0 -285
  202. package/backend/dist/routes/sounds.js.map +0 -1
  203. package/backend/dist/tracks/__tests__/verify-starter-templates.d.ts +0 -24
  204. package/backend/dist/tracks/__tests__/verify-starter-templates.d.ts.map +0 -1
  205. package/backend/dist/tracks/__tests__/verify-starter-templates.js +0 -175
  206. package/backend/dist/tracks/__tests__/verify-starter-templates.js.map +0 -1
  207. package/backend/dist/tracks/__tests__/verify-track-cancel.d.ts +0 -23
  208. package/backend/dist/tracks/__tests__/verify-track-cancel.d.ts.map +0 -1
  209. package/backend/dist/tracks/__tests__/verify-track-cancel.js +0 -153
  210. package/backend/dist/tracks/__tests__/verify-track-cancel.js.map +0 -1
  211. package/backend/dist/tracks/__tests__/verify-track-t1.d.ts +0 -11
  212. package/backend/dist/tracks/__tests__/verify-track-t1.d.ts.map +0 -1
  213. package/backend/dist/tracks/__tests__/verify-track-t1.js +0 -293
  214. package/backend/dist/tracks/__tests__/verify-track-t1.js.map +0 -1
  215. package/backend/dist/tracks/__tests__/verify-track.d.ts +0 -20
  216. package/backend/dist/tracks/__tests__/verify-track.d.ts.map +0 -1
  217. package/backend/dist/tracks/__tests__/verify-track.js +0 -187
  218. package/backend/dist/tracks/__tests__/verify-track.js.map +0 -1
  219. package/backend/dist/tracks/registry.d.ts +0 -61
  220. package/backend/dist/tracks/registry.d.ts.map +0 -1
  221. package/backend/dist/tracks/registry.js +0 -202
  222. package/backend/dist/tracks/registry.js.map +0 -1
  223. package/backend/dist/tracks/track-runner.d.ts +0 -69
  224. package/backend/dist/tracks/track-runner.d.ts.map +0 -1
  225. package/backend/dist/tracks/track-runner.js +0 -158
  226. package/backend/dist/tracks/track-runner.js.map +0 -1
  227. package/backend/dist/tracks/train-loader.d.ts +0 -44
  228. package/backend/dist/tracks/train-loader.d.ts.map +0 -1
  229. package/backend/dist/tracks/train-loader.js +0 -32
  230. package/backend/dist/tracks/train-loader.js.map +0 -1
  231. package/backend/dist/tracks/types-train.d.ts +0 -12
  232. package/backend/dist/tracks/types-train.d.ts.map +0 -1
  233. package/backend/dist/tracks/types-train.js +0 -13
  234. package/backend/dist/tracks/types-train.js.map +0 -1
  235. package/backend/dist/usage-terminal.d.ts +0 -18
  236. package/backend/dist/usage-terminal.d.ts.map +0 -1
  237. package/backend/dist/usage-terminal.js +0 -191
  238. package/backend/dist/usage-terminal.js.map +0 -1
  239. package/frontend/dist/assets/index-GfCaMXPO.css +0 -1
@@ -1,837 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- const express_1 = require("express");
37
- const fs = __importStar(require("fs"));
38
- const path = __importStar(require("path"));
39
- const config_1 = require("../config");
40
- const templates_1 = require("../memory-pool/templates");
41
- const pool_manager_1 = require("../memory-pool/pool-manager");
42
- const buoyancy_1 = require("../memory-pool/buoyancy");
43
- const pool_lock_1 = require("../memory-pool/pool-lock");
44
- const global_pool_manager_1 = require("../memory-pool/global-pool-manager");
45
- const router = (0, express_1.Router)();
46
- const BALL_ID_RE = /^ball_\d{1,6}$/;
47
- const VALID_TYPES = ['feedback', 'user', 'project', 'reference'];
48
- const DEFAULT_B0 = { feedback: 9, user: 6, project: 5, reference: 3 };
49
- const MEMORY_POOL_DIR = '.memory-pool';
50
- const CLAUDE_MD_MARKER = '## 记忆池(Memory Pool)';
51
- function resolveProjectFolder(projectId, username, res) {
52
- const project = (0, config_1.getProject)(projectId);
53
- if (!project) {
54
- res.status(404).json({ error: 'Project not found' });
55
- return null;
56
- }
57
- if (!(0, config_1.isAdminUser)(username) && !(0, config_1.isProjectOwner)(project, username) &&
58
- !project.shares?.some((s) => s.username === username && s.permission === 'edit')) {
59
- res.status(403).json({ error: 'Access denied' });
60
- return null;
61
- }
62
- return project.folderPath;
63
- }
64
- function validateBallIds(ids) {
65
- return ids.every((id) => typeof id === 'string' && BALL_ID_RE.test(id));
66
- }
67
- // ══════════════════════════════════════════════════════════════════════════════
68
- // Global Memory Pool Endpoints (MUST be before /:projectId routes)
69
- // ══════════════════════════════════════════════════════════════════════════════
70
- // GET /api/memory-pool/global/status
71
- router.get('/global/status', (_req, res) => {
72
- if (!(0, global_pool_manager_1.isGlobalPoolInitialized)()) {
73
- res.json({ initialized: false });
74
- return;
75
- }
76
- const pool = (0, global_pool_manager_1.readGlobalPool)();
77
- if (!pool) {
78
- res.json({ initialized: false });
79
- return;
80
- }
81
- const sources = (0, global_pool_manager_1.readSources)();
82
- const freshT = (0, global_pool_manager_1.computeGlobalT)(pool.initialized_at);
83
- res.json({
84
- initialized: true,
85
- state: {
86
- version: pool.version,
87
- t: freshT,
88
- lambda: pool.lambda,
89
- alpha: pool.alpha,
90
- active_capacity: pool.active_capacity,
91
- surface_width: pool.surface_width ?? 10000,
92
- next_id: pool.next_id,
93
- pool: pool.pool,
94
- initialized_at: pool.initialized_at,
95
- },
96
- ballCount: pool.balls.length,
97
- sourceCount: sources.sources.filter((s) => s.status === 'active').length,
98
- activeBalls: Math.min(pool.balls.length, pool.active_capacity),
99
- });
100
- });
101
- // GET /api/memory-pool/global/index
102
- router.get('/global/index', (_req, res) => {
103
- const pool = (0, global_pool_manager_1.readGlobalPool)();
104
- if (!pool) {
105
- res.status(404).json({ error: 'Global pool not initialized' });
106
- return;
107
- }
108
- pool.t = (0, global_pool_manager_1.computeGlobalT)(pool.initialized_at);
109
- const balls = (0, pool_manager_1.enrichBallsWithBuoyancy)(pool);
110
- res.json({ t: pool.t, updated_at: new Date().toISOString(), balls, active_capacity: pool.active_capacity });
111
- });
112
- // GET /api/memory-pool/global/surface
113
- router.get('/global/surface', (_req, res) => {
114
- const globalDir = (0, global_pool_manager_1.getGlobalPoolDir)();
115
- (0, pool_lock_1.withPoolLock)(globalDir, () => {
116
- const pool = (0, global_pool_manager_1.readGlobalPool)();
117
- if (!pool) {
118
- res.status(404).json({ error: 'Global pool not initialized' });
119
- return;
120
- }
121
- const freshT = (0, global_pool_manager_1.computeGlobalT)(pool.initialized_at);
122
- if (pool.t !== freshT) {
123
- pool.t = freshT;
124
- (0, pool_manager_1.writePool)(globalDir, pool);
125
- }
126
- const { surfaceBalls, totalTokens } = (0, pool_manager_1.buildSurface)(globalDir);
127
- res.json({
128
- t: pool.t,
129
- surface_width: pool.surface_width ?? 10000,
130
- used_tokens: totalTokens,
131
- balls: surfaceBalls,
132
- });
133
- }).catch((err) => {
134
- if (!res.headersSent)
135
- res.status(500).json({ error: err.message || err });
136
- });
137
- });
138
- // GET /api/memory-pool/global/ball/:ballId (pure read, no side effect)
139
- router.get('/global/ball/:ballId', (req, res) => {
140
- const { ballId } = req.params;
141
- if (!BALL_ID_RE.test(ballId)) {
142
- res.status(400).json({ error: 'Invalid ball ID format' });
143
- return;
144
- }
145
- const content = (0, pool_manager_1.readBallContent)((0, global_pool_manager_1.getGlobalPoolDir)(), ballId);
146
- if (content === null) {
147
- res.status(404).json({ error: 'Ball not found' });
148
- return;
149
- }
150
- res.json({ id: ballId, content });
151
- });
152
- // POST /api/memory-pool/global/balls/:ballId/hit
153
- router.post('/global/balls/:ballId/hit', (req, res) => {
154
- const { ballId } = req.params;
155
- if (!BALL_ID_RE.test(ballId)) {
156
- res.status(400).json({ error: 'Invalid ball ID format' });
157
- return;
158
- }
159
- const globalDir = (0, global_pool_manager_1.getGlobalPoolDir)();
160
- (0, pool_lock_1.withPoolLock)(globalDir, () => {
161
- const pool = (0, global_pool_manager_1.readGlobalPool)();
162
- if (!pool) {
163
- res.status(404).json({ error: 'Global pool not initialized' });
164
- return;
165
- }
166
- pool.t = (0, global_pool_manager_1.computeGlobalT)(pool.initialized_at);
167
- const ball = pool.balls.find((b) => b.id === ballId);
168
- if (!ball) {
169
- res.status(404).json({ error: 'Ball not found' });
170
- return;
171
- }
172
- ball.H += 1;
173
- ball.t_last = pool.t;
174
- (0, pool_manager_1.writePool)(globalDir, pool);
175
- const content = (0, pool_manager_1.readBallContent)(globalDir, ballId);
176
- const buoy = (0, buoyancy_1.computeBuoyancy)(ball.B0, ball.H, pool.alpha, pool.lambda, pool.t, ball.t_last, ball.permanent);
177
- const linkedBalls = ball.links
178
- .map((lid) => pool.balls.find((b) => b.id === lid))
179
- .filter((b) => !!b)
180
- .map((b) => ({
181
- id: b.id,
182
- type: b.type,
183
- summary: b.summary,
184
- buoyancy: (0, buoyancy_1.computeBuoyancy)(b.B0, b.H, pool.alpha, pool.lambda, pool.t, b.t_last, b.permanent),
185
- }));
186
- res.json({ id: ballId, content, buoyancy: buoy, linked_balls: linkedBalls });
187
- }).catch((err) => {
188
- if (!res.headersSent)
189
- res.status(500).json({ error: err.message || err });
190
- });
191
- });
192
- // GET /api/memory-pool/global/sources
193
- router.get('/global/sources', (_req, res) => {
194
- const sources = (0, global_pool_manager_1.readSources)();
195
- res.json(sources);
196
- });
197
- // DELETE /api/memory-pool/global/sources/:projectId
198
- router.delete('/global/sources/:projectId', (req, res) => {
199
- (0, global_pool_manager_1.removeSource)(req.params.projectId).then((removed) => {
200
- if (!removed) {
201
- res.status(404).json({ error: 'Source not found' });
202
- return;
203
- }
204
- res.json({ success: true });
205
- }).catch((err) => {
206
- if (!res.headersSent)
207
- res.status(500).json({ error: err.message || err });
208
- });
209
- });
210
- // POST /api/memory-pool/global/sync
211
- router.post('/global/sync', (_req, res) => {
212
- (0, global_pool_manager_1.syncToGlobal)().then((result) => {
213
- res.json(result);
214
- }).catch((err) => {
215
- if (err.message === 'SYNC_IN_PROGRESS') {
216
- res.status(409).json({ error: 'Sync already in progress' });
217
- return;
218
- }
219
- if (!res.headersSent)
220
- res.status(500).json({ error: 'Sync failed: ' + (err.message || err) });
221
- });
222
- });
223
- // ══════════════════════════════════════════════════════════════════════════════
224
- // Project Memory Pool Endpoints
225
- // ══════════════════════════════════════════════════════════════════════════════
226
- // GET /api/memory-pool/:projectId/status
227
- router.get('/:projectId/status', (req, res) => {
228
- const folder = resolveProjectFolder(req.params.projectId, req.user?.username || '', res);
229
- if (!folder)
230
- return;
231
- const poolDir = path.join(folder, MEMORY_POOL_DIR);
232
- if (!(0, pool_manager_1.isInitialized)(poolDir)) {
233
- res.json({ initialized: false });
234
- return;
235
- }
236
- const pool = (0, pool_manager_1.readPool)(poolDir);
237
- if (pool) {
238
- res.json({
239
- initialized: true,
240
- needsUpgrade: false,
241
- state: {
242
- version: pool.version,
243
- t: pool.t,
244
- lambda: pool.lambda,
245
- alpha: pool.alpha,
246
- active_capacity: pool.active_capacity,
247
- surface_width: pool.surface_width ?? 10000,
248
- next_id: pool.next_id,
249
- pool: pool.pool,
250
- initialized_at: pool.initialized_at,
251
- },
252
- ballCount: pool.balls.length,
253
- });
254
- return;
255
- }
256
- const stateFile = path.join(poolDir, 'state.json');
257
- try {
258
- const state = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
259
- const ballsDir = path.join(poolDir, 'balls');
260
- let ballCount = 0;
261
- try {
262
- ballCount = fs.readdirSync(ballsDir).filter(f => f.endsWith('.md')).length;
263
- }
264
- catch { /* empty */ }
265
- res.json({ initialized: true, needsUpgrade: true, state, ballCount });
266
- }
267
- catch {
268
- res.json({ initialized: false });
269
- }
270
- });
271
- // POST /api/memory-pool/:projectId/init
272
- router.post('/:projectId/init', (req, res) => {
273
- const folder = resolveProjectFolder(req.params.projectId, req.user?.username || '', res);
274
- if (!folder)
275
- return;
276
- const poolDir = path.join(folder, MEMORY_POOL_DIR);
277
- (0, pool_lock_1.withPoolLock)(poolDir, async () => {
278
- if ((0, pool_manager_1.isInitialized)(poolDir)) {
279
- res.status(409).json({ error: 'Memory pool already initialized' });
280
- return;
281
- }
282
- fs.mkdirSync(path.join(poolDir, 'balls'), { recursive: true });
283
- (0, config_1.atomicWriteSync)(path.join(poolDir, 'SPEC.md'), (0, templates_1.generateSpecMd)());
284
- (0, config_1.atomicWriteSync)(path.join(poolDir, 'QUICK-REF.md'), (0, templates_1.generateQuickRefMd)());
285
- const now = new Date().toISOString();
286
- const pool = {
287
- version: 2,
288
- t: 0,
289
- lambda: 0.97,
290
- alpha: 1.0,
291
- active_capacity: 20,
292
- surface_width: 10000,
293
- next_id: 1,
294
- pool: 'project',
295
- initialized_at: now,
296
- balls: [],
297
- };
298
- (0, pool_manager_1.writePool)(poolDir, pool);
299
- try {
300
- const project = (0, config_1.getProject)(req.params.projectId);
301
- if (project) {
302
- await (0, global_pool_manager_1.registerProject)(req.params.projectId, project.name, poolDir);
303
- pool.global_pool_path = (0, global_pool_manager_1.getGlobalPoolDir)();
304
- (0, pool_manager_1.writePool)(poolDir, pool);
305
- }
306
- }
307
- catch { /* non-fatal */ }
308
- const claudeMdPath = path.join(folder, 'CLAUDE.md');
309
- try {
310
- const existing = fs.existsSync(claudeMdPath) ? fs.readFileSync(claudeMdPath, 'utf-8') : '';
311
- if (!existing.includes(CLAUDE_MD_MARKER)) {
312
- (0, config_1.atomicWriteSync)(claudeMdPath, existing + '\n' + (0, templates_1.generateClaudeMdBlock)());
313
- }
314
- }
315
- catch { /* non-fatal */ }
316
- (0, pool_manager_1.buildSurface)(poolDir);
317
- res.json({ success: true });
318
- }).catch((err) => {
319
- if (!res.headersSent)
320
- res.status(500).json({ error: err.message || err });
321
- });
322
- });
323
- // POST /api/memory-pool/:projectId/upgrade
324
- router.post('/:projectId/upgrade', (req, res) => {
325
- const folder = resolveProjectFolder(req.params.projectId, req.user?.username || '', res);
326
- if (!folder)
327
- return;
328
- const poolDir = path.join(folder, MEMORY_POOL_DIR);
329
- if (!(0, pool_manager_1.isInitialized)(poolDir)) {
330
- res.status(404).json({ error: 'Memory pool not initialized' });
331
- return;
332
- }
333
- (0, pool_lock_1.withPoolLock)(poolDir, async () => {
334
- const allChanges = [];
335
- if ((0, pool_manager_1.needsUpgrade)(poolDir)) {
336
- const { changes } = (0, pool_manager_1.migrateV1toV2)(poolDir);
337
- allChanges.push(...changes);
338
- }
339
- const pool = (0, pool_manager_1.readPool)(poolDir);
340
- if (pool) {
341
- (0, config_1.atomicWriteSync)(path.join(poolDir, 'SPEC.md'), (0, templates_1.generateSpecMd)());
342
- (0, config_1.atomicWriteSync)(path.join(poolDir, 'QUICK-REF.md'), (0, templates_1.generateQuickRefMd)());
343
- allChanges.push('updated SPEC.md and QUICK-REF.md');
344
- const claudeMdPath = path.join(folder, 'CLAUDE.md');
345
- try {
346
- if (fs.existsSync(claudeMdPath)) {
347
- let content = fs.readFileSync(claudeMdPath, 'utf-8');
348
- const markerIdx = content.indexOf(CLAUDE_MD_MARKER);
349
- if (markerIdx !== -1) {
350
- const afterMarker = content.slice(markerIdx + CLAUDE_MD_MARKER.length);
351
- const nextSection = afterMarker.search(/\n## /);
352
- const before = content.slice(0, markerIdx);
353
- const after = nextSection !== -1 ? afterMarker.slice(nextSection) : '';
354
- content = before + (0, templates_1.generateClaudeMdBlock)() + after;
355
- }
356
- else {
357
- content += '\n' + (0, templates_1.generateClaudeMdBlock)();
358
- }
359
- (0, config_1.atomicWriteSync)(claudeMdPath, content);
360
- allChanges.push('updated CLAUDE.md memory pool section');
361
- }
362
- }
363
- catch { /* non-fatal */ }
364
- try {
365
- const project = (0, config_1.getProject)(req.params.projectId);
366
- if (project) {
367
- await (0, global_pool_manager_1.registerProject)(req.params.projectId, project.name, poolDir);
368
- pool.global_pool_path = (0, global_pool_manager_1.getGlobalPoolDir)();
369
- (0, pool_manager_1.writePool)(poolDir, pool);
370
- allChanges.push('registered with global memory pool');
371
- }
372
- }
373
- catch { /* non-fatal */ }
374
- (0, pool_manager_1.buildSurface)(poolDir);
375
- res.json({ success: true, version: pool.version, changes: allChanges });
376
- }
377
- else {
378
- res.status(500).json({ error: 'Failed to read pool after migration' });
379
- }
380
- }).catch((err) => {
381
- if (!res.headersSent)
382
- res.status(500).json({ error: 'Upgrade failed: ' + (err.message || err) });
383
- });
384
- });
385
- // GET /api/memory-pool/:projectId/index
386
- router.get('/:projectId/index', (req, res) => {
387
- const folder = resolveProjectFolder(req.params.projectId, req.user?.username || '', res);
388
- if (!folder)
389
- return;
390
- const poolDir = path.join(folder, MEMORY_POOL_DIR);
391
- const pool = (0, pool_manager_1.readPool)(poolDir);
392
- if (pool) {
393
- const balls = (0, pool_manager_1.enrichBallsWithBuoyancy)(pool);
394
- res.json({ t: pool.t, updated_at: new Date().toISOString(), balls, active_capacity: pool.active_capacity });
395
- return;
396
- }
397
- const indexFile = path.join(poolDir, 'index.json');
398
- try {
399
- const data = JSON.parse(fs.readFileSync(indexFile, 'utf-8'));
400
- res.json(data);
401
- }
402
- catch {
403
- res.status(404).json({ error: 'Memory pool not initialized' });
404
- }
405
- });
406
- // GET /api/memory-pool/:projectId/snapshot
407
- router.get('/:projectId/snapshot', (req, res) => {
408
- const folder = resolveProjectFolder(req.params.projectId, req.user?.username || '', res);
409
- if (!folder)
410
- return;
411
- const poolDir = path.join(folder, MEMORY_POOL_DIR);
412
- const pool = (0, pool_manager_1.readPool)(poolDir);
413
- if (!pool) {
414
- res.status(404).json({ error: 'Memory pool not initialized or needs upgrade' });
415
- return;
416
- }
417
- const cap = pool.active_capacity;
418
- const ballCount = pool.balls.length;
419
- const snapshot = (0, pool_manager_1.generateSnapshot)(pool);
420
- res.json({ snapshot, t: pool.t, activeCount: Math.min(ballCount, cap), deepCount: Math.max(0, ballCount - cap) });
421
- });
422
- // GET /api/memory-pool/:projectId/ball/:ballId (pure read, no side effect)
423
- router.get('/:projectId/ball/:ballId', (req, res) => {
424
- const folder = resolveProjectFolder(req.params.projectId, req.user?.username || '', res);
425
- if (!folder)
426
- return;
427
- const { ballId } = req.params;
428
- if (!BALL_ID_RE.test(ballId)) {
429
- res.status(400).json({ error: 'Invalid ball ID format' });
430
- return;
431
- }
432
- const poolDir = path.join(folder, MEMORY_POOL_DIR);
433
- const content = (0, pool_manager_1.readBallContent)(poolDir, ballId);
434
- if (content === null) {
435
- res.status(404).json({ error: 'Ball not found' });
436
- return;
437
- }
438
- res.json({ id: ballId, content });
439
- });
440
- // PUT /api/memory-pool/:projectId/surface-width
441
- router.put('/:projectId/surface-width', (req, res) => {
442
- const folder = resolveProjectFolder(req.params.projectId, req.user?.username || '', res);
443
- if (!folder)
444
- return;
445
- const poolDir = path.join(folder, MEMORY_POOL_DIR);
446
- (0, pool_lock_1.withPoolLock)(poolDir, () => {
447
- const pool = (0, pool_manager_1.readPool)(poolDir);
448
- if (!pool) {
449
- res.status(404).json({ error: 'Memory pool not initialized' });
450
- return;
451
- }
452
- const { surface_width } = req.body;
453
- if (typeof surface_width !== 'number' || surface_width < 1000 || surface_width > 100000) {
454
- res.status(400).json({ error: 'surface_width must be a number between 1000 and 100000' });
455
- return;
456
- }
457
- pool.surface_width = surface_width;
458
- (0, pool_manager_1.writePool)(poolDir, pool);
459
- const { surfaceBalls, totalTokens } = (0, pool_manager_1.buildSurface)(poolDir);
460
- res.json({ success: true, surface_width, surface_balls: surfaceBalls.length, total_tokens: totalTokens });
461
- }).catch((err) => {
462
- if (!res.headersSent)
463
- res.status(500).json({ error: err.message || err });
464
- });
465
- });
466
- // ══════════════════════════════════════════════════════════════════════════════
467
- // Ball CRUD Endpoints (ccweb-managed)
468
- // ══════════════════════════════════════════════════════════════════════════════
469
- // POST /api/memory-pool/:projectId/balls — Create ball
470
- router.post('/:projectId/balls', (req, res) => {
471
- const folder = resolveProjectFolder(req.params.projectId, req.user?.username || '', res);
472
- if (!folder)
473
- return;
474
- const poolDir = path.join(folder, MEMORY_POOL_DIR);
475
- const { type, summary, content, links, b0_override } = req.body;
476
- // Validate required fields
477
- if (!type || !VALID_TYPES.includes(type)) {
478
- res.status(400).json({ error: `type must be one of: ${VALID_TYPES.join(', ')}` });
479
- return;
480
- }
481
- if (!summary || typeof summary !== 'string' || summary.length > 500) {
482
- res.status(400).json({ error: 'summary is required and must be <= 500 characters' });
483
- return;
484
- }
485
- if (!content || typeof content !== 'string' || content.length > 100000) {
486
- res.status(400).json({ error: 'content is required and must be <= 100KB' });
487
- return;
488
- }
489
- if (links && (!Array.isArray(links) || !validateBallIds(links))) {
490
- res.status(400).json({ error: 'links must be an array of valid ball IDs' });
491
- return;
492
- }
493
- if (b0_override !== undefined && b0_override !== null &&
494
- (typeof b0_override !== 'number' || b0_override < 1 || b0_override > 10)) {
495
- res.status(400).json({ error: 'b0_override must be a number between 1 and 10' });
496
- return;
497
- }
498
- (0, pool_lock_1.withPoolLock)(poolDir, () => {
499
- const pool = (0, pool_manager_1.readPool)(poolDir);
500
- if (!pool) {
501
- res.status(404).json({ error: 'Memory pool not initialized' });
502
- return;
503
- }
504
- // M-2 fix: validate that linked ball IDs exist in pool
505
- const validLinks = [];
506
- if (links) {
507
- const existingIds = new Set(pool.balls.map((b) => b.id));
508
- for (const lid of links) {
509
- if (!existingIds.has(lid)) {
510
- res.status(400).json({ error: `Linked ball ${lid} does not exist` });
511
- return;
512
- }
513
- validLinks.push(lid);
514
- }
515
- }
516
- const ballId = `ball_${String(pool.next_id).padStart(4, '0')}`;
517
- pool.next_id++;
518
- const B0 = b0_override ?? DEFAULT_B0[type] ?? 5;
519
- const diameter = (0, pool_manager_1.estimateTokens)(content);
520
- // Write ball content first (gate pattern)
521
- (0, pool_manager_1.writeBallContent)(poolDir, ballId, content);
522
- const newBall = {
523
- id: ballId,
524
- type: type,
525
- summary,
526
- B0,
527
- H: 0,
528
- t_last: pool.t,
529
- hardness: 5,
530
- permanent: false,
531
- links: validLinks,
532
- created_at: new Date().toISOString(),
533
- diameter,
534
- };
535
- pool.balls.push(newBall);
536
- (0, pool_manager_1.writePool)(poolDir, pool);
537
- (0, pool_manager_1.buildSurface)(poolDir);
538
- res.json({ id: ballId, B0, diameter });
539
- }).catch((err) => {
540
- if (!res.headersSent)
541
- res.status(500).json({ error: err.message || err });
542
- });
543
- });
544
- // PUT /api/memory-pool/:projectId/balls/:ballId — Update ball metadata after content edit
545
- router.put('/:projectId/balls/:ballId', (req, res) => {
546
- const folder = resolveProjectFolder(req.params.projectId, req.user?.username || '', res);
547
- if (!folder)
548
- return;
549
- const { ballId } = req.params;
550
- if (!BALL_ID_RE.test(ballId)) {
551
- res.status(400).json({ error: 'Invalid ball ID format' });
552
- return;
553
- }
554
- const poolDir = path.join(folder, MEMORY_POOL_DIR);
555
- (0, pool_lock_1.withPoolLock)(poolDir, () => {
556
- const pool = (0, pool_manager_1.readPool)(poolDir);
557
- if (!pool) {
558
- res.status(404).json({ error: 'Memory pool not initialized' });
559
- return;
560
- }
561
- const ball = pool.balls.find((b) => b.id === ballId);
562
- if (!ball) {
563
- res.status(404).json({ error: 'Ball not found in pool' });
564
- return;
565
- }
566
- // Recalculate diameter from current content
567
- const diameter = (0, pool_manager_1.computeDiameter)(poolDir, ballId);
568
- ball.diameter = diameter;
569
- // Update summary if provided
570
- const { summary } = req.body;
571
- if (summary && typeof summary === 'string') {
572
- if (summary.length > 500) {
573
- res.status(400).json({ error: 'summary must be ≤ 500 characters' });
574
- return;
575
- }
576
- ball.summary = summary;
577
- }
578
- (0, pool_manager_1.writePool)(poolDir, pool);
579
- (0, pool_manager_1.buildSurface)(poolDir);
580
- res.json({ id: ballId, diameter, summary: ball.summary });
581
- }).catch((err) => {
582
- if (!res.headersSent)
583
- res.status(500).json({ error: err.message || err });
584
- });
585
- });
586
- // POST /api/memory-pool/:projectId/balls/:ballId/hit — Hit query (read + count)
587
- router.post('/:projectId/balls/:ballId/hit', (req, res) => {
588
- const folder = resolveProjectFolder(req.params.projectId, req.user?.username || '', res);
589
- if (!folder)
590
- return;
591
- const { ballId } = req.params;
592
- if (!BALL_ID_RE.test(ballId)) {
593
- res.status(400).json({ error: 'Invalid ball ID format' });
594
- return;
595
- }
596
- const poolDir = path.join(folder, MEMORY_POOL_DIR);
597
- (0, pool_lock_1.withPoolLock)(poolDir, () => {
598
- const pool = (0, pool_manager_1.readPool)(poolDir);
599
- if (!pool) {
600
- res.status(404).json({ error: 'Memory pool not initialized' });
601
- return;
602
- }
603
- const ball = pool.balls.find((b) => b.id === ballId);
604
- if (!ball) {
605
- res.status(404).json({ error: 'Ball not found' });
606
- return;
607
- }
608
- // Update hit count
609
- ball.H += 1;
610
- ball.t_last = pool.t;
611
- (0, pool_manager_1.writePool)(poolDir, pool);
612
- // Skip surface rebuild for hit — H change rarely affects surface order
613
- // Read content
614
- const content = (0, pool_manager_1.readBallContent)(poolDir, ballId);
615
- const buoy = (0, buoyancy_1.computeBuoyancy)(ball.B0, ball.H, pool.alpha, pool.lambda, pool.t, ball.t_last, ball.permanent);
616
- // Read linked balls' summaries
617
- const linkedBalls = ball.links
618
- .map((lid) => pool.balls.find((b) => b.id === lid))
619
- .filter((b) => !!b)
620
- .map((b) => ({
621
- id: b.id,
622
- type: b.type,
623
- summary: b.summary,
624
- buoyancy: (0, buoyancy_1.computeBuoyancy)(b.B0, b.H, pool.alpha, pool.lambda, pool.t, b.t_last, b.permanent),
625
- }));
626
- res.json({ id: ballId, content, buoyancy: buoy, linked_balls: linkedBalls });
627
- }).catch((err) => {
628
- if (!res.headersSent)
629
- res.status(500).json({ error: err.message || err });
630
- });
631
- });
632
- // DELETE /api/memory-pool/:projectId/balls/:ballId — Delete ball
633
- router.delete('/:projectId/balls/:ballId', (req, res) => {
634
- const folder = resolveProjectFolder(req.params.projectId, req.user?.username || '', res);
635
- if (!folder)
636
- return;
637
- const { ballId } = req.params;
638
- if (!BALL_ID_RE.test(ballId)) {
639
- res.status(400).json({ error: 'Invalid ball ID format' });
640
- return;
641
- }
642
- const poolDir = path.join(folder, MEMORY_POOL_DIR);
643
- (0, pool_lock_1.withPoolLock)(poolDir, () => {
644
- const pool = (0, pool_manager_1.readPool)(poolDir);
645
- if (!pool) {
646
- res.status(404).json({ error: 'Memory pool not initialized' });
647
- return;
648
- }
649
- const idx = pool.balls.findIndex((b) => b.id === ballId);
650
- if (idx === -1) {
651
- res.status(404).json({ error: 'Ball not found' });
652
- return;
653
- }
654
- // Remove from balls array
655
- pool.balls.splice(idx, 1);
656
- // Clean up links in other balls that reference this ball
657
- let linksCleaned = 0;
658
- for (const b of pool.balls) {
659
- const before = b.links.length;
660
- b.links = b.links.filter((l) => l !== ballId);
661
- linksCleaned += before - b.links.length;
662
- }
663
- (0, pool_manager_1.writePool)(poolDir, pool);
664
- // Delete ball file
665
- const ballFile = path.join(poolDir, 'balls', `${ballId}.md`);
666
- try {
667
- fs.unlinkSync(ballFile);
668
- }
669
- catch { /* may not exist */ }
670
- (0, pool_manager_1.buildSurface)(poolDir);
671
- res.json({ id: ballId, deleted: true, links_cleaned: linksCleaned });
672
- }).catch((err) => {
673
- if (!res.headersSent)
674
- res.status(500).json({ error: err.message || err });
675
- });
676
- });
677
- // PATCH /api/memory-pool/:projectId/balls/:ballId/links — Manage links
678
- router.patch('/:projectId/balls/:ballId/links', (req, res) => {
679
- const folder = resolveProjectFolder(req.params.projectId, req.user?.username || '', res);
680
- if (!folder)
681
- return;
682
- const { ballId } = req.params;
683
- if (!BALL_ID_RE.test(ballId)) {
684
- res.status(400).json({ error: 'Invalid ball ID format' });
685
- return;
686
- }
687
- const poolDir = path.join(folder, MEMORY_POOL_DIR);
688
- const { add, remove } = req.body;
689
- if (add && (!Array.isArray(add) || !validateBallIds(add))) {
690
- res.status(400).json({ error: 'add must be an array of valid ball IDs' });
691
- return;
692
- }
693
- if (remove && (!Array.isArray(remove) || !validateBallIds(remove))) {
694
- res.status(400).json({ error: 'remove must be an array of valid ball IDs' });
695
- return;
696
- }
697
- (0, pool_lock_1.withPoolLock)(poolDir, () => {
698
- const pool = (0, pool_manager_1.readPool)(poolDir);
699
- if (!pool) {
700
- res.status(404).json({ error: 'Memory pool not initialized' });
701
- return;
702
- }
703
- const ball = pool.balls.find((b) => b.id === ballId);
704
- if (!ball) {
705
- res.status(404).json({ error: 'Ball not found' });
706
- return;
707
- }
708
- const existingIds = new Set(pool.balls.map((b) => b.id));
709
- if (remove) {
710
- const removeSet = new Set(remove);
711
- ball.links = ball.links.filter((l) => !removeSet.has(l));
712
- }
713
- if (add) {
714
- for (const lid of add) {
715
- if (existingIds.has(lid) && !ball.links.includes(lid) && lid !== ballId) {
716
- ball.links.push(lid);
717
- }
718
- }
719
- }
720
- (0, pool_manager_1.writePool)(poolDir, pool);
721
- (0, pool_manager_1.buildSurface)(poolDir);
722
- res.json({ id: ballId, links: ball.links });
723
- }).catch((err) => {
724
- if (!res.headersSent)
725
- res.status(500).json({ error: err.message || err });
726
- });
727
- });
728
- // POST /api/memory-pool/:projectId/tick — Increment turn
729
- router.post('/:projectId/tick', (req, res) => {
730
- const folder = resolveProjectFolder(req.params.projectId, req.user?.username || '', res);
731
- if (!folder)
732
- return;
733
- const poolDir = path.join(folder, MEMORY_POOL_DIR);
734
- const { session } = req.body || {};
735
- (0, pool_lock_1.withPoolLock)(poolDir, () => {
736
- const result = (0, pool_manager_1.tickPool)(poolDir, session);
737
- if (!result) {
738
- res.status(404).json({ error: 'Memory pool not initialized' });
739
- return;
740
- }
741
- res.json(result);
742
- }).catch((err) => {
743
- if (!res.headersSent)
744
- res.status(500).json({ error: err.message || err });
745
- });
746
- });
747
- // GET /api/memory-pool/:projectId/surface — Get active layer summary
748
- router.get('/:projectId/surface', (req, res) => {
749
- const folder = resolveProjectFolder(req.params.projectId, req.user?.username || '', res);
750
- if (!folder)
751
- return;
752
- const poolDir = path.join(folder, MEMORY_POOL_DIR);
753
- (0, pool_lock_1.withPoolLock)(poolDir, () => {
754
- const pool = (0, pool_manager_1.readPool)(poolDir);
755
- if (!pool) {
756
- res.status(404).json({ error: 'Memory pool not initialized' });
757
- return;
758
- }
759
- // Compensate missed tick if last_tick_at > 10 minutes ago.
760
- // Uses a special session marker so the next Stop hook tick won't be deduped —
761
- // the compensate tick covers the *previous* stale gap, Stop hook covers *this* session.
762
- if (pool.last_tick_at) {
763
- const elapsed = Date.now() - new Date(pool.last_tick_at).getTime();
764
- if (elapsed > 10 * 60 * 1000) {
765
- pool.t += 1;
766
- pool.last_tick_at = new Date().toISOString();
767
- pool.last_tick_session = '__surface_compensate__';
768
- (0, pool_manager_1.writePool)(poolDir, pool);
769
- }
770
- }
771
- const { surfaceBalls, totalTokens } = (0, pool_manager_1.buildSurface)(poolDir);
772
- res.json({
773
- t: pool.t,
774
- surface_width: pool.surface_width ?? 10000,
775
- used_tokens: totalTokens,
776
- balls: surfaceBalls,
777
- });
778
- }).catch((err) => {
779
- if (!res.headersSent)
780
- res.status(500).json({ error: err.message || err });
781
- });
782
- });
783
- // POST /api/memory-pool/:projectId/maintenance — Maintenance suggestions + self-healing
784
- router.post('/:projectId/maintenance', (req, res) => {
785
- const folder = resolveProjectFolder(req.params.projectId, req.user?.username || '', res);
786
- if (!folder)
787
- return;
788
- const poolDir = path.join(folder, MEMORY_POOL_DIR);
789
- const pool = (0, pool_manager_1.readPool)(poolDir);
790
- if (!pool) {
791
- res.status(404).json({ error: 'Memory pool not initialized' });
792
- return;
793
- }
794
- const enriched = (0, pool_manager_1.enrichBallsWithBuoyancy)(pool);
795
- const cap = pool.active_capacity;
796
- const activeFull = enriched.length >= cap;
797
- // Split suggestions: all active layer balls, mark recommended based on hardness + size
798
- const suggestions = [];
799
- const activeBalls = enriched.slice(0, cap);
800
- for (const b of activeBalls) {
801
- const diameter = b.diameter ?? (0, pool_manager_1.computeDiameter)(poolDir, b.id);
802
- if (diameter > 100) {
803
- const recommended = b.hardness < 7;
804
- suggestions.push({
805
- action: 'split',
806
- ball_id: b.id,
807
- reason: `diameter=${diameter}tok, hardness=${b.hardness}${recommended ? ' — 可考虑拆分' : ' — 硬度过高,不建议拆分'}`,
808
- recommended,
809
- });
810
- }
811
- }
812
- // Self-healing: detect inconsistencies (H-4 fix)
813
- const anomalies = [];
814
- const ballsDir = path.join(poolDir, 'balls');
815
- // Check for ghost entries (pool.json has ball but file missing)
816
- for (const b of pool.balls) {
817
- const file = path.join(ballsDir, `${b.id}.md`);
818
- if (!fs.existsSync(file)) {
819
- anomalies.push(`ghost: ${b.id} in pool.json but file missing`);
820
- }
821
- }
822
- // Check for orphan files (file exists but not in pool.json)
823
- try {
824
- const poolIds = new Set(pool.balls.map((b) => b.id));
825
- const files = fs.readdirSync(ballsDir).filter((f) => f.endsWith('.md') && f.startsWith('ball_'));
826
- for (const f of files) {
827
- const id = f.replace('.md', '');
828
- if (!poolIds.has(id)) {
829
- anomalies.push(`orphan_file: ${f} exists but not in pool.json`);
830
- }
831
- }
832
- }
833
- catch { /* balls dir may not exist */ }
834
- res.json({ active_full: activeFull, suggestions, anomalies });
835
- });
836
- exports.default = router;
837
- //# sourceMappingURL=memory-pool.js.map