diff --git a/data/scripts/acme/acme-ollie-ensure b/data/scripts/acme/acme-ollie-ensure index 45c8ccc..1067ac2 100755 --- a/data/scripts/acme/acme-ollie-ensure +++ b/data/scripts/acme/acme-ollie-ensure @@ -1,14 +1,32 @@ #!/bin/bash # acme-ollie-ensure - Ensure acme session and agent exist # -# One session per project root (git worktree aware), one agent "assistant". -# Session name: acme-{hash of project root} +# One session "acme", one agent per project directory. +# Agent name: parent:basename (e.g., src:ollie, ABC-123:repo) +# If basename contains _, use prefix before first _ (e.g., ABC-123_feature -> ABC-123) # # Usage: eval $(acme-ollie-ensure) # Then use $OLLIE_SESSION and $OLLIE_AGENT set -euo pipefail +# Compute agent name from directory path +# Format: parent:basename (basename split at first _ if present) +agent_name_from_cwd() { + local cwd="$1" + local parent basename prefix + parent=$(basename "$(dirname "$cwd")") + basename=$(basename "$cwd") + # Split basename at first underscore if present + if [[ "$basename" == *_* ]]; then + prefix="${basename%%_*}" + else + prefix="$basename" + fi + # Slugify: lowercase, replace non-alnum with - + echo "${parent}:${prefix}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9:-]/-/g' +} + # Acme sets $PWD to the window's directory for executed commands. # Fall back to tag parsing if $winid is set (e.g. for pipe commands). cwd="$PWD" @@ -28,19 +46,17 @@ if wt_root=$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null); then project_root="$wt_root" fi -# Session name from project root hash -session_hash=$(printf '%s' "$project_root" | md5sum | cut -c1-8) -SESSION="acme-$session_hash" -AGENT="assistant" +SESSION="acme" +AGENT=$(agent_name_from_cwd "$project_root") -# Ensure session exists (with project root as cwd) +# Ensure session exists if ! ollie-9p read "session/$SESSION/env" >/dev/null 2>&1; then - printf 'name=%s\ncwd=%s\n' "$SESSION" "$project_root" | ollie-9p write "session/new" 2>/dev/null || true + printf 'name=%s\ncwd=%s\n' "$SESSION" "$project_root" | ollie-9p rdwr "session/new" >/dev/null 2>&1 || true fi -# Ensure agent exists +# Ensure agent exists (with project root as cwd) if ! ollie-9p read "session/$SESSION/agent/$AGENT/cfg" >/dev/null 2>&1; then - printf 'name=%s\n' "$AGENT" | ollie-9p write "session/$SESSION/agent/new" 2>/dev/null || true + printf 'name=%s\ncwd=%s\n' "$AGENT" "$project_root" | ollie-9p rdwr "session/$SESSION/agent/new" >/dev/null 2>&1 || true fi # Output for eval diff --git a/kde/dolphin/ollie-session-here b/kde/dolphin/ollie-session-here index fb6c0ab..a8ac0bf 100755 --- a/kde/dolphin/ollie-session-here +++ b/kde/dolphin/ollie-session-here @@ -2,7 +2,8 @@ # ollie-session-here: start a session in the given directory # Works from both Dolphin (passes %f) and terminal (uses $PWD). # -# Session is always "default". Agent name is the base directory name. +# Session is always "default". Agent name follows convention: parent:prefix +# where prefix is basename split at first _ (e.g., src:ollie, ABC-123:repo) # Session/agent creation is idempotent. TARGET="$1" @@ -10,12 +11,27 @@ TARGET="$1" [ -z "$TARGET" ] && TARGET="$PWD" TARGET="$(cd "$TARGET" 2>/dev/null && pwd)" || TARGET="$PWD" -# Agent name is base directory name -AGENT_NAME="$(basename "$TARGET")" +# Compute agent name: parent:prefix (prefix = basename up to first _) +PARENT="$(basename "$(dirname "$TARGET")")" +BASENAME="$(basename "$TARGET")" +case "$BASENAME" in + *_*) PREFIX="${BASENAME%%_*}" ;; + *) PREFIX="$BASENAME" ;; +esac +# Slugify: lowercase, replace non-alnum with - +AGENT_NAME="$(printf '%s:%s' "$PARENT" "$PREFIX" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9:-]/-/g')" -# Create default session (idempotent) and named agent -printf 'name=default\ncwd=%s\n' "$TARGET" | ollie-9p write session/new -printf 'name=%s\n' "$AGENT_NAME" | ollie-9p write session/default/agent/new +# Create default session (idempotent) +printf 'name=default\ncwd=%s\n' "$TARGET" | ollie-9p rdwr session/new >/dev/null 2>&1 + +# Create or update agent with computed name +if ollie-9p read "session/default/agent/$AGENT_NAME/cfg" >/dev/null 2>&1; then + # Agent exists, update cwd + printf 'cwd %s' "$TARGET" | ollie-9p rdwr "session/default/agent/$AGENT_NAME/ctl" >/dev/null 2>&1 +else + # Create new agent + printf 'name=%s\ncwd=%s\n' "$AGENT_NAME" "$TARGET" | ollie-9p rdwr session/default/agent/new >/dev/null 2>&1 +fi if [ -t 1 ]; then # Terminal: print session info diff --git a/kde/kate/ollie_kate.cpp b/kde/kate/ollie_kate.cpp index 518fd91..f232abb 100644 --- a/kde/kate/ollie_kate.cpp +++ b/kde/kate/ollie_kate.cpp @@ -180,16 +180,46 @@ OllieKateView::~OllieKateView() } } +// --- Agent Naming --- + +// Compute agent name from directory path +// Format: parent:basename (basename split at first _ if present) +// Examples: /home/user/src/ollie -> src:ollie +// /home/user/proj/ABC-123_feature/repo -> ABC-123:repo +QString OllieKateView::agentNameFromCwd(const QString &cwd) +{ + QFileInfo fi(cwd); + QString parent = QFileInfo(fi.path()).fileName(); // parent directory name + QString basename = fi.fileName(); + + // Split basename at first underscore if present + int underscoreIdx = basename.indexOf('_'); + QString prefix = (underscoreIdx > 0) ? basename.left(underscoreIdx) : basename; + + // Slugify: lowercase, replace non-alnum with - + QString slug = (parent + ":" + prefix).toLower(); + QString result; + for (QChar c : slug) { + if (c.isLetterOrNumber() || c == ':' || c == '-') { + result += c; + } else { + result += '-'; + } + } + return result; +} + // --- Agent Management --- void OllieKateView::ensureAgent() { - if (!m_agentId.isEmpty()) return; + QString cwd = projectBaseDir(); + if (cwd.isEmpty()) cwd = QDir::homePath(); + + QString agentName = agentNameFromCwd(cwd); // Ensure kate session exists if (m_sessionId.isEmpty()) { - QString cwd = projectBaseDir(); - if (cwd.isEmpty()) cwd = QDir::homePath(); QByteArray resp = m_9p->rdwr("session/new", ("name=kate\ncwd=" + cwd + "\n").toUtf8()); if (resp.isEmpty()) { qWarning() << "Ollie Kate: failed to create session"; @@ -198,22 +228,18 @@ void OllieKateView::ensureAgent() m_sessionId = "kate"; } - // Look for an existing agent in the kate session - QStringList agents = m_9p->ls("session/kate/agent"); - agents.removeAll("new"); - agents.removeAll("idx"); - - if (!agents.isEmpty()) { - m_agentId = agents.first(); + // Check if agent with this name already exists + QByteArray check = m_9p->read("session/kate/agent/" + agentName + "/cfg"); + if (!check.isEmpty()) { + m_agentId = agentName; + // Ensure cwd is up to date + m_9p->rdwr("session/kate/agent/" + agentName + "/ctl", ("cwd " + cwd).toUtf8()); } else { - // Create one - QString cwd = projectBaseDir(); - if (cwd.isEmpty()) cwd = QDir::homePath(); - - QString agentCfg = "cwd=" + cwd + "\n"; + // Create new agent with computed name + QString agentCfg = "name=" + agentName + "\ncwd=" + cwd + "\n"; QByteArray resp = m_9p->rdwr("session/kate/agent/new", agentCfg.toUtf8()); if (!resp.isEmpty()) { - m_agentId = QString::fromUtf8(resp).trimmed(); + m_agentId = agentName; } } updateStatusLabel(); @@ -226,26 +252,25 @@ bool OllieKateView::selectAgentForProject() if (sessCheck.isEmpty()) return false; m_sessionId = "kate"; - // Find first agent in the session - QStringList agents = m_9p->ls("session/kate/agent"); - agents.removeAll("new"); - agents.removeAll("idx"); + QString cwd = projectBaseDir(); + if (cwd.isEmpty()) return false; + + QString agentName = agentNameFromCwd(cwd); - if (agents.isEmpty()) return false; - - QString agentId = agents.first(); - if (agentId != m_agentId) { - m_agentId = agentId; - } - - // Update cwd to match current project - QString baseDir = projectBaseDir(); - if (!baseDir.isEmpty() && !m_agentId.isEmpty()) { - m_9p->write(agentPath() + "/ctl", ("cwd " + baseDir).toUtf8()); + // Check if agent exists + QByteArray check = m_9p->read("session/kate/agent/" + agentName + "/cfg"); + if (!check.isEmpty()) { + m_agentId = agentName; + // Ensure cwd is up to date + m_9p->rdwr("session/kate/agent/" + agentName + "/ctl", ("cwd " + cwd).toUtf8()); + updateStatusLabel(); + return true; } + // Agent doesn't exist yet — will be created on first use + m_agentId.clear(); updateStatusLabel(); - return true; + return false; } void OllieKateView::switchToAgent(const QString &agentId) @@ -257,25 +282,35 @@ void OllieKateView::switchToAgent(const QString &agentId) void OllieKateView::onProjectChanged() { - // Update the agent's cwd to the new project directory - QString baseDir = projectBaseDir(); - if (!baseDir.isEmpty() && !m_agentId.isEmpty()) { - m_9p->write(agentPath() + "/ctl", ("cwd " + baseDir).toUtf8()); - } - updateStatusLabel(); + // Switch to the agent for the new project (or create if needed) + selectAgentForProject(); } QString OllieKateView::projectBaseDir() { + // Try Kate's project plugin first QObject *projectView = m_mainWindow->pluginView("kateprojectplugin"); if (projectView) { QString dir = projectView->property("projectBaseDir").toString(); if (!dir.isEmpty()) return dir; } + // Fall back to git root detection from active document auto *view = m_mainWindow->activeView(); if (view && view->document() && !view->document()->url().toLocalFile().isEmpty()) { - return QFileInfo(view->document()->url().toLocalFile()).absolutePath(); + QString docDir = QFileInfo(view->document()->url().toLocalFile()).absolutePath(); + + // Walk up to find .git directory + QDir dir(docDir); + while (!dir.isRoot()) { + if (QFileInfo(dir.filePath(".git")).exists()) { + return dir.absolutePath(); + } + if (!dir.cdUp()) break; + } + + // No git root found, use document directory + return docDir; } return QString(); } diff --git a/kde/kate/ollie_kate.h b/kde/kate/ollie_kate.h index 6d07c64..2e11957 100644 --- a/kde/kate/ollie_kate.h +++ b/kde/kate/ollie_kate.h @@ -71,6 +71,9 @@ private: void reviewDiff(); void startSession(); + // Agent naming + QString agentNameFromCwd(const QString &cwd); + // Agent management void ensureAgent(); bool selectAgentForProject();