@emseepea/feedback 0.5.2 → 0.6.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 CHANGED
@@ -241,6 +241,37 @@ and cannot change the MCP result. Hook code must honor that signal and deadline.
241
241
  For reliable delivery, use the PostgreSQL or Firestore outbox, or GitHub and
242
242
  Zendesk native automation.
243
243
 
244
+ ### Notify an AI client about a team reply
245
+
246
+ A protected conversation can optionally use MCP Events to tell a subscribed AI
247
+ client that a team reply is ready. The event carries only the account or client
248
+ scope and stable thread, message, and source-event IDs. It does not carry the
249
+ reply text. The client should call `get-feedback-thread` and present the reply
250
+ to the person.
251
+
252
+ 1. Configure `createFeedbackReplyEventsOptions` from
253
+ `@emseepea/feedback/mcp-events` as the server's `events` option. Use the same
254
+ stable account or client scope for its `ownerKey` as for
255
+ `defineFeedbackConversation({ scope })`.
256
+ 2. Supply `canReadThread(ownerKey, threadId)` to check access to the exact
257
+ thread. The check runs when subscribing and again before delivery. Supply
258
+ your durable MCP Events store and health check as described in the
259
+ [MCP Events guide](https://emseepea.github.io/emseepea/mcp-events/).
260
+ 3. After saving a team reply, pass its typed feedback event to
261
+ `publishFeedbackTeamReplyEvent(app, event)`. For PostgreSQL or Firestore,
262
+ publish from the transactional outbox: a saved queue that can be retried
263
+ after failures. For GitHub or Zendesk, use the checked provider webhook.
264
+ The helper ignores non-team events and publishes only a reference to a team
265
+ message.
266
+
267
+ An outbox retry can publish the same reply more than once. Use the stable
268
+ `sourceEventId` in the event payload to recognise duplicates.
269
+
270
+ The Markdown destination is one-way and has no team reply to announce. An
271
+ event delivery, or a reply being offered to an AI client, does not prove a
272
+ person saw it. Test the complete reply journey in your own client and
273
+ deployment before relying on it.
274
+
244
275
  ## Effect and Retry Limits
245
276
 
246
277
  Creating a thread, submitting feedback, and appending a message are
@@ -0,0 +1,15 @@
1
+ import { publishMcpEvent, type McpEventsOptions } from "@emseepea/server";
2
+ export declare const feedbackReplyEventName = "feedback.reply.ready";
3
+ export interface FeedbackReplyEventsOptions {
4
+ /** Use the same stable scope projection as defineFeedbackConversation. */
5
+ readonly ownerKey: McpEventsOptions["ownerKey"];
6
+ /** Check access to this exact thread, including when a delivery is due. */
7
+ readonly canReadThread: (ownerKey: string, threadId: string) => boolean | Promise<boolean>;
8
+ readonly health: McpEventsOptions["health"];
9
+ readonly store: McpEventsOptions["store"];
10
+ readonly callbackTimeoutMs?: number;
11
+ }
12
+ export declare function createFeedbackReplyEventsOptions(options: FeedbackReplyEventsOptions): McpEventsOptions;
13
+ /** Publish a durable team reply reference after the feedback backend commits it. */
14
+ export declare function publishFeedbackTeamReplyEvent(app: Parameters<typeof publishMcpEvent>[0], event: unknown): Promise<boolean>;
15
+ //# sourceMappingURL=mcp-events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-events.d.ts","sourceRoot":"","sources":["../src/mcp-events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAI1E,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAa7D,MAAM,WAAW,0BAA0B;IACzC,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAChD,2EAA2E;IAC3E,QAAQ,CAAC,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3F,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC5C,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC1C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,0BAA0B,GAAG,gBAAgB,CAuBtG;AAED,oFAAoF;AACpF,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,CAAC,EAC1C,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,OAAO,CAAC,CAYlB"}
@@ -0,0 +1,57 @@
1
+ import { publishMcpEvent } from "@emseepea/server";
2
+ import { z } from "zod";
3
+ import { feedbackEventSchema } from "./index.js";
4
+ export const feedbackReplyEventName = "feedback.reply.ready";
5
+ const identifier = z.string().min(1).max(240);
6
+ const threadArguments = z.strictObject({
7
+ threadId: identifier.describe("Exact feedback thread to watch for a team reply."),
8
+ });
9
+ const replyReference = z.strictObject({
10
+ scope: identifier,
11
+ threadId: identifier,
12
+ messageId: identifier,
13
+ sourceEventId: identifier,
14
+ });
15
+ export function createFeedbackReplyEventsOptions(options) {
16
+ if (typeof options?.canReadThread !== "function")
17
+ throw new TypeError("feedback reply events require canReadThread");
18
+ return {
19
+ definitions: [{
20
+ name: feedbackReplyEventName,
21
+ description: "A team reply is ready. Read the exact thread with get-feedback-thread and present the reply to the user.",
22
+ inputSchema: threadArguments,
23
+ payloadSchema: replyReference,
24
+ matches: (arguments_, data, ownerKey) => arguments_.threadId === data.threadId && ownerKey === data.scope,
25
+ }],
26
+ ownerKey: options.ownerKey,
27
+ async authorize({ ownerKey, name, arguments: arguments_, phase }) {
28
+ if (name !== feedbackReplyEventName)
29
+ return false;
30
+ if (phase === "list")
31
+ return true;
32
+ const parsed = threadArguments.safeParse(arguments_);
33
+ return parsed.success && await options.canReadThread(ownerKey, parsed.data.threadId);
34
+ },
35
+ health: options.health,
36
+ store: options.store,
37
+ ...(options.callbackTimeoutMs === undefined ? {} : { callbackTimeoutMs: options.callbackTimeoutMs }),
38
+ };
39
+ }
40
+ /** Publish a durable team reply reference after the feedback backend commits it. */
41
+ export async function publishFeedbackTeamReplyEvent(app, event) {
42
+ const checked = feedbackEventSchema.safeParse(event);
43
+ if (!checked.success)
44
+ throw new TypeError("Invalid feedback event");
45
+ if (checked.data.type !== "feedback.message.added" || checked.data.author !== "team")
46
+ return false;
47
+ if (!checked.data.messageId)
48
+ throw new TypeError("Team reply event requires messageId");
49
+ await publishMcpEvent(app, feedbackReplyEventName, replyReference.parse({
50
+ scope: checked.data.scope,
51
+ threadId: checked.data.threadId,
52
+ messageId: checked.data.messageId,
53
+ sourceEventId: checked.data.id,
54
+ }));
55
+ return true;
56
+ }
57
+ //# sourceMappingURL=mcp-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-events.js","sourceRoot":"","sources":["../src/mcp-events.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emseepea/feedback",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Optional detailed feedback and support conversations for Em See Pea servers",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -44,6 +44,10 @@
44
44
  "types": "./dist/webhooks.d.ts",
45
45
  "import": "./dist/webhooks.js"
46
46
  },
