@actagent/diffs 2026.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,236 @@
1
+ # @actagent/diffs
2
+
3
+ Read-only diff viewer plugin for **ACTAgent** agents.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ actagent plugins install @actagent/diffs
9
+ ```
10
+
11
+ Restart the Gateway after installing or updating the plugin.
12
+
13
+ It gives agents one tool, `diffs`, that can:
14
+
15
+ - render a gateway-hosted diff viewer for canvas use
16
+ - render the same diff to a file (PNG or PDF)
17
+ - accept either arbitrary `before` and `after` text or a unified patch
18
+
19
+ ## What Agents Get
20
+
21
+ The tool can return:
22
+
23
+ - `details.viewerUrl`: a gateway URL that can be opened in the canvas
24
+ - `details.filePath`: a local rendered artifact path when file rendering is requested
25
+ - `details.fileFormat`: the rendered file format (`png` or `pdf`)
26
+ - `details.artifactId` and `details.expiresAt`: artifact identity and TTL metadata
27
+ - `details.context`: available routing metadata such as `agentId`, `sessionId`, `messageChannel`, and `agentAccountId`
28
+
29
+ When the plugin is enabled, it also ships a companion skill from `skills/` and prepends stable tool-usage guidance into system-prompt space via `before_prompt_build`. The hook uses `prependSystemContext`, so the guidance stays out of user-prompt space while still being available every turn.
30
+
31
+ This means an agent can:
32
+
33
+ - call `diffs` with `mode=view`, then pass `details.viewerUrl` to `canvas present`
34
+ - call `diffs` with `mode=file`, then send the file through the normal `message` tool using `path` or `filePath`
35
+ - call `diffs` with `mode=both` when it wants both outputs
36
+
37
+ ## Tool Inputs
38
+
39
+ Before and after:
40
+
41
+ ```json
42
+ {
43
+ "before": "# Hello\n\nOne",
44
+ "after": "# Hello\n\nTwo",
45
+ "path": "docs/example.md",
46
+ "mode": "view"
47
+ }
48
+ ```
49
+
50
+ Patch:
51
+
52
+ ```json
53
+ {
54
+ "patch": "diff --git a/src/example.ts b/src/example.ts\n--- a/src/example.ts\n+++ b/src/example.ts\n@@ -1 +1 @@\n-const x = 1;\n+const x = 2;\n",
55
+ "mode": "both"
56
+ }
57
+ ```
58
+
59
+ Useful options:
60
+
61
+ - `mode`: `view`, `file`, or `both`
62
+ Deprecated alias: `image` behaves like `file` and is still accepted for backward compatibility.
63
+ - `layout`: `unified` or `split`
64
+ - `theme`: `light` or `dark` (default: `dark`)
65
+ - `fileFormat`: `png` or `pdf` (default: `png`)
66
+ - `fileQuality`: `standard`, `hq`, or `print`
67
+ - `fileScale`: device scale override (`1`-`4`)
68
+ - `fileMaxWidth`: max width override in CSS pixels (`640`-`2400`)
69
+ - `expandUnchanged`: expand unchanged sections (per-call option only, not a plugin default key)
70
+ - `path`: display name for before and after input
71
+ - `lang`: language hint for before/after input; unknown values fall back to plain text
72
+ - Default syntax highlighting covers common source, config, and documentation languages. Install `diffs-language-pack` for the extended language catalog.
73
+ - `title`: explicit viewer title
74
+ - `ttlSeconds`: artifact lifetime for viewer and standalone file outputs
75
+ - `baseUrl`: override the gateway base URL used in the returned viewer link (origin or origin+base path only; no query/hash)
76
+ - `viewerBaseUrl` plugin config: persistent fallback used when a tool call omits `baseUrl`
77
+
78
+ Legacy input aliases still accepted for backward compatibility:
79
+
80
+ - `format` -> `fileFormat`
81
+ - `imageFormat` -> `fileFormat`
82
+ - `imageQuality` -> `fileQuality`
83
+ - `imageScale` -> `fileScale`
84
+ - `imageMaxWidth` -> `fileMaxWidth`
85
+
86
+ Input safety limits:
87
+
88
+ - `before` and `after`: max 512 KiB each
89
+ - `patch`: max 2 MiB
90
+ - patch rendering cap: max 128 files / 120,000 lines
91
+
92
+ ## Plugin Defaults
93
+
94
+ Set plugin-wide defaults in `~/.actagent/actagent.json`:
95
+
96
+ ```json5
97
+ {
98
+ plugins: {
99
+ entries: {
100
+ diffs: {
101
+ enabled: true,
102
+ config: {
103
+ defaults: {
104
+ fontFamily: "Fira Code",
105
+ fontSize: 15,
106
+ lineSpacing: 1.6,
107
+ layout: "unified",
108
+ showLineNumbers: true,
109
+ diffIndicators: "bars",
110
+ wordWrap: true,
111
+ background: true,
112
+ theme: "dark",
113
+ fileFormat: "png",
114
+ fileQuality: "standard",
115
+ fileScale: 2,
116
+ fileMaxWidth: 960,
117
+ mode: "both",
118
+ ttlSeconds: 21600,
119
+ },
120
+ },
121
+ },
122
+ },
123
+ },
124
+ }
125
+ ```
126
+
127
+ Explicit tool parameters still win over these defaults.
128
+
129
+ ## Docs
130
+
131
+ - https://docs.actagent.ai/tools/diffs
132
+
133
+ ## Package
134
+
135
+ - Plugin id: `diffs`
136
+ - Package: `@actagent/diffs`
137
+ - Minimum ACTAgent host: `2026.4.30`
138
+
139
+ Security options:
140
+
141
+ - `security.allowRemoteViewer` (default `false`): allows non-loopback access to `/plugins/diffs/view/...` token URLs
142
+ - `viewerBaseUrl` (optional): persistent viewer-link origin/path fallback for shareable URLs
143
+ - `defaults.ttlSeconds` (default `1800`, max `21600`): default artifact lifetime for viewer and standalone file outputs
144
+
145
+ Example:
146
+
147
+ ```json5
148
+ {
149
+ plugins: {
150
+ entries: {
151
+ diffs: {
152
+ enabled: true,
153
+ config: {
154
+ viewerBaseUrl: "https://gateway.example.com/actagent",
155
+ },
156
+ },
157
+ },
158
+ },
159
+ }
160
+ ```
161
+
162
+ ## Example Agent Prompts
163
+
164
+ Open in canvas:
165
+
166
+ ```text
167
+ Use the `diffs` tool in `view` mode for this before and after content, then open the returned viewer URL in the canvas.
168
+
169
+ Path: docs/example.md
170
+
171
+ Before:
172
+ # Hello
173
+
174
+ This is version one.
175
+
176
+ After:
177
+ # Hello
178
+
179
+ This is version two.
180
+ ```
181
+
182
+ Render a file (PNG or PDF):
183
+
184
+ ```text
185
+ Use the `diffs` tool in `file` mode for this before and after input. After it returns `details.filePath`, use the `message` tool with `path` or `filePath` to send me the rendered diff file.
186
+
187
+ Path: README.md
188
+
189
+ Before:
190
+ ACTAgent supports plugins.
191
+
192
+ After:
193
+ ACTAgent supports plugins and hosted diff views.
194
+ ```
195
+
196
+ Do both:
197
+
198
+ ```text
199
+ Use the `diffs` tool in `both` mode for this diff. Open the viewer in the canvas and then send the rendered file by passing `details.filePath` to the `message` tool.
200
+
201
+ Path: src/demo.ts
202
+
203
+ Before:
204
+ const status = "old";
205
+
206
+ After:
207
+ const status = "new";
208
+ ```
209
+
210
+ Patch input:
211
+
212
+ ```text
213
+ Use the `diffs` tool with this unified patch in `view` mode. After it returns the viewer URL, present it in the canvas.
214
+
215
+ diff --git a/src/example.ts b/src/example.ts
216
+ --- a/src/example.ts
217
+ +++ b/src/example.ts
218
+ @@ -1,3 +1,3 @@
219
+ export function add(a: number, b: number) {
220
+ - return a + b;
221
+ + return a + b + 1;
222
+ }
223
+ ```
224
+
225
+ ## Notes
226
+
227
+ - The viewer is hosted locally through the gateway under `/plugins/diffs/...`.
228
+ - Artifacts are ephemeral and stored in the plugin temp subfolder (`$TMPDIR/actagent-diffs`).
229
+ - Default viewer URLs use loopback (`127.0.0.1`) unless you set plugin `viewerBaseUrl`, pass `baseUrl`, or use `gateway.bind=custom` + `gateway.customBindHost`.
230
+ - If `gateway.trustedProxies` includes loopback for a same-host proxy (for example Tailscale Serve), raw `127.0.0.1` viewer requests without forwarded client-IP headers fail closed by design.
231
+ - In that topology, prefer `mode=file` / `mode=both` for attachments, or intentionally enable remote viewers and set plugin `viewerBaseUrl` (or pass a proxy/public `baseUrl`) when you need a shareable viewer URL.
232
+ - Remote viewer misses are throttled to reduce token-guess abuse.
233
+ - PNG or PDF rendering requires a Chromium-compatible browser. Set `browser.executablePath` if auto-detection is not enough.
234
+ - If your delivery channel compresses images heavily (for example Telegram or WhatsApp), prefer `fileFormat: "pdf"` to preserve readability.
235
+ - `N unmodified lines` rows may not always include expand controls for patch input, because many patch hunks do not carry full expandable context data.
236
+ - Diff rendering is powered by [Diffs](https://diffs.com).
@@ -0,0 +1,218 @@
1
+ {
2
+ "id": "diffs",
3
+ "activation": {
4
+ "onStartup": true
5
+ },
6
+ "name": "Diffs",
7
+ "description": "ACTAgent read-only diff viewer plugin and file renderer for agents.",
8
+ "contracts": {
9
+ "tools": ["diffs"]
10
+ },
11
+ "toolMetadata": {
12
+ "diffs": {
13
+ "optional": true
14
+ }
15
+ },
16
+ "skills": ["./skills"],
17
+ "uiHints": {
18
+ "viewerBaseUrl": {
19
+ "label": "Viewer Base URL",
20
+ "help": "Persistent gateway base URL used for returned viewer links when a tool call does not pass baseUrl."
21
+ },
22
+ "defaults.fontFamily": {
23
+ "label": "Default Font",
24
+ "help": "Preferred font family name for diff content and headers."
25
+ },
26
+ "defaults.fontSize": {
27
+ "label": "Default Font Size",
28
+ "help": "Base diff font size in pixels."
29
+ },
30
+ "defaults.lineSpacing": {
31
+ "label": "Default Line Spacing",
32
+ "help": "Line-height multiplier applied to diff rows."
33
+ },
34
+ "defaults.layout": {
35
+ "label": "Default Layout",
36
+ "help": "Initial diff layout shown in the viewer."
37
+ },
38
+ "defaults.showLineNumbers": {
39
+ "label": "Show Line Numbers",
40
+ "help": "Show line numbers by default."
41
+ },
42
+ "defaults.diffIndicators": {
43
+ "label": "Diff Indicator Style",
44
+ "help": "Choose added/removed indicators style."
45
+ },
46
+ "defaults.wordWrap": {
47
+ "label": "Default Word Wrap",
48
+ "help": "Wrap long lines by default."
49
+ },
50
+ "defaults.background": {
51
+ "label": "Default Background Highlights",
52
+ "help": "Show added/removed background highlights by default."
53
+ },
54
+ "defaults.theme": {
55
+ "label": "Default Theme",
56
+ "help": "Initial viewer theme."
57
+ },
58
+ "defaults.fileFormat": {
59
+ "label": "Default File Format",
60
+ "help": "Rendered file format for file mode (PNG or PDF)."
61
+ },
62
+ "defaults.fileQuality": {
63
+ "label": "Default File Quality",
64
+ "help": "Quality preset for PNG/PDF rendering."
65
+ },
66
+ "defaults.fileScale": {
67
+ "label": "Default File Scale",
68
+ "help": "Device scale factor used while rendering file artifacts."
69
+ },
70
+ "defaults.fileMaxWidth": {
71
+ "label": "Default File Max Width",
72
+ "help": "Maximum file render width in CSS pixels."
73
+ },
74
+ "defaults.mode": {
75
+ "label": "Default Output Mode",
76
+ "help": "Tool default when mode is omitted. Use view for canvas/gateway viewer, file for PNG/PDF, or both."
77
+ },
78
+ "defaults.ttlSeconds": {
79
+ "label": "Default Artifact TTL",
80
+ "help": "Default lifetime in seconds for diff viewer and file artifacts. Maximum: 21600."
81
+ },
82
+ "security.allowRemoteViewer": {
83
+ "label": "Allow Remote Viewer",
84
+ "help": "Allow non-loopback access to diff viewer URLs when the token path is known."
85
+ }
86
+ },
87
+ "configSchema": {
88
+ "type": "object",
89
+ "additionalProperties": false,
90
+ "properties": {
91
+ "viewerBaseUrl": {
92
+ "type": "string",
93
+ "format": "uri",
94
+ "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://",
95
+ "not": {
96
+ "pattern": "[?#]"
97
+ }
98
+ },
99
+ "defaults": {
100
+ "type": "object",
101
+ "additionalProperties": false,
102
+ "properties": {
103
+ "fontFamily": {
104
+ "type": "string",
105
+ "default": "Fira Code"
106
+ },
107
+ "fontSize": {
108
+ "type": "number",
109
+ "minimum": 10,
110
+ "maximum": 24,
111
+ "default": 15
112
+ },
113
+ "lineSpacing": {
114
+ "type": "number",
115
+ "minimum": 1,
116
+ "maximum": 3,
117
+ "default": 1.6
118
+ },
119
+ "layout": {
120
+ "type": "string",
121
+ "enum": ["unified", "split"],
122
+ "default": "unified"
123
+ },
124
+ "showLineNumbers": {
125
+ "type": "boolean",
126
+ "default": true
127
+ },
128
+ "diffIndicators": {
129
+ "type": "string",
130
+ "enum": ["bars", "classic", "none"],
131
+ "default": "bars"
132
+ },
133
+ "wordWrap": {
134
+ "type": "boolean",
135
+ "default": true
136
+ },
137
+ "background": {
138
+ "type": "boolean",
139
+ "default": true
140
+ },
141
+ "theme": {
142
+ "type": "string",
143
+ "enum": ["light", "dark"],
144
+ "default": "dark"
145
+ },
146
+ "fileFormat": {
147
+ "type": "string",
148
+ "enum": ["png", "pdf"],
149
+ "default": "png"
150
+ },
151
+ "format": {
152
+ "type": "string",
153
+ "description": "Deprecated alias for fileFormat.",
154
+ "enum": ["png", "pdf"]
155
+ },
156
+ "fileQuality": {
157
+ "type": "string",
158
+ "enum": ["standard", "hq", "print"],
159
+ "default": "standard"
160
+ },
161
+ "fileScale": {
162
+ "type": "number",
163
+ "minimum": 1,
164
+ "maximum": 4
165
+ },
166
+ "fileMaxWidth": {
167
+ "type": "number",
168
+ "minimum": 640,
169
+ "maximum": 2400
170
+ },
171
+ "imageFormat": {
172
+ "type": "string",
173
+ "description": "Deprecated alias for fileFormat.",
174
+ "enum": ["png", "pdf"]
175
+ },
176
+ "imageQuality": {
177
+ "type": "string",
178
+ "description": "Deprecated alias for fileQuality.",
179
+ "enum": ["standard", "hq", "print"]
180
+ },
181
+ "imageScale": {
182
+ "type": "number",
183
+ "description": "Deprecated alias for fileScale.",
184
+ "minimum": 1,
185
+ "maximum": 4
186
+ },
187
+ "imageMaxWidth": {
188
+ "type": "number",
189
+ "description": "Deprecated alias for fileMaxWidth.",
190
+ "minimum": 640,
191
+ "maximum": 2400
192
+ },
193
+ "mode": {
194
+ "type": "string",
195
+ "enum": ["view", "image", "file", "both"],
196
+ "default": "both"
197
+ },
198
+ "ttlSeconds": {
199
+ "type": "number",
200
+ "minimum": 1,
201
+ "maximum": 21600,
202
+ "default": 1800
203
+ }
204
+ }
205
+ },
206
+ "security": {
207
+ "type": "object",
208
+ "additionalProperties": false,
209
+ "properties": {
210
+ "allowRemoteViewer": {
211
+ "type": "boolean",
212
+ "default": false
213
+ }
214
+ }
215
+ }
216
+ }
217
+ }
218
+ }
package/api.ts ADDED
@@ -0,0 +1,11 @@
1
+ // Diffs API module exposes the plugin public contract.
2
+ export type { ACTAgentConfig } from "actagent/plugin-sdk/config-contracts";
3
+ export {
4
+ definePluginEntry,
5
+ type AnyAgentTool,
6
+ type ACTAgentPluginApi,
7
+ type ACTAgentPluginConfigSchema,
8
+ type ACTAgentPluginToolContext,
9
+ type PluginLogger,
10
+ } from "actagent/plugin-sdk/plugin-entry";
11
+ export { resolvePreferredACTAgentTmpDir } from "actagent/plugin-sdk/temp-path";