consistent agent naming: parent:prefix convention
Agent names derived from cwd using parent:prefix format: - /home/user/src/ollie -> src:ollie - /home/user/proj/ABC-123_feature/repo -> abc-123:repo Updates: - Kate plugin: one agent per project, git root fallback if no project plugin - acme-ollie-ensure: single 'acme' session, per-project agents - ollie-session-here: single 'default' session, per-project agents Also fixed write -> rdwr for session/agent creation endpoints
This commit is contained in:
parent
1c02b50add
commit
d5efe575ce
|
|
@ -1,14 +1,32 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# acme-ollie-ensure - Ensure acme session and agent exist
|
# acme-ollie-ensure - Ensure acme session and agent exist
|
||||||
#
|
#
|
||||||
# One session per project root (git worktree aware), one agent "assistant".
|
# One session "acme", one agent per project directory.
|
||||||
# Session name: acme-{hash of project root}
|
# 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)
|
# Usage: eval $(acme-ollie-ensure)
|
||||||
# Then use $OLLIE_SESSION and $OLLIE_AGENT
|
# Then use $OLLIE_SESSION and $OLLIE_AGENT
|
||||||
|
|
||||||
set -euo pipefail
|
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.
|
# 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).
|
# Fall back to tag parsing if $winid is set (e.g. for pipe commands).
|
||||||
cwd="$PWD"
|
cwd="$PWD"
|
||||||
|
|
@ -28,19 +46,17 @@ if wt_root=$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null); then
|
||||||
project_root="$wt_root"
|
project_root="$wt_root"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Session name from project root hash
|
SESSION="acme"
|
||||||
session_hash=$(printf '%s' "$project_root" | md5sum | cut -c1-8)
|
AGENT=$(agent_name_from_cwd "$project_root")
|
||||||
SESSION="acme-$session_hash"
|
|
||||||
AGENT="assistant"
|
|
||||||
|
|
||||||
# Ensure session exists (with project root as cwd)
|
# Ensure session exists
|
||||||
if ! ollie-9p read "session/$SESSION/env" >/dev/null 2>&1; then
|
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
|
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
|
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
|
fi
|
||||||
|
|
||||||
# Output for eval
|
# Output for eval
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
# ollie-session-here: start a session in the given directory
|
# ollie-session-here: start a session in the given directory
|
||||||
# Works from both Dolphin (passes %f) and terminal (uses $PWD).
|
# 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.
|
# Session/agent creation is idempotent.
|
||||||
|
|
||||||
TARGET="$1"
|
TARGET="$1"
|
||||||
|
|
@ -10,12 +11,27 @@ TARGET="$1"
|
||||||
[ -z "$TARGET" ] && TARGET="$PWD"
|
[ -z "$TARGET" ] && TARGET="$PWD"
|
||||||
TARGET="$(cd "$TARGET" 2>/dev/null && pwd)" || TARGET="$PWD"
|
TARGET="$(cd "$TARGET" 2>/dev/null && pwd)" || TARGET="$PWD"
|
||||||
|
|
||||||
# Agent name is base directory name
|
# Compute agent name: parent:prefix (prefix = basename up to first _)
|
||||||
AGENT_NAME="$(basename "$TARGET")"
|
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
|
# Create default session (idempotent)
|
||||||
printf 'name=default\ncwd=%s\n' "$TARGET" | ollie-9p write session/new
|
printf 'name=default\ncwd=%s\n' "$TARGET" | ollie-9p rdwr session/new >/dev/null 2>&1
|
||||||
printf 'name=%s\n' "$AGENT_NAME" | ollie-9p write session/default/agent/new
|
|
||||||
|
# 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
|
if [ -t 1 ]; then
|
||||||
# Terminal: print session info
|
# Terminal: print session info
|
||||||
|
|
|
||||||
|
|
@ -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 ---
|
// --- Agent Management ---
|
||||||
|
|
||||||
void OllieKateView::ensureAgent()
|
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
|
// Ensure kate session exists
|
||||||
if (m_sessionId.isEmpty()) {
|
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());
|
QByteArray resp = m_9p->rdwr("session/new", ("name=kate\ncwd=" + cwd + "\n").toUtf8());
|
||||||
if (resp.isEmpty()) {
|
if (resp.isEmpty()) {
|
||||||
qWarning() << "Ollie Kate: failed to create session";
|
qWarning() << "Ollie Kate: failed to create session";
|
||||||
|
|
@ -198,22 +228,18 @@ void OllieKateView::ensureAgent()
|
||||||
m_sessionId = "kate";
|
m_sessionId = "kate";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for an existing agent in the kate session
|
// Check if agent with this name already exists
|
||||||
QStringList agents = m_9p->ls("session/kate/agent");
|
QByteArray check = m_9p->read("session/kate/agent/" + agentName + "/cfg");
|
||||||
agents.removeAll("new");
|
if (!check.isEmpty()) {
|
||||||
agents.removeAll("idx");
|
m_agentId = agentName;
|
||||||
|
// Ensure cwd is up to date
|
||||||
if (!agents.isEmpty()) {
|
m_9p->rdwr("session/kate/agent/" + agentName + "/ctl", ("cwd " + cwd).toUtf8());
|
||||||
m_agentId = agents.first();
|
|
||||||
} else {
|
} else {
|
||||||
// Create one
|
// Create new agent with computed name
|
||||||
QString cwd = projectBaseDir();
|
QString agentCfg = "name=" + agentName + "\ncwd=" + cwd + "\n";
|
||||||
if (cwd.isEmpty()) cwd = QDir::homePath();
|
|
||||||
|
|
||||||
QString agentCfg = "cwd=" + cwd + "\n";
|
|
||||||
QByteArray resp = m_9p->rdwr("session/kate/agent/new", agentCfg.toUtf8());
|
QByteArray resp = m_9p->rdwr("session/kate/agent/new", agentCfg.toUtf8());
|
||||||
if (!resp.isEmpty()) {
|
if (!resp.isEmpty()) {
|
||||||
m_agentId = QString::fromUtf8(resp).trimmed();
|
m_agentId = agentName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateStatusLabel();
|
updateStatusLabel();
|
||||||
|
|
@ -226,26 +252,25 @@ bool OllieKateView::selectAgentForProject()
|
||||||
if (sessCheck.isEmpty()) return false;
|
if (sessCheck.isEmpty()) return false;
|
||||||
m_sessionId = "kate";
|
m_sessionId = "kate";
|
||||||
|
|
||||||
// Find first agent in the session
|
QString cwd = projectBaseDir();
|
||||||
QStringList agents = m_9p->ls("session/kate/agent");
|
if (cwd.isEmpty()) return false;
|
||||||
agents.removeAll("new");
|
|
||||||
agents.removeAll("idx");
|
QString agentName = agentNameFromCwd(cwd);
|
||||||
|
|
||||||
if (agents.isEmpty()) return false;
|
// Check if agent exists
|
||||||
|
QByteArray check = m_9p->read("session/kate/agent/" + agentName + "/cfg");
|
||||||
QString agentId = agents.first();
|
if (!check.isEmpty()) {
|
||||||
if (agentId != m_agentId) {
|
m_agentId = agentName;
|
||||||
m_agentId = agentId;
|
// Ensure cwd is up to date
|
||||||
}
|
m_9p->rdwr("session/kate/agent/" + agentName + "/ctl", ("cwd " + cwd).toUtf8());
|
||||||
|
updateStatusLabel();
|
||||||
// Update cwd to match current project
|
return true;
|
||||||
QString baseDir = projectBaseDir();
|
|
||||||
if (!baseDir.isEmpty() && !m_agentId.isEmpty()) {
|
|
||||||
m_9p->write(agentPath() + "/ctl", ("cwd " + baseDir).toUtf8());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Agent doesn't exist yet — will be created on first use
|
||||||
|
m_agentId.clear();
|
||||||
updateStatusLabel();
|
updateStatusLabel();
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OllieKateView::switchToAgent(const QString &agentId)
|
void OllieKateView::switchToAgent(const QString &agentId)
|
||||||
|
|
@ -257,25 +282,35 @@ void OllieKateView::switchToAgent(const QString &agentId)
|
||||||
|
|
||||||
void OllieKateView::onProjectChanged()
|
void OllieKateView::onProjectChanged()
|
||||||
{
|
{
|
||||||
// Update the agent's cwd to the new project directory
|
// Switch to the agent for the new project (or create if needed)
|
||||||
QString baseDir = projectBaseDir();
|
selectAgentForProject();
|
||||||
if (!baseDir.isEmpty() && !m_agentId.isEmpty()) {
|
|
||||||
m_9p->write(agentPath() + "/ctl", ("cwd " + baseDir).toUtf8());
|
|
||||||
}
|
|
||||||
updateStatusLabel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString OllieKateView::projectBaseDir()
|
QString OllieKateView::projectBaseDir()
|
||||||
{
|
{
|
||||||
|
// Try Kate's project plugin first
|
||||||
QObject *projectView = m_mainWindow->pluginView("kateprojectplugin");
|
QObject *projectView = m_mainWindow->pluginView("kateprojectplugin");
|
||||||
if (projectView) {
|
if (projectView) {
|
||||||
QString dir = projectView->property("projectBaseDir").toString();
|
QString dir = projectView->property("projectBaseDir").toString();
|
||||||
if (!dir.isEmpty()) return dir;
|
if (!dir.isEmpty()) return dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fall back to git root detection from active document
|
||||||
auto *view = m_mainWindow->activeView();
|
auto *view = m_mainWindow->activeView();
|
||||||
if (view && view->document() && !view->document()->url().toLocalFile().isEmpty()) {
|
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();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,9 @@ private:
|
||||||
void reviewDiff();
|
void reviewDiff();
|
||||||
void startSession();
|
void startSession();
|
||||||
|
|
||||||
|
// Agent naming
|
||||||
|
QString agentNameFromCwd(const QString &cwd);
|
||||||
|
|
||||||
// Agent management
|
// Agent management
|
||||||
void ensureAgent();
|
void ensureAgent();
|
||||||
bool selectAgentForProject();
|
bool selectAgentForProject();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue