@ericsanchezok/synergy-plugin 1.1.28 → 1.2.1

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,405 @@
1
+ # Synergy Plugin SDK
2
+
3
+ `@ericsanchezok/synergy-plugin` is the server-side plugin SDK for Synergy.
4
+
5
+ A plugin extends the runtime with one or more of these capabilities:
6
+
7
+ - custom tools
8
+ - lifecycle hooks around sessions, agenda runs, notes, engram search, and tool execution
9
+ - provider auth integration
10
+ - config and event observers
11
+
12
+ This package is for developers who want to add behavior to the Synergy runtime itself. Plugins do **not** run in the Web client. They run on the server/runtime side, inside the active Scope context, and can access:
13
+
14
+ - `ctx.client` — a Synergy SDK client pointed at the current server
15
+ - `ctx.scope` — the resolved Scope
16
+ - `ctx.directory` / `ctx.worktree` — the current workspace paths
17
+ - `ctx.serverUrl` — the current server URL
18
+ - `ctx.$` — Bun shell access for local runtime-side commands
19
+
20
+ If you want the smallest possible example, see [`src/example.ts`](./src/example.ts). It is intentionally minimal. The example in this README shows a more realistic shape.
21
+
22
+ ## What a plugin looks like
23
+
24
+ A plugin module exports one or more async functions of type `Plugin`.
25
+ Each function is initialized once and returns a set of hooks and capabilities.
26
+ In practice, most plugins should export a single default plugin function.
27
+
28
+ ```ts
29
+ import type { Plugin } from "@ericsanchezok/synergy-plugin"
30
+
31
+ const MyPlugin: Plugin = async (ctx) => {
32
+ return {
33
+ // hooks, tools, auth, config observer, event observer
34
+ }
35
+ }
36
+
37
+ export default MyPlugin
38
+ ```
39
+
40
+ A plugin function receives this runtime context:
41
+
42
+ ```ts
43
+ type PluginInput = {
44
+ client: ReturnType<typeof createSynergyClient>
45
+ scope: {
46
+ type: "global" | "project"
47
+ id: string
48
+ directory: string
49
+ worktree: string
50
+ // ...other scope metadata
51
+ }
52
+ directory: string
53
+ worktree: string
54
+ serverUrl: URL
55
+ $: BunShell
56
+ }
57
+ ```
58
+
59
+ ## Setup
60
+
61
+ For an external plugin package:
62
+
63
+ ```bash
64
+ bun add @ericsanchezok/synergy-plugin zod
65
+ ```
66
+
67
+ Use ESM and export your plugin from your package entrypoint.
68
+ A typical package entry might look like this:
69
+
70
+ ```ts
71
+ import type { Plugin } from "@ericsanchezok/synergy-plugin"
72
+
73
+ const MyPlugin: Plugin = async () => ({})
74
+
75
+ export default MyPlugin
76
+ ```
77
+
78
+ If you are writing a local plugin directly in a Synergy config directory such as `.synergy/plugin/` or `~/.synergy/config/plugin/`, Synergy will install `@ericsanchezok/synergy-plugin` in that config directory automatically. Any additional dependencies declared in that directory's `package.json` are installed there as well.
79
+
80
+ ## How plugins are loaded
81
+
82
+ Synergy loads plugins from two places:
83
+
84
+ ### 1. Explicit plugin entries in `synergy.jsonc`
85
+
86
+ The config schema includes a top-level `plugin` field:
87
+
88
+ ```jsonc
89
+ {
90
+ "plugin": ["your-plugin-package"],
91
+ }
92
+ ```
93
+
94
+ Those entries are resolved as module specifiers and loaded by the runtime.
95
+ Published plugin packages are installed automatically when needed.
96
+
97
+ ### 2. Auto-discovered local plugin files
98
+
99
+ Synergy also scans these directories for `*.ts` and `*.js` files:
100
+
101
+ - project scope: `<project>/.synergy/plugin/` and `<project>/.synergy/plugins/`
102
+ - global config: `~/.synergy/config/plugin/` and `~/.synergy/config/plugins/`
103
+
104
+ This makes local development straightforward:
105
+
106
+ ```text
107
+ my-project/
108
+ .synergy/
109
+ plugin/
110
+ my-plugin.ts
111
+ ```
112
+
113
+ A few practical details:
114
+
115
+ - plugins are initialized in the current runtime Scope
116
+ - every exported plugin function in a module is loaded once
117
+ - `default` export is the safest convention unless you intentionally want multiple plugin instances from one file
118
+ - reloading plugin state also reloads plugin-provided tools
119
+
120
+ ## Minimal plugin
121
+
122
+ This is the smallest useful plugin: one observation hook and no custom tools.
123
+
124
+ ```ts
125
+ import type { Plugin } from "@ericsanchezok/synergy-plugin"
126
+
127
+ const SessionLogger: Plugin = async () => {
128
+ return {
129
+ async "session.turn.after"(input) {
130
+ if (input.error) {
131
+ console.error("session turn failed", input.sessionID, input.error)
132
+ return
133
+ }
134
+
135
+ console.log("session turn completed", input.sessionID, input.assistantMessageID)
136
+ },
137
+ }
138
+ }
139
+
140
+ export default SessionLogger
141
+ ```
142
+
143
+ ## Custom tools
144
+
145
+ Plugins can register tools by returning a `tool` map.
146
+ Use the `tool()` helper to define the tool schema and execution function.
147
+
148
+ ```ts
149
+ import type { Plugin } from "@ericsanchezok/synergy-plugin"
150
+ import { tool } from "@ericsanchezok/synergy-plugin/tool"
151
+
152
+ const GitPlugin: Plugin = async (ctx) => {
153
+ return {
154
+ tool: {
155
+ current_branch: tool({
156
+ description: "Get the current git branch",
157
+ args: {},
158
+ async execute(_args, toolCtx) {
159
+ const out = await ctx.$`git rev-parse --abbrev-ref HEAD`.cwd(ctx.worktree).quiet().text()
160
+
161
+ return [`session: ${toolCtx.sessionID}`, `agent: ${toolCtx.agent}`, `branch: ${out.trim()}`].join("\n")
162
+ },
163
+ }),
164
+ },
165
+ }
166
+ }
167
+
168
+ export default GitPlugin
169
+ ```
170
+
171
+ Tool execution receives a narrower context:
172
+
173
+ ```ts
174
+ type ToolContext = {
175
+ sessionID: string
176
+ messageID: string
177
+ agent: string
178
+ abort: AbortSignal
179
+ }
180
+ ```
181
+
182
+ A few things to know about plugin tools:
183
+
184
+ - tool args are defined with Zod shapes
185
+ - the helper returns plain text output; Synergy wraps it into the runtime tool result format
186
+ - long output may be truncated by the runtime, just like built-in tools
187
+ - if you need runtime context like Scope paths or shell access, close over the outer plugin `ctx`
188
+
189
+ ## Hooks overview
190
+
191
+ If you want a current CLI list instead of reading source, run `synergy plugin hooks` or `synergy plugin hooks --json`.
192
+
193
+ Hooks fall into two broad categories.
194
+
195
+ ### Observation hooks
196
+
197
+ These let you react to runtime events without changing the main result.
198
+ They either have no mutable output object, or their output is not used to drive the main flow.
199
+
200
+ Common examples:
201
+
202
+ - `event`
203
+ - `config`
204
+ - `session.turn.after`
205
+ - `cortex.task.after`
206
+ - `agenda.run.after`
207
+ - `agenda.run.error`
208
+ - `engram.experience.encode.after`
209
+
210
+ Use these for logging, metrics, side effects, external notifications, and indexing.
211
+
212
+ ### Mutation hooks
213
+
214
+ These can shape what Synergy does by mutating the `output` object passed as the second argument.
215
+ The runtime passes an object, your hook edits it in place, and the updated object continues through the pipeline.
216
+
217
+ Common examples:
218
+
219
+ - `chat.message`
220
+ - `chat.params`
221
+ - `permission.ask`
222
+ - `tool.execute.before`
223
+ - `tool.execute.after`
224
+ - `agenda.run.before`
225
+ - `note.create.before`
226
+ - `note.update.before`
227
+ - `note.search.before`
228
+ - `note.search.after`
229
+ - `engram.memory.search.before`
230
+ - `engram.memory.search.after`
231
+ - `experimental.*` transform hooks
232
+
233
+ The rule of thumb is simple: treat `input` as context, and treat `output` as the thing you may change.
234
+
235
+ ## Hook reference
236
+
237
+ ### Core capabilities
238
+
239
+ | Hook / field | Purpose | Typical use |
240
+ | ------------ | ------------------------------------------ | -------------------------------------------- |
241
+ | `tool` | Register custom tools | Runtime-side integrations, project utilities |
242
+ | `auth` | Add provider auth methods and auth loaders | Custom providers, OAuth, API key flows |
243
+ | `config` | Observe loaded config | Initialize plugin state from current config |
244
+ | `event` | Observe bus events | Logging, metrics, passive integrations |
245
+
246
+ ### Chat and session hooks
247
+
248
+ | Hook | Mutates output? | Notes |
249
+ | -------------------------------------- | --------------- | ---------------------------------------------------------------- |
250
+ | `chat.message` | Yes | Inspect or rewrite incoming user message parts before processing |
251
+ | `chat.params` | Yes | Adjust model parameters and provider options before LLM calls |
252
+ | `session.turn.after` | No | Observe the completed turn, including error state if present |
253
+ | `experimental.chat.messages.transform` | Yes | Rewrite the message history sent to the model |
254
+ | `experimental.chat.system.transform` | Yes | Rewrite the assembled system prompt |
255
+ | `experimental.session.compacting` | Yes | Add compaction context or replace the compaction prompt |
256
+ | `experimental.text.complete` | Yes | Rewrite a text completion result |
257
+
258
+ ### Permission and tool hooks
259
+
260
+ | Hook | Mutates output? | Notes |
261
+ | --------------------- | --------------- | ------------------------------------------------------- |
262
+ | `permission.ask` | Yes | Override `ask` / `deny` / `allow` decisions |
263
+ | `tool.execute.before` | Yes | Rewrite tool args before execution |
264
+ | `tool.execute.after` | Yes | Rewrite tool output, title, or metadata after execution |
265
+
266
+ ### Cortex and agenda hooks
267
+
268
+ | Hook | Mutates output? | Notes |
269
+ | ------------------- | --------------- | --------------------------------------------------------- |
270
+ | `cortex.task.after` | No | Observe completed Cortex task execution |
271
+ | `agenda.run.before` | Yes | Skip a run or replace the `AgendaItem` used for execution |
272
+ | `agenda.run.after` | No | Observe a successful agenda run |
273
+ | `agenda.run.error` | No | Observe a failed agenda run |
274
+
275
+ ### Note hooks
276
+
277
+ | Hook | Mutates output? | Notes |
278
+ | -------------------- | --------------- | ------------------------------------------------------------------- |
279
+ | `note.create.before` | Yes | Rewrite note creation input before storage |
280
+ | `note.create.after` | Usually no | Observe the created note after persistence |
281
+ | `note.update.before` | Yes | Rewrite the patch before update logic runs |
282
+ | `note.update.after` | Usually no | Observe the updated note after persistence |
283
+ | `note.search.before` | Yes | Rewrite search pattern, scope, date filters, tags, or pinned filter |
284
+ | `note.search.after` | Yes | Filter or reorder returned notes |
285
+
286
+ ### Engram hooks
287
+
288
+ | Hook | Mutates output? | Notes |
289
+ | -------------------------------- | --------------- | -------------------------------------------------------------------------- |
290
+ | `engram.memory.search.before` | Yes | Rewrite query, vector, top-k, categories, recall modes, or rerank behavior |
291
+ | `engram.memory.search.after` | Yes | Filter or rerank returned engram memory results |
292
+ | `engram.experience.encode.after` | No | Observe whether an experience was encoded, skipped, or deduplicated |
293
+
294
+ ### Experimental hooks
295
+
296
+ Hooks under `experimental.*` are available, but they are less stable than the core hook surface. Use them when you need them, but expect them to evolve more quickly than the main plugin API.
297
+
298
+ ## Short example
299
+
300
+ This example combines a custom tool with two hooks:
301
+
302
+ - `session.turn.after` for observation
303
+ - `note.search.after` for result shaping
304
+
305
+ ```ts
306
+ import type { Plugin } from "@ericsanchezok/synergy-plugin"
307
+ import { tool } from "@ericsanchezok/synergy-plugin/tool"
308
+
309
+ const ExamplePlugin: Plugin = async (ctx) => {
310
+ return {
311
+ tool: {
312
+ scope_info: tool({
313
+ description: "Show the current Scope and worktree",
314
+ args: {},
315
+ async execute() {
316
+ return [
317
+ `scope: ${ctx.scope.id}`,
318
+ `scopeType: ${ctx.scope.type}`,
319
+ `directory: ${ctx.directory}`,
320
+ `worktree: ${ctx.worktree}`,
321
+ ].join("\n")
322
+ },
323
+ }),
324
+ },
325
+
326
+ async "session.turn.after"(input) {
327
+ if (!input.error) {
328
+ console.log("assistant replied in session", input.sessionID)
329
+ }
330
+ },
331
+
332
+ async "note.search.after"(_input, output) {
333
+ output.notes = output.notes.filter((note) => !note.tags.includes("private"))
334
+ },
335
+ }
336
+ }
337
+
338
+ export default ExamplePlugin
339
+ ```
340
+
341
+ ## Best practices
342
+
343
+ ### Keep hooks fast
344
+
345
+ Most hooks run inline with real user work. If a hook blocks, the user feels it.
346
+ Do the minimum in the hook path. Push slow work to another system when possible.
347
+
348
+ ### Be conservative with mutation
349
+
350
+ If you mutate an output object, make the change narrow and obvious.
351
+ A good plugin should be easy to reason about six months later.
352
+
353
+ ### Treat Scope as real context
354
+
355
+ Plugins run inside a resolved Scope. Prefer `ctx.scope`, `ctx.directory`, and `ctx.worktree` over guessing from process state.
356
+
357
+ ### Use `ctx.$` carefully
358
+
359
+ `ctx.$` is useful for local runtime-side commands, but it also makes it easy to couple a plugin to one machine or one repository layout. Shell out when that is the simplest correct answer, not by default.
360
+
361
+ ### Prefer observation hooks for telemetry and indexing
362
+
363
+ If you only need to watch what happened, use an after-hook or `event` hook instead of intercepting earlier stages.
364
+
365
+ ### Be explicit about note and engram behavior
366
+
367
+ Hooks like `note.search.after` and `engram.memory.search.after` can quietly change what users see. Document those policies in the plugin itself and keep them predictable.
368
+
369
+ ### Expect experimental hooks to move
370
+
371
+ If your plugin depends on an `experimental.*` hook, isolate that logic so future API changes are easy to update.
372
+
373
+ ### Export one plugin by default
374
+
375
+ A single default export keeps module behavior obvious. Multiple exported plugin functions are supported, but they are loaded independently.
376
+
377
+ ## Auth plugins
378
+
379
+ A plugin can also provide `auth` for a custom provider. That is how plugins participate in provider-specific API key or OAuth flows and, when needed, compute provider options through an auth loader.
380
+
381
+ If you only need custom tools or hooks, you can ignore `auth` entirely.
382
+
383
+ ## When to use a plugin vs other extension points
384
+
385
+ Use a plugin when you need runtime behavior:
386
+
387
+ - add a tool
388
+ - intercept a session, agenda, note, or engram lifecycle step
389
+ - integrate provider auth
390
+ - react to runtime events
391
+
392
+ Use other extension points when the problem is simpler:
393
+
394
+ - use `.synergy/command/` for reusable prompt commands
395
+ - use `.synergy/skill/` for domain instructions and workflows
396
+ - use config in `synergy.jsonc` for static runtime configuration
397
+
398
+ ## Development notes
399
+
400
+ - local plugin files can live under `.synergy/plugin/` or `.synergy/plugins/`
401
+ - global plugin files can live under `~/.synergy/config/plugin/` or `~/.synergy/config/plugins/`
402
+ - explicit package plugins can be listed in `synergy.jsonc` under `plugin`
403
+ - plugin reload also refreshes plugin-provided tools
404
+
405
+ That is the core model: a plugin is an async server-side module that receives runtime context, returns hooks and tools, and participates directly in Scope-aware execution.
package/dist/example.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  import { Plugin } from "./index";
2
2
  export declare const ExamplePlugin: Plugin;
