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 = {
|
MAIN_TEMPLATE_MAP = {
|
||||||
"AGENTS.md": "BOARD_AGENTS.md.j2",
|
"AGENTS.md": "BOARD_AGENTS.md.j2",
|
||||||
|
"IDENTITY.md": "BOARD_IDENTITY.md.j2",
|
||||||
"SOUL.md": "BOARD_SOUL.md.j2",
|
"SOUL.md": "BOARD_SOUL.md.j2",
|
||||||
|
"MEMORY.md": "BOARD_MEMORY.md.j2",
|
||||||
"HEARTBEAT.md": "BOARD_HEARTBEAT.md.j2",
|
"HEARTBEAT.md": "BOARD_HEARTBEAT.md.j2",
|
||||||
"USER.md": "MAIN_USER.md",
|
"USER.md": "BOARD_USER.md.j2",
|
||||||
"TOOLS.md": "MAIN_TOOLS.md",
|
"TOOLS.md": "BOARD_TOOLS.md.j2",
|
||||||
}
|
}
|
||||||
|
|
||||||
BOARD_SHARED_TEMPLATE_MAP = {
|
BOARD_SHARED_TEMPLATE_MAP = {
|
||||||
|
|||||||
@@ -161,16 +161,41 @@ def _workspace_path(agent: Agent, workspace_root: str) -> str:
|
|||||||
return f"{root}/workspace-{slugify(key)}"
|
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:
|
def _preferred_name(user: User | None) -> str:
|
||||||
preferred_name = (user.preferred_name or "") if user else ""
|
preferred_name = (user.preferred_name or "") if user else ""
|
||||||
if preferred_name:
|
if preferred_name:
|
||||||
preferred_name = preferred_name.strip().split()[0]
|
preferred_name = preferred_name.strip().split()[0]
|
||||||
|
if preferred_name:
|
||||||
return 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]:
|
def _user_context(user: User | None) -> dict[str, str]:
|
||||||
return {
|
return {
|
||||||
"user_name": (user.name or "") if user else "",
|
"user_name": _display_name(user),
|
||||||
"user_preferred_name": _preferred_name(user),
|
"user_preferred_name": _preferred_name(user),
|
||||||
"user_pronouns": (user.pronouns or "") if user else "",
|
"user_pronouns": (user.pronouns or "") if user else "",
|
||||||
"user_timezone": (user.timezone or "") if user else "",
|
"user_timezone": (user.timezone or "") if user else "",
|
||||||
|
|||||||
@@ -252,9 +252,25 @@ class OpenClawProvisioningService(OpenClawDBService):
|
|||||||
reset_sessions=options.reset_sessions,
|
reset_sessions=options.reset_sessions,
|
||||||
rotate_tokens=options.rotate_tokens,
|
rotate_tokens=options.rotate_tokens,
|
||||||
force_bootstrap=options.force_bootstrap,
|
force_bootstrap=options.force_bootstrap,
|
||||||
|
overwrite=options.overwrite,
|
||||||
board_id=options.board_id,
|
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(
|
result = _base_result(
|
||||||
gateway,
|
gateway,
|
||||||
include_main=options.include_main,
|
include_main=options.include_main,
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ def _parse_args() -> argparse.Namespace:
|
|||||||
default=None,
|
default=None,
|
||||||
help="Optional Board UUID filter",
|
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(
|
parser.add_argument(
|
||||||
"--include-main",
|
"--include-main",
|
||||||
action=argparse.BooleanOptionalAction,
|
action=argparse.BooleanOptionalAction,
|
||||||
@@ -62,6 +68,7 @@ def _parse_args() -> argparse.Namespace:
|
|||||||
async def _run() -> int:
|
async def _run() -> int:
|
||||||
from app.db.session import async_session_maker
|
from app.db.session import async_session_maker
|
||||||
from app.models.gateways import Gateway
|
from app.models.gateways import Gateway
|
||||||
|
from app.models.users import User
|
||||||
from app.services.openclaw.provisioning_db import (
|
from app.services.openclaw.provisioning_db import (
|
||||||
GatewayTemplateSyncOptions,
|
GatewayTemplateSyncOptions,
|
||||||
OpenClawProvisioningService,
|
OpenClawProvisioningService,
|
||||||
@@ -70,17 +77,22 @@ async def _run() -> int:
|
|||||||
args = _parse_args()
|
args = _parse_args()
|
||||||
gateway_id = UUID(args.gateway_id)
|
gateway_id = UUID(args.gateway_id)
|
||||||
board_id = UUID(args.board_id) if args.board_id else None
|
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:
|
async with async_session_maker() as session:
|
||||||
gateway = await session.get(Gateway, gateway_id)
|
gateway = await session.get(Gateway, gateway_id)
|
||||||
if gateway is None:
|
if gateway is None:
|
||||||
message = f"Gateway not found: {gateway_id}"
|
message = f"Gateway not found: {gateway_id}"
|
||||||
raise SystemExit(message)
|
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(
|
result = await OpenClawProvisioningService(session).sync_gateway_templates(
|
||||||
gateway,
|
gateway,
|
||||||
GatewayTemplateSyncOptions(
|
GatewayTemplateSyncOptions(
|
||||||
user=None,
|
user=template_user,
|
||||||
include_main=bool(args.include_main),
|
include_main=bool(args.include_main),
|
||||||
lead_only=bool(args.lead_only),
|
lead_only=bool(args.lead_only),
|
||||||
reset_sessions=bool(args.reset_sessions),
|
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"] %}
|
{% set is_lead = (is_board_lead | default(false) | string | lower) in ["true", "1", "yes"] %}
|
||||||
|
|
||||||
|
# IDENTITY.md
|
||||||
|
|
||||||
## Core
|
## Core
|
||||||
- Name: {{ agent_name }}
|
- Name: {{ agent_name }}
|
||||||
- Agent ID: {{ agent_id }}
|
- 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"] %}
|
{% set is_lead = (is_board_lead | default(false) | string | lower) in ["true", "1", "yes"] %}
|
||||||
|
# TOOLS.md
|
||||||
|
|
||||||
- `BASE_URL={{ base_url }}`
|
- `BASE_URL={{ base_url }}`
|
||||||
- `AUTH_TOKEN={{ auth_token }}`
|
- `AUTH_TOKEN={{ auth_token }}`
|
||||||
- `AGENT_NAME={{ agent_name }}`
|
- `AGENT_NAME={{ agent_name }}`
|
||||||
- `AGENT_ID={{ agent_id }}`
|
- `AGENT_ID={{ agent_id }}`
|
||||||
|
{% if board_id is defined %}
|
||||||
- `BOARD_ID={{ board_id }}`
|
- `BOARD_ID={{ board_id }}`
|
||||||
|
{% endif %}
|
||||||
- `WORKSPACE_ROOT={{ workspace_root }}`
|
- `WORKSPACE_ROOT={{ workspace_root }}`
|
||||||
|
{% if workspace_path is defined %}
|
||||||
- `WORKSPACE_PATH={{ workspace_path }}`
|
- `WORKSPACE_PATH={{ workspace_path }}`
|
||||||
|
{% endif %}
|
||||||
- Required tools: `curl`, `jq`
|
- Required tools: `curl`, `jq`
|
||||||
|
|
||||||
{% if is_lead %}
|
{% if is_main %}
|
||||||
|
{% set role_tag = "agent-main" %}
|
||||||
|
{% elif is_lead %}
|
||||||
{% set role_tag = "agent-lead" %}
|
{% set role_tag = "agent-lead" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% set role_tag = "agent-worker" %}
|
{% set role_tag = "agent-worker" %}
|
||||||
|
|||||||
@@ -1,35 +1,21 @@
|
|||||||
# USER.md - About Your Human
|
# 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 }}
|
||||||
- Name: {{ user_name }}
|
- **What to call them:** {{ user_preferred_name }}
|
||||||
- Preferred name: {{ user_preferred_name }}
|
- **Pronouns:** _(optional)_ {{ user_pronouns }}
|
||||||
- Pronouns (optional): {{ user_pronouns }}
|
- **Timezone:** {{ user_timezone }}
|
||||||
- Timezone: {{ user_timezone }}
|
- **Notes:** {{ user_notes }}
|
||||||
- Notes: {{ user_notes }}
|
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
|
{% if user_context %}
|
||||||
{{ user_context }}
|
{{ user_context }}
|
||||||
|
{% else %}
|
||||||
## Board Objective Snapshot
|
_(What do they care about? What projects are they working on? What annoys them? What makes them laugh? Build this over time.)_
|
||||||
- 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] ...
|
|
||||||
{% endif %}
|
{% 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.
|
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.
|
|
||||||
|
|||||||
@@ -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()
|
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
|
@dataclass
|
||||||
class _GatewayStub:
|
class _GatewayStub:
|
||||||
id: UUID
|
id: UUID
|
||||||
|
|||||||
Reference in New Issue
Block a user