@pellux/goodvibes-sdk 0.25.2 → 0.25.4

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.
Files changed (55) hide show
  1. package/dist/_internal/platform/adapters/ntfy/index.d.ts.map +1 -1
  2. package/dist/_internal/platform/adapters/ntfy/index.js +105 -0
  3. package/dist/_internal/platform/adapters/types.d.ts +21 -0
  4. package/dist/_internal/platform/adapters/types.d.ts.map +1 -1
  5. package/dist/_internal/platform/channels/builtin/account-actions.js +18 -3
  6. package/dist/_internal/platform/channels/builtin/accounts.d.ts.map +1 -1
  7. package/dist/_internal/platform/channels/builtin/accounts.js +3 -0
  8. package/dist/_internal/platform/channels/builtin/plugins.d.ts.map +1 -1
  9. package/dist/_internal/platform/channels/builtin/plugins.js +3 -0
  10. package/dist/_internal/platform/channels/builtin/setup-schema.d.ts.map +1 -1
  11. package/dist/_internal/platform/channels/builtin/setup-schema.js +7 -3
  12. package/dist/_internal/platform/channels/delivery/strategies-core.d.ts.map +1 -1
  13. package/dist/_internal/platform/channels/delivery/strategies-core.js +1 -0
  14. package/dist/_internal/platform/channels/provider-runtime.d.ts +1 -1
  15. package/dist/_internal/platform/channels/provider-runtime.d.ts.map +1 -1
  16. package/dist/_internal/platform/channels/provider-runtime.js +14 -9
  17. package/dist/_internal/platform/channels/reply-pipeline.d.ts +1 -0
  18. package/dist/_internal/platform/channels/reply-pipeline.d.ts.map +1 -1
  19. package/dist/_internal/platform/channels/reply-pipeline.js +32 -1
  20. package/dist/_internal/platform/channels/surface-registry.d.ts.map +1 -1
  21. package/dist/_internal/platform/channels/surface-registry.js +3 -0
  22. package/dist/_internal/platform/companion/companion-chat-manager.d.ts +12 -0
  23. package/dist/_internal/platform/companion/companion-chat-manager.d.ts.map +1 -1
  24. package/dist/_internal/platform/companion/companion-chat-manager.js +41 -0
  25. package/dist/_internal/platform/config/schema-domain-surfaces.d.ts +3 -0
  26. package/dist/_internal/platform/config/schema-domain-surfaces.d.ts.map +1 -1
  27. package/dist/_internal/platform/config/schema-domain-surfaces.js +22 -1
  28. package/dist/_internal/platform/config/schema-types.d.ts +5 -2
  29. package/dist/_internal/platform/config/schema-types.d.ts.map +1 -1
  30. package/dist/_internal/platform/control-plane/conversation-message.d.ts +1 -1
  31. package/dist/_internal/platform/control-plane/conversation-message.d.ts.map +1 -1
  32. package/dist/_internal/platform/daemon/facade-composition.d.ts.map +1 -1
  33. package/dist/_internal/platform/daemon/facade-composition.js +3 -0
  34. package/dist/_internal/platform/daemon/surface-actions.d.ts +21 -1
  35. package/dist/_internal/platform/daemon/surface-actions.d.ts.map +1 -1
  36. package/dist/_internal/platform/daemon/surface-actions.js +184 -0
  37. package/dist/_internal/platform/daemon/surface-delivery.d.ts.map +1 -1
  38. package/dist/_internal/platform/daemon/surface-delivery.js +2 -0
  39. package/dist/_internal/platform/integrations/index.d.ts +2 -2
  40. package/dist/_internal/platform/integrations/index.d.ts.map +1 -1
  41. package/dist/_internal/platform/integrations/index.js +1 -1
  42. package/dist/_internal/platform/integrations/ntfy.d.ts +23 -0
  43. package/dist/_internal/platform/integrations/ntfy.d.ts.map +1 -1
  44. package/dist/_internal/platform/integrations/ntfy.js +281 -30
  45. package/dist/_internal/platform/providers/registry.d.ts +1 -0
  46. package/dist/_internal/platform/providers/registry.d.ts.map +1 -1
  47. package/dist/_internal/platform/providers/registry.js +15 -5
  48. package/dist/_internal/platform/runtime/emitters/agents.d.ts +3 -0
  49. package/dist/_internal/platform/runtime/emitters/agents.d.ts.map +1 -1
  50. package/dist/_internal/platform/runtime/events/agents.d.ts +3 -0
  51. package/dist/_internal/platform/runtime/events/agents.d.ts.map +1 -1
  52. package/dist/_internal/platform/tools/agent/manager.d.ts.map +1 -1
  53. package/dist/_internal/platform/tools/agent/manager.js +3 -0
  54. package/dist/_internal/platform/version.js +1 -1
  55. package/package.json +1 -1
