@vibe80/vibe80 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +93 -4
  2. package/bin/vibe80.js +1728 -16
  3. package/client/dist/assets/{DiffPanel-Luk1GrAd.js → DiffPanel-BUJhQj_Q.js} +1 -1
  4. package/client/dist/assets/{ExplorerPanel-CGVmxql1.js → ExplorerPanel-DugEeaO2.js} +1 -1
  5. package/client/dist/assets/{LogsPanel-D8Z8Zam8.js → LogsPanel-BQrGxMu_.js} +1 -1
  6. package/client/dist/assets/{SettingsPanel-H2q2eoDf.js → SettingsPanel-Ci2BdIYO.js} +1 -1
  7. package/client/dist/assets/{TerminalPanel-kEz-cjes.js → TerminalPanel-C-T3t-6T.js} +1 -1
  8. package/client/dist/assets/index-cFi4LM0j.js +711 -0
  9. package/client/dist/assets/index-qNyFxUjK.css +32 -0
  10. package/client/dist/icon_square-512x512.png +0 -0
  11. package/client/dist/icon_square.svg +58 -0
  12. package/client/dist/index.html +3 -2
  13. package/client/dist/sw.js +1 -1
  14. package/client/index.html +1 -0
  15. package/client/public/icon_square-512x512.png +0 -0
  16. package/client/public/icon_square.svg +58 -0
  17. package/client/src/App.jsx +204 -2
  18. package/client/src/assets/vibe80_dark.png +0 -0
  19. package/client/src/assets/vibe80_light.png +0 -0
  20. package/client/src/components/Chat/ChatMessages.jsx +1 -1
  21. package/client/src/components/SessionGate/SessionGate.jsx +282 -90
  22. package/client/src/components/WorktreeTabs.css +11 -0
  23. package/client/src/components/WorktreeTabs.jsx +77 -47
  24. package/client/src/hooks/useChatSocket.js +8 -7
  25. package/client/src/hooks/useRepoBranchesModels.js +12 -6
  26. package/client/src/hooks/useWorktreeCloseConfirm.js +19 -7
  27. package/client/src/hooks/useWorktrees.js +3 -1
  28. package/client/src/index.css +26 -3
  29. package/client/src/locales/en.json +12 -1
  30. package/client/src/locales/fr.json +12 -1
  31. package/docs/api/openapi.json +1 -1
  32. package/package.json +2 -1
  33. package/server/scripts/rotate-workspace-secret.js +1 -1
  34. package/server/src/claudeClient.js +3 -3
  35. package/server/src/codexClient.js +3 -3
  36. package/server/src/config.js +6 -6
  37. package/server/src/index.js +14 -12
  38. package/server/src/middleware/auth.js +7 -7
  39. package/server/src/middleware/debug.js +36 -4
  40. package/server/src/providerLogger.js +2 -2
  41. package/server/src/routes/sessions.js +133 -21
  42. package/server/src/routes/workspaces.js +1 -1
  43. package/server/src/runAs.js +14 -14
  44. package/server/src/services/auth.js +3 -3
  45. package/server/src/services/session.js +182 -14
  46. package/server/src/services/workspace.js +86 -42
  47. package/server/src/storage/index.js +2 -2
  48. package/server/src/storage/redis.js +38 -36
  49. package/server/src/storage/sqlite.js +13 -13
  50. package/server/src/worktreeManager.js +87 -19
  51. package/server/tests/integration/routes/workspaces-routes.test.js +8 -8
  52. package/server/tests/setup/env.js +5 -5
  53. package/server/tests/unit/services/auth.test.js +3 -3
  54. package/client/dist/assets/index-BDQQz6SJ.css +0 -32
  55. package/client/dist/assets/index-knjescVH.js +0 -711
@@ -11,18 +11,18 @@ import storage from "../storage/index.js";
11
11
  import { generateId } from "../helpers.js";
12
12
  import { logDebug } from "../middleware/debug.js";
13
13
 
