refactor: enhance user context handling and update template mappings
This commit is contained in:
@@ -93,10 +93,12 @@ _SESSION_KEY_PARTS_MIN = SESSION_KEY_PARTS_MIN
|
||||
|
||||
MAIN_TEMPLATE_MAP = {
|
||||
"AGENTS.md": "BOARD_AGENTS.md.j2",
|
||||
"IDENTITY.md": "BOARD_IDENTITY.md.j2",
|
||||
"SOUL.md": "BOARD_SOUL.md.j2",
|
||||
"MEMORY.md": "BOARD_MEMORY.md.j2",
|
||||
"HEARTBEAT.md": "BOARD_HEARTBEAT.md.j2",
|
||||
"USER.md": "MAIN_USER.md",
|
||||
"TOOLS.md": "MAIN_TOOLS.md",
|
||||
"USER.md": "BOARD_USER.md.j2",
|
||||
"TOOLS.md": "BOARD_TOOLS.md.j2",
|
||||
}
|
||||
|
||||
BOARD_SHARED_TEMPLATE_MAP = {
|
||||
|
||||
@@ -161,16 +161,41 @@ def _workspace_path(agent: Agent, workspace_root: str) -> str:
|
||||
return f"{root}/workspace-{slugify(key)}"
|
||||
|
||||
|
||||
def _email_local_part(email: str) -> str:
|
||||
normalized = email.strip()
|
||||
if not normalized:
|
||||
return ""
|
||||
local, _sep, _domain = normalized.partition("@")
|
||||
return local.strip() or normalized
|
||||
|
||||
|
||||
def _display_name(user: User | None) -> str:
|
||||
if user is None:
|
||||
return ""
|
||||
name = (user.name or "").strip()
|
||||
if name:
|
||||
return name
|
||||
return (user.email or "").strip()
|
||||
|
||||
|
||||
def _preferred_name(user: User | None) -> str:
|
||||
preferred_name = (user.preferred_name or "") if user else ""
|
||||
if preferred_name:
|
||||
preferred_name = preferred_name.strip().split()[0]
|
||||
if preferred_name:
|
||||
return preferred_name
|
||||
display_name = _display_name(user)
|
||||
if display_name:
|
||||
if "@" in display_name:
|
||||
return _email_local_part(display_name)
|
||||
return display_name.split()[0]
|
||||
email = (user.email or "") if user else ""
|
||||
return _email_local_part(email)
|
||||
|
||||
|
||||
def _user_context(user: User | None) -> dict[str, str]:
|
||||
return {
|
||||
"user_name": (user.name or "") if user else "",
|
||||
"user_name": _display_name(user),
|
||||
"user_preferred_name": _preferred_name(user),
|
||||
"user_pronouns": (user.pronouns or "") if user else "",
|
||||
"user_timezone": (user.timezone or "") if user else "",
|
||||
|
||||
@@ -252,9 +252,25 @@ class OpenClawProvisioningService(OpenClawDBService):
|
||||
reset_sessions=options.reset_sessions,
|
||||
rotate_tokens=options.rotate_tokens,
|
||||
force_bootstrap=options.force_bootstrap,
|
||||
overwrite=options.overwrite,
|
||||
board_id=options.board_id,
|
||||
)
|
||||
|
||||
if template_user is None:
|
||||
result = _base_result(
|
||||
gateway,
|
||||
include_main=options.include_main,
|
||||
reset_sessions=options.reset_sessions,
|
||||
)
|
||||
_append_sync_error(
|
||||
result,
|
||||
message=(
|
||||
"Organization owner not found (required for gateway template USER.md "
|
||||
"rendering)."
|
||||
),
|
||||
)
|
||||
return result
|
||||
|
||||
result = _base_result(
|
||||
gateway,
|
||||
include_main=options.include_main,
|
||||
|
||||
@@ -23,6 +23,12 @@ def _parse_args() -> argparse.Namespace:
|
||||
default=None,
|
||||
help="Optional Board UUID filter",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--user-id",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Optional User UUID for USER.md rendering context",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-main",
|
||||
action=argparse.BooleanOptionalAction,
|
||||
@@ -62,6 +68,7 @@ def _parse_args() -> argparse.Namespace:
|
||||
async def _run() -> int:
|
||||
from app.db.session import async_session_maker
|
||||
from app.models.gateways import Gateway
|
||||
from app.models.users import User
|
||||
from app.services.openclaw.provisioning_db import (
|
||||
GatewayTemplateSyncOptions,
|
||||
OpenClawProvisioningService,
|
||||
@@ -70,17 +77,22 @@ async def _run() -> int:
|
||||
args = _parse_args()
|
||||
gateway_id = UUID(args.gateway_id)
|
||||
board_id = UUID(args.board_id) if args.board_id else None
|
||||
user_id = UUID(args.user_id) if args.user_id else None
|
||||
|
||||
async with async_session_maker() as session:
|
||||
gateway = await session.get(Gateway, gateway_id)
|
||||
if gateway is None:
|
||||
message = f"Gateway not found: {gateway_id}"
|
||||
raise SystemExit(message)
|
||||
template_user = await session.get(User, user_id) if user_id else None
|
||||
if user_id and template_user is None:
|
||||
message = f"User not found: {user_id}"
|
||||
raise SystemExit(message)
|
||||
|
||||
result = await OpenClawProvisioningService(session).sync_gateway_templates(
|
||||
gateway,
|
||||
GatewayTemplateSyncOptions(
|
||||
user=None,
|
||||
user=template_user,
|
||||
include_main=bool(args.include_main),
|
||||
lead_only=bool(args.lead_only),
|
||||
reset_sessions=bool(args.reset_sessions),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# IDENTITY.md
|
||||
{% set is_lead = (is_board_lead | default(false) | string | lower) in ["true", "1", "yes"] %}
|
||||
|
||||
# IDENTITY.md
|
||||
|
||||
## Core
|
||||
- Name: {{ agent_name }}
|
||||
- Agent ID: {{ agent_id }}
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
# TOOLS.md
|
||||
{% set is_main = (is_main_agent | default(false) | string | lower) in ["true", "1", "yes"] %}
|
||||
{% set is_lead = (is_board_lead | default(false) | string | lower) in ["true", "1", "yes"] %}
|
||||
# TOOLS.md
|
||||
|
||||
- `BASE_URL={{ base_url }}`
|
||||
- `AUTH_TOKEN={{ auth_token }}`
|
||||
- `AGENT_NAME={{ agent_name }}`
|
||||
- `AGENT_ID={{ agent_id }}`
|
||||
{% if board_id is defined %}
|
||||
- `BOARD_ID={{ board_id }}`
|
||||
{% endif %}
|
||||
- `WORKSPACE_ROOT={{ workspace_root }}`
|
||||
{% if workspace_path is defined %}
|
||||
- `WORKSPACE_PATH={{ workspace_path }}`
|
||||
{% endif %}
|
||||
- Required tools: `curl`, `jq`
|
||||
|
||||
{% if is_lead %}
|
||||
{% if is_main %}
|
||||
{% set role_tag = "agent-main" %}
|
||||
{% elif is_lead %}
|
||||
{% set role_tag = "agent-lead" %}
|
||||
{% else %}
|
||||
{% set role_tag = "agent-worker" %}
|
||||
|
||||
@@ -1,35 +1,21 @@
|
||||
# USER.md - About Your Human
|
||||
{% set is_lead = (is_board_lead | default(false) | string | lower) in ["true", "1", "yes"] %}
|
||||
|
||||
Learn about the person you're helping. Update this as you go.
|
||||
_Learn about the person you're helping. Update this as you go._
|
||||
|
||||
## Human Profile
|
||||
- Name: {{ user_name }}
|
||||
- Preferred name: {{ user_preferred_name }}
|
||||
- Pronouns (optional): {{ user_pronouns }}
|
||||
- Timezone: {{ user_timezone }}
|
||||
- Notes: {{ user_notes }}
|
||||
- **Name:** {{ user_name }}
|
||||
- **What to call them:** {{ user_preferred_name }}
|
||||
- **Pronouns:** _(optional)_ {{ user_pronouns }}
|
||||
- **Timezone:** {{ user_timezone }}
|
||||
- **Notes:** {{ user_notes }}
|
||||
|
||||
## Context
|
||||
|
||||
{% if user_context %}
|
||||
{{ user_context }}
|
||||
|
||||
## Board Objective Snapshot
|
||||
- Board name: {{ board_name }}
|
||||
- Board type: {{ board_type }}
|
||||
- Objective: {{ board_objective }}
|
||||
- Success metrics: {{ board_success_metrics }}
|
||||
- Target date: {{ board_target_date }}
|
||||
|
||||
{% if is_lead %}
|
||||
## Intake Notes (Lead)
|
||||
Use this section for durable, human-provided answers gathered in board chat
|
||||
(goal clarification, constraints, preferences). Keep it short and factual.
|
||||
|
||||
- [YYYY-MM-DD] ...
|
||||
{% else %}
|
||||
_(What do they care about? What projects are they working on? What annoys them? What makes them laugh? Build this over time.)_
|
||||
{% endif %}
|
||||
|
||||
---
|
||||
|
||||
The more you know, the better you can help. But remember: you're learning about a person, not building a dossier. Respect the difference.
|
||||
|
||||
If any field is blank, leave it blank. Do not invent values.
|
||||
The more you know, the better you can help. But remember - you're learning about a person, not building a dossier. Respect the difference.
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
# IDENTITY.md — Who Am I?
|
||||
|
||||
Name: {{ agent_name }}
|
||||
|
||||
Agent ID: {{ agent_id }}
|
||||
|
||||
Creature: {{ identity_role }}
|
||||
|
||||
Vibe: {{ identity_communication_style }}
|
||||
|
||||
Emoji: {{ identity_emoji }}
|
||||
|
||||
{% if identity_purpose %}
|
||||
Purpose: {{ identity_purpose }}
|
||||
{% endif %}
|
||||
|
||||
{% if identity_personality %}
|
||||
Personality: {{ identity_personality }}
|
||||
{% endif %}
|
||||
|
||||
{% if identity_custom_instructions %}
|
||||
Custom Instructions:
|
||||
{{ identity_custom_instructions }}
|
||||
{% endif %}
|
||||
@@ -1,11 +0,0 @@
|
||||
# TOOLS.md (Main Agent)
|
||||
|
||||
BASE_URL={{ base_url }}
|
||||
AUTH_TOKEN={{ auth_token }}
|
||||
AGENT_NAME={{ agent_name }}
|
||||
AGENT_ID={{ agent_id }}
|
||||
WORKSPACE_ROOT={{ workspace_root }}
|
||||
|
||||
Notes:
|
||||
- Use curl for API calls.
|
||||
- Use Mission Control API for outputs.
|
||||
@@ -1,19 +0,0 @@
|
||||
# USER.md (Main Agent)
|
||||
|
||||
## User
|
||||
- Name: {{ user_name }}
|
||||
- Preferred name: {{ user_preferred_name }}
|
||||
- Pronouns: {{ user_pronouns }}
|
||||
- Timezone: {{ user_timezone }}
|
||||
|
||||
## Context
|
||||
{{ user_context }}
|
||||
|
||||
## Notes
|
||||
{{ user_notes }}
|
||||
|
||||
## Mission Control
|
||||
- Base URL: {{ base_url }}
|
||||
- Auth token: {{ auth_token }}
|
||||
|
||||
You are the **Main Agent** for this gateway. You are not tied to a specific board.
|
||||
@@ -1,40 +0,0 @@
|
||||
# MEMORY.md - Long-Term Memory
|
||||
|
||||
This is curated knowledge. Update it during consolidation, not constantly during sessions.
|
||||
|
||||
Use this for durable facts, decisions, constraints, recurring patterns, and evolving identity/preferences.
|
||||
Update during consolidation, not constantly.
|
||||
|
||||
- Preferences / working style:
|
||||
- What I learned about the human:
|
||||
- What changed recently:
|
||||
|
||||
{% if board_id is defined %}
|
||||
## Board Context (read-only unless board goal changes)
|
||||
|
||||
- Board: {{ board_name }}
|
||||
- Board type: {{ board_type }}
|
||||
- Objective: {{ board_objective }}
|
||||
- Success metrics: {{ board_success_metrics }}
|
||||
- Target date: {{ board_target_date }}
|
||||
{% endif %}
|
||||
|
||||
## Constraints / Assumptions
|
||||
|
||||
- [Add constraints that affect decisions and task execution]
|
||||
|
||||
## Decisions (with rationale)
|
||||
|
||||
- [Decision] - [Why]
|
||||
|
||||
## Known Risks / Open Questions
|
||||
|
||||
- [Risk or question] - [Mitigation or next step]
|
||||
|
||||
## Useful References
|
||||
|
||||
- [Commands, paths, URLs (without secrets)]
|
||||
|
||||
---
|
||||
|
||||
Last consolidated: [YYYY-MM-DD]
|
||||
@@ -70,6 +70,40 @@ def test_templates_root_points_to_repo_templates_dir():
|
||||
assert (root / "BOARD_AGENTS.md.j2").exists()
|
||||
|
||||
|
||||
def test_user_context_uses_email_fallback_when_name_is_missing():
|
||||
user = SimpleNamespace(
|
||||
name=None,
|
||||
preferred_name=None,
|
||||
pronouns=None,
|
||||
timezone=None,
|
||||
notes=None,
|
||||
context=None,
|
||||
email="jane.doe@example.com",
|
||||
)
|
||||
|
||||
context = agent_provisioning._user_context(user)
|
||||
|
||||
assert context["user_name"] == "jane.doe@example.com"
|
||||
assert context["user_preferred_name"] == "jane.doe"
|
||||
|
||||
|
||||
def test_user_context_prefers_name_token_when_preferred_name_missing():
|
||||
user = SimpleNamespace(
|
||||
name="Jane Doe",
|
||||
preferred_name=None,
|
||||
pronouns=None,
|
||||
timezone=None,
|
||||
notes=None,
|
||||
context=None,
|
||||
email=None,
|
||||
)
|
||||
|
||||
context = agent_provisioning._user_context(user)
|
||||
|
||||
assert context["user_name"] == "Jane Doe"
|
||||
assert context["user_preferred_name"] == "Jane"
|
||||
|
||||
|
||||
@dataclass
|
||||
class _GatewayStub:
|
||||
id: UUID
|
||||
|
||||
Reference in New Issue
Block a user