diff --git a/apps/x/LIVE_NOTE.md b/apps/x/LIVE_NOTE.md index fe31d019..d8a157d7 100644 --- a/apps/x/LIVE_NOTE.md +++ b/apps/x/LIVE_NOTE.md @@ -70,7 +70,7 @@ The `once` trigger from the prior model has been **dropped** — it didn't fit t Two paths, both producing identical on-disk YAML: 1. **Hand-written** — type the `live:` block directly into the note's frontmatter. The scheduler picks it up on its next 15-second tick. -2. **Sidebar chat** — mention a note (or have it attached) and ask Copilot for something dynamic. Copilot is tuned to recognize a wide range of phrasings beyond "live" or "track" (see "Prompts Catalog → Copilot trigger paragraph"); it loads the `live-note` skill, edits the frontmatter via `workspace-edit`, then **runs the agent once by default** so the user immediately sees content. The auto-run is skipped only when the user explicitly says not to run yet. +2. **Sidebar chat** — mention a note (or have it attached) and ask Copilot for something dynamic. Copilot is tuned to recognize a wide range of phrasings beyond "live" or "track" (see "Prompts Catalog → Copilot trigger paragraph"); it loads the `live-note` skill, edits the frontmatter via `file-editText`, then **runs the agent once by default** so the user immediately sees content. The auto-run is skipped only when the user explicitly says not to run yet. When the note is **already live** and the user asks to track something new, Copilot extends the existing `live.objective` in natural-language prose. It does not create a second `live:` block. @@ -92,8 +92,8 @@ When a trigger fires, the live-note agent receives a short message: - For event runs only: the matching `eventMatchCriteria` text and the event payload, with a Pass-2 decision directive ("only edit if the event genuinely warrants it"). The agent's system prompt tells it to: -1. Call `workspace-readFile` to read the current note (the body may be long; no body snapshot is passed in the message — fetch fresh). -2. Make small, **patch-style** edits with `workspace-edit` — change one region, re-read, change the next region — rather than one-shot rewrites. +1. Call `file-readText` to read the current note (the body may be long; no body snapshot is passed in the message — fetch fresh). +2. Make small, **patch-style** edits with `file-editText` — change one region, re-read, change the next region — rather than one-shot rewrites. 3. Follow default body structure unless the objective overrides: H1 stays the title; a 1-3 sentence rolling summary at the top; H2 sub-topic sections below, freshest first. 4. Never modify YAML frontmatter — that's owned by the user and the runtime. 5. End with a 1-2 sentence summary stored as `lastRunSummary`. @@ -115,7 +115,7 @@ Backend (main process) ├─ Event processor (5 s) ──┼──► runLiveNoteAgent() ──► live-note-agent └─ Builtin tool │ │ run-live-note-agent ────┘ ▼ - workspace-readFile / -edit + file-readText / -edit │ ▼ body region(s) rewritten on disk @@ -249,7 +249,7 @@ The contract (defined in the run-agent system prompt — `packages/core/src/know - Then content organized by sub-topic under H2 headings, freshest/most-important first. - Tightness over decoration. - **Override** — if the objective specifies a different layout (e.g. "show the top 5 stories at the top, with a one-paragraph summary above them"), follow that exactly. -- **Patch-style updates** — make small, incremental `workspace-edit` calls (read → edit one region → re-read → next), not one-shot whole-body rewrites. This preserves user-added content the agent didn't account for and keeps diffs reviewable. +- **Patch-style updates** — make small, incremental `file-editText` calls (read → edit one region → re-read → next), not one-shot whole-body rewrites. This preserves user-added content the agent didn't account for and keeps diffs reviewable. - **Boundaries**: never modify the frontmatter; the agent is the sole writer of the body below the H1. --- @@ -316,7 +316,7 @@ Every LLM-facing prompt in the feature, with file pointers. After any edit: `cd - **Purpose**: the user message seeded into each agent run. - **File**: `packages/core/src/knowledge/live-note/runner.ts` (`buildMessage`). - **Inputs**: `filePath` (presented as `knowledge/${filePath}` in the message), `live.objective`, `live.triggers?.eventMatchCriteria` (only on event runs), `trigger`, optional `context`, plus `localNow` / `tz`. -- **Behavior**: tells the agent to call `workspace-readFile` itself (no body snapshot included, since the body can be long and may have been edited by a concurrent run) and to make patch-style edits. +- **Behavior**: tells the agent to call `file-readText` itself (no body snapshot included, since the body can be long and may have been edited by a concurrent run) and to make patch-style edits. Three branches by `trigger`: - **`manual`** — base message. If `context` is passed, it's appended as a `**Context:**` section. The `run-live-note-agent` tool uses this path for both plain refreshes and context-biased backfills. diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index 33eda548..080ddf58 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -5677,6 +5677,7 @@ function App() { {rendered} handlePermissionResponse(permRequest.toolCall.toolCallId, permRequest.subflow, 'approve')} onApproveSession={() => handlePermissionResponse(permRequest.toolCall.toolCallId, permRequest.subflow, 'approve', 'session')} onApproveAlways={() => handlePermissionResponse(permRequest.toolCall.toolCallId, permRequest.subflow, 'approve', 'always')} diff --git a/apps/x/apps/renderer/src/components/ai-elements/permission-request.tsx b/apps/x/apps/renderer/src/components/ai-elements/permission-request.tsx index e9cef6dc..c0369a9a 100644 --- a/apps/x/apps/renderer/src/components/ai-elements/permission-request.tsx +++ b/apps/x/apps/renderer/src/components/ai-elements/permission-request.tsx @@ -12,6 +12,7 @@ import { cn } from "@/lib/utils"; import { AlertTriangleIcon, CheckCircleIcon, CheckIcon, ChevronDownIcon, XCircleIcon, XIcon } from "lucide-react"; import type { ComponentProps } from "react"; import { ToolCallPart } from "@x/shared/dist/message.js"; +import { ToolPermissionMetadata } from "@x/shared/dist/runs.js"; import z from "zod"; export type PermissionRequestProps = ComponentProps<"div"> & { @@ -22,6 +23,15 @@ export type PermissionRequestProps = ComponentProps<"div"> & { onDeny?: () => void; isProcessing?: boolean; response?: 'approve' | 'deny' | null; + permission?: z.infer; +}; + +const fileActionLabels: Record = { + read: "Read file", + list: "List folder", + search: "Search files", + write: "Write files", + delete: "Delete path", }; export const PermissionRequest = ({ @@ -33,14 +43,16 @@ export const PermissionRequest = ({ onDeny, isProcessing = false, response = null, + permission, ...props }: PermissionRequestProps) => { // Extract command from arguments if it's executeCommand - const command = toolCall.toolName === "executeCommand" + const command = permission?.kind === "command" || toolCall.toolName === "executeCommand" ? (typeof toolCall.arguments === "object" && toolCall.arguments !== null && "command" in toolCall.arguments ? String(toolCall.arguments.command) : JSON.stringify(toolCall.arguments)) : null; + const filePermission = permission?.kind === "file" ? permission : null; const isResponded = response !== null; const isApproved = response === 'approve'; @@ -113,7 +125,35 @@ export const PermissionRequest = ({ )} - {!command && toolCall.arguments && ( + {filePermission && ( +
+
+

+ Action +

+

+ {fileActionLabels[filePermission.operation] ?? filePermission.operation} +

+
+
+

+ Path{filePermission.paths.length === 1 ? "" : "s"} +

+
+                    {filePermission.paths.join("\n")}
+                  
+
+
+

+ Approval Scope +

+
+                    {filePermission.pathPrefix}
+                  
+
+
+ )} + {!command && !filePermission && toolCall.arguments && (

Arguments @@ -133,12 +173,12 @@ export const PermissionRequest = ({ size="sm" onClick={onApprove} disabled={isProcessing} - className={cn("flex-1", command && "rounded-r-none")} + className={cn("flex-1", (command || filePermission) && "rounded-r-none")} > Approve - {command && ( + {(command || filePermission) && (