@@ -1,5 +1,13 @@
1
+ import { emitCompanionMessageReceived } from '../runtime/emitters/index.js';
2
+ import { NtfyIntegration } from '../integrations/ntfy.js';
3
+ import { logger } from '../utils/logger.js';
4
+ import { summarizeError } from '../utils/error-display.js';
1
5
  export class DaemonSurfaceActionHelper {
2
6
  context;
7
+ static NTFY_CHAT_REPLY_TTL_MS = 10 * 60_000;
8
+ pendingNtfyChatReplies = new Map();
9
+ ntfyChatReplyUnsubscribers = [];
10
+ ntfyRemoteSessionId = null;
3
11
  constructor(context) {
4
12
  this.context = context;
5
13
  }
@@ -15,6 +23,9 @@ export class DaemonSurfaceActionHelper {
15
23
  performInteractiveSurfaceAction: (actionId, surface, request) => this.performInteractiveSurfaceAction(actionId, surface, request),
16
24
  trySpawnAgent: (input, logLabel, sessionId) => this.context.trySpawnAgent(input, logLabel, sessionId),
17
25
  queueSurfaceReplyFromBinding: (binding, input) => this.context.queueSurfaceReplyFromBinding(binding, input),
26
+ publishConversationFollowup: (sessionId, envelope) => this.publishConversationFollowup(sessionId, envelope),
27
+ queueNtfyChatReply: (input) => this.queueNtfyChatReply(input),
28
+ postNtfyRemoteChatMessage: (input) => this.postNtfyRemoteChatMessage(input),
18
29
  };
19
30
  }
20
31
  buildGenericWebhookAdapterContext() {
@@ -121,4 +132,177 @@ export class DaemonSurfaceActionHelper {
121
132
  }
122
133
  return `No handler for ${surface} action ${actionId}`;
123
134
  }
