@nextclaw/extension-sdk 0.3.23 → 0.3.25

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/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { ChannelCommandExecuteRequest, ChannelCommandExecuteResponse, ChannelCommandListRequest, ChannelCommandListResponse, ChannelCommandOption, ChannelCommandOptionType, ChannelCommandSpec, ChannelConfigGetRequest, ChannelConfigGetResponse, ChannelFileContent, ChannelImageContent, ChannelMessageContent, ChannelSubmittedAttachment, ChannelSubmittedMessage, ChannelTextContent, ExtensionCapabilities, ExtensionCapabilityHandler, ExtensionCapabilityPayload, ExtensionChannel, ExtensionChannelCommands, ExtensionChannelConfig, ExtensionChannels, ExtensionRequest, ExtensionRequestHandler, ExtensionRequestResponse, ExtensionTransportEnvelope, NextClawExtensionOptions, NextClawExtensionWebSocketLike } from "./types/extension-sdk.types.js";
1
+ import { ChannelCommandExecuteRequest, ChannelCommandExecuteResponse, ChannelCommandListRequest, ChannelCommandListResponse, ChannelCommandOption, ChannelCommandOptionType, ChannelCommandSpec, ChannelConfigGetRequest, ChannelConfigGetResponse, ChannelFileContent, ChannelImageContent, ChannelMessageContent, ChannelSubmittedAttachment, ChannelSubmittedMessage, ChannelTextContent, ExtensionCapabilities, ExtensionCapabilityHandler, ExtensionCapabilityPayload, ExtensionChannel, ExtensionChannelCommands, ExtensionChannelConfig, ExtensionChannels, ExtensionDiagnostics, ExtensionRequest, ExtensionRequestHandler, ExtensionRequestResponse, ExtensionTransportEnvelope, NextClawExtensionOptions, NextClawExtensionWebSocketLike } from "./types/extension-sdk.types.js";
2
2
  import { NextClawExtension } from "./services/extension-client.service.js";
3
3
  import { ChannelTypingController, ChannelTypingControllerOptions } from "./services/channel-typing-controller.service.js";
4
4
  import { BusChannelCreateContext, BusChannelExtensionDefinition, BusChannelInboundMessage, BusChannelMessageBus, BusChannelRuntime, ChannelExtensionContext, ChannelExtensionDefinition, ChannelSubmittedMessageInput, ExtensionChannelAdapter, ExtensionChannelController, startBusChannelExtension, startChannelExtension, warnNcpEventError } from "./services/extension-channel-controller.service.js";
5
- export { type BusChannelCreateContext, type BusChannelExtensionDefinition, type BusChannelInboundMessage, type BusChannelMessageBus, type BusChannelRuntime, type ChannelCommandExecuteRequest, type ChannelCommandExecuteResponse, type ChannelCommandListRequest, type ChannelCommandListResponse, type ChannelCommandOption, type ChannelCommandOptionType, type ChannelCommandSpec, type ChannelConfigGetRequest, type ChannelConfigGetResponse, type ChannelExtensionContext, type ChannelExtensionDefinition, type ChannelFileContent, type ChannelImageContent, type ChannelMessageContent, type ChannelSubmittedAttachment, type ChannelSubmittedMessage, type ChannelSubmittedMessageInput, type ChannelTextContent, ChannelTypingController, type ChannelTypingControllerOptions, type ExtensionCapabilities, type ExtensionCapabilityHandler, type ExtensionCapabilityPayload, type ExtensionChannel, type ExtensionChannelAdapter, type ExtensionChannelCommands, type ExtensionChannelConfig, ExtensionChannelController, type ExtensionChannels, type ExtensionRequest, type ExtensionRequestHandler, type ExtensionRequestResponse, type ExtensionTransportEnvelope, NextClawExtension, type NextClawExtensionOptions, type NextClawExtensionWebSocketLike, startBusChannelExtension, startChannelExtension, warnNcpEventError };
5
+ import { DIAGNOSTIC_CORRELATION_METADATA_KEY, DiagnosticErrorClassification, classifyDiagnosticError } from "@nextclaw/shared";
6
+ export { type BusChannelCreateContext, type BusChannelExtensionDefinition, type BusChannelInboundMessage, type BusChannelMessageBus, type BusChannelRuntime, type ChannelCommandExecuteRequest, type ChannelCommandExecuteResponse, type ChannelCommandListRequest, type ChannelCommandListResponse, type ChannelCommandOption, type ChannelCommandOptionType, type ChannelCommandSpec, type ChannelConfigGetRequest, type ChannelConfigGetResponse, type ChannelExtensionContext, type ChannelExtensionDefinition, type ChannelFileContent, type ChannelImageContent, type ChannelMessageContent, type ChannelSubmittedAttachment, type ChannelSubmittedMessage, type ChannelSubmittedMessageInput, type ChannelTextContent, ChannelTypingController, type ChannelTypingControllerOptions, DIAGNOSTIC_CORRELATION_METADATA_KEY, type DiagnosticErrorClassification, type ExtensionCapabilities, type ExtensionCapabilityHandler, type ExtensionCapabilityPayload, type ExtensionChannel, type ExtensionChannelAdapter, type ExtensionChannelCommands, type ExtensionChannelConfig, ExtensionChannelController, type ExtensionChannels, type ExtensionDiagnostics, type ExtensionRequest, type ExtensionRequestHandler, type ExtensionRequestResponse, type ExtensionTransportEnvelope, NextClawExtension, type NextClawExtensionOptions, type NextClawExtensionWebSocketLike, classifyDiagnosticError, startBusChannelExtension, startChannelExtension, warnNcpEventError };
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { NextClawExtension } from "./services/extension-client.service.js";
2
2
  import { ChannelTypingController } from "./services/channel-typing-controller.service.js";
3
3
  import { ExtensionChannelController, startBusChannelExtension, startChannelExtension, warnNcpEventError } from "./services/extension-channel-controller.service.js";
4
- export { ChannelTypingController, ExtensionChannelController, NextClawExtension, startBusChannelExtension, startChannelExtension, warnNcpEventError };
4
+ import { DIAGNOSTIC_CORRELATION_METADATA_KEY, classifyDiagnosticError } from "@nextclaw/shared";
5
+ export { ChannelTypingController, DIAGNOSTIC_CORRELATION_METADATA_KEY, ExtensionChannelController, NextClawExtension, classifyDiagnosticError, startBusChannelExtension, startChannelExtension, warnNcpEventError };
@@ -1,4 +1,4 @@
1
- import { ChannelSubmittedAttachment, ChannelSubmittedMessage, ExtensionChannel, NextClawExtensionOptions } from "../types/extension-sdk.types.js";
1
+ import { ChannelSubmittedAttachment, ChannelSubmittedMessage, ExtensionChannel, ExtensionDiagnostics, NextClawExtensionOptions } from "../types/extension-sdk.types.js";
2
2
  import { NcpEndpointEvent } from "@nextclaw/ncp";
3
3
 
4
4
  //#region src/services/extension-channel-controller.service.d.ts
@@ -53,6 +53,7 @@ type BusChannelCreateContext<TConfig, TBus = BusChannelMessageBus> = {
53
53
  config: TConfig;
54
54
  bus: TBus;
55
55
  channel: ExtensionChannel;
56
+ diagnostics: ExtensionDiagnostics;
56
57
  };