3
+ export default ExamplePlugin;
package/dist/example.js CHANGED
@@ -1,16 +1,88 @@
1
1
  import { tool } from "./tool";
2
+ const MAX_NOTE_TAGS = 8;
3
+ const AUTO_TAG = "reference-plugin";
4
+ function normalizeTag(value) {
5
+ return value
6
+ .trim()
7
+ .toLowerCase()
8
+ .replace(/\s+/g, "-")
9
+ .replace(/[^a-z0-9:_-]/g, "");
10
+ }
11
+ function mergeTags(tags) {
12
+ const unique = new Set();
13
+ for (const tag of tags ?? []) {
14
+ const normalized = normalizeTag(tag);
15
+ if (normalized)
16
+ unique.add(normalized);
17
+ if (unique.size >= MAX_NOTE_TAGS)
18
+ break;
19
+ }
20
+ unique.add(AUTO_TAG);
21
+ return Array.from(unique).slice(0, MAX_NOTE_TAGS);
22
+ }
23
+ function preview(text, limit = 160) {
24
+ const singleLine = text.replace(/\s+/g, " ").trim();
25
+ if (singleLine.length <= limit)
26
+ return singleLine;
27
+ return `${singleLine.slice(0, limit - 1)}…`;
28
+ }
2
29
  export const ExamplePlugin = async (ctx) => {
30
+ const logger = ctx.$.env({ SYNERGY_LOG_LEVEL: "warn" });
3
31
  return {
4
32
  tool: {
5
- mytool: tool({
6
- description: "This is a custom tool",
33
+ workspace_summary: tool({
34
+ description: "Summarize the active Synergy plugin runtime context",
7
35
  args: {
8
- foo: tool.schema.string().describe("foo"),
36
+ includeDirectorySample: tool.schema.boolean().optional().describe("Include a short directory listing sample"),
9
37
  },
10
- async execute(args) {
11
- return `Hello ${args.foo}!`;
38
+ async execute(args, context) {
39
+ const lines = [
40
+ `scope: ${ctx.scope.type}:${ctx.scope.id}`,
41
+ `directory: ${ctx.directory}`,
42
+ `worktree: ${ctx.worktree}`,
43
+ `server: ${ctx.serverUrl.toString()}`,
44
+ `session: ${context.sessionID}`,
45
+ `agent: ${context.agent}`,
46
+ ];
47
+ if (args.includeDirectorySample) {
48
+ const result = await logger `ls`;
49
+ const sample = result
50
+ .text()
51
+ .split("\n")
52
+ .map((line) => line.trim())
53
+ .filter(Boolean)
54
+ .slice(0, 10);
55
+ if (sample.length > 0) {
56
+ lines.push(`entries: ${sample.join(", ")}`);
57
+ }
58
+ }
59
+ return lines.join("\n");
12
60
  },
13
61
  }),
14
62
  },
63
+ async "session.turn.after"(input) {
64
+ const status = input.error ? "error" : (input.finish ?? "unknown");
65
+ const summary = {
66
+ sessionID: input.sessionID,
67
+ assistantMessageID: input.assistantMessageID,
68
+ agent: input.assistant.agent,
69
+ finish: status,
70
+ };
71
+ console.info("[example-plugin] session.turn.after", summary);
72
+ },
73
+ async "note.create.before"(_, output) {
74
+ output.note.title = output.note.title.trim() || "Untitled note";
75
+ output.note.tags = mergeTags(output.note.tags);
76
+ if (!output.note.contentText?.trim()) {
77
+ output.note.contentText = preview(output.note.title);
78
+ }
79
+ },
80
+ async "engram.memory.search.after"(input, output) {
81
+ output.results = output.results
82
+ .slice()
83
+ .sort((left, right) => right.similarity - left.similarity)
84
+ .slice(0, input.topK);
85
+ },
15
86
  };
16
87
  };
88
+ export default ExamplePlugin;
@@ -0,0 +1,9 @@
1
+ export type HookCategory = "core" | "chat" | "permission" | "tool" | "session" | "cortex" | "agenda" | "note" | "engram" | "experimental";
2
+ export interface HookDescriptor {
3
+ name: string;
4
+ category: HookCategory;
5
+ mutatesOutput: boolean;
6
+ summary: string;
7
+ }
8
+ export declare const HOOKS: HookDescriptor[];
9
+ export declare const HOOK_CATEGORIES: HookCategory[];
package/dist/hooks.js ADDED
@@ -0,0 +1,176 @@
1
+ export const HOOKS = [
2
+ {
3
+ name: "tool",
4
+ category: "core",
5
+ mutatesOutput: false,
6
+ summary: "Register custom runtime-side tools",
7
+ },
8
+ {
9
+ name: "auth",
10
+ category: "core",
11
+ mutatesOutput: false,
12
+ summary: "Add provider auth methods and auth loaders",
13
+ },
14
+ {
15
+ name: "config",
16
+ category: "core",
17
+ mutatesOutput: false,
18
+ summary: "Observe the loaded runtime config",
19
+ },
20
+ {
21
+ name: "event",
22
+ category: "core",
23
+ mutatesOutput: false,
24
+ summary: "Observe runtime bus events",
25
+ },
26
+ {
27
+ name: "chat.message",
28
+ category: "chat",
29
+ mutatesOutput: true,
30
+ summary: "Rewrite incoming user messages before processing",
31
+ },
32
+ {
33
+ name: "chat.params",
34
+ category: "chat",
35
+ mutatesOutput: true,
36
+ summary: "Adjust model parameters before LLM calls",
37
+ },
38
+ {
39
+ name: "permission.ask",
40
+ category: "permission",
41
+ mutatesOutput: true,
42
+ summary: "Override ask, deny, or allow decisions",
43
+ },
44
+ {
45
+ name: "tool.execute.before",
46
+ category: "tool",
47
+ mutatesOutput: true,
48
+ summary: "Rewrite tool args before execution",
49
+ },
50
+ {
51
+ name: "tool.execute.after",
52
+ category: "tool",
53
+ mutatesOutput: true,
54
+ summary: "Rewrite tool output, title, or metadata",
55
+ },
56
+ {
57
+ name: "session.turn.after",
58
+ category: "session",
59
+ mutatesOutput: false,
60
+ summary: "Observe completed assistant turns",
61
+ },
62
+ {
63
+ name: "cortex.task.after",
64
+ category: "cortex",
65
+ mutatesOutput: false,
66
+ summary: "Observe completed Cortex tasks",
67
+ },
68
+ {
69
+ name: "agenda.run.before",
70
+ category: "agenda",
71
+ mutatesOutput: true,
72
+ summary: "Skip or rewrite an agenda run before execution",
73
+ },
74
+ {
75
+ name: "agenda.run.after",
76
+ category: "agenda",
77
+ mutatesOutput: false,
78
+ summary: "Observe successful agenda runs",
79
+ },
80
+ {
81
+ name: "agenda.run.error",
82
+ category: "agenda",
83
+ mutatesOutput: false,
84
+ summary: "Observe failed agenda runs",
85
+ },
86
+ {
87
+ name: "note.create.before",
88
+ category: "note",
89
+ mutatesOutput: true,
90
+ summary: "Rewrite note creation input before persistence",
91
+ },
92
+ {
93
+ name: "note.create.after",
94
+ category: "note",
95
+ mutatesOutput: false,
96
+ summary: "Observe created notes after persistence",
97
+ },
98
+ {
99
+ name: "note.update.before",
100
+ category: "note",
101
+ mutatesOutput: true,
102
+ summary: "Rewrite note patches before update logic runs",
103
+ },
104
+ {
105
+ name: "note.update.after",
106
+ category: "note",
107
+ mutatesOutput: false,
108
+ summary: "Observe updated notes after persistence",
109
+ },
110
+ {
111
+ name: "note.search.before",
112
+ category: "note",
113
+ mutatesOutput: true,
114
+ summary: "Rewrite note search filters before execution",
115
+ },
116
+ {
117
+ name: "note.search.after",
118
+ category: "note",
119
+ mutatesOutput: true,
120
+ summary: "Filter or reorder note search results",
121
+ },
122
+ {
123
+ name: "engram.memory.search.before",
124
+ category: "engram",
125
+ mutatesOutput: true,
126
+ summary: "Rewrite engram memory search query and options",
127
+ },
128
+ {
129
+ name: "engram.memory.search.after",
130
+ category: "engram",
131
+ mutatesOutput: true,
132
+ summary: "Filter or reorder engram memory results",
133
+ },
134
+ {
135
+ name: "engram.experience.encode.after",
136
+ category: "engram",
137
+ mutatesOutput: false,
138
+ summary: "Observe experience encoding outcomes",
139
+ },
140
+ {
141
+ name: "experimental.chat.messages.transform",
142
+ category: "experimental",
143
+ mutatesOutput: true,
144
+ summary: "Rewrite the chat message history sent to the model",
145
+ },
146
+ {
147
+ name: "experimental.chat.system.transform",
148
+ category: "experimental",
149
+ mutatesOutput: true,
150
+ summary: "Rewrite the assembled system prompt",
151
+ },
152
+ {
153
+ name: "experimental.session.compacting",
154
+ category: "experimental",
155
+ mutatesOutput: true,
156
+ summary: "Customize session compaction context or prompt",
157
+ },
158
+ {
159
+ name: "experimental.text.complete",
160
+ category: "experimental",
161
+ mutatesOutput: true,
162
+ summary: "Rewrite text completion output before finalization",
163
+ },
164
+ ];
165
+ export const HOOK_CATEGORIES = [
166
+ "core",
167
+ "chat",
168
+ "permission",
169
+ "tool",
170
+ "session",
171
+ "cortex",
172
+ "agenda",
173
+ "note",
174
+ "engram",
175
+ "experimental",
176
+ ];
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Event, createSynergyClient, Model, Provider, PermissionRequest, UserMessage, Message, Part, Auth, Config } from "@ericsanchezok/synergy-sdk";
1
+ import type { AgendaItem, AgendaRunLog, CortexTask, Event, createSynergyClient, MemoryCategory, MemoryRecallMode, MemorySearchResult, Model, NoteCreateInput, NoteInfo, NotePatchInput, Provider, PermissionRequest, UserMessage, Message, Part, Auth, Config } from "@ericsanchezok/synergy-sdk";
2
2
  import type { BunShell } from "./shell";
3
3
  import { type ToolDefinition } from "./tool";
4
4
  export * from "./tool";
@@ -208,4 +208,114 @@ export interface Hooks {
208
208
  }, output: {
209
209
  text: string;
210
210
  }) => Promise<void>;
211
+ "session.turn.after"?: (input: {
212
+ sessionID: string;
213
+ userMessageID: string;
214
+ assistantMessageID: string;
215
+ assistant: Message;
216
+ finish?: string;
217
+ error?: unknown;
218
+ }, output: {}) => Promise<void>;
219
+ "cortex.task.after"?: (input: {
220
+ task: CortexTask;
221
+ }, output: {}) => Promise<void>;
222
+ "agenda.run.before"?: (input: {
223
+ signal: {
224
+ type: string;
225
+ source: string;
226
+ payload?: Record<string, unknown>;
227
+ timestamp: number;
228
+ };
229
+ item: AgendaItem;
230
+ scopeID: string;
231
+ }, output: {
232
+ skip: boolean;
233
+ item: AgendaItem;
234
+ }) => Promise<void>;
235
+ "agenda.run.after"?: (input: {
236
+ signal: {
237
+ type: string;
238
+ source: string;
239
+ payload?: Record<string, unknown>;
240
+ timestamp: number;
241
+ };
242
+ item: AgendaItem;
243
+ run: AgendaRunLog;
244
+ scopeID: string;
245
+ }, output: {}) => Promise<void>;
246
+ "agenda.run.error"?: (input: {
247
+ signal: {
248
+ type: string;
249
+ source: string;
250
+ payload?: Record<string, unknown>;
251
+ timestamp: number;
252
+ };
253
+ item: AgendaItem;
254
+ scopeID: string;
255
+ error: string;
256
+ sessionID?: string;
257
+ }, output: {}) => Promise<void>;
258
+ "note.create.before"?: (input: {
259
+ scopeID: string;
260
+ }, output: {
261
+ note: NoteCreateInput;
262
+ }) => Promise<void>;
263
+ "note.create.after"?: (input: {
264
+ scopeID: string;
265
+ noteID: string;
266
+ }, output: {
267
+ note: NoteInfo;
268
+ }) => Promise<void>;
269
+ "note.update.before"?: (input: {
270
+ scopeID: string;
271
+ noteID: string;
272
+ current: NoteInfo;
273
+ }, output: {
274
+ patch: NotePatchInput;
275
+ }) => Promise<void>;
276
+ "note.update.after"?: (input: {
277
+ scopeID: string;
278
+ noteID: string;
279
+ }, output: {
280
+ note: NoteInfo;
281
+ }) => Promise<void>;
282
+ "note.search.before"?: (input: {
283
+ scopeID: string;
284
+ }, output: {
285
+ pattern: string;
286
+ scope: "current" | "global" | "all";
287
+ since?: string;
288
+ before?: string;
289
+ tags?: string[];
290
+ pinned?: boolean;
291
+ }) => Promise<void>;
292
+ "note.search.after"?: (input: {
293
+ scopeID: string;
294
+ pattern: string;
295
+ }, output: {
296
+ notes: NoteInfo[];
297
+ }) => Promise<void>;
298
+ "engram.memory.search.before"?: (input: {}, output: {
299
+ query: string;
300
+ vector?: number[];
301
+ topK?: number;
302
+ categories?: MemoryCategory[];
303
+ recallModes?: MemoryRecallMode[];
304
+ rerank?: boolean;
305
+ }) => Promise<void>;
306
+ "engram.memory.search.after"?: (input: {
307
+ query: string;
308
+ topK: number;
309
+ }, output: {
310
+ results: MemorySearchResult[];
311
+ }) => Promise<void>;
312
+ "engram.experience.encode.after"?: (input: {
313
+ sessionID: string;
314
+ userMessageID: string;
315
+ }, output: {
316
+ encoded: boolean;
317
+ skipped: boolean;
318
+ duplicateOf?: string;
319
+ experienceID?: string;
320
+ }) => Promise<void>;
211
321
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@ericsanchezok/synergy-plugin",
4
- "version": "1.1.28",
4
+ "version": "1.2.1",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -24,6 +24,10 @@
24
24
  "./tool": {
25
25
  "import": "./dist/tool.js",
26
26
  "types": "./dist/tool.d.ts"
27
+ },
28
+ "./hooks": {
29
+ "import": "./dist/hooks.js",
30
+ "types": "./dist/hooks.d.ts"
27
31
  }
28
32
  },
29
33
  "files": [