Files
Loki/internal/jean/chat_workspace.go
T
nathaninline 1f3961eaa9 v0.6.10 : dossier de travail pour l'agent, lancement Windows explicite, mise à jour lisible, icône noire
- mode agent : les outils write/edit/bash résolvent les chemins relatifs dans
  JEAN_HOME/workspace et le shell y démarre, au lieu du répertoire courant du
  processus (Bureau, ProgramData\jean\bin). Les chemins absolus demandés
  explicitement restent honorés.
- Windows : le double-clic n'installe plus en silence. Boîte de dialogue unique,
  raccourcis menu Démarrer + Bureau, puis relance depuis la copie installée — un
  seul binaire fait foi. Désinstallation : retrait des raccourcis.
- update : contrôle des droits AVANT le téléchargement (l'échec tombait sur le
  fichier temporaire, avec un message brut), message donnant la commande exacte,
  timeout UI porté de 30 s à 10 min.
- jean where + bouton « Où sont mes fichiers ? » : emplacements, et alerte quand
  le binaire lancé n'est pas celui installé.
- icônes : source unique (sys_brand_icon.go) pour la zone de notification, la
  barre de menus macOS et l'icône du .exe, alignée sur le favicon noir. macOS en
  icône template (une icône noire figée disparaît sur une barre sombre).
- CI : le bundle macOS part de icon.png au lieu de convertir le .ico via sips,
  dont l'échec était masqué par « || true » ; notes de release versionnées.
2026-08-04 11:17:43 +02:00

88 lines
2.6 KiB
Go

package jean
import (
"os"
"path/filepath"
"strings"
"sync"
)
// Dossier de travail du mode agent.
//
// Sans ça, les outils write/edit/bash héritent du répertoire courant du PROCESSUS :
// le modèle écrit "meteo.json", et le fichier atterrit là d'où l'utilisateur a
// lancé jean — le Bureau quand on double-clique l'installateur, C:\ProgramData\jean\bin
// quand on lance le binaire installé. Personne ne s'attend à voir un chat déposer
// des fichiers sur son Bureau. On donne donc à l'agent un dossier à lui : les
// chemins relatifs y sont résolus, et le shell y démarre. Les chemins ABSOLUS
// restent honorés tels quels — quand l'utilisateur demande d'écrire dans un
// dossier précis, ça doit marcher.
const workspaceEnv = "JEAN_WORKSPACE"
var (
workspaceOnce sync.Once
workspacePath string
)
// agentWorkspace renvoie le dossier de travail de l'agent, créé au besoin. Il
// essaie plusieurs emplacements car JeanHome() vaut %ProgramData%\jean sous
// Windows, qui n'est pas inscriptible par un utilisateur non administrateur.
func agentWorkspace() string {
workspaceOnce.Do(func() {
for _, dir := range workspaceCandidates() {
if dir == "" {
continue
}
if err := os.MkdirAll(dir, 0o755); err != nil {
continue
}
// MkdirAll réussit sur un dossier existant même non inscriptible :
// on vérifie l'écriture réelle.
f, err := os.CreateTemp(dir, ".probe-*")
if err != nil {
continue
}
name := f.Name()
f.Close()
os.Remove(name)
workspacePath = dir
return
}
// Dernier recours : le répertoire courant, comportement historique.
workspacePath, _ = os.Getwd()
})
return workspacePath
}
func workspaceCandidates() []string {
var c []string
if v := strings.TrimSpace(os.Getenv(workspaceEnv)); v != "" {
c = append(c, v)
}
c = append(c, filepath.Join(JeanHome(), "workspace"))
if home, err := os.UserHomeDir(); err == nil && home != "" {
c = append(c, filepath.Join(home, "jean", "workspace"))
}
c = append(c, filepath.Join(os.TempDir(), "jean-workspace"))
return c
}
// resolveAgentPath résout un chemin fourni par le modèle. Absolu → inchangé ;
// "~/x" → dans le home de l'utilisateur ; relatif → dans le workspace.
func resolveAgentPath(p string) string {
p = strings.TrimSpace(p)
if p == "" {
return p
}
if p == "~" || strings.HasPrefix(p, "~/") || strings.HasPrefix(p, `~\`) {
if home, err := os.UserHomeDir(); err == nil && home != "" {
return filepath.Join(home, filepath.FromSlash(strings.TrimPrefix(p[1:], "/")))
}
}
if filepath.IsAbs(p) {
return p
}
return filepath.Join(agentWorkspace(), filepath.FromSlash(p))
}