@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
@@ -63,6 +63,16 @@ const runSessionCommandOutput = (session, command, args, options = {}) =>
63
63
  env: buildSessionEnv(session, options),
64
64
  });
65
65
 
66
+ const parseCloneDepth = (value) => {
67
+ const parsed = Number.parseInt(String(value ?? "").trim(), 10);
68
+ if (!Number.isFinite(parsed) || parsed <= 0) {
69
+ return 50;
70
+ }
71
+ return parsed;
72
+ };
73
+
74
+ const cloneDepth = parseCloneDepth(process.env.VIBE80_CLONE_DEPTH);
75
+
66
76
  const resolveSessionGitDir = (session) =>
67
77
  session?.gitDir || path.join(session.dir, "git");
68
78
 
@@ -153,14 +163,26 @@ const touchSession = async (session) => {
153
163
  return updated;
154
164
  };
155
165
 
156
- const loadWorktree = async (worktreeId) => {
166
+ const loadWorktree = async (session, worktreeId) => {
157
167
  if (!worktreeId) return null;
158
- return storage.getWorktree(worktreeId);
168
+ const worktree = await storage.getWorktree(session.sessionId, worktreeId);
169
+ if (!worktree) {
170
+ return null;
171
+ }
172
+ if (worktree.sessionId && worktree.sessionId !== session.sessionId) {
173
+ console.warn("Cross-session worktree access blocked", {
174
+ requestedSessionId: session.sessionId,
175
+ worktreeId,
176
+ ownerSessionId: worktree.sessionId,
177
+ });
178
+ return null;
179
+ }
180
+ return worktree;
159
181
  };
160
182
 
161
183
  const ensureMainWorktree = async (session) => {
162
184
  const worktreeId = getMainWorktreeStorageId(session.sessionId);
163
- const existing = await loadWorktree(worktreeId);
185
+ const existing = await loadWorktree(session, worktreeId);
164
186
  if (existing) return existing;
165
187
  const branchName = await resolveCurrentBranchName(session);
166
188
  const worktree = {
@@ -275,7 +297,7 @@ export async function createWorktree(session, options) {
275
297
  let startCommit = "HEAD";
276
298
  let sourceBranchName = "";
277
299
  if (parentWorktreeId) {
278
- const parent = await loadWorktree(parentWorktreeId);
300
+ const parent = await loadWorktree(session, parentWorktreeId);
279
301
  if (parent) {
280
302
  startCommit = await runSessionCommandOutput(
281
303
  session,
@@ -318,8 +340,45 @@ export async function createWorktree(session, options) {
318
340
  }
319
341
  };
320
342
 
343
+ const ensureRemoteBranchAvailable = async (branch) => {
344
+ const normalized = normalizeBranchName(branch);
345
+ if (!normalized) {
346
+ return false;
347
+ }
348
+ const alreadyAvailable = await checkRemoteBranchExists(normalized);
349
+ if (alreadyAvailable) {
350
+ return true;
351
+ }
352
+ const remoteHead = `refs/heads/${normalized}`;
353
+ const localRemoteRef = `refs/remotes/origin/${normalized}`;
354
+ try {
355
+ await runSessionCommand(
356
+ session,
357
+ "git",
358
+ [
359
+ "fetch",
360
+ "--depth",
361
+ String(cloneDepth),
362
+ "origin",
363
+ `${remoteHead}:${localRemoteRef}`,
364
+ ],
365
+ { cwd: session.repoDir }
366
+ );
367
+ } catch {
368
+ return false;
369
+ }
370
+ return checkRemoteBranchExists(normalized);
371
+ };
372
+
373
+ if (startingBranch) {
374
+ const normalizedStartingBranch = normalizeBranchName(startingBranch);
375
+ if (normalizedStartingBranch) {
376
+ await ensureRemoteBranchAvailable(normalizedStartingBranch);
377
+ }
378
+ }
379
+
321
380
  const remoteBranchExists = requestedBranchName
322
- ? await checkRemoteBranchExists(requestedBranchName)
381
+ ? await ensureRemoteBranchAvailable(requestedBranchName)
323
382
  : false;
324
383
 
325
384
  if (requestedBranchName) {
@@ -436,7 +495,7 @@ export async function removeWorktree(session, worktreeId, deleteBranch = true) {
436
495
  if (isMainWorktreeStorageId(session, resolvedId)) {
437
496
  throw new Error("Main worktree cannot be removed");
438
497
  }
439
- const worktree = await loadWorktree(resolvedId);
498
+ const worktree = await loadWorktree(session, resolvedId);
440
499
  if (!worktree) {
441
500
  throw new Error("Worktree not found");
442
501
  }
@@ -477,7 +536,7 @@ export async function removeWorktree(session, worktreeId, deleteBranch = true) {
477
536
 
478
537
  export async function getWorktreeDiff(session, worktreeId) {
479
538
  const resolvedId = resolveWorktreeStorageId(session, worktreeId);
480
- const worktree = await loadWorktree(resolvedId);
539
+ const worktree = await loadWorktree(session, resolvedId);
481
540
  if (!worktree) {
482
541
  throw new Error("Worktree not found");
483
542
  }
@@ -492,7 +551,7 @@ export async function getWorktreeDiff(session, worktreeId) {
492
551
 
493
552
  export async function getWorktreeCommits(session, worktreeId, limit = 20) {
494
553
  const resolvedId = resolveWorktreeStorageId(session, worktreeId);
495
- const worktree = await loadWorktree(resolvedId);
554
+ const worktree = await loadWorktree(session, resolvedId);
496
555
  if (!worktree) {
497
556
  throw new Error("Worktree not found");
498
557
  }
@@ -515,11 +574,17 @@ export async function getWorktreeCommits(session, worktreeId, limit = 20) {
515
574
  }
516
575
 
517
576
  export async function mergeWorktree(session, sourceWorktreeId, targetWorktreeId) {
518
- const source = await loadWorktree(resolveWorktreeStorageId(session, sourceWorktreeId));
577
+ const source = await loadWorktree(
578
+ session,
579
+ resolveWorktreeStorageId(session, sourceWorktreeId)
580
+ );
519
581
  if (!source) {
520
582
  throw new Error("Source worktree not found");
521
583
  }
522
- const target = await loadWorktree(resolveWorktreeStorageId(session, targetWorktreeId));
584
+ const target = await loadWorktree(
585
+ session,
586
+ resolveWorktreeStorageId(session, targetWorktreeId)
587
+ );
523
588
  if (!target) {
524
589
  throw new Error("Target worktree not found");
525
590
  }
@@ -548,7 +613,7 @@ export async function mergeWorktree(session, sourceWorktreeId, targetWorktreeId)
548
613
  }
549
614
 
550
615
  export async function abortMerge(session, worktreeId) {
551
- const worktree = await loadWorktree(resolveWorktreeStorageId(session, worktreeId));
616
+ const worktree = await loadWorktree(session, resolveWorktreeStorageId(session, worktreeId));
552
617
  if (!worktree) {
553
618
  throw new Error("Worktree not found");
554
619
  }
@@ -556,7 +621,10 @@ export async function abortMerge(session, worktreeId) {
556
621
  }
557
622
 
558
623
  export async function cherryPickCommit(session, commitSha, targetWorktreeId) {
559
- const target = await loadWorktree(resolveWorktreeStorageId(session, targetWorktreeId));
624
+ const target = await loadWorktree(
625
+ session,
626
+ resolveWorktreeStorageId(session, targetWorktreeId)
627
+ );
560
628
  if (!target) {
561
629
  throw new Error("Target worktree not found");
562
630
  }
@@ -618,7 +686,7 @@ export async function getWorktree(session, worktreeId) {
618
686
  const mainWorktree = await ensureMainWorktree(session);
619
687
  return mainWorktree;
620
688
  }
621
- const worktree = await loadWorktree(resolvedId);
689
+ const worktree = await loadWorktree(session, resolvedId);
622
690
  if (!worktree) return null;
623
691
  const runtime = getSessionRuntime(session.sessionId);
624
692
  if (runtime?.worktreeClients?.has(resolvedId)) {
@@ -629,7 +697,7 @@ export async function getWorktree(session, worktreeId) {
629
697
 
630
698
  export async function updateWorktreeStatus(session, worktreeId, status) {
631
699
  const resolvedId = resolveWorktreeStorageId(session, worktreeId);
632
- let worktree = await loadWorktree(resolvedId);
700
+ let worktree = await loadWorktree(session, resolvedId);
633
701
  if (!worktree && isMainWorktreeStorageId(session, resolvedId)) {
634
702
  worktree = await ensureMainWorktree(session);
635
703
  }
@@ -644,7 +712,7 @@ export async function updateWorktreeStatus(session, worktreeId, status) {
644
712
 
645
713
  export async function updateWorktreeThreadId(session, worktreeId, threadId) {
646
714
  const resolvedId = resolveWorktreeStorageId(session, worktreeId);
647
- const worktree = await loadWorktree(resolvedId);
715
+ const worktree = await loadWorktree(session, resolvedId);
648
716
  if (!worktree || !threadId) return;
649
717
  const updated = {
650
718
  ...worktree,
@@ -661,7 +729,7 @@ export async function updateWorktreeModel(
661
729
  reasoningEffort = null
662
730
  ) {
663
731
  const resolvedId = resolveWorktreeStorageId(session, worktreeId);
664
- let worktree = await loadWorktree(resolvedId);
732
+ let worktree = await loadWorktree(session, resolvedId);
665
733
  if (!worktree && isMainWorktreeStorageId(session, resolvedId)) {
666
734
  worktree = await ensureMainWorktree(session);
667
735
  }
@@ -677,7 +745,7 @@ export async function updateWorktreeModel(
677
745
 
678
746
  export async function appendWorktreeMessage(session, worktreeId, message) {
679
747
  const resolvedId = resolveWorktreeStorageId(session, worktreeId);
680
- let worktree = await loadWorktree(resolvedId);
748
+ let worktree = await loadWorktree(session, resolvedId);
681
749
  if (!worktree && isMainWorktreeStorageId(session, resolvedId)) {
682
750
  worktree = await ensureMainWorktree(session);
683
751
  }
@@ -694,7 +762,7 @@ export async function appendWorktreeMessage(session, worktreeId, message) {
694
762
 
695
763
  export async function clearWorktreeMessages(session, worktreeId) {
696
764
  const resolvedId = resolveWorktreeStorageId(session, worktreeId);
697
- let worktree = await loadWorktree(resolvedId);
765
+ let worktree = await loadWorktree(session, resolvedId);
698
766
  if (!worktree && isMainWorktreeStorageId(session, resolvedId)) {
699
767
  worktree = await ensureMainWorktree(session);
700
768
  }
@@ -710,7 +778,7 @@ export async function clearWorktreeMessages(session, worktreeId) {
710
778
  export async function renameWorktree(session, worktreeId, newName) {
711
779
  const resolvedId = resolveWorktreeStorageId(session, worktreeId);
712
780
  if (isMainWorktreeStorageId(session, resolvedId)) return;
713
- const worktree = await loadWorktree(resolvedId);
781
+ const worktree = await loadWorktree(session, resolvedId);
714
782
  if (!worktree || !newName) return;
715
783
  const updated = { ...worktree, name: newName };
716
784
  await storage.saveWorktree(session.sessionId, resolvedId, serializeWorktree(updated));
@@ -49,7 +49,7 @@ describe("routes/workspaces", () => {
49
49
 
50
50
  beforeEach(() => {
51
51
  vi.clearAllMocks();
52
- process.env.DEPLOYMENT_MODE = "multi_user";
52
+ process.env.VIBE80_DEPLOYMENT_MODE = "multi_user";
53
53
  issueWorkspaceTokensMock.mockResolvedValue({
54
54
  workspaceToken: "workspace-token",
55
55
  refreshToken: "refresh-token",
@@ -89,7 +89,7 @@ describe("routes/workspaces", () => {
89
89
  });
90
90
 
91
91
  it("POST /api/v1/workspaces/login mono_auth_token renvoie 403 hors mono_user", async () => {
92
- process.env.DEPLOYMENT_MODE = "multi_user";
92
+ process.env.VIBE80_DEPLOYMENT_MODE = "multi_user";
93
93
  const handler = await createRouteHandler("/workspaces/login", "post");
94
94
  const res = createMockRes();
95
95
  await handler(
@@ -104,7 +104,7 @@ describe("routes/workspaces", () => {
104
104
  });
105
105
 
106
106
  it("POST /api/v1/workspaces renvoie 403 en mode mono_user", async () => {
107
- process.env.DEPLOYMENT_MODE = "mono_user";
107
+ process.env.VIBE80_DEPLOYMENT_MODE = "mono_user";
108
108
  const handler = await createRouteHandler("/workspaces", "post");
109
109
  const res = createMockRes();
110
110
  await handler(
@@ -124,7 +124,7 @@ describe("routes/workspaces", () => {
124
124
  });
125
125
 
126
126
  it("POST /api/v1/workspaces/login credentials renvoie 403 en mode mono_user", async () => {
127
- process.env.DEPLOYMENT_MODE = "mono_user";
127
+ process.env.VIBE80_DEPLOYMENT_MODE = "mono_user";
128
128
  const handler = await createRouteHandler("/workspaces/login", "post");
129
129
  const res = createMockRes();
130
130
  await handler(
@@ -145,7 +145,7 @@ describe("routes/workspaces", () => {
145
145
  });
146
146
 
147
147
  it("POST /api/v1/workspaces/login mono_auth_token renvoie 400 si token manquant", async () => {
148
- process.env.DEPLOYMENT_MODE = "mono_user";
148
+ process.env.VIBE80_DEPLOYMENT_MODE = "mono_user";
149
149
  const handler = await createRouteHandler("/workspaces/login", "post");
150
150
  const res = createMockRes();
151
151
  await handler({ body: { grantType: "mono_auth_token" } }, res);
@@ -157,7 +157,7 @@ describe("routes/workspaces", () => {
157
157
  });
158
158
 
159
159
  it("POST /api/v1/workspaces/login mono_auth_token renvoie 401 si token invalide", async () => {
160
- process.env.DEPLOYMENT_MODE = "mono_user";
160
+ process.env.VIBE80_DEPLOYMENT_MODE = "mono_user";
161
161
  consumeMonoAuthTokenMock.mockReturnValueOnce({
162
162
  ok: false,
163
163
  code: "MONO_AUTH_TOKEN_INVALID",
@@ -176,7 +176,7 @@ describe("routes/workspaces", () => {
176
176
  });
177
177
 
178
178
  it("POST /api/v1/workspaces/login mono_auth_token renvoie 401 si workspace non résolu", async () => {
179
- process.env.DEPLOYMENT_MODE = "mono_user";
179
+ process.env.VIBE80_DEPLOYMENT_MODE = "mono_user";
180
180
  consumeMonoAuthTokenMock.mockReturnValueOnce({
181
181
  ok: true,
182
182
  workspaceId: validCredentials.workspaceId,
@@ -201,7 +201,7 @@ describe("routes/workspaces", () => {
201
201
  });
202
202
 
203
203
  it("POST /api/v1/workspaces/login mono_auth_token renvoie 200 en succès", async () => {
204
- process.env.DEPLOYMENT_MODE = "mono_user";
204
+ process.env.VIBE80_DEPLOYMENT_MODE = "mono_user";
205
205
  consumeMonoAuthTokenMock.mockReturnValueOnce({
206
206
  ok: true,
207
207
  workspaceId: validCredentials.workspaceId,
@@ -2,8 +2,8 @@ import path from "path";
2
2
  import os from "os";
3
3
 
4
4
  process.env.NODE_ENV = "test";
5
- process.env.STORAGE_BACKEND = process.env.STORAGE_BACKEND || "sqlite";
6
- process.env.DEPLOYMENT_MODE = process.env.DEPLOYMENT_MODE || "multi_user";
7
- process.env.JWT_KEY = process.env.JWT_KEY || "test-jwt-key";
8
- process.env.JWT_KEY_PATH =
9
- process.env.JWT_KEY_PATH || path.join(os.tmpdir(), "vibe80-jwt-test.key");
5
+ process.env.VIBE80_STORAGE_BACKEND = process.env.VIBE80_STORAGE_BACKEND || "sqlite";
6
+ process.env.VIBE80_DEPLOYMENT_MODE = process.env.VIBE80_DEPLOYMENT_MODE || "multi_user";
7
+ process.env.VIBE80_JWT_KEY = process.env.VIBE80_JWT_KEY || "test-jwt-key";
8
+ process.env.VIBE80_JWT_KEY_PATH =
9
+ process.env.VIBE80_JWT_KEY_PATH || path.join(os.tmpdir(), "vibe80-jwt-test.key");
@@ -34,7 +34,7 @@ describe("services/auth", () => {
34
34
  vi.clearAllMocks();
35
35
  idSeq.value = 0;
36
36
  vi.useRealTimers();
37
- delete process.env.MONO_AUTH_TOKEN_TTL_MS;
37
+ delete process.env.VIBE80_MONO_AUTH_TOKEN_TTL_MS;
38
38
  });
39
39
 
40
40
  it("issueWorkspaceTokens crée un refresh token indépendant", async () => {
@@ -128,7 +128,7 @@ describe("services/auth", () => {
128
128
 
129
129
  it("consumeMonoAuthToken retourne EXPIRED pour un token expiré", async () => {
130
130
  vi.useFakeTimers();
131
- process.env.MONO_AUTH_TOKEN_TTL_MS = "1";
131
+ process.env.VIBE80_MONO_AUTH_TOKEN_TTL_MS = "1";
132
132
  const { createMonoAuthToken, consumeMonoAuthToken } = await loadAuthModule();
133
133
  const record = createMonoAuthToken("default");
134
134
  vi.advanceTimersByTime(10);
@@ -162,7 +162,7 @@ describe("services/auth", () => {
162
162
 
163
163
  it("cleanupMonoAuthTokens purge les tokens expirés", async () => {
164
164
  vi.useFakeTimers();
165
- process.env.MONO_AUTH_TOKEN_TTL_MS = "1";
165
+ process.env.VIBE80_MONO_AUTH_TOKEN_TTL_MS = "1";
166
166
  const {
167
167
  createMonoAuthToken,
168
168
  cleanupMonoAuthTokens,