14
- const deploymentMode = process.env.DEPLOYMENT_MODE;
14
+ const deploymentMode = process.env.VIBE80_DEPLOYMENT_MODE;
15
15
  const isMonoUser = deploymentMode === "mono_user";
16
- const workspaceHomeBase = process.env.WORKSPACE_HOME_BASE || "/home";
17
- const workspaceRootBase = process.env.WORKSPACE_ROOT_DIRECTORY || "/workspaces";
16
+ const workspaceHomeBase = process.env.VIBE80_WORKSPACE_HOME_BASE || "/home";
17
+ const workspaceRootBase = process.env.VIBE80_WORKSPACE_ROOT_DIRECTORY || "/workspaces";
18
18
  const workspaceRootName = "vibe80_workspace";
19
19
  const monoUserWorkspaceDir =
20
- process.env.MONO_USER_WORKSPACE_DIR || path.join(os.homedir(), workspaceRootName);
20
+ process.env.VIBE80_MONO_USER_WORKSPACE_DIR || path.join(os.homedir(), workspaceRootName);
21
21
  const workspaceSessionsDirName = "sessions";
22
22
  const rootHelperPath = process.env.VIBE80_ROOT_HELPER || "/usr/local/bin/vibe80-root";
23
23
  const sudoPath = process.env.VIBE80_SUDO_PATH || "sudo";
24
- const workspaceUidMin = Number.parseInt(process.env.WORKSPACE_UID_MIN, 10) || 200000;
25
- const workspaceUidMax = Number.parseInt(process.env.WORKSPACE_UID_MAX, 10) || 999999999;
24
+ const workspaceUidMin = Number.parseInt(process.env.VIBE80_WORKSPACE_UID_MIN, 10) || 200000;
25
+ const workspaceUidMax = Number.parseInt(process.env.VIBE80_WORKSPACE_UID_MAX, 10) || 999999999;
26
26
  const workspaceUserExistsCache = new Map();
27
27
 
28
28
  export const workspaceIdPattern = isMonoUser ? /^default$/ : /^w[0-9a-f]{24}$/;
@@ -36,6 +36,36 @@ const runRootCommand = (args, options = {}) => {
36
36
  return runCommand(sudoPath, ["-n", rootHelperPath, ...args], options);
37
37
  };
38
38
 
