Skip to content

Commit cf84f38

Browse files
lesaltzmCopilot
andauthored
Add hook to Canvas plugin to encourage using the sync tool on every turn start (#320)
* Add hook to encourage using the sync tool on every turn start * Clean up file reference * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent e52e38b commit cf84f38

6 files changed

Lines changed: 112 additions & 19 deletions

File tree

plugins/canvas-apps/.claude-plugin/plugin.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "canvas-apps",
3-
"version": "2.1.1",
3+
"version": "2.2.0",
44
"description": "Build Power Apps Canvas Apps using the Canvas Authoring MCP server.",
55
"author": {
66
"name": "Microsoft",
@@ -9,6 +9,7 @@
99
"homepage": "https://github.com/microsoft/power-platform-skills/",
1010
"repository": "https://github.com/microsoft/power-platform-skills/",
1111
"license": "MIT",
12+
"hooks": "hooks/hooks.json",
1213
"keywords": [
1314
"canvas-apps",
1415
"power-apps",

plugins/canvas-apps/.plugin/plugin.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "canvas-apps",
3-
"version": "2.1.1",
3+
"version": "2.2.0",
44
"description": "Build Power Apps Canvas Apps using the Canvas Authoring MCP server.",
55
"author": {
66
"name": "Microsoft",
@@ -9,6 +9,7 @@
99
"homepage": "https://github.com/microsoft/power-platform-skills/",
1010
"repository": "https://github.com/microsoft/power-platform-skills/",
1111
"license": "MIT",
12+
"hooks": "hooks/hooks.json",
1213
"keywords": [
1314
"canvas-apps",
1415
"power-apps",

plugins/canvas-apps/AGENTS.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ claude --plugin-dir /path/to/plugins/canvas-apps
1919
## Architecture
2020

2121
```
22-
.plugin/plugin.json ← Open Plugins metadata (name, version, keywords)
22+
.plugin/plugin.json ← Open Plugins metadata and Copilot hook path
2323
.mcp.json ← MCP server config (canvas-authoring, auto-registered)
2424
AGENTS.md ← Plugin guidance for AI agents (this file)
2525
CLAUDE.md ← Symlink → AGENTS.md
26+
hooks/
27+
hooks.json ← Claude and Copilot prompt hook registration
28+
inject-sync-reminder.cs ← File-based .NET app that injects a cross-host sync reminder
2629
references/
2730
TechnicalGuide.md ← YAML syntax, control selection, layout strategies, Power Fx patterns
2831
DesignGuide.md ← Aesthetic guidelines, anti-patterns, design process
@@ -75,6 +78,20 @@ The `canvas-authoring` MCP server exposes the following tools:
7578
| `list_data_sources` | Lists all available data sources in the current authoring session |
7679
| `sync_canvas` | Syncs the current coauthoring session state from the server to a local directory, writing all YAML files |
7780

81+
## Hooks
82+
83+
Hook registration is centralized in `hooks/hooks.json`. It registers Claude Code's
84+
`UserPromptSubmit` event and Copilot's `userPromptTransformed` event. Both invoke the
85+
file-based .NET app at `hooks/inject-sync-reminder.cs` with `dotnet run --file`; the app
86+
emits `modifiedTransformedPrompt` for Copilot and
87+
`hookSpecificOutput.additionalContext` for Claude Code. This avoids requiring Node.js at
88+
plugin runtime. Both paths remind the agent to call `sync_canvas` (targeting the app's
89+
working directory) before acting, so it never edits stale local `.pa.yaml` files. The
90+
reminder is conditional in wording — the agent skips the sync when no coauthoring session
91+
is active or the request is unrelated to a canvas app. Keep the Copilot event name
92+
lowercase: Copilot ignores the PascalCase variant, while Claude Code ignores the
93+
Copilot-only event and continues loading `UserPromptSubmit`.
94+
7895
## Prerequisites
7996

8097
Before the MCP server will start, you need:
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"description": "Canvas Apps lifecycle hooks",
3+
"hooks": {
4+
"UserPromptSubmit": [
5+
{
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"command": "dotnet run --file \"${CLAUDE_PLUGIN_ROOT}/hooks/inject-sync-reminder.cs\"",
10+
"timeout": 30
11+
}
12+
]
13+
}
14+
],
15+
"userPromptTransformed": [
16+
{
17+
"hooks": [
18+
{
19+
"type": "command",
20+
"command": "dotnet run --file \"${PLUGIN_ROOT}/hooks/inject-sync-reminder.cs\"",
21+
"timeout": 30
22+
}
23+
]
24+
}
25+
]
26+
}
27+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#:property PublishAot=false
2+
3+
using System.Text.Json;
4+
5+
// Prompt hook for the canvas-apps plugin.
6+
//
7+
// The hook injects guidance instead of calling sync_canvas directly because it only
8+
// receives the session cwd, not the per-app working directory. A blanket sync could
9+
// overwrite the wrong directory or clobber local edits that have not been pushed.
10+
const string Reminder =
11+
"Canvas Apps coauthoring reminder: " +
12+
"Before acting on this request, if a Canvas Authoring session is " +
13+
"active (i.e. `connect` has already been called for a canvas app), first call " +
14+
"the `sync_canvas` MCP tool targeting the app's working directory to pull the " +
15+
"current session state into the local .pa.yaml files. This avoids editing stale " +
16+
"YAML. If no coauthoring session is active yet, or this request is unrelated to " +
17+
"a canvas app, skip the sync.";
18+
19+
try
20+
{
21+
var rawInput = await Console.In.ReadToEndAsync();
22+
string? transformedPrompt = null;
23+
24+
try
25+
{
26+
using var input = JsonDocument.Parse(rawInput);
27+
if (input.RootElement.ValueKind == JsonValueKind.Object &&
28+
input.RootElement.TryGetProperty("transformedPrompt", out var prompt) &&
29+
prompt.ValueKind == JsonValueKind.String)
30+
{
31+
transformedPrompt = prompt.GetString();
32+
}
33+
}
34+
catch (JsonException)
35+
{
36+
// Hook failures must not block the user's prompt. If parsing fails, emit the
37+
// Claude Code additional-context shape (Copilot will ignore it).
38+
}
39+
40+
using var writer = new Utf8JsonWriter(Console.OpenStandardOutput());
41+
writer.WriteStartObject();
42+
43+
if (transformedPrompt is not null)
44+
{
45+
writer.WriteString(
46+
"modifiedTransformedPrompt",
47+
$"{transformedPrompt}\n\n{Reminder}");
48+
}
49+
else
50+
{
51+
writer.WriteStartObject("hookSpecificOutput");
52+
writer.WriteString("hookEventName", "UserPromptSubmit");
53+
writer.WriteString("additionalContext", Reminder);
54+
writer.WriteEndObject();
55+
}
56+
57+
writer.WriteEndObject();
58+
writer.Flush();
59+
}
60+
catch
61+
{
62+
// Hooks are advisory and must never prevent the host from processing a prompt.
63+
}

plugins/canvas-apps/skills/generate-canvas-app/SKILL.md

Lines changed: 0 additions & 16 deletions
This file was deleted.

0 commit comments

Comments
 (0)