@seele00/seele 0.1.63 → 0.1.65

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 (105) hide show
  1. package/README.md +5 -3
  2. package/bin/seele-codex +1 -1
  3. package/libexec/seele-server +0 -0
  4. package/libexec/seele-worker +0 -0
  5. package/package.json +1 -4
  6. package/scripts/deploy/bootstrap.sh +14 -3
  7. package/scripts/deploy/start.sh +14 -9
  8. package/web/dist/assets/{abnfDiagram-VRR7QNED-DXJQwnn-.js → abnfDiagram-VRR7QNED-BB7DT0BN.js} +1 -1
  9. package/web/dist/assets/{arc-D_47-IET.js → arc-BlEU_mit.js} +1 -1
  10. package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-CKUldIzR.js → architectureDiagram-ZJ3FMSHR-Dy9cu2Mg.js} +1 -1
  11. package/web/dist/assets/{blockDiagram-677ZJIJ3-BD4Ym8Xi.js → blockDiagram-677ZJIJ3-DP4jMSfo.js} +1 -1
  12. package/web/dist/assets/{c4Diagram-LMCZKHZV-Cn268vLB.js → c4Diagram-LMCZKHZV-BtqFzX1w.js} +1 -1
  13. package/web/dist/assets/channel-mhSuRKJT.js +1 -0
  14. package/web/dist/assets/{chunk-2Q5K7J3B-D00lQ7mJ.js → chunk-2Q5K7J3B-BK9F5a0L.js} +1 -1
  15. package/web/dist/assets/{chunk-32BRIVSS-z7gv8Mry.js → chunk-32BRIVSS-nuQI6gtU.js} +1 -1
  16. package/web/dist/assets/{chunk-5VM5RSS4-BZJeO_rz.js → chunk-5VM5RSS4-D4IT05bt.js} +1 -1
  17. package/web/dist/assets/{chunk-EX3LRPZG-DoIg9UGx.js → chunk-EX3LRPZG-CrOtgxcr.js} +1 -1
  18. package/web/dist/assets/{chunk-JWPE2WC7-C1LcQus2.js → chunk-JWPE2WC7-Dn9JHm4W.js} +1 -1
  19. package/web/dist/assets/{chunk-MOJQB5TN--JPbFu4n.js → chunk-MOJQB5TN-VtD0_fGF.js} +1 -1
  20. package/web/dist/assets/{chunk-RYQCIY6F-BVv30W60.js → chunk-RYQCIY6F-TqRYsCot.js} +1 -1
  21. package/web/dist/assets/{chunk-V7JOEXUC-DYoSFgRT.js → chunk-V7JOEXUC-U9b5a7vL.js} +1 -1
  22. package/web/dist/assets/{chunk-VR4S4FIN-C8BRkTOa.js → chunk-VR4S4FIN-B3qLfeNJ.js} +1 -1
  23. package/web/dist/assets/{chunk-XXDRQBXY-CDfofIob.js → chunk-XXDRQBXY-DazVAZe-.js} +1 -1
  24. package/web/dist/assets/classDiagram-OUVF2IWQ-DYQSxD4g.js +1 -0
  25. package/web/dist/assets/classDiagram-v2-EOCWNBFH-DYQSxD4g.js +1 -0
  26. package/web/dist/assets/{core-C10OFTno.js → core-2RdBW0rZ.js} +1 -1
  27. package/web/dist/assets/{cose-bilkent-JH36ORCC-g9E8J4Fz.js → cose-bilkent-JH36ORCC-vvKttQXU.js} +1 -1
  28. package/web/dist/assets/{cynefin-VYW2F7L2-Dc689veM.js → cynefin-VYW2F7L2-BKMZmA-n.js} +1 -1
  29. package/web/dist/assets/{cynefinDiagram-TSTJHNR4-BAAq_jvr.js → cynefinDiagram-TSTJHNR4-ckdzxzlr.js} +1 -1
  30. package/web/dist/assets/{dagre-VKFMJZFB-CPtiniZQ.js → dagre-VKFMJZFB-CZw8DTb1.js} +1 -1
  31. package/web/dist/assets/{diagram-FQU43EPY-CvbIL3lu.js → diagram-FQU43EPY-C06X0Kru.js} +1 -1
  32. package/web/dist/assets/{diagram-G47NLZAW-u5m78Utg.js → diagram-G47NLZAW-HX9CdauD.js} +1 -1
  33. package/web/dist/assets/{diagram-NH7WQ7WH-4f-Ipubv.js → diagram-NH7WQ7WH-Dlo1tIod.js} +1 -1
  34. package/web/dist/assets/{diagram-OA4YK3LP-SUl1EQ56.js → diagram-OA4YK3LP-C9GXFhgw.js} +1 -1
  35. package/web/dist/assets/{diagram-WEI45ONY-C_Upd46l.js → diagram-WEI45ONY-Ch1Xx234.js} +1 -1
  36. package/web/dist/assets/{ebnfDiagram-CCIWWBDH-CHp2D7Q9.js → ebnfDiagram-CCIWWBDH-Q0_rEUgk.js} +1 -1
  37. package/web/dist/assets/{erDiagram-Q63AITRT-DQliHxOw.js → erDiagram-Q63AITRT-BsDRdpx6.js} +1 -1
  38. package/web/dist/assets/{flowDiagram-23GEKE2U-BcOYSJFM.js → flowDiagram-23GEKE2U-DDFuMRJN.js} +1 -1
  39. package/web/dist/assets/{ganttDiagram-NO4QXBWP-CDLHtJq0.js → ganttDiagram-NO4QXBWP-Cjpm08-d.js} +1 -1
  40. package/web/dist/assets/{gitGraphDiagram-IHSO6WYX-2j8C3mlk.js → gitGraphDiagram-IHSO6WYX-CnIosh2K.js} +1 -1
  41. package/web/dist/assets/index-eN6ddNRA.js +111 -0
  42. package/web/dist/assets/{infoDiagram-FWYZ7A6U-ClxdyQnM.js → infoDiagram-FWYZ7A6U-CH163HyC.js} +1 -1
  43. package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-Bc2nIRgb.js → ishikawaDiagram-FXEZZL3T-DkT-3n3D.js} +1 -1
  44. package/web/dist/assets/{journeyDiagram-5HDEW3XC-R-vJ00LW.js → journeyDiagram-5HDEW3XC-34TnH3wG.js} +1 -1
  45. package/web/dist/assets/{kanban-definition-HUTT4EX6-CR0s_Mo8.js → kanban-definition-HUTT4EX6-CJbOm0lN.js} +1 -1
  46. package/web/dist/assets/{linear-BQgrawZS.js → linear-CTKXTLwe.js} +1 -1
  47. package/web/dist/assets/{mermaid.core-faLMbjSU.js → mermaid.core-CAVP16_-.js} +4 -4
  48. package/web/dist/assets/{mindmap-definition-LN4V7U3C-BZULnQui.js → mindmap-definition-LN4V7U3C-rlppHZj_.js} +1 -1
  49. package/web/dist/assets/{pegDiagram-2B236MQR-DUm1p_1o.js → pegDiagram-2B236MQR-MbcWBNJi.js} +1 -1
  50. package/web/dist/assets/{pieDiagram-ENE6RG2P-r3LCASRK.js → pieDiagram-ENE6RG2P-DKU7a4tv.js} +1 -1
  51. package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-CGaCp0Px.js → quadrantDiagram-ABIIQ3AL-DpDsJVLi.js} +1 -1
  52. package/web/dist/assets/{railroadDiagram-RFXS5EU6-DmEKGO1A.js → railroadDiagram-RFXS5EU6-DrPM3aKC.js} +1 -1
  53. package/web/dist/assets/{requirementDiagram-TGXJPOKE-Byc4ivmp.js → requirementDiagram-TGXJPOKE-ZzS_iVag.js} +1 -1
  54. package/web/dist/assets/{sankeyDiagram-HTMAVEWB-BtJiL4zX.js → sankeyDiagram-HTMAVEWB-DrRrN0Lc.js} +1 -1
  55. package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-Bg3vdDGW.js → sequenceDiagram-DBY2YBRQ-CP8GBGZM.js} +1 -1
  56. package/web/dist/assets/{sizeCapture-X5ZJPWSS-CCRwjaWU.js → sizeCapture-X5ZJPWSS-BYVg_dQd.js} +1 -1
  57. package/web/dist/assets/{stateDiagram-2N3HPSRC-CQU-udox.js → stateDiagram-2N3HPSRC-CsJ3M80-.js} +1 -1
  58. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-CaFOY1Xb.js +1 -0
  59. package/web/dist/assets/{swimlanes-5IMT3BWC-r3tJnQLJ.js → swimlanes-5IMT3BWC-XgRKQ20Q.js} +2 -2
  60. package/web/dist/assets/swimlanesDiagram-G3AALYLV-DaKP_iDy.js +8 -0
  61. package/web/dist/assets/{timeline-definition-FHXFAJF6-DryYrdts.js → timeline-definition-FHXFAJF6-CU_Cq10d.js} +1 -1
  62. package/web/dist/assets/{vennDiagram-L72KCM5P-ZLPc0qvQ.js → vennDiagram-L72KCM5P-CLSDbg6o.js} +1 -1
  63. package/web/dist/assets/{wardleyDiagram-EHGQE667-RmgA6vQ9.js → wardleyDiagram-EHGQE667-DnAOuvzv.js} +1 -1
  64. package/web/dist/assets/{xychartDiagram-FW5EYKEG-BGIzispI.js → xychartDiagram-FW5EYKEG-CyC6V11l.js} +1 -1
  65. package/web/dist/index.html +1 -1
  66. package/codex/dist/app-server.js +0 -63
  67. package/codex/dist/errors.js +0 -16
  68. package/codex/dist/index.js +0 -4
  69. package/codex/dist/rpc-client.js +0 -96
  70. package/codex/dist/transports/stdio.js +0 -81
  71. package/codex/dist/types.js +0 -1
  72. package/codex/package.json +0 -12
  73. package/scripts/deploy/npm/postinstall.mjs +0 -10
  74. package/server/dist/auth/session.js +0 -71
  75. package/server/dist/auth/users.js +0 -276
  76. package/server/dist/codex-runtime/run-streamed-turn.js +0 -587
  77. package/server/dist/config/config.js +0 -71
  78. package/server/dist/contracts/artifact-rendering.js +0 -14
  79. package/server/dist/contracts/global-environment.js +0 -23
  80. package/server/dist/contracts/index.js +0 -114
  81. package/server/dist/contracts/managed-assets.js +0 -13
  82. package/server/dist/contracts/response-style.js +0 -23
  83. package/server/dist/contracts/task-clarification.js +0 -13
  84. package/server/dist/contracts/types.js +0 -1
  85. package/server/dist/contracts/wakeup.js +0 -13
  86. package/server/dist/contracts/workspace-boundary.js +0 -20
  87. package/server/dist/contracts/workspace-knowledge.js +0 -48
  88. package/server/dist/contracts/workspace-memory.js +0 -32
  89. package/server/dist/contracts/workspace-responsibility.js +0 -23
  90. package/server/dist/contracts/workspace-transcript.js +0 -26
  91. package/server/dist/conversations/knowledge.js +0 -380
  92. package/server/dist/conversations/store.js +0 -2466
  93. package/server/dist/doctor.js +0 -105
  94. package/server/dist/realtime/sse.js +0 -12
  95. package/server/dist/runtime-worker/client.js +0 -405
  96. package/server/dist/runtime-worker/process.js +0 -216
  97. package/server/dist/runtime-worker/protocol.js +0 -1
  98. package/server/dist/server.js +0 -4605
  99. package/server/dist/storage/sqlite.js +0 -39
  100. package/web/dist/assets/channel-Uz6zF6Xw.js +0 -1
  101. package/web/dist/assets/classDiagram-OUVF2IWQ-D6mpE1t2.js +0 -1
  102. package/web/dist/assets/classDiagram-v2-EOCWNBFH-D6mpE1t2.js +0 -1
  103. package/web/dist/assets/index-BqSosI7W.js +0 -111
  104. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-YNSWW4ip.js +0 -1
  105. package/web/dist/assets/swimlanesDiagram-G3AALYLV-BGZAc11_.js +0 -8