57
58
  type BusChannelExtensionDefinition<TConfig, TBus = BusChannelMessageBus> = {
58
59
  channelId: string;
@@ -69,6 +70,7 @@ type ExtensionChannelControllerOptions<TConfig, TInbound> = {
69
70
  };
70
71
  type ChannelExtensionContext = {
71
72
  channel: ExtensionChannel;
73
+ diagnostics: ExtensionDiagnostics;
72
74
  };
73
75
  type ChannelExtensionDefinition<TConfig, TInbound> = {
74
76
  channelId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"extension-channel-controller.service.d.ts","names":[],"sources":["../../src/services/extension-channel-controller.service.ts"],"mappings":"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"}
1
+ {"version":3,"file":"extension-channel-controller.service.d.ts","names":[],"sources":["../../src/services/extension-channel-controller.service.ts"],"mappings":";;;;KAiDY,uBAAA;EACV,SAAA,GAAY,MAAA,EAAQ,OAAA,KAAY,OAAA;EAChC,KAAA,QAAa,OAAA;EACb,IAAA,QAAY,OAAA;EACZ,SAAA,GAAY,OAAA,GAAU,OAAA,EAAS,QAAA,YAAoB,OAAA;EACnD,YAAA,GAAe,KAAA,EAAO,gBAAA,KAAqB,OAAA;EAC3C,gBAAA,GAAmB,MAAA;IACjB,EAAA;IACA,IAAA;IACA,SAAA;IACA,OAAA;IACA,KAAA;IACA,QAAA,GAAW,MAAA;EAAA,MACP,OAAA;AAAA;AAAA,KAGI,4BAAA,GAA+B,IAAA,CAAK,uBAAA;AAAA,KAEpC,wBAAA;EACV,OAAA;EACA,QAAA;EACA,MAAA;EACA,OAAA;EACA,WAAA,GAAc,0BAAA;EACd,QAAA,GAAW,MAAA;AAAA;AAAA,KAGD,oBAAA;EACV,cAAA,GAAiB,OAAA,EAAS,wBAAA,KAA6B,OAAA;AAAA;AAAA,KAG7C,iBAAA;EACV,KAAA,QAAa,OAAA;EACb,IAAA,QAAY,OAAA;EACZ,oBAAA,IAAwB,OAAA;IACtB,OAAA;IACA,MAAA;IACA,OAAA;IACA,OAAA;IACA,KAAA;IACA,QAAA,EAAU,MAAA;EAAA,MACN,OAAA;EACN,IAAA,GAAO,OAAA;IACL,OAAA;IACA,MAAA;IACA,OAAA;IACA,OAAA;IACA,KAAA;IACA,QAAA,EAAU,MAAA;EAAA,MACN,OAAA;AAAA;AAAA,KAGI,uBAAA,iBAAwC,oBAAA;EAClD,MAAA,EAAQ,OAAA;EACR,GAAA,EAAK,IAAA;EACL,OAAA,EAAS,gBAAA;EACT,WAAA,EAAa,oBAAA;AAAA;AAAA,KAGH,6BAAA,iBAA8C,oBAAA;EACxD,SAAA;EACA,aAAA,GACE,OAAA,EAAS,uBAAA,CAAwB,OAAA,EAAS,IAAA,MACvC,iBAAA,GAAoB,OAAA,CAAQ,iBAAA;EACjC,UAAA,GAAa,6BAAA,CAA8B,wBAAA;EAC3C,mBAAA,IAAuB,KAAA,qBAA0B,OAAA;AAAA;AAAA,KAGvC,6BAAA,cACT,OAAA,EAAS,QAAA,KAAa,4BAAA,GAA+B,OAAA,CAAQ,4BAAA;AAAA,KAE3D,iCAAA;EACH,OAAA,EAAS,gBAAA;EACT,OAAA,EAAS,uBAAA,CAAwB,OAAA,EAAS,QAAA;EAC1C,UAAA,EAAY,6BAAA,CAA8B,QAAA;EAC1C,eAAA,IAAmB,KAAA,WAAgB,KAAA,EAAO,gBAAA,YAA4B,OAAA;AAAA;AAAA,KAG5D,uBAAA;EACV,OAAA,EAAS,gBAAA;EACT,WAAA,EAAa,oBAAA;AAAA;AAAA,KAGH,0BAAA;EACV,SAAA;EACA,aAAA,GACE,OAAA,EAAS,uBAAA,KACN,uBAAA,CAAwB,OAAA,EAAS,QAAA,IAAY,OAAA,CAAQ,uBAAA,CAAwB,OAAA,EAAS,QAAA;EAC3F,UAAA,EAAY,6BAAA,CAA8B,QAAA;EAC1C,oBAAA,IAAwB,OAAA,EAAS,uBAAA;EACjC,eAAA,IAAmB,KAAA,WAAgB,KAAA,EAAO,gBAAA,YAA4B,OAAA;AAAA;AAAA,cAiF3D,0BAAA;EAAA,iBAKQ,OAAA;EAAA,iBAJF,QAAA;EAAA,QACT,OAAA;cAGW,OAAA,EAAS,iCAAA,CAAkC,OAAA,EAAS,QAAA;EAGvE,KAAA,QAAkB,OAAA;EAalB,IAAA,QAAiB,OAAA;EASjB,gBAAA,GAA0B,MAAA;IACxB,EAAA;IACA,IAAA;IACA,SAAA;IACA,OAAA;IACA,KAAA;IACA,QAAA,GAAW,MAAA;EAAA,MACT,OAAA;IAAU,QAAA;EAAA;EAAA,QAKN,WAAA;EAAA,QAUA,aAAA;EAAA,QAIA,YAAA;EAAA,QAQA,aAAA;AAAA;AAAA,iBAyHY,qBAAA,mBAAA,CACpB,UAAA,EAAY,0BAAA,CAA2B,OAAA,EAAS,QAAA,GAChD,OAAA,GAAS,wBAAA,GACR,OAAA;AAAA,iBAiCmB,wBAAA,iBAAyC,oBAAA,CAAA,CAC7D,UAAA,EAAY,6BAAA,CAA8B,OAAA,EAAS,IAAA,GACnD,OAAA,GAAS,wBAAA,GACR,OAAA;AAAA,iBAea,iBAAA,CAAkB,SAAA,YAAqB,KAAA"}
@@ -128,9 +128,13 @@ var ExtensionChannelController = class {
128
128
  var BusChannelAdapter = class {
129
129
  channel = null;
130
130
  messageHandler = null;
131
- constructor(definition, extensionChannel) {
132
- this.definition = definition;
133
- this.extensionChannel = extensionChannel;
131
+ definition;
132
+ diagnostics;
133
+ extensionChannel;
134
+ constructor(params) {
135
+ this.definition = params.definition;
136
+ this.diagnostics = params.diagnostics;
137
+ this.extensionChannel = params.extensionChannel;
134
138
  }
135
139
  configure = async (config) => {
136
140
  await this.stop();
@@ -139,7 +143,8 @@ var BusChannelAdapter = class {
139
143
  bus: { publishInbound: async (message) => {
140
144
  await this.messageHandler?.(message);
141
145
  } },
142
- channel: this.extensionChannel
146
+ channel: this.extensionChannel,
147
+ diagnostics: this.diagnostics
143
148
  });
144
149
  };
145
150
  start = async () => {
@@ -203,11 +208,17 @@ async function startChannelExtension(definition, options = {}) {
203
208
  const channel = extension.channels.use(definition.channelId);
204
209
  const controller = new ExtensionChannelController({
205
210
  channel,
206
- adapter: new LazyExtensionChannelAdapter(() => definition.createAdapter({ channel })),
211
+ adapter: new LazyExtensionChannelAdapter(() => definition.createAdapter({
212
+ channel,
213
+ diagnostics: extension.diagnostics
214
+ })),
207
215
  mapInbound: definition.mapInbound,
208
216
  onNcpEventError: definition.onNcpEventError
209
217
  });
210
- if (definition.createAuthCapability) extension.capabilities.provide("channel.auth", definition.createAuthCapability({ channel }));
218
+ if (definition.createAuthCapability) extension.capabilities.provide("channel.auth", definition.createAuthCapability({
219
+ channel,
220
+ diagnostics: extension.diagnostics
221
+ }));
211
222
  extension.capabilities.provideHandler("channel.outbound.sendText", async (payload) => await controller.sendOutboundText({
212
223
  to: readRequiredPayloadString(payload.to, "to"),
213
224
  text: readRequiredPayloadString(payload.text, "text"),
@@ -222,7 +233,11 @@ async function startChannelExtension(definition, options = {}) {
222
233
  async function startBusChannelExtension(definition, options = {}) {
223
234
  await startChannelExtension({
224
235
  channelId: definition.channelId,
225
- createAdapter: ({ channel }) => new BusChannelAdapter(definition, channel),
236
+ createAdapter: ({ channel, diagnostics }) => new BusChannelAdapter({
237
+ definition,
238
+ diagnostics,
239
+ extensionChannel: channel
240
+ }),
226
241
  mapInbound: definition.mapInbound ?? toSubmittedTextMessage
227
242
  }, options);
228
243
  }
@@ -1 +1 @@
1
- {"version":3,"file":"extension-channel-controller.service.js","names":[],"sources":["../../src/services/extension-channel-controller.service.ts"],"sourcesContent":["import { NcpEventType, type NcpEndpointEvent, type NcpMessagePart } from \"@nextclaw/ncp\";\nimport type {\n ChannelSubmittedMessage,\n ChannelSubmittedAttachment,\n ExtensionChannel,\n NextClawExtensionOptions,\n} from \"../types/extension-sdk.types.js\";\nimport { NextClawExtension } from \"./extension-client.service.js\";\n\nfunction readRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : {};\n}\n\nconst AGENT_ROUTE_KINDS = new Set([\"direct\", \"group\", \"channel\", \"thread\"]);\n\nfunction parseSessionDeliveryRoute(sessionId: string): {\n channel?: string;\n chatId?: string;\n accountId?: string;\n} {\n const parts = sessionId.trim().split(\":\");\n if (parts[0] !== \"agent\" || parts.length < 5) {\n return {};\n }\n const hasAccount = !AGENT_ROUTE_KINDS.has(parts[3]);\n const kind = parts[hasAccount ? 4 : 3];\n if (!AGENT_ROUTE_KINDS.has(kind ?? \"\")) {\n return {};\n }\n return {\n channel: parts[2],\n ...(hasAccount ? { accountId: parts[3] } : {}),\n chatId: parts.slice(hasAccount ? 5 : 4).join(\":\"),\n };\n}\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction renderMessageText(parts: readonly NcpMessagePart[]): string {\n return (parts as Array<{ type: string; text?: string }>).map((part) =>\n (part.type === \"text\" || part.type === \"rich-text\") && part.text ? part.text : \"\",\n ).filter(Boolean).join(\"\\n\\n\");\n}\n\nexport type ExtensionChannelAdapter<TConfig, TInbound> = {\n configure: (config: TConfig) => Promise<void>;\n start: () => Promise<void>;\n stop: () => Promise<void>;\n onMessage: (handler: (message: TInbound) => void | Promise<void>) => () => void;\n sendNcpEvent: (event: NcpEndpointEvent) => Promise<void>;\n sendOutboundText: (params: {\n to: string;\n text: string;\n accountId?: string | null;\n replyTo?: string | null;\n media?: string[];\n metadata?: Record<string, unknown>;\n }) => Promise<void>;\n};\n\nexport type ChannelSubmittedMessageInput = Omit<ChannelSubmittedMessage, \"channelId\">;\n\nexport type BusChannelInboundMessage = {\n channel: string;\n senderId: string;\n chatId: string;\n content: string;\n attachments?: ChannelSubmittedAttachment[];\n metadata?: Record<string, unknown>;\n};\n\nexport type BusChannelMessageBus = {\n publishInbound: (message: BusChannelInboundMessage) => Promise<void>;\n};\n\nexport type BusChannelRuntime = {\n start: () => Promise<void>;\n stop: () => Promise<void>;\n handleControlMessage?: (message: {\n channel: string;\n chatId: string;\n content: string;\n replyTo?: string | null;\n media: string[];\n metadata: Record<string, unknown>;\n }) => Promise<boolean>;\n send: (message: {\n channel: string;\n chatId: string;\n content: string;\n replyTo?: string | null;\n media: string[];\n metadata: Record<string, unknown>;\n }) => Promise<void>;\n};\n\nexport type BusChannelCreateContext<TConfig, TBus = BusChannelMessageBus> = {\n config: TConfig;\n bus: TBus;\n channel: ExtensionChannel;\n};\n\nexport type BusChannelExtensionDefinition<TConfig, TBus = BusChannelMessageBus> = {\n channelId: string;\n createChannel: (\n context: BusChannelCreateContext<TConfig, TBus>,\n ) => BusChannelRuntime | Promise<BusChannelRuntime>;\n mapInbound?: ExtensionChannelInboundMapper<BusChannelInboundMessage>;\n onChannelStartError?: (error: unknown) => void | Promise<void>;\n};\n\nexport type ExtensionChannelInboundMapper<TInbound> =\n (message: TInbound) => ChannelSubmittedMessageInput | Promise<ChannelSubmittedMessageInput>;\n\ntype ExtensionChannelControllerOptions<TConfig, TInbound> = {\n channel: ExtensionChannel;\n adapter: ExtensionChannelAdapter<TConfig, TInbound>;\n mapInbound: ExtensionChannelInboundMapper<TInbound>;\n onNcpEventError?: (error: unknown, event: NcpEndpointEvent) => void | Promise<void>;\n};\n\nexport type ChannelExtensionContext = {\n channel: ExtensionChannel;\n};\n\nexport type ChannelExtensionDefinition<TConfig, TInbound> = {\n channelId: string;\n createAdapter: (\n context: ChannelExtensionContext,\n ) => ExtensionChannelAdapter<TConfig, TInbound> | Promise<ExtensionChannelAdapter<TConfig, TInbound>>;\n mapInbound: ExtensionChannelInboundMapper<TInbound>;\n createAuthCapability?: (context: ChannelExtensionContext) => object;\n onNcpEventError?: (error: unknown, event: NcpEndpointEvent) => void | Promise<void>;\n};\n\nfunction isChannelEnabled(config: unknown): boolean {\n return Boolean(\n config &&\n typeof config === \"object\" &&\n !Array.isArray(config) &&\n (config as { enabled?: unknown }).enabled === true\n );\n}\n\nclass LazyExtensionChannelAdapter<TConfig, TInbound> implements ExtensionChannelAdapter<TConfig, TInbound> {\n private adapterCleanup: (() => void) | null = null;\n private adapterPromise: Promise<ExtensionChannelAdapter<TConfig, TInbound>> | null = null;\n private messageHandler: ((message: TInbound) => void | Promise<void>) | null = null;\n\n constructor(\n private readonly createAdapter: () =>\n ExtensionChannelAdapter<TConfig, TInbound> | Promise<ExtensionChannelAdapter<TConfig, TInbound>>,\n ) {}\n\n configure = async (config: TConfig): Promise<void> => {\n await (await this.getAdapter()).configure(config);\n };\n\n start = async (): Promise<void> => {\n await (await this.getAdapter()).start();\n };\n\n stop = async (): Promise<void> => {\n if (this.adapterPromise) {\n await (await this.adapterPromise).stop();\n }\n };\n\n onMessage = (handler: (message: TInbound) => void | Promise<void>): (() => void) => {\n this.messageHandler = handler;\n if (this.adapterPromise) {\n void this.adapterPromise.then((adapter) => this.attachMessageHandler(adapter)).catch(() => undefined);\n }\n return () => {\n if (this.messageHandler === handler) {\n this.messageHandler = null;\n this.adapterCleanup?.();\n this.adapterCleanup = null;\n }\n };\n };\n\n sendNcpEvent = async (event: NcpEndpointEvent): Promise<void> => {\n if (this.adapterPromise) {\n await (await this.adapterPromise).sendNcpEvent(event);\n }\n };\n\n sendOutboundText = async (params: {\n to: string;\n text: string;\n accountId?: string | null;\n replyTo?: string | null;\n media?: string[];\n metadata?: Record<string, unknown>;\n }): Promise<void> => {\n await (await this.getAdapter()).sendOutboundText(params);\n };\n\n private getAdapter = async (): Promise<ExtensionChannelAdapter<TConfig, TInbound>> => {\n this.adapterPromise ??= Promise.resolve(this.createAdapter()).then((adapter) => {\n this.attachMessageHandler(adapter);\n return adapter;\n });\n return await this.adapterPromise;\n };\n\n private attachMessageHandler = (adapter: ExtensionChannelAdapter<TConfig, TInbound>): void => {\n this.adapterCleanup?.();\n this.adapterCleanup = this.messageHandler ? adapter.onMessage(this.messageHandler) : null;\n };\n}\n\nexport class ExtensionChannelController<TConfig, TInbound> {\n private readonly cleanups: Array<() => void> = [];\n private started = false;\n\n constructor(\n private readonly options: ExtensionChannelControllerOptions<TConfig, TInbound>,\n ) {}\n\n start = async (): Promise<void> => {\n if (this.started) {\n return;\n }\n this.started = true;\n this.cleanups.push(this.options.adapter.onMessage(this.submitMessage));\n this.cleanups.push(this.options.channel.onNcpEvent(this.sendNcpEvent));\n this.cleanups.push(this.options.channel.config.onChange(async () => {\n await this.applyConfig();\n }));\n await this.applyConfig();\n };\n\n stop = async (): Promise<void> => {\n if (!this.started) {\n return;\n }\n this.started = false;\n this.drainCleanups();\n await this.options.adapter.stop();\n };\n\n sendOutboundText = async (params: {\n to: string;\n text: string;\n accountId?: string | null;\n replyTo?: string | null;\n media?: string[];\n metadata?: Record<string, unknown>;\n }): Promise<{ accepted: true }> => {\n await this.options.adapter.sendOutboundText(params);\n return { accepted: true };\n };\n\n private applyConfig = async (): Promise<void> => {\n const config = await this.options.channel.config.get<TConfig>();\n if (!isChannelEnabled(config)) {\n await this.options.adapter.stop();\n return;\n }\n await this.options.adapter.configure(config);\n await this.options.adapter.start();\n };\n\n private submitMessage = async (message: TInbound): Promise<void> => {\n await this.options.channel.submitMessage(await this.options.mapInbound(message));\n };\n\n private sendNcpEvent = async (event: NcpEndpointEvent): Promise<void> => {\n try {\n await this.options.adapter.sendNcpEvent(event);\n } catch (error) {\n await this.options.onNcpEventError?.(error, event);\n }\n };\n\n private drainCleanups = (): void => {\n for (const cleanup of this.cleanups.splice(0).reverse()) {\n cleanup();\n }\n };\n}\n\nclass BusChannelAdapter<TConfig, TBus> implements ExtensionChannelAdapter<TConfig, BusChannelInboundMessage> {\n private channel: BusChannelRuntime | null = null;\n private messageHandler: ((message: BusChannelInboundMessage) => void | Promise<void>) | null = null;\n\n constructor(\n private readonly definition: BusChannelExtensionDefinition<TConfig, TBus>,\n private readonly extensionChannel: ExtensionChannel,\n ) {}\n\n configure = async (config: TConfig): Promise<void> => {\n await this.stop();\n this.channel = await this.definition.createChannel({\n config,\n bus: {\n publishInbound: async (message: BusChannelInboundMessage) => {\n await this.messageHandler?.(message);\n },\n } as TBus,\n channel: this.extensionChannel,\n });\n };\n\n start = async (): Promise<void> => {\n const channel = this.channel;\n if (!channel) {\n return;\n }\n void channel.start().catch(async (error: unknown) => {\n await this.definition.onChannelStartError?.(error);\n });\n };\n\n stop = async (): Promise<void> => {\n const channel = this.channel;\n this.channel = null;\n await channel?.stop();\n };\n\n onMessage = (\n handler: (message: BusChannelInboundMessage) => void | Promise<void>,\n ): (() => void) => {\n this.messageHandler = handler;\n return () => {\n if (this.messageHandler === handler) {\n this.messageHandler = null;\n }\n };\n };\n\n sendNcpEvent = async (event: NcpEndpointEvent): Promise<void> => {\n if (event.type !== NcpEventType.MessageCompleted) {\n return;\n }\n const metadata = readRecord(event.payload.message.metadata);\n const sessionRoute = parseSessionDeliveryRoute(event.payload.message.sessionId);\n const eventChannel = readString(metadata.channel) ?? readString(metadata.channelId) ?? sessionRoute.channel;\n if (eventChannel !== this.definition.channelId) {\n return;\n }\n const chatId = readString(metadata.chatId) ?? readString(metadata.chat_id) ?? sessionRoute.chatId;\n const content = renderMessageText(event.payload.message.parts).trim();\n if (!chatId || !content) {\n return;\n }\n const replyTo = readString(metadata.message_id);\n await this.channel?.send({\n channel: this.definition.channelId,\n chatId,\n content,\n ...(replyTo ? { replyTo } : {}),\n media: [],\n metadata: {\n ...metadata,\n ...(sessionRoute.accountId && !metadata.accountId ? { accountId: sessionRoute.accountId } : {}),\n },\n });\n };\n\n sendOutboundText = async (params: {\n to: string;\n text: string;\n accountId?: string | null;\n replyTo?: string | null;\n media?: string[];\n metadata?: Record<string, unknown>;\n }): Promise<void> => {\n const { accountId, media, metadata, replyTo, text, to } = params;\n const outbound = {\n channel: this.definition.channelId,\n chatId: to,\n content: text,\n ...(replyTo !== undefined ? { replyTo } : {}),\n media: media ?? [],\n metadata: {\n ...(metadata ?? {}),\n ...(accountId ? { accountId } : {}),\n },\n };\n if (await this.channel?.handleControlMessage?.(outbound)) {\n return;\n }\n await this.channel?.send(outbound);\n };\n}\n\nexport async function startChannelExtension<TConfig, TInbound>(\n definition: ChannelExtensionDefinition<TConfig, TInbound>,\n options: NextClawExtensionOptions = {},\n): Promise<void> {\n const extension = new NextClawExtension(options);\n const channel = extension.channels.use(definition.channelId);\n const adapter = new LazyExtensionChannelAdapter(() => definition.createAdapter({ channel }));\n const controller = new ExtensionChannelController({\n channel,\n adapter,\n mapInbound: definition.mapInbound,\n onNcpEventError: definition.onNcpEventError,\n });\n if (definition.createAuthCapability) {\n extension.capabilities.provide(\"channel.auth\", definition.createAuthCapability({ channel }));\n }\n extension.capabilities.provideHandler(\"channel.outbound.sendText\", async (payload) =>\n await controller.sendOutboundText({\n to: readRequiredPayloadString(payload.to, \"to\"),\n text: readRequiredPayloadString(payload.text, \"text\"),\n accountId: readOptionalPayloadString(payload.accountId),\n replyTo: readOptionalPayloadString(payload.replyTo),\n media: readStringArray(payload.media),\n metadata: readOptionalRecord(payload.metadata),\n }),\n );\n await controller.start();\n await extension.ready();\n}\n\nexport async function startBusChannelExtension<TConfig, TBus = BusChannelMessageBus>(\n definition: BusChannelExtensionDefinition<TConfig, TBus>,\n options: NextClawExtensionOptions = {},\n): Promise<void> {\n await startChannelExtension(\n {\n channelId: definition.channelId,\n createAdapter: ({ channel }) => new BusChannelAdapter(definition, channel),\n mapInbound: definition.mapInbound ?? toSubmittedTextMessage,\n },\n options,\n );\n}\n\nexport function warnNcpEventError(channelId: string): (error: unknown) => void {\n return (error) => {\n const message = error instanceof Error ? error.message : String(error);\n console.warn(`[${channelId}] failed to send NCP event: ${message}`);\n };\n}\n\nfunction readRequiredPayloadString(value: unknown, name: string): string {\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`${name} is required`);\n }\n return value.trim();\n}\n\nfunction readOptionalPayloadString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction readStringArray(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) {\n return undefined;\n }\n return value.filter((item): item is string => typeof item === \"string\");\n}\n\nfunction readOptionalRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : undefined;\n}\n\nfunction toSubmittedTextMessage(message: BusChannelInboundMessage): ChannelSubmittedMessageInput {\n return {\n conversationId: message.chatId,\n senderId: message.senderId,\n content: {\n type: \"text\",\n text: message.content,\n },\n ...(message.attachments ? { attachments: message.attachments } : {}),\n ...(message.metadata ? { metadata: message.metadata } : {}),\n };\n}\n"],"mappings":";;;AASA,SAAS,WAAW,OAAyC;AAC3D,QAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM,GAC9D,QACA,EAAE;;AAGR,MAAM,oBAAoB,IAAI,IAAI;CAAC;CAAU;CAAS;CAAW;CAAS,CAAC;AAE3E,SAAS,0BAA0B,WAIjC;CACA,MAAM,QAAQ,UAAU,MAAM,CAAC,MAAM,IAAI;AACzC,KAAI,MAAM,OAAO,WAAW,MAAM,SAAS,EACzC,QAAO,EAAE;CAEX,MAAM,aAAa,CAAC,kBAAkB,IAAI,MAAM,GAAG;CACnD,MAAM,OAAO,MAAM,aAAa,IAAI;AACpC,KAAI,CAAC,kBAAkB,IAAI,QAAQ,GAAG,CACpC,QAAO,EAAE;AAEX,QAAO;EACL,SAAS,MAAM;EACf,GAAI,aAAa,EAAE,WAAW,MAAM,IAAI,GAAG,EAAE;EAC7C,QAAQ,MAAM,MAAM,aAAa,IAAI,EAAE,CAAC,KAAK,IAAI;EAClD;;AAGH,SAAS,WAAW,OAAoC;AACtD,QAAO,OAAO,UAAU,YAAY,MAAM,MAAM,GAAG,MAAM,MAAM,GAAG,KAAA;;AAGpE,SAAS,kBAAkB,OAA0C;AACnE,QAAQ,MAAiD,KAAK,UAC3D,KAAK,SAAS,UAAU,KAAK,SAAS,gBAAgB,KAAK,OAAO,KAAK,OAAO,GAChF,CAAC,OAAO,QAAQ,CAAC,KAAK,OAAO;;AA8FhC,SAAS,iBAAiB,QAA0B;AAClD,QAAO,QACL,UACA,OAAO,WAAW,YAClB,CAAC,MAAM,QAAQ,OAAO,IACrB,OAAiC,YAAY,KAC/C;;AAGH,IAAM,8BAAN,MAA2G;CACzG,iBAA8C;CAC9C,iBAAqF;CACrF,iBAA+E;CAE/E,YACE,eAEA;AAFiB,OAAA,gBAAA;;CAInB,YAAY,OAAO,WAAmC;AACpD,SAAO,MAAM,KAAK,YAAY,EAAE,UAAU,OAAO;;CAGnD,QAAQ,YAA2B;AACjC,SAAO,MAAM,KAAK,YAAY,EAAE,OAAO;;CAGzC,OAAO,YAA2B;AAChC,MAAI,KAAK,eACP,QAAO,MAAM,KAAK,gBAAgB,MAAM;;CAI5C,aAAa,YAAuE;AAClF,OAAK,iBAAiB;AACtB,MAAI,KAAK,eACF,MAAK,eAAe,MAAM,YAAY,KAAK,qBAAqB,QAAQ,CAAC,CAAC,YAAY,KAAA,EAAU;AAEvG,eAAa;AACX,OAAI,KAAK,mBAAmB,SAAS;AACnC,SAAK,iBAAiB;AACtB,SAAK,kBAAkB;AACvB,SAAK,iBAAiB;;;;CAK5B,eAAe,OAAO,UAA2C;AAC/D,MAAI,KAAK,eACP,QAAO,MAAM,KAAK,gBAAgB,aAAa,MAAM;;CAIzD,mBAAmB,OAAO,WAOL;AACnB,SAAO,MAAM,KAAK,YAAY,EAAE,iBAAiB,OAAO;;CAG1D,aAAqB,YAAiE;AACpF,OAAK,mBAAmB,QAAQ,QAAQ,KAAK,eAAe,CAAC,CAAC,MAAM,YAAY;AAC9E,QAAK,qBAAqB,QAAQ;AAClC,UAAO;IACP;AACF,SAAO,MAAM,KAAK;;CAGpB,wBAAgC,YAA8D;AAC5F,OAAK,kBAAkB;AACvB,OAAK,iBAAiB,KAAK,iBAAiB,QAAQ,UAAU,KAAK,eAAe,GAAG;;;AAIzF,IAAa,6BAAb,MAA2D;CACzD,WAA+C,EAAE;CACjD,UAAkB;CAElB,YACE,SACA;AADiB,OAAA,UAAA;;CAGnB,QAAQ,YAA2B;AACjC,MAAI,KAAK,QACP;AAEF,OAAK,UAAU;AACf,OAAK,SAAS,KAAK,KAAK,QAAQ,QAAQ,UAAU,KAAK,cAAc,CAAC;AACtE,OAAK,SAAS,KAAK,KAAK,QAAQ,QAAQ,WAAW,KAAK,aAAa,CAAC;AACtE,OAAK,SAAS,KAAK,KAAK,QAAQ,QAAQ,OAAO,SAAS,YAAY;AAClE,SAAM,KAAK,aAAa;IACxB,CAAC;AACH,QAAM,KAAK,aAAa;;CAG1B,OAAO,YAA2B;AAChC,MAAI,CAAC,KAAK,QACR;AAEF,OAAK,UAAU;AACf,OAAK,eAAe;AACpB,QAAM,KAAK,QAAQ,QAAQ,MAAM;;CAGnC,mBAAmB,OAAO,WAOS;AACjC,QAAM,KAAK,QAAQ,QAAQ,iBAAiB,OAAO;AACnD,SAAO,EAAE,UAAU,MAAM;;CAG3B,cAAsB,YAA2B;EAC/C,MAAM,SAAS,MAAM,KAAK,QAAQ,QAAQ,OAAO,KAAc;AAC/D,MAAI,CAAC,iBAAiB,OAAO,EAAE;AAC7B,SAAM,KAAK,QAAQ,QAAQ,MAAM;AACjC;;AAEF,QAAM,KAAK,QAAQ,QAAQ,UAAU,OAAO;AAC5C,QAAM,KAAK,QAAQ,QAAQ,OAAO;;CAGpC,gBAAwB,OAAO,YAAqC;AAClE,QAAM,KAAK,QAAQ,QAAQ,cAAc,MAAM,KAAK,QAAQ,WAAW,QAAQ,CAAC;;CAGlF,eAAuB,OAAO,UAA2C;AACvE,MAAI;AACF,SAAM,KAAK,QAAQ,QAAQ,aAAa,MAAM;WACvC,OAAO;AACd,SAAM,KAAK,QAAQ,kBAAkB,OAAO,MAAM;;;CAItD,sBAAoC;AAClC,OAAK,MAAM,WAAW,KAAK,SAAS,OAAO,EAAE,CAAC,SAAS,CACrD,UAAS;;;AAKf,IAAM,oBAAN,MAA6G;CAC3G,UAA4C;CAC5C,iBAA+F;CAE/F,YACE,YACA,kBACA;AAFiB,OAAA,aAAA;AACA,OAAA,mBAAA;;CAGnB,YAAY,OAAO,WAAmC;AACpD,QAAM,KAAK,MAAM;AACjB,OAAK,UAAU,MAAM,KAAK,WAAW,cAAc;GACjD;GACA,KAAK,EACH,gBAAgB,OAAO,YAAsC;AAC3D,UAAM,KAAK,iBAAiB,QAAQ;MAEvC;GACD,SAAS,KAAK;GACf,CAAC;;CAGJ,QAAQ,YAA2B;EACjC,MAAM,UAAU,KAAK;AACrB,MAAI,CAAC,QACH;AAEG,UAAQ,OAAO,CAAC,MAAM,OAAO,UAAmB;AACnD,SAAM,KAAK,WAAW,sBAAsB,MAAM;IAClD;;CAGJ,OAAO,YAA2B;EAChC,MAAM,UAAU,KAAK;AACrB,OAAK,UAAU;AACf,QAAM,SAAS,MAAM;;CAGvB,aACE,YACiB;AACjB,OAAK,iBAAiB;AACtB,eAAa;AACX,OAAI,KAAK,mBAAmB,QAC1B,MAAK,iBAAiB;;;CAK5B,eAAe,OAAO,UAA2C;AAC/D,MAAI,MAAM,SAAS,aAAa,iBAC9B;EAEF,MAAM,WAAW,WAAW,MAAM,QAAQ,QAAQ,SAAS;EAC3D,MAAM,eAAe,0BAA0B,MAAM,QAAQ,QAAQ,UAAU;AAE/E,OADqB,WAAW,SAAS,QAAQ,IAAI,WAAW,SAAS,UAAU,IAAI,aAAa,aAC/E,KAAK,WAAW,UACnC;EAEF,MAAM,SAAS,WAAW,SAAS,OAAO,IAAI,WAAW,SAAS,QAAQ,IAAI,aAAa;EAC3F,MAAM,UAAU,kBAAkB,MAAM,QAAQ,QAAQ,MAAM,CAAC,MAAM;AACrE,MAAI,CAAC,UAAU,CAAC,QACd;EAEF,MAAM,UAAU,WAAW,SAAS,WAAW;AAC/C,QAAM,KAAK,SAAS,KAAK;GACvB,SAAS,KAAK,WAAW;GACzB;GACA;GACA,GAAI,UAAU,EAAE,SAAS,GAAG,EAAE;GAC9B,OAAO,EAAE;GACT,UAAU;IACR,GAAG;IACH,GAAI,aAAa,aAAa,CAAC,SAAS,YAAY,EAAE,WAAW,aAAa,WAAW,GAAG,EAAE;IAC/F;GACF,CAAC;;CAGJ,mBAAmB,OAAO,WAOL;EACnB,MAAM,EAAE,WAAW,OAAO,UAAU,SAAS,MAAM,OAAO;EAC1D,MAAM,WAAW;GACf,SAAS,KAAK,WAAW;GACzB,QAAQ;GACR,SAAS;GACT,GAAI,YAAY,KAAA,IAAY,EAAE,SAAS,GAAG,EAAE;GAC5C,OAAO,SAAS,EAAE;GAClB,UAAU;IACR,GAAI,YAAY,EAAE;IAClB,GAAI,YAAY,EAAE,WAAW,GAAG,EAAE;IACnC;GACF;AACD,MAAI,MAAM,KAAK,SAAS,uBAAuB,SAAS,CACtD;AAEF,QAAM,KAAK,SAAS,KAAK,SAAS;;;AAItC,eAAsB,sBACpB,YACA,UAAoC,EAAE,EACvB;CACf,MAAM,YAAY,IAAI,kBAAkB,QAAQ;CAChD,MAAM,UAAU,UAAU,SAAS,IAAI,WAAW,UAAU;CAE5D,MAAM,aAAa,IAAI,2BAA2B;EAChD;EACA,SAHc,IAAI,kCAAkC,WAAW,cAAc,EAAE,SAAS,CAAC,CAAC;EAI1F,YAAY,WAAW;EACvB,iBAAiB,WAAW;EAC7B,CAAC;AACF,KAAI,WAAW,qBACb,WAAU,aAAa,QAAQ,gBAAgB,WAAW,qBAAqB,EAAE,SAAS,CAAC,CAAC;AAE9F,WAAU,aAAa,eAAe,6BAA6B,OAAO,YACxE,MAAM,WAAW,iBAAiB;EAChC,IAAI,0BAA0B,QAAQ,IAAI,KAAK;EAC/C,MAAM,0BAA0B,QAAQ,MAAM,OAAO;EACrD,WAAW,0BAA0B,QAAQ,UAAU;EACvD,SAAS,0BAA0B,QAAQ,QAAQ;EACnD,OAAO,gBAAgB,QAAQ,MAAM;EACrC,UAAU,mBAAmB,QAAQ,SAAS;EAC/C,CAAC,CACH;AACD,OAAM,WAAW,OAAO;AACxB,OAAM,UAAU,OAAO;;AAGzB,eAAsB,yBACpB,YACA,UAAoC,EAAE,EACvB;AACf,OAAM,sBACJ;EACE,WAAW,WAAW;EACtB,gBAAgB,EAAE,cAAc,IAAI,kBAAkB,YAAY,QAAQ;EAC1E,YAAY,WAAW,cAAc;EACtC,EACD,QACD;;AAGH,SAAgB,kBAAkB,WAA6C;AAC7E,SAAQ,UAAU;EAChB,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,UAAQ,KAAK,IAAI,UAAU,8BAA8B,UAAU;;;AAIvE,SAAS,0BAA0B,OAAgB,MAAsB;AACvE,KAAI,OAAO,UAAU,YAAY,CAAC,MAAM,MAAM,CAC5C,OAAM,IAAI,MAAM,GAAG,KAAK,cAAc;AAExC,QAAO,MAAM,MAAM;;AAGrB,SAAS,0BAA0B,OAAoC;AACrE,QAAO,OAAO,UAAU,YAAY,MAAM,MAAM,GAAG,MAAM,MAAM,GAAG,KAAA;;AAGpE,SAAS,gBAAgB,OAAsC;AAC7D,KAAI,CAAC,MAAM,QAAQ,MAAM,CACvB;AAEF,QAAO,MAAM,QAAQ,SAAyB,OAAO,SAAS,SAAS;;AAGzE,SAAS,mBAAmB,OAAqD;AAC/E,QAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM,GAC9D,QACA,KAAA;;AAGN,SAAS,uBAAuB,SAAiE;AAC/F,QAAO;EACL,gBAAgB,QAAQ;EACxB,UAAU,QAAQ;EAClB,SAAS;GACP,MAAM;GACN,MAAM,QAAQ;GACf;EACD,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,aAAa,GAAG,EAAE;EACnE,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,UAAU,GAAG,EAAE;EAC3D"}
1
+ {"version":3,"file":"extension-channel-controller.service.js","names":[],"sources":["../../src/services/extension-channel-controller.service.ts"],"sourcesContent":["import { NcpEventType, type NcpEndpointEvent, type NcpMessagePart } from \"@nextclaw/ncp\";\nimport type {\n ChannelSubmittedMessage,\n ChannelSubmittedAttachment,\n ExtensionChannel,\n ExtensionDiagnostics,\n NextClawExtensionOptions,\n} from \"../types/extension-sdk.types.js\";\nimport { NextClawExtension } from \"./extension-client.service.js\";\n\nfunction readRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : {};\n}\n\nconst AGENT_ROUTE_KINDS = new Set([\"direct\", \"group\", \"channel\", \"thread\"]);\n\nfunction parseSessionDeliveryRoute(sessionId: string): {\n channel?: string;\n chatId?: string;\n accountId?: string;\n} {\n const parts = sessionId.trim().split(\":\");\n if (parts[0] !== \"agent\" || parts.length < 5) {\n return {};\n }\n const hasAccount = !AGENT_ROUTE_KINDS.has(parts[3]);\n const kind = parts[hasAccount ? 4 : 3];\n if (!AGENT_ROUTE_KINDS.has(kind ?? \"\")) {\n return {};\n }\n return {\n channel: parts[2],\n ...(hasAccount ? { accountId: parts[3] } : {}),\n chatId: parts.slice(hasAccount ? 5 : 4).join(\":\"),\n };\n}\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction renderMessageText(parts: readonly NcpMessagePart[]): string {\n return (parts as Array<{ type: string; text?: string }>).map((part) =>\n (part.type === \"text\" || part.type === \"rich-text\") && part.text ? part.text : \"\",\n ).filter(Boolean).join(\"\\n\\n\");\n}\n\nexport type ExtensionChannelAdapter<TConfig, TInbound> = {\n configure: (config: TConfig) => Promise<void>;\n start: () => Promise<void>;\n stop: () => Promise<void>;\n onMessage: (handler: (message: TInbound) => void | Promise<void>) => () => void;\n sendNcpEvent: (event: NcpEndpointEvent) => Promise<void>;\n sendOutboundText: (params: {\n to: string;\n text: string;\n accountId?: string | null;\n replyTo?: string | null;\n media?: string[];\n metadata?: Record<string, unknown>;\n }) => Promise<void>;\n};\n\nexport type ChannelSubmittedMessageInput = Omit<ChannelSubmittedMessage, \"channelId\">;\n\nexport type BusChannelInboundMessage = {\n channel: string;\n senderId: string;\n chatId: string;\n content: string;\n attachments?: ChannelSubmittedAttachment[];\n metadata?: Record<string, unknown>;\n};\n\nexport type BusChannelMessageBus = {\n publishInbound: (message: BusChannelInboundMessage) => Promise<void>;\n};\n\nexport type BusChannelRuntime = {\n start: () => Promise<void>;\n stop: () => Promise<void>;\n handleControlMessage?: (message: {\n channel: string;\n chatId: string;\n content: string;\n replyTo?: string | null;\n media: string[];\n metadata: Record<string, unknown>;\n }) => Promise<boolean>;\n send: (message: {\n channel: string;\n chatId: string;\n content: string;\n replyTo?: string | null;\n media: string[];\n metadata: Record<string, unknown>;\n }) => Promise<void>;\n};\n\nexport type BusChannelCreateContext<TConfig, TBus = BusChannelMessageBus> = {\n config: TConfig;\n bus: TBus;\n channel: ExtensionChannel;\n diagnostics: ExtensionDiagnostics;\n};\n\nexport type BusChannelExtensionDefinition<TConfig, TBus = BusChannelMessageBus> = {\n channelId: string;\n createChannel: (\n context: BusChannelCreateContext<TConfig, TBus>,\n ) => BusChannelRuntime | Promise<BusChannelRuntime>;\n mapInbound?: ExtensionChannelInboundMapper<BusChannelInboundMessage>;\n onChannelStartError?: (error: unknown) => void | Promise<void>;\n};\n\nexport type ExtensionChannelInboundMapper<TInbound> =\n (message: TInbound) => ChannelSubmittedMessageInput | Promise<ChannelSubmittedMessageInput>;\n\ntype ExtensionChannelControllerOptions<TConfig, TInbound> = {\n channel: ExtensionChannel;\n adapter: ExtensionChannelAdapter<TConfig, TInbound>;\n mapInbound: ExtensionChannelInboundMapper<TInbound>;\n onNcpEventError?: (error: unknown, event: NcpEndpointEvent) => void | Promise<void>;\n};\n\nexport type ChannelExtensionContext = {\n channel: ExtensionChannel;\n diagnostics: ExtensionDiagnostics;\n};\n\nexport type ChannelExtensionDefinition<TConfig, TInbound> = {\n channelId: string;\n createAdapter: (\n context: ChannelExtensionContext,\n ) => ExtensionChannelAdapter<TConfig, TInbound> | Promise<ExtensionChannelAdapter<TConfig, TInbound>>;\n mapInbound: ExtensionChannelInboundMapper<TInbound>;\n createAuthCapability?: (context: ChannelExtensionContext) => object;\n onNcpEventError?: (error: unknown, event: NcpEndpointEvent) => void | Promise<void>;\n};\n\nfunction isChannelEnabled(config: unknown): boolean {\n return Boolean(\n config &&\n typeof config === \"object\" &&\n !Array.isArray(config) &&\n (config as { enabled?: unknown }).enabled === true\n );\n}\n\nclass LazyExtensionChannelAdapter<TConfig, TInbound> implements ExtensionChannelAdapter<TConfig, TInbound> {\n private adapterCleanup: (() => void) | null = null;\n private adapterPromise: Promise<ExtensionChannelAdapter<TConfig, TInbound>> | null = null;\n private messageHandler: ((message: TInbound) => void | Promise<void>) | null = null;\n\n constructor(\n private readonly createAdapter: () =>\n ExtensionChannelAdapter<TConfig, TInbound> | Promise<ExtensionChannelAdapter<TConfig, TInbound>>,\n ) {}\n\n configure = async (config: TConfig): Promise<void> => {\n await (await this.getAdapter()).configure(config);\n };\n\n start = async (): Promise<void> => {\n await (await this.getAdapter()).start();\n };\n\n stop = async (): Promise<void> => {\n if (this.adapterPromise) {\n await (await this.adapterPromise).stop();\n }\n };\n\n onMessage = (handler: (message: TInbound) => void | Promise<void>): (() => void) => {\n this.messageHandler = handler;\n if (this.adapterPromise) {\n void this.adapterPromise.then((adapter) => this.attachMessageHandler(adapter)).catch(() => undefined);\n }\n return () => {\n if (this.messageHandler === handler) {\n this.messageHandler = null;\n this.adapterCleanup?.();\n this.adapterCleanup = null;\n }\n };\n };\n\n sendNcpEvent = async (event: NcpEndpointEvent): Promise<void> => {\n if (this.adapterPromise) {\n await (await this.adapterPromise).sendNcpEvent(event);\n }\n };\n\n sendOutboundText = async (params: {\n to: string;\n text: string;\n accountId?: string | null;\n replyTo?: string | null;\n media?: string[];\n metadata?: Record<string, unknown>;\n }): Promise<void> => {\n await (await this.getAdapter()).sendOutboundText(params);\n };\n\n private getAdapter = async (): Promise<ExtensionChannelAdapter<TConfig, TInbound>> => {\n this.adapterPromise ??= Promise.resolve(this.createAdapter()).then((adapter) => {\n this.attachMessageHandler(adapter);\n return adapter;\n });\n return await this.adapterPromise;\n };\n\n private attachMessageHandler = (adapter: ExtensionChannelAdapter<TConfig, TInbound>): void => {\n this.adapterCleanup?.();\n this.adapterCleanup = this.messageHandler ? adapter.onMessage(this.messageHandler) : null;\n };\n}\n\nexport class ExtensionChannelController<TConfig, TInbound> {\n private readonly cleanups: Array<() => void> = [];\n private started = false;\n\n constructor(\n private readonly options: ExtensionChannelControllerOptions<TConfig, TInbound>,\n ) {}\n\n start = async (): Promise<void> => {\n if (this.started) {\n return;\n }\n this.started = true;\n this.cleanups.push(this.options.adapter.onMessage(this.submitMessage));\n this.cleanups.push(this.options.channel.onNcpEvent(this.sendNcpEvent));\n this.cleanups.push(this.options.channel.config.onChange(async () => {\n await this.applyConfig();\n }));\n await this.applyConfig();\n };\n\n stop = async (): Promise<void> => {\n if (!this.started) {\n return;\n }\n this.started = false;\n this.drainCleanups();\n await this.options.adapter.stop();\n };\n\n sendOutboundText = async (params: {\n to: string;\n text: string;\n accountId?: string | null;\n replyTo?: string | null;\n media?: string[];\n metadata?: Record<string, unknown>;\n }): Promise<{ accepted: true }> => {\n await this.options.adapter.sendOutboundText(params);\n return { accepted: true };\n };\n\n private applyConfig = async (): Promise<void> => {\n const config = await this.options.channel.config.get<TConfig>();\n if (!isChannelEnabled(config)) {\n await this.options.adapter.stop();\n return;\n }\n await this.options.adapter.configure(config);\n await this.options.adapter.start();\n };\n\n private submitMessage = async (message: TInbound): Promise<void> => {\n await this.options.channel.submitMessage(await this.options.mapInbound(message));\n };\n\n private sendNcpEvent = async (event: NcpEndpointEvent): Promise<void> => {\n try {\n await this.options.adapter.sendNcpEvent(event);\n } catch (error) {\n await this.options.onNcpEventError?.(error, event);\n }\n };\n\n private drainCleanups = (): void => {\n for (const cleanup of this.cleanups.splice(0).reverse()) {\n cleanup();\n }\n };\n}\n\nclass BusChannelAdapter<TConfig, TBus> implements ExtensionChannelAdapter<TConfig, BusChannelInboundMessage> {\n private channel: BusChannelRuntime | null = null;\n private messageHandler: ((message: BusChannelInboundMessage) => void | Promise<void>) | null = null;\n private readonly definition: BusChannelExtensionDefinition<TConfig, TBus>;\n private readonly diagnostics: ExtensionDiagnostics;\n private readonly extensionChannel: ExtensionChannel;\n\n constructor(params: {\n definition: BusChannelExtensionDefinition<TConfig, TBus>;\n diagnostics: ExtensionDiagnostics;\n extensionChannel: ExtensionChannel;\n }) {\n this.definition = params.definition;\n this.diagnostics = params.diagnostics;\n this.extensionChannel = params.extensionChannel;\n }\n\n configure = async (config: TConfig): Promise<void> => {\n await this.stop();\n this.channel = await this.definition.createChannel({\n config,\n bus: {\n publishInbound: async (message: BusChannelInboundMessage) => {\n await this.messageHandler?.(message);\n },\n } as TBus,\n channel: this.extensionChannel,\n diagnostics: this.diagnostics,\n });\n };\n\n start = async (): Promise<void> => {\n const channel = this.channel;\n if (!channel) {\n return;\n }\n void channel.start().catch(async (error: unknown) => {\n await this.definition.onChannelStartError?.(error);\n });\n };\n\n stop = async (): Promise<void> => {\n const channel = this.channel;\n this.channel = null;\n await channel?.stop();\n };\n\n onMessage = (\n handler: (message: BusChannelInboundMessage) => void | Promise<void>,\n ): (() => void) => {\n this.messageHandler = handler;\n return () => {\n if (this.messageHandler === handler) {\n this.messageHandler = null;\n }\n };\n };\n\n sendNcpEvent = async (event: NcpEndpointEvent): Promise<void> => {\n if (event.type !== NcpEventType.MessageCompleted) {\n return;\n }\n const metadata = readRecord(event.payload.message.metadata);\n const sessionRoute = parseSessionDeliveryRoute(event.payload.message.sessionId);\n const eventChannel = readString(metadata.channel) ?? readString(metadata.channelId) ?? sessionRoute.channel;\n if (eventChannel !== this.definition.channelId) {\n return;\n }\n const chatId = readString(metadata.chatId) ?? readString(metadata.chat_id) ?? sessionRoute.chatId;\n const content = renderMessageText(event.payload.message.parts).trim();\n if (!chatId || !content) {\n return;\n }\n const replyTo = readString(metadata.message_id);\n await this.channel?.send({\n channel: this.definition.channelId,\n chatId,\n content,\n ...(replyTo ? { replyTo } : {}),\n media: [],\n metadata: {\n ...metadata,\n ...(sessionRoute.accountId && !metadata.accountId ? { accountId: sessionRoute.accountId } : {}),\n },\n });\n };\n\n sendOutboundText = async (params: {\n to: string;\n text: string;\n accountId?: string | null;\n replyTo?: string | null;\n media?: string[];\n metadata?: Record<string, unknown>;\n }): Promise<void> => {\n const { accountId, media, metadata, replyTo, text, to } = params;\n const outbound = {\n channel: this.definition.channelId,\n chatId: to,\n content: text,\n ...(replyTo !== undefined ? { replyTo } : {}),\n media: media ?? [],\n metadata: {\n ...(metadata ?? {}),\n ...(accountId ? { accountId } : {}),\n },\n };\n if (await this.channel?.handleControlMessage?.(outbound)) {\n return;\n }\n await this.channel?.send(outbound);\n };\n}\n\nexport async function startChannelExtension<TConfig, TInbound>(\n definition: ChannelExtensionDefinition<TConfig, TInbound>,\n options: NextClawExtensionOptions = {},\n): Promise<void> {\n const extension = new NextClawExtension(options);\n const channel = extension.channels.use(definition.channelId);\n const adapter = new LazyExtensionChannelAdapter(() => definition.createAdapter({\n channel,\n diagnostics: extension.diagnostics,\n }));\n const controller = new ExtensionChannelController({\n channel,\n adapter,\n mapInbound: definition.mapInbound,\n onNcpEventError: definition.onNcpEventError,\n });\n if (definition.createAuthCapability) {\n extension.capabilities.provide(\"channel.auth\", definition.createAuthCapability({\n channel,\n diagnostics: extension.diagnostics,\n }));\n }\n extension.capabilities.provideHandler(\"channel.outbound.sendText\", async (payload) =>\n await controller.sendOutboundText({\n to: readRequiredPayloadString(payload.to, \"to\"),\n text: readRequiredPayloadString(payload.text, \"text\"),\n accountId: readOptionalPayloadString(payload.accountId),\n replyTo: readOptionalPayloadString(payload.replyTo),\n media: readStringArray(payload.media),\n metadata: readOptionalRecord(payload.metadata),\n }),\n );\n await controller.start();\n await extension.ready();\n}\n\nexport async function startBusChannelExtension<TConfig, TBus = BusChannelMessageBus>(\n definition: BusChannelExtensionDefinition<TConfig, TBus>,\n options: NextClawExtensionOptions = {},\n): Promise<void> {\n await startChannelExtension(\n {\n channelId: definition.channelId,\n createAdapter: ({ channel, diagnostics }) => new BusChannelAdapter({\n definition,\n diagnostics,\n extensionChannel: channel,\n }),\n mapInbound: definition.mapInbound ?? toSubmittedTextMessage,\n },\n options,\n );\n}\n\nexport function warnNcpEventError(channelId: string): (error: unknown) => void {\n return (error) => {\n const message = error instanceof Error ? error.message : String(error);\n console.warn(`[${channelId}] failed to send NCP event: ${message}`);\n };\n}\n\nfunction readRequiredPayloadString(value: unknown, name: string): string {\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`${name} is required`);\n }\n return value.trim();\n}\n\nfunction readOptionalPayloadString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction readStringArray(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) {\n return undefined;\n }\n return value.filter((item): item is string => typeof item === \"string\");\n}\n\nfunction readOptionalRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : undefined;\n}\n\nfunction toSubmittedTextMessage(message: BusChannelInboundMessage): ChannelSubmittedMessageInput {\n return {\n conversationId: message.chatId,\n senderId: message.senderId,\n content: {\n type: \"text\",\n text: message.content,\n },\n ...(message.attachments ? { attachments: message.attachments } : {}),\n ...(message.metadata ? { metadata: message.metadata } : {}),\n };\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { ExtensionCapabilities, ExtensionChannels, ExtensionRequestHandler, NextClawExtensionOptions } from "../types/extension-sdk.types.js";
1
+ import { ExtensionCapabilities, ExtensionChannels, ExtensionDiagnostics, ExtensionRequestHandler, NextClawExtensionOptions } from "../types/extension-sdk.types.js";
2
2
  import { EventBus } from "@nextclaw/shared";
3
3
 
4
4
  //#region src/services/extension-client.service.d.ts
@@ -6,6 +6,7 @@ declare class NextClawExtension {
6
6
  readonly eventBus: EventBus;
7
7
  readonly channels: ExtensionChannels;
8
8
  readonly capabilities: ExtensionCapabilities;
9
+ readonly diagnostics: ExtensionDiagnostics;
9
10
  readonly extensionId: string;
10
11
  readonly generation: string;
11
12
  private readonly transport;
@@ -1 +1 @@
1
- {"version":3,"file":"extension-client.service.d.ts","names":[],"sources":["../../src/services/extension-client.service.ts"],"mappings":";;;;cAwKa,iBAAA;EAAA,SACF,QAAA,EAAU,QAAA;EAAA,SACV,QAAA,EAAU,iBAAA;EAAA,SACV,YAAA,EAAc,qBAAA;EAAA,SACd,WAAA;EAAA,SACA,UAAA;EAAA,iBACQ,SAAA;EAAA,QACT,uBAAA;EAAA,QACA,oBAAA;cAEI,OAAA,GAAS,wBAAA;EAAA,SA2BZ,KAAA;EAAA,SASA,KAAA,QAAkB,OAAA;EAAA,SASlB,SAAA,GAAa,OAAA,EAAS,uBAAA;EAAA,iBAYd,kBAAA;EAAA,iBAMA,yBAAA;AAAA"}
1
+ {"version":3,"file":"extension-client.service.d.ts","names":[],"sources":["../../src/services/extension-client.service.ts"],"mappings":";;;;cAwNa,iBAAA;EAAA,SACF,QAAA,EAAU,QAAA;EAAA,SACV,QAAA,EAAU,iBAAA;EAAA,SACV,YAAA,EAAc,qBAAA;EAAA,SACd,WAAA,EAAa,oBAAA;EAAA,SACb,WAAA;EAAA,SACA,UAAA;EAAA,iBACQ,SAAA;EAAA,QACT,uBAAA;EAAA,QACA,oBAAA;cAEI,OAAA,GAAS,wBAAA;EAAA,SA+BZ,KAAA;EAAA,SASA,KAAA,QAAkB,OAAA;EAAA,SASlB,SAAA,GAAa,OAAA,EAAS,uBAAA;EAAA,iBAYd,kBAAA;EAAA,iBAMA,yBAAA;AAAA"}
@@ -1,8 +1,38 @@
1
1
  import { ExtensionChannelService } from "./extension-channel.service.js";
2
2
  import { ExtensionTransportService } from "./extension-transport.service.js";
3
- import { EventBus } from "@nextclaw/shared";
3
+ import { EventBus, getKeyId, ingressKeys } from "@nextclaw/shared";
4
+ import { createHash, randomUUID } from "node:crypto";
4
5
  //#region src/services/extension-client.service.ts
5
6
  const EXTENSION_PARENT_WATCH_INTERVAL_MS = 1e3;
7
+ var ExtensionDiagnosticClient = class {
8
+ constructor(transport, timeoutMs) {
9
+ this.transport = transport;
10
+ this.timeoutMs = timeoutMs;
11
+ }
12
+ createTraceId = (stableId) => {
13
+ const normalizedStableId = stableId?.trim();
14
+ if (!normalizedStableId) return randomUUID();
15
+ return createHash("sha256").update(normalizedStableId).digest("hex").slice(0, 24);
16
+ };
17
+ emit = async (input) => {
18
+ const controller = new AbortController();
19
+ let timeout;
20
+ try {
21
+ await Promise.race([this.transport.postIngress(getKeyId(ingressKeys.extension.diagnosticEmit), input, { signal: controller.signal }), new Promise((_resolve, reject) => {
22
+ timeout = setTimeout(() => {
23
+ controller.abort();
24
+ reject(/* @__PURE__ */ new Error("Extension diagnostic emission timed out."));
25
+ }, this.timeoutMs);
26
+ timeout.unref?.();
27
+ })]);
28
+ return true;
29
+ } catch {
30
+ return false;
31
+ } finally {
32
+ if (timeout) clearTimeout(timeout);
33
+ }
34
+ };
35
+ };
6
36
  var ExtensionChannelRegistry = class {
7
37
  channels = /* @__PURE__ */ new Map();
8
38
  constructor(params) {
@@ -96,6 +126,7 @@ var NextClawExtension = class {
96
126
  eventBus;
97
127
  channels;
98
128
  capabilities;
129
+ diagnostics;
99
130
  extensionId;
100
131
  generation;
101
132
  transport;
@@ -125,6 +156,7 @@ var NextClawExtension = class {
125
156
  extensionId: this.extensionId,
126
157
  transport: this.transport
127
158
  });
159
+ this.diagnostics = new ExtensionDiagnosticClient(this.transport, Math.max(50, options.diagnosticTimeoutMs ?? 2e3));
128
160
  this.parentProcessWatcher = this.startParentProcessWatcher();
129
161
  }
130
162
  close = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"extension-client.service.js","names":[],"sources":["../../src/services/extension-client.service.ts"],"sourcesContent":["import { EventBus } from \"@nextclaw/shared\";\nimport type {\n ExtensionCapabilities,\n ExtensionCapabilityHandler,\n ExtensionChannels,\n ExtensionRequest,\n ExtensionRequestHandler,\n ExtensionTransportEnvelope,\n NextClawExtensionOptions,\n} from \"../types/extension-sdk.types.js\";\nimport { ExtensionChannelService } from \"./extension-channel.service.js\";\nimport { ExtensionTransportService } from \"./extension-transport.service.js\";\n\nconst EXTENSION_PARENT_WATCH_INTERVAL_MS = 1000;\n\nclass ExtensionChannelRegistry implements ExtensionChannels {\n private readonly channels = new Map<string, ExtensionChannelService>();\n\n constructor(\n private readonly params: {\n eventBus: EventBus;\n transport: ExtensionTransportService;\n },\n ) {}\n\n readonly use = (channelId: string): ExtensionChannelService => {\n const normalizedChannelId = channelId.trim();\n if (!normalizedChannelId) {\n throw new Error(\"channelId is required.\");\n }\n const existing = this.channels.get(normalizedChannelId);\n if (existing) {\n return existing;\n }\n const channel = new ExtensionChannelService({\n channelId: normalizedChannelId,\n eventBus: this.params.eventBus,\n transport: this.params.transport,\n });\n this.channels.set(normalizedChannelId, channel);\n return channel;\n };\n}\n\nfunction readRequest(payload: unknown): ExtensionRequest | null {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n return null;\n }\n const record = payload as Record<string, unknown>;\n if (\n typeof record.requestId !== \"string\" ||\n typeof record.extensionId !== \"string\" ||\n typeof record.generation !== \"string\" ||\n typeof record.kind !== \"string\"\n ) {\n return null;\n }\n return {\n requestId: record.requestId,\n extensionId: record.extensionId,\n generation: record.generation,\n kind: record.kind,\n payload: record.payload && typeof record.payload === \"object\" && !Array.isArray(record.payload)\n ? record.payload as Record<string, unknown>\n : {},\n };\n}\n\nfunction normalizeName(value: string, name: string): string {\n const normalized = value.trim();\n if (!normalized) {\n throw new Error(`${name} is required.`);\n }\n return normalized;\n}\n\nasync function handleRequest(\n transport: ExtensionTransportService,\n request: ExtensionRequest,\n handler: ExtensionRequestHandler,\n): Promise<void> {\n try {\n const data = await handler(request);\n await transport.respondToRequest({\n requestId: request.requestId,\n ok: true,\n data,\n });\n } catch (error) {\n await transport.respondToRequest({\n requestId: request.requestId,\n ok: false,\n error: {\n message: error instanceof Error ? error.message : String(error),\n },\n });\n }\n}\n\nfunction readParentProcessId(): number | null {\n const raw = process.env.NEXTCLAW_EXTENSION_PARENT_PID?.trim();\n if (!raw) {\n return null;\n }\n const pid = Number(raw);\n return Number.isInteger(pid) && pid > 0 ? pid : null;\n}\n\nfunction isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\nclass ExtensionCapabilityRegistry implements ExtensionCapabilities {\n constructor(\n private readonly params: {\n eventBus: EventBus;\n extensionId: string;\n transport: ExtensionTransportService;\n },\n ) {}\n\n readonly provide = (namespace: string, capability: object): (() => void) => {\n const normalizedNamespace = normalizeName(namespace, \"namespace\");\n const unsubscribeHandlers = this.readHandlers(capability).map(([name, handler]) =>\n this.provideHandler(`${normalizedNamespace}.${name}`, handler),\n );\n if (unsubscribeHandlers.length === 0) {\n throw new Error(`capability '${normalizedNamespace}' has no callable methods.`);\n }\n return () => {\n for (const unsubscribe of unsubscribeHandlers) {\n unsubscribe();\n }\n };\n };\n\n readonly provideHandler = (kind: string, handler: ExtensionCapabilityHandler): (() => void) => {\n const normalizedKind = normalizeName(kind, \"kind\");\n return this.params.eventBus.subscribeAll((event) => {\n if (event.type !== \"extension.request\") {\n return;\n }\n const request = readRequest(event.payload);\n if (\n !request ||\n request.extensionId !== this.params.extensionId ||\n request.generation !== this.params.transport.generation ||\n request.kind !== normalizedKind\n ) {\n return;\n }\n void handleRequest(this.params.transport, request, async (matchedRequest) =>\n await handler(matchedRequest.payload ?? {}, matchedRequest),\n );\n });\n };\n\n private readonly readHandlers = (capability: object): Array<[string, ExtensionCapabilityHandler]> =>\n Object.entries(capability).filter(\n (entry): entry is [string, ExtensionCapabilityHandler] => typeof entry[1] === \"function\",\n );\n}\n\nexport class NextClawExtension {\n readonly eventBus: EventBus;\n readonly channels: ExtensionChannels;\n readonly capabilities: ExtensionCapabilities;\n readonly extensionId: string;\n readonly generation: string;\n private readonly transport: ExtensionTransportService;\n private eventStreamSubscription: { close: () => void; ready: Promise<void> } | null = null;\n private parentProcessWatcher: ReturnType<typeof setInterval> | null = null;\n\n constructor(options: NextClawExtensionOptions = {}) {\n this.transport = new ExtensionTransportService(options);\n this.extensionId = this.transport.extensionId;\n this.generation = this.transport.generation;\n this.eventBus = new EventBus({\n onFirstSubscriber: () => {\n this.eventStreamSubscription ??= this.transport.subscribe((event) => {\n this.eventBus.emitEnvelope(this.toEventBusEnvelope(event));\n });\n },\n onNoSubscribers: () => {\n this.eventStreamSubscription?.close();\n this.eventStreamSubscription = null;\n },\n });\n this.channels = new ExtensionChannelRegistry({\n eventBus: this.eventBus,\n transport: this.transport,\n });\n this.capabilities = new ExtensionCapabilityRegistry({\n eventBus: this.eventBus,\n extensionId: this.extensionId,\n transport: this.transport,\n });\n this.parentProcessWatcher = this.startParentProcessWatcher();\n }\n\n readonly close = (): void => {\n if (this.parentProcessWatcher) {\n clearInterval(this.parentProcessWatcher);\n this.parentProcessWatcher = null;\n }\n this.eventStreamSubscription?.close();\n this.eventStreamSubscription = null;\n };\n\n readonly ready = async (): Promise<void> => {\n const subscription = this.eventStreamSubscription;\n if (!subscription) {\n throw new Error(`Extension ${this.extensionId} has no event-stream subscription.`);\n }\n await subscription.ready;\n await this.transport.reportReady(process.pid);\n };\n\n readonly onRequest = (handler: ExtensionRequestHandler): (() => void) =>\n this.eventBus.subscribeAll((event) => {\n if (event.type !== \"extension.request\") {\n return;\n }\n const request = readRequest(event.payload);\n if (!request || request.extensionId !== this.extensionId || request.generation !== this.generation) {\n return;\n }\n void handleRequest(this.transport, request, handler);\n });\n\n private readonly toEventBusEnvelope = (event: ExtensionTransportEnvelope): ExtensionTransportEnvelope => ({\n ...event,\n emittedAt: event.emittedAt ?? new Date().toISOString(),\n source: event.source ?? \"event-stream\",\n });\n\n private readonly startParentProcessWatcher = (): ReturnType<typeof setInterval> | null => {\n const parentPid = readParentProcessId();\n if (!parentPid) {\n return null;\n }\n const timer = setInterval(() => {\n if (isProcessRunning(parentPid)) {\n return;\n }\n this.close();\n process.exit(0);\n }, EXTENSION_PARENT_WATCH_INTERVAL_MS);\n (timer as NodeJS.Timeout).unref?.();\n return timer;\n };\n\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension-client.service.js","names":[],"sources":["../../src/services/extension-client.service.ts"],"sourcesContent":["import { EventBus, getKeyId, ingressKeys } from \"@nextclaw/shared\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport type {\n ExtensionCapabilities,\n ExtensionCapabilityHandler,\n ExtensionChannels,\n ExtensionDiagnostics,\n ExtensionRequest,\n ExtensionRequestHandler,\n ExtensionTransportEnvelope,\n NextClawExtensionOptions,\n} from \"../types/extension-sdk.types.js\";\nimport { ExtensionChannelService } from \"./extension-channel.service.js\";\nimport { ExtensionTransportService } from \"./extension-transport.service.js\";\n\nconst EXTENSION_PARENT_WATCH_INTERVAL_MS = 1000;\n\nclass ExtensionDiagnosticClient implements ExtensionDiagnostics {\n constructor(\n private readonly transport: ExtensionTransportService,\n private readonly timeoutMs: number,\n ) {}\n\n readonly createTraceId = (stableId?: string): string => {\n const normalizedStableId = stableId?.trim();\n if (!normalizedStableId) {\n return randomUUID();\n }\n return createHash(\"sha256\")\n .update(normalizedStableId)\n .digest(\"hex\")\n .slice(0, 24);\n };\n\n readonly emit: ExtensionDiagnostics[\"emit\"] = async (input) => {\n const controller = new AbortController();\n let timeout: ReturnType<typeof setTimeout> | undefined;\n try {\n await Promise.race([\n this.transport.postIngress(\n getKeyId(ingressKeys.extension.diagnosticEmit),\n input,\n { signal: controller.signal },\n ),\n new Promise<never>((_resolve, reject) => {\n timeout = setTimeout(() => {\n controller.abort();\n reject(new Error(\"Extension diagnostic emission timed out.\"));\n }, this.timeoutMs);\n timeout.unref?.();\n }),\n ]);\n return true;\n } catch {\n return false;\n } finally {\n if (timeout) {\n clearTimeout(timeout);\n }\n }\n };\n}\n\nclass ExtensionChannelRegistry implements ExtensionChannels {\n private readonly channels = new Map<string, ExtensionChannelService>();\n\n constructor(\n private readonly params: {\n eventBus: EventBus;\n transport: ExtensionTransportService;\n },\n ) {}\n\n readonly use = (channelId: string): ExtensionChannelService => {\n const normalizedChannelId = channelId.trim();\n if (!normalizedChannelId) {\n throw new Error(\"channelId is required.\");\n }\n const existing = this.channels.get(normalizedChannelId);\n if (existing) {\n return existing;\n }\n const channel = new ExtensionChannelService({\n channelId: normalizedChannelId,\n eventBus: this.params.eventBus,\n transport: this.params.transport,\n });\n this.channels.set(normalizedChannelId, channel);\n return channel;\n };\n}\n\nfunction readRequest(payload: unknown): ExtensionRequest | null {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n return null;\n }\n const record = payload as Record<string, unknown>;\n if (\n typeof record.requestId !== \"string\" ||\n typeof record.extensionId !== \"string\" ||\n typeof record.generation !== \"string\" ||\n typeof record.kind !== \"string\"\n ) {\n return null;\n }\n return {\n requestId: record.requestId,\n extensionId: record.extensionId,\n generation: record.generation,\n kind: record.kind,\n payload: record.payload && typeof record.payload === \"object\" && !Array.isArray(record.payload)\n ? record.payload as Record<string, unknown>\n : {},\n };\n}\n\nfunction normalizeName(value: string, name: string): string {\n const normalized = value.trim();\n if (!normalized) {\n throw new Error(`${name} is required.`);\n }\n return normalized;\n}\n\nasync function handleRequest(\n transport: ExtensionTransportService,\n request: ExtensionRequest,\n handler: ExtensionRequestHandler,\n): Promise<void> {\n try {\n const data = await handler(request);\n await transport.respondToRequest({\n requestId: request.requestId,\n ok: true,\n data,\n });\n } catch (error) {\n await transport.respondToRequest({\n requestId: request.requestId,\n ok: false,\n error: {\n message: error instanceof Error ? error.message : String(error),\n },\n });\n }\n}\n\nfunction readParentProcessId(): number | null {\n const raw = process.env.NEXTCLAW_EXTENSION_PARENT_PID?.trim();\n if (!raw) {\n return null;\n }\n const pid = Number(raw);\n return Number.isInteger(pid) && pid > 0 ? pid : null;\n}\n\nfunction isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\nclass ExtensionCapabilityRegistry implements ExtensionCapabilities {\n constructor(\n private readonly params: {\n eventBus: EventBus;\n extensionId: string;\n transport: ExtensionTransportService;\n },\n ) {}\n\n readonly provide = (namespace: string, capability: object): (() => void) => {\n const normalizedNamespace = normalizeName(namespace, \"namespace\");\n const unsubscribeHandlers = this.readHandlers(capability).map(([name, handler]) =>\n this.provideHandler(`${normalizedNamespace}.${name}`, handler),\n );\n if (unsubscribeHandlers.length === 0) {\n throw new Error(`capability '${normalizedNamespace}' has no callable methods.`);\n }\n return () => {\n for (const unsubscribe of unsubscribeHandlers) {\n unsubscribe();\n }\n };\n };\n\n readonly provideHandler = (kind: string, handler: ExtensionCapabilityHandler): (() => void) => {\n const normalizedKind = normalizeName(kind, \"kind\");\n return this.params.eventBus.subscribeAll((event) => {\n if (event.type !== \"extension.request\") {\n return;\n }\n const request = readRequest(event.payload);\n if (\n !request ||\n request.extensionId !== this.params.extensionId ||\n request.generation !== this.params.transport.generation ||\n request.kind !== normalizedKind\n ) {\n return;\n }\n void handleRequest(this.params.transport, request, async (matchedRequest) =>\n await handler(matchedRequest.payload ?? {}, matchedRequest),\n );\n });\n };\n\n private readonly readHandlers = (capability: object): Array<[string, ExtensionCapabilityHandler]> =>\n Object.entries(capability).filter(\n (entry): entry is [string, ExtensionCapabilityHandler] => typeof entry[1] === \"function\",\n );\n}\n\nexport class NextClawExtension {\n readonly eventBus: EventBus;\n readonly channels: ExtensionChannels;\n readonly capabilities: ExtensionCapabilities;\n readonly diagnostics: ExtensionDiagnostics;\n readonly extensionId: string;\n readonly generation: string;\n private readonly transport: ExtensionTransportService;\n private eventStreamSubscription: { close: () => void; ready: Promise<void> } | null = null;\n private parentProcessWatcher: ReturnType<typeof setInterval> | null = null;\n\n constructor(options: NextClawExtensionOptions = {}) {\n this.transport = new ExtensionTransportService(options);\n this.extensionId = this.transport.extensionId;\n this.generation = this.transport.generation;\n this.eventBus = new EventBus({\n onFirstSubscriber: () => {\n this.eventStreamSubscription ??= this.transport.subscribe((event) => {\n this.eventBus.emitEnvelope(this.toEventBusEnvelope(event));\n });\n },\n onNoSubscribers: () => {\n this.eventStreamSubscription?.close();\n this.eventStreamSubscription = null;\n },\n });\n this.channels = new ExtensionChannelRegistry({\n eventBus: this.eventBus,\n transport: this.transport,\n });\n this.capabilities = new ExtensionCapabilityRegistry({\n eventBus: this.eventBus,\n extensionId: this.extensionId,\n transport: this.transport,\n });\n this.diagnostics = new ExtensionDiagnosticClient(\n this.transport,\n Math.max(50, options.diagnosticTimeoutMs ?? 2_000),\n );\n this.parentProcessWatcher = this.startParentProcessWatcher();\n }\n\n readonly close = (): void => {\n if (this.parentProcessWatcher) {\n clearInterval(this.parentProcessWatcher);\n this.parentProcessWatcher = null;\n }\n this.eventStreamSubscription?.close();\n this.eventStreamSubscription = null;\n };\n\n readonly ready = async (): Promise<void> => {\n const subscription = this.eventStreamSubscription;\n if (!subscription) {\n throw new Error(`Extension ${this.extensionId} has no event-stream subscription.`);\n }\n await subscription.ready;\n await this.transport.reportReady(process.pid);\n };\n\n readonly onRequest = (handler: ExtensionRequestHandler): (() => void) =>\n this.eventBus.subscribeAll((event) => {\n if (event.type !== \"extension.request\") {\n return;\n }\n const request = readRequest(event.payload);\n if (!request || request.extensionId !== this.extensionId || request.generation !== this.generation) {\n return;\n }\n void handleRequest(this.transport, request, handler);\n });\n\n private readonly toEventBusEnvelope = (event: ExtensionTransportEnvelope): ExtensionTransportEnvelope => ({\n ...event,\n emittedAt: event.emittedAt ?? new Date().toISOString(),\n source: event.source ?? \"event-stream\",\n });\n\n private readonly startParentProcessWatcher = (): ReturnType<typeof setInterval> | null => {\n const parentPid = readParentProcessId();\n if (!parentPid) {\n return null;\n }\n const timer = setInterval(() => {\n if (isProcessRunning(parentPid)) {\n return;\n }\n this.close();\n process.exit(0);\n }, EXTENSION_PARENT_WATCH_INTERVAL_MS);\n (timer as NodeJS.Timeout).unref?.();\n return timer;\n };\n\n}\n"],"mappings":"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"}
@@ -27,7 +27,7 @@ var ExtensionTransportService = class {
27
27
  this.webSocketFactory = options.webSocketFactory;
28
28
  if (typeof this.fetchImpl !== "function") throw new Error("fetch is unavailable. Provide fetch when creating the extension.");
29
29
  }
30
- postIngress = async (type, payload) => {
30
+ postIngress = async (type, payload, options = {}) => {
31
31
  const envelope = {
32
32
  type,
33
33
  extensionId: this.extensionId,
@@ -42,10 +42,15 @@ var ExtensionTransportService = class {
42
42
  "content-type": "application/json",
43
43
  authorization: `Bearer ${this.token}`
44
44
  },
45
- body: JSON.stringify(envelope)
45
+ body: JSON.stringify(envelope),
46
+ signal: options.signal
46
47
  });
47
48
  const body = await response.json().catch(() => null);
48
- if (!response.ok) throw new Error(this.readErrorMessage(body, `NextClaw ingress failed with ${response.status}`));
49
+ if (!response.ok) {
50
+ const error = new Error(this.readErrorMessage(body, `NextClaw ingress failed with ${response.status}`));
51
+ Object.assign(error, { status: response.status });
52
+ throw error;
53
+ }
49
54
  return this.readResponseData(body);
50
55
  };
51
56
  respondToRequest = async (response) => {
@@ -1 +1 @@
1
- {"version":3,"file":"extension-transport.service.js","names":[],"sources":["../../src/services/extension-transport.service.ts"],"sourcesContent":["import type {\n ExtensionRequestResponse,\n ExtensionTransportEnvelope,\n NextClawExtensionOptions,\n NextClawExtensionWebSocketFactoryOptions,\n NextClawExtensionWebSocketLike,\n} from \"../types/extension-sdk.types.js\";\nimport { getKeyId, ingressKeys } from \"@nextclaw/shared\";\nimport { normalizeEndpoint, resolveWebSocketUrl } from \"../utils/extension-url.utils.js\";\nimport WebSocket from \"ws\";\n\ntype EventStreamHandler = (event: ExtensionTransportEnvelope) => void;\n\ntype RuntimeEnv = {\n NEXTCLAW_EXTENSION_ENDPOINT?: string;\n NEXTCLAW_EXTENSION_GENERATION?: string;\n NEXTCLAW_EXTENSION_TOKEN?: string;\n NEXTCLAW_EXTENSION_ID?: string;\n};\n\ndeclare const process: { env: RuntimeEnv } | undefined;\n\nfunction readRuntimeEnv(): RuntimeEnv {\n return typeof process === \"undefined\" ? {} : process.env;\n}\n\nfunction requireRuntimeValue(value: string | undefined, name: string): string {\n const trimmed = value?.trim();\n if (!trimmed) {\n throw new Error(`${name} is required.`);\n }\n return trimmed;\n}\n\nexport class ExtensionTransportService {\n readonly generation: string;\n readonly token: string;\n readonly extensionId: string;\n private readonly endpoint: string;\n private readonly fetchImpl: typeof fetch;\n private readonly webSocketFactory?: (\n url: string,\n options?: NextClawExtensionWebSocketFactoryOptions,\n ) => NextClawExtensionWebSocketLike;\n\n constructor(options: NextClawExtensionOptions = {}) {\n const env = readRuntimeEnv();\n this.endpoint = normalizeEndpoint(\n options.endpoint ?? requireRuntimeValue(env.NEXTCLAW_EXTENSION_ENDPOINT, \"NEXTCLAW_EXTENSION_ENDPOINT\"),\n );\n this.token = options.token ?? requireRuntimeValue(env.NEXTCLAW_EXTENSION_TOKEN, \"NEXTCLAW_EXTENSION_TOKEN\");\n this.extensionId =\n options.extensionId ?? requireRuntimeValue(env.NEXTCLAW_EXTENSION_ID, \"NEXTCLAW_EXTENSION_ID\");\n this.generation =\n options.generation ?? requireRuntimeValue(env.NEXTCLAW_EXTENSION_GENERATION, \"NEXTCLAW_EXTENSION_GENERATION\");\n this.fetchImpl = options.fetch ?? globalThis.fetch;\n this.webSocketFactory = options.webSocketFactory;\n if (typeof this.fetchImpl !== \"function\") {\n throw new Error(\"fetch is unavailable. Provide fetch when creating the extension.\");\n }\n }\n\n readonly postIngress = async <TResponse = unknown>(\n type: string,\n payload: unknown,\n ): Promise<TResponse> => {\n const envelope: ExtensionTransportEnvelope = {\n type,\n extensionId: this.extensionId,\n generation: this.generation,\n payload,\n emittedAt: new Date().toISOString(),\n source: \"extension-sdk\",\n };\n const response = await this.fetchImpl(`${this.endpoint}/webhook`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${this.token}`,\n },\n body: JSON.stringify(envelope),\n });\n const body = (await response.json().catch(() => null)) as unknown;\n if (!response.ok) {\n throw new Error(this.readErrorMessage(body, `NextClaw ingress failed with ${response.status}`));\n }\n return this.readResponseData<TResponse>(body);\n };\n\n readonly respondToRequest = async (response: ExtensionRequestResponse): Promise<void> => {\n await this.postIngress(getKeyId(ingressKeys.extension.response), response);\n };\n\n readonly reportReady = async (pid: number): Promise<void> => {\n await this.postIngress(getKeyId(ingressKeys.extension.runtimeReady), {\n generation: this.generation,\n pid,\n });\n };\n\n readonly subscribe = (handler: EventStreamHandler): { close: () => void; ready: Promise<void> } => {\n const socket = this.createSocket(resolveWebSocketUrl(this.endpoint, \"/ws\"), {\n headers: {\n authorization: `Bearer ${this.token}`,\n \"x-nextclaw-extension-id\": this.extensionId,\n \"x-nextclaw-extension-generation\": this.generation,\n },\n });\n let opened = false;\n let resolveReady!: () => void;\n let rejectReady!: (error: Error) => void;\n const ready = new Promise<void>((resolve, reject) => {\n resolveReady = resolve;\n rejectReady = reject;\n });\n void ready.catch(() => undefined);\n socket.onopen = () => {\n opened = true;\n resolveReady();\n };\n socket.onerror = () => {\n if (!opened) {\n rejectReady(new Error(`Extension ${this.extensionId} event stream failed before opening.`));\n }\n };\n socket.onclose = () => {\n if (!opened) {\n rejectReady(new Error(`Extension ${this.extensionId} event stream closed before opening.`));\n }\n };\n socket.onmessage = (event) => {\n const envelope = this.parseEnvelope(event.data);\n if (envelope) {\n handler(envelope);\n }\n };\n return {\n close: () => socket.close(),\n ready,\n };\n };\n\n private readonly createSocket = (\n url: string,\n options: NextClawExtensionWebSocketFactoryOptions,\n ): NextClawExtensionWebSocketLike => {\n if (this.webSocketFactory) {\n return this.webSocketFactory(url, options);\n }\n const socket = new WebSocket(url, { headers: options.headers });\n socket.on(\"error\", () => undefined);\n return socket as unknown as NextClawExtensionWebSocketLike;\n };\n\n private readonly parseEnvelope = (value: unknown): ExtensionTransportEnvelope | null => {\n if (typeof value !== \"string\") {\n return null;\n }\n try {\n const parsed = JSON.parse(value) as ExtensionTransportEnvelope;\n return parsed && typeof parsed === \"object\" && typeof parsed.type === \"string\" ? parsed : null;\n } catch {\n return null;\n }\n };\n\n private readonly readErrorMessage = (body: unknown, fallback: string): string => {\n if (!body || typeof body !== \"object\") {\n return fallback;\n }\n const error = (body as { error?: { message?: unknown } }).error;\n return typeof error?.message === \"string\" && error.message.trim() ? error.message : fallback;\n };\n\n private readonly readResponseData = <TResponse>(body: unknown): TResponse => {\n if (body && typeof body === \"object\" && !Array.isArray(body)) {\n const record = body as { ok?: unknown; data?: unknown };\n if (record.ok === true && \"data\" in record) {\n return record.data as TResponse;\n }\n }\n return body as TResponse;\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension-transport.service.js","names":[],"sources":["../../src/services/extension-transport.service.ts"],"sourcesContent":["import type {\n ExtensionRequestResponse,\n ExtensionTransportEnvelope,\n NextClawExtensionOptions,\n NextClawExtensionWebSocketFactoryOptions,\n NextClawExtensionWebSocketLike,\n} from \"../types/extension-sdk.types.js\";\nimport { getKeyId, ingressKeys } from \"@nextclaw/shared\";\nimport { normalizeEndpoint, resolveWebSocketUrl } from \"../utils/extension-url.utils.js\";\nimport WebSocket from \"ws\";\n\ntype EventStreamHandler = (event: ExtensionTransportEnvelope) => void;\n\ntype RuntimeEnv = {\n NEXTCLAW_EXTENSION_ENDPOINT?: string;\n NEXTCLAW_EXTENSION_GENERATION?: string;\n NEXTCLAW_EXTENSION_TOKEN?: string;\n NEXTCLAW_EXTENSION_ID?: string;\n};\n\ndeclare const process: { env: RuntimeEnv } | undefined;\n\nfunction readRuntimeEnv(): RuntimeEnv {\n return typeof process === \"undefined\" ? {} : process.env;\n}\n\nfunction requireRuntimeValue(value: string | undefined, name: string): string {\n const trimmed = value?.trim();\n if (!trimmed) {\n throw new Error(`${name} is required.`);\n }\n return trimmed;\n}\n\nexport class ExtensionTransportService {\n readonly generation: string;\n readonly token: string;\n readonly extensionId: string;\n private readonly endpoint: string;\n private readonly fetchImpl: typeof fetch;\n private readonly webSocketFactory?: (\n url: string,\n options?: NextClawExtensionWebSocketFactoryOptions,\n ) => NextClawExtensionWebSocketLike;\n\n constructor(options: NextClawExtensionOptions = {}) {\n const env = readRuntimeEnv();\n this.endpoint = normalizeEndpoint(\n options.endpoint ?? requireRuntimeValue(env.NEXTCLAW_EXTENSION_ENDPOINT, \"NEXTCLAW_EXTENSION_ENDPOINT\"),\n );\n this.token = options.token ?? requireRuntimeValue(env.NEXTCLAW_EXTENSION_TOKEN, \"NEXTCLAW_EXTENSION_TOKEN\");\n this.extensionId =\n options.extensionId ?? requireRuntimeValue(env.NEXTCLAW_EXTENSION_ID, \"NEXTCLAW_EXTENSION_ID\");\n this.generation =\n options.generation ?? requireRuntimeValue(env.NEXTCLAW_EXTENSION_GENERATION, \"NEXTCLAW_EXTENSION_GENERATION\");\n this.fetchImpl = options.fetch ?? globalThis.fetch;\n this.webSocketFactory = options.webSocketFactory;\n if (typeof this.fetchImpl !== \"function\") {\n throw new Error(\"fetch is unavailable. Provide fetch when creating the extension.\");\n }\n }\n\n readonly postIngress = async <TResponse = unknown>(\n type: string,\n payload: unknown,\n options: { signal?: AbortSignal } = {},\n ): Promise<TResponse> => {\n const envelope: ExtensionTransportEnvelope = {\n type,\n extensionId: this.extensionId,\n generation: this.generation,\n payload,\n emittedAt: new Date().toISOString(),\n source: \"extension-sdk\",\n };\n const response = await this.fetchImpl(`${this.endpoint}/webhook`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${this.token}`,\n },\n body: JSON.stringify(envelope),\n signal: options.signal,\n });\n const body = (await response.json().catch(() => null)) as unknown;\n if (!response.ok) {\n const error = new Error(this.readErrorMessage(body, `NextClaw ingress failed with ${response.status}`));\n Object.assign(error, { status: response.status });\n throw error;\n }\n return this.readResponseData<TResponse>(body);\n };\n\n readonly respondToRequest = async (response: ExtensionRequestResponse): Promise<void> => {\n await this.postIngress(getKeyId(ingressKeys.extension.response), response);\n };\n\n readonly reportReady = async (pid: number): Promise<void> => {\n await this.postIngress(getKeyId(ingressKeys.extension.runtimeReady), {\n generation: this.generation,\n pid,\n });\n };\n\n readonly subscribe = (handler: EventStreamHandler): { close: () => void; ready: Promise<void> } => {\n const socket = this.createSocket(resolveWebSocketUrl(this.endpoint, \"/ws\"), {\n headers: {\n authorization: `Bearer ${this.token}`,\n \"x-nextclaw-extension-id\": this.extensionId,\n \"x-nextclaw-extension-generation\": this.generation,\n },\n });\n let opened = false;\n let resolveReady!: () => void;\n let rejectReady!: (error: Error) => void;\n const ready = new Promise<void>((resolve, reject) => {\n resolveReady = resolve;\n rejectReady = reject;\n });\n void ready.catch(() => undefined);\n socket.onopen = () => {\n opened = true;\n resolveReady();\n };\n socket.onerror = () => {\n if (!opened) {\n rejectReady(new Error(`Extension ${this.extensionId} event stream failed before opening.`));\n }\n };\n socket.onclose = () => {\n if (!opened) {\n rejectReady(new Error(`Extension ${this.extensionId} event stream closed before opening.`));\n }\n };\n socket.onmessage = (event) => {\n const envelope = this.parseEnvelope(event.data);\n if (envelope) {\n handler(envelope);\n }\n };\n return {\n close: () => socket.close(),\n ready,\n };\n };\n\n private readonly createSocket = (\n url: string,\n options: NextClawExtensionWebSocketFactoryOptions,\n ): NextClawExtensionWebSocketLike => {\n if (this.webSocketFactory) {\n return this.webSocketFactory(url, options);\n }\n const socket = new WebSocket(url, { headers: options.headers });\n socket.on(\"error\", () => undefined);\n return socket as unknown as NextClawExtensionWebSocketLike;\n };\n\n private readonly parseEnvelope = (value: unknown): ExtensionTransportEnvelope | null => {\n if (typeof value !== \"string\") {\n return null;\n }\n try {\n const parsed = JSON.parse(value) as ExtensionTransportEnvelope;\n return parsed && typeof parsed === \"object\" && typeof parsed.type === \"string\" ? parsed : null;\n } catch {\n return null;\n }\n };\n\n private readonly readErrorMessage = (body: unknown, fallback: string): string => {\n if (!body || typeof body !== \"object\") {\n return fallback;\n }\n const error = (body as { error?: { message?: unknown } }).error;\n return typeof error?.message === \"string\" && error.message.trim() ? error.message : fallback;\n };\n\n private readonly readResponseData = <TResponse>(body: unknown): TResponse => {\n if (body && typeof body === \"object\" && !Array.isArray(body)) {\n const record = body as { ok?: unknown; data?: unknown };\n if (record.ok === true && \"data\" in record) {\n return record.data as TResponse;\n }\n }\n return body as TResponse;\n };\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { ExtensionChannelCommandExecuteIngressPayload as ChannelCommandExecuteRequest, ExtensionChannelCommandExecuteResponse, ExtensionChannelCommandExecuteResponse as ChannelCommandExecuteResponse, ExtensionChannelCommandListIngressPayload as ChannelCommandListRequest, ExtensionChannelCommandListResponse as ChannelCommandListResponse, ExtensionChannelCommandOption as ChannelCommandOption, ExtensionChannelCommandOptionType as ChannelCommandOptionType, ExtensionChannelCommandSpec, ExtensionChannelCommandSpec as ChannelCommandSpec, ExtensionChannelConfigGetIngressPayload as ChannelConfigGetRequest, ExtensionChannelFileContent as ChannelFileContent, ExtensionChannelImageContent as ChannelImageContent, ExtensionChannelMessageContent as ChannelMessageContent, ExtensionChannelMessageSubmitIngressPayload, ExtensionChannelMessageSubmitIngressPayload as ChannelSubmittedMessage, ExtensionChannelSubmittedAttachment as ChannelSubmittedAttachment, ExtensionChannelTextContent as ChannelTextContent, ExtensionResponseIngressPayload as ExtensionRequestResponse, Unsubscribe } from "@nextclaw/shared";
1
+ import { ExtensionChannelCommandExecuteIngressPayload as ChannelCommandExecuteRequest, ExtensionChannelCommandExecuteResponse, ExtensionChannelCommandExecuteResponse as ChannelCommandExecuteResponse, ExtensionChannelCommandListIngressPayload as ChannelCommandListRequest, ExtensionChannelCommandListResponse as ChannelCommandListResponse, ExtensionChannelCommandOption as ChannelCommandOption, ExtensionChannelCommandOptionType as ChannelCommandOptionType, ExtensionChannelCommandSpec, ExtensionChannelCommandSpec as ChannelCommandSpec, ExtensionChannelConfigGetIngressPayload as ChannelConfigGetRequest, ExtensionChannelFileContent as ChannelFileContent, ExtensionChannelImageContent as ChannelImageContent, ExtensionChannelMessageContent as ChannelMessageContent, ExtensionChannelMessageSubmitIngressPayload, ExtensionChannelMessageSubmitIngressPayload as ChannelSubmittedMessage, ExtensionChannelSubmittedAttachment as ChannelSubmittedAttachment, ExtensionChannelTextContent as ChannelTextContent, ExtensionDiagnosticIngressPayload, ExtensionResponseIngressPayload as ExtensionRequestResponse, Unsubscribe } from "@nextclaw/shared";
2
2
  import { NcpEndpointEvent } from "@nextclaw/ncp";
3
3
 
4
4
  //#region src/types/extension-sdk.types.d.ts
@@ -7,6 +7,7 @@ type NextClawExtensionOptions = {
7
7
  token?: string;
8
8
  extensionId?: string;
9
9
  generation?: string;
10
+ diagnosticTimeoutMs?: number;
10
11
  fetch?: typeof fetch;
11
12
  webSocketFactory?: (url: string, options?: NextClawExtensionWebSocketFactoryOptions) => NextClawExtensionWebSocketLike;
12
13
  };
@@ -60,6 +61,10 @@ type ExtensionChannelCommands = {
60
61
  metadata?: Record<string, unknown>;
61
62
  }) => Promise<ExtensionChannelCommandExecuteResponse | null>;
62
63
  };
64
+ type ExtensionDiagnostics = {
65
+ createTraceId: (providerMessageId?: string) => string;
66
+ emit: (input: ExtensionDiagnosticIngressPayload) => Promise<boolean>;
67
+ };
63
68
  type ExtensionChannel = {
64
69
  id: string;
65
70
  submitMessage: (input: Omit<ExtensionChannelMessageSubmitIngressPayload, "channelId">) => Promise<void>;
@@ -78,5 +83,5 @@ type ExtensionCapabilities = {
78
83
  provideHandler: (kind: string, handler: ExtensionCapabilityHandler) => Unsubscribe;
79
84
  };
80
85
  //#endregion
81
- export { type ChannelCommandExecuteRequest, type ChannelCommandExecuteResponse, type ChannelCommandListRequest, type ChannelCommandListResponse, type ChannelCommandOption, type ChannelCommandOptionType, type ChannelCommandSpec, type ChannelConfigGetRequest, ChannelConfigGetResponse, type ChannelFileContent, type ChannelImageContent, type ChannelMessageContent, type ChannelSubmittedAttachment, type ChannelSubmittedMessage, type ChannelTextContent, ExtensionCapabilities, ExtensionCapabilityHandler, ExtensionCapabilityPayload, ExtensionChannel, ExtensionChannelCommands, ExtensionChannelConfig, ExtensionChannels, ExtensionRequest, ExtensionRequestHandler, type ExtensionRequestResponse, ExtensionTransportEnvelope, NextClawExtensionOptions, NextClawExtensionWebSocketLike };
86
+ export { type ChannelCommandExecuteRequest, type ChannelCommandExecuteResponse, type ChannelCommandListRequest, type ChannelCommandListResponse, type ChannelCommandOption, type ChannelCommandOptionType, type ChannelCommandSpec, type ChannelConfigGetRequest, ChannelConfigGetResponse, type ChannelFileContent, type ChannelImageContent, type ChannelMessageContent, type ChannelSubmittedAttachment, type ChannelSubmittedMessage, type ChannelTextContent, ExtensionCapabilities, ExtensionCapabilityHandler, ExtensionCapabilityPayload, ExtensionChannel, ExtensionChannelCommands, ExtensionChannelConfig, ExtensionChannels, ExtensionDiagnostics, ExtensionRequest, ExtensionRequestHandler, type ExtensionRequestResponse, ExtensionTransportEnvelope, NextClawExtensionOptions, NextClawExtensionWebSocketLike };
82
87
  //# sourceMappingURL=extension-sdk.types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension-sdk.types.d.ts","names":[],"sources":["../../src/types/extension-sdk.types.ts"],"mappings":";;;;KAyBY,wBAAA;EACV,QAAA;EACA,KAAA;EACA,WAAA;EACA,UAAA;EACA,KAAA,UAAe,KAAA;EACf,gBAAA,IACE,GAAA,UACA,OAAA,GAAU,wCAAA,KACP,8BAAA;AAAA;AAAA,KAGK,wCAAA;EACV,OAAA,GAAU,MAAA;AAAA;AAAA,KAGA,8BAAA;EACV,MAAA;EACA,SAAA,IAAa,KAAA;IAAS,IAAA;EAAA;EACtB,OAAA,IAAW,KAAA;EACX,OAAA;EACA,KAAA;AAAA;AAAA,KAGU,0BAAA;EACV,IAAA;EACA,WAAA;EACA,UAAA;EACA,OAAA,EAAS,QAAA;EACT,SAAA;EACA,MAAA;AAAA;AAAA,KAGU,gBAAA;EACV,SAAA;EACA,WAAA;EACA,UAAA;EACA,IAAA;EACA,OAAA,GAAU,MAAA;AAAA;AAAA,KAGA,wBAAA;EACV,MAAA,EAAQ,OAAA;AAAA;AAAA,KAGE,sBAAA;EACV,GAAA,2BAA8B,OAAA,CAAQ,OAAA;EACtC,QAAA,sBACE,OAAA,GAAU,MAAA,EAAQ,OAAA,YAAmB,OAAA,WAClC,WAAA;AAAA;AAAA,KAGK,wBAAA;EACV,IAAA,QAAY,OAAA,CAAQ,2BAAA;EACpB,OAAA,GAAU,KAAA;IACR,WAAA;IACA,IAAA,GAAO,MAAA;IACP,cAAA;IACA,QAAA;IACA,QAAA,GAAW,MAAA;EAAA,MACP,OAAA,CAAQ,sCAAA;EACd,WAAA,GAAc,KAAA;IACZ,OAAA;IACA,cAAA;IACA,QAAA;IACA,QAAA,GAAW,MAAA;EAAA,MACP,OAAA,CAAQ,sCAAA;AAAA;AAAA,KAGJ,gBAAA;EACV,EAAA;EACA,aAAA,GAAgB,KAAA,EAAO,IAAA,CAAK,2CAAA,mBAA8D,OAAA;EAC1F,UAAA,GAAa,OAAA,GAAU,KAAA,EAAO,gBAAA,YAA4B,OAAA,WAAkB,WAAA;EAC5E,MAAA,EAAQ,sBAAA;EACR,QAAA,EAAU,wBAAA;AAAA;AAAA,KAGA,iBAAA;EACV,GAAA,GAAM,SAAA,aAAsB,gBAAA;AAAA;AAAA,KAGlB,uBAAA,IAA2B,OAAA,EAAS,gBAAA,eAA+B,OAAA;AAAA,KAEnE,0BAAA,GAA6B,MAAA;AAAA,KAE7B,0BAAA,kBAA4C,0BAAA,GAA6B,0BAAA,KACnF,OAAA,EAAS,QAAA,EACT,OAAA,EAAS,gBAAA,eACI,OAAA;AAAA,KAEH,qBAAA;EACV,OAAA,GAAU,SAAA,UAAmB,UAAA,aAAuB,WAAA;EACpD,cAAA,GAAiB,IAAA,UAAc,OAAA,EAAS,0BAAA,KAA+B,WAAA;AAAA"}
1
+ {"version":3,"file":"extension-sdk.types.d.ts","names":[],"sources":["../../src/types/extension-sdk.types.ts"],"mappings":";;;;KA2BY,wBAAA;EACV,QAAA;EACA,KAAA;EACA,WAAA;EACA,UAAA;EACA,mBAAA;EACA,KAAA,UAAe,KAAA;EACf,gBAAA,IACE,GAAA,UACA,OAAA,GAAU,wCAAA,KACP,8BAAA;AAAA;AAAA,KAGK,wCAAA;EACV,OAAA,GAAU,MAAA;AAAA;AAAA,KAGA,8BAAA;EACV,MAAA;EACA,SAAA,IAAa,KAAA;IAAS,IAAA;EAAA;EACtB,OAAA,IAAW,KAAA;EACX,OAAA;EACA,KAAA;AAAA;AAAA,KAGU,0BAAA;EACV,IAAA;EACA,WAAA;EACA,UAAA;EACA,OAAA,EAAS,QAAA;EACT,SAAA;EACA,MAAA;AAAA;AAAA,KAGU,gBAAA;EACV,SAAA;EACA,WAAA;EACA,UAAA;EACA,IAAA;EACA,OAAA,GAAU,MAAA;AAAA;AAAA,KAGA,wBAAA;EACV,MAAA,EAAQ,OAAA;AAAA;AAAA,KAGE,sBAAA;EACV,GAAA,2BAA8B,OAAA,CAAQ,OAAA;EACtC,QAAA,sBACE,OAAA,GAAU,MAAA,EAAQ,OAAA,YAAmB,OAAA,WAClC,WAAA;AAAA;AAAA,KAGK,wBAAA;EACV,IAAA,QAAY,OAAA,CAAQ,2BAAA;EACpB,OAAA,GAAU,KAAA;IACR,WAAA;IACA,IAAA,GAAO,MAAA;IACP,cAAA;IACA,QAAA;IACA,QAAA,GAAW,MAAA;EAAA,MACP,OAAA,CAAQ,sCAAA;EACd,WAAA,GAAc,KAAA;IACZ,OAAA;IACA,cAAA;IACA,QAAA;IACA,QAAA,GAAW,MAAA;EAAA,MACP,OAAA,CAAQ,sCAAA;AAAA;AAAA,KAGJ,oBAAA;EACV,aAAA,GAAgB,iBAAA;EAChB,IAAA,GAAO,KAAA,EAAO,iCAAA,KAAsC,OAAA;AAAA;AAAA,KAG1C,gBAAA;EACV,EAAA;EACA,aAAA,GAAgB,KAAA,EAAO,IAAA,CAAK,2CAAA,mBAA8D,OAAA;EAC1F,UAAA,GAAa,OAAA,GAAU,KAAA,EAAO,gBAAA,YAA4B,OAAA,WAAkB,WAAA;EAC5E,MAAA,EAAQ,sBAAA;EACR,QAAA,EAAU,wBAAA;AAAA;AAAA,KAKA,iBAAA;EACV,GAAA,GAAM,SAAA,aAAsB,gBAAA;AAAA;AAAA,KAGlB,uBAAA,IAA2B,OAAA,EAAS,gBAAA,eAA+B,OAAA;AAAA,KAEnE,0BAAA,GAA6B,MAAA;AAAA,KAE7B,0BAAA,kBAA4C,0BAAA,GAA6B,0BAAA,KACnF,OAAA,EAAS,QAAA,EACT,OAAA,EAAS,gBAAA,eACI,OAAA;AAAA,KAEH,qBAAA;EACV,OAAA,GAAU,SAAA,UAAmB,UAAA,aAAuB,WAAA;EACpD,cAAA,GAAiB,IAAA,UAAc,OAAA,EAAS,0BAAA,KAA+B,WAAA;AAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/extension-sdk",
3
- "version": "0.3.23",
3
+ "version": "0.3.25",
4
4
  "private": false,
5
5
  "description": "Backend SDK for NextClaw extension server processes.",
6
6
  "type": "module",
@@ -36,8 +36,8 @@
36
36
  ],
37
37
  "dependencies": {
38
38
  "ws": "^8.18.0",
39
- "@nextclaw/ncp": "0.7.17",
40
- "@nextclaw/shared": "0.4.23"
39
+ "@nextclaw/shared": "0.4.25",
40
+ "@nextclaw/ncp": "0.8.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "^20.17.6",