47
+ "./mcp-events": {
48
+ "types": "./dist/mcp-events.d.ts",
49
+ "import": "./dist/mcp-events.js"
50
+ },
47
51
  "./testing-server": "./dist/testing-server.js"
48
52
  },
49
53
  "files": [
@@ -55,18 +59,18 @@
55
59
  "build": "tsc -p tsconfig.json && tsc -p tsconfig.type-tests.json",
56
60
  "prepack": "npm run build",
57
61
  "test": "npm run build && npm run test:built",
58
- "test:built": "node --test test/*.test.mjs",
62
+ "test:built": "node --test test/*.test.mjs src/*.test.js",
59
63
  "test:postgres": "cd ../../examples/multi-instance-postgres-server && node test/with-postgres.mjs node --test ../../packages/feedback/test/postgres.integration.mjs",
60
64
  "test:llm": "npm run build && npm run test:llm:built",
61
65
  "test:llm:built": "emseepea-test eval",
62
66
  "lint": "oxlint src test eval"
63
67
  },
64
68
  "dependencies": {
65
- "@emseepea/server": "0.20.0",
69
+ "@emseepea/server": "0.21.1",
66
70
  "zod": "4.4.3"
67
71
  },
68
72
  "devDependencies": {
69
- "@emseepea/testing": "0.20.1",
73
+ "@emseepea/testing": "0.20.3",
70
74
  "pg": "8.23.0"
71
75
  },
72
76
  "engines": {