39
+ const runWorkspaceStatOutput = async (
40
+ workspaceId,
41
+ targetPath,
42
+ { gnuFormat, bsdFormat, options = {} }
43
+ ) => {
44
+ try {
45
+ return await runAsCommandOutput(
46
+ workspaceId,
47
+ "/usr/bin/stat",
48
+ ["-c", gnuFormat, targetPath],
49
+ options
50
+ );
51
+ } catch {
52
+ return runAsCommandOutput(
53
+ workspaceId,
54
+ "/usr/bin/stat",
55
+ ["-f", bsdFormat, targetPath],
56
+ options
57
+ );
58
+ }
59
+ };
60
+
61
+ const runHostStatOutput = async (targetPath, { gnuFormat, bsdFormat }) => {
62
+ try {
63
+ return await runCommandOutput("/usr/bin/stat", ["-c", gnuFormat, targetPath]);
64
+ } catch {
65
+ return runCommandOutput("/usr/bin/stat", ["-f", bsdFormat, targetPath]);
66
+ }
67
+ };
68
+
39
69
  export const getWorkspacePaths = (workspaceId) => {
40
70
  const home = isMonoUser ? os.homedir() : path.join(workspaceHomeBase, workspaceId);
41
71
  const root = isMonoUser
@@ -161,34 +191,50 @@ export const listWorkspaceEntries = async (workspaceId, dirPath) => {
161
191
  };
162
192
 
163
193
  export const getWorkspaceStat = async (workspaceId, targetPath, options = {}) => {
164
- const output = await runAsCommandOutput(workspaceId, "/usr/bin/stat", [
165
- "-c",
166
- "%f\t%s\t%a",
167
- targetPath,
168
- ], options);
169
- const [modeHex, sizeRaw, modeRaw] = output.trim().split("\t");
170
- const modeValue = Number.parseInt(modeHex, 16);
194
+ const output = await runWorkspaceStatOutput(workspaceId, targetPath, {
195
+ gnuFormat: "%f\t%s\t%a",
196
+ bsdFormat: "%HT\t%z\t%Lp",
197
+ options,
198
+ });
199
+ const [typeOrMode, sizeRaw, modeRaw] = output.trim().split("\t");
200
+ const modeValue = Number.parseInt(typeOrMode, 16);
171
201
  const typeBits = Number.isFinite(modeValue) ? modeValue & 0o170000 : null;
172
202
  let type = "";
173
- if (typeBits === 0o100000) {
174
- type = "regular";
175
- } else if (typeBits === 0o040000) {
176
- type = "directory";
177
- } else if (typeBits === 0o120000) {
178
- type = "symlink";
179
- } else if (Number.isFinite(typeBits)) {
180
- type = "other";
203
+ if (Number.isFinite(typeBits)) {
204
+ if (typeBits === 0o100000) {
205
+ type = "regular";
206
+ } else if (typeBits === 0o040000) {
207
+ type = "directory";
208
+ } else if (typeBits === 0o120000) {
209
+ type = "symlink";
210
+ } else {
211
+ type = "other";
212
+ }
213
+ } else {
214
+ const normalizedType = String(typeOrMode || "").toLowerCase();
215
+ if (normalizedType.includes("regular")) {
216
+ type = "regular";
217
+ } else if (normalizedType.includes("directory")) {
218
+ type = "directory";
219
+ } else if (normalizedType.includes("symbolic")) {
220
+ type = "symlink";
221
+ } else if (normalizedType) {
222
+ type = "other";
223
+ }
181
224
  }
182
225
  return {
183
226
  type,
184
227
  size: Number.parseInt(sizeRaw, 10),
185
- mode: modeRaw,
228
+ mode: String(modeRaw || ""),
186
229
  };
187
230
  };
188
231
 
189
232
  export const workspacePathExists = async (workspaceId, targetPath) => {
190
233
  try {
191
- await runAsCommandOutput(workspaceId, "/usr/bin/stat", ["-c", "%F", targetPath]);
234
+ await runWorkspaceStatOutput(workspaceId, targetPath, {
235
+ gnuFormat: "%F",
236
+ bsdFormat: "%HT",
237
+ });
192
238
  return true;
193
239
  } catch {
194
240
  return false;
@@ -578,11 +624,10 @@ const recoverWorkspaceIds = async (workspaceId) => {
578
624
  let gid = Number.isFinite(workspaceRecord?.gid) ? Number(workspaceRecord.gid) : null;
579
625
  if (!Number.isFinite(uid) || !Number.isFinite(gid)) {
580
626
  try {
581
- const output = await runAsCommandOutput(workspaceId, "/usr/bin/stat", [
582
- "-c",
583
- "%u\t%g",
584
- homeDir,
585
- ]);
627
+ const output = await runWorkspaceStatOutput(workspaceId, homeDir, {
628
+ gnuFormat: "%u\t%g",
629
+ bsdFormat: "%u\t%g",
630
+ });
586
631
  const [uidRaw, gidRaw] = output.trim().split("\t");
587
632
  if (!Number.isFinite(uid)) {
588
633
  uid = Number(uidRaw);
@@ -592,11 +637,10 @@ const recoverWorkspaceIds = async (workspaceId) => {
592
637
  }
593
638
  } catch {
594
639
  try {
595
- const output = await runCommandOutput("/usr/bin/stat", [
596
- "-c",
597
- "%u\t%g",
598
- homeDir,
599
- ]);
640
+ const output = await runHostStatOutput(homeDir, {
641
+ gnuFormat: "%u\t%g",
642
+ bsdFormat: "%u\t%g",
643
+ });
600
644
  const [uidRaw, gidRaw] = output.trim().split("\t");
601
645
  if (!Number.isFinite(uid)) {
602
646
  uid = Number(uidRaw);
@@ -843,12 +887,12 @@ const applyMonoEnabledProviders = (providers = {}, enabledConfig) => {
843
887
  };
844
888
 
845
889
  const buildMonoUserProviderOverridesFromEnv = () => {
846
- const codexApiKey = readEnvValue("CODEX_API_KEY");
847
- const codexAuthJsonB64Raw = process.env.CODEX_AUTH_JSON_B64;
890
+ const codexApiKey = readEnvValue("VIBE80_CODEX_API_KEY");
891
+ const codexAuthJsonB64Raw = process.env.VIBE80_CODEX_AUTH_JSON_B64;
848
892
  const hasCodexAuthJsonB64 = typeof codexAuthJsonB64Raw === "string";
849
893
  const codexAuthJsonB64 = hasCodexAuthJsonB64 ? codexAuthJsonB64Raw.trim() : "";
850
- const claudeApiKey = readEnvValue("CLAUDE_API_KEY");
851
- const claudeSetupTokenRaw = process.env.CLAUDE_SETUP_TOKEN;
894
+ const claudeApiKey = readEnvValue("VIBE80_CLAUDE_API_KEY");
895
+ const claudeSetupTokenRaw = process.env.VIBE80_CLAUDE_SETUP_TOKEN;
852
896
  const hasClaudeSetupToken = typeof claudeSetupTokenRaw === "string";
853
897
  const claudeSetupToken = hasClaudeSetupToken ? claudeSetupTokenRaw.trim() : "";
854
898
 
@@ -856,13 +900,13 @@ const buildMonoUserProviderOverridesFromEnv = () => {
856
900
 
857
901
  if (codexApiKey && hasCodexAuthJsonB64) {
858
902
  console.warn(
859
- "[warn] Both CODEX_API_KEY and CODEX_AUTH_JSON_B64 are set; using CODEX_AUTH_JSON_B64."
903
+ "[warn] Both VIBE80_CODEX_API_KEY and VIBE80_CODEX_AUTH_JSON_B64 are set; using VIBE80_CODEX_AUTH_JSON_B64."
860
904
  );
861
905
  }
862
906
  if (hasCodexAuthJsonB64) {
863
907
  if (!codexAuthJsonB64) {
864
908
  console.warn(
865
- "[warn] Invalid CODEX_AUTH_JSON_B64 detected; ignoring codex preprovisioning."
909
+ "[warn] Invalid VIBE80_CODEX_AUTH_JSON_B64 detected; ignoring codex preprovisioning."
866
910
  );
867
911
  } else {
868
912
  try {
@@ -873,7 +917,7 @@ const buildMonoUserProviderOverridesFromEnv = () => {
873
917
  };
874
918
  } catch {
875
919
  console.warn(
876
- "[warn] Invalid CODEX_AUTH_JSON_B64 detected; ignoring codex preprovisioning."
920
+ "[warn] Invalid VIBE80_CODEX_AUTH_JSON_B64 detected; ignoring codex preprovisioning."
877
921
  );
878
922
  }
879
923
  }
@@ -885,13 +929,13 @@ const buildMonoUserProviderOverridesFromEnv = () => {
885
929
 
886
930
  if (claudeApiKey && hasClaudeSetupToken) {
887
931
  console.warn(
888
- "[warn] Both CLAUDE_API_KEY and CLAUDE_SETUP_TOKEN are set; using CLAUDE_SETUP_TOKEN."
932
+ "[warn] Both VIBE80_CLAUDE_API_KEY and VIBE80_CLAUDE_SETUP_TOKEN are set; using VIBE80_CLAUDE_SETUP_TOKEN."
889
933
  );
890
934
  }
891
935
  if (hasClaudeSetupToken) {
892
936
  if (!claudeSetupToken) {
893
937
  console.warn(
894
- "[warn] Invalid CLAUDE_SETUP_TOKEN detected; ignoring claude preprovisioning."
938
+ "[warn] Invalid VIBE80_CLAUDE_SETUP_TOKEN detected; ignoring claude preprovisioning."
895
939
  );
896
940
  } else {
897
941
  overrides.claude = {
@@ -1,7 +1,7 @@
1
1
  import { createRedisStorage } from "./redis.js";
2
2
  import { createSqliteStorage } from "./sqlite.js";
3
3
 
4
- const backend = process.env.STORAGE_BACKEND || "sqlite";
4
+ const backend = process.env.VIBE80_STORAGE_BACKEND || "sqlite";
5
5
 
6
6
  let storage = null;
7
7
 
@@ -10,7 +10,7 @@ if (backend === "redis") {
10
10
  } else if (backend === "sqlite") {
11
11
  storage = createSqliteStorage();
12
12
  } else {
13
- throw new Error(`Unsupported STORAGE_BACKEND: ${backend}.`);
13
+ throw new Error(`Unsupported VIBE80_STORAGE_BACKEND: ${backend}.`);
14
14
  }
15
15
 
16
16
  export const storageBackend = backend;
@@ -24,11 +24,11 @@ const sanitizeSessionData = (data) => {
24
24
  };
25
25
 
26
26
  export const createRedisStorage = () => {
27
- const url = process.env.REDIS_URL;
27
+ const url = process.env.VIBE80_REDIS_URL;
28
28
  if (!url) {
29
- throw new Error("REDIS_URL is required when STORAGE_BACKEND=redis.");
29
+ throw new Error("VIBE80_REDIS_URL is required when VIBE80_STORAGE_BACKEND=redis.");
30
30
  }
31
- const prefix = process.env.REDIS_KEY_PREFIX || DEFAULT_PREFIX;
31
+ const prefix = process.env.VIBE80_REDIS_KEY_PREFIX || DEFAULT_PREFIX;
32
32
  const client = createClient({ url });
33
33
 
34
34
  const sessionKey = (sessionId) => buildKey(prefix, "session", sessionId);
@@ -36,13 +36,14 @@ export const createRedisStorage = () => {
36
36
  buildKey(prefix, "session", sessionId, "worktrees");
37
37
  const workspaceSessionsKey = (workspaceId) =>
38
38
  buildKey(prefix, "workspace", workspaceId, "sessions");
39
- const worktreeKey = (worktreeId) => buildKey(prefix, "worktree", worktreeId);
40
- const worktreeMessagesKey = (worktreeId) =>
41
- buildKey(prefix, "worktree", worktreeId, "messages");
42
- const worktreeMessageIndexKey = (worktreeId) =>
43
- buildKey(prefix, "worktree", worktreeId, "messageIndex");
44
- const worktreeMessageSeqKey = (worktreeId) =>
45
- buildKey(prefix, "worktree", worktreeId, "messageSeq");
39
+ const worktreeKey = (sessionId, worktreeId) =>
40
+ buildKey(prefix, "worktree", sessionId, worktreeId);
41
+ const worktreeMessagesKey = (sessionId, worktreeId) =>
42
+ buildKey(prefix, "worktree", sessionId, worktreeId, "messages");
43
+ const worktreeMessageIndexKey = (sessionId, worktreeId) =>
44
+ buildKey(prefix, "worktree", sessionId, worktreeId, "messageIndex");
45
+ const worktreeMessageSeqKey = (sessionId, worktreeId) =>
46
+ buildKey(prefix, "worktree", sessionId, worktreeId, "messageSeq");
46
47
  const workspaceUserIdsKey = (workspaceId) =>
47
48
  buildKey(prefix, "workspaceUserIds", workspaceId);
48
49
  const workspaceKey = (workspaceId) => buildKey(prefix, "workspace", workspaceId);
@@ -52,11 +53,12 @@ export const createRedisStorage = () => {
52
53
  const refreshTokenKey = (tokenHash) => buildKey(prefix, "refreshToken", tokenHash);
53
54
  const globalSessionsKey = () => buildKey(prefix, "sessions");
54
55
 
55
- const sessionTtlMs = Number.parseInt(process.env.SESSION_MAX_TTL_MS, 10) || 0;
56
+ const sessionTtlMs =
57
+ (Number.parseInt(process.env.VIBE80_SESSION_MAX_TTL_SECONDS, 10) || 0) * 1000;
56
58
  const workspaceUidMin =
57
- Number.parseInt(process.env.WORKSPACE_UID_MIN, 10) || 200000;
59
+ Number.parseInt(process.env.VIBE80_WORKSPACE_UID_MIN, 10) || 200000;
58
60
  const workspaceUidMax =
59
- Number.parseInt(process.env.WORKSPACE_UID_MAX, 10) || 999999999;
61
+ Number.parseInt(process.env.VIBE80_WORKSPACE_UID_MAX, 10) || 999999999;
60
62
 
61
63
  const ensureConnected = async () => {
62
64
  if (client.isOpen) {
@@ -104,10 +106,10 @@ export const createRedisStorage = () => {
104
106
  const worktreeIds = await client.sMembers(sessionWorktreesKey(sessionId));
105
107
  if (worktreeIds.length) {
106
108
  const keys = worktreeIds.flatMap((id) => [
107
- worktreeKey(id),
108
- worktreeMessagesKey(id),
109
- worktreeMessageIndexKey(id),
110
- worktreeMessageSeqKey(id),
109
+ worktreeKey(sessionId, id),
110
+ worktreeMessagesKey(sessionId, id),
111
+ worktreeMessageIndexKey(sessionId, id),
112
+ worktreeMessageSeqKey(sessionId, id),
111
113
  ]);
112
114
  await client.del(keys);
113
115
  }
@@ -143,24 +145,24 @@ export const createRedisStorage = () => {
143
145
 
144
146
  const saveWorktree = async (sessionId, worktreeId, data) => {
145
147
  await ensureConnected();
146
- await setWithTtl(worktreeKey(worktreeId), toJson(data), sessionTtlMs);
148
+ await setWithTtl(worktreeKey(sessionId, worktreeId), toJson(data), sessionTtlMs);
147
149
  await client.sAdd(sessionWorktreesKey(sessionId), worktreeId);
148
150
  await touchTtl(sessionWorktreesKey(sessionId), sessionTtlMs);
149
151
  };
150
152
 
151
- const getWorktree = async (worktreeId) => {
153
+ const getWorktree = async (sessionId, worktreeId) => {
152
154
  await ensureConnected();
153
- const raw = await client.get(worktreeKey(worktreeId));
155
+ const raw = await client.get(worktreeKey(sessionId, worktreeId));
154
156
  return fromJson(raw);
155
157
  };
156
158
 
157
159
  const deleteWorktree = async (sessionId, worktreeId) => {
158
160
  await ensureConnected();
159
161
  await client.del(
160
- worktreeKey(worktreeId),
161
- worktreeMessagesKey(worktreeId),
162
- worktreeMessageIndexKey(worktreeId),
163
- worktreeMessageSeqKey(worktreeId)
162
+ worktreeKey(sessionId, worktreeId),
163
+ worktreeMessagesKey(sessionId, worktreeId),
164
+ worktreeMessageIndexKey(sessionId, worktreeId),
165
+ worktreeMessageSeqKey(sessionId, worktreeId)
164
166
  );
165
167
  await client.sRem(sessionWorktreesKey(sessionId), worktreeId);
166
168
  };
@@ -171,7 +173,7 @@ export const createRedisStorage = () => {
171
173
  if (!ids.length) {
172
174
  return [];
173
175
  }
174
- const keys = ids.map((id) => worktreeKey(id));
176
+ const keys = ids.map((id) => worktreeKey(sessionId, id));
175
177
  const raw = await client.mGet(keys);
176
178
  return raw.map(fromJson).filter(Boolean);
177
179
  };
@@ -182,12 +184,12 @@ export const createRedisStorage = () => {
182
184
  if (!messageId) {
183
185
  throw new Error("Message id is required.");
184
186
  }
185
- const seq = await client.incr(worktreeMessageSeqKey(worktreeId));
186
- await client.hSet(worktreeMessageIndexKey(worktreeId), messageId, seq);
187
- await client.rPush(worktreeMessagesKey(worktreeId), toJson(message));
188
- await touchTtl(worktreeMessagesKey(worktreeId), sessionTtlMs);
189
- await touchTtl(worktreeMessageIndexKey(worktreeId), sessionTtlMs);
190
- await touchTtl(worktreeMessageSeqKey(worktreeId), sessionTtlMs);
187
+ const seq = await client.incr(worktreeMessageSeqKey(sessionId, worktreeId));
188
+ await client.hSet(worktreeMessageIndexKey(sessionId, worktreeId), messageId, seq);
189
+ await client.rPush(worktreeMessagesKey(sessionId, worktreeId), toJson(message));
190
+ await touchTtl(worktreeMessagesKey(sessionId, worktreeId), sessionTtlMs);
191
+ await touchTtl(worktreeMessageIndexKey(sessionId, worktreeId), sessionTtlMs);
192
+ await touchTtl(worktreeMessageSeqKey(sessionId, worktreeId), sessionTtlMs);
191
193
  };
192
194
 
193
195
  const getWorktreeMessages = async (
@@ -196,7 +198,7 @@ export const createRedisStorage = () => {
196
198
  { limit = null, beforeMessageId = null } = {}
197
199
  ) => {
198
200
  await ensureConnected();
199
- const listKey = worktreeMessagesKey(worktreeId);
201
+ const listKey = worktreeMessagesKey(sessionId, worktreeId);
200
202
  const listLength = await client.lLen(listKey);
201
203
  if (!listLength) {
202
204
  return [];
@@ -207,7 +209,7 @@ export const createRedisStorage = () => {
207
209
 
208
210
  if (beforeMessageId) {
209
211
  const seqValue = await client.hGet(
210
- worktreeMessageIndexKey(worktreeId),
212
+ worktreeMessageIndexKey(sessionId, worktreeId),
211
213
  beforeMessageId
212
214
  );
213
215
  const seq = Number.parseInt(seqValue, 10);
@@ -233,9 +235,9 @@ export const createRedisStorage = () => {
233
235
  const clearWorktreeMessages = async (sessionId, worktreeId) => {
234
236
  await ensureConnected();
235
237
  await client.del(
236
- worktreeMessagesKey(worktreeId),
237
- worktreeMessageIndexKey(worktreeId),
238
- worktreeMessageSeqKey(worktreeId)
238
+ worktreeMessagesKey(sessionId, worktreeId),
239
+ worktreeMessageIndexKey(sessionId, worktreeId),
240
+ worktreeMessageSeqKey(sessionId, worktreeId)
239
241
  );
240
242
  };
241
243
 
@@ -72,12 +72,12 @@ const all = (db, sql, params = []) =>
72
72
 
73
73
  export const createSqliteStorage = () => {
74
74
  const homeDir = process.env.HOME || os.homedir();
75
- const isMonoUser = process.env.DEPLOYMENT_MODE === "mono_user";
75
+ const isMonoUser = process.env.VIBE80_DEPLOYMENT_MODE === "mono_user";
76
76
  const defaultDataDirectory = isMonoUser
77
77
  ? path.join(homeDir, ".vibe80")
78
78
  : "/var/lib/vibe80";
79
79
  const dataDirectory = process.env.VIBE80_DATA_DIRECTORY || defaultDataDirectory;
80
- const dbPath = process.env.SQLITE_PATH || path.join(dataDirectory, "base.sqlite");
80
+ const dbPath = process.env.VIBE80_SQLITE_PATH || path.join(dataDirectory, "base.sqlite");
81
81
  const resolvedPath = path.resolve(dbPath);
82
82
  const dir = path.dirname(resolvedPath);
83
83
  fs.mkdirSync(dir, { recursive: true, mode: 0o750 });
@@ -108,9 +108,10 @@ export const createSqliteStorage = () => {
108
108
  await run(
109
109
  db,
110
110
  `CREATE TABLE IF NOT EXISTS worktrees (
111
- worktreeId TEXT PRIMARY KEY,
111
+ worktreeId TEXT NOT NULL,
112
112
  sessionId TEXT NOT NULL,
113
113
  data TEXT NOT NULL,
114
+ PRIMARY KEY (sessionId, worktreeId),
114
115
  FOREIGN KEY(sessionId) REFERENCES sessions(sessionId) ON DELETE CASCADE
115
116
  );`
116
117
  );
@@ -127,7 +128,7 @@ export const createSqliteStorage = () => {
127
128
  worktreeId TEXT NOT NULL,
128
129
  createdAt INTEGER NOT NULL,
129
130
  data TEXT NOT NULL,
130
- PRIMARY KEY (worktreeId, messageId),
131
+ PRIMARY KEY (sessionId, worktreeId, messageId),
131
132
  FOREIGN KEY(sessionId) REFERENCES sessions(sessionId) ON DELETE CASCADE
132
133
  );`
133
134
  );
@@ -194,7 +195,7 @@ export const createSqliteStorage = () => {
194
195
  const row = await get(db, "SELECT lastUid FROM workspace_uid_seq WHERE id = 1");
195
196
  if (!row) {
196
197
  const workspaceUidMin =
197
- Number.parseInt(process.env.WORKSPACE_UID_MIN, 10) || 200000;
198
+ Number.parseInt(process.env.VIBE80_WORKSPACE_UID_MIN, 10) || 200000;
198
199
  await run(
199
200
  db,
200
201
  "INSERT INTO workspace_uid_seq (id, lastUid) VALUES (1, ?)",
@@ -275,19 +276,18 @@ export const createSqliteStorage = () => {
275
276
  db,
276
277
  `INSERT INTO worktrees (worktreeId, sessionId, data)
277
278
  VALUES (?, ?, ?)
278
- ON CONFLICT(worktreeId) DO UPDATE SET
279
- sessionId=excluded.sessionId,
279
+ ON CONFLICT(sessionId, worktreeId) DO UPDATE SET
280
280
  data=excluded.data;`,
281
281
  [worktreeId, sessionId, toJson(data)]
282
282
  );
283
283
  };
284
284
 
285
- const getWorktree = async (worktreeId) => {
285
+ const getWorktree = async (sessionId, worktreeId) => {
286
286
  await ensureConnected();
287
287
  const row = await get(
288
288
  db,
289
- "SELECT data FROM worktrees WHERE worktreeId = ?",
290
- [worktreeId]
289
+ "SELECT data FROM worktrees WHERE sessionId = ? AND worktreeId = ?",
290
+ [sessionId, worktreeId]
291
291
  );
292
292
  return fromJson(row?.data);
293
293
  };
@@ -328,7 +328,7 @@ export const createSqliteStorage = () => {
328
328
  db,
329
329
  `INSERT INTO worktree_messages (messageId, sessionId, worktreeId, createdAt, data)
330
330
  VALUES (?, ?, ?, ?, ?)
331
- ON CONFLICT(worktreeId, messageId) DO NOTHING;`,
331
+ ON CONFLICT(sessionId, worktreeId, messageId) DO NOTHING;`,
332
332
  [messageId, sessionId, worktreeId, createdAt, toJson(message)]
333
333
  );
334
334
  };
@@ -559,9 +559,9 @@ export const createSqliteStorage = () => {
559
559
  const getNextWorkspaceUid = async () => {
560
560
  await ensureConnected();
561
561
  const workspaceUidMin =
562
- Number.parseInt(process.env.WORKSPACE_UID_MIN, 10) || 200000;
562
+ Number.parseInt(process.env.VIBE80_WORKSPACE_UID_MIN, 10) || 200000;
563
563
  const workspaceUidMax =
564
- Number.parseInt(process.env.WORKSPACE_UID_MAX, 10) || 999999999;
564
+ Number.parseInt(process.env.VIBE80_WORKSPACE_UID_MAX, 10) || 999999999;
565
565
  await run(db, "BEGIN IMMEDIATE");
566
566
  try {
567
567
  const row = await get(db, "SELECT lastUid FROM workspace_uid_seq WHERE id = 1");