Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
1d4e874
✨ feat(user-style-management): gallery pin UI with section layout
ShotaroKataoka May 3, 2026
1c1baad
✨ feat(user-style-management): Engine + MCP Local pin/filter infrastr…
ShotaroKataoka May 3, 2026
59f6582
✨ feat(user-style-management): Local API routes + frontend API connec…
ShotaroKataoka May 3, 2026
b5c7c29
✨ feat(user-style-management): /styles page + header tab navigation
ShotaroKataoka May 3, 2026
ad789c5
♻️ refactor(user-style-management): shared StyleSlidePreview + /style…
ShotaroKataoka May 3, 2026
908238f
🐛 fix(user-style-management): nested button hydration error in StyleCard
ShotaroKataoka May 3, 2026
792c37f
✨ feat(user-style-management): Phase 2 — save/delete/import/export + …
ShotaroKataoka May 3, 2026
80f4583
✨ feat(user-style-management): hash routing, Copy to My Styles, All S…
ShotaroKataoka May 5, 2026
60c4472
✨ feat(user-style-management): Phase 3b — style chat UI + shared comp…
ShotaroKataoka May 5, 2026
cf31418
✨ feat(user-style-management): Phase 3c+3d — run_style_python sandbox…
ShotaroKataoka May 5, 2026
28c7d18
🐛 fix(user-style-management): chat input pinned to bottom in style panel
ShotaroKataoka May 5, 2026
dca65f1
✨ feat(style-chat-ux-polish): Edit with AI button + prompt chips
ShotaroKataoka May 5, 2026
1cbcddd
♻️ refactor(user-style-management): simplify run_style_python + add n…
ShotaroKataoka May 5, 2026
00a3468
✨ feat(user-style-management): improve style chat welcome with capabi…
ShotaroKataoka May 5, 2026
a237a6e
✨ feat(acp-upload-path-direct-access): pass file paths + color analys…
ShotaroKataoka May 6, 2026
6d8de48
🐛 fix(acp-upload-path-direct-access): fix SDPM_DECK_ROOT fallback whe…
ShotaroKataoka May 6, 2026
c82faf8
✨ feat(user-style-management): streamline style naming and rename UX
ShotaroKataoka May 6, 2026
e1eadf0
♻️ refactor(user-style-management): ChatPanel internal refactor
ShotaroKataoka May 6, 2026
80daf12
🐛 fix(user-style-management): restore full-panel file drop zone
ShotaroKataoka May 6, 2026
c82e7fe
✨ feat(user-style-management): implement Cloud版 style management
ShotaroKataoka May 6, 2026
7501c9d
🔀 merge: incorporate origin/main (PR #112 sdpm-mcp/invoke scope fix)
ShotaroKataoka May 6, 2026
2c76bd7
🐛 fix(user-style-management): StyleChatPanel agent config + mode key
ShotaroKataoka May 6, 2026
df048ef
✨ feat(user-style-management): fix tool lists + image color analysis
ShotaroKataoka May 6, 2026
d56e8e9
🐛 fix(user-style-management): fix MODE_TO_AGENT key for style creator
ShotaroKataoka May 6, 2026
e427beb
🐛 fix(user-style-management): fix coverHtml extraction for styles wit…
ShotaroKataoka May 6, 2026
d806fe6
🔧 config(user-style-management): update next-env.d.ts auto-generated …
ShotaroKataoka May 6, 2026
b0f4728
Merge remote-tracking branch 'origin/main' into feature/user-style-ma…
ShotaroKataoka May 6, 2026
9f80f7c
🐛 fix(user-style-management): surface agent errors in useChatStream c…
ShotaroKataoka May 6, 2026
2ff91e3
🔒 fix(user-style-management): add path traversal guard for CodeQL
ShotaroKataoka May 6, 2026
b309869
🐛 fix(user-style-management): fix lint errors (ref access during render)
ShotaroKataoka May 6, 2026
e1e3382
✨ feat(user-template-management): implement Phase 1+2 — Engine API, M…
ShotaroKataoka May 6, 2026
33391eb
🐛 fix(user-template-management): cache template metadata in state.json
ShotaroKataoka May 6, 2026
9d5360f
🎯 perf(user-template-management): polish styles page dialogs and menus
ShotaroKataoka May 6, 2026
87515ca
✨ feat(user-template-management): implement Phase 3 — Cloud layer
ShotaroKataoka May 6, 2026
a24b47e
🐛 fix(user-template-management): use proxy resource to avoid Lambda p…
ShotaroKataoka May 6, 2026
3f1519d
🐛 fix(user-template-management): fix CORS + Lambda policy size
ShotaroKataoka May 6, 2026
4bdf4d2
🐛 fix(user-template-management): use proxy with inline CORS for templ…
ShotaroKataoka May 6, 2026
b623ff4
♻️ refactor(user-template-management): S3 source of truth for builtin…
ShotaroKataoka May 6, 2026
55917ec
♻️ refactor(rest-to-http-api-migration): migrate REST API to HTTP API
ShotaroKataoka May 6, 2026
583c439
🐛 fix(rest-to-http-api-migration): add trailing slash to apiBaseUrl
ShotaroKataoka May 6, 2026
07ef340
🐛 fix(rest-to-http-api-migration): fix CORS preflight 401
ShotaroKataoka May 6, 2026
d21c443
🐛 fix(rest-to-http-api-migration): fix get_user_id for HTTP API v2 event
ShotaroKataoka May 7, 2026
acd7f47
🎨 fix(user-template-management): improve delete modal visibility
ShotaroKataoka May 7, 2026
528667b
✨ feat(confirm-dialog-component): add shared ConfirmDialog, replace 4…
ShotaroKataoka May 7, 2026
e783fec
🔒 fix(user-style-management): resolve ruff F401 and ASH/CodeQL securi…
ShotaroKataoka May 7, 2026
95200f3
🔒 fix(user-style-management): add path traversal guards for remaining…
ShotaroKataoka May 7, 2026
ced1f3b
🐛 fix(user-template-management): presigned URL upload to avoid 413 pa…
ShotaroKataoka May 7, 2026
30bfe39
🔒 fix(user-template-management): suppress false positive path travers…
ShotaroKataoka May 7, 2026
86fd2dd
🐛 fix(template-download): use presigned URL from API response
ShotaroKataoka May 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions agent/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ def _resolve_model_id(requested: str | None, default: str) -> str:


_MCP_FACTORIES = [
lambda jwt_token: mcp_agentcore_runtime(jwt_token=jwt_token),
lambda jwt_token: mcp_aws_knowledge(),
lambda jwt_token: mcp_aws_pricing(),
lambda jwt_token, tool_filters=None: mcp_agentcore_runtime(jwt_token=jwt_token, tool_filters=tool_filters),
lambda jwt_token, tool_filters=None: mcp_aws_knowledge(),
lambda jwt_token, tool_filters=None: mcp_aws_pricing(),
]


Expand Down Expand Up @@ -101,9 +101,11 @@ def create_agent(mode: str, user_id: str, session_id: str, jwt_token: str, chat_
# MCP servers
mcp_servers = []
mcp_status = []
for (name, required), factory_fn in zip(MCP_DEFS, _MCP_FACTORIES):
for i, ((name, required), factory_fn) in enumerate(zip(MCP_DEFS, _MCP_FACTORIES)):
try:
mcp_servers.append(factory_fn(jwt_token))
# Apply tool_filters only to the Presentation Maker server (index 0)
filters = {"allowed": cfg.allowed_tools} if (i == 0 and cfg.allowed_tools) else None
mcp_servers.append(factory_fn(jwt_token, tool_filters=filters))
mcp_status.append({"name": name, "status": "ok"})
except Exception as e:
mcp_status.append({"name": name, "status": "error", "error": str(e)})
Expand Down
8 changes: 7 additions & 1 deletion agent/mcp_clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,30 @@
]


def mcp_agentcore_runtime(jwt_token: str) -> MCPClient:
def mcp_agentcore_runtime(jwt_token: str, tool_filters: dict | None = None) -> MCPClient:
"""Pattern 1: Amazon Bedrock AgentCore Runtime MCP Server with JWT Bearer authentication.

Args:
jwt_token: JWT access token from the caller (without "Bearer " prefix).
tool_filters: Optional tool filter dict (e.g. {"allowed": ["tool1", "tool2"]}).
"""
region = os.environ.get("AWS_REGION", os.environ.get("AWS_DEFAULT_REGION", "us-east-1"))
runtime_arn = os.environ["MCP_RUNTIME_ARN"]
encoded_arn = urllib.parse.quote(runtime_arn, safe="")
url = f"https://bedrock-agentcore.{region}.amazonaws.com/runtimes/{encoded_arn}/invocations?qualifier=DEFAULT"

kwargs: dict = {}
if tool_filters:
kwargs["tool_filters"] = tool_filters

return MCPClient(
lambda: streamablehttp_client(
url=url,
headers={"Authorization": f"Bearer {jwt_token}"},
timeout=120,
terminate_on_close=False,
),
**kwargs,
)


Expand Down
40 changes: 38 additions & 2 deletions agent/modes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ class ModeConfig:
use_composer: Include compose_slides tool.
agent_model: Which model setting to use for the main agent — "chat" for
conversations/planning, "create" for modes that also generate artifacts.
allowed_tools: If set, only these MCP tool names are loaded from the
Presentation Maker server. None = all tools except style-only tools.
"""

parts: list[Part] = field(default_factory=list)
use_composer: bool = True
agent_model: Literal["chat", "create"] = "chat"
allowed_tools: list[str] | None = None


# Shared parts — referenced by multiple modes
Expand All @@ -40,6 +43,21 @@ class ModeConfig:
prefill_text="Starting the Briefing phase. I'll read the workflow to conduct the hearing properly.",
)

# Tool allowlists — explicit control over which MCP tools each mode can use.
# run_style_python is only available to style_creator.
_DECK_TOOLS = [
"init_presentation", "analyze_template", "read_uploaded_file",
"list_styles", "apply_style", "read_examples", "list_workflows",
"read_workflows", "list_guides", "read_guides", "search_assets",
"list_asset_sources", "list_templates",
"run_python", "generate_pptx", "get_preview", "code_to_slide",
"grid", "import_attachment",
]

_STYLE_TOOLS = [
"run_style_python", "list_styles", "analyze_template", "read_uploaded_file",
]


MODES: dict[str, ModeConfig] = {
"separated": ModeConfig(parts=[
Expand All @@ -51,7 +69,7 @@ class ModeConfig:
_WF_SLIDE_GROUPS,
_NOW,
_PREFETCH_BRIEFING,
]),
], allowed_tools=_DECK_TOOLS),
"vibe": ModeConfig(parts=[
_COMMON_LANGUAGE,
Part(Source.file("role/vibe_agent"), target="system"),
Expand All @@ -61,7 +79,7 @@ class ModeConfig:
_WF_POST_COMPOSE,
_WF_SLIDE_GROUPS,
_NOW,
]),
], allowed_tools=_DECK_TOOLS),
"single": ModeConfig(
parts=[
_COMMON_LANGUAGE,
Expand All @@ -71,6 +89,7 @@ class ModeConfig:
],
use_composer=False,
agent_model="create",
allowed_tools=_DECK_TOOLS,
),
# Composer is a sub-agent invoked by compose_slides; ModeConfig is used
# by compose_slides to build its prompt via the same resolve_parts path.
Expand All @@ -94,5 +113,22 @@ class ModeConfig:
target="history:tool_result", label="read_examples"),
],
use_composer=False,
allowed_tools=_DECK_TOOLS,
),
"style_creator": ModeConfig(
parts=[
_COMMON_LANGUAGE,
Part(Source.file("role/style_creator"), target="system"),
_NOW,
Part(
Source.mcp("read_workflows", {"names": ["create-style"]}),
target="history:tool_result",
label="read_workflows",
prefill_text="I'll read the style creation workflow.",
),
],
use_composer=False,
agent_model="create",
allowed_tools=_STYLE_TOOLS,
),
}
61 changes: 61 additions & 0 deletions agent/prompts/role/style_creator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
You are a style creator for spec-driven-presentation-maker.
You create reusable style guides (HTML files) through dialogue with the user.
Respond in the same language as the user.

## Your Role

Help users create presentation styles that capture their design preferences.
A style is an HTML file with CSS variables and example slides that an agent reads
to understand how to design presentations.

## Workflow

Follow the `create-style` workflow loaded in your context. It defines the full process:
1. Gather preferences (analyze references, ask about design direction)
2. Find the premise (the core idea tying preferences together)
3. Design the style (tokens → composition → HTML)
4. Review with user (iterate until confirmed)

**Critical:** Do NOT skip to HTML generation. Gather preferences and confirm direction first.

## Tools

**Primary tool: `run_style_python`**

Execute Python code in a sandbox with file I/O access.

Workspace layout:
- `style.html` — the target style file (read/write, saved back when save=True)
- `ref/{name}.html` — reference styles (read-only, loaded via ref_styles parameter)

Usage patterns:
- Read a reference: `run_style_python(code="html = open('ref/corporate-executive.html').read(); print(html[:500])", ref_styles=["corporate-executive"])`
- Create/edit style: `run_style_python(code="open('style.html','w').write(html)", style_name="style-20260506-1430", save=True)`
- Compute colors: `run_style_python(code="from colorsys import rgb_to_hls; print(rgb_to_hls(0.2, 0.4, 0.6))")`

Import statements are allowed — PIL, colorsys, numpy are available for color computation and palette extraction.

The user's first message contains `[Style: <name>]` — use this as the `style_name` parameter.

**Other tools:**
- `list_styles` — see available styles (discover names for ref_styles)
- `analyze_template` — analyze reference PPTX themes
- `hearing` — display questions to the user

## HTML Writing Strategy

Write incrementally via `run_style_python`:
1. First call: full HTML skeleton (head, :root variables, base CSS, first slide) with save=True
2. Subsequent calls: read back with `open('style.html').read()`, add/modify slides, save=True
3. Each save triggers a live preview update for the user

## Quality Standards

- All design tokens in `:root` as CSS variables
- All colors via `var()` references, never hardcoded in elements
- Text style classes (`.t-title`, `.t-body`, etc.) reference CSS variables
- Inline style only for position/size (`left`, `top`, `width`, `height`)
- Coordinate system: 1920×1080 absolute positioning
- Font sizes: pt units only
- `body { zoom: 0.7; }` for display scaling
- 5–6 slides maximum (cover + design areas)
22 changes: 16 additions & 6 deletions api/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
def get_user_id(event: Any) -> str:
"""Extract user ID (Cognito sub) from API Gateway authorizer claims.

Supports both REST API (v1) and HTTP API (v2) JWT authorizer formats.

Args:
event: Powertools current_event with request_context.authorizer.

Expand All @@ -27,9 +29,13 @@ def get_user_id(event: Any) -> str:
Raises:
ValueError: If authorizer claims or sub are missing.
"""
authorizer = event.request_context.authorizer
claims = (authorizer or {}).get("claims", {})
sub = claims.get("sub")
# HTTP API v2: requestContext.authorizer.jwt.claims
# REST API v1: requestContext.authorizer.claims
raw = event.raw_event.get("requestContext", {}).get("authorizer", {})
if "jwt" in raw:
sub = raw["jwt"].get("claims", {}).get("sub")
else:
sub = raw.get("claims", {}).get("sub")
if not sub:
raise ValueError("Unauthorized: missing user identity")
return sub
Expand All @@ -38,15 +44,19 @@ def get_user_id(event: Any) -> str:
def get_user_alias(event: Any) -> str:
"""Extract user alias (email prefix) from API Gateway authorizer claims.

Supports both REST API (v1) and HTTP API (v2) JWT authorizer formats.

Args:
event: Powertools current_event with request_context.authorizer.

Returns:
Alias string (email prefix before @), or empty string if unavailable.
"""
authorizer = event.request_context.authorizer
claims = (authorizer or {}).get("claims", {})
email = claims.get("email", "")
raw = event.raw_event.get("requestContext", {}).get("authorizer", {})
if "jwt" in raw:
email = raw["jwt"].get("claims", {}).get("email", "")
else:
email = raw.get("claims", {}).get("email", "")
return email.split("@")[0] if email else ""


Expand Down
Loading
Loading