135
+ publishConversationFollowup(sessionId, envelope) {
136
+ this.context.controlPlaneGateway.publishEvent('conversation.followup.companion', { sessionId, ...envelope }, { clientKind: 'tui' });
137
+ emitCompanionMessageReceived(this.context.runtimeBus, { sessionId, traceId: `ntfy:${envelope.messageId}`, source: 'ntfy-chat' }, {
138
+ sessionId,
139
+ messageId: envelope.messageId,
140
+ body: envelope.body,
141
+ source: envelope.source,
142
+ timestamp: envelope.timestamp,
143
+ });
144
+ }
145
+ queueNtfyChatReply(input) {
146
+ this.ensureNtfyChatReplyListeners();
147
+ this.cleanupExpiredNtfyChatReplies();
148
+ const bucket = this.pendingNtfyChatReplies.get(input.sessionId) ?? [];
149
+ bucket.push({
150
+ ...input,
151
+ createdAt: Date.now(),
152
+ });
153
+ this.pendingNtfyChatReplies.set(input.sessionId, bucket);
154
+ }
155
+ ensureNtfyChatReplyListeners() {
156
+ if (this.ntfyChatReplyUnsubscribers.length > 0)
157
+ return;
158
+ this.ntfyChatReplyUnsubscribers = [
159
+ this.context.runtimeBus.on('TURN_SUBMITTED', (envelope) => this.matchNtfyChatReplyTurn(envelope.sessionId, envelope.payload.turnId, envelope.payload.prompt)),
160
+ this.context.runtimeBus.on('TURN_COMPLETED', (envelope) => {
161
+ void this.deliverNtfyChatReply(envelope.sessionId, envelope.payload.turnId, envelope.payload.response);
162
+ }),
163
+ this.context.runtimeBus.on('TURN_ERROR', (envelope) => {
164
+ void this.deliverNtfyChatReply(envelope.sessionId, envelope.payload.turnId, `Error: ${envelope.payload.error}`);
165
+ }),
166
+ ];
167
+ }
168
+ matchNtfyChatReplyTurn(sessionId, turnId, prompt) {
169
+ this.cleanupExpiredNtfyChatReplies();
170
+ const bucket = this.pendingNtfyChatReplies.get(sessionId);
171
+ if (!bucket)
172
+ return;
173
+ const normalizedPrompt = prompt.trim();
174
+ const pending = bucket.find((entry) => !entry.turnId && entry.body.trim() === normalizedPrompt);
175
+ if (pending) {
176
+ pending.turnId = turnId;
177
+ }
178
+ }
179
+ async deliverNtfyChatReply(sessionId, turnId, message) {
180
+ const pending = this.takeNtfyChatReply(sessionId, turnId);
181
+ if (!pending)
182
+ return;
183
+ try {
184
+ await this.publishNtfyReply(pending.topic, message.trim() || '(empty response)', pending.title ?? 'GoodVibes chat');
185
+ }
186
+ catch (error) {
187
+ logger.warn('DaemonSurfaceActionHelper: failed to publish ntfy chat reply', {
188
+ sessionId,
189
+ turnId,
190
+ topic: pending.topic,
191
+ error: summarizeError(error),
192
+ });
193
+ }
194
+ }
195
+ takeNtfyChatReply(sessionId, turnId) {
196
+ const bucket = this.pendingNtfyChatReplies.get(sessionId);
197
+ if (!bucket)
198
+ return null;
199
+ const index = bucket.findIndex((entry) => entry.turnId === turnId);
200
+ if (index < 0)
201
+ return null;
202
+ const [pending] = bucket.splice(index, 1);
203
+ if (bucket.length === 0) {
204
+ this.pendingNtfyChatReplies.delete(sessionId);
205
+ }
206
+ return pending ?? null;
207
+ }
208
+ cleanupExpiredNtfyChatReplies(now = Date.now()) {
209
+ for (const [sessionId, bucket] of this.pendingNtfyChatReplies.entries()) {
210
+ const fresh = bucket.filter((entry) => now - entry.createdAt < DaemonSurfaceActionHelper.NTFY_CHAT_REPLY_TTL_MS);
211
+ if (fresh.length === 0) {
212
+ this.pendingNtfyChatReplies.delete(sessionId);
213
+ }
214
+ else if (fresh.length !== bucket.length) {
215
+ this.pendingNtfyChatReplies.set(sessionId, fresh);
216
+ }
217
+ }
218
+ }
219
+ async postNtfyRemoteChatMessage(input) {
220
+ const manager = this.context.companionChatManager;
221
+ if (!manager) {
222
+ return {
223
+ sessionId: '',
224
+ messageId: '',
225
+ delivered: false,
226
+ error: 'ntfy remote chat manager is unavailable',
227
+ };
228
+ }
229
+ let sessionId = this.ntfyRemoteSessionId ?? '';
230
+ try {
231
+ await manager.init();
232
+ let session = sessionId ? manager.getSession(sessionId) : null;
233
+ if (!session || session.status === 'closed') {
234
+ session = manager.createSession({
235
+ title: input.title ?? 'GoodVibes ntfy',
236
+ });
237
+ this.ntfyRemoteSessionId = session.id;
238
+ }
239
+ sessionId = session.id;
240
+ void this.runNtfyRemoteChatTurn(manager, sessionId, input).catch((error) => {
241
+ logger.warn('DaemonSurfaceActionHelper: ntfy remote chat turn failed', {
242
+ sessionId,
243
+ topic: input.topic,
244
+ error: summarizeError(error),
245
+ });
246
+ });
247
+ return {
248
+ sessionId,
249
+ messageId: '',
250
+ delivered: true,
251
+ };
252
+ }
253
+ catch (error) {
254
+ const errorMessage = summarizeError(error);
255
+ try {
256
+ await this.publishNtfyReply(input.topic, `Error: ${errorMessage}`, input.title ?? 'GoodVibes ntfy');
257
+ }
258
+ catch (publishError) {
259
+ logger.warn('DaemonSurfaceActionHelper: failed to publish ntfy remote chat error', {
260
+ topic: input.topic,
261
+ error: summarizeError(publishError),
262
+ });
263
+ }
264
+ return {
265
+ sessionId,
266
+ messageId: '',
267
+ delivered: false,
268
+ error: errorMessage,
269
+ };
270
+ }
271
+ }
272
+ async runNtfyRemoteChatTurn(manager, sessionId, input) {
273
+ try {
274
+ const result = await manager.postMessageAndWaitForReply(sessionId, input.body, `ntfy:${input.topic}`, { timeoutMs: 120_000 });
275
+ const response = result.response?.trim();
276
+ const resultError = result.error ?? (response ? undefined : 'No response from ntfy remote chat');
277
+ const outbound = response || `Error: ${resultError}`;
278
+ await this.publishNtfyReply(input.topic, outbound, input.title ?? 'GoodVibes ntfy');
279
+ }
280
+ catch (error) {
281
+ const errorMessage = summarizeError(error);
282
+ try {
283
+ await this.publishNtfyReply(input.topic, `Error: ${errorMessage}`, input.title ?? 'GoodVibes ntfy');
284
+ }
285
+ catch (publishError) {
286
+ logger.warn('DaemonSurfaceActionHelper: failed to publish ntfy remote chat error', {
287
+ topic: input.topic,
288
+ error: summarizeError(publishError),
289
+ });
290
+ }
291
+ }
292
+ }
293
+ async publishNtfyReply(topic, message, title) {
294
+ if (!topic || !message.trim())
295
+ return;
296
+ const ntfy = new NtfyIntegration(String(this.context.configManager.get('surfaces.ntfy.baseUrl') || 'https://ntfy.sh'), await this.resolveNtfyToken() ?? undefined);
297
+ await ntfy.publish(topic, message, {
298
+ title,
299
+ markGoodVibesOrigin: true,
300
+ });
301
+ }
302
+ async resolveNtfyToken() {
303
+ return await this.context.serviceRegistry.resolveSecret('ntfy', 'primary')
304
+ || String(this.context.configManager.get('surfaces.ntfy.token') || '')
305
+ || process.env.NTFY_ACCESS_TOKEN
306
+ || null;
307
+ }
124
308
  }
