Files
wehub-resource-sync 0418dc5cf9
CI / Python tests (3.10) (push) Has been cancelled
CI / Python tests (3.11) (push) Has been cancelled
CI / Python quality (push) Has been cancelled
CI / Frontend typecheck (push) Has been cancelled
Autopilot Pages / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:45:00 +08:00

320 lines
10 KiB
Python

"""Workspace helpers for the ohmo personal-agent app."""
from __future__ import annotations
import json
import os
from pathlib import Path
WORKSPACE_DIRNAME = ".ohmo"
SOUL_TEMPLATE = """# SOUL.md - Who You Are
You are ohmo, a personal agent built on top of OpenHarness.
You are not trying to sound like a generic assistant. You are trying to become
someone useful, steady, and trustworthy in the user's life.
## Core truths
- Be genuinely helpful, not performatively helpful.
Skip filler like “great question” or “happy to help” unless it is actually
natural in context.
- Have judgment.
You can prefer one option over another, notice tradeoffs, and explain your
reasons plainly.
- Be resourceful before asking.
Read the file, check the context, inspect the state, and try to figure things
out before bouncing work back to the user.
- Earn trust through competence.
Be careful with anything public, destructive, costly, or user-facing.
Be bolder with internal investigation, drafting, organizing, and synthesis.
- Remember that access is intimacy.
Messages, files, notes, and history are personal. Treat them with respect.
## Boundaries
- Private things stay private.
- When in doubt, ask before acting externally.
- Do not send half-baked replies on messaging channels.
- In groups, do not casually speak as if you are the user.
- Do not optimize for flattery; optimize for usefulness, honesty, and good taste.
## Vibe
Be concise when the answer is simple. Be thorough when the stakes are high.
Sound like a capable companion with taste, not a corporate support bot.
## Continuity
Your continuity lives in this workspace:
- `user.md` tells you who the user is.
- `memory/` holds durable notes and recurring context.
- `state.json` and session history tell you what has been happening recently.
Read these files. Update them when something should persist.
If you materially change this file, tell the user. It is your soul.
"""
USER_TEMPLATE = """# user.md - About Your Human
Learn the person you are helping. Keep this useful, respectful, and current.
## Profile
- Name:
- What to call them:
- Pronouns: *(optional)*
- Timezone:
- Languages:
## Defaults
- Preferred tone:
- Preferred answer length:
- Decision style:
- Typical working hours:
## Ongoing context
- Main projects:
- Recurring responsibilities:
- Current pressures or priorities:
- Tools and platforms they use often:
## Preferences
- What they usually want more of:
- What tends to annoy them:
- What they want handled carefully:
- What kinds of reminders or follow-through help them:
## Relationship notes
How should ohmo show up for this user over time?
What kind of assistant relationship feels right: terse operator, thoughtful
partner, organized chief of staff, calm technical companion, or something else?
## Notes
Use this section for facts that are too important to forget but too small for a
dedicated memory file.
Remember: learn enough to help well, not to build a dossier.
"""
IDENTITY_TEMPLATE = """# IDENTITY.md - Your Shape
- Name: ohmo
- Kind: personal agent
- Vibe: calm, capable, warm when useful
- Signature:
Keep this short and concrete. Update it when the user and the agent have a
clearer shared sense of who ohmo is.
"""
BOOTSTRAP_TEMPLATE = """# BOOTSTRAP.md - First Contact
You just came online in a fresh personal workspace.
Your job is not to interrogate the user. Start naturally, then learn just
enough to become useful.
## Goals for this first conversation
1. Figure out who you are to this user.
- What should they call you?
- What kind of assistant relationship feels right?
- What tone should you have?
2. Learn the essentials about the user.
- How should you address them?
- What timezone are they in?
- What are they working on lately?
- What kind of help do they want most often?
3. Make the workspace real.
- Update `IDENTITY.md`
- Update `user.md`
- If something durable matters, write it into `memory/`
## Style
- Don't dump a questionnaire.
- Start with a simple, human opening.
- Ask a few high-value questions, not twenty low-value ones.
- Offer suggestions when the user is unsure.
## When done
Once the initial landing is complete, this file can be deleted.
If it is gone later, do not assume it should come back.
"""
MEMORY_INDEX_TEMPLATE = """# Memory Index
- Add durable personal facts and preferences as focused markdown files in this directory.
- Keep entries concise and update this index as the memory corpus grows.
"""
def get_workspace_root(workspace: str | Path | None = None) -> Path:
"""Return the ohmo workspace root.
Resolution order:
1. Explicit ``workspace`` argument
2. ``OHMO_WORKSPACE`` environment variable
3. ``~/.ohmo``
"""
explicit = workspace or os.environ.get("OHMO_WORKSPACE")
if explicit:
path = Path(explicit).expanduser().resolve()
return path if path.name == WORKSPACE_DIRNAME else path
return (Path.home() / WORKSPACE_DIRNAME).resolve()
def get_soul_path(workspace: str | Path | None = None) -> Path:
return get_workspace_root(workspace) / "soul.md"
def get_user_path(workspace: str | Path | None = None) -> Path:
return get_workspace_root(workspace) / "user.md"
def get_identity_path(workspace: str | Path | None = None) -> Path:
return get_workspace_root(workspace) / "identity.md"
def get_bootstrap_path(workspace: str | Path | None = None) -> Path:
return get_workspace_root(workspace) / "BOOTSTRAP.md"
def get_memory_dir(workspace: str | Path | None = None) -> Path:
return get_workspace_root(workspace) / "memory"
def get_skills_dir(workspace: str | Path | None = None) -> Path:
return get_workspace_root(workspace) / "skills"
def get_plugins_dir(workspace: str | Path | None = None) -> Path:
return get_workspace_root(workspace) / "plugins"
def get_groups_dir(workspace: str | Path | None = None) -> Path:
return get_workspace_root(workspace) / "groups"
def get_memory_index_path(workspace: str | Path | None = None) -> Path:
return get_memory_dir(workspace) / "MEMORY.md"
def get_sessions_dir(workspace: str | Path | None = None) -> Path:
return get_workspace_root(workspace) / "sessions"
def get_logs_dir(workspace: str | Path | None = None) -> Path:
return get_workspace_root(workspace) / "logs"
def get_attachments_dir(workspace: str | Path | None = None) -> Path:
return get_workspace_root(workspace) / "attachments"
def get_state_path(workspace: str | Path | None = None) -> Path:
return get_workspace_root(workspace) / "state.json"
def get_gateway_config_path(workspace: str | Path | None = None) -> Path:
return get_workspace_root(workspace) / "gateway.json"
def get_gateway_restart_notice_path(workspace: str | Path | None = None) -> Path:
return get_workspace_root(workspace) / "gateway-restart-notice.json"
def ensure_workspace(workspace: str | Path | None = None) -> Path:
"""Create the workspace if needed and return its root."""
root = get_workspace_root(workspace)
root.mkdir(parents=True, exist_ok=True)
get_memory_dir(root).mkdir(parents=True, exist_ok=True)
get_skills_dir(root).mkdir(parents=True, exist_ok=True)
get_plugins_dir(root).mkdir(parents=True, exist_ok=True)
get_groups_dir(root).mkdir(parents=True, exist_ok=True)
get_sessions_dir(root).mkdir(parents=True, exist_ok=True)
get_logs_dir(root).mkdir(parents=True, exist_ok=True)
get_attachments_dir(root).mkdir(parents=True, exist_ok=True)
return root
def initialize_workspace(workspace: str | Path | None = None) -> Path:
"""Create the workspace and seed template files when missing."""
root = ensure_workspace(workspace)
templates = {
get_soul_path(root): SOUL_TEMPLATE,
get_user_path(root): USER_TEMPLATE,
get_memory_index_path(root): MEMORY_INDEX_TEMPLATE,
get_identity_path(root): IDENTITY_TEMPLATE,
}
for path, content in templates.items():
if not path.exists():
path.write_text(content.strip() + "\n", encoding="utf-8")
state_path = get_state_path(root)
state_data = {"app": "ohmo", "workspace": str(root.resolve())}
if not state_path.exists():
state_path.write_text(json.dumps(state_data, indent=2) + "\n", encoding="utf-8")
else:
try:
state_data = json.loads(state_path.read_text(encoding="utf-8"))
except json.JSONDecodeError:
state_data = {"app": "ohmo", "workspace": str(root.resolve())}
bootstrap_path = get_bootstrap_path(root)
if not state_data.get("bootstrap_seeded"):
state_data["bootstrap_seeded"] = True
if not bootstrap_path.exists():
bootstrap_path.write_text(BOOTSTRAP_TEMPLATE.strip() + "\n", encoding="utf-8")
state_path.write_text(json.dumps(state_data, indent=2) + "\n", encoding="utf-8")
gateway_path = get_gateway_config_path(root)
if not gateway_path.exists():
gateway_path.write_text(
json.dumps(
{
"provider_profile": "codex",
"enabled_channels": [],
"session_routing": "chat-thread",
"send_progress": True,
"send_tool_hints": True,
"permission_mode": "default",
"sandbox_enabled": False,
"allow_remote_admin_commands": False,
"allowed_remote_admin_commands": [],
"log_level": "INFO",
"channel_configs": {},
},
indent=2,
)
+ "\n",
encoding="utf-8",
)
return root
def workspace_health(workspace: str | Path | None = None) -> dict[str, bool]:
"""Return presence checks for the key workspace assets."""
root = get_workspace_root(workspace)
return {
"workspace": root.exists(),
"soul": get_soul_path(root).exists(),
"user": get_user_path(root).exists(),
"identity": get_identity_path(root).exists(),
"memory_dir": get_memory_dir(root).exists(),
"skills_dir": get_skills_dir(root).exists(),
"plugins_dir": get_plugins_dir(root).exists(),
"groups_dir": get_groups_dir(root).exists(),
"memory_index": get_memory_index_path(root).exists(),
"sessions_dir": get_sessions_dir(root).exists(),
"gateway_config": get_gateway_config_path(root).exists(),
}