@@ -1,2466 +0,0 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, } from "node:fs";
2
- import path from "node:path";
3
- import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
4
- import { openSqliteDatabase, runInTransaction, } from "../storage/sqlite.js";
5
- const conversations = new Map();
6
- const workspaceGroups = new Map();
7
- const workspaceDatabases = new Map();
8
- let workspaceRootPath = null;
9
- let centerDatabase = null;
10
- let defaultOwnerId = null;
11
- const DEFAULT_WORKSPACE_GROUP_ID = "default";
12
- const CENTER_SCHEMA_VERSION = 9;
13
- const WORKSPACE_SCHEMA_VERSION = 8;
14
- const WORKSPACE_MEMORY_ITEMS_SETTING_KEY = "workspace.memory.items";
15
- const WORKSPACE_RESPONSIBILITY_SETTING_KEY = "workspace.responsibility";
16
- const WORKSPACE_ENVIRONMENT_VALUE_PREFIX = "environment.";
17
- const WORKSPACE_ENVIRONMENT_DESCRIPTION_PREFIX = "environment_description.";
18
- const MAX_IN_MEMORY_EVENTS_PER_WORKSPACE = 500;
19
- const MAX_COMPACT_EVENT_TEXT_LENGTH = 4_000;
20
- const LEGACY_MIGRATED_DIR_NAME = "legacy-migrated";
21
- export function initializeConversationStore(input) {
22
- workspaceRootPath = input.workspaceRoot;
23
- defaultOwnerId = input.defaultOwnerId;
24
- mkdirSync(input.workspaceRoot, { recursive: true });
25
- centerDatabase = openCenterDatabase(input.centerDbPath);
26
- conversations.clear();
27
- workspaceGroups.clear();
28
- for (const db of workspaceDatabases.values()) {
29
- db.close();
30
- }
31
- workspaceDatabases.clear();
32
- initializeCenterSchema(getCenterDatabase());
33
- migrateLegacyWorkspaceGroups(input.workspaceRoot);
34
- for (const entry of readdirSync(input.workspaceRoot)) {
35
- const workspacePath = path.join(input.workspaceRoot, entry);
36
- if (!statSync(workspacePath).isDirectory()) {
37
- continue;
38
- }
39
- migrateLegacyWorkspace(input.workspaceRoot, workspacePath, input.defaultSandbox);
40
- }
41
- ensureDefaultWorkspaceGroup();
42
- loadWorkspaceGroupsFromDatabase();
43
- loadConversationsFromDatabase(input.defaultSandbox);
44
- initializeWorkspaceUnreadBaselines();
45
- }
46
- export function closeConversationStore() {
47
- for (const db of workspaceDatabases.values()) {
48
- db.close();
49
- }
50
- workspaceDatabases.clear();
51
- centerDatabase?.close();
52
- centerDatabase = null;
53
- workspaceRootPath = null;
54
- defaultOwnerId = null;
55
- conversations.clear();
56
- workspaceGroups.clear();
57
- }
58
- export function listWorkspaceGroups() {
59
- return [...workspaceGroups.values()].sort((left, right) => left.createdAt.localeCompare(right.createdAt));
60
- }
61
- export function createWorkspaceGroup(title, ownerId) {
62
- const now = new Date().toISOString();
63
- const group = {
64
- id: createWorkspaceGroupId(title),
65
- ownerId,
66
- title,
67
- createdAt: now,
68
- updatedAt: now,
69
- };
70
- workspaceGroups.set(group.id, group);
71
- upsertWorkspaceGroup(group);
72
- return group;
73
- }
74
- export function deleteWorkspaceGroup(id) {
75
- if (id === DEFAULT_WORKSPACE_GROUP_ID || !workspaceGroups.has(id)) {
76
- return false;
77
- }
78
- workspaceGroups.delete(id);
79
- for (const conversation of conversations.values()) {
80
- if (conversation.groupId === id) {
81
- conversation.groupId = DEFAULT_WORKSPACE_GROUP_ID;
82
- touch(conversation);
83
- }
84
- }
85
- getCenterDatabase()
86
- .prepare("delete from workspace_groups where id = ?")
87
- .run(id);
88
- return true;
89
- }
90
- export function setWorkspaceGroupTitle(id, title) {
91
- const group = workspaceGroups.get(id);
92
- if (!group) {
93
- return null;
94
- }
95
- const updatedGroup = {
96
- ...group,
97
- title,
98
- updatedAt: new Date().toISOString(),
99
- };
100
- upsertWorkspaceGroup(updatedGroup);
101
- return updatedGroup;
102
- }
103
- export function hasWorkspaceGroup(id) {
104
- return workspaceGroups.has(id);
105
- }
106
- export function listGlobalEnvironmentVariables() {
107
- const rows = getCenterDatabase()
108
- .prepare("select * from global_environment_variables order by key asc")
109
- .all();
110
- return rows.map(globalEnvironmentVariableFromRow);
111
- }
112
- export function getGlobalEnvironmentMap() {
113
- return Object.fromEntries(listGlobalEnvironmentVariables().map((item) => [item.key, item.value]));
114
- }
115
- export function getGlobalEnvironmentDescriptions() {
116
- return Object.fromEntries(listGlobalEnvironmentVariables().map((item) => [
117
- item.key,
118
- item.description,
119
- ]));
120
- }
121
- export function listConversationEnvironmentVariables(conversation) {
122
- const settings = readWorkspaceSettings(conversation.workspacePath);
123
- const variables = Object.entries(settings)
124
- .filter(([key]) => key.startsWith(WORKSPACE_ENVIRONMENT_VALUE_PREFIX))
125
- .map(([settingKey, value]) => {
126
- const key = settingKey.slice(WORKSPACE_ENVIRONMENT_VALUE_PREFIX.length);
127
- const description = settings[`${WORKSPACE_ENVIRONMENT_DESCRIPTION_PREFIX}${key}`] ?? "";
128
- const updatedAt = readWorkspaceSettingUpdatedAt(conversation.workspacePath, settingKey);
129
- return {
130
- key,
131
- value,
132
- description,
133
- createdAt: updatedAt,
134
- updatedAt,
135
- };
136
- })
137
- .filter((item) => isValidStoredEnvironmentKey(item.key));
138
- return variables.sort((left, right) => left.key.localeCompare(right.key));
139
- }
140
- export function getConversationEnvironmentMap(conversation) {
141
- return Object.fromEntries(listConversationEnvironmentVariables(conversation).map((item) => [
142
- item.key,
143
- item.value,
144
- ]));
145
- }
146
- export function getConversationEnvironmentDescriptions(conversation) {
147
- return Object.fromEntries(listConversationEnvironmentVariables(conversation).map((item) => [
148
- item.key,
149
- item.description,
150
- ]));
151
- }
152
- export function revealConversationEnvironmentVariable(conversation, key) {
153
- return (listConversationEnvironmentVariables(conversation).find((item) => item.key === key) ?? null);
154
- }
155
- export function upsertConversationEnvironmentVariable(conversation, key, value, description = "") {
156
- const existing = revealConversationEnvironmentVariable(conversation, key);
157
- const normalizedValue = value.length > 0 ? value : existing?.value ?? "";
158
- const valueSetting = upsertWorkspaceSetting(conversation, `${WORKSPACE_ENVIRONMENT_VALUE_PREFIX}${key}`, normalizedValue);
159
- upsertWorkspaceSetting(conversation, `${WORKSPACE_ENVIRONMENT_DESCRIPTION_PREFIX}${key}`, description);
160
- return {
161
- key,
162
- value: normalizedValue,
163
- description,
164
- createdAt: existing?.createdAt ?? valueSetting.updatedAt,
165
- updatedAt: valueSetting.updatedAt,
166
- };
167
- }
168
- export function deleteConversationEnvironmentVariable(conversation, key) {
169
- const db = getWorkspaceDatabase(conversation.workspacePath);
170
- const valueResult = db
171
- .prepare("delete from workspace_settings where key = ?")
172
- .run(`${WORKSPACE_ENVIRONMENT_VALUE_PREFIX}${key}`);
173
- db.prepare("delete from workspace_settings where key = ?").run(`${WORKSPACE_ENVIRONMENT_DESCRIPTION_PREFIX}${key}`);
174
- touch(conversation);
175
- return valueResult.changes > 0;
176
- }
177
- export function revealGlobalEnvironmentVariable(key) {
178
- return getGlobalEnvironmentVariable(key);
179
- }
180
- export function upsertGlobalEnvironmentVariable(key, value, description = "") {
181
- const existing = getGlobalEnvironmentVariable(key);
182
- const now = new Date().toISOString();
183
- const variable = {
184
- key,
185
- value: value.length > 0 ? value : existing?.value ?? "",
186
- description,
187
- createdAt: existing?.createdAt ?? now,
188
- updatedAt: now,
189
- };
190
- getCenterDatabase()
191
- .prepare(`
192
- insert into global_environment_variables (key, value, description, created_at, updated_at)
193
- values (?, ?, ?, ?, ?)
194
- on conflict(key) do update set
195
- value = excluded.value,
196
- description = excluded.description,
197
- updated_at = excluded.updated_at
198
- `)
199
- .run(variable.key, variable.value, variable.description, variable.createdAt, variable.updatedAt);
200
- return variable;
201
- }
202
- export function deleteGlobalEnvironmentVariable(key) {
203
- const result = getCenterDatabase()
204
- .prepare("delete from global_environment_variables where key = ?")
205
- .run(key);
206
- return result.changes > 0;
207
- }
208
- export function getAppSetting(key) {
209
- const row = getCenterDatabase()
210
- .prepare("select * from app_settings where key = ?")
211
- .get(key);
212
- return row ? appSettingFromRow(row) : null;
213
- }
214
- export function getAppSettingValue(key) {
215
- return getAppSetting(key)?.value ?? null;
216
- }
217
- export function upsertAppSetting(key, value) {
218
- const now = new Date().toISOString();
219
- getCenterDatabase()
220
- .prepare(`
221
- insert into app_settings (key, value, updated_at)
222
- values (?, ?, ?)
223
- on conflict(key) do update set
224
- value = excluded.value,
225
- updated_at = excluded.updated_at
226
- `)
227
- .run(key, value, now);
228
- return {
229
- key,
230
- value,
231
- updatedAt: now,
232
- };
233
- }
234
- export function setConversationMemory(conversation, memory) {
235
- const value = memory.trim();
236
- return setConversationMemoryItems(conversation, value
237
- ? [
238
- {
239
- key: "default",
240
- value,
241
- updatedAt: new Date().toISOString(),
242
- },
243
- ]
244
- : []);
245
- }
246
- export function setConversationMemoryItems(conversation, items) {
247
- const normalized = normalizeMemoryItems(items);
248
- conversation.memoryItems = normalized;
249
- conversation.memory = formatMemoryItems(normalized);
250
- upsertWorkspaceSetting(conversation, WORKSPACE_MEMORY_ITEMS_SETTING_KEY, JSON.stringify(normalized));
251
- touch(conversation);
252
- return conversation;
253
- }
254
- export function setConversationResponsibility(conversation, responsibility) {
255
- const normalized = responsibility.trim();
256
- conversation.responsibility = normalized;
257
- upsertWorkspaceSetting(conversation, WORKSPACE_RESPONSIBILITY_SETTING_KEY, normalized);
258
- touch(conversation);
259
- return conversation;
260
- }
261
- export function setConversationWorkspaceSetting(conversation, key, value) {
262
- const normalizedKey = key.trim();
263
- if (!normalizedKey) {
264
- throw new Error("workspace setting key is required");
265
- }
266
- return upsertWorkspaceSetting(conversation, normalizedKey, value);
267
- }
268
- export function deleteConversationWorkspaceSetting(conversation, key) {
269
- const normalizedKey = key.trim();
270
- if (!normalizedKey) {
271
- return;
272
- }
273
- getWorkspaceDatabase(conversation.workspacePath)
274
- .prepare("delete from workspace_settings where key = ?")
275
- .run(normalizedKey);
276
- touch(conversation);
277
- }
278
- export function getConversationWorkspaceSettings(conversation) {
279
- return readWorkspaceSettings(conversation.workspacePath);
280
- }
281
- export function listConversations() {
282
- return [...conversations.values()]
283
- .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
284
- .map(toSummary);
285
- }
286
- export function countConversationUnreadTurns(conversation, userId) {
287
- const baseline = getCenterDatabase()
288
- .prepare(`
289
- select terminal_turn_rowid
290
- from workspace_unread_baselines
291
- where workspace_id = ?
292
- `)
293
- .get(conversation.id);
294
- const readState = getCenterDatabase()
295
- .prepare(`
296
- select last_read_turn_rowid
297
- from workspace_read_states
298
- where user_id = ? and workspace_id = ?
299
- `)
300
- .get(userId, conversation.id);
301
- const cursor = Math.max(baseline?.terminal_turn_rowid ?? 0, readState?.last_read_turn_rowid ?? 0);
302
- const row = getWorkspaceDatabase(conversation.workspacePath)
303
- .prepare(`
304
- select count(*) as count
305
- from turns
306
- where rowid > ? and status in ('completed', 'failed')
307
- `)
308
- .get(cursor);
309
- return row?.count ?? 0;
310
- }
311
- export function markConversationRead(conversation, userId) {
312
- const lastReadTurnRowid = latestTerminalTurnRowid(conversation);
313
- getCenterDatabase()
314
- .prepare(`
315
- insert into workspace_read_states (
316
- user_id, workspace_id, last_read_turn_rowid, updated_at
317
- ) values (?, ?, ?, ?)
318
- on conflict(user_id, workspace_id) do update set
319
- last_read_turn_rowid = excluded.last_read_turn_rowid,
320
- updated_at = excluded.updated_at
321
- `)
322
- .run(userId, conversation.id, lastReadTurnRowid, new Date().toISOString());
323
- }
324
- export function getConversationStoreDiagnostics() {
325
- return {
326
- centerSchemaVersion: centerDatabase
327
- ? readSchemaVersion(centerDatabase)
328
- : null,
329
- expectedCenterSchemaVersion: CENTER_SCHEMA_VERSION,
330
- expectedWorkspaceSchemaVersion: WORKSPACE_SCHEMA_VERSION,
331
- workspaceRoot: workspaceRootPath,
332
- workspaces: conversations.size,
333
- groups: workspaceGroups.size,
334
- globalEnvironmentVariables: countRows(getCenterDatabase(), "global_environment_variables"),
335
- appSettings: countRows(getCenterDatabase(), "app_settings"),
336
- wakeups: countWakeupsByStatus(),
337
- activeConversations: [...conversations.values()].filter((conversation) => conversation.status === "running").length,
338
- workspaceDatabases: [...conversations.values()]
339
- .sort((left, right) => left.id.localeCompare(right.id))
340
- .map((conversation) => {
341
- const db = getWorkspaceDatabase(conversation.workspacePath);
342
- return {
343
- id: conversation.id,
344
- title: conversation.title,
345
- workspacePath: conversation.workspacePath,
346
- schemaVersion: readSchemaVersion(db),
347
- messages: countRows(db, "messages"),
348
- events: countRows(db, "events"),
349
- turns: countRows(db, "turns"),
350
- status: conversation.status,
351
- };
352
- }),
353
- };
354
- }
355
- export function createWorkspaceWakeup(input) {
356
- if (!conversations.has(input.workspaceId)) {
357
- throw new Error(`workspace not found: ${input.workspaceId}`);
358
- }
359
- const now = new Date().toISOString();
360
- const wakeup = {
361
- id: randomUUID(),
362
- workspaceId: input.workspaceId,
363
- status: "scheduled",
364
- wakeAt: input.wakeAt,
365
- instruction: input.instruction,
366
- createdBy: input.createdBy ?? "user",
367
- createdTurnId: input.createdTurnId ?? null,
368
- resultTurnId: null,
369
- error: null,
370
- attemptCount: 0,
371
- maxAttempts: clampWakeupMaxAttempts(input.maxAttempts),
372
- createdAt: now,
373
- updatedAt: now,
374
- };
375
- upsertWorkspaceWakeup(wakeup);
376
- return wakeup;
377
- }
378
- export function listDueWorkspaceWakeups(now) {
379
- const rows = getCenterDatabase()
380
- .prepare(`
381
- select * from workspace_wakeups
382
- where status = 'scheduled'
383
- and wake_at <= ?
384
- order by wake_at asc, created_at asc
385
- `)
386
- .all(now);
387
- return rows.map(wakeupFromRow);
388
- }
389
- export function listWorkspaceWakeups(workspaceId, options = {}) {
390
- const rows = getCenterDatabase()
391
- .prepare(`
392
- select * from workspace_wakeups
393
- where workspace_id = ?
394
- order by wake_at desc, created_at desc
395
- limit ?
396
- `)
397
- .all(workspaceId, Math.max(1, Math.min(100, options.limit ?? 20)));
398
- return rows.map(wakeupFromRow);
399
- }
400
- export function getNextScheduledWorkspaceWakeup(workspaceId) {
401
- const row = getCenterDatabase()
402
- .prepare(`
403
- select * from workspace_wakeups
404
- where workspace_id = ?
405
- and status = 'scheduled'
406
- order by wake_at asc, created_at asc
407
- limit 1
408
- `)
409
- .get(workspaceId);
410
- return row ? wakeupFromRow(row) : null;
411
- }
412
- export function updateWorkspaceWakeup(id, input) {
413
- const existing = getWorkspaceWakeup(id);
414
- if (!existing) {
415
- return null;
416
- }
417
- const wakeup = {
418
- ...existing,
419
- status: input.status ?? existing.status,
420
- resultTurnId: Object.prototype.hasOwnProperty.call(input, "resultTurnId")
421
- ? input.resultTurnId ?? null
422
- : existing.resultTurnId,
423
- error: Object.prototype.hasOwnProperty.call(input, "error")
424
- ? input.error ?? null
425
- : existing.error,
426
- attemptCount: typeof input.attemptCount === "number"
427
- ? Math.max(0, Math.floor(input.attemptCount))
428
- : existing.attemptCount,
429
- maxAttempts: typeof input.maxAttempts === "number"
430
- ? clampWakeupMaxAttempts(input.maxAttempts)
431
- : existing.maxAttempts,
432
- updatedAt: new Date().toISOString(),
433
- };
434
- upsertWorkspaceWakeup(wakeup);
435
- return wakeup;
436
- }
437
- export function cancelWorkspaceWakeup(id, options = {}) {
438
- const existing = getWorkspaceWakeup(id);
439
- if (!existing ||
440
- existing.status !== "scheduled" ||
441
- (options.workspaceId && existing.workspaceId !== options.workspaceId)) {
442
- return null;
443
- }
444
- return updateWorkspaceWakeup(id, {
445
- status: "cancelled",
446
- error: options.reason ?? "cancelled_by_user",
447
- });
448
- }
449
- export function recoverInterruptedWakeupsOnStartup() {
450
- const now = new Date().toISOString();
451
- const rows = getCenterDatabase()
452
- .prepare(`
453
- select * from workspace_wakeups
454
- where status = 'running'
455
- order by updated_at asc
456
- `)
457
- .all();
458
- if (rows.length === 0) {
459
- return {
460
- recoveredWakeupCount: 0,
461
- recoveredWorkspaceIds: [],
462
- };
463
- }
464
- runInTransaction(getCenterDatabase(), () => {
465
- for (const row of rows) {
466
- getCenterDatabase()
467
- .prepare(`
468
- update workspace_wakeups
469
- set status = 'failed',
470
- error = ?,
471
- updated_at = ?
472
- where id = ?
473
- `)
474
- .run("interrupted_by_server_restart", now, row.id);
475
- }
476
- });
477
- for (const row of rows) {
478
- const conversation = conversations.get(row.workspace_id);
479
- if (!conversation) {
480
- continue;
481
- }
482
- appendEvent(conversation, "server.wakeup.recovered_interrupted", {
483
- wakeupId: row.id,
484
- previousStatus: row.status,
485
- reason: "server_restart",
486
- }, { turnId: row.result_turn_id, createdAt: now });
487
- }
488
- return {
489
- recoveredWakeupCount: rows.length,
490
- recoveredWorkspaceIds: [...new Set(rows.map((row) => row.workspace_id))].sort(),
491
- };
492
- }
493
- export function getConversation(id) {
494
- return conversations.get(id) ?? null;
495
- }
496
- export function recoverInterruptedTurnsOnStartup() {
497
- const now = new Date().toISOString();
498
- const recoveredWorkspaceIds = [];
499
- let recoveredTurnCount = 0;
500
- for (const conversation of conversations.values()) {
501
- const db = getWorkspaceDatabase(conversation.workspacePath);
502
- const rows = db
503
- .prepare(`
504
- select * from turns
505
- where status in ('queued', 'running', 'waiting_approval')
506
- order by started_at asc
507
- `)
508
- .all();
509
- if (rows.length === 0) {
510
- continue;
511
- }
512
- runInTransaction(db, () => {
513
- for (const row of rows) {
514
- db.prepare(`
515
- update turns
516
- set status = 'failed',
517
- finished_at = ?,
518
- error = ?,
519
- updated_at = ?
520
- where id = ?
521
- `).run(now, "interrupted_by_server_restart", now, row.id);
522
- db.prepare(`
523
- insert into events (id, turn_id, event, data_json, created_at)
524
- values (?, ?, ?, ?, ?)
525
- `).run(randomUUID(), row.id, "server.recovered_interrupted_turn", JSON.stringify({
526
- previousStatus: row.status,
527
- reason: "server_restart",
528
- }), now);
529
- }
530
- });
531
- conversation.status = "error";
532
- conversation.updatedAt = now;
533
- upsertConversationMetadata(conversation);
534
- recoveredTurnCount += rows.length;
535
- recoveredWorkspaceIds.push(conversation.id);
536
- }
537
- return {
538
- recoveredTurnCount,
539
- recoveredWorkspaceIds: recoveredWorkspaceIds.sort(),
540
- };
541
- }
542
- export function getConversationHistoryPage(conversation, options = {}) {
543
- const limit = clampPageLimit(options.limit ?? 12);
544
- const turns = listTurnsFromDatabase(conversation, options.before, Math.max(1, Math.ceil(limit / 2)));
545
- const messages = listMessagesForTurns(conversation, turns);
546
- const oldestMessageCreatedAt = messages[0]?.createdAt ?? turns[0]?.startedAt ?? null;
547
- const events = listEventsForTurns(conversation, turns.map((turn) => turn.id));
548
- const hasMoreBefore = turns[0]?.startedAt === undefined
549
- ? false
550
- : hasTurnsBefore(conversation, turns[0].startedAt);
551
- return {
552
- messages,
553
- events,
554
- history: {
555
- hasMoreBefore,
556
- oldestMessageCreatedAt,
557
- messageTotal: messages.length,
558
- eventTotal: events.length,
559
- loadedMessageCount: messages.length,
560
- loadedEventCount: events.length,
561
- },
562
- };
563
- }
564
- export function getLatestConversationNotification(conversation, method) {
565
- const row = getWorkspaceDatabase(conversation.workspacePath)
566
- .prepare(`
567
- select * from events
568
- where event = 'codex.notification'
569
- and json_extract(data_json, '$.method') = ?
570
- order by created_at desc, id desc
571
- limit 1
572
- `)
573
- .get(method);
574
- return row ? eventFromRow(row) : null;
575
- }
576
- export function listConversationTranscriptMessages(conversation, limit = 500) {
577
- const rows = getWorkspaceDatabase(conversation.workspacePath)
578
- .prepare(`
579
- select * from messages
580
- order by created_at desc, id desc
581
- limit ?
582
- `)
583
- .all(Math.max(1, Math.min(5_000, Math.trunc(limit))));
584
- return rows
585
- .reverse()
586
- .map(messageFromRow);
587
- }
588
- export function listAllConversationVisibleMessages(conversation) {
589
- const rows = getWorkspaceDatabase(conversation.workspacePath)
590
- .prepare(`
591
- select * from messages
592
- order by created_at asc, id asc
593
- `)
594
- .all();
595
- return rows.map(messageFromRow);
596
- }
597
- export function listConversationTranscriptMessagesBefore(conversation, beforeMessageId, limit = 100) {
598
- const db = getWorkspaceDatabase(conversation.workspacePath);
599
- const normalizedLimit = Math.max(1, Math.min(500, Math.trunc(limit)));
600
- const before = beforeMessageId
601
- ? db
602
- .prepare("select created_at from messages where id = ?")
603
- .get(beforeMessageId)
604
- : null;
605
- const rows = before?.created_at
606
- ? db
607
- .prepare(`
608
- select * from messages
609
- where created_at < ?
610
- order by created_at desc, id desc
611
- limit ?
612
- `)
613
- .all(before.created_at, normalizedLimit)
614
- : db
615
- .prepare(`
616
- select * from messages
617
- order by created_at desc, id desc
618
- limit ?
619
- `)
620
- .all(normalizedLimit);
621
- return rows.reverse().map(messageFromRow);
622
- }
623
- export function listConversationTranscriptMessageRange(conversation, startMessageId, endMessageId, limit = 500) {
624
- const db = getWorkspaceDatabase(conversation.workspacePath);
625
- const start = db
626
- .prepare("select created_at from messages where id = ?")
627
- .get(startMessageId);
628
- const end = db
629
- .prepare("select created_at from messages where id = ?")
630
- .get(endMessageId);
631
- if (!start?.created_at || !end?.created_at) {
632
- return [];
633
- }
634
- const from = start.created_at <= end.created_at ? start.created_at : end.created_at;
635
- const to = start.created_at <= end.created_at ? end.created_at : start.created_at;
636
- const rows = db
637
- .prepare(`
638
- select * from messages
639
- where created_at >= ? and created_at <= ?
640
- order by created_at asc, id asc
641
- limit ?
642
- `)
643
- .all(from, to, Math.max(1, Math.min(2_000, Math.trunc(limit))));
644
- return rows.map(messageFromRow);
645
- }
646
- export function recordRuntimeReset(conversation, input) {
647
- const completedAt = input.completedAt ?? new Date().toISOString();
648
- const record = {
649
- id: randomUUID(),
650
- oldCodexThreadId: input.oldCodexThreadId,
651
- newCodexThreadId: input.newCodexThreadId,
652
- mode: input.mode ?? "lossless-visible-history",
653
- status: input.status,
654
- injectedMessageCount: input.injectedMessageCount,
655
- injectedUserMessageCount: input.injectedUserMessageCount,
656
- injectedAssistantMessageCount: input.injectedAssistantMessageCount,
657
- injectedMessages: input.injectedMessages ?? [],
658
- error: input.error ?? null,
659
- createdAt: input.createdAt ?? completedAt,
660
- completedAt,
661
- };
662
- getWorkspaceDatabase(conversation.workspacePath)
663
- .prepare(`
664
- insert into runtime_resets (
665
- id,
666
- old_codex_thread_id,
667
- new_codex_thread_id,
668
- mode,
669
- status,
670
- injected_message_count,
671
- injected_user_message_count,
672
- injected_assistant_message_count,
673
- injected_messages_json,
674
- error,
675
- created_at,
676
- completed_at
677
- )
678
- values (
679
- @id,
680
- @oldCodexThreadId,
681
- @newCodexThreadId,
682
- @mode,
683
- @status,
684
- @injectedMessageCount,
685
- @injectedUserMessageCount,
686
- @injectedAssistantMessageCount,
687
- @injectedMessagesJson,
688
- @error,
689
- @createdAt,
690
- @completedAt
691
- )
692
- `)
693
- .run({
694
- ...record,
695
- injectedMessagesJson: JSON.stringify(record.injectedMessages),
696
- });
697
- return record;
698
- }
699
- export function listRuntimeResets(conversation, limit = 10) {
700
- const rows = getWorkspaceDatabase(conversation.workspacePath)
701
- .prepare(`
702
- select * from runtime_resets
703
- order by created_at desc, id desc
704
- limit ?
705
- `)
706
- .all(Math.max(1, Math.min(50, Math.trunc(limit))));
707
- return rows.map(runtimeResetFromRow);
708
- }
709
- export function upsertTranscriptRangeSummary(conversation, input) {
710
- const now = new Date().toISOString();
711
- const id = `summary-${input.startMessageId}-${input.endMessageId}`;
712
- const summary = {
713
- id,
714
- startMessageId: input.startMessageId,
715
- endMessageId: input.endMessageId,
716
- title: input.title,
717
- summary: input.summary,
718
- createdAt: now,
719
- updatedAt: now,
720
- };
721
- getWorkspaceDatabase(conversation.workspacePath)
722
- .prepare(`
723
- insert into transcript_range_summaries (
724
- id,
725
- start_message_id,
726
- end_message_id,
727
- title,
728
- summary,
729
- created_at,
730
- updated_at
731
- )
732
- values (@id, @startMessageId, @endMessageId, @title, @summary, @createdAt, @updatedAt)
733
- on conflict(id) do update set
734
- title = excluded.title,
735
- summary = excluded.summary,
736
- updated_at = excluded.updated_at
737
- `)
738
- .run(summary);
739
- return summary;
740
- }
741
- export function listTranscriptRangeSummaries(conversation) {
742
- const rows = getWorkspaceDatabase(conversation.workspacePath)
743
- .prepare(`
744
- select * from transcript_range_summaries
745
- order by created_at asc, id asc
746
- `)
747
- .all();
748
- return rows.map(transcriptRangeSummaryFromRow);
749
- }
750
- export function getConversationRevision(conversation) {
751
- return {
752
- id: conversation.id,
753
- status: conversation.status,
754
- updatedAt: conversation.updatedAt,
755
- messageTotal: countWorkspaceRows(conversation.workspacePath, "messages"),
756
- eventTotal: countWorkspaceRows(conversation.workspacePath, "events"),
757
- nextWakeup: getNextScheduledWorkspaceWakeup(conversation.id),
758
- };
759
- }
760
- export function listConversationTurnEvents(conversation, turnId) {
761
- return listEventsForTurns(conversation, [turnId], {
762
- compact: false,
763
- });
764
- }
765
- export function listConversationEventsByType(conversation, eventTypes, limit = 100) {
766
- if (eventTypes.length === 0) {
767
- return [];
768
- }
769
- const placeholders = eventTypes.map(() => "?").join(", ");
770
- const normalizedLimit = Math.max(1, Math.min(500, Math.floor(limit)));
771
- const rows = getWorkspaceDatabase(conversation.workspacePath)
772
- .prepare(`
773
- select * from events
774
- where event in (${placeholders})
775
- order by created_at desc, id desc
776
- limit ?
777
- `)
778
- .all(...eventTypes, normalizedLimit);
779
- return rows.map(eventFromRow);
780
- }
781
- export function getConversationTurn(conversation, turnId) {
782
- return getTurn(conversation, turnId);
783
- }
784
- export function deleteConversation(id) {
785
- const conversation = conversations.get(id);
786
- if (!conversation) {
787
- return null;
788
- }
789
- ensureWorkspacePathIsManaged(conversation.workspacePath);
790
- moveWorkspaceToRecycle(conversation);
791
- conversations.delete(id);
792
- getCenterDatabase().prepare("delete from workspaces where id = ?").run(id);
793
- getCenterDatabase()
794
- .prepare("delete from workspace_read_states where workspace_id = ?")
795
- .run(id);
796
- getCenterDatabase()
797
- .prepare("delete from workspace_unread_baselines where workspace_id = ?")
798
- .run(id);
799
- const workspaceDb = workspaceDatabases.get(conversation.workspacePath);
800
- if (workspaceDb) {
801
- workspaceDb.close();
802
- workspaceDatabases.delete(conversation.workspacePath);
803
- }
804
- return conversation;
805
- }
806
- export function createConversation(input) {
807
- const now = new Date().toISOString();
808
- const id = createWorkspaceId();
809
- const workspacePath = path.join(input.workspaceRoot, id);
810
- mkdirSync(workspacePath, { recursive: true });
811
- const contextVersion = {
812
- id: randomUUID(),
813
- codexThreadId: null,
814
- source: "initial",
815
- summary: null,
816
- createdAt: now,
817
- };
818
- const conversation = {
819
- id,
820
- ownerId: input.ownerId,
821
- title: input.title ?? "New workspace",
822
- groupId: normalizeWorkspaceGroupId(input.groupId),
823
- memory: "",
824
- memoryItems: [],
825
- responsibility: "",
826
- codexThreadId: null,
827
- workspaceKind: "directory",
828
- workspacePath,
829
- sandbox: input.sandbox,
830
- contextVersions: [contextVersion],
831
- status: "idle",
832
- messages: [],
833
- events: [],
834
- createdAt: now,
835
- updatedAt: now,
836
- };
837
- conversations.set(conversation.id, conversation);
838
- initializeWorkspaceDatabase(workspacePath);
839
- upsertConversationMetadata(conversation);
840
- getCenterDatabase()
841
- .prepare(`
842
- insert into workspace_unread_baselines (
843
- workspace_id, terminal_turn_rowid
844
- ) values (?, 0)
845
- `)
846
- .run(conversation.id);
847
- upsertContextVersion(conversation, contextVersion);
848
- return conversation;
849
- }
850
- function createWorkspaceId() {
851
- for (let attempts = 0; attempts < 8; attempts += 1) {
852
- const id = randomInt(36 ** 6).toString(36).padStart(6, "0");
853
- if (!conversations.has(id)) {
854
- return id;
855
- }
856
- }
857
- return randomUUID();
858
- }
859
- export function appendMessage(conversation, role, content, turnId = null) {
860
- const createdAt = new Date().toISOString();
861
- const message = {
862
- id: createMessageId(createdAt),
863
- role,
864
- content,
865
- createdAt,
866
- turnId,
867
- };
868
- conversation.messages.push(message);
869
- insertMessage(conversation, message);
870
- touch(conversation);
871
- return message;
872
- }
873
- export function createConversationTurn(conversation, input) {
874
- const now = new Date().toISOString();
875
- const turn = {
876
- id: input.id,
877
- userMessageId: input.userMessageId ?? null,
878
- codexTurnId: null,
879
- status: input.status ?? "queued",
880
- startedAt: now,
881
- finishedAt: null,
882
- error: null,
883
- createdAt: now,
884
- updatedAt: now,
885
- };
886
- upsertTurn(conversation, turn);
887
- return turn;
888
- }
889
- export function updateConversationTurn(conversation, turnId, input) {
890
- const existing = getTurn(conversation, turnId);
891
- const now = new Date().toISOString();
892
- const turn = {
893
- id: turnId,
894
- userMessageId: existing?.userMessageId ?? null,
895
- codexTurnId: Object.prototype.hasOwnProperty.call(input, "codexTurnId")
896
- ? input.codexTurnId ?? null
897
- : existing?.codexTurnId ?? null,
898
- status: input.status ?? existing?.status ?? "running",
899
- startedAt: existing?.startedAt ?? now,
900
- finishedAt: Object.prototype.hasOwnProperty.call(input, "finishedAt")
901
- ? input.finishedAt ?? null
902
- : existing?.finishedAt ?? null,
903
- error: Object.prototype.hasOwnProperty.call(input, "error")
904
- ? input.error ?? null
905
- : existing?.error ?? null,
906
- createdAt: existing?.createdAt ?? now,
907
- updatedAt: now,
908
- };
909
- upsertTurn(conversation, turn);
910
- }
911
- export function appendEvent(conversation, event, data, options = {}) {
912
- const record = {
913
- id: randomUUID(),
914
- turnId: options.turnId ?? null,
915
- event,
916
- data,
917
- createdAt: options.createdAt ?? new Date().toISOString(),
918
- };
919
- conversation.events.push(record);
920
- trimInMemoryEvents(conversation);
921
- insertEvent(conversation, record, options.turnId ?? null);
922
- touch(conversation, { persistMetadata: false });
923
- return record;
924
- }
925
- export function setConversationStatus(conversation, status) {
926
- conversation.status = status;
927
- touch(conversation);
928
- }
929
- export function setConversationTitle(conversation, title) {
930
- conversation.title = title;
931
- touch(conversation);
932
- }
933
- export function setConversationSandbox(conversation, sandbox) {
934
- conversation.sandbox = sandbox;
935
- touch(conversation);
936
- }
937
- export function setConversationGroup(conversation, groupId) {
938
- conversation.groupId = normalizeWorkspaceGroupId(groupId);
939
- touch(conversation);
940
- }
941
- export function setConversationThread(conversation, codexThreadId) {
942
- conversation.codexThreadId = codexThreadId;
943
- touch(conversation);
944
- }
945
- export function appendConversationContextVersion(conversation, input) {
946
- const contextVersion = {
947
- id: randomUUID(),
948
- codexThreadId: input.codexThreadId,
949
- source: input.source,
950
- summary: input.summary,
951
- createdAt: new Date().toISOString(),
952
- };
953
- conversation.contextVersions.push(contextVersion);
954
- upsertContextVersion(conversation, contextVersion);
955
- return contextVersion;
956
- }
957
- function clampPageLimit(value) {
958
- if (!Number.isFinite(value)) {
959
- return 12;
960
- }
961
- return Math.max(1, Math.min(40, Math.floor(value)));
962
- }
963
- function toSummary(conversation) {
964
- return {
965
- id: conversation.id,
966
- ownerId: conversation.ownerId,
967
- title: conversation.title,
968
- groupId: conversation.groupId,
969
- memory: conversation.memory,
970
- memoryItems: conversation.memoryItems,
971
- responsibility: conversation.responsibility,
972
- status: conversation.status,
973
- codexThreadId: conversation.codexThreadId,
974
- workspaceKind: conversation.workspaceKind,
975
- workspacePath: conversation.workspacePath,
976
- sandbox: conversation.sandbox,
977
- contextVersionCount: conversation.contextVersions.length,
978
- nextWakeup: getNextScheduledWorkspaceWakeup(conversation.id),
979
- createdAt: conversation.createdAt,
980
- updatedAt: conversation.updatedAt,
981
- };
982
- }
983
- export function isConversationSandbox(value) {
984
- return (value === "read-only" ||
985
- value === "workspace-write" ||
986
- value === "danger-full-access");
987
- }
988
- function normalizeSandbox(value, fallback) {
989
- return isConversationSandbox(value) ? value : fallback;
990
- }
991
- function normalizeWorkspaceGroupId(value) {
992
- return typeof value === "string" && workspaceGroups.has(value)
993
- ? value
994
- : DEFAULT_WORKSPACE_GROUP_ID;
995
- }
996
- function createWorkspaceGroupId(title) {
997
- const base = title
998
- .trim()
999
- .toLowerCase()
1000
- .replace(/[^a-z0-9]+/g, "-")
1001
- .replace(/^-+|-+$/g, "")
1002
- .slice(0, 24) || "group";
1003
- let id = base === DEFAULT_WORKSPACE_GROUP_ID ? `${base}-2` : base;
1004
- let suffix = 2;
1005
- while (workspaceGroups.has(id)) {
1006
- id = `${base}-${suffix}`;
1007
- suffix += 1;
1008
- }
1009
- return id;
1010
- }
1011
- function touch(conversation, options = {}) {
1012
- conversation.updatedAt = new Date().toISOString();
1013
- if (options.persistMetadata !== false) {
1014
- updateConversationMetadata(conversation);
1015
- }
1016
- }
1017
- function openCenterDatabase(databasePath) {
1018
- mkdirSync(path.dirname(databasePath), { recursive: true });
1019
- const db = openSqliteDatabase(databasePath);
1020
- db.exec("pragma journal_mode = WAL; pragma foreign_keys = ON");
1021
- return db;
1022
- }
1023
- function getCenterDatabase() {
1024
- if (!centerDatabase) {
1025
- throw new Error("conversation store is not initialized");
1026
- }
1027
- return centerDatabase;
1028
- }
1029
- function initializeCenterSchema(db) {
1030
- db.exec(`
1031
- create table if not exists schema_migrations (
1032
- id integer primary key check (id = 1),
1033
- version integer not null,
1034
- updated_at text not null
1035
- );
1036
-
1037
- create table if not exists workspace_groups (
1038
- id text primary key,
1039
- owner_id text,
1040
- title text not null,
1041
- created_at text not null,
1042
- updated_at text not null
1043
- );
1044
-
1045
- create table if not exists global_environment_variables (
1046
- key text primary key,
1047
- value text not null,
1048
- description text not null default '',
1049
- created_at text not null,
1050
- updated_at text not null
1051
- );
1052
-
1053
- create table if not exists app_settings (
1054
- key text primary key,
1055
- value text not null,
1056
- updated_at text not null
1057
- );
1058
-
1059
- create table if not exists workspaces (
1060
- id text primary key,
1061
- owner_id text,
1062
- title text not null,
1063
- group_id text not null,
1064
- codex_thread_id text,
1065
- workspace_kind text not null,
1066
- workspace_path text not null unique,
1067
- sandbox text not null,
1068
- status text not null,
1069
- created_at text not null,
1070
- updated_at text not null
1071
- );
1072
-
1073
- create index if not exists idx_workspaces_group_updated
1074
- on workspaces(group_id, updated_at);
1075
-
1076
- create table if not exists workspace_wakeups (
1077
- id text primary key,
1078
- workspace_id text not null,
1079
- status text not null,
1080
- wake_at text not null,
1081
- instruction text not null,
1082
- created_by text not null,
1083
- created_turn_id text,
1084
- result_turn_id text,
1085
- error text,
1086
- created_at text not null,
1087
- updated_at text not null
1088
- );
1089
-
1090
- create table if not exists workspace_unread_baselines (
1091
- workspace_id text primary key,
1092
- terminal_turn_rowid integer not null
1093
- );
1094
-
1095
- create table if not exists workspace_read_states (
1096
- user_id text not null,
1097
- workspace_id text not null,
1098
- last_read_turn_rowid integer not null,
1099
- updated_at text not null,
1100
- primary key (user_id, workspace_id)
1101
- );
1102
-
1103
- create index if not exists idx_workspace_read_states_workspace
1104
- on workspace_read_states(workspace_id);
1105
-
1106
- create index if not exists idx_workspace_wakeups_status_wake_at
1107
- on workspace_wakeups(status, wake_at);
1108
- create index if not exists idx_workspace_wakeups_workspace_wake_at
1109
- on workspace_wakeups(workspace_id, wake_at);
1110
- `);
1111
- migrateCenterWorkspaceRuntimeSchema(db);
1112
- ensureColumn(db, "global_environment_variables", "description", "text not null default ''");
1113
- ensureColumn(db, "workspace_wakeups", "attempt_count", "integer not null default 0");
1114
- ensureColumn(db, "workspace_wakeups", "max_attempts", "integer not null default 3");
1115
- ensureColumn(db, "workspace_groups", "owner_id", "text");
1116
- ensureColumn(db, "workspaces", "owner_id", "text");
1117
- if (!defaultOwnerId) {
1118
- throw new Error("default workspace owner is not initialized");
1119
- }
1120
- db.prepare("update workspaces set owner_id = ? where owner_id is null or owner_id = ''")
1121
- .run(defaultOwnerId);
1122
- db.prepare("update workspaces set owner_id = ? where owner_id = 'local-admin'")
1123
- .run(defaultOwnerId);
1124
- db.prepare("update workspace_groups set owner_id = ? where id <> ? and owner_id is null")
1125
- .run(defaultOwnerId, DEFAULT_WORKSPACE_GROUP_ID);
1126
- db.prepare("update workspace_groups set owner_id = ? where owner_id = 'local-admin'")
1127
- .run(defaultOwnerId);
1128
- setSchemaVersion(db, CENTER_SCHEMA_VERSION);
1129
- }
1130
- function initializeWorkspaceSchema(db) {
1131
- db.exec(`
1132
- create table if not exists schema_migrations (
1133
- id integer primary key check (id = 1),
1134
- version integer not null,
1135
- updated_at text not null
1136
- );
1137
-
1138
- create table if not exists context_versions (
1139
- id text primary key,
1140
- codex_thread_id text,
1141
- source text not null,
1142
- summary text,
1143
- created_at text not null
1144
- );
1145
-
1146
- create table if not exists messages (
1147
- id text primary key,
1148
- role text not null,
1149
- content text not null,
1150
- turn_id text,
1151
- created_at text not null
1152
- );
1153
-
1154
- create table if not exists events (
1155
- id text primary key,
1156
- turn_id text,
1157
- event text not null,
1158
- data_json text not null,
1159
- created_at text not null
1160
- );
1161
-
1162
- create table if not exists turns (
1163
- id text primary key,
1164
- user_message_id text,
1165
- codex_turn_id text,
1166
- status text not null,
1167
- started_at text not null,
1168
- finished_at text,
1169
- error text,
1170
- created_at text not null,
1171
- updated_at text not null
1172
- );
1173
-
1174
- create table if not exists workspace_settings (
1175
- key text primary key,
1176
- value text not null,
1177
- updated_at text not null
1178
- );
1179
-
1180
- create table if not exists workspace_knowledge (
1181
- id text primary key,
1182
- data_json text not null,
1183
- created_at text not null,
1184
- updated_at text not null
1185
- );
1186
-
1187
- create table if not exists transcript_range_summaries (
1188
- id text primary key,
1189
- start_message_id text not null,
1190
- end_message_id text not null,
1191
- title text not null,
1192
- summary text not null,
1193
- created_at text not null,
1194
- updated_at text not null
1195
- );
1196
-
1197
- create table if not exists runtime_resets (
1198
- id text primary key,
1199
- old_codex_thread_id text,
1200
- new_codex_thread_id text,
1201
- mode text not null,
1202
- status text not null,
1203
- injected_message_count integer not null,
1204
- injected_user_message_count integer not null,
1205
- injected_assistant_message_count integer not null,
1206
- injected_messages_json text not null default '[]',
1207
- error text,
1208
- created_at text not null,
1209
- completed_at text not null
1210
- );
1211
-
1212
- create index if not exists idx_messages_created_at on messages(created_at);
1213
- create index if not exists idx_events_created_at on events(created_at);
1214
- create index if not exists idx_turns_user_message on turns(user_message_id);
1215
- create index if not exists idx_turns_status_updated_at on turns(status, updated_at);
1216
- create index if not exists idx_workspace_knowledge_updated_at
1217
- on workspace_knowledge(updated_at);
1218
- create index if not exists idx_transcript_range_summaries_start
1219
- on transcript_range_summaries(start_message_id);
1220
- create index if not exists idx_transcript_range_summaries_updated_at
1221
- on transcript_range_summaries(updated_at);
1222
- create index if not exists idx_runtime_resets_created_at
1223
- on runtime_resets(created_at);
1224
- `);
1225
- ensureColumn(db, "messages", "turn_id", "text");
1226
- ensureColumn(db, "events", "turn_id", "text");
1227
- ensureColumn(db, "runtime_resets", "injected_messages_json", "text not null default '[]'");
1228
- db.exec(`
1229
- create index if not exists idx_messages_turn_created_at on messages(turn_id, created_at);
1230
- create index if not exists idx_events_turn_created_at on events(turn_id, created_at);
1231
- create index if not exists idx_transcript_range_summaries_start
1232
- on transcript_range_summaries(start_message_id);
1233
- create index if not exists idx_transcript_range_summaries_updated_at
1234
- on transcript_range_summaries(updated_at);
1235
- create index if not exists idx_runtime_resets_created_at
1236
- on runtime_resets(created_at);
1237
- `);
1238
- setSchemaVersion(db, WORKSPACE_SCHEMA_VERSION);
1239
- }
1240
- export function getConversationWorkspaceDatabase(conversation) {
1241
- return getWorkspaceDatabase(conversation.workspacePath);
1242
- }
1243
- function setSchemaVersion(db, version) {
1244
- db.prepare(`
1245
- insert into schema_migrations (id, version, updated_at)
1246
- values (1, ?, ?)
1247
- on conflict(id) do update set
1248
- version = excluded.version,
1249
- updated_at = excluded.updated_at
1250
- `).run(version, new Date().toISOString());
1251
- }
1252
- function readSchemaVersion(db) {
1253
- const table = db
1254
- .prepare("select name from sqlite_master where type = ? and name = ?")
1255
- .get("table", "schema_migrations");
1256
- if (!table) {
1257
- return null;
1258
- }
1259
- const row = db
1260
- .prepare("select version from schema_migrations where id = 1")
1261
- .get();
1262
- return typeof row?.version === "number" ? row.version : null;
1263
- }
1264
- function ensureColumn(db, table, column, definition) {
1265
- const columns = db.prepare(`pragma table_info(${table})`).all();
1266
- if (columns.some((entry) => entry.name === column)) {
1267
- return;
1268
- }
1269
- db.prepare(`alter table ${table} add column ${column} ${definition}`).run();
1270
- }
1271
- function tableHasColumn(db, table, column) {
1272
- const columns = db.prepare(`pragma table_info(${table})`).all();
1273
- return columns.some((entry) => entry.name === column);
1274
- }
1275
- function migrateCenterWorkspaceRuntimeSchema(db) {
1276
- if (!tableHasColumn(db, "workspaces", "current_context_version_id")) {
1277
- return;
1278
- }
1279
- runInTransaction(db, () => {
1280
- db.exec(`
1281
- drop index if exists idx_workspaces_group_updated;
1282
-
1283
- create table workspaces_next (
1284
- id text primary key,
1285
- title text not null,
1286
- group_id text not null,
1287
- codex_thread_id text,
1288
- workspace_kind text not null,
1289
- workspace_path text not null unique,
1290
- sandbox text not null,
1291
- status text not null,
1292
- created_at text not null,
1293
- updated_at text not null
1294
- );
1295
-
1296
- insert into workspaces_next (
1297
- id,
1298
- title,
1299
- group_id,
1300
- codex_thread_id,
1301
- workspace_kind,
1302
- workspace_path,
1303
- sandbox,
1304
- status,
1305
- created_at,
1306
- updated_at
1307
- )
1308
- select
1309
- id,
1310
- title,
1311
- group_id,
1312
- codex_thread_id,
1313
- workspace_kind,
1314
- workspace_path,
1315
- sandbox,
1316
- status,
1317
- created_at,
1318
- updated_at
1319
- from workspaces;
1320
-
1321
- drop table workspaces;
1322
- alter table workspaces_next rename to workspaces;
1323
-
1324
- create index if not exists idx_workspaces_group_updated
1325
- on workspaces(group_id, updated_at);
1326
- `);
1327
- });
1328
- }
1329
- function upsertWorkspaceSetting(conversation, key, value) {
1330
- return upsertWorkspaceSettingByPath(conversation.workspacePath, key, value);
1331
- }
1332
- function upsertWorkspaceSettingByPath(workspacePath, key, value) {
1333
- const now = new Date().toISOString();
1334
- getWorkspaceDatabase(workspacePath)
1335
- .prepare(`
1336
- insert into workspace_settings (key, value, updated_at)
1337
- values (?, ?, ?)
1338
- on conflict(key) do update set
1339
- value = excluded.value,
1340
- updated_at = excluded.updated_at
1341
- `)
1342
- .run(key, value, now);
1343
- return { key, value, updatedAt: now };
1344
- }
1345
- function migrateMessageIds(db) {
1346
- const rows = db
1347
- .prepare(`
1348
- select id, role, content, created_at
1349
- from messages
1350
- order by created_at asc, id asc
1351
- `)
1352
- .all();
1353
- if (rows.length === 0) {
1354
- return;
1355
- }
1356
- const usedIds = new Set(rows.map((row) => row.id));
1357
- const replacements = new Map();
1358
- for (let index = 0; index < rows.length; index += 1) {
1359
- const row = rows[index];
1360
- if (isCanonicalMessageId(row.id)) {
1361
- continue;
1362
- }
1363
- usedIds.delete(row.id);
1364
- let nextId = createStableMessageId(row.created_at, `${row.id}:${index}:${row.role}:${row.content}`);
1365
- for (let attempt = 1; usedIds.has(nextId); attempt += 1) {
1366
- nextId = createStableMessageId(row.created_at, `${row.id}:${index}:${row.role}:${row.content}:${attempt}`);
1367
- }
1368
- usedIds.add(nextId);
1369
- replacements.set(row.id, nextId);
1370
- }
1371
- if (replacements.size === 0) {
1372
- return;
1373
- }
1374
- const updateMessage = db.prepare("update messages set id = ? where id = ?");
1375
- const updateTurn = db.prepare(`
1376
- update turns
1377
- set user_message_id = ?
1378
- where user_message_id = ?
1379
- `);
1380
- const updateSummaryStart = db.prepare(`
1381
- update transcript_range_summaries
1382
- set start_message_id = ?
1383
- where start_message_id = ?
1384
- `);
1385
- const updateSummaryEnd = db.prepare(`
1386
- update transcript_range_summaries
1387
- set end_message_id = ?
1388
- where end_message_id = ?
1389
- `);
1390
- runInTransaction(db, () => {
1391
- for (const [oldId, nextId] of replacements) {
1392
- updateMessage.run(nextId, oldId);
1393
- updateTurn.run(nextId, oldId);
1394
- updateSummaryStart.run(nextId, oldId);
1395
- updateSummaryEnd.run(nextId, oldId);
1396
- }
1397
- });
1398
- }
1399
- function migrateMessageTurnIds(db) {
1400
- runInTransaction(db, () => {
1401
- db.exec(`
1402
- update messages
1403
- set turn_id = (
1404
- select turns.id
1405
- from turns
1406
- where turns.user_message_id = messages.id
1407
- limit 1
1408
- )
1409
- where turn_id is null
1410
- and role = 'user'
1411
- and exists (
1412
- select 1 from turns where turns.user_message_id = messages.id
1413
- );
1414
-
1415
- update messages
1416
- set turn_id = (
1417
- select events.turn_id
1418
- from events
1419
- where events.event = 'server.steer'
1420
- and events.turn_id is not null
1421
- and json_extract(events.data_json, '$.messageId') = messages.id
1422
- order by events.created_at asc
1423
- limit 1
1424
- )
1425
- where role = 'user'
1426
- and exists (
1427
- select 1
1428
- from events
1429
- where events.event = 'server.steer'
1430
- and events.turn_id is not null
1431
- and json_extract(events.data_json, '$.messageId') = messages.id
1432
- );
1433
-
1434
- update messages
1435
- set turn_id = (
1436
- select turns.id
1437
- from turns
1438
- where turns.finished_at is not null
1439
- and turns.finished_at >= messages.created_at
1440
- order by turns.finished_at asc
1441
- limit 1
1442
- )
1443
- where turn_id is null
1444
- and role = 'assistant';
1445
-
1446
- delete from turns
1447
- where id like 'legacy-msg_%'
1448
- and exists (
1449
- select 1
1450
- from messages
1451
- where messages.id = turns.user_message_id
1452
- and messages.turn_id is not null
1453
- and messages.turn_id != turns.id
1454
- );
1455
- `);
1456
- });
1457
- }
1458
- function migrateWorkspaceTurns(db) {
1459
- const userMessages = db
1460
- .prepare(`
1461
- select id, created_at
1462
- from messages
1463
- where role = 'user' and turn_id is null
1464
- order by created_at asc, id asc
1465
- `)
1466
- .all();
1467
- if (userMessages.length === 0) {
1468
- return;
1469
- }
1470
- const existingTurnByMessageId = new Map();
1471
- for (const row of db
1472
- .prepare(`
1473
- select id, user_message_id
1474
- from turns
1475
- where user_message_id is not null
1476
- `)
1477
- .all()) {
1478
- existingTurnByMessageId.set(row.user_message_id, row.id);
1479
- }
1480
- const insertLegacyTurn = db.prepare(`
1481
- insert or ignore into turns (
1482
- id,
1483
- user_message_id,
1484
- codex_turn_id,
1485
- status,
1486
- started_at,
1487
- finished_at,
1488
- error,
1489
- created_at,
1490
- updated_at
1491
- )
1492
- values (?, ?, null, 'completed', ?, ?, null, ?, ?)
1493
- `);
1494
- const updateEventsWithEnd = db.prepare(`
1495
- update events
1496
- set turn_id = ?
1497
- where turn_id is null
1498
- and created_at >= ?
1499
- and created_at < ?
1500
- `);
1501
- const updateEventsWithoutEnd = db.prepare(`
1502
- update events
1503
- set turn_id = ?
1504
- where turn_id is null
1505
- and created_at >= ?
1506
- `);
1507
- const maxCreatedAtWithEnd = db.prepare(`
1508
- select max(created_at) as value
1509
- from (
1510
- select created_at from messages where created_at >= ? and created_at < ?
1511
- union all
1512
- select created_at from events where created_at >= ? and created_at < ?
1513
- )
1514
- `);
1515
- const maxCreatedAtWithoutEnd = db.prepare(`
1516
- select max(created_at) as value
1517
- from (
1518
- select created_at from messages where created_at >= ?
1519
- union all
1520
- select created_at from events where created_at >= ?
1521
- )
1522
- `);
1523
- runInTransaction(db, () => {
1524
- for (let index = 0; index < userMessages.length; index += 1) {
1525
- const message = userMessages[index];
1526
- const nextMessage = userMessages[index + 1] ?? null;
1527
- const turnId = existingTurnByMessageId.get(message.id) ?? `legacy-${message.id}`;
1528
- const finishedAt = nextMessage
1529
- ? maxCreatedAtWithEnd.get(message.created_at, nextMessage.created_at, message.created_at, nextMessage.created_at).value
1530
- : maxCreatedAtWithoutEnd.get(message.created_at, message.created_at).value;
1531
- const completedAt = finishedAt ?? message.created_at;
1532
- insertLegacyTurn.run(turnId, message.id, message.created_at, completedAt, message.created_at, completedAt);
1533
- if (nextMessage) {
1534
- updateEventsWithEnd.run(turnId, message.created_at, nextMessage.created_at);
1535
- }
1536
- else {
1537
- updateEventsWithoutEnd.run(turnId, message.created_at);
1538
- }
1539
- }
1540
- });
1541
- }
1542
- function initializeWorkspaceDatabase(workspacePath) {
1543
- const db = getWorkspaceDatabase(workspacePath);
1544
- initializeWorkspaceSchema(db);
1545
- migrateMessageIds(db);
1546
- migrateMessageTurnIds(db);
1547
- migrateWorkspaceTurns(db);
1548
- return db;
1549
- }
1550
- function getWorkspaceDatabase(workspacePath) {
1551
- const existing = workspaceDatabases.get(workspacePath);
1552
- if (existing) {
1553
- return existing;
1554
- }
1555
- const databasePath = getWorkspaceDatabasePath(workspacePath);
1556
- mkdirSync(path.dirname(databasePath), { recursive: true });
1557
- const db = openSqliteDatabase(databasePath);
1558
- db.exec("pragma journal_mode = WAL");
1559
- initializeWorkspaceSchema(db);
1560
- migrateMessageIds(db);
1561
- migrateMessageTurnIds(db);
1562
- migrateWorkspaceTurns(db);
1563
- workspaceDatabases.set(workspacePath, db);
1564
- return db;
1565
- }
1566
- function migrateLegacyWorkspaceGroups(workspaceRoot) {
1567
- const groupsPath = getWorkspaceGroupsPath(workspaceRoot);
1568
- if (existsSync(groupsPath)) {
1569
- const data = JSON.parse(readFileSync(groupsPath, "utf8"));
1570
- for (const group of data.groups ?? []) {
1571
- if (group &&
1572
- typeof group.id === "string" &&
1573
- typeof group.title === "string" &&
1574
- group.id &&
1575
- group.title.trim()) {
1576
- upsertWorkspaceGroup({
1577
- id: group.id,
1578
- ownerId: defaultOwnerId,
1579
- title: group.title.trim(),
1580
- createdAt: typeof group.createdAt === "string"
1581
- ? group.createdAt
1582
- : new Date().toISOString(),
1583
- updatedAt: typeof group.updatedAt === "string"
1584
- ? group.updatedAt
1585
- : new Date().toISOString(),
1586
- });
1587
- }
1588
- }
1589
- archiveLegacyFile(groupsPath);
1590
- }
1591
- ensureDefaultWorkspaceGroup();
1592
- }
1593
- function migrateLegacyWorkspace(workspaceRoot, workspacePath, defaultSandbox) {
1594
- const metadataPath = getWorkspaceMetadataPath(workspacePath);
1595
- if (!existsSync(metadataPath)) {
1596
- return;
1597
- }
1598
- const data = JSON.parse(readFileSync(metadataPath, "utf8"));
1599
- if (!data.conversation) {
1600
- return;
1601
- }
1602
- const legacy = data.conversation;
1603
- if (workspaceExistsInCenter(legacy.id, workspacePath)) {
1604
- archiveLegacyFile(metadataPath);
1605
- return;
1606
- }
1607
- const conversation = {
1608
- ...legacy,
1609
- ownerId: legacy.ownerId || requireDefaultOwnerId(),
1610
- groupId: normalizeWorkspaceGroupId(legacy.groupId),
1611
- memory: typeof legacy.memory === "string" ? legacy.memory : "",
1612
- responsibility: typeof legacy.responsibility === "string" ? legacy.responsibility : "",
1613
- workspacePath,
1614
- sandbox: normalizeSandbox(legacy.sandbox, defaultSandbox),
1615
- status: legacy.status === "running" ? "error" : legacy.status,
1616
- messages: normalizeLegacyMessages(legacy.messages),
1617
- events: Array.isArray(legacy.events) ? legacy.events : [],
1618
- contextVersions: Array.isArray(legacy.contextVersions)
1619
- ? legacy.contextVersions
1620
- : [],
1621
- };
1622
- if (!conversation.contextVersions.length) {
1623
- const now = conversation.createdAt || new Date().toISOString();
1624
- const contextVersion = {
1625
- id: randomUUID(),
1626
- codexThreadId: conversation.codexThreadId,
1627
- source: "initial",
1628
- summary: null,
1629
- createdAt: now,
1630
- };
1631
- conversation.contextVersions = [contextVersion];
1632
- }
1633
- if (!path.resolve(workspacePath).startsWith(path.resolve(workspaceRoot))) {
1634
- return;
1635
- }
1636
- upsertConversationMetadata(conversation);
1637
- const workspaceDb = initializeWorkspaceDatabase(workspacePath);
1638
- if (countRows(workspaceDb, "context_versions") === 0) {
1639
- for (const version of conversation.contextVersions) {
1640
- upsertContextVersion(conversation, version);
1641
- }
1642
- }
1643
- if (countRows(workspaceDb, "messages") === 0) {
1644
- for (const message of conversation.messages) {
1645
- insertMessage(conversation, message);
1646
- }
1647
- }
1648
- if (countRows(workspaceDb, "events") === 0) {
1649
- for (const event of conversation.events) {
1650
- insertEvent(conversation, event);
1651
- }
1652
- }
1653
- migrateWorkspaceTurns(workspaceDb);
1654
- migrateMessageTurnIds(workspaceDb);
1655
- archiveLegacyFile(metadataPath);
1656
- }
1657
- function archiveLegacyFile(filePath) {
1658
- if (!existsSync(filePath)) {
1659
- return;
1660
- }
1661
- const archiveDir = path.join(path.dirname(filePath), LEGACY_MIGRATED_DIR_NAME);
1662
- mkdirSync(archiveDir, { recursive: true });
1663
- const archivePath = path.join(archiveDir, path.basename(filePath));
1664
- if (existsSync(archivePath)) {
1665
- const parsed = path.parse(filePath);
1666
- renameSync(filePath, path.join(archiveDir, `${parsed.name}-${new Date().toISOString().replace(/[:.]/g, "")}${parsed.ext}`));
1667
- return;
1668
- }
1669
- renameSync(filePath, archivePath);
1670
- }
1671
- function workspaceExistsInCenter(id, workspacePath) {
1672
- if (typeof id !== "string" || !id) {
1673
- return false;
1674
- }
1675
- const row = getCenterDatabase()
1676
- .prepare("select id from workspaces where id = ? or workspace_path = ? limit 1")
1677
- .get(id, workspacePath);
1678
- return Boolean(row);
1679
- }
1680
- function normalizeLegacyMessages(messages) {
1681
- if (!Array.isArray(messages)) {
1682
- return [];
1683
- }
1684
- return messages
1685
- .map((message, index) => {
1686
- if (!isLegacyMessageRecord(message)) {
1687
- return null;
1688
- }
1689
- const role = message.role === "assistant" ? "assistant" : "user";
1690
- const content = typeof message.content === "string" ? message.content : "";
1691
- const createdAt = typeof message.createdAt === "string" && message.createdAt.trim()
1692
- ? message.createdAt
1693
- : new Date(0).toISOString();
1694
- const id = typeof message.id === "string" && message.id.trim()
1695
- ? normalizeMessageId(message.id, createdAt, `${index}:${role}:${content}`)
1696
- : createStableMessageId(createdAt, `${index}:${role}:${content}`);
1697
- return {
1698
- id,
1699
- role,
1700
- content,
1701
- createdAt,
1702
- turnId: null,
1703
- };
1704
- })
1705
- .filter((message) => message !== null);
1706
- }
1707
- function isLegacyMessageRecord(value) {
1708
- return value !== null && typeof value === "object" && !Array.isArray(value);
1709
- }
1710
- function normalizeMessageId(id, createdAt, seed) {
1711
- const trimmed = id.trim();
1712
- return isCanonicalMessageId(trimmed)
1713
- ? trimmed
1714
- : createStableMessageId(createdAt, `${trimmed}:${seed}`);
1715
- }
1716
- function createMessageId(createdAt) {
1717
- return `msg_${formatMessageIdTimestamp(createdAt)}_${randomMessageSuffix()}`;
1718
- }
1719
- function createStableMessageId(createdAt, seed) {
1720
- return `msg_${formatMessageIdTimestamp(createdAt)}_${shortHash(seed)}`;
1721
- }
1722
- function isCanonicalMessageId(id) {
1723
- return /^msg_\d{8}_\d{6}_[0-9a-f]{6}$/.test(id);
1724
- }
1725
- function formatMessageIdTimestamp(value) {
1726
- const date = new Date(value);
1727
- const safeDate = Number.isNaN(date.getTime()) ? new Date() : date;
1728
- const pad = (input) => String(input).padStart(2, "0");
1729
- return [
1730
- safeDate.getFullYear(),
1731
- pad(safeDate.getMonth() + 1),
1732
- pad(safeDate.getDate()),
1733
- "_",
1734
- pad(safeDate.getHours()),
1735
- pad(safeDate.getMinutes()),
1736
- pad(safeDate.getSeconds()),
1737
- ].join("");
1738
- }
1739
- function randomMessageSuffix() {
1740
- return randomBytes(3).toString("hex");
1741
- }
1742
- function shortHash(value) {
1743
- return createHash("sha256").update(value).digest("hex").slice(0, 6);
1744
- }
1745
- function loadWorkspaceGroupsFromDatabase() {
1746
- workspaceGroups.clear();
1747
- const rows = getCenterDatabase()
1748
- .prepare("select * from workspace_groups order by created_at asc")
1749
- .all();
1750
- for (const row of rows) {
1751
- workspaceGroups.set(row.id, {
1752
- id: row.id,
1753
- ownerId: row.owner_id,
1754
- title: row.title,
1755
- createdAt: row.created_at,
1756
- updatedAt: row.updated_at,
1757
- });
1758
- }
1759
- }
1760
- function getGlobalEnvironmentVariable(key) {
1761
- const row = getCenterDatabase()
1762
- .prepare("select * from global_environment_variables where key = ?")
1763
- .get(key);
1764
- return row ? globalEnvironmentVariableFromRow(row) : null;
1765
- }
1766
- function globalEnvironmentVariableFromRow(row) {
1767
- return {
1768
- key: row.key,
1769
- value: row.value,
1770
- description: row.description ?? "",
1771
- createdAt: row.created_at,
1772
- updatedAt: row.updated_at,
1773
- };
1774
- }
1775
- function appSettingFromRow(row) {
1776
- return {
1777
- key: row.key,
1778
- value: row.value,
1779
- updatedAt: row.updated_at,
1780
- };
1781
- }
1782
- function loadConversationsFromDatabase(defaultSandbox) {
1783
- conversations.clear();
1784
- const rows = getCenterDatabase()
1785
- .prepare("select * from workspaces order by updated_at desc")
1786
- .all();
1787
- for (const row of rows) {
1788
- if (!existsSync(row.workspace_path)) {
1789
- continue;
1790
- }
1791
- let contextVersions = loadContextVersions(row.workspace_path);
1792
- if (!contextVersions.length) {
1793
- const contextVersion = {
1794
- id: randomUUID(),
1795
- codexThreadId: row.codex_thread_id,
1796
- source: "initial",
1797
- summary: null,
1798
- createdAt: row.created_at,
1799
- };
1800
- contextVersions = [contextVersion];
1801
- }
1802
- const memoryItems = readWorkspaceMemoryItems(row.workspace_path);
1803
- conversations.set(row.id, {
1804
- id: row.id,
1805
- ownerId: row.owner_id,
1806
- title: row.title,
1807
- groupId: normalizeWorkspaceGroupId(row.group_id),
1808
- memory: formatMemoryItems(memoryItems),
1809
- memoryItems,
1810
- responsibility: readWorkspaceResponsibility(row.workspace_path),
1811
- codexThreadId: row.codex_thread_id,
1812
- workspaceKind: row.workspace_kind,
1813
- workspacePath: row.workspace_path,
1814
- sandbox: normalizeSandbox(row.sandbox, defaultSandbox),
1815
- contextVersions,
1816
- status: row.status === "running" ? "error" : row.status,
1817
- messages: [],
1818
- events: [],
1819
- createdAt: row.created_at,
1820
- updatedAt: row.updated_at,
1821
- });
1822
- }
1823
- }
1824
- function readWorkspaceMemoryItems(workspacePath) {
1825
- return parseMemoryItems(readWorkspaceSettings(workspacePath)[WORKSPACE_MEMORY_ITEMS_SETTING_KEY]);
1826
- }
1827
- function readWorkspaceResponsibility(workspacePath) {
1828
- return readWorkspaceSettings(workspacePath)[WORKSPACE_RESPONSIBILITY_SETTING_KEY] ?? "";
1829
- }
1830
- function readWorkspaceSettings(workspacePath) {
1831
- const rows = getWorkspaceDatabase(workspacePath)
1832
- .prepare("select key, value from workspace_settings")
1833
- .all();
1834
- return Object.fromEntries(rows
1835
- .filter((row) => typeof row.key === "string")
1836
- .map((row) => [row.key, typeof row.value === "string" ? row.value : ""]));
1837
- }
1838
- function readWorkspaceSettingUpdatedAt(workspacePath, key) {
1839
- const row = getWorkspaceDatabase(workspacePath)
1840
- .prepare("select updated_at from workspace_settings where key = ?")
1841
- .get(key);
1842
- return typeof row?.updated_at === "string"
1843
- ? row.updated_at
1844
- : new Date().toISOString();
1845
- }
1846
- function initializeWorkspaceUnreadBaselines() {
1847
- const insert = getCenterDatabase().prepare(`
1848
- insert or ignore into workspace_unread_baselines (
1849
- workspace_id, terminal_turn_rowid
1850
- ) values (?, ?)
1851
- `);
1852
- runInTransaction(getCenterDatabase(), () => {
1853
- for (const conversation of conversations.values()) {
1854
- insert.run(conversation.id, latestTerminalTurnRowid(conversation));
1855
- }
1856
- });
1857
- }
1858
- function latestTerminalTurnRowid(conversation) {
1859
- const row = getWorkspaceDatabase(conversation.workspacePath)
1860
- .prepare(`
1861
- select coalesce(max(rowid), 0) as rowid
1862
- from turns
1863
- where status in ('completed', 'failed')
1864
- `)
1865
- .get();
1866
- return row?.rowid ?? 0;
1867
- }
1868
- export function parseMemoryItems(value) {
1869
- if (!value?.trim()) {
1870
- return [];
1871
- }
1872
- try {
1873
- const parsed = JSON.parse(value);
1874
- if (!Array.isArray(parsed)) {
1875
- return [];
1876
- }
1877
- return normalizeMemoryItems(parsed.map((item) => {
1878
- const record = item && typeof item === "object" ? item : {};
1879
- return {
1880
- key: "key" in record && typeof record.key === "string"
1881
- ? record.key
1882
- : "",
1883
- value: "value" in record && typeof record.value === "string"
1884
- ? record.value
1885
- : "",
1886
- updatedAt: "updatedAt" in record && typeof record.updatedAt === "string"
1887
- ? record.updatedAt
1888
- : new Date().toISOString(),
1889
- };
1890
- }));
1891
- }
1892
- catch {
1893
- return [];
1894
- }
1895
- }
1896
- export function normalizeMemoryItems(items) {
1897
- const byKey = new Map();
1898
- for (const item of items) {
1899
- const key = normalizeMemoryKey(item.key);
1900
- const value = item.value.trim();
1901
- if (!key || !value) {
1902
- continue;
1903
- }
1904
- byKey.set(key, {
1905
- key,
1906
- value,
1907
- updatedAt: item.updatedAt || new Date().toISOString(),
1908
- });
1909
- }
1910
- return [...byKey.values()].sort((left, right) => left.key.localeCompare(right.key));
1911
- }
1912
- export function formatMemoryItems(items) {
1913
- return normalizeMemoryItems(items)
1914
- .map((item) => `${item.key}: ${item.value}`)
1915
- .join("\n");
1916
- }
1917
- function normalizeMemoryKey(key) {
1918
- return key
1919
- .trim()
1920
- .toLowerCase()
1921
- .replace(/\s+/g, "-")
1922
- .replace(/[^a-z0-9_.-]/g, "-")
1923
- .replace(/-+/g, "-")
1924
- .replace(/^[^a-z_]+/, "")
1925
- .replace(/[-.]+$/g, "");
1926
- }
1927
- function isValidStoredEnvironmentKey(key) {
1928
- return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key);
1929
- }
1930
- function loadContextVersions(workspacePath) {
1931
- const rows = getWorkspaceDatabase(workspacePath)
1932
- .prepare("select * from context_versions order by created_at asc")
1933
- .all();
1934
- return rows.map((row) => ({
1935
- id: row.id,
1936
- codexThreadId: row.codex_thread_id,
1937
- source: row.source,
1938
- summary: row.summary,
1939
- createdAt: row.created_at,
1940
- }));
1941
- }
1942
- function ensureDefaultWorkspaceGroup() {
1943
- const row = getCenterDatabase()
1944
- .prepare("select id from workspace_groups where id = ?")
1945
- .get(DEFAULT_WORKSPACE_GROUP_ID);
1946
- if (row) {
1947
- return;
1948
- }
1949
- const now = new Date().toISOString();
1950
- upsertWorkspaceGroup({
1951
- id: DEFAULT_WORKSPACE_GROUP_ID,
1952
- ownerId: null,
1953
- title: "Default",
1954
- createdAt: now,
1955
- updatedAt: now,
1956
- });
1957
- }
1958
- function requireDefaultOwnerId() {
1959
- if (!defaultOwnerId) {
1960
- throw new Error("default workspace owner is not initialized");
1961
- }
1962
- return defaultOwnerId;
1963
- }
1964
- function upsertWorkspaceGroup(group) {
1965
- getCenterDatabase()
1966
- .prepare(`
1967
- insert into workspace_groups (id, owner_id, title, created_at, updated_at)
1968
- values (@id, @ownerId, @title, @createdAt, @updatedAt)
1969
- on conflict(id) do update set
1970
- owner_id = excluded.owner_id,
1971
- title = excluded.title,
1972
- updated_at = excluded.updated_at
1973
- `)
1974
- .run(group);
1975
- workspaceGroups.set(group.id, group);
1976
- }
1977
- function upsertConversationMetadata(conversation) {
1978
- getCenterDatabase()
1979
- .prepare(`
1980
- insert into workspaces (
1981
- id,
1982
- owner_id,
1983
- title,
1984
- group_id,
1985
- codex_thread_id,
1986
- workspace_kind,
1987
- workspace_path,
1988
- sandbox,
1989
- status,
1990
- created_at,
1991
- updated_at
1992
- )
1993
- values (
1994
- @id,
1995
- @ownerId,
1996
- @title,
1997
- @groupId,
1998
- @codexThreadId,
1999
- @workspaceKind,
2000
- @workspacePath,
2001
- @sandbox,
2002
- @status,
2003
- @createdAt,
2004
- @updatedAt
2005
- )
2006
- on conflict(id) do update set
2007
- title = excluded.title,
2008
- owner_id = excluded.owner_id,
2009
- group_id = excluded.group_id,
2010
- codex_thread_id = excluded.codex_thread_id,
2011
- workspace_kind = excluded.workspace_kind,
2012
- workspace_path = excluded.workspace_path,
2013
- sandbox = excluded.sandbox,
2014
- status = excluded.status,
2015
- updated_at = excluded.updated_at
2016
- `)
2017
- .run(conversation);
2018
- }
2019
- function upsertWorkspaceWakeup(wakeup) {
2020
- getCenterDatabase()
2021
- .prepare(`
2022
- insert into workspace_wakeups (
2023
- id,
2024
- workspace_id,
2025
- status,
2026
- wake_at,
2027
- instruction,
2028
- created_by,
2029
- created_turn_id,
2030
- result_turn_id,
2031
- error,
2032
- attempt_count,
2033
- max_attempts,
2034
- created_at,
2035
- updated_at
2036
- )
2037
- values (
2038
- @id,
2039
- @workspaceId,
2040
- @status,
2041
- @wakeAt,
2042
- @instruction,
2043
- @createdBy,
2044
- @createdTurnId,
2045
- @resultTurnId,
2046
- @error,
2047
- @attemptCount,
2048
- @maxAttempts,
2049
- @createdAt,
2050
- @updatedAt
2051
- )
2052
- on conflict(id) do update set
2053
- status = excluded.status,
2054
- wake_at = excluded.wake_at,
2055
- instruction = excluded.instruction,
2056
- result_turn_id = excluded.result_turn_id,
2057
- error = excluded.error,
2058
- attempt_count = excluded.attempt_count,
2059
- max_attempts = excluded.max_attempts,
2060
- updated_at = excluded.updated_at
2061
- `)
2062
- .run(wakeup);
2063
- }
2064
- function getWorkspaceWakeup(id) {
2065
- const row = getCenterDatabase()
2066
- .prepare("select * from workspace_wakeups where id = ?")
2067
- .get(id);
2068
- return row ? wakeupFromRow(row) : null;
2069
- }
2070
- function countWakeupsByStatus() {
2071
- const counts = {
2072
- scheduled: 0,
2073
- running: 0,
2074
- completed: 0,
2075
- cancelled: 0,
2076
- failed: 0,
2077
- };
2078
- const rows = getCenterDatabase()
2079
- .prepare("select status, count(*) as count from workspace_wakeups group by status")
2080
- .all();
2081
- for (const row of rows) {
2082
- if (row.status in counts) {
2083
- counts[row.status] = row.count;
2084
- }
2085
- }
2086
- return counts;
2087
- }
2088
- function updateConversationMetadata(conversation) {
2089
- upsertConversationMetadata(conversation);
2090
- }
2091
- function upsertContextVersion(conversation, contextVersion) {
2092
- getWorkspaceDatabase(conversation.workspacePath)
2093
- .prepare(`
2094
- insert into context_versions (id, codex_thread_id, source, summary, created_at)
2095
- values (@id, @codexThreadId, @source, @summary, @createdAt)
2096
- on conflict(id) do update set
2097
- codex_thread_id = excluded.codex_thread_id,
2098
- source = excluded.source,
2099
- summary = excluded.summary
2100
- `)
2101
- .run(contextVersion);
2102
- }
2103
- function insertMessage(conversation, message) {
2104
- getWorkspaceDatabase(conversation.workspacePath)
2105
- .prepare(`
2106
- insert or ignore into messages (id, role, content, turn_id, created_at)
2107
- values (@id, @role, @content, @turnId, @createdAt)
2108
- `)
2109
- .run(message);
2110
- }
2111
- function upsertTurn(conversation, turn) {
2112
- getWorkspaceDatabase(conversation.workspacePath)
2113
- .prepare(`
2114
- insert into turns (
2115
- id,
2116
- user_message_id,
2117
- codex_turn_id,
2118
- status,
2119
- started_at,
2120
- finished_at,
2121
- error,
2122
- created_at,
2123
- updated_at
2124
- )
2125
- values (
2126
- @id,
2127
- @userMessageId,
2128
- @codexTurnId,
2129
- @status,
2130
- @startedAt,
2131
- @finishedAt,
2132
- @error,
2133
- @createdAt,
2134
- @updatedAt
2135
- )
2136
- on conflict(id) do update set
2137
- user_message_id = excluded.user_message_id,
2138
- codex_turn_id = excluded.codex_turn_id,
2139
- status = excluded.status,
2140
- started_at = excluded.started_at,
2141
- finished_at = excluded.finished_at,
2142
- error = excluded.error,
2143
- updated_at = excluded.updated_at
2144
- `)
2145
- .run(turn);
2146
- }
2147
- function getTurn(conversation, turnId) {
2148
- const row = getWorkspaceDatabase(conversation.workspacePath)
2149
- .prepare("select * from turns where id = ?")
2150
- .get(turnId);
2151
- return row ? turnFromRow(row) : null;
2152
- }
2153
- function countWorkspaceRows(workspacePath, table) {
2154
- const row = getWorkspaceDatabase(workspacePath)
2155
- .prepare(`select count(*) as count from ${table}`)
2156
- .get();
2157
- return row?.count ?? 0;
2158
- }
2159
- function insertEvent(conversation, event, turnId = null) {
2160
- getWorkspaceDatabase(conversation.workspacePath)
2161
- .prepare(`
2162
- insert or ignore into events (id, turn_id, event, data_json, created_at)
2163
- values (@id, @turnId, @event, @dataJson, @createdAt)
2164
- `)
2165
- .run({
2166
- id: event.id,
2167
- turnId,
2168
- event: event.event,
2169
- dataJson: JSON.stringify(event.data),
2170
- createdAt: event.createdAt,
2171
- });
2172
- }
2173
- function listTurnsFromDatabase(conversation, before, limit) {
2174
- const db = getWorkspaceDatabase(conversation.workspacePath);
2175
- const rows = before
2176
- ? db
2177
- .prepare(`
2178
- select * from turns
2179
- where started_at < ?
2180
- order by started_at desc
2181
- limit ?
2182
- `)
2183
- .all(before, limit)
2184
- : db
2185
- .prepare(`
2186
- select * from turns
2187
- order by started_at desc
2188
- limit ?
2189
- `)
2190
- .all(limit);
2191
- return rows.reverse().map(turnFromRow);
2192
- }
2193
- function listMessagesForTurns(conversation, turns) {
2194
- if (turns.length === 0) {
2195
- return [];
2196
- }
2197
- const windowClauses = turns
2198
- .map(() => "(created_at >= ? and created_at <= ?)")
2199
- .join(" or ");
2200
- const now = new Date().toISOString();
2201
- const windowParams = turns.flatMap((turn, index) => {
2202
- const nextTurn = turns[index + 1] ?? null;
2203
- return [
2204
- turn.startedAt,
2205
- turn.finishedAt ?? nextTurn?.startedAt ?? now,
2206
- ];
2207
- });
2208
- const userMessageIds = turns
2209
- .map((turn) => turn.userMessageId)
2210
- .filter((id) => typeof id === "string" && id.length > 0);
2211
- const userMessageClause = userMessageIds.length > 0
2212
- ? `id in (${userMessageIds.map(() => "?").join(", ")}) or `
2213
- : "";
2214
- const turnIds = turns.map((turn) => turn.id);
2215
- const turnPlaceholders = turnIds.map(() => "?").join(", ");
2216
- const rows = getWorkspaceDatabase(conversation.workspacePath)
2217
- .prepare(`
2218
- select * from messages
2219
- where turn_id in (${turnPlaceholders})
2220
- or (
2221
- turn_id is null
2222
- and (${userMessageClause}${windowClauses})
2223
- )
2224
- order by created_at asc, id asc
2225
- `)
2226
- .all(...turnIds, ...userMessageIds, ...windowParams);
2227
- return rows.map(messageFromRow);
2228
- }
2229
- function listEventsForTurns(conversation, turnIds, options = {}) {
2230
- if (turnIds.length === 0) {
2231
- return [];
2232
- }
2233
- const placeholders = turnIds.map(() => "?").join(", ");
2234
- const rows = getWorkspaceDatabase(conversation.workspacePath)
2235
- .prepare(`
2236
- select * from events
2237
- where turn_id in (${placeholders})
2238
- order by created_at asc, id asc
2239
- `)
2240
- .all(...turnIds);
2241
- const events = rows.map(eventFromRow);
2242
- if (options.compact === false) {
2243
- return events;
2244
- }
2245
- return events
2246
- .map(compactHistoryEvent)
2247
- .filter((event) => event !== null);
2248
- }
2249
- function eventFromRow(row) {
2250
- return {
2251
- id: row.id,
2252
- turnId: row.turn_id,
2253
- event: row.event,
2254
- data: parseEventData(row.data_json),
2255
- createdAt: row.created_at,
2256
- };
2257
- }
2258
- function compactHistoryEvent(event) {
2259
- if (event.event === "server.thread") {
2260
- return null;
2261
- }
2262
- if (event.event === "codex.notification") {
2263
- const notification = readEventObject(event.data);
2264
- const method = notification.method;
2265
- const params = readEventObject(notification.params);
2266
- if (method === "account/rateLimits/updated") {
2267
- return event;
2268
- }
2269
- if (method === "item/agentMessage/delta" ||
2270
- method === "item/agentMessage/thinking_delta" ||
2271
- method === "item/commandExecution/outputDelta" ||
2272
- (typeof method === "string" && method.startsWith("hook/"))) {
2273
- return null;
2274
- }
2275
- if (method === "item/started" || method === "item/completed") {
2276
- const item = readEventObject(params.item);
2277
- if (item.type === "agentMessage" || item.type === "userMessage") {
2278
- return null;
2279
- }
2280
- if (item.type === "commandExecution") {
2281
- return null;
2282
- }
2283
- return event;
2284
- }
2285
- return null;
2286
- }
2287
- if (event.event === "codex.stderr") {
2288
- return null;
2289
- }
2290
- return event;
2291
- }
2292
- function compactCommandExecutionItem(item) {
2293
- return {
2294
- ...item,
2295
- aggregatedOutput: typeof item.aggregatedOutput === "string"
2296
- ? truncateHistoryText(item.aggregatedOutput)
2297
- : item.aggregatedOutput,
2298
- };
2299
- }
2300
- function truncateHistoryText(value) {
2301
- if (value.length <= MAX_COMPACT_EVENT_TEXT_LENGTH) {
2302
- return value;
2303
- }
2304
- return `${value.slice(0, MAX_COMPACT_EVENT_TEXT_LENGTH)}\n... [truncated in history view]`;
2305
- }
2306
- function readEventObject(value) {
2307
- return value && typeof value === "object" && !Array.isArray(value)
2308
- ? value
2309
- : {};
2310
- }
2311
- function turnFromRow(row) {
2312
- return {
2313
- id: row.id,
2314
- userMessageId: row.user_message_id,
2315
- codexTurnId: row.codex_turn_id,
2316
- status: row.status,
2317
- startedAt: row.started_at,
2318
- finishedAt: row.finished_at,
2319
- error: row.error,
2320
- createdAt: row.created_at,
2321
- updatedAt: row.updated_at,
2322
- };
2323
- }
2324
- function messageFromRow(row) {
2325
- return {
2326
- id: row.id,
2327
- role: row.role,
2328
- content: row.content,
2329
- createdAt: row.created_at,
2330
- turnId: row.turn_id,
2331
- };
2332
- }
2333
- function wakeupFromRow(row) {
2334
- return {
2335
- id: row.id,
2336
- workspaceId: row.workspace_id,
2337
- status: row.status,
2338
- wakeAt: row.wake_at,
2339
- instruction: row.instruction,
2340
- createdBy: row.created_by,
2341
- createdTurnId: row.created_turn_id,
2342
- resultTurnId: row.result_turn_id,
2343
- error: row.error,
2344
- attemptCount: row.attempt_count,
2345
- maxAttempts: row.max_attempts,
2346
- createdAt: row.created_at,
2347
- updatedAt: row.updated_at,
2348
- };
2349
- }
2350
- function transcriptRangeSummaryFromRow(row) {
2351
- return {
2352
- id: row.id,
2353
- startMessageId: row.start_message_id,
2354
- endMessageId: row.end_message_id,
2355
- title: row.title,
2356
- summary: row.summary,
2357
- createdAt: row.created_at,
2358
- updatedAt: row.updated_at,
2359
- };
2360
- }
2361
- function runtimeResetFromRow(row) {
2362
- return {
2363
- id: row.id,
2364
- oldCodexThreadId: row.old_codex_thread_id,
2365
- newCodexThreadId: row.new_codex_thread_id,
2366
- mode: row.mode,
2367
- status: row.status,
2368
- injectedMessageCount: row.injected_message_count,
2369
- injectedUserMessageCount: row.injected_user_message_count,
2370
- injectedAssistantMessageCount: row.injected_assistant_message_count,
2371
- injectedMessages: parseRuntimeResetMessages(row.injected_messages_json ?? "[]"),
2372
- error: row.error,
2373
- createdAt: row.created_at,
2374
- completedAt: row.completed_at,
2375
- };
2376
- }
2377
- function parseRuntimeResetMessages(dataJson) {
2378
- try {
2379
- const value = JSON.parse(dataJson);
2380
- if (!Array.isArray(value)) {
2381
- return [];
2382
- }
2383
- return value.filter(isConversationMessage);
2384
- }
2385
- catch {
2386
- return [];
2387
- }
2388
- }
2389
- function isConversationMessage(value) {
2390
- if (!value || typeof value !== "object") {
2391
- return false;
2392
- }
2393
- const item = value;
2394
- return (typeof item.id === "string" &&
2395
- (item.role === "user" || item.role === "assistant") &&
2396
- typeof item.content === "string" &&
2397
- typeof item.createdAt === "string");
2398
- }
2399
- function clampWakeupMaxAttempts(value) {
2400
- if (typeof value !== "number" || !Number.isFinite(value)) {
2401
- return 3;
2402
- }
2403
- return Math.max(1, Math.min(20, Math.floor(value)));
2404
- }
2405
- function hasTurnsBefore(conversation, timestamp) {
2406
- const row = getWorkspaceDatabase(conversation.workspacePath)
2407
- .prepare("select 1 as found from turns where started_at < ? limit 1")
2408
- .get(timestamp);
2409
- return Boolean(row);
2410
- }
2411
- function countRows(db, table) {
2412
- const row = db.prepare(`select count(*) as count from ${table}`).get();
2413
- return row.count;
2414
- }
2415
- function parseEventData(dataJson) {
2416
- try {
2417
- return JSON.parse(dataJson);
2418
- }
2419
- catch {
2420
- return null;
2421
- }
2422
- }
2423
- function trimInMemoryEvents(conversation) {
2424
- if (conversation.events.length <= MAX_IN_MEMORY_EVENTS_PER_WORKSPACE) {
2425
- return;
2426
- }
2427
- conversation.events.splice(0, conversation.events.length - MAX_IN_MEMORY_EVENTS_PER_WORKSPACE);
2428
- }
2429
- function getWorkspaceMetadataPath(workspacePath) {
2430
- return path.join(workspacePath, ".seele", "workspace.json");
2431
- }
2432
- function getWorkspaceDatabasePath(workspacePath) {
2433
- return path.join(workspacePath, ".seele", "workspace.db");
2434
- }
2435
- function getWorkspaceGroupsPath(workspaceRoot) {
2436
- return path.join(workspaceRoot, ".seele", "groups.json");
2437
- }
2438
- function ensureWorkspacePathIsManaged(workspacePath) {
2439
- if (!workspaceRootPath) {
2440
- throw new Error("workspace store is not initialized");
2441
- }
2442
- const resolvedRoot = path.resolve(workspaceRootPath);
2443
- const resolvedWorkspace = path.resolve(workspacePath);
2444
- const relativePath = path.relative(resolvedRoot, resolvedWorkspace);
2445
- if (relativePath === "" ||
2446
- relativePath.startsWith("..") ||
2447
- path.isAbsolute(relativePath)) {
2448
- throw new Error(`refusing to delete unmanaged workspace: ${workspacePath}`);
2449
- }
2450
- }
2451
- function moveWorkspaceToRecycle(conversation) {
2452
- if (!workspaceRootPath) {
2453
- throw new Error("workspace store is not initialized");
2454
- }
2455
- const recycleRoot = path.join(path.dirname(workspaceRootPath), "recycle");
2456
- mkdirSync(recycleRoot, { recursive: true });
2457
- const timestamp = new Date()
2458
- .toISOString()
2459
- .replaceAll(":", "")
2460
- .replaceAll(".", "");
2461
- let recyclePath = path.join(recycleRoot, `${conversation.id}-${timestamp}`);
2462
- for (let attempt = 1; existsSync(recyclePath); attempt += 1) {
2463
- recyclePath = path.join(recycleRoot, `${conversation.id}-${timestamp}-${attempt}`);
2464
- }
2465
- renameSync(conversation.workspacePath, recyclePath);
2466
- }