@@ -1 +1 @@
1
- {"version":3,"file":"surface-delivery.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/daemon/surface-delivery.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"surface-delivery.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/daemon/surface-delivery.ts"],"names":[],"mappings":"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"}
@@ -201,6 +201,7 @@ export class DaemonSurfaceDeliveryHelper {
201
201
  ],
202
202
  }
203
203
  : {}),
204
+ markGoodVibesOrigin: true,
204
205
  });
205
206
  }
206
207
  async deliverWebhookAgentReply(pending, message) {
@@ -428,6 +429,7 @@ export class DaemonSurfaceDeliveryHelper {
428
429
  await ntfy.publish(topic, `${isPending ? 'Approval required' : `Approval ${approval.status}`}: ${summary}`, {
429
430
  title: approval.request.tool,
430
431
  ...(webUrl ? { click: webUrl } : {}),
432
+ markGoodVibesOrigin: true,
431
433
  }).catch(() => { });
432
434
  }
433
435
  async deliverWebhookApprovalUpdate(approval, binding) {
@@ -7,7 +7,7 @@ export { Notifier } from './notifier.js';
7
7
  export { GitHubIntegration } from './github.js';
8
8
  export type { GitHubWebhookEvent } from './github.js';
9
9
  export { DeliveryQueue, DeliveryError, classifyDeliveryError, snapshotQueueStatus } from './delivery.js';
10
- export { NtfyIntegration } from './ntfy.js';
10
+ export { GOODVIBES_NTFY_ORIGIN, GOODVIBES_NTFY_ORIGIN_HEADER, GOODVIBES_NTFY_OUTBOUND_TAG, GOODVIBES_NTFY_AGENT_TOPIC, GOODVIBES_NTFY_CHAT_TOPIC, GOODVIBES_NTFY_DEFAULT_TOPICS, GOODVIBES_NTFY_REMOTE_TOPIC, NtfyIntegration, isGoodVibesNtfyDeliveryEcho, resolveGoodVibesNtfyTopics, } from './ntfy.js';
11
11
  export type { DeliveryOutcome, DeliveryFailureClass, DeadLetterEntry, DeliveryMetrics, DeliveryQueueConfig, IntegrationQueueStatus, } from './delivery.js';
12
- export type { NtfyMessage, NtfyPublishOptions, NtfySubscribeOptions, NtfyWebSocketOptions } from './ntfy.js';
12
+ export type { GoodVibesNtfyTopicConfig, GoodVibesNtfyTopics, NtfyMessage, NtfyPublishOptions, NtfySubscribeOptions, NtfyWebSocketOptions, } from './ntfy.js';
13
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/integrations/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACrE,YAAY,EACV,mBAAmB,EACnB,uBAAuB,EACvB,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EAChB,0BAA0B,EAC1B,yBAAyB,EACzB,wBAAwB,EACxB,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACvB,4BAA4B,EAC5B,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACpH,YAAY,EACV,yBAAyB,EACzB,+BAA+B,EAC/B,yBAAyB,EACzB,2BAA2B,EAC3B,sBAAsB,EACtB,kBAAkB,EAClB,4BAA4B,GAC7B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,8BAA8B,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,YAAY,EACV,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/integrations/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACrE,YAAY,EACV,mBAAmB,EACnB,uBAAuB,EACvB,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EAChB,0BAA0B,EAC1B,yBAAyB,EACzB,wBAAwB,EACxB,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACvB,4BAA4B,EAC5B,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACpH,YAAY,EACV,yBAAyB,EACzB,+BAA+B,EAC/B,yBAAyB,EACzB,2BAA2B,EAC3B,sBAAsB,EACtB,kBAAkB,EAClB,4BAA4B,GAC7B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,8BAA8B,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,EACL,qBAAqB,EACrB,4BAA4B,EAC5B,2BAA2B,EAC3B,0BAA0B,EAC1B,yBAAyB,EACzB,6BAA6B,EAC7B,2BAA2B,EAC3B,eAAe,EACf,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,wBAAwB,EACxB,mBAAmB,EACnB,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,WAAW,CAAC"}
@@ -4,4 +4,4 @@ export { DiscordInteractionType, DiscordInteractionResponseType } from './discor
4
4
  export { Notifier } from './notifier.js';
5
5
  export { GitHubIntegration } from './github.js';
6
6
  export { DeliveryQueue, DeliveryError, classifyDeliveryError, snapshotQueueStatus } from './delivery.js';
7
- export { NtfyIntegration } from './ntfy.js';
7
+ export { GOODVIBES_NTFY_ORIGIN, GOODVIBES_NTFY_ORIGIN_HEADER, GOODVIBES_NTFY_OUTBOUND_TAG, GOODVIBES_NTFY_AGENT_TOPIC, GOODVIBES_NTFY_CHAT_TOPIC, GOODVIBES_NTFY_DEFAULT_TOPICS, GOODVIBES_NTFY_REMOTE_TOPIC, NtfyIntegration, isGoodVibesNtfyDeliveryEcho, resolveGoodVibesNtfyTopics, } from './ntfy.js';
@@ -1,3 +1,22 @@
1
+ export declare const GOODVIBES_NTFY_ORIGIN = "goodvibes-sdk";
2
+ export declare const GOODVIBES_NTFY_ORIGIN_HEADER = "X-Goodvibes-Origin";
3
+ export declare const GOODVIBES_NTFY_OUTBOUND_TAG = "goodvibes-sdk-outbound";
4
+ export declare const GOODVIBES_NTFY_CHAT_TOPIC = "goodvibes-chat";
5
+ export declare const GOODVIBES_NTFY_AGENT_TOPIC = "goodvibes-agent";
6
+ export declare const GOODVIBES_NTFY_REMOTE_TOPIC = "goodvibes-ntfy";
7
+ export declare const GOODVIBES_NTFY_DEFAULT_TOPICS: readonly ["goodvibes-chat", "goodvibes-agent", "goodvibes-ntfy"];
8
+ export interface GoodVibesNtfyTopicConfig {
9
+ readonly chatTopic?: string | null;
10
+ readonly agentTopic?: string | null;
11
+ readonly remoteTopic?: string | null;
12
+ }
13
+ export interface GoodVibesNtfyTopics {
14
+ readonly chatTopic: string;
15
+ readonly agentTopic: string;
16
+ readonly remoteTopic: string;
17
+ readonly all: readonly string[];
18
+ }
19
+ export declare function resolveGoodVibesNtfyTopics(config?: GoodVibesNtfyTopicConfig): GoodVibesNtfyTopics;
1
20
  export interface NtfyPublishOptions {
2
21
  readonly title?: string;
3
22
  readonly priority?: 1 | 2 | 3 | 4 | 5;
@@ -5,6 +24,7 @@ export interface NtfyPublishOptions {
5
24
  readonly click?: string;
6
25
  readonly attach?: string;
7
26
  readonly actions?: readonly string[];
27
+ readonly markGoodVibesOrigin?: boolean;
8
28
  }
9
29
  export interface NtfyMessage {
10
30
  readonly id?: string;
@@ -25,6 +45,8 @@ export interface NtfySubscribeOptions {
25
45
  readonly poll?: boolean;
26
46
  readonly filters?: Record<string, string | number | boolean | readonly string[]>;
27
47
  readonly signal?: AbortSignal;
48
+ readonly reconnect?: boolean;
49
+ readonly reconnectDelayMs?: number;
28
50
  }
29
51
  export interface NtfyWebSocketOptions extends NtfySubscribeOptions {
30
52
  readonly WebSocketImpl?: typeof WebSocket;
@@ -40,4 +62,5 @@ export declare class NtfyIntegration {
40
62
  connectWebSocket(topic: string, onMessage: (message: NtfyMessage) => void | Promise<void>, options?: NtfyWebSocketOptions): WebSocket;
41
63
  private buildAuthHeaders;
42
64
  }
65
+ export declare function isGoodVibesNtfyDeliveryEcho(message: Record<string, unknown>): boolean;
43
66
  //# sourceMappingURL=ntfy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ntfy.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/integrations/ntfy.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACtC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,gBAAgB,GAAG,eAAe,GAAG,cAAc,GAAG,MAAM,CAAC;IAChH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,MAAM,EAAE,CAAC,CAAC;IACjF,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAqB,SAAQ,oBAAoB;IAChE,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,SAAS,CAAC;CAC3C;AAED,qBAAa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;gBADN,OAAO,SAAoB,EAC3B,KAAK,CAAC,EAAE,MAAM,YAAA;IAG3B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB9F,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,IAAa,EAAE,OAAO,GAAE,oBAAyB,GAAG,MAAM;IAepH,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,IAAI,CAAC,oBAAoB,EAAE,MAAM,CAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAoB7F,mBAAmB,CACvB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACzD,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,IAAI,CAAC;IAgChB,gBAAgB,CACd,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACzD,OAAO,GAAE,oBAAyB,GACjC,SAAS;IAiBZ,OAAO,CAAC,gBAAgB;CAKzB"}
1
+ {"version":3,"file":"ntfy.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/integrations/ntfy.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,31 @@
1
1
  /** SDK-owned platform module. This implementation is maintained in goodvibes-sdk. */
2
+ import { request as httpRequest } from 'node:http';
3
+ import { connect as http2Connect } from 'node:http2';
4
+ import { request as httpsRequest } from 'node:https';
2
5
  import { fetchWithTimeout } from '../utils/fetch-with-timeout.js';
3
6
  import { instrumentedFetch } from '../utils/fetch-with-timeout.js';
7
+ export const GOODVIBES_NTFY_ORIGIN = 'goodvibes-sdk';
8
+ export const GOODVIBES_NTFY_ORIGIN_HEADER = 'X-Goodvibes-Origin';
9
+ export const GOODVIBES_NTFY_OUTBOUND_TAG = 'goodvibes-sdk-outbound';
10
+ export const GOODVIBES_NTFY_CHAT_TOPIC = 'goodvibes-chat';
11
+ export const GOODVIBES_NTFY_AGENT_TOPIC = 'goodvibes-agent';
12
+ export const GOODVIBES_NTFY_REMOTE_TOPIC = 'goodvibes-ntfy';
13
+ export const GOODVIBES_NTFY_DEFAULT_TOPICS = [
14
+ GOODVIBES_NTFY_CHAT_TOPIC,
15
+ GOODVIBES_NTFY_AGENT_TOPIC,
16
+ GOODVIBES_NTFY_REMOTE_TOPIC,
17
+ ];
18
+ export function resolveGoodVibesNtfyTopics(config = {}) {
19
+ const chatTopic = normalizeNtfyTopic(config.chatTopic, GOODVIBES_NTFY_CHAT_TOPIC);
20
+ const agentTopic = normalizeNtfyTopic(config.agentTopic, GOODVIBES_NTFY_AGENT_TOPIC);
21
+ const remoteTopic = normalizeNtfyTopic(config.remoteTopic, GOODVIBES_NTFY_REMOTE_TOPIC);
22
+ return {
23
+ chatTopic,
24
+ agentTopic,
25
+ remoteTopic,
26
+ all: [...new Set([chatTopic, agentTopic, remoteTopic])],
27
+ };
28
+ }
4
29
  export class NtfyIntegration {
5
30
  baseUrl;
6
31
  token;
@@ -17,14 +42,19 @@ export class NtfyIntegration {
17
42
  headers.set('Title', options.title);
18
43
  if (options.priority)
19
44
  headers.set('Priority', String(options.priority));
20
- if (options.tags?.length)
21
- headers.set('Tags', options.tags.join(','));
45
+ const tags = options.markGoodVibesOrigin
46
+ ? [...new Set([...(options.tags ?? []), GOODVIBES_NTFY_OUTBOUND_TAG])]
47
+ : options.tags ?? [];
48
+ if (tags.length)
49
+ headers.set('Tags', tags.join(','));
22
50
  if (options.click)
23
51
  headers.set('Click', options.click);
24
52
  if (options.attach)
25
53
  headers.set('Attach', options.attach);
26
54
  if (options.actions?.length)
27
55
  headers.set('Actions', options.actions.join(';'));
56
+ if (options.markGoodVibesOrigin)
57
+ headers.set(GOODVIBES_NTFY_ORIGIN_HEADER, GOODVIBES_NTFY_ORIGIN);
28
58
  if (this.token)
29
59
  headers.set('Authorization', `Bearer ${this.token}`);
30
60
  const response = await fetchWithTimeout(target, {
@@ -74,35 +104,21 @@ export class NtfyIntegration {
74
104
  .map((line) => JSON.parse(line));
75
105
  }
76
106
  async subscribeJsonStream(topic, onMessage, options = {}) {
77
- const url = this.buildSubscribeUrl(topic, 'json', options);
78
- const response = await instrumentedFetch(url, {
79
- method: 'GET',
80
- headers: this.buildAuthHeaders(),
81
- signal: options.signal,
82
- });
83
- if (!response.ok || !response.body) {
84
- const body = await response.text().catch(() => '');
85
- throw new Error(`NtfyIntegration.subscribeJsonStream failed (${response.status}): ${body}`);
86
- }
87
- const reader = response.body.getReader();
88
- const decoder = new TextDecoder();
89
- let buffer = '';
90
- while (true) {
91
- const { done, value } = await reader.read();
92
- if (done)
93
- break;
94
- buffer += decoder.decode(value, { stream: true });
95
- const lines = buffer.split(/\r?\n/);
96
- buffer = lines.pop() ?? '';
97
- for (const line of lines) {
98
- const trimmed = line.trim();
99
- if (!trimmed)
100
- continue;
101
- await onMessage(JSON.parse(trimmed));
107
+ const reconnect = options.reconnect ?? !options.poll;
108
+ while (!options.signal?.aborted) {
109
+ const url = this.buildSubscribeUrl(topic, 'json', options);
110
+ try {
111
+ await readNtfyJsonStreamWithNodeTransport(url, this.buildAuthHeaders(), onMessage, options.signal);
102
112
  }
103
- }
104
- if (buffer.trim()) {
105
- await onMessage(JSON.parse(buffer.trim()));
113
+ catch (error) {
114
+ if (options.signal?.aborted)
115
+ return;
116
+ if (!reconnect || isFatalNtfyStreamError(error))
117
+ throw error;
118
+ }
119
+ if (!reconnect || options.signal?.aborted)
120
+ return;
121
+ await waitForNtfyReconnectDelay(options.signal, options.reconnectDelayMs ?? 1_000);
106
122
  }
107
123
  }
108
124
  connectWebSocket(topic, onMessage, options = {}) {
@@ -129,3 +145,238 @@ export class NtfyIntegration {
129
145
  return headers;
130
146
  }
131
147
  }
148
+ export function isGoodVibesNtfyDeliveryEcho(message) {
149
+ if (message.event !== 'message')
150
+ return false;
151
+ const tags = message.tags;
152
+ if (Array.isArray(tags) && tags.includes(GOODVIBES_NTFY_OUTBOUND_TAG))
153
+ return true;
154
+ const headers = readRecord(message.headers);
155
+ const originHeader = headers ? readCaseInsensitiveHeader(headers, GOODVIBES_NTFY_ORIGIN_HEADER) : undefined;
156
+ return originHeader === GOODVIBES_NTFY_ORIGIN;
157
+ }
158
+ function normalizeNtfyTopic(value, fallback) {
159
+ const topic = typeof value === 'string' ? value.trim() : '';
160
+ return topic || fallback;
161
+ }
162
+ class NtfyStreamHttpError extends Error {
163
+ status;
164
+ constructor(status, body) {
165
+ super(`NtfyIntegration.subscribeJsonStream failed (${status}): ${body}`);
166
+ this.status = status;
167
+ this.name = 'NtfyStreamHttpError';
168
+ }
169
+ }
170
+ async function readNtfyJsonStreamWithNodeTransport(url, headers, onMessage, signal) {
171
+ const parsed = new URL(url);
172
+ if (parsed.protocol === 'http:') {
173
+ return readNtfyJsonStreamWithHttp1(url, headers, onMessage, signal, httpRequest);
174
+ }
175
+ if (parsed.protocol === 'https:') {
176
+ try {
177
+ await readNtfyJsonStreamWithHttp2(url, headers, onMessage, signal);
178
+ return;
179
+ }
180
+ catch (error) {
181
+ if (signal?.aborted || error instanceof NtfyStreamHttpError)
182
+ throw error;
183
+ return readNtfyJsonStreamWithHttp1(url, headers, onMessage, signal, httpsRequest);
184
+ }
185
+ }
186
+ throw new Error(`Unsupported ntfy stream protocol: ${parsed.protocol}`);
187
+ }
188
+ function readNtfyJsonStreamWithHttp1(url, headers, onMessage, signal, requestImpl) {
189
+ if (signal?.aborted)
190
+ return Promise.resolve();
191
+ return new Promise((resolve, reject) => {
192
+ let settled = false;
193
+ let aborted = false;
194
+ const state = { buffer: '', queue: Promise.resolve() };
195
+ const settle = (callback) => {
196
+ if (settled)
197
+ return;
198
+ settled = true;
199
+ signal?.removeEventListener('abort', abort);
200
+ callback();
201
+ };
202
+ const fail = (error) => settle(() => reject(error));
203
+ const finish = () => settle(resolve);
204
+ const abort = () => {
205
+ aborted = true;
206
+ req.destroy();
207
+ finish();
208
+ };
209
+ const req = requestImpl(url, {
210
+ method: 'GET',
211
+ headers: headersToRecord(headers),
212
+ }, (res) => {
213
+ res.setEncoding('utf8');
214
+ const status = res.statusCode ?? 0;
215
+ if (status < 200 || status >= 300) {
216
+ let body = '';
217
+ res.on('data', (chunk) => {
218
+ if (body.length < 4096)
219
+ body += chunk;
220
+ });
221
+ res.on('end', () => fail(new NtfyStreamHttpError(status, body)));
222
+ res.on('error', fail);
223
+ return;
224
+ }
225
+ res.on('data', (chunk) => {
226
+ state.queue = state.queue
227
+ .then(() => consumeNtfyJsonChunk(state, chunk, onMessage))
228
+ .catch(fail);
229
+ });
230
+ res.on('end', () => {
231
+ state.queue
232
+ .then(() => flushNtfyJsonBuffer(state, onMessage))
233
+ .then(finish, fail);
234
+ });
235
+ res.on('error', (error) => {
236
+ if (aborted)
237
+ finish();
238
+ else
239
+ fail(error);
240
+ });
241
+ });
242
+ signal?.addEventListener('abort', abort, { once: true });
243
+ req.on('error', (error) => {
244
+ if (aborted)
245
+ finish();
246
+ else
247
+ fail(error);
248
+ });
249
+ req.end();
250
+ });
251
+ }
252
+ function readNtfyJsonStreamWithHttp2(url, headers, onMessage, signal) {
253
+ if (signal?.aborted)
254
+ return Promise.resolve();
255
+ return new Promise((resolve, reject) => {
256
+ const parsed = new URL(url);
257
+ const client = http2Connect(parsed.origin);
258
+ const path = `${parsed.pathname}${parsed.search}`;
259
+ const state = { buffer: '', queue: Promise.resolve() };
260
+ let settled = false;
261
+ let aborted = false;
262
+ let status = 0;
263
+ let errorBody = '';
264
+ const settle = (callback) => {
265
+ if (settled)
266
+ return;
267
+ settled = true;
268
+ signal?.removeEventListener('abort', abort);
269
+ client.close();
270
+ callback();
271
+ };
272
+ const fail = (error) => settle(() => reject(error));
273
+ const finish = () => settle(resolve);
274
+ const abort = () => {
275
+ aborted = true;
276
+ stream.close();
277
+ finish();
278
+ };
279
+ const stream = client.request({
280
+ ':method': 'GET',
281
+ ':path': path,
282
+ ...headersToRecord(headers),
283
+ });
284
+ signal?.addEventListener('abort', abort, { once: true });
285
+ stream.setEncoding('utf8');
286
+ stream.on('response', (responseHeaders) => {
287
+ status = Number(responseHeaders[':status'] ?? 0);
288
+ });
289
+ stream.on('data', (chunk) => {
290
+ if (status !== 0 && (status < 200 || status >= 300)) {
291
+ if (errorBody.length < 4096)
292
+ errorBody += chunk;
293
+ return;
294
+ }
295
+ state.queue = state.queue
296
+ .then(() => consumeNtfyJsonChunk(state, chunk, onMessage))
297
+ .catch(fail);
298
+ });
299
+ stream.on('end', () => {
300
+ if (status < 200 || status >= 300) {
301
+ fail(new NtfyStreamHttpError(status, errorBody));
302
+ return;
303
+ }
304
+ state.queue
305
+ .then(() => flushNtfyJsonBuffer(state, onMessage))
306
+ .then(finish, fail);
307
+ });
308
+ stream.on('error', (error) => {
309
+ if (aborted)
310
+ finish();
311
+ else
312
+ fail(error);
313
+ });
314
+ client.on('error', (error) => {
315
+ if (aborted)
316
+ finish();
317
+ else
318
+ fail(error);
319
+ });
320
+ stream.end();
321
+ });
322
+ }
323
+ function headersToRecord(headers) {
324
+ const record = {};
325
+ headers.forEach((value, key) => {
326
+ record[key.toLowerCase()] = value;
327
+ });
328
+ return record;
329
+ }
330
+ async function consumeNtfyJsonChunk(state, chunk, onMessage) {
331
+ state.buffer += chunk;
332
+ const lines = state.buffer.split(/\r?\n/);
333
+ state.buffer = lines.pop() ?? '';
334
+ for (const line of lines) {
335
+ const trimmed = line.trim();
336
+ if (!trimmed)
337
+ continue;
338
+ await onMessage(JSON.parse(trimmed));
339
+ }
340
+ }
341
+ async function flushNtfyJsonBuffer(state, onMessage) {
342
+ const trimmed = state.buffer.trim();
343
+ state.buffer = '';
344
+ if (trimmed)
345
+ await onMessage(JSON.parse(trimmed));
346
+ }
347
+ function isFatalNtfyStreamError(error) {
348
+ return error instanceof NtfyStreamHttpError
349
+ && error.status >= 400
350
+ && error.status < 500
351
+ && error.status !== 408
352
+ && error.status !== 429;
353
+ }
354
+ function waitForNtfyReconnectDelay(signal, delayMs) {
355
+ if (signal?.aborted || delayMs <= 0)
356
+ return Promise.resolve();
357
+ return new Promise((resolve) => {
358
+ let timeout;
359
+ const done = () => {
360
+ clearTimeout(timeout);
361
+ signal?.removeEventListener('abort', abort);
362
+ resolve();
363
+ };
364
+ const abort = () => done();
365
+ timeout = setTimeout(done, delayMs);
366
+ signal?.addEventListener('abort', abort, { once: true });
367
+ });
368
+ }
369
+ function readRecord(value) {
370
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
371
+ return null;
372
+ return value;
373
+ }
374
+ function readCaseInsensitiveHeader(headers, name) {
375
+ const wanted = name.toLowerCase();
376
+ for (const [key, value] of Object.entries(headers)) {
377
+ if (key.toLowerCase() !== wanted)
378
+ continue;
379
+ return typeof value === 'string' ? value : undefined;
380
+ }
381
+ return undefined;
382
+ }
@@ -118,6 +118,7 @@ export declare class ProviderRegistry {
118
118
  * Explicit provider constraints do not fall through to other providers.
119
119
  */
120
120
  getForModel(modelId: string, provider?: string): LLMProvider;
121
+ private getExplicitOpenAIProviderForStaleCatalog;
121
122
  /** All registered model definitions. */
122
123
  listModels(): ModelDefinition[];
123
124
  getCostFromCatalog(modelId: string): {