diff --git a/01 Projects/Barclays-banner-builder/Barclays Banner Builder.md b/01 Projects/Barclays-banner-builder/Barclays Banner Builder.md index 09c5038..ff989b3 100644 --- a/01 Projects/Barclays-banner-builder/Barclays Banner Builder.md +++ b/01 Projects/Barclays-banner-builder/Barclays Banner Builder.md @@ -23,6 +23,10 @@ created: 2026-04-17 - **Local path:** `/Volumes/SSD/Projects/Oliver/Barclays-banner-builder` ## Sessions +### 2026-04-17 – Create deployment script and app concept +**Asked:** Create deployment script and app concept for Docker-based Ubuntu server with Apache reverse proxy, avoiding WebSockets. +**Done:** Fixed Apache configuration routing issue by reordering Include directives so the app's config loads before hp-prod-tracker's catch-all rule. + ### 2026-04-17 – How to deploy the app on **Asked:** How to deploy the app on Ubuntu server with Docker, idempotent deployment script, and fix Apache routing issues? **Done:** Created idempotent deploy script with Docker build, database initialization, migrations, and fixed Apache VirtualHost configuration by reordering Include directives. @@ -127,6 +131,7 @@ created: 2026-04-17 ## Change Log | Date | Requested | Changed | Files | |------|-----------|---------|-------| +| 2026-04-17 | Deployment setup | Apache conf Include order, deploy.sh idempotent script | deploy.sh, /etc/apache2/sites-available/optical-dev.oliver.solutions.conf | | 2026-04-17 | Deployment setup | deploy.sh creation, Apache VirtualHost reordering, Docker build cache management | deploy.sh, /etc/apache2/sites-available/optical-dev.oliver.solutions.conf | | 2026-04-17 | Deployment script & Apache config | Docker build caching, idempotent database init, Alembic migrations, Apache ProxyPass setup | deploy.sh, optical-dev.oliver.solutions.conf | | 2026-04-17 | Deployment script & Apache config | Docker build caching, database migrations, Apache proxy setup | deploy.sh, optical-dev.oliver.solutions.conf | diff --git a/99 Daily/2026-04-17.md b/99 Daily/2026-04-17.md index d0d3157..3b75e80 100644 --- a/99 Daily/2026-04-17.md +++ b/99 Daily/2026-04-17.md @@ -158,3 +158,9 @@ tags: [daily] - 12:49 (<1min) | `memory-compiler` - **Asked:** Compile a new raw article into the wiki knowledge base with proper indexing. - **Done:** Filed article as `wiki/agent-sdk/custom-tools.md` and updated both topic and master indices. +- 12:49 (<1min) | `Barclays-banner-builder` + - **Asked:** Create deployment script and app concept for Docker-based Ubuntu server with Apache reverse proxy, avoiding WebSockets. + - **Done:** Fixed Apache configuration routing issue by reordering Include directives so the app's config loads before hp-prod-tracker's catch-all rule. +- 12:50 | `memory-compiler` + - **Asked:** Compile a new article about user input approvals into the agent-sdk wiki section. + - **Done:** Created structured wiki article documenting canUseTool and AskUserQuestion with response strategies and workarounds. diff --git a/raw/Handle approvals and user input.md b/raw/_processed/Handle approvals and user input.md similarity index 100% rename from raw/Handle approvals and user input.md rename to raw/_processed/Handle approvals and user input.md diff --git a/wiki/_master-index.md b/wiki/_master-index.md index 872ef9a..c754b4d 100644 --- a/wiki/_master-index.md +++ b/wiki/_master-index.md @@ -30,7 +30,7 @@ This 3-hop pattern works for hundreds of articles without vector search. | [[wiki/web-agency/_index\|web-agency/]] | AI-assisted website building & selling: Claude Code, Nanobanana 2, Kling, LaunchPath MCP | 1 | | [[wiki/dotfiles/_index\|dotfiles/]] | Linux terminal ricing: Kitty, Fish, WezTerm CLI, modern Rust CLI tools, LazyVim, unified themes, Tabby | 9 | -| [[wiki/agent-sdk/_index\|agent-sdk/]] | Claude Agent SDK (formerly Claude Code SDK) — build autonomous AI agents in Python and TypeScript | 10 | +| [[wiki/agent-sdk/_index\|agent-sdk/]] | Claude Agent SDK (formerly Claude Code SDK) — build autonomous AI agents in Python and TypeScript | 11 | | [[wiki/llm-models/_index\|llm-models/]] | OpenAI model catalog — GPT-5.x, o-series reasoning, audio/realtime, embeddings, moderation | 1 | | [[wiki/claude-code/_index\|claude-code/]] | Claude Code product docs — install, capabilities, surfaces, MCP, hooks, scheduling, multi-agent, plugins, skills, error recovery | 7 | diff --git a/wiki/agent-sdk/_index.md b/wiki/agent-sdk/_index.md index c974ea1..2d03f0c 100644 --- a/wiki/agent-sdk/_index.md +++ b/wiki/agent-sdk/_index.md @@ -24,3 +24,4 @@ Build production AI agents using the same tools, agent loop, and context managem | [[wiki/agent-sdk/mcp-integration\|mcp-integration]] | MCP server setup: transport types (stdio/HTTP/SSE), allowedTools, auth, tool search, error handling | raw/Connect to external tools with MCP.md | 2026-04-17 | | [[wiki/agent-sdk/structured-outputs\|structured-outputs]] | Return validated JSON from agent workflows using JSON Schema, Zod, or Pydantic; error subtypes, tips | raw/Get structured output from agents.md | 2026-04-17 | | [[wiki/agent-sdk/custom-tools\|custom-tools]] | Define custom tools with @tool/@tool(), in-process MCP server, error handling, images, resources, annotations | raw/Give Claude custom tools.md | 2026-04-17 | +| [[wiki/agent-sdk/user-input-approvals\|user-input-approvals]] | canUseTool callback: tool approvals, AskUserQuestion clarifying questions, allow/deny/modify, Python streaming workaround | raw/Handle approvals and user input.md | 2026-04-17 | diff --git a/wiki/agent-sdk/user-input-approvals.md b/wiki/agent-sdk/user-input-approvals.md new file mode 100644 index 0000000..1bbb33b --- /dev/null +++ b/wiki/agent-sdk/user-input-approvals.md @@ -0,0 +1,171 @@ +--- +title: "Handle Approvals and User Input" +aliases: [canUseTool, user-approvals, ask-user-question] +tags: [agent-sdk, permissions, user-input, python, typescript] +sources: [raw/Handle approvals and user input.md] +created: 2026-04-17 +updated: 2026-04-17 +--- + +# Handle Approvals and User Input + +Surface Claude's approval requests and clarifying questions to users, then return their decisions back to the SDK via the `canUseTool` callback. + +## When Claude Pauses for Input + +Claude stops and fires `canUseTool` in two situations: + +| Situation | `tool_name` value | Trigger | +|-----------|------------------|---------| +| Tool needs permission | `"Bash"`, `"Write"`, `"Edit"`, etc. | Tool not auto-approved by [[wiki/agent-sdk/configure-permissions\|permission rules]] | +| Clarifying question | `"AskUserQuestion"` | Claude needs direction before proceeding | + +This is distinct from a normal conversation turn — execution is paused until your callback returns. + +## canUseTool Callback Setup + +```python +async def handle_tool_request(tool_name, input_data, context): + # prompt user, return allow or deny + ... + +options = ClaudeAgentOptions(can_use_tool=handle_tool_request) +``` + +**Python requirement:** streaming mode + a `PreToolUse` hook returning `{"continue_": True}` — without it, the stream closes before the callback fires. + +```python +async def dummy_hook(input_data, tool_use_id, context): + return {"continue_": True} + +options = ClaudeAgentOptions( + can_use_tool=can_use_tool, + hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[dummy_hook])]}, +) +``` + +## Tool Approval Requests + +### Callback arguments + +| Argument | Description | +|----------|-------------| +| `tool_name` | Tool Claude wants to use (`"Bash"`, `"Write"`, `"Edit"`, `"Read"`) | +| `input_data` | Tool-specific parameters (e.g. `command`, `file_path`, `content`) | +| `context` | Suggestions (`PermissionUpdate` entries) + cancellation signal | + +### Response types + +| Response | Python | TypeScript | +|----------|--------|------------| +| Allow | `PermissionResultAllow(updated_input=input_data)` | `{ behavior: "allow", updatedInput }` | +| Deny | `PermissionResultDeny(message="reason")` | `{ behavior: "deny", message }` | + +### Response strategies + +- **Approve** — pass input unchanged +- **Approve with changes** — sanitize paths, add constraints before passing +- **Reject** — block the tool, Claude sees your message and may adjust +- **Suggest alternative** — block + guide Claude toward what user wants +- **Redirect** — use streaming input to send a new instruction entirely + +## AskUserQuestion — Clarifying Questions + +When Claude needs direction (common in [[wiki/agent-sdk/configure-permissions\|plan mode]]), it calls `AskUserQuestion`. Route on `tool_name == "AskUserQuestion"`. + +### Input format Claude sends + +```json +{ + "questions": [ + { + "question": "How should I format the output?", + "header": "Format", + "options": [ + { "label": "Summary", "description": "Brief overview of key points" }, + { "label": "Detailed", "description": "Full explanation with examples" } + ], + "multiSelect": false + } + ] +} +``` + +### Response format you return + +```json +{ + "questions": [ /* pass through original */ ], + "answers": { + "How should I format the output?": "Summary", + "Which sections should I include?": "Introduction, Conclusion" + } +} +``` + +- Single-select: one label +- Multi-select: labels joined with `", "` +- Free text: user's raw text (add an "Other" option in your UI) + +### Option previews (TypeScript only) + +Set `toolConfig.askUserQuestion.previewFormat` to `"markdown"` or `"html"` — Claude adds a `preview` field to options where a visual comparison helps. Check for `undefined` before rendering. + +## Complete Python Example + +```python +def parse_response(response: str, options: list) -> str: + try: + indices = [int(s.strip()) - 1 for s in response.split(",")] + labels = [options[i]["label"] for i in indices if 0 <= i < len(options)] + return ", ".join(labels) if labels else response + except ValueError: + return response + +async def handle_ask_user_question(input_data: dict) -> PermissionResultAllow: + answers = {} + for q in input_data.get("questions", []): + print(f"\n{q['header']}: {q['question']}") + for i, opt in enumerate(q["options"]): + print(f" {i+1}. {opt['label']} - {opt['description']}") + response = input("Your choice: ").strip() + answers[q["question"]] = parse_response(response, q["options"]) + return PermissionResultAllow( + updated_input={"questions": input_data.get("questions", []), "answers": answers} + ) + +async def can_use_tool(tool_name, input_data, context): + if tool_name == "AskUserQuestion": + return await handle_ask_user_question(input_data) + # display tool info, prompt y/n + response = input(f"Allow {tool_name}? (y/n): ") + if response.lower() == "y": + return PermissionResultAllow(updated_input=input_data) + return PermissionResultDeny(message="User denied this action") +``` + +## Limitations + +- `AskUserQuestion` is **not available in subagents** spawned via the Agent tool +- Each call supports **1–4 questions**, each with **2–4 options** + +## Alternatives to canUseTool + +| Mechanism | Best for | +|-----------|----------| +| [[wiki/agent-sdk/hooks-guide\|Hooks]] | Auto-allow/deny without prompting; external notifications (Slack, email) via `PermissionRequest` hook | +| Streaming input | Interrupting mid-task, providing context, chat interfaces | +| [[wiki/agent-sdk/custom-tools\|Custom tools]] | Structured forms, multi-step workflows, external approval systems | + +## Key Takeaways + +- `canUseTool` is the single callback for both tool approvals and `AskUserQuestion` — branch on `tool_name` +- Python requires streaming mode + a `PreToolUse` dummy hook to keep the stream alive +- Deny responses include a message Claude reads — use it to redirect, not just block +- `AskUserQuestion` is especially common in plan mode; include it in your `tools` array if you define one +- You can modify tool input before allowing (sanitize, constrain) — not just pass-through +- For automation without user prompts, use [[wiki/agent-sdk/hooks-guide\|hooks]] instead of `canUseTool` + +## Sources + +- `raw/Handle approvals and user input.md` — official Agent SDK docs on user input handling