@vibe80/vibe80 0.2.0 → 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.
- package/README.md +132 -16
- package/bin/vibe80.js +1728 -16
- package/client/dist/assets/{DiffPanel-BKLnyIAZ.js → DiffPanel-BUJhQj_Q.js} +1 -1
- package/client/dist/assets/{ExplorerPanel-D3IbBsXz.js → ExplorerPanel-DugEeaO2.js} +1 -1
- package/client/dist/assets/{LogsPanel-BwJAFHRP.js → LogsPanel-BQrGxMu_.js} +1 -1
- package/client/dist/assets/{SettingsPanel-BfkchMnR.js → SettingsPanel-Ci2BdIYO.js} +1 -1
- package/client/dist/assets/{TerminalPanel-BQfMEm-u.js → TerminalPanel-C-T3t-6T.js} +1 -1
- package/client/dist/assets/index-cFi4LM0j.js +711 -0
- package/client/dist/assets/index-qNyFxUjK.css +32 -0
- package/client/dist/icon_square-512x512.png +0 -0
- package/client/dist/icon_square.svg +58 -0
- package/client/dist/index.html +3 -2
- package/client/dist/sw.js +1 -1
- package/client/index.html +1 -0
- package/client/public/icon_square-512x512.png +0 -0
- package/client/public/icon_square.svg +58 -0
- package/client/src/App.jsx +205 -2
- package/client/src/assets/vibe80_dark.png +0 -0
- package/client/src/assets/vibe80_light.png +0 -0
- package/client/src/components/Chat/ChatMessages.jsx +1 -1
- package/client/src/components/SessionGate/SessionGate.jsx +295 -91
- package/client/src/components/WorktreeTabs.css +11 -0
- package/client/src/components/WorktreeTabs.jsx +77 -47
- package/client/src/hooks/useChatSocket.js +8 -7
- package/client/src/hooks/useRepoBranchesModels.js +12 -6
- package/client/src/hooks/useWorktreeCloseConfirm.js +19 -7
- package/client/src/hooks/useWorktrees.js +3 -1
- package/client/src/index.css +26 -3
- package/client/src/locales/en.json +12 -1
- package/client/src/locales/fr.json +12 -1
- package/docs/api/openapi.json +1 -1
- package/package.json +2 -1
- package/server/scripts/rotate-workspace-secret.js +1 -1
- package/server/src/claudeClient.js +3 -3
- package/server/src/codexClient.js +3 -3
- package/server/src/config.js +6 -6
- package/server/src/index.js +14 -12
- package/server/src/middleware/auth.js +7 -7
- package/server/src/middleware/debug.js +36 -4
- package/server/src/providerLogger.js +2 -2
- package/server/src/routes/sessions.js +133 -21
- package/server/src/routes/workspaces.js +1 -1
- package/server/src/runAs.js +14 -14
- package/server/src/services/auth.js +3 -3
- package/server/src/services/session.js +182 -14
- package/server/src/services/workspace.js +86 -42
- package/server/src/storage/index.js +2 -2
- package/server/src/storage/redis.js +38 -36
- package/server/src/storage/sqlite.js +13 -13
- package/server/src/worktreeManager.js +87 -19
- package/server/tests/integration/routes/workspaces-routes.test.js +8 -8
- package/server/tests/setup/env.js +5 -5
- package/server/tests/unit/services/auth.test.js +3 -3
- package/client/dist/assets/index-BDQQz6SJ.css +0 -32
- package/client/dist/assets/index-D1UJw1oP.js +0 -711
|
@@ -47,7 +47,6 @@ export default function useChatSocket({
|
|
|
47
47
|
maybeNotify,
|
|
48
48
|
normalizeAttachments,
|
|
49
49
|
loadRepoLastCommit,
|
|
50
|
-
loadBranches,
|
|
51
50
|
loadWorktreeLastCommit,
|
|
52
51
|
openAiLoginRequest,
|
|
53
52
|
setOpenAiLoginRequest,
|
|
@@ -264,12 +263,14 @@ export default function useChatSocket({
|
|
|
264
263
|
id: payload.itemId,
|
|
265
264
|
role: "assistant",
|
|
266
265
|
text: payload.delta,
|
|
266
|
+
isStreaming: true,
|
|
267
267
|
});
|
|
268
268
|
return next;
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
const updated = { ...next[existingIndex] };
|
|
272
272
|
updated.text += payload.delta;
|
|
273
|
+
updated.isStreaming = true;
|
|
273
274
|
next[existingIndex] = updated;
|
|
274
275
|
return next;
|
|
275
276
|
});
|
|
@@ -294,6 +295,7 @@ export default function useChatSocket({
|
|
|
294
295
|
id: payload.itemId,
|
|
295
296
|
role: "assistant",
|
|
296
297
|
text: payload.text,
|
|
298
|
+
isStreaming: false,
|
|
297
299
|
});
|
|
298
300
|
return next;
|
|
299
301
|
}
|
|
@@ -301,6 +303,7 @@ export default function useChatSocket({
|
|
|
301
303
|
next[existingIndex] = {
|
|
302
304
|
...next[existingIndex],
|
|
303
305
|
text: payload.text,
|
|
306
|
+
isStreaming: false,
|
|
304
307
|
};
|
|
305
308
|
return next;
|
|
306
309
|
});
|
|
@@ -366,9 +369,6 @@ export default function useChatSocket({
|
|
|
366
369
|
});
|
|
367
370
|
if (payload.request === "run" || payload.request === "git") {
|
|
368
371
|
void loadRepoLastCommit();
|
|
369
|
-
if (typeof loadBranches === "function") {
|
|
370
|
-
void loadBranches();
|
|
371
|
-
}
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
374
|
|
|
@@ -823,9 +823,6 @@ export default function useChatSocket({
|
|
|
823
823
|
});
|
|
824
824
|
if (payload.request === "run" || payload.request === "git") {
|
|
825
825
|
void loadWorktreeLastCommit(wtId);
|
|
826
|
-
if (typeof loadBranches === "function" && wtId === "main") {
|
|
827
|
-
void loadBranches();
|
|
828
|
-
}
|
|
829
826
|
}
|
|
830
827
|
}
|
|
831
828
|
|
|
@@ -924,12 +921,14 @@ export default function useChatSocket({
|
|
|
924
921
|
id: payload.itemId,
|
|
925
922
|
role: "assistant",
|
|
926
923
|
text: payload.delta || "",
|
|
924
|
+
isStreaming: true,
|
|
927
925
|
});
|
|
928
926
|
} else {
|
|
929
927
|
messages[existingIdx] = {
|
|
930
928
|
...messages[existingIdx],
|
|
931
929
|
text:
|
|
932
930
|
(messages[existingIdx].text || "") + (payload.delta || ""),
|
|
931
|
+
isStreaming: true,
|
|
933
932
|
};
|
|
934
933
|
}
|
|
935
934
|
} else {
|
|
@@ -938,11 +937,13 @@ export default function useChatSocket({
|
|
|
938
937
|
id: payload.itemId,
|
|
939
938
|
role: "assistant",
|
|
940
939
|
text: payload.text || "",
|
|
940
|
+
isStreaming: false,
|
|
941
941
|
});
|
|
942
942
|
} else {
|
|
943
943
|
messages[existingIdx] = {
|
|
944
944
|
...messages[existingIdx],
|
|
945
945
|
text: payload.text || "",
|
|
946
|
+
isStreaming: false,
|
|
946
947
|
};
|
|
947
948
|
}
|
|
948
949
|
}
|
|
@@ -23,12 +23,14 @@ export default function useRepoBranchesModels({
|
|
|
23
23
|
const [branchError, setBranchError] = useState("");
|
|
24
24
|
const initialBranchRef = useRef("");
|
|
25
25
|
|
|
26
|
-
const loadBranches = useCallback(async () => {
|
|
26
|
+
const loadBranches = useCallback(async ({ background = false } = {}) => {
|
|
27
27
|
if (!attachmentSessionId) {
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
if (!background) {
|
|
31
|
+
setBranchLoading(true);
|
|
32
|
+
setBranchError("");
|
|
33
|
+
}
|
|
32
34
|
try {
|
|
33
35
|
const response = await apiFetch(
|
|
34
36
|
`/api/v1/sessions/${encodeURIComponent(
|
|
@@ -46,9 +48,13 @@ export default function useRepoBranchesModels({
|
|
|
46
48
|
setDefaultBranch(payload.current);
|
|
47
49
|
}
|
|
48
50
|
} catch (error) {
|
|
49
|
-
|
|
51
|
+
if (!background) {
|
|
52
|
+
setBranchError(error.message || t("Unable to load branches."));
|
|
53
|
+
}
|
|
50
54
|
} finally {
|
|
51
|
-
|
|
55
|
+
if (!background) {
|
|
56
|
+
setBranchLoading(false);
|
|
57
|
+
}
|
|
52
58
|
}
|
|
53
59
|
}, [attachmentSessionId, apiFetch, t]);
|
|
54
60
|
|
|
@@ -110,7 +116,7 @@ export default function useRepoBranchesModels({
|
|
|
110
116
|
initialBranchRef.current = "";
|
|
111
117
|
setDefaultBranch("");
|
|
112
118
|
setProviderModelState({});
|
|
113
|
-
loadBranches();
|
|
119
|
+
loadBranches({ background: true });
|
|
114
120
|
}, [attachmentSessionId, loadBranches]);
|
|
115
121
|
|
|
116
122
|
useEffect(() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback } from "react";
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
2
|
|
|
3
3
|
export default function useWorktreeCloseConfirm({
|
|
4
4
|
closeConfirm,
|
|
@@ -7,6 +7,8 @@ export default function useWorktreeCloseConfirm({
|
|
|
7
7
|
activeWorktreeIdRef,
|
|
8
8
|
closeWorktree,
|
|
9
9
|
}) {
|
|
10
|
+
const [closeConfirmDeleting, setCloseConfirmDeleting] = useState(false);
|
|
11
|
+
|
|
10
12
|
const openCloseConfirm = useCallback(
|
|
11
13
|
(worktreeId) => {
|
|
12
14
|
if (!worktreeId || worktreeId === "main") {
|
|
@@ -18,21 +20,30 @@ export default function useWorktreeCloseConfirm({
|
|
|
18
20
|
);
|
|
19
21
|
|
|
20
22
|
const closeCloseConfirm = useCallback(() => {
|
|
23
|
+
if (closeConfirmDeleting) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
21
26
|
setCloseConfirm(null);
|
|
22
|
-
}, [setCloseConfirm]);
|
|
27
|
+
}, [closeConfirmDeleting, setCloseConfirm]);
|
|
23
28
|
|
|
24
29
|
const handleConfirmDelete = useCallback(async () => {
|
|
25
|
-
if (!closeConfirm?.worktreeId) {
|
|
30
|
+
if (!closeConfirm?.worktreeId || closeConfirmDeleting) {
|
|
26
31
|
return;
|
|
27
32
|
}
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
setCloseConfirmDeleting(true);
|
|
34
|
+
try {
|
|
35
|
+
if (activeWorktreeIdRef.current === closeConfirm.worktreeId) {
|
|
36
|
+
setActiveWorktreeId("main");
|
|
37
|
+
}
|
|
38
|
+
await closeWorktree(closeConfirm.worktreeId);
|
|
39
|
+
setCloseConfirm(null);
|
|
40
|
+
} finally {
|
|
41
|
+
setCloseConfirmDeleting(false);
|
|
30
42
|
}
|
|
31
|
-
await closeWorktree(closeConfirm.worktreeId);
|
|
32
|
-
setCloseConfirm(null);
|
|
33
43
|
}, [
|
|
34
44
|
activeWorktreeIdRef,
|
|
35
45
|
closeConfirm,
|
|
46
|
+
closeConfirmDeleting,
|
|
36
47
|
closeWorktree,
|
|
37
48
|
setActiveWorktreeId,
|
|
38
49
|
setCloseConfirm,
|
|
@@ -42,5 +53,6 @@ export default function useWorktreeCloseConfirm({
|
|
|
42
53
|
openCloseConfirm,
|
|
43
54
|
closeCloseConfirm,
|
|
44
55
|
handleConfirmDelete,
|
|
56
|
+
closeConfirmDeleting,
|
|
45
57
|
};
|
|
46
58
|
}
|
|
@@ -249,7 +249,7 @@ export default function useWorktrees({
|
|
|
249
249
|
}) => {
|
|
250
250
|
if (!attachmentSessionId) {
|
|
251
251
|
showToast?.(t("Session not found."), "error");
|
|
252
|
-
return;
|
|
252
|
+
return false;
|
|
253
253
|
}
|
|
254
254
|
try {
|
|
255
255
|
const response = await apiFetch(
|
|
@@ -313,11 +313,13 @@ export default function useWorktrees({
|
|
|
313
313
|
}));
|
|
314
314
|
setActiveWorktreeId(payload.worktreeId);
|
|
315
315
|
void requestWorktreesList();
|
|
316
|
+
return true;
|
|
316
317
|
} catch (error) {
|
|
317
318
|
showToast?.(
|
|
318
319
|
error.message || t("Failed to create parallel request."),
|
|
319
320
|
"error"
|
|
320
321
|
);
|
|
322
|
+
return false;
|
|
321
323
|
}
|
|
322
324
|
},
|
|
323
325
|
[
|
package/client/src/index.css
CHANGED
|
@@ -633,14 +633,21 @@ textarea {
|
|
|
633
633
|
|
|
634
634
|
.session-item {
|
|
635
635
|
display: flex;
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
gap: 12px;
|
|
636
|
+
flex-direction: column;
|
|
637
|
+
gap: 10px;
|
|
639
638
|
padding: 10px 12px;
|
|
640
639
|
border-radius: 12px;
|
|
641
640
|
background: rgba(20, 19, 17, 0.05);
|
|
642
641
|
}
|
|
643
642
|
|
|
643
|
+
.session-item-row {
|
|
644
|
+
display: flex;
|
|
645
|
+
justify-content: space-between;
|
|
646
|
+
align-items: center;
|
|
647
|
+
gap: 12px;
|
|
648
|
+
width: 100%;
|
|
649
|
+
}
|
|
650
|
+
|
|
644
651
|
.session-item-meta {
|
|
645
652
|
display: flex;
|
|
646
653
|
flex-direction: column;
|
|
@@ -669,6 +676,15 @@ textarea {
|
|
|
669
676
|
cursor: pointer;
|
|
670
677
|
}
|
|
671
678
|
|
|
679
|
+
.session-list-icon-button {
|
|
680
|
+
width: 36px;
|
|
681
|
+
height: 36px;
|
|
682
|
+
padding: 0;
|
|
683
|
+
display: inline-flex;
|
|
684
|
+
align-items: center;
|
|
685
|
+
justify-content: center;
|
|
686
|
+
}
|
|
687
|
+
|
|
672
688
|
.session-list-button.is-danger {
|
|
673
689
|
background: #e05a4f;
|
|
674
690
|
}
|
|
@@ -684,6 +700,13 @@ textarea {
|
|
|
684
700
|
align-items: center;
|
|
685
701
|
}
|
|
686
702
|
|
|
703
|
+
.session-config-actions {
|
|
704
|
+
display: flex;
|
|
705
|
+
justify-content: flex-end;
|
|
706
|
+
gap: 8px;
|
|
707
|
+
margin-top: 10px;
|
|
708
|
+
}
|
|
709
|
+
|
|
687
710
|
.toast-container {
|
|
688
711
|
position: fixed;
|
|
689
712
|
right: 24px;
|
|
@@ -33,5 +33,16 @@
|
|
|
33
33
|
"Annotations": "Annotations",
|
|
34
34
|
"Only sent with the next message.": "Only sent with the next message.",
|
|
35
35
|
"No annotations yet.": "No annotations yet.",
|
|
36
|
-
"Write annotation...": "Write annotation..."
|
|
36
|
+
"Write annotation...": "Write annotation...",
|
|
37
|
+
"Configure session": "Configurer la session",
|
|
38
|
+
"Keep current credentials": "Conserver les identifiants actuels",
|
|
39
|
+
"Session name": "Nom de la session",
|
|
40
|
+
"Session name is required.": "Le nom de session est requis.",
|
|
41
|
+
"Session updated.": "Session mise a jour.",
|
|
42
|
+
"Failed to update session.": "Echec de la mise a jour de la session.",
|
|
43
|
+
"Private SSH key is required.": "La cle SSH privee est requise.",
|
|
44
|
+
"Validate": "Valider",
|
|
45
|
+
"Keep unchanged": "Conserver tel quel",
|
|
46
|
+
"Update & Resume": "Mettre a jour et reprendre",
|
|
47
|
+
"Updating...": "Mise a jour..."
|
|
37
48
|
}
|
|
@@ -317,5 +317,16 @@
|
|
|
317
317
|
"{{count}} item(s)": "{{count}} élément(s)",
|
|
318
318
|
"{{count}} KB": "{{count}} Ko",
|
|
319
319
|
"{{count}} lines": "{{count}} lignes",
|
|
320
|
-
"{{count}} MB": "{{count}} Mo"
|
|
320
|
+
"{{count}} MB": "{{count}} Mo",
|
|
321
|
+
"Configure session": "Configurer la session",
|
|
322
|
+
"Keep current credentials": "Conserver les identifiants actuels",
|
|
323
|
+
"Session name": "Nom de la session",
|
|
324
|
+
"Session name is required.": "Le nom de session est requis.",
|
|
325
|
+
"Session updated.": "Session mise a jour.",
|
|
326
|
+
"Failed to update session.": "Echec de la mise a jour de la session.",
|
|
327
|
+
"Private SSH key is required.": "La cle SSH privee est requise.",
|
|
328
|
+
"Validate": "Valider",
|
|
329
|
+
"Keep unchanged": "Conserver tel quel",
|
|
330
|
+
"Update & Resume": "Mettre a jour et reprendre",
|
|
331
|
+
"Updating...": "Mise a jour..."
|
|
321
332
|
}
|
package/docs/api/openapi.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibe80/vibe80",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"workspaces": [
|
|
6
6
|
"server",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"prepack": "npm --workspace client run build"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
+
"commander": "^12.1.0",
|
|
34
35
|
"concurrently": "^8.2.2",
|
|
35
36
|
"docker-names": "^1.2.1",
|
|
36
37
|
"express": "^4.19.2",
|
|
@@ -13,7 +13,7 @@ const printUsage = () => {
|
|
|
13
13
|
" node server/scripts/rotate-workspace-secret.js --workspace-id <workspaceId> [--workspace-secret <secret>] [--json]",
|
|
14
14
|
"",
|
|
15
15
|
"Requirements:",
|
|
16
|
-
" - SERVER env vars must be set (e.g.
|
|
16
|
+
" - SERVER env vars must be set (e.g. VIBE80_STORAGE_BACKEND, VIBE80_DEPLOYMENT_MODE, etc.)",
|
|
17
17
|
].join("\n")
|
|
18
18
|
);
|
|
19
19
|
};
|
|
@@ -2,13 +2,13 @@ import { spawn } from "child_process";
|
|
|
2
2
|
import { EventEmitter } from "events";
|
|
3
3
|
import crypto from "crypto";
|
|
4
4
|
import path from "path";
|
|
5
|
-
import {
|
|
5
|
+
import { VIBE80_SYSTEM_PROMPT } from "./config.js";
|
|
6
6
|
import { createProviderLogger } from "./providerLogger.js";
|
|
7
7
|
import { buildSandboxArgs, getWorkspaceHome, runAsCommand } from "./runAs.js";
|
|
8
8
|
|
|
9
9
|
const RUN_AS_HELPER = process.env.VIBE80_RUN_AS_HELPER || "/usr/local/bin/vibe80-run-as";
|
|
10
10
|
const SUDO_PATH = process.env.VIBE80_SUDO_PATH || "sudo";
|
|
11
|
-
const isMonoUser = process.env.
|
|
11
|
+
const isMonoUser = process.env.VIBE80_DEPLOYMENT_MODE === "mono_user";
|
|
12
12
|
|
|
13
13
|
const createTurnId = () =>
|
|
14
14
|
typeof crypto.randomUUID === "function"
|
|
@@ -62,7 +62,7 @@ export class ClaudeCliClient extends EventEmitter {
|
|
|
62
62
|
sessionId: this.sessionId,
|
|
63
63
|
worktreeId: this.worktreeId,
|
|
64
64
|
});
|
|
65
|
-
this.systemPrompt =
|
|
65
|
+
this.systemPrompt = VIBE80_SYSTEM_PROMPT;
|
|
66
66
|
this.activeProcess = null;
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
2
|
import { EventEmitter } from "events";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import {
|
|
4
|
+
import { VIBE80_SYSTEM_PROMPT } from "./config.js";
|
|
5
5
|
import { createProviderLogger } from "./providerLogger.js";
|
|
6
6
|
import { buildSandboxArgs, getWorkspaceHome } from "./runAs.js";
|
|
7
7
|
|
|
8
8
|
const RUN_AS_HELPER = process.env.VIBE80_RUN_AS_HELPER || "/usr/local/bin/vibe80-run-as";
|
|
9
9
|
const SUDO_PATH = process.env.VIBE80_SUDO_PATH || "sudo";
|
|
10
|
-
const isMonoUser = process.env.
|
|
10
|
+
const isMonoUser = process.env.VIBE80_DEPLOYMENT_MODE === "mono_user";
|
|
11
11
|
|
|
12
12
|
export class CodexAppServerClient extends EventEmitter {
|
|
13
13
|
constructor({
|
|
@@ -430,7 +430,7 @@ export class CodexAppServerClient extends EventEmitter {
|
|
|
430
430
|
"sandbox_workspace_write.network_access": Boolean(this.internetAccess),
|
|
431
431
|
"web_search": this.internetAccess ? "live" : "disabled"
|
|
432
432
|
},
|
|
433
|
-
baseInstructions:
|
|
433
|
+
baseInstructions: VIBE80_SYSTEM_PROMPT,
|
|
434
434
|
sandbox: sandboxMode,
|
|
435
435
|
approvalPolicy: "never"
|
|
436
436
|
};
|
package/server/src/config.js
CHANGED
|
@@ -4,8 +4,8 @@ import { fileURLToPath } from "url";
|
|
|
4
4
|
const __filename = fileURLToPath(import.meta.url);
|
|
5
5
|
const __dirname = path.dirname(__filename);
|
|
6
6
|
|
|
7
|
-
export const
|
|
8
|
-
process.env.
|
|
7
|
+
export const VIBE80_SYSTEM_PROMPT =
|
|
8
|
+
process.env.VIBE80_SYSTEM_PROMPT ||
|
|
9
9
|
"output markdown format for inline generated text;" +
|
|
10
10
|
"Reference files using relative paths when possible; " +
|
|
11
11
|
"When proposing possible next steps, use: " +
|
|
@@ -18,10 +18,10 @@ export const SYSTEM_PROMPT =
|
|
|
18
18
|
"Use <!-- vibe80:task <short_task_description> --> to notify the user about what you are doing;" +
|
|
19
19
|
"Use <!-- vibe80:fileref <filepath> --> to reference any file in the current repository";
|
|
20
20
|
|
|
21
|
-
export const
|
|
22
|
-
process.env.
|
|
23
|
-
export const
|
|
24
|
-
process.env.
|
|
21
|
+
export const VIBE80_DEFAULT_GIT_AUTHOR_NAME =
|
|
22
|
+
process.env.VIBE80_DEFAULT_GIT_AUTHOR_NAME || "Vibe80 agent";
|
|
23
|
+
export const VIBE80_DEFAULT_GIT_AUTHOR_EMAIL =
|
|
24
|
+
process.env.VIBE80_DEFAULT_GIT_AUTHOR_EMAIL || "vibe80@example.org";
|
|
25
25
|
|
|
26
26
|
export const GIT_HOOKS_DIR = process.env.VIBE80_GIT_HOOKS_DIR
|
|
27
27
|
|| path.resolve(__dirname, "../../git_hooks");
|
package/server/src/index.js
CHANGED
|
@@ -85,7 +85,7 @@ const __dirname = path.dirname(__filename);
|
|
|
85
85
|
const app = express();
|
|
86
86
|
const server = http.createServer(app);
|
|
87
87
|
const wss = new WebSocketServer({ noServer: true });
|
|
88
|
-
const trustProxySetting = process.env.
|
|
88
|
+
const trustProxySetting = process.env.VIBE80_TRUST_PROXY;
|
|
89
89
|
if (trustProxySetting !== undefined) {
|
|
90
90
|
const normalized = trustProxySetting.trim().toLowerCase();
|
|
91
91
|
if (normalized === "true") {
|
|
@@ -102,7 +102,7 @@ if (trustProxySetting !== undefined) {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
const terminalRequested = !/^(0|false|no|off)$/i.test(
|
|
105
|
-
process.env.
|
|
105
|
+
process.env.VIBE80_TERMINAL_ENABLED || ""
|
|
106
106
|
);
|
|
107
107
|
let pty = null;
|
|
108
108
|
let terminalAvailable = false;
|
|
@@ -119,17 +119,17 @@ if (terminalRequested) {
|
|
|
119
119
|
}
|
|
120
120
|
const terminalEnabled = terminalRequested && terminalAvailable;
|
|
121
121
|
const allowRunSlashCommand = !/^(0|false|no|off)$/i.test(
|
|
122
|
-
process.env.
|
|
122
|
+
process.env.VIBE80_ALLOW_RUN_SLASH_COMMAND || ""
|
|
123
123
|
);
|
|
124
124
|
const allowGitSlashCommand = !/^(0|false|no|off)$/i.test(
|
|
125
|
-
process.env.
|
|
125
|
+
process.env.VIBE80_ALLOW_GIT_SLASH_COMMAND || ""
|
|
126
126
|
);
|
|
127
127
|
const codexIdleTtlSeconds = Number.parseInt(
|
|
128
|
-
process.env.
|
|
128
|
+
process.env.VIBE80_CODEX_IDLE_TTL_SECONDS || "300",
|
|
129
129
|
10
|
|
130
130
|
);
|
|
131
131
|
const codexIdleGcIntervalSeconds = Number.parseInt(
|
|
132
|
-
process.env.
|
|
132
|
+
process.env.VIBE80_CODEX_IDLE_GC_INTERVAL_SECONDS || "60",
|
|
133
133
|
10
|
|
134
134
|
);
|
|
135
135
|
const worktreeStatusIntervalMs = 10 * 1000;
|
|
@@ -157,9 +157,9 @@ const resolveWorkspaceTokenErrorCode = (error) => {
|
|
|
157
157
|
return "WORKSPACE_TOKEN_INVALID";
|
|
158
158
|
};
|
|
159
159
|
|
|
160
|
-
const deploymentMode = process.env.
|
|
160
|
+
const deploymentMode = process.env.VIBE80_DEPLOYMENT_MODE || "mono_user";
|
|
161
161
|
if (deploymentMode !== "mono_user" && deploymentMode !== "multi_user") {
|
|
162
|
-
console.error(`Invalid
|
|
162
|
+
console.error(`Invalid VIBE80_DEPLOYMENT_MODE: ${deploymentMode}. Use mono_user or multi_user.`);
|
|
163
163
|
process.exit(1);
|
|
164
164
|
}
|
|
165
165
|
|
|
@@ -182,8 +182,10 @@ const apiLimiter = rateLimit({
|
|
|
182
182
|
});
|
|
183
183
|
|
|
184
184
|
const authLimiter = rateLimit({
|
|
185
|
-
windowMs: 60 * 1000,
|
|
186
|
-
max:
|
|
185
|
+
windowMs: 5 * 60 * 1000,
|
|
186
|
+
max: 5,
|
|
187
|
+
skipSuccessfulRequests: true,
|
|
188
|
+
requestWasSuccessful: (_req, res) => res.statusCode !== 401,
|
|
187
189
|
standardHeaders: true,
|
|
188
190
|
legacyHeaders: false,
|
|
189
191
|
});
|
|
@@ -1508,12 +1510,12 @@ process.on("SIGINT", () => {
|
|
|
1508
1510
|
void gracefulShutdown("SIGINT");
|
|
1509
1511
|
});
|
|
1510
1512
|
|
|
1511
|
-
const port = process.env.
|
|
1513
|
+
const port = process.env.VIBE80_PORT || 5179;
|
|
1512
1514
|
server.listen(port, async () => {
|
|
1513
1515
|
console.log(`Server listening on http://localhost:${port}`);
|
|
1514
1516
|
if (deploymentMode === "mono_user") {
|
|
1515
1517
|
const monoAuthRecord = createMonoAuthToken("default");
|
|
1516
|
-
const monoAuthOrigin = process.env.
|
|
1518
|
+
const monoAuthOrigin = process.env.VIBE80_MONO_AUTH_ORIGIN || `http://127.0.0.1:${port}`;
|
|
1517
1519
|
const monoAuthUrl = `${monoAuthOrigin.replace(/\/+$/, "")}/#mono_auth=${encodeURIComponent(
|
|
1518
1520
|
monoAuthRecord.token
|
|
1519
1521
|
)}`;
|
|
@@ -5,20 +5,20 @@ import path from "path";
|
|
|
5
5
|
import jwt from "jsonwebtoken";
|
|
6
6
|
|
|
7
7
|
const homeDir = process.env.HOME || os.homedir();
|
|
8
|
-
const isMonoUser = process.env.
|
|
8
|
+
const isMonoUser = process.env.VIBE80_DEPLOYMENT_MODE === "mono_user";
|
|
9
9
|
const defaultDataDirectory = isMonoUser
|
|
10
10
|
? path.join(homeDir, ".vibe80")
|
|
11
11
|
: "/var/lib/vibe80";
|
|
12
12
|
const dataDirectory = process.env.VIBE80_DATA_DIRECTORY || defaultDataDirectory;
|
|
13
|
-
const jwtKeyPath = process.env.
|
|
14
|
-
const jwtIssuer = process.env.
|
|
15
|
-
const jwtAudience = process.env.
|
|
13
|
+
const jwtKeyPath = process.env.VIBE80_JWT_KEY_PATH || path.join(dataDirectory, "jwt.key");
|
|
14
|
+
const jwtIssuer = process.env.VIBE80_JWT_ISSUER || "vibe80";
|
|
15
|
+
const jwtAudience = process.env.VIBE80_JWT_AUDIENCE || "workspace";
|
|
16
16
|
const accessTokenTtlSeconds =
|
|
17
|
-
Number(process.env.
|
|
17
|
+
Number(process.env.VIBE80_ACCESS_TOKEN_TTL_SECONDS) || 60 * 60;
|
|
18
18
|
|
|
19
19
|
const loadJwtKey = () => {
|
|
20
|
-
if (process.env.
|
|
21
|
-
return process.env.
|
|
20
|
+
if (process.env.VIBE80_JWT_KEY) {
|
|
21
|
+
return process.env.VIBE80_JWT_KEY;
|
|
22
22
|
}
|
|
23
23
|
if (fs.existsSync(jwtKeyPath)) {
|
|
24
24
|
return fs.readFileSync(jwtKeyPath, "utf8").trim();
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createDebugId, formatDebugPayload } from "../helpers.js";
|
|
2
2
|
|
|
3
3
|
const debugApiWsLog = /^(1|true|yes|on)$/i.test(
|
|
4
|
-
process.env.
|
|
4
|
+
process.env.VIBE80_DEBUG_API_WS_LOG || ""
|
|
5
5
|
);
|
|
6
|
-
const debugLogMaxBody = Number.isFinite(Number(process.env.
|
|
7
|
-
? Number(process.env.
|
|
6
|
+
const debugLogMaxBody = Number.isFinite(Number(process.env.VIBE80_DEBUG_API_WS_LOG_MAX_BODY))
|
|
7
|
+
? Number(process.env.VIBE80_DEBUG_API_WS_LOG_MAX_BODY)
|
|
8
8
|
: 2000;
|
|
9
9
|
|
|
10
10
|
export { debugApiWsLog };
|
|
@@ -14,6 +14,38 @@ export const logDebug = (...args) => {
|
|
|
14
14
|
console.log(...args);
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
const SENSITIVE_KEYS = new Set([
|
|
18
|
+
"password",
|
|
19
|
+
"privatekey",
|
|
20
|
+
"token",
|
|
21
|
+
"refreshtoken",
|
|
22
|
+
"workspaceSecret",
|
|
23
|
+
"workspacesecret",
|
|
24
|
+
"httpPassword",
|
|
25
|
+
"httppassword",
|
|
26
|
+
"sshkey",
|
|
27
|
+
"auth",
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
const redactSensitive = (value) => {
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
return value.map((item) => redactSensitive(item));
|
|
33
|
+
}
|
|
34
|
+
if (!value || typeof value !== "object") {
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
const next = {};
|
|
38
|
+
Object.entries(value).forEach(([key, entry]) => {
|
|
39
|
+
const normalized = String(key || "").toLowerCase();
|
|
40
|
+
if (SENSITIVE_KEYS.has(key) || SENSITIVE_KEYS.has(normalized)) {
|
|
41
|
+
next[key] = "[REDACTED]";
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
next[key] = redactSensitive(entry);
|
|
45
|
+
});
|
|
46
|
+
return next;
|
|
47
|
+
};
|
|
48
|
+
|
|
17
49
|
export const attachWebSocketDebug = (socket, req, label) => {
|
|
18
50
|
if (!debugApiWsLog) return;
|
|
19
51
|
const connectionId = createDebugId();
|
|
@@ -61,7 +93,7 @@ export function debugMiddleware(req, res, next) {
|
|
|
61
93
|
method: req.method,
|
|
62
94
|
url: req.originalUrl,
|
|
63
95
|
query: req.query,
|
|
64
|
-
body: formatDebugPayload(req.body, debugLogMaxBody),
|
|
96
|
+
body: formatDebugPayload(redactSensitive(req.body), debugLogMaxBody),
|
|
65
97
|
});
|
|
66
98
|
|
|
67
99
|
let responseBody;
|
|
@@ -3,7 +3,7 @@ import os from "os";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
|
|
5
5
|
const isLoggingEnabled = () => {
|
|
6
|
-
const value = process.env.
|
|
6
|
+
const value = process.env.VIBE80_ACTIVATE_PROVIDER_LOG;
|
|
7
7
|
if (!value) {
|
|
8
8
|
return false;
|
|
9
9
|
}
|
|
@@ -19,7 +19,7 @@ export const createProviderLogger = ({ provider, sessionId, worktreeId }) => {
|
|
|
19
19
|
}
|
|
20
20
|
const safeWorktreeId = worktreeId || "main";
|
|
21
21
|
const baseDir =
|
|
22
|
-
process.env.
|
|
22
|
+
process.env.VIBE80_PROVIDER_LOG_DIRECTORY || path.join(os.homedir(), "logs");
|
|
23
23
|
try {
|
|
24
24
|
fs.mkdirSync(baseDir, { recursive: true, mode: 0o700 });
|
|
25
25
|
const filePath = path.join(
|