phase 2: interaction + state queries
- GetConfig/SetConfig: per-session config (model, backend, agent, cwd) - GetContext: JSONL message history - PeerAdd/PeerRemove/PeerList/PeerSubmit: agent-to-agent communication - End-to-end tested: CreateSession → Submit → GetChat → KillSession - 25 C exports total, all wired through D-Bus
This commit is contained in:
parent
318153493d
commit
8531671556
|
|
@ -62,6 +62,27 @@
|
|||
<arg name="cost" type="s" direction="out"/>
|
||||
</method>
|
||||
|
||||
<!-- Config -->
|
||||
<method name="GetConfig">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="config" type="s" direction="out"/>
|
||||
<!-- key=value\n pairs -->
|
||||
</method>
|
||||
|
||||
<method name="SetConfig">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="key" type="s" direction="in"/>
|
||||
<arg name="value" type="s" direction="in"/>
|
||||
<arg name="success" type="b" direction="out"/>
|
||||
</method>
|
||||
|
||||
<!-- Context -->
|
||||
<method name="GetContext">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="context" type="s" direction="out"/>
|
||||
<!-- JSONL: one JSON message per line -->
|
||||
</method>
|
||||
|
||||
<!-- Model/backend management -->
|
||||
<method name="ListBackends">
|
||||
<arg name="backends" type="as" direction="out"/>
|
||||
|
|
@ -77,6 +98,31 @@
|
|||
<arg name="agents" type="as" direction="out"/>
|
||||
</method>
|
||||
|
||||
<!-- Peers -->
|
||||
<method name="PeerAdd">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="peer_id" type="s" direction="in"/>
|
||||
<arg name="success" type="b" direction="out"/>
|
||||
</method>
|
||||
|
||||
<method name="PeerRemove">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="peer_id" type="s" direction="in"/>
|
||||
<arg name="success" type="b" direction="out"/>
|
||||
</method>
|
||||
|
||||
<method name="PeerList">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="peers" type="as" direction="out"/>
|
||||
</method>
|
||||
|
||||
<method name="PeerSubmit">
|
||||
<arg name="session_id" type="s" direction="in"/>
|
||||
<arg name="peer_id" type="s" direction="in"/>
|
||||
<arg name="prompt" type="s" direction="in"/>
|
||||
<arg name="success" type="b" direction="out"/>
|
||||
</method>
|
||||
|
||||
<!-- Signals -->
|
||||
<signal name="SessionCreated">
|
||||
<arg name="session_id" type="s"/>
|
||||
|
|
|
|||
|
|
@ -191,6 +191,44 @@ QString SessionManager::GetCost(const QString &session_id)
|
|||
return cost;
|
||||
}
|
||||
|
||||
// --- Config ---
|
||||
|
||||
QString SessionManager::GetConfig(const QString &session_id)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
char *out = nullptr;
|
||||
int rc = ollie_get_config(const_cast<char*>(idUtf8.constData()), &out);
|
||||
if (rc != 0 || !out) return QString();
|
||||
QString config = QString::fromUtf8(out);
|
||||
ollie_free(out);
|
||||
return config;
|
||||
}
|
||||
|
||||
bool SessionManager::SetConfig(const QString &session_id, const QString &key, const QString &value)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
QByteArray keyUtf8 = key.toUtf8();
|
||||
QByteArray valueUtf8 = value.toUtf8();
|
||||
int rc = ollie_set_config(
|
||||
const_cast<char*>(idUtf8.constData()),
|
||||
const_cast<char*>(keyUtf8.constData()),
|
||||
const_cast<char*>(valueUtf8.constData()));
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
// --- Context ---
|
||||
|
||||
QString SessionManager::GetContext(const QString &session_id)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
char *out = nullptr;
|
||||
int rc = ollie_get_context(const_cast<char*>(idUtf8.constData()), &out);
|
||||
if (rc != 0 || !out) return QString();
|
||||
QString context = QString::fromUtf8(out);
|
||||
ollie_free(out);
|
||||
return context;
|
||||
}
|
||||
|
||||
// --- Backends/models/agents ---
|
||||
|
||||
QStringList SessionManager::ListBackends()
|
||||
|
|
@ -223,3 +261,48 @@ QStringList SessionManager::ListAgents()
|
|||
ollie_free(out);
|
||||
return raw.split('\n', Qt::SkipEmptyParts);
|
||||
}
|
||||
|
||||
// --- Peers ---
|
||||
|
||||
bool SessionManager::PeerAdd(const QString &session_id, const QString &peer_id)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
QByteArray peerUtf8 = peer_id.toUtf8();
|
||||
int rc = ollie_peer_add(
|
||||
const_cast<char*>(idUtf8.constData()),
|
||||
const_cast<char*>(peerUtf8.constData()));
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
bool SessionManager::PeerRemove(const QString &session_id, const QString &peer_id)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
QByteArray peerUtf8 = peer_id.toUtf8();
|
||||
int rc = ollie_peer_remove(
|
||||
const_cast<char*>(idUtf8.constData()),
|
||||
const_cast<char*>(peerUtf8.constData()));
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
QStringList SessionManager::PeerList(const QString &session_id)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
char *out = nullptr;
|
||||
ollie_peer_list(const_cast<char*>(idUtf8.constData()), &out);
|
||||
if (!out) return {};
|
||||
QString raw = QString::fromUtf8(out);
|
||||
ollie_free(out);
|
||||
return raw.split('\n', Qt::SkipEmptyParts);
|
||||
}
|
||||
|
||||
bool SessionManager::PeerSubmit(const QString &session_id, const QString &peer_id, const QString &prompt)
|
||||
{
|
||||
QByteArray idUtf8 = session_id.toUtf8();
|
||||
QByteArray peerUtf8 = peer_id.toUtf8();
|
||||
QByteArray promptUtf8 = prompt.toUtf8();
|
||||
int rc = ollie_peer_submit(
|
||||
const_cast<char*>(idUtf8.constData()),
|
||||
const_cast<char*>(peerUtf8.constData()),
|
||||
const_cast<char*>(promptUtf8.constData()));
|
||||
return rc == 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,11 +36,24 @@ public slots:
|
|||
QString GetUsage(const QString &session_id);
|
||||
QString GetCost(const QString &session_id);
|
||||
|
||||
// Config
|
||||
QString GetConfig(const QString &session_id);
|
||||
bool SetConfig(const QString &session_id, const QString &key, const QString &value);
|
||||
|
||||
// Context
|
||||
QString GetContext(const QString &session_id);
|
||||
|
||||
// Backends/models/agents
|
||||
QStringList ListBackends();
|
||||
QStringList ListModels(const QString &session_id);
|
||||
QStringList ListAgents();
|
||||
|
||||
// Peers
|
||||
bool PeerAdd(const QString &session_id, const QString &peer_id);
|
||||
bool PeerRemove(const QString &session_id, const QString &peer_id);
|
||||
QStringList PeerList(const QString &session_id);
|
||||
bool PeerSubmit(const QString &session_id, const QString &peer_id, const QString &prompt);
|
||||
|
||||
signals:
|
||||
void SessionCreated(const QString &session_id);
|
||||
void SessionKilled(const QString &session_id);
|
||||
|
|
|
|||
162
lib/capi.go
162
lib/capi.go
|
|
@ -19,6 +19,7 @@ static inline void ollie_chat_cb_invoke(ollie_chat_cb cb, const char *sid, int64
|
|||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
|
@ -27,6 +28,7 @@ import (
|
|||
"ollie/pkg/agent"
|
||||
"ollie/pkg/backend"
|
||||
"ollie/pkg/config"
|
||||
"ollie/pkg/env"
|
||||
"ollie/pkg/paths"
|
||||
"ollie/pkg/tools"
|
||||
"ollie/pkg/tools/execute"
|
||||
|
|
@ -50,12 +52,14 @@ type managedSession struct {
|
|||
agent string
|
||||
log []byte
|
||||
logMu sync.Mutex
|
||||
peers map[string]bool
|
||||
}
|
||||
|
||||
// --- Lifecycle ---
|
||||
|
||||
//export ollie_init
|
||||
func ollie_init(configPath *C.char) C.int {
|
||||
env.EnsureDefaults()
|
||||
mu.Lock()
|
||||
sessions = make(map[string]*managedSession)
|
||||
mu.Unlock()
|
||||
|
|
@ -381,6 +385,164 @@ func ollie_get_cost(sessionIDC *C.char, out **C.char) C.int {
|
|||
return 0
|
||||
}
|
||||
|
||||
//export ollie_get_config
|
||||
func ollie_get_config(sessionIDC *C.char, outBuf **C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
|
||||
mu.RLock()
|
||||
sess, ok := sessions[id]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Expose key=value pairs for session config
|
||||
var result string
|
||||
result += "model=" + sess.core.ModelName() + "\n"
|
||||
result += "backend=" + sess.core.BackendName() + "\n"
|
||||
result += "agent=" + sess.agent + "\n"
|
||||
result += "cwd=" + sess.core.CWD() + "\n"
|
||||
result += "state=" + sess.core.State() + "\n"
|
||||
*outBuf = C.CString(result)
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_set_config
|
||||
func ollie_set_config(sessionIDC *C.char, keyC *C.char, valueC *C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
key := C.GoString(keyC)
|
||||
value := C.GoString(valueC)
|
||||
|
||||
mu.RLock()
|
||||
sess, ok := sessions[id]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
switch key {
|
||||
case "model":
|
||||
// Submit a /model command to change model
|
||||
sess.core.Submit(sess.ctx, "/model "+value)
|
||||
case "cwd":
|
||||
if err := sess.core.SetCWD(value); err != nil {
|
||||
return -1
|
||||
}
|
||||
default:
|
||||
return -1 // unknown key
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_get_context
|
||||
func ollie_get_context(sessionIDC *C.char, outBuf **C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
|
||||
mu.RLock()
|
||||
sess, ok := sessions[id]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Return context as JSONL (one JSON message per line)
|
||||
msgs := sess.core.Context()
|
||||
var result string
|
||||
for _, msg := range msgs {
|
||||
data, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
result += string(data) + "\n"
|
||||
}
|
||||
*outBuf = C.CString(result)
|
||||
return 0
|
||||
}
|
||||
|
||||
// --- Peers ---
|
||||
|
||||
//export ollie_peer_add
|
||||
func ollie_peer_add(sessionIDC *C.char, peerIDC *C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
peerID := C.GoString(peerIDC)
|
||||
|
||||
mu.RLock()
|
||||
_, ok := sessions[id]
|
||||
_, peerOk := sessions[peerID]
|
||||
mu.RUnlock()
|
||||
if !ok || !peerOk {
|
||||
return -1
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
sess := sessions[id]
|
||||
if sess.peers == nil {
|
||||
sess.peers = make(map[string]bool)
|
||||
}
|
||||
sess.peers[peerID] = true
|
||||
mu.Unlock()
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_peer_remove
|
||||
func ollie_peer_remove(sessionIDC *C.char, peerIDC *C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
peerID := C.GoString(peerIDC)
|
||||
|
||||
mu.Lock()
|
||||
sess, ok := sessions[id]
|
||||
if !ok {
|
||||
mu.Unlock()
|
||||
return -1
|
||||
}
|
||||
delete(sess.peers, peerID)
|
||||
mu.Unlock()
|
||||
return 0
|
||||
}
|
||||
|
||||
//export ollie_peer_list
|
||||
func ollie_peer_list(sessionIDC *C.char, outBuf **C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
|
||||
mu.RLock()
|
||||
sess, ok := sessions[id]
|
||||
mu.RUnlock()
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
var result string
|
||||
for peerID := range sess.peers {
|
||||
result += peerID + "\n"
|
||||
}
|
||||
*outBuf = C.CString(result)
|
||||
return C.int(len(sess.peers))
|
||||
}
|
||||
|
||||
//export ollie_peer_submit
|
||||
func ollie_peer_submit(sessionIDC *C.char, peerIDC *C.char, promptC *C.char) C.int {
|
||||
id := C.GoString(sessionIDC)
|
||||
peerID := C.GoString(peerIDC)
|
||||
prompt := C.GoString(promptC)
|
||||
|
||||
mu.RLock()
|
||||
sess, ok := sessions[id]
|
||||
peer, peerOk := sessions[peerID]
|
||||
mu.RUnlock()
|
||||
if !ok || !peerOk {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Verify peer is registered
|
||||
if !sess.peers[peerID] {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Submit to the peer session
|
||||
go peer.core.Submit(peer.ctx, prompt)
|
||||
return 0
|
||||
}
|
||||
|
||||
// --- Backends/models/agents ---
|
||||
|
||||
//export ollie_list_backends
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
#!/bin/sh
|
||||
# End-to-end test for ollie-kde Phase 2
|
||||
# Run this while the daemon is running: make run
|
||||
set -e
|
||||
|
||||
SERVICE="org.ollie.SessionManager"
|
||||
PATH_OBJ="/org/ollie/SessionManager"
|
||||
IFACE="org.ollie.SessionManager"
|
||||
|
||||
call() {
|
||||
dbus-send --session --dest=$SERVICE --type=method_call --print-reply \
|
||||
$PATH_OBJ "$IFACE.$1" "${@:2}"
|
||||
}
|
||||
|
||||
echo "=== ListBackends ==="
|
||||
call ListBackends
|
||||
|
||||
echo ""
|
||||
echo "=== ListAgents ==="
|
||||
call ListAgents
|
||||
|
||||
echo ""
|
||||
echo "=== CreateSession ==="
|
||||
RESULT=$(dbus-send --session --dest=$SERVICE --type=method_call --print-reply \
|
||||
$PATH_OBJ $IFACE.CreateSession \
|
||||
string:"$HOME" string:"" string:"" string:"default")
|
||||
echo "$RESULT"
|
||||
SESSION_ID=$(echo "$RESULT" | grep 'string "' | head -1 | sed 's/.*string "//;s/"//')
|
||||
echo "Session ID: $SESSION_ID"
|
||||
|
||||
if [ -z "$SESSION_ID" ]; then
|
||||
echo "FAIL: no session created"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== GetState ==="
|
||||
call GetState string:"$SESSION_ID"
|
||||
|
||||
echo ""
|
||||
echo "=== GetConfig ==="
|
||||
call GetConfig string:"$SESSION_ID"
|
||||
|
||||
echo ""
|
||||
echo "=== Submit prompt ==="
|
||||
call Submit string:"$SESSION_ID" string:"Say hello in exactly 5 words."
|
||||
|
||||
echo ""
|
||||
echo "=== Waiting 5s for response... ==="
|
||||
sleep 5
|
||||
|
||||
echo ""
|
||||
echo "=== GetState (should be idle) ==="
|
||||
call GetState string:"$SESSION_ID"
|
||||
|
||||
echo ""
|
||||
echo "=== GetChat (offset 0) ==="
|
||||
dbus-send --session --dest=$SERVICE --type=method_call --print-reply \
|
||||
$PATH_OBJ $IFACE.GetChat \
|
||||
string:"$SESSION_ID" int64:0
|
||||
|
||||
echo ""
|
||||
echo "=== GetUsage ==="
|
||||
call GetUsage string:"$SESSION_ID"
|
||||
|
||||
echo ""
|
||||
echo "=== GetCost ==="
|
||||
call GetCost string:"$SESSION_ID"
|
||||
|
||||
echo ""
|
||||
echo "=== GetContext (first 200 chars) ==="
|
||||
CONTEXT=$(dbus-send --session --dest=$SERVICE --type=method_call --print-reply \
|
||||
$PATH_OBJ $IFACE.GetContext string:"$SESSION_ID" 2>&1)
|
||||
echo "$CONTEXT" | head -5
|
||||
|
||||
echo ""
|
||||
echo "=== KillSession ==="
|
||||
call KillSession string:"$SESSION_ID"
|
||||
|
||||
echo ""
|
||||
echo "=== ListSessions (should be empty) ==="
|
||||
call ListSessions
|
||||
|
||||
echo ""
|
||||
echo "=== DONE ==="
|
||||
Reference in New Issue