feat: adding workspace, session, and message create

This commit is contained in:
ajspig 2026-04-14 17:48:29 -04:00
parent 1cced7817e
commit fd00047933
4 changed files with 130 additions and 5 deletions

View File

@ -55,6 +55,7 @@ Per-command scoping (workspace / peer / session) is handled via `-w` / `-p` / `-
| Command | Description |
|---------|-------------|
| `honcho workspace list` | List accessible workspaces |
| `honcho workspace create <id>` | Create or get a workspace |
| `honcho workspace inspect` | Peers, sessions, config for a workspace |
| `honcho workspace search <query>` | Search messages across workspace |
| `honcho workspace queue-status` | Deriver queue status (filter with `--observer` / `--sender`) |
@ -78,6 +79,7 @@ Per-command scoping (workspace / peer / session) is handled via `-w` / `-p` / `-
| Command | Description |
|---------|-------------|
| `honcho session list` | List sessions in the workspace (filter with `--peer/-p`) |
| `honcho session create <id>` | Create or get a session (optionally `--peers` to add peers, `--metadata`) |
| `honcho session inspect <id>` | Peers, message count, summaries, config |
| `honcho session context <id>` | What an agent would see |
| `honcho session summaries <id>` | Short + long summaries |
@ -92,6 +94,7 @@ Per-command scoping (workspace / peer / session) is handled via `-w` / `-p` / `-
| Command | Description |
|---------|-------------|
| `honcho message list` | List messages in a session (session via `-s` / `HONCHO_SESSION_ID`) |
| `honcho message create <content>` | Create a message (requires `--peer/-p`, session via `-s`) |
| `honcho message get <id>` | Get a single message (session via `-s` / `HONCHO_SESSION_ID`) |
### Conclusions (observations)

View File

@ -1,19 +1,20 @@
"""Message commands: list, get."""
"""Message commands: list, get, create."""
from __future__ import annotations
import hashlib
import json
from typing import Optional
import typer
from honcho.api_types import MessageResponse
from honcho.api_types import MessageCreateParams, MessageResponse
from honcho.http import routes
from honcho.message import Message
from honcho_cli.commands.session import _get_session_id
from honcho_cli.commands.workspace import _handle_error
from honcho_cli.output import print_result, status
from honcho_cli.output import print_error, print_result, status
from honcho_cli.validation import validate_resource_id
from honcho_cli.common import add_common_options, get_client, handle_cmd_flags
@ -87,6 +88,48 @@ def list_messages(
_handle_error(e, "message", "list")
@app.command("create")
def create_message(
content: str = typer.Argument(help="Message content"),
peer_id: str = typer.Option(..., "--peer", "-p", help="Peer ID of the message sender"),
metadata: Optional[str] = typer.Option(None, "--metadata", help="JSON metadata to associate with the message"),
session_id: Optional[str] = typer.Option(None, "--session", "-s", help="Session ID"),
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
) -> None:
"""Create a message in a session."""
handle_cmd_flags(json_output=json_output, workspace=workspace, session=session_id)
sid = _get_session_id(None)
validate_resource_id(peer_id, "peer")
client, config = get_client()
sess = client.session(sid)
parsed_metadata = None
if metadata:
try:
parsed_metadata = json.loads(metadata)
except json.JSONDecodeError as e:
print_error("INVALID_JSON", f"--metadata must be valid JSON: {e}", {})
raise typer.Exit(1)
try:
msgs = sess.add_messages(MessageCreateParams(
peer_id=peer_id,
content=content,
metadata=parsed_metadata,
))
msg = msgs[0]
print_result({
"id": msg.id,
"peer_id": msg.peer_id,
"content": msg.content,
"token_count": msg.token_count,
"created_at": str(msg.created_at),
})
except Exception as e:
_handle_error(e, "message", "create")
@app.command("get")
def get_message(
message_id: str = typer.Argument(help="Message ID"),

View File

@ -60,6 +60,45 @@ def list_sessions(
_handle_error(e, "session", "list")
@app.command("create")
def create_session(
session_id: str = typer.Argument(help="Session ID to create or get"),
peers: Optional[str] = typer.Option(None, "--peers", help="Comma-separated peer IDs to add to the session"),
metadata: Optional[str] = typer.Option(None, "--metadata", help="JSON metadata to associate with the session"),
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
) -> None:
"""Create or get a session."""
handle_cmd_flags(json_output=json_output, workspace=workspace)
sid = validate_resource_id(session_id, "session")
client, config = get_client()
parsed_metadata = None
if metadata:
try:
parsed_metadata = json.loads(metadata)
except json.JSONDecodeError as e:
print_error("INVALID_JSON", f"--metadata must be valid JSON: {e}", {})
raise typer.Exit(1)
peer_ids = [p.strip() for p in peers.split(",") if p.strip()] if peers else []
for pid in peer_ids:
validate_resource_id(pid, "peer")
try:
sess = client.session(sid, metadata=parsed_metadata)
if peer_ids:
sess.add_peers(peer_ids)
result: dict[str, object] = {"session_id": sess.id}
if parsed_metadata is not None:
result["metadata"] = parsed_metadata
if peer_ids:
result["peers"] = peer_ids
print_result(result)
except Exception as e:
_handle_error(e, "session", sid)
@app.command()
def inspect(
session_id: Optional[str] = typer.Argument(None, help="Session ID (uses default if omitted)"),

View File

@ -1,7 +1,8 @@
"""Workspace commands: list, inspect, delete, search, queue-status."""
"""Workspace commands: list, inspect, create, delete, search, queue-status."""
from __future__ import annotations
import json
from typing import Optional
import typer
@ -62,6 +63,38 @@ def list_workspaces(
_handle_error(e, "workspace", "list")
@app.command("create")
def create_workspace(
workspace_id: str = typer.Argument(help="Workspace ID to create or get"),
metadata: Optional[str] = typer.Option(None, "--metadata", help="JSON metadata to associate with the workspace"),
json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
) -> None:
"""Create or get a workspace."""
handle_cmd_flags(json_output=json_output)
wid = validate_resource_id(workspace_id, "workspace")
client, config = get_client(require_workspace=False)
ws_client = _with_workspace(client, wid)
parsed_metadata = None
if metadata:
try:
parsed_metadata = json.loads(metadata)
except json.JSONDecodeError as e:
print_error("INVALID_JSON", f"--metadata must be valid JSON: {e}", {})
raise typer.Exit(1)
try:
# Trigger get-or-create via the workspace ensure mechanism
ws_client.get_configuration()
result: dict[str, object] = {"workspace_id": wid}
if parsed_metadata is not None:
ws_client.set_metadata(parsed_metadata)
result["metadata"] = parsed_metadata
print_result(result)
except Exception as e:
_handle_error(e, "workspace", wid)
@app.command()
def inspect(
workspace_id: Optional[str] = typer.Argument(None, help="Workspace ID (uses default if omitted)"),
@ -129,6 +162,13 @@ def delete(
client, config = get_client(require_workspace=False)
ws_client = _with_workspace(client, workspace_id)
# Verify workspace exists before prompting for confirmation
try:
ws_client.get_metadata()
except Exception as e:
_handle_error(e, "workspace", workspace_id)
return
# Always fetch sessions for dry-run or cascade
raw_sessions = _raw_list(ws_client.sessions()) if (dry_run or cascade) else []
@ -225,7 +265,7 @@ def _with_workspace(client, workspace_id: str):
"""Return a new client pointed at a different workspace."""
return Honcho(
base_url=str(client.base_url),
api_key=client._http._api_key if hasattr(client._http, "_api_key") else None,
api_key=client._http.api_key if hasattr(client._http, "api_key") else None,
workspace_id=workspace_id,
)