@grackle-ai/plugin-sdk 0.95.0

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,92 @@
1
+ # @grackle-ai/plugin-sdk
2
+
3
+ Plugin contract and loader for Grackle. Defines the `GracklePlugin` interface that all plugins implement, the `PluginContext` they receive, and the `loadPlugins()` function that topologically sorts, initializes, and collects contributions from plugins.
4
+
5
+ ## GracklePlugin
6
+
7
+ A plugin contributes server capabilities through five extension points:
8
+
9
+ ```typescript
10
+ import type { GracklePlugin } from "@grackle-ai/plugin-sdk";
11
+
12
+ const myPlugin: GracklePlugin = {
13
+ name: "my-plugin",
14
+ dependencies: ["core"], // loaded after "core"
15
+
16
+ grpcHandlers: (ctx) => [
17
+ { service: MyProtoService, handlers: { listItems, createItem } },
18
+ ],
19
+
20
+ reconciliationPhases: (ctx) => [
21
+ { name: "my-phase", execute: async () => { /* runs every tick */ } },
22
+ ],
23
+
24
+ mcpTools: (ctx) => [
25
+ { name: "my_tool", group: "my", description: "...", /* ... */ },
26
+ ],
27
+
28
+ eventSubscribers: (ctx) => {
29
+ const unsub = ctx.subscribe((event) => {
30
+ if (event.type === "task.created") { /* react */ }
31
+ });
32
+ return [{ dispose: unsub }];
33
+ },
34
+
35
+ initialize: async (ctx) => {
36
+ ctx.logger.info("my-plugin initialized");
37
+ },
38
+
39
+ shutdown: async () => {
40
+ // clean up external connections
41
+ },
42
+ };
43
+ ```
44
+
45
+ ## PluginContext
46
+
47
+ Plugins receive a thin runtime context. Database stores are accessed via direct package imports (e.g., `import { taskStore } from "@grackle-ai/database"`), not through the context.
48
+
49
+ ```typescript
50
+ interface PluginContext {
51
+ subscribe: (cb: (event: GrackleEvent) => void) => () => void;
52
+ emit: (type: GrackleEventType, payload: Record<string, unknown>) => GrackleEvent;
53
+ logger: Logger; // pino structured logger
54
+ config: ServerConfig; // ports, host, grackleHome, apiKey, etc.
55
+ }
56
+ ```
57
+
58
+ ## loadPlugins()
59
+
60
+ Loads an array of plugins in dependency order:
61
+
62
+ 1. Validates no duplicate names or missing dependencies
63
+ 2. Topological sort (Kahn's algorithm) on declared `dependencies`
64
+ 3. Calls `initialize()` in dependency-first order
65
+ 4. Collects all contributions (gRPC handlers, phases, tools, subscribers)
66
+ 5. Returns a `LoadedPlugins` object with aggregated contributions and a `shutdown()` function
67
+
68
+ ```typescript
69
+ import { loadPlugins } from "@grackle-ai/plugin-sdk";
70
+
71
+ const result = await loadPlugins([corePlugin, orchestrationPlugin], ctx);
72
+
73
+ // Use contributions
74
+ for (const reg of result.serviceRegistrations) {
75
+ collector.addHandlers(reg.service, reg.handlers);
76
+ }
77
+ const manager = new ReconciliationManager(result.reconciliationPhases);
78
+
79
+ // On server shutdown
80
+ await result.shutdown(); // disposes subscribers, then calls plugin.shutdown() in reverse order
81
+ ```
82
+
83
+ ## Extension Points
84
+
85
+ | Method | What it contributes | Consumed by |
86
+ |---|---|---|
87
+ | `grpcHandlers` | `ServiceRegistration[]` — proto service + handler pairs | `ServiceCollector` |
88
+ | `reconciliationPhases` | `ReconciliationPhase[]` — named async phases | `ReconciliationManager` |
89
+ | `mcpTools` | `PluginToolDefinition[]` — MCP tool definitions | `ToolRegistry` |
90
+ | `eventSubscribers` | `Disposable[]` — event bus subscriptions | Server shutdown |
91
+ | `initialize` | Async startup hook (e.g., connect to Neo4j) | Plugin loader |
92
+ | `shutdown` | Async teardown hook | Plugin loader (reverse order) |
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Plugin context types — the runtime environment provided to plugins.
3
+ *
4
+ * Stores (taskStore, sessionStore, etc.) are accessed via direct package
5
+ * imports from `@grackle-ai/database`, not through the context. The context
6
+ * provides only runtime-dynamic infrastructure: event bus, logger, and config.
7
+ *
8
+ * @module
9
+ */
10
+ import type { Logger } from "pino";
11
+ /** A resource that can be disposed to clean up subscriptions and state. */
12
+ export interface Disposable {
13
+ /** Release all resources (unsubscribe callbacks, clear dedup maps, etc.). */
14
+ dispose(): void;
15
+ }
16
+ /** Resolved server configuration available to plugins. */
17
+ export interface ServerConfig {
18
+ /** gRPC server port. */
19
+ grpcPort: number;
20
+ /** Web UI + WebSocket port. */
21
+ webPort: number;
22
+ /** MCP server port. */
23
+ mcpPort: number;
24
+ /** PowerLine server port. */
25
+ powerlinePort: number;
26
+ /** Bind address for all servers. */
27
+ host: string;
28
+ /** Grackle home directory (databases, API key, logs). */
29
+ grackleHome: string;
30
+ /** Loaded API key for authenticated requests. */
31
+ apiKey: string;
32
+ /** Override agent working directory (GRACKLE_WORKING_DIRECTORY). */
33
+ workingDirectory?: string;
34
+ /** Worktree base path (GRACKLE_WORKTREE_BASE). */
35
+ worktreeBase?: string;
36
+ /** Docker host for host mapping (GRACKLE_DOCKER_HOST). */
37
+ dockerHost?: string;
38
+ }
39
+ /** Event types emitted by the domain event bus. */
40
+ export type GrackleEventType = "task.created" | "task.updated" | "task.started" | "task.completed" | "task.deleted" | "task.reparented" | "workspace.created" | "workspace.archived" | "workspace.updated" | "persona.created" | "persona.updated" | "persona.deleted" | "finding.posted" | "environment.added" | "environment.removed" | "environment.changed" | "environment.provision_progress" | "token.changed" | "credential.providers_changed" | "setting.changed" | "schedule.created" | "schedule.updated" | "schedule.deleted" | "schedule.fired" | "notification.escalated";
41
+ /** A domain event from the event bus. */
42
+ export interface GrackleEvent {
43
+ /** ULID — chronologically sortable unique identifier. */
44
+ id: string;
45
+ /** Dot-notation event type (e.g. "task.created"). */
46
+ type: GrackleEventType;
47
+ /** ISO 8601 timestamp. */
48
+ timestamp: string;
49
+ /** Domain-specific payload. */
50
+ payload: Record<string, unknown>;
51
+ }
52
+ /**
53
+ * Runtime context provided to plugins.
54
+ *
55
+ * Stores (taskStore, sessionStore, etc.) are accessed via direct package
56
+ * imports — not injected through the context. This keeps the contract surface
57
+ * minimal and avoids coupling plugins to a fat DI interface.
58
+ */
59
+ export interface PluginContext {
60
+ /** Subscribe to all domain events. Returns an unsubscribe function. */
61
+ subscribe: (cb: (event: GrackleEvent) => void) => () => void;
62
+ /** Emit a domain event. */
63
+ emit: (type: GrackleEventType, payload: Record<string, unknown>) => GrackleEvent;
64
+ /** Structured logger (pino). */
65
+ logger: Logger;
66
+ /** Resolved server configuration. */
67
+ config: ServerConfig;
68
+ }
69
+ /** Factory function that creates a subscriber and returns a Disposable for cleanup. */
70
+ export type SubscriberFactory = (ctx: PluginContext) => Disposable;
71
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,2EAA2E;AAC3E,MAAM,WAAW,UAAU;IACzB,6EAA6E;IAC7E,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,0DAA0D;AAC1D,MAAM,WAAW,YAAY;IAC3B,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,mDAAmD;AACnD,MAAM,MAAM,gBAAgB,GACxB,cAAc,GACd,cAAc,GACd,cAAc,GACd,gBAAgB,GAChB,cAAc,GACd,iBAAiB,GACjB,mBAAmB,GACnB,oBAAoB,GACpB,mBAAmB,GACnB,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,GACjB,gBAAgB,GAChB,mBAAmB,GACnB,qBAAqB,GACrB,qBAAqB,GACrB,gCAAgC,GAChC,eAAe,GACf,8BAA8B,GAC9B,iBAAiB,GACjB,kBAAkB,GAClB,kBAAkB,GAClB,kBAAkB,GAClB,gBAAgB,GAChB,wBAAwB,CAAC;AAE7B,yCAAyC;AACzC,MAAM,WAAW,YAAY;IAC3B,yDAAyD;IACzD,EAAE,EAAE,MAAM,CAAC;IACX,qDAAqD;IACrD,IAAI,EAAE,gBAAgB,CAAC;IACvB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAC7D,2BAA2B;IAC3B,IAAI,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,YAAY,CAAC;IACjF,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,uFAAuF;AACvF,MAAM,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,aAAa,KAAK,UAAU,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Plugin context types — the runtime environment provided to plugins.
3
+ *
4
+ * Stores (taskStore, sessionStore, etc.) are accessed via direct package
5
+ * imports from `@grackle-ai/database`, not through the context. The context
6
+ * provides only runtime-dynamic infrastructure: event bus, logger, and config.
7
+ *
8
+ * @module
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,5 @@
1
+ export type { Disposable, ServerConfig, GrackleEventType, GrackleEvent, PluginContext, SubscriberFactory, } from "./context.js";
2
+ export type { GracklePlugin, ServiceRegistration, ReconciliationPhase, PluginToolDefinition, } from "./plugin.js";
3
+ export type { LoadedPlugins } from "./loader.js";
4
+ export { loadPlugins } from "./loader.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,iBAAiB,GAClB,MAAM,cAAc,CAAC;AAGtB,YAAY,EACV,aAAa,EACb,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAGrB,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { loadPlugins } from "./loader.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Plugin loader — topological sort, initialization, and contribution collection.
3
+ *
4
+ * @module
5
+ */
6
+ import type { GracklePlugin, ReconciliationPhase, ServiceRegistration, PluginToolDefinition } from "./plugin.js";
7
+ import type { PluginContext, Disposable } from "./context.js";
8
+ /** Aggregated contributions from all loaded plugins. */
9
+ export interface LoadedPlugins {
10
+ /** All gRPC service registrations, in plugin load order. */
11
+ serviceRegistrations: ServiceRegistration[];
12
+ /** All reconciliation phases, in plugin load order. */
13
+ reconciliationPhases: ReconciliationPhase[];
14
+ /** All MCP tool definitions, in plugin load order. */
15
+ mcpTools: PluginToolDefinition[];
16
+ /** All subscriber disposables (for shutdown). */
17
+ subscriberDisposables: Disposable[];
18
+ /** Dispose all subscribers, then shutdown plugins in reverse initialization order. */
19
+ shutdown: () => Promise<void>;
20
+ }
21
+ /**
22
+ * Load, sort, initialize, and collect contributions from plugins.
23
+ *
24
+ * 1. Validate: no duplicate names, no missing dependencies
25
+ * 2. Topological sort on declared dependencies (error on cycles)
26
+ * 3. Call `initialize()` in dependency order
27
+ * 4. Collect grpcHandlers, reconciliationPhases, mcpTools, eventSubscribers
28
+ * 5. Return aggregated contributions + a shutdown function
29
+ *
30
+ * @param plugins - Unordered array of plugins to load.
31
+ * @param ctx - Runtime context provided to each plugin.
32
+ * @returns Aggregated contributions and a shutdown function.
33
+ */
34
+ export declare function loadPlugins(plugins: GracklePlugin[], ctx: PluginContext): Promise<LoadedPlugins>;
35
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE9D,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,oBAAoB,EAAE,mBAAmB,EAAE,CAAC;IAC5C,uDAAuD;IACvD,oBAAoB,EAAE,mBAAmB,EAAE,CAAC;IAC5C,sDAAsD;IACtD,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IACjC,iDAAiD;IACjD,qBAAqB,EAAE,UAAU,EAAE,CAAC;IACpC,sFAAsF;IACtF,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,aAAa,EAAE,EACxB,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,aAAa,CAAC,CA6HxB"}
package/dist/loader.js ADDED
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Plugin loader — topological sort, initialization, and contribution collection.
3
+ *
4
+ * @module
5
+ */
6
+ /**
7
+ * Load, sort, initialize, and collect contributions from plugins.
8
+ *
9
+ * 1. Validate: no duplicate names, no missing dependencies
10
+ * 2. Topological sort on declared dependencies (error on cycles)
11
+ * 3. Call `initialize()` in dependency order
12
+ * 4. Collect grpcHandlers, reconciliationPhases, mcpTools, eventSubscribers
13
+ * 5. Return aggregated contributions + a shutdown function
14
+ *
15
+ * @param plugins - Unordered array of plugins to load.
16
+ * @param ctx - Runtime context provided to each plugin.
17
+ * @returns Aggregated contributions and a shutdown function.
18
+ */
19
+ export async function loadPlugins(plugins, ctx) {
20
+ // 1. Validate
21
+ const byName = new Map();
22
+ for (const plugin of plugins) {
23
+ if (byName.has(plugin.name)) {
24
+ throw new Error(`Duplicate plugin name: "${plugin.name}"`);
25
+ }
26
+ byName.set(plugin.name, plugin);
27
+ }
28
+ for (const plugin of plugins) {
29
+ for (const dep of plugin.dependencies ?? []) {
30
+ if (!byName.has(dep)) {
31
+ throw new Error(`Plugin "${plugin.name}" depends on "${dep}" which was not provided`);
32
+ }
33
+ }
34
+ }
35
+ // 2. Topological sort (Kahn's algorithm)
36
+ const sorted = topologicalSort(plugins);
37
+ // 3. Initialize in dependency order (clean up on failure)
38
+ const initialized = [];
39
+ try {
40
+ for (const plugin of sorted) {
41
+ if (plugin.initialize) {
42
+ await plugin.initialize(ctx);
43
+ }
44
+ initialized.push(plugin);
45
+ }
46
+ }
47
+ catch (err) {
48
+ // Shutdown already-initialized plugins in reverse order before re-throwing
49
+ for (let i = initialized.length - 1; i >= 0; i--) {
50
+ const plugin = initialized[i];
51
+ if (plugin.shutdown) {
52
+ try {
53
+ await plugin.shutdown();
54
+ }
55
+ catch (shutdownErr) {
56
+ ctx.logger.error({ err: shutdownErr, plugin: plugin.name }, "Plugin '%s' shutdown failed during initialization rollback", plugin.name);
57
+ }
58
+ }
59
+ }
60
+ throw err;
61
+ }
62
+ // 4. Collect contributions (roll back on failure)
63
+ const serviceRegistrations = [];
64
+ const reconciliationPhases = [];
65
+ const mcpTools = [];
66
+ const subscriberDisposables = [];
67
+ try {
68
+ for (const plugin of sorted) {
69
+ if (plugin.grpcHandlers) {
70
+ serviceRegistrations.push(...plugin.grpcHandlers(ctx));
71
+ }
72
+ if (plugin.reconciliationPhases) {
73
+ reconciliationPhases.push(...plugin.reconciliationPhases(ctx));
74
+ }
75
+ if (plugin.mcpTools) {
76
+ mcpTools.push(...plugin.mcpTools(ctx));
77
+ }
78
+ if (plugin.eventSubscribers) {
79
+ subscriberDisposables.push(...plugin.eventSubscribers(ctx));
80
+ }
81
+ }
82
+ }
83
+ catch (err) {
84
+ // Dispose any subscribers already collected, then shutdown initialized plugins
85
+ for (const disposable of subscriberDisposables) {
86
+ try {
87
+ disposable.dispose();
88
+ }
89
+ catch { /* best-effort */ }
90
+ }
91
+ for (let i = initialized.length - 1; i >= 0; i--) {
92
+ const plugin = initialized[i];
93
+ if (plugin.shutdown) {
94
+ try {
95
+ await plugin.shutdown();
96
+ }
97
+ catch (shutdownErr) {
98
+ ctx.logger.error({ err: shutdownErr, plugin: plugin.name }, "Plugin '%s' shutdown failed during contribution rollback", plugin.name);
99
+ }
100
+ }
101
+ }
102
+ throw err;
103
+ }
104
+ // 5. Build shutdown function (reverse order, catch errors)
105
+ const shutdown = async () => {
106
+ // Dispose subscribers first
107
+ for (const disposable of subscriberDisposables) {
108
+ try {
109
+ disposable.dispose();
110
+ }
111
+ catch (err) {
112
+ ctx.logger.warn({ err }, "Subscriber dispose failed during shutdown");
113
+ }
114
+ }
115
+ // Shutdown plugins in reverse initialization order
116
+ for (let i = initialized.length - 1; i >= 0; i--) {
117
+ const plugin = initialized[i];
118
+ if (plugin.shutdown) {
119
+ try {
120
+ await plugin.shutdown();
121
+ }
122
+ catch (err) {
123
+ ctx.logger.error({ err, plugin: plugin.name }, "Plugin '%s' shutdown failed", plugin.name);
124
+ }
125
+ }
126
+ }
127
+ };
128
+ return {
129
+ serviceRegistrations,
130
+ reconciliationPhases,
131
+ mcpTools,
132
+ subscriberDisposables,
133
+ shutdown,
134
+ };
135
+ }
136
+ /**
137
+ * Topological sort using Kahn's algorithm.
138
+ *
139
+ * @param plugins - Plugins with optional `dependencies` arrays.
140
+ * @returns Plugins in dependency-first order.
141
+ * @throws If a cycle is detected.
142
+ */
143
+ function topologicalSort(plugins) {
144
+ const byName = new Map();
145
+ const inDegree = new Map();
146
+ const dependents = new Map();
147
+ // Initialize
148
+ for (const plugin of plugins) {
149
+ byName.set(plugin.name, plugin);
150
+ inDegree.set(plugin.name, 0);
151
+ dependents.set(plugin.name, []);
152
+ }
153
+ // Build edges: dependency → dependent (deduplicate to avoid inflated inDegree)
154
+ for (const plugin of plugins) {
155
+ const uniqueDeps = [...new Set(plugin.dependencies ?? [])];
156
+ for (const dep of uniqueDeps) {
157
+ dependents.get(dep).push(plugin.name);
158
+ inDegree.set(plugin.name, inDegree.get(plugin.name) + 1);
159
+ }
160
+ }
161
+ // Seed queue with zero in-degree nodes
162
+ const queue = [];
163
+ for (const [name, degree] of inDegree) {
164
+ if (degree === 0) {
165
+ queue.push(name);
166
+ }
167
+ }
168
+ // Process
169
+ const sorted = [];
170
+ while (queue.length > 0) {
171
+ const name = queue.shift();
172
+ sorted.push(byName.get(name));
173
+ for (const dependent of dependents.get(name)) {
174
+ const newDegree = inDegree.get(dependent) - 1;
175
+ inDegree.set(dependent, newDegree);
176
+ if (newDegree === 0) {
177
+ queue.push(dependent);
178
+ }
179
+ }
180
+ }
181
+ if (sorted.length !== plugins.length) {
182
+ // Find the cycle for a descriptive error message
183
+ const remaining = plugins
184
+ .filter((p) => !sorted.some((s) => s.name === p.name))
185
+ .map((p) => p.name);
186
+ throw new Error(`Dependency cycle detected among plugins: ${remaining.join(", ")}`);
187
+ }
188
+ return sorted;
189
+ }
190
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * GracklePlugin interface — the universal plugin contract.
3
+ *
4
+ * A plugin contributes server capabilities through five extension points:
5
+ * gRPC handlers, reconciliation phases, MCP tools, event subscribers,
6
+ * and lifecycle hooks.
7
+ *
8
+ * @module
9
+ */
10
+ import type { DescService } from "@bufbuild/protobuf";
11
+ import type { PluginContext, Disposable } from "./context.js";
12
+ /** A set of gRPC handler methods contributed to a ConnectRPC service. */
13
+ export interface ServiceRegistration {
14
+ /** The proto service definition (e.g., grackle.Grackle). */
15
+ service: DescService;
16
+ /**
17
+ * Handler method implementations.
18
+ *
19
+ * Uses `any` because handler functions have concrete parameter types that
20
+ * are not assignable to `(...args: unknown[]) => unknown` due to contravariance.
21
+ */
22
+ handlers: Record<string, (...args: any[]) => any>;
23
+ }
24
+ /** A named async phase that runs during each reconciliation tick. */
25
+ export interface ReconciliationPhase {
26
+ /** Short name for logging (e.g. "cron", "dispatch"). */
27
+ name: string;
28
+ /** Execute the phase. Errors are caught by the manager. */
29
+ execute: () => Promise<void>;
30
+ }
31
+ /**
32
+ * Declarative MCP tool definition contributed by a plugin.
33
+ *
34
+ * Intentionally uses `unknown` for schema/handler types to avoid depending
35
+ * on `@grackle-ai/mcp`. The server maps these to concrete ToolDefinition
36
+ * objects when registering with the MCP tool registry.
37
+ */
38
+ export interface PluginToolDefinition {
39
+ /** Unique tool name (snake_case by convention). */
40
+ name: string;
41
+ /** Logical group for filtering (e.g. "task", "session"). */
42
+ group: string;
43
+ /** Human-readable description. */
44
+ description: string;
45
+ /** Zod schema for input validation. */
46
+ inputSchema: unknown;
47
+ /** The gRPC method this tool calls. */
48
+ rpcMethod: string;
49
+ /** Whether this tool mutates state. */
50
+ mutating: boolean;
51
+ /** Optional MCP tool annotations. */
52
+ annotations?: Record<string, unknown>;
53
+ /** Handler function invoked when the tool is called. */
54
+ handler: (args: unknown, client: unknown, authContext?: unknown) => Promise<unknown>;
55
+ }
56
+ /**
57
+ * A Grackle plugin that contributes server capabilities.
58
+ *
59
+ * Plugins are loaded by {@link loadPlugins} in topological order based on
60
+ * declared dependencies. Each plugin can contribute gRPC handlers,
61
+ * reconciliation phases, MCP tools, and event subscribers.
62
+ */
63
+ export interface GracklePlugin {
64
+ /** Unique identifier (e.g., "core", "orchestration", "my-linear-sync"). */
65
+ name: string;
66
+ /** Plugins this one requires. The loader topologically sorts on this. */
67
+ dependencies?: string[];
68
+ /** gRPC handler groups to register on ConnectRPC services. */
69
+ grpcHandlers?: (ctx: PluginContext) => ServiceRegistration[];
70
+ /** Reconciliation phases to run on each tick. */
71
+ reconciliationPhases?: (ctx: PluginContext) => ReconciliationPhase[];
72
+ /** MCP tool definitions to register. */
73
+ mcpTools?: (ctx: PluginContext) => PluginToolDefinition[];
74
+ /** Event subscribers to wire up. Returns disposables for shutdown. */
75
+ eventSubscribers?: (ctx: PluginContext) => Disposable[];
76
+ /** Called after dependency plugins are initialized, in dependency order. */
77
+ initialize?: (ctx: PluginContext) => Promise<void>;
78
+ /** Called on graceful shutdown, in reverse load order. */
79
+ shutdown?: () => Promise<void>;
80
+ }
81
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE9D,yEAAyE;AACzE,MAAM,WAAW,mBAAmB;IAClC,4DAA4D;IAC5D,OAAO,EAAE,WAAW,CAAC;IACrB;;;;;OAKG;IAEH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC;CACnD;AAED,qEAAqE;AACrE,MAAM,WAAW,mBAAmB;IAClC,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,WAAW,EAAE,OAAO,CAAC;IACrB,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,QAAQ,EAAE,OAAO,CAAC;IAClB,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,wDAAwD;IACxD,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACtF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,mBAAmB,EAAE,CAAC;IAC7D,iDAAiD;IACjD,oBAAoB,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,mBAAmB,EAAE,CAAC;IACrE,wCAAwC;IACxC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,oBAAoB,EAAE,CAAC;IAC1D,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,UAAU,EAAE,CAAC;IAExD,4EAA4E;IAC5E,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC"}
package/dist/plugin.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * GracklePlugin interface — the universal plugin contract.
3
+ *
4
+ * A plugin contributes server capabilities through five extension points:
5
+ * gRPC handlers, reconciliation phases, MCP tools, event subscribers,
6
+ * and lifecycle hooks.
7
+ *
8
+ * @module
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,11 @@
1
+ // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
2
+ // It should be published with your NPM package. It should not be tracked by Git.
3
+ {
4
+ "tsdocVersion": "0.12",
5
+ "toolPackages": [
6
+ {
7
+ "packageName": "@microsoft/api-extractor",
8
+ "packageVersion": "7.57.7"
9
+ }
10
+ ]
11
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@grackle-ai/plugin-sdk",
3
+ "version": "0.95.0",
4
+ "description": "Plugin contract and loader for Grackle — defines GracklePlugin, PluginContext, and loadPlugins()",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/nick-pape/grackle.git",
9
+ "directory": "packages/plugin-sdk"
10
+ },
11
+ "keywords": [
12
+ "grackle",
13
+ "plugin",
14
+ "sdk"
15
+ ],
16
+ "homepage": "https://github.com/nick-pape/grackle#readme",
17
+ "bugs": {
18
+ "url": "https://github.com/nick-pape/grackle/issues"
19
+ },
20
+ "engines": {
21
+ "node": ">=22.0.0 <24.0.0"
22
+ },
23
+ "type": "module",
24
+ "main": "dist/index.js",
25
+ "types": "dist/index.d.ts",
26
+ "files": [
27
+ "dist/"
28
+ ],
29
+ "dependencies": {
30
+ "@bufbuild/protobuf": "^2.11.0",
31
+ "pino": "~9.6.0"
32
+ },
33
+ "devDependencies": {
34
+ "@rushstack/heft": "1.2.7",
35
+ "@types/node": "^22.0.0",
36
+ "vitest": "^3.2.1",
37
+ "@grackle-ai/heft-rig": "0.0.1"
38
+ },
39
+ "scripts": {
40
+ "build": "heft build --clean",
41
+ "test": "vitest run",
42
+ "clean": "heft clean",
43
+ "_phase:build": "heft run --only build -- --clean",
44
+ "_phase:test": "vitest run"
45
+ }
46
+ }