@vertesia/tools-sdk 1.5.0-dev.20260725.083715Z → 1.5.0-dev.20260806.125520Z

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertesia/tools-sdk",
3
- "version": "1.5.0-dev.20260725.083715Z",
3
+ "version": "1.5.0-dev.20260806.125520Z",
4
4
  "description": "Tools SDK - utilities for building remote tools",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -18,19 +18,19 @@
18
18
  },
19
19
  "devDependencies": {
20
20
  "@types/node": "^24.13.3",
21
- "typescript": "^6.0.3",
21
+ "typescript": "7.0.2",
22
22
  "vitest": "^4.1.10",
23
23
  "@vertesia/tsconfig": "0.1.0"
24
24
  },
25
25
  "peerDependencies": {
26
- "hono": "^4.12.30"
26
+ "hono": "^4.12.34"
27
27
  },
28
28
  "dependencies": {
29
- "jose": "^6.2.3",
29
+ "jose": "^6.2.4",
30
30
  "zod": "^4.4.3",
31
- "@vertesia/client": "1.5.0-dev.20260725.083715Z",
32
- "@vertesia/common": "1.5.0-dev.20260725.083715Z",
33
- "@llumiverse/common": "1.5.0-dev.20260725.065646Z"
31
+ "@llumiverse/common": "1.5.0-dev.20260806.124634Z",
32
+ "@vertesia/common": "1.5.0-dev.20260806.125520Z",
33
+ "@vertesia/client": "1.5.0-dev.20260806.125520Z"
34
34
  },
35
35
  "repository": {
36
36
  "type": "git",
@@ -46,7 +46,7 @@
46
46
  "agents",
47
47
  "typescript"
48
48
  ],
49
- "gitHead": "57bcc2f27dd17b3387f27c18e89329c4c37f98ac",
49
+ "gitHead": "416423ef4182d8668809604902a871ca79629298",
50
50
  "scripts": {
51
51
  "lint": "biome lint src",
52
52
  "test": "vitest run",
@@ -1,5 +1,5 @@
1
1
  import { PromptRole } from '@llumiverse/common';
2
- import { PROCESS_DEFINITION_FORMAT_VERSION, TemplateType } from '@vertesia/common';
2
+ import { PROCESS_DEFINITION_FORMAT_VERSION, SystemRoles, TemplateType } from '@vertesia/common';
3
3
  import { describe, expect, it } from 'vitest';
4
4
  import { ContentTypesCollection } from '../ContentTypesCollection.js';
5
5
  import { InteractionCollection } from '../InteractionCollection.js';
@@ -75,6 +75,7 @@ describe('buildAppPackage', () => {
75
75
  src: '/lib/plugin.js',
76
76
  available_in: ['app_portal'],
77
77
  },
78
+ hooks: [{ kind: 'lifecycle', name: 'install', handler: async () => undefined }],
78
79
  } satisfies ToolServerConfig;
79
80
 
80
81
  const pkg = await buildAppPackage(config, {
@@ -87,6 +88,7 @@ describe('buildAppPackage', () => {
87
88
  expect(pkg.processes?.map((process) => process.id)).toEqual(['claims:intake']);
88
89
  expect(pkg.dashboards?.map((dashboard) => dashboard.id)).toEqual(['claims:ops']);
89
90
  expect(pkg.ui?.src).toBe('https://apps.example.test/lib/plugin.js');
91
+ expect(pkg.hooks).toEqual({ install: '/api/hooks/install' });
90
92
  });
91
93
 
92
94
  it('honors package scopes', async () => {
@@ -110,6 +112,132 @@ describe('buildAppPackage', () => {
110
112
  expect(pkg.types?.map((type) => type.id)).toEqual(['claim']);
111
113
  expect(pkg.interactions).toBeUndefined();
112
114
  });
115
+
116
+ it('returns lifecycle and event hook metadata for the hooks scope', async () => {
117
+ const config = {
118
+ interactions: [
119
+ new InteractionCollection({
120
+ name: 'claims',
121
+ interactions: [{ name: 'review', prompts: [] }],
122
+ }),
123
+ ],
124
+ hooks: [
125
+ { kind: 'lifecycle', name: 'install', handler: async () => undefined },
126
+ { kind: 'lifecycle', name: 'uninstall', handler: async () => undefined },
127
+ {
128
+ kind: 'event',
129
+ name: 'content-updated',
130
+ description: 'Processes updated content.',
131
+ handler: async () => undefined,
132
+ },
133
+ ],
134
+ } satisfies ToolServerConfig;
135
+
136
+ const pkg = await buildAppPackage(config, { scope: 'hooks' });
137
+
138
+ expect(pkg.hooks).toEqual({
139
+ install: '/api/hooks/install',
140
+ uninstall: '/api/hooks/uninstall',
141
+ events: [
142
+ {
143
+ name: 'content-updated',
144
+ path: '/api/hooks/content-updated',
145
+ description: 'Processes updated content.',
146
+ },
147
+ ],
148
+ });
149
+ expect(pkg.interactions).toBeUndefined();
150
+ });
151
+
152
+ it('returns subscriptions that reference registered event hooks', async () => {
153
+ const config = {
154
+ hooks: [
155
+ {
156
+ kind: 'event',
157
+ name: 'content-updated',
158
+ handler: async () => undefined,
159
+ },
160
+ ],
161
+ subscriptions: [
162
+ {
163
+ id: 'content-updated',
164
+ name: 'Content updated',
165
+ description: 'Refresh app-owned projections after content changes.',
166
+ hook: 'content-updated',
167
+ filter: { action: ['update'], resource_type: ['content_object'] },
168
+ run_as_role: SystemRoles.automation,
169
+ priority: 'normal',
170
+ },
171
+ ],
172
+ } satisfies ToolServerConfig;
173
+
174
+ const pkg = await buildAppPackage(config, { scope: 'subscriptions' });
175
+
176
+ expect(pkg.subscriptions).toEqual(config.subscriptions);
177
+ expect(pkg.hooks).toBeUndefined();
178
+ });
179
+
180
+ it('rejects subscriptions that reference missing or lifecycle hooks', async () => {
181
+ const missingHook = {
182
+ subscriptions: [
183
+ {
184
+ id: 'content-updated',
185
+ name: 'Content updated',
186
+ hook: 'content-updated',
187
+ filter: { action: ['update'] },
188
+ run_as_role: SystemRoles.automation,
189
+ },
190
+ ],
191
+ } satisfies ToolServerConfig;
192
+ await expect(buildAppPackage(missingHook, { scope: 'subscriptions' })).rejects.toThrow(
193
+ /references missing hook 'content-updated'/,
194
+ );
195
+
196
+ const lifecycleHook = {
197
+ hooks: [{ kind: 'lifecycle', name: 'install', handler: async () => undefined }],
198
+ subscriptions: [
199
+ {
200
+ id: 'on-install',
201
+ name: 'On install',
202
+ hook: 'install',
203
+ filter: { action: ['installed'] },
204
+ run_as_role: SystemRoles.automation,
205
+ },
206
+ ],
207
+ } satisfies ToolServerConfig;
208
+ await expect(buildAppPackage(lifecycleHook, { scope: 'subscriptions' })).rejects.toThrow(
209
+ /must reference an event hook/,
210
+ );
211
+ });
212
+
213
+ it('rejects duplicate or non-kebab-case subscription ids', async () => {
214
+ const eventHook = {
215
+ kind: 'event' as const,
216
+ name: 'content-updated',
217
+ handler: async () => undefined,
218
+ };
219
+ const subscription = {
220
+ id: 'content-updated',
221
+ name: 'Content updated',
222
+ hook: 'content-updated',
223
+ filter: { action: ['update'] },
224
+ run_as_role: SystemRoles.automation,
225
+ };
226
+
227
+ await expect(
228
+ buildAppPackage(
229
+ { hooks: [eventHook], subscriptions: [subscription, { ...subscription }] },
230
+ { scope: 'subscriptions' },
231
+ ),
232
+ ).rejects.toThrow(/Duplicate app event subscription id 'content-updated'/);
233
+
234
+ await expect(
235
+ buildAppPackage(
236
+ { hooks: [eventHook], subscriptions: [{ ...subscription, id: 'Content Updated' }] },
237
+ { scope: 'subscriptions' },
238
+ ),
239
+ ).rejects.toThrow(/must be kebab case/);
240
+ });
113
241
  });
114
242
 
115
243
  describe('buildAppPackage type identity', () => {
@@ -158,6 +158,57 @@ const builders: Record<Exclude<AppPackageScope, 'all'>, AppPackageBuilder> = {
158
158
  }
159
159
  pkg.activities = allActivities;
160
160
  },
161
+ async hooks(pkg: AppPackage, config: ToolServerConfig) {
162
+ const prefix = config.prefix || '/api';
163
+ const lifecycleHooks = new Set(
164
+ config.hooks?.filter((hook) => hook.kind === 'lifecycle').map((hook) => hook.name),
165
+ );
166
+ const eventHooks = (config.hooks ?? [])
167
+ .filter((hook) => hook.kind === 'event')
168
+ .map((hook) => ({
169
+ name: hook.name,
170
+ path: `${prefix}/hooks/${hook.name}`,
171
+ ...(hook.description ? { description: hook.description } : {}),
172
+ }));
173
+ if (lifecycleHooks.size > 0 || eventHooks.length > 0) {
174
+ pkg.hooks = {
175
+ ...(lifecycleHooks.has('install') && { install: `${prefix}/hooks/install` }),
176
+ ...(lifecycleHooks.has('uninstall') && { uninstall: `${prefix}/hooks/uninstall` }),
177
+ ...(eventHooks.length > 0 && { events: eventHooks }),
178
+ };
179
+ }
180
+ },
181
+ async subscriptions(pkg: AppPackage, config: ToolServerConfig) {
182
+ const subscriptions = config.subscriptions ?? [];
183
+ const seenIds = new Set<string>();
184
+ const hooksByName = new Map((config.hooks ?? []).map((hook) => [hook.name, hook]));
185
+
186
+ for (const subscription of subscriptions) {
187
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(subscription.id)) {
188
+ throw new Error(
189
+ `Invalid app event subscription id '${subscription.id}'. Subscription ids must be kebab case.`,
190
+ );
191
+ }
192
+ if (seenIds.has(subscription.id)) {
193
+ throw new Error(`Duplicate app event subscription id '${subscription.id}'.`);
194
+ }
195
+ seenIds.add(subscription.id);
196
+
197
+ const hook = hooksByName.get(subscription.hook);
198
+ if (!hook) {
199
+ throw new Error(
200
+ `App event subscription '${subscription.id}' references missing hook '${subscription.hook}'.`,
201
+ );
202
+ }
203
+ if (hook.kind !== 'event') {
204
+ throw new Error(
205
+ `App event subscription '${subscription.id}' must reference an event hook; '${subscription.hook}' is a lifecycle hook.`,
206
+ );
207
+ }
208
+ }
209
+
210
+ pkg.subscriptions = subscriptions.map((subscription) => ({ ...subscription }));
211
+ },
161
212
  };
162
213
 
163
214
  function normalizeScopes(scope: BuildAppPackageOptions['scope']): Set<AppPackageScope> {
@@ -184,6 +235,8 @@ export async function buildAppPackage(
184
235
  await builders.ui(pkg, config, options);
185
236
  await builders.settings(pkg, config, options);
186
237
  await builders.activities(pkg, config, options);
238
+ await builders.hooks(pkg, config, options);
239
+ await builders.subscriptions(pkg, config, options);
187
240
  } else {
188
241
  if (scopes.has('tools')) {
189
242
  await builders.tools(pkg, config, options);
@@ -218,6 +271,12 @@ export async function buildAppPackage(
218
271
  if (scopes.has('activities')) {
219
272
  await builders.activities(pkg, config, options);
220
273
  }
274
+ if (scopes.has('hooks')) {
275
+ await builders.hooks(pkg, config, options);
276
+ }
277
+ if (scopes.has('subscriptions')) {
278
+ await builders.subscriptions(pkg, config, options);
279
+ }
221
280
  }
222
281
 
223
282
  return pkg;
@@ -0,0 +1,158 @@
1
+ import type { AuthTokenPayload, PlatformEvent } from '@vertesia/common';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import type { AppEventHookPayload, AppLifecycleHookContext } from '../types.js';
4
+
5
+ const authorizeMock = vi.hoisted(() => vi.fn());
6
+
7
+ vi.mock('../auth.js', () => ({ authorize: authorizeMock }));
8
+
9
+ import { createToolServer } from '../server.js';
10
+
11
+ const event = {
12
+ event_id: 'event-1',
13
+ root_event_id: 'event-1',
14
+ hop_count: 0,
15
+ event_category: 'content',
16
+ action: 'updated',
17
+ resource_type: 'content_object',
18
+ resource_id: 'object-1',
19
+ account_id: 'account-1',
20
+ project_id: 'project-1',
21
+ tenant_id: 'account-1_project-1',
22
+ timestamp: '2026-08-05T10:00:00.000Z',
23
+ source: 'zeno-server',
24
+ } satisfies PlatformEvent;
25
+
26
+ const eventPayload = {
27
+ event,
28
+ delivery: {
29
+ id: 'delivery-1',
30
+ subscription_id: 'subscription-1',
31
+ attempt: 1,
32
+ },
33
+ } satisfies AppEventHookPayload;
34
+
35
+ describe('app hooks', () => {
36
+ beforeEach(() => {
37
+ authorizeMock.mockReset();
38
+ authorizeMock.mockResolvedValue({
39
+ token: 'project-token',
40
+ payload: { project: { id: 'project-1' } } as AuthTokenPayload,
41
+ getClient: vi.fn(),
42
+ });
43
+ });
44
+
45
+ it('executes a registered hook with authenticated context and metadata', async () => {
46
+ const install = vi.fn(async (_context: AppLifecycleHookContext) => ({ message: 'installed' }));
47
+ const app = createToolServer({
48
+ disableHtml: true,
49
+ hooks: [{ kind: 'lifecycle', name: 'install', handler: install }],
50
+ });
51
+
52
+ const response = await app.request('/api/hooks/install', {
53
+ method: 'POST',
54
+ headers: { Authorization: 'Bearer project-token', 'Content-Type': 'application/json' },
55
+ body: JSON.stringify({ metadata: { app_install_id: 'install-1', app_settings: { locale: 'en' } } }),
56
+ });
57
+
58
+ expect(response.status).toBe(200);
59
+ await expect(response.json()).resolves.toEqual({ ok: true, message: 'installed' });
60
+ expect(install).toHaveBeenCalledWith(
61
+ expect.objectContaining({
62
+ token: 'project-token',
63
+ metadata: { app_install_id: 'install-1', app_settings: { locale: 'en' } },
64
+ }),
65
+ );
66
+ });
67
+
68
+ it('returns 404 when the hook is not registered', async () => {
69
+ const app = createToolServer({ disableHtml: true });
70
+
71
+ const response = await app.request('/api/hooks/uninstall', {
72
+ method: 'POST',
73
+ headers: { Authorization: 'Bearer project-token', 'Content-Type': 'application/json' },
74
+ body: '{}',
75
+ });
76
+
77
+ expect(response.status).toBe(404);
78
+ expect(authorizeMock).not.toHaveBeenCalled();
79
+ });
80
+
81
+ it('executes an event hook with the canonical delivery envelope and authenticated context', async () => {
82
+ const contentUpdated = vi.fn(async (_payload: AppEventHookPayload) => ({ message: 'processed' }));
83
+ const app = createToolServer({
84
+ disableHtml: true,
85
+ hooks: [
86
+ {
87
+ kind: 'event',
88
+ name: 'content-updated',
89
+ description: 'Processes updated content.',
90
+ handler: contentUpdated,
91
+ },
92
+ ],
93
+ });
94
+
95
+ const response = await app.request('/api/hooks/content-updated', {
96
+ method: 'POST',
97
+ headers: { Authorization: 'Bearer project-token', 'Content-Type': 'application/json' },
98
+ body: JSON.stringify(eventPayload),
99
+ });
100
+
101
+ expect(response.status).toBe(200);
102
+ await expect(response.json()).resolves.toEqual({ ok: true, message: 'processed' });
103
+ expect(contentUpdated).toHaveBeenCalledWith(
104
+ eventPayload,
105
+ expect.objectContaining({ token: 'project-token', payload: { project: { id: 'project-1' } } }),
106
+ );
107
+ });
108
+
109
+ it('rejects malformed event hook payloads before authentication', async () => {
110
+ const handler = vi.fn(async () => undefined);
111
+ const app = createToolServer({
112
+ disableHtml: true,
113
+ hooks: [{ kind: 'event', name: 'content-updated', handler }],
114
+ });
115
+
116
+ const response = await app.request('/api/hooks/content-updated', {
117
+ method: 'POST',
118
+ headers: { Authorization: 'Bearer project-token', 'Content-Type': 'application/json' },
119
+ body: JSON.stringify({ event: eventPayload.event }),
120
+ });
121
+
122
+ expect(response.status).toBe(400);
123
+ expect(handler).not.toHaveBeenCalled();
124
+ expect(authorizeMock).not.toHaveBeenCalled();
125
+ });
126
+
127
+ it.each(['install', 'uninstall'])('reserves the lifecycle hook name %s', (name) => {
128
+ expect(() =>
129
+ createToolServer({
130
+ disableHtml: true,
131
+ hooks: [{ kind: 'event', name, handler: async () => undefined }],
132
+ }),
133
+ ).toThrow(`App event hook name is reserved: ${name}`);
134
+ });
135
+
136
+ it('rejects event hook names that are not safe path segments', () => {
137
+ expect(() =>
138
+ createToolServer({
139
+ disableHtml: true,
140
+ hooks: [{ kind: 'event', name: 'content/updated', handler: async () => undefined }],
141
+ }),
142
+ ).toThrow('Invalid app event hook name: content/updated');
143
+ });
144
+
145
+ it('rejects duplicate lifecycle hook registrations', () => {
146
+ const handler = vi.fn(async () => undefined);
147
+
148
+ expect(() =>
149
+ createToolServer({
150
+ disableHtml: true,
151
+ hooks: [
152
+ { kind: 'lifecycle', name: 'install', handler },
153
+ { kind: 'lifecycle', name: 'install', handler },
154
+ ],
155
+ }),
156
+ ).toThrow('Duplicate app hook: install');
157
+ });
158
+ });
@@ -0,0 +1,95 @@
1
+ import { AppEventHookPayloadSchema } from '@vertesia/common/api-schemas';
2
+ import type { Context, Hono } from 'hono';
3
+ import { HTTPException } from 'hono/http-exception';
4
+ import { authorize } from '../auth.js';
5
+ import type {
6
+ AppEventHookDefinition,
7
+ AppEventHookPayload,
8
+ AppLifecycleHookContext,
9
+ AppLifecycleHookDefinition,
10
+ AppLifecycleHookName,
11
+ AppLifecycleHookPayload,
12
+ AppLifecycleHookResult,
13
+ } from '../types.js';
14
+ import type { ToolContext, ToolServerConfig } from './types.js';
15
+
16
+ const HOOK_NAMES = new Set<AppLifecycleHookName>(['install', 'uninstall']);
17
+ const EVENT_HOOK_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
18
+
19
+ export function createHooksRoute(app: Hono, basePath: string, config: ToolServerConfig) {
20
+ const hooks = new Map<string, AppLifecycleHookDefinition | AppEventHookDefinition>();
21
+ for (const hook of config.hooks ?? []) {
22
+ if (hook.kind === 'lifecycle' && !HOOK_NAMES.has(hook.name)) {
23
+ throw new Error(`Unknown app lifecycle hook: ${hook.name}`);
24
+ }
25
+ if (hook.kind === 'event' && !EVENT_HOOK_NAME_PATTERN.test(hook.name)) {
26
+ throw new Error(`Invalid app event hook name: ${hook.name}`);
27
+ }
28
+ if (hook.kind === 'event' && HOOK_NAMES.has(hook.name as AppLifecycleHookName)) {
29
+ throw new Error(`App event hook name is reserved: ${hook.name}`);
30
+ }
31
+ if (hooks.has(hook.name)) {
32
+ throw new Error(`Duplicate app hook: ${hook.name}`);
33
+ }
34
+ hooks.set(hook.name, hook);
35
+ }
36
+
37
+ app.post(`${basePath}/:name`, async (c: Context) => {
38
+ const name = c.req.param('name') ?? '';
39
+ const hook = hooks.get(name);
40
+ if (!hook) {
41
+ throw new HTTPException(404, { message: `App hook is not registered: ${name}` });
42
+ }
43
+
44
+ const requestContext = c as unknown as ToolContext;
45
+ if (hook.kind === 'event') {
46
+ return executeEventHook(c, requestContext, hook);
47
+ }
48
+
49
+ const payload = parseHookPayload(requestContext.requestBody);
50
+ const metadata = payload.metadata ?? {};
51
+ const session = await authorize(c, metadata.endpoints, { toolName: `hook:${name}` });
52
+ const context: AppLifecycleHookContext = {
53
+ token: session.token,
54
+ payload: session.payload,
55
+ getClient: () => session.getClient(),
56
+ metadata,
57
+ };
58
+ const result = await hook.handler(context);
59
+
60
+ return c.json({ ok: true, ...(result ?? {}) } satisfies { ok: true } & AppLifecycleHookResult);
61
+ });
62
+ }
63
+
64
+ async function executeEventHook(c: Context, requestContext: ToolContext, hook: AppEventHookDefinition) {
65
+ const eventPayload = parseEventHookPayload(requestContext.requestBody);
66
+ const session = await authorize(c, undefined, { toolName: `hook:${hook.name}` });
67
+ const context = {
68
+ token: session.token,
69
+ payload: session.payload,
70
+ getClient: () => session.getClient(),
71
+ };
72
+ const result = await hook.handler(eventPayload, context);
73
+
74
+ return c.json({ ok: true, ...(result ?? {}) } satisfies { ok: true } & AppLifecycleHookResult);
75
+ }
76
+
77
+ function parseHookPayload(body: unknown): AppLifecycleHookPayload {
78
+ if (body === undefined) return {};
79
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
80
+ throw new HTTPException(400, { message: 'Invalid lifecycle hook payload' });
81
+ }
82
+ const metadata = (body as AppLifecycleHookPayload).metadata;
83
+ if (metadata !== undefined && (!metadata || typeof metadata !== 'object' || Array.isArray(metadata))) {
84
+ throw new HTTPException(400, { message: 'Invalid lifecycle hook metadata' });
85
+ }
86
+ return { metadata };
87
+ }
88
+
89
+ function parseEventHookPayload(body: unknown): AppEventHookPayload {
90
+ const result = AppEventHookPayloadSchema.safeParse(body);
91
+ if (!result.success) {
92
+ throw new HTTPException(400, { message: 'Invalid event hook payload' });
93
+ }
94
+ return result.data;
95
+ }
@@ -14,7 +14,12 @@ import type { InteractionCollection } from '../InteractionCollection.js';
14
14
  import type { RenderingTemplateCollection } from '../RenderingTemplateCollection.js';
15
15
  import type { SkillCollection } from '../SkillCollection.js';
16
16
  import type { ToolCollection } from '../ToolCollection.js';
17
- import type { MCPConnectionDetails, ToolExecutionPayload } from '../types.js';
17
+ import type {
18
+ AppEventSubscriptionDefinition,
19
+ AppHookDefinition,
20
+ MCPConnectionDetails,
21
+ ToolExecutionPayload,
22
+ } from '../types.js';
18
23
 
19
24
  /**
20
25
  * Extended context with parsed payload for tool/skill execution
@@ -26,6 +31,8 @@ export interface ToolContext extends Context {
26
31
  toolUseId?: string;
27
32
  /** The tool_use.tool_name from the payload */
28
33
  toolName?: string;
34
+ /** Parsed JSON body for non-tool routes such as lifecycle hooks. */
35
+ requestBody?: unknown;
29
36
  }
30
37
 
31
38
  /**
@@ -90,6 +97,12 @@ export interface ToolServerConfig {
90
97
  */
91
98
  mcpProviders?: MCPProviderConfig[];
92
99
 
100
+ /** Authenticated application hooks aggregated from active application modules. */
101
+ hooks?: AppHookDefinition[];
102
+
103
+ /** App-owned subscriptions whose delivery target is a registered event hook. */
104
+ subscriptions?: AppEventSubscriptionDefinition[];
105
+
93
106
  /**
94
107
  * A JSON schema defining settings for the application using this server
95
108
  */
package/src/server.ts CHANGED
@@ -6,6 +6,7 @@ import { createActivitiesRoute } from './server/activities.js';
6
6
  import { createPackageRoute } from './server/app-package.js';
7
7
  import { createContentTypesRoute } from './server/content-types.js';
8
8
  import { createDashboardsRoute } from './server/dashboards.js';
9
+ import { createHooksRoute } from './server/hooks.js';
9
10
  import { createInteractionsRoute } from './server/interactions.js';
10
11
  import { createMcpRoute } from './server/mcp.js';
11
12
  import { createProcessesRoute } from './server/processes.js';
@@ -68,6 +69,7 @@ export function createToolServer(config: ToolServerConfig): Hono {
68
69
  try {
69
70
  const text = await c.req.text();
70
71
  const body = JSON.parse(text);
72
+ ctx.requestBody = body;
71
73
  const result = ToolExecutionPayloadSchema.safeParse(body);
72
74
  if (result.success) {
73
75
  ctx.payload = result.data as ToolExecutionPayload;
@@ -112,6 +114,7 @@ export function createToolServer(config: ToolServerConfig): Hono {
112
114
  });
113
115
 
114
116
  createPackageRoute(app, `${prefix}/package`, config);
117
+ createHooksRoute(app, `${prefix}/hooks`, config);
115
118
  createToolsRoute(app, `${prefix}/tools`, config);
116
119
  createSkillsRoute(app, `${prefix}/skills`, config);
117
120
  createWidgetsRoute(app, `${prefix}/widgets`, config);