@nextclaw/extension-sdk 0.3.19 → 0.3.21
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/services/extension-channel-controller.service.d.ts +2 -2
- package/dist/services/extension-channel-controller.service.d.ts.map +1 -1
- package/dist/services/extension-channel-controller.service.js +52 -6
- package/dist/services/extension-channel-controller.service.js.map +1 -1
- package/dist/services/extension-client.service.d.ts +2 -0
- package/dist/services/extension-client.service.d.ts.map +1 -1
- package/dist/services/extension-client.service.js +12 -3
- package/dist/services/extension-client.service.js.map +1 -1
- package/dist/services/extension-transport.service.js +33 -2
- package/dist/services/extension-transport.service.js.map +1 -1
- package/dist/types/extension-sdk.types.d.ts +3 -0
- package/dist/types/extension-sdk.types.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -56,7 +56,7 @@ type BusChannelCreateContext<TConfig, TBus = BusChannelMessageBus> = {
|
|
|
56
56
|
};
|
|
57
57
|
type BusChannelExtensionDefinition<TConfig, TBus = BusChannelMessageBus> = {
|
|
58
58
|
channelId: string;
|
|
59
|
-
createChannel: (context: BusChannelCreateContext<TConfig, TBus>) => BusChannelRuntime
|
|
59
|
+
createChannel: (context: BusChannelCreateContext<TConfig, TBus>) => BusChannelRuntime | Promise<BusChannelRuntime>;
|
|
60
60
|
mapInbound?: ExtensionChannelInboundMapper<BusChannelInboundMessage>;
|
|
61
61
|
onChannelStartError?: (error: unknown) => void | Promise<void>;
|
|
62
62
|
};
|
|
@@ -72,7 +72,7 @@ type ChannelExtensionContext = {
|
|
|
72
72
|
};
|
|
73
73
|
type ChannelExtensionDefinition<TConfig, TInbound> = {
|
|
74
74
|
channelId: string;
|
|
75
|
-
createAdapter: (context: ChannelExtensionContext) => ExtensionChannelAdapter<TConfig, TInbound
|
|
75
|
+
createAdapter: (context: ChannelExtensionContext) => ExtensionChannelAdapter<TConfig, TInbound> | Promise<ExtensionChannelAdapter<TConfig, TInbound>>;
|
|
76
76
|
mapInbound: ExtensionChannelInboundMapper<TInbound>;
|
|
77
77
|
createAuthCapability?: (context: ChannelExtensionContext) => object;
|
|
78
78
|
onNcpEventError?: (error: unknown, event: NcpEndpointEvent) => void | Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension-channel-controller.service.d.ts","names":[],"sources":["../../src/services/extension-channel-controller.service.ts"],"mappings":";;;;KAgDY,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;AAAA;AAAA,KAGC,6BAAA,iBAA8C,oBAAA;EACxD,SAAA;EACA,aAAA,
|
|
1
|
+
{"version":3,"file":"extension-channel-controller.service.d.ts","names":[],"sources":["../../src/services/extension-channel-controller.service.ts"],"mappings":"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"}
|
|
@@ -28,9 +28,54 @@ function readString(value) {
|
|
|
28
28
|
function renderMessageText(parts) {
|
|
29
29
|
return parts.map((part) => (part.type === "text" || part.type === "rich-text") && part.text ? part.text : "").filter(Boolean).join("\n\n");
|
|
30
30
|
}
|
|
31
|
-
function
|
|
32
|
-
return
|
|
31
|
+
function isChannelEnabled(config) {
|
|
32
|
+
return Boolean(config && typeof config === "object" && !Array.isArray(config) && config.enabled === true);
|
|
33
33
|
}
|
|
34
|
+
var LazyExtensionChannelAdapter = class {
|
|
35
|
+
adapterCleanup = null;
|
|
36
|
+
adapterPromise = null;
|
|
37
|
+
messageHandler = null;
|
|
38
|
+
constructor(createAdapter) {
|
|
39
|
+
this.createAdapter = createAdapter;
|
|
40
|
+
}
|
|
41
|
+
configure = async (config) => {
|
|
42
|
+
await (await this.getAdapter()).configure(config);
|
|
43
|
+
};
|
|
44
|
+
start = async () => {
|
|
45
|
+
await (await this.getAdapter()).start();
|
|
46
|
+
};
|
|
47
|
+
stop = async () => {
|
|
48
|
+
if (this.adapterPromise) await (await this.adapterPromise).stop();
|
|
49
|
+
};
|
|
50
|
+
onMessage = (handler) => {
|
|
51
|
+
this.messageHandler = handler;
|
|
52
|
+
if (this.adapterPromise) this.adapterPromise.then((adapter) => this.attachMessageHandler(adapter)).catch(() => void 0);
|
|
53
|
+
return () => {
|
|
54
|
+
if (this.messageHandler === handler) {
|
|
55
|
+
this.messageHandler = null;
|
|
56
|
+
this.adapterCleanup?.();
|
|
57
|
+
this.adapterCleanup = null;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
sendNcpEvent = async (event) => {
|
|
62
|
+
if (this.adapterPromise) await (await this.adapterPromise).sendNcpEvent(event);
|
|
63
|
+
};
|
|
64
|
+
sendOutboundText = async (params) => {
|
|
65
|
+
await (await this.getAdapter()).sendOutboundText(params);
|
|
66
|
+
};
|
|
67
|
+
getAdapter = async () => {
|
|
68
|
+
this.adapterPromise ??= Promise.resolve(this.createAdapter()).then((adapter) => {
|
|
69
|
+
this.attachMessageHandler(adapter);
|
|
70
|
+
return adapter;
|
|
71
|
+
});
|
|
72
|
+
return await this.adapterPromise;
|
|
73
|
+
};
|
|
74
|
+
attachMessageHandler = (adapter) => {
|
|
75
|
+
this.adapterCleanup?.();
|
|
76
|
+
this.adapterCleanup = this.messageHandler ? adapter.onMessage(this.messageHandler) : null;
|
|
77
|
+
};
|
|
78
|
+
};
|
|
34
79
|
var ExtensionChannelController = class {
|
|
35
80
|
cleanups = [];
|
|
36
81
|
started = false;
|
|
@@ -59,11 +104,11 @@ var ExtensionChannelController = class {
|
|
|
59
104
|
};
|
|
60
105
|
applyConfig = async () => {
|
|
61
106
|
const config = await this.options.channel.config.get();
|
|
62
|
-
|
|
63
|
-
if (defaultChannelEnabled(config) === false) {
|
|
107
|
+
if (!isChannelEnabled(config)) {
|
|
64
108
|
await this.options.adapter.stop();
|
|
65
109
|
return;
|
|
66
110
|
}
|
|
111
|
+
await this.options.adapter.configure(config);
|
|
67
112
|
await this.options.adapter.start();
|
|
68
113
|
};
|
|
69
114
|
submitMessage = async (message) => {
|
|
@@ -89,7 +134,7 @@ var BusChannelAdapter = class {
|
|
|
89
134
|
}
|
|
90
135
|
configure = async (config) => {
|
|
91
136
|
await this.stop();
|
|
92
|
-
this.channel = this.definition.createChannel({
|
|
137
|
+
this.channel = await this.definition.createChannel({
|
|
93
138
|
config,
|
|
94
139
|
bus: { publishInbound: async (message) => {
|
|
95
140
|
await this.messageHandler?.(message);
|
|
@@ -158,7 +203,7 @@ async function startChannelExtension(definition, options = {}) {
|
|
|
158
203
|
const channel = extension.channels.use(definition.channelId);
|
|
159
204
|
const controller = new ExtensionChannelController({
|
|
160
205
|
channel,
|
|
161
|
-
adapter: definition.createAdapter({ channel }),
|
|
206
|
+
adapter: new LazyExtensionChannelAdapter(() => definition.createAdapter({ channel })),
|
|
162
207
|
mapInbound: definition.mapInbound,
|
|
163
208
|
onNcpEventError: definition.onNcpEventError
|
|
164
209
|
});
|
|
@@ -172,6 +217,7 @@ async function startChannelExtension(definition, options = {}) {
|
|
|
172
217
|
metadata: readOptionalRecord(payload.metadata)
|
|
173
218
|
}));
|
|
174
219
|
await controller.start();
|
|
220
|
+
await extension.ready();
|
|
175
221
|
}
|
|
176
222
|
async function startBusChannelExtension(definition, options = {}) {
|
|
177
223
|
await startChannelExtension({
|
|
@@ -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: (context: BusChannelCreateContext<TConfig, TBus>) => 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: (context: ChannelExtensionContext) => ExtensionChannelAdapter<TConfig, TInbound>;\n mapInbound: ExtensionChannelInboundMapper<TInbound>;\n createAuthCapability?: (context: ChannelExtensionContext) => object;\n onNcpEventError?: (error: unknown, event: NcpEndpointEvent) => void | Promise<void>;\n};\n\nfunction defaultChannelEnabled(config: unknown): boolean {\n return !(\n config &&\n typeof config === \"object\" &&\n !Array.isArray(config) &&\n (config as { enabled?: unknown }).enabled === false\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 await this.options.adapter.configure(config);\n if (defaultChannelEnabled(config) === false) {\n await this.options.adapter.stop();\n return;\n }\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 = 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 = 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}\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":"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"}
|
|
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":"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"}
|
|
@@ -7,11 +7,13 @@ declare class NextClawExtension {
|
|
|
7
7
|
readonly channels: ExtensionChannels;
|
|
8
8
|
readonly capabilities: ExtensionCapabilities;
|
|
9
9
|
readonly extensionId: string;
|
|
10
|
+
readonly generation: string;
|
|
10
11
|
private readonly transport;
|
|
11
12
|
private eventStreamSubscription;
|
|
12
13
|
private parentProcessWatcher;
|
|
13
14
|
constructor(options?: NextClawExtensionOptions);
|
|
14
15
|
readonly close: () => void;
|
|
16
|
+
readonly ready: () => Promise<void>;
|
|
15
17
|
readonly onRequest: (handler: ExtensionRequestHandler) => (() => void);
|
|
16
18
|
private readonly toEventBusEnvelope;
|
|
17
19
|
private readonly startParentProcessWatcher;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension-client.service.d.ts","names":[],"sources":["../../src/services/extension-client.service.ts"],"mappings":";;;;
|
|
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"}
|
|
@@ -25,10 +25,11 @@ var ExtensionChannelRegistry = class {
|
|
|
25
25
|
function readRequest(payload) {
|
|
26
26
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
|
|
27
27
|
const record = payload;
|
|
28
|
-
if (typeof record.requestId !== "string" || typeof record.extensionId !== "string" || typeof record.kind !== "string") return null;
|
|
28
|
+
if (typeof record.requestId !== "string" || typeof record.extensionId !== "string" || typeof record.generation !== "string" || typeof record.kind !== "string") return null;
|
|
29
29
|
return {
|
|
30
30
|
requestId: record.requestId,
|
|
31
31
|
extensionId: record.extensionId,
|
|
32
|
+
generation: record.generation,
|
|
32
33
|
kind: record.kind,
|
|
33
34
|
payload: record.payload && typeof record.payload === "object" && !Array.isArray(record.payload) ? record.payload : {}
|
|
34
35
|
};
|
|
@@ -85,7 +86,7 @@ var ExtensionCapabilityRegistry = class {
|
|
|
85
86
|
return this.params.eventBus.subscribeAll((event) => {
|
|
86
87
|
if (event.type !== "extension.request") return;
|
|
87
88
|
const request = readRequest(event.payload);
|
|
88
|
-
if (!request || request.extensionId !== this.params.extensionId || request.kind !== normalizedKind) return;
|
|
89
|
+
if (!request || request.extensionId !== this.params.extensionId || request.generation !== this.params.transport.generation || request.kind !== normalizedKind) return;
|
|
89
90
|
handleRequest(this.params.transport, request, async (matchedRequest) => await handler(matchedRequest.payload ?? {}, matchedRequest));
|
|
90
91
|
});
|
|
91
92
|
};
|
|
@@ -96,12 +97,14 @@ var NextClawExtension = class {
|
|
|
96
97
|
channels;
|
|
97
98
|
capabilities;
|
|
98
99
|
extensionId;
|
|
100
|
+
generation;
|
|
99
101
|
transport;
|
|
100
102
|
eventStreamSubscription = null;
|
|
101
103
|
parentProcessWatcher = null;
|
|
102
104
|
constructor(options = {}) {
|
|
103
105
|
this.transport = new ExtensionTransportService(options);
|
|
104
106
|
this.extensionId = this.transport.extensionId;
|
|
107
|
+
this.generation = this.transport.generation;
|
|
105
108
|
this.eventBus = new EventBus({
|
|
106
109
|
onFirstSubscriber: () => {
|
|
107
110
|
this.eventStreamSubscription ??= this.transport.subscribe((event) => {
|
|
@@ -132,10 +135,16 @@ var NextClawExtension = class {
|
|
|
132
135
|
this.eventStreamSubscription?.close();
|
|
133
136
|
this.eventStreamSubscription = null;
|
|
134
137
|
};
|
|
138
|
+
ready = async () => {
|
|
139
|
+
const subscription = this.eventStreamSubscription;
|
|
140
|
+
if (!subscription) throw new Error(`Extension ${this.extensionId} has no event-stream subscription.`);
|
|
141
|
+
await subscription.ready;
|
|
142
|
+
await this.transport.reportReady(process.pid);
|
|
143
|
+
};
|
|
135
144
|
onRequest = (handler) => this.eventBus.subscribeAll((event) => {
|
|
136
145
|
if (event.type !== "extension.request") return;
|
|
137
146
|
const request = readRequest(event.payload);
|
|
138
|
-
if (!request || request.extensionId !== this.extensionId) return;
|
|
147
|
+
if (!request || request.extensionId !== this.extensionId || request.generation !== this.generation) return;
|
|
139
148
|
handleRequest(this.transport, request, handler);
|
|
140
149
|
});
|
|
141
150
|
toEventBusEnvelope = (event) => ({
|
|
@@ -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.kind !== \"string\"\n ) {\n return null;\n }\n return {\n requestId: record.requestId,\n extensionId: record.extensionId,\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 (!request || request.extensionId !== this.params.extensionId || request.kind !== normalizedKind) {\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 private readonly transport: ExtensionTransportService;\n private eventStreamSubscription: { close: () => 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.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 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) {\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 } 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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"}
|
|
@@ -11,6 +11,7 @@ function requireRuntimeValue(value, name) {
|
|
|
11
11
|
return trimmed;
|
|
12
12
|
}
|
|
13
13
|
var ExtensionTransportService = class {
|
|
14
|
+
generation;
|
|
14
15
|
token;
|
|
15
16
|
extensionId;
|
|
16
17
|
endpoint;
|
|
@@ -21,6 +22,7 @@ var ExtensionTransportService = class {
|
|
|
21
22
|
this.endpoint = normalizeEndpoint(options.endpoint ?? requireRuntimeValue(env.NEXTCLAW_EXTENSION_ENDPOINT, "NEXTCLAW_EXTENSION_ENDPOINT"));
|
|
22
23
|
this.token = options.token ?? requireRuntimeValue(env.NEXTCLAW_EXTENSION_TOKEN, "NEXTCLAW_EXTENSION_TOKEN");
|
|
23
24
|
this.extensionId = options.extensionId ?? requireRuntimeValue(env.NEXTCLAW_EXTENSION_ID, "NEXTCLAW_EXTENSION_ID");
|
|
25
|
+
this.generation = options.generation ?? requireRuntimeValue(env.NEXTCLAW_EXTENSION_GENERATION, "NEXTCLAW_EXTENSION_GENERATION");
|
|
24
26
|
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
25
27
|
this.webSocketFactory = options.webSocketFactory;
|
|
26
28
|
if (typeof this.fetchImpl !== "function") throw new Error("fetch is unavailable. Provide fetch when creating the extension.");
|
|
@@ -29,6 +31,7 @@ var ExtensionTransportService = class {
|
|
|
29
31
|
const envelope = {
|
|
30
32
|
type,
|
|
31
33
|
extensionId: this.extensionId,
|
|
34
|
+
generation: this.generation,
|
|
32
35
|
payload,
|
|
33
36
|
emittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
34
37
|
source: "extension-sdk"
|
|
@@ -48,16 +51,44 @@ var ExtensionTransportService = class {
|
|
|
48
51
|
respondToRequest = async (response) => {
|
|
49
52
|
await this.postIngress(getKeyId(ingressKeys.extension.response), response);
|
|
50
53
|
};
|
|
54
|
+
reportReady = async (pid) => {
|
|
55
|
+
await this.postIngress(getKeyId(ingressKeys.extension.runtimeReady), {
|
|
56
|
+
generation: this.generation,
|
|
57
|
+
pid
|
|
58
|
+
});
|
|
59
|
+
};
|
|
51
60
|
subscribe = (handler) => {
|
|
52
61
|
const socket = this.createSocket(resolveWebSocketUrl(this.endpoint, "/ws"), { headers: {
|
|
53
62
|
authorization: `Bearer ${this.token}`,
|
|
54
|
-
"x-nextclaw-extension-id": this.extensionId
|
|
63
|
+
"x-nextclaw-extension-id": this.extensionId,
|
|
64
|
+
"x-nextclaw-extension-generation": this.generation
|
|
55
65
|
} });
|
|
66
|
+
let opened = false;
|
|
67
|
+
let resolveReady;
|
|
68
|
+
let rejectReady;
|
|
69
|
+
const ready = new Promise((resolve, reject) => {
|
|
70
|
+
resolveReady = resolve;
|
|
71
|
+
rejectReady = reject;
|
|
72
|
+
});
|
|
73
|
+
ready.catch(() => void 0);
|
|
74
|
+
socket.onopen = () => {
|
|
75
|
+
opened = true;
|
|
76
|
+
resolveReady();
|
|
77
|
+
};
|
|
78
|
+
socket.onerror = () => {
|
|
79
|
+
if (!opened) rejectReady(/* @__PURE__ */ new Error(`Extension ${this.extensionId} event stream failed before opening.`));
|
|
80
|
+
};
|
|
81
|
+
socket.onclose = () => {
|
|
82
|
+
if (!opened) rejectReady(/* @__PURE__ */ new Error(`Extension ${this.extensionId} event stream closed before opening.`));
|
|
83
|
+
};
|
|
56
84
|
socket.onmessage = (event) => {
|
|
57
85
|
const envelope = this.parseEnvelope(event.data);
|
|
58
86
|
if (envelope) handler(envelope);
|
|
59
87
|
};
|
|
60
|
-
return {
|
|
88
|
+
return {
|
|
89
|
+
close: () => socket.close(),
|
|
90
|
+
ready
|
|
91
|
+
};
|
|
61
92
|
};
|
|
62
93
|
createSocket = (url, options) => {
|
|
63
94
|
if (this.webSocketFactory) return this.webSocketFactory(url, options);
|
|
@@ -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_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 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.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 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 subscribe = (handler: EventStreamHandler): { close: () => 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 },\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 };\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":";;;;
|
|
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":";;;;AAsBA,SAAS,iBAA6B;AACpC,QAAO,OAAO,YAAY,cAAc,EAAE,GAAG,QAAQ;;AAGvD,SAAS,oBAAoB,OAA2B,MAAsB;CAC5E,MAAM,UAAU,OAAO,MAAM;AAC7B,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,GAAG,KAAK,eAAe;AAEzC,QAAO;;AAGT,IAAa,4BAAb,MAAuC;CACrC;CACA;CACA;CACA;CACA;CACA;CAKA,YAAY,UAAoC,EAAE,EAAE;EAClD,MAAM,MAAM,gBAAgB;AAC5B,OAAK,WAAW,kBACd,QAAQ,YAAY,oBAAoB,IAAI,6BAA6B,8BAA8B,CACxG;AACD,OAAK,QAAQ,QAAQ,SAAS,oBAAoB,IAAI,0BAA0B,2BAA2B;AAC3G,OAAK,cACH,QAAQ,eAAe,oBAAoB,IAAI,uBAAuB,wBAAwB;AAChG,OAAK,aACH,QAAQ,cAAc,oBAAoB,IAAI,+BAA+B,gCAAgC;AAC/G,OAAK,YAAY,QAAQ,SAAS,WAAW;AAC7C,OAAK,mBAAmB,QAAQ;AAChC,MAAI,OAAO,KAAK,cAAc,WAC5B,OAAM,IAAI,MAAM,mEAAmE;;CAIvF,cAAuB,OACrB,MACA,YACuB;EACvB,MAAM,WAAuC;GAC3C;GACA,aAAa,KAAK;GAClB,YAAY,KAAK;GACjB;GACA,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC,QAAQ;GACT;EACD,MAAM,WAAW,MAAM,KAAK,UAAU,GAAG,KAAK,SAAS,WAAW;GAChE,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,eAAe,UAAU,KAAK;IAC/B;GACD,MAAM,KAAK,UAAU,SAAS;GAC/B,CAAC;EACF,MAAM,OAAQ,MAAM,SAAS,MAAM,CAAC,YAAY,KAAK;AACrD,MAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,KAAK,iBAAiB,MAAM,gCAAgC,SAAS,SAAS,CAAC;AAEjG,SAAO,KAAK,iBAA4B,KAAK;;CAG/C,mBAA4B,OAAO,aAAsD;AACvF,QAAM,KAAK,YAAY,SAAS,YAAY,UAAU,SAAS,EAAE,SAAS;;CAG5E,cAAuB,OAAO,QAA+B;AAC3D,QAAM,KAAK,YAAY,SAAS,YAAY,UAAU,aAAa,EAAE;GACnE,YAAY,KAAK;GACjB;GACD,CAAC;;CAGJ,aAAsB,YAA6E;EACjG,MAAM,SAAS,KAAK,aAAa,oBAAoB,KAAK,UAAU,MAAM,EAAE,EAC1E,SAAS;GACP,eAAe,UAAU,KAAK;GAC9B,2BAA2B,KAAK;GAChC,mCAAmC,KAAK;GACzC,EACF,CAAC;EACF,IAAI,SAAS;EACb,IAAI;EACJ,IAAI;EACJ,MAAM,QAAQ,IAAI,SAAe,SAAS,WAAW;AACnD,kBAAe;AACf,iBAAc;IACd;AACG,QAAM,YAAY,KAAA,EAAU;AACjC,SAAO,eAAe;AACpB,YAAS;AACT,iBAAc;;AAEhB,SAAO,gBAAgB;AACrB,OAAI,CAAC,OACH,6BAAY,IAAI,MAAM,aAAa,KAAK,YAAY,sCAAsC,CAAC;;AAG/F,SAAO,gBAAgB;AACrB,OAAI,CAAC,OACH,6BAAY,IAAI,MAAM,aAAa,KAAK,YAAY,sCAAsC,CAAC;;AAG/F,SAAO,aAAa,UAAU;GAC5B,MAAM,WAAW,KAAK,cAAc,MAAM,KAAK;AAC/C,OAAI,SACF,SAAQ,SAAS;;AAGrB,SAAO;GACL,aAAa,OAAO,OAAO;GAC3B;GACD;;CAGH,gBACE,KACA,YACmC;AACnC,MAAI,KAAK,iBACP,QAAO,KAAK,iBAAiB,KAAK,QAAQ;EAE5C,MAAM,SAAS,IAAI,UAAU,KAAK,EAAE,SAAS,QAAQ,SAAS,CAAC;AAC/D,SAAO,GAAG,eAAe,KAAA,EAAU;AACnC,SAAO;;CAGT,iBAAkC,UAAsD;AACtF,MAAI,OAAO,UAAU,SACnB,QAAO;AAET,MAAI;GACF,MAAM,SAAS,KAAK,MAAM,MAAM;AAChC,UAAO,UAAU,OAAO,WAAW,YAAY,OAAO,OAAO,SAAS,WAAW,SAAS;UACpF;AACN,UAAO;;;CAIX,oBAAqC,MAAe,aAA6B;AAC/E,MAAI,CAAC,QAAQ,OAAO,SAAS,SAC3B,QAAO;EAET,MAAM,QAAS,KAA2C;AAC1D,SAAO,OAAO,OAAO,YAAY,YAAY,MAAM,QAAQ,MAAM,GAAG,MAAM,UAAU;;CAGtF,oBAAgD,SAA6B;AAC3E,MAAI,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,KAAK,EAAE;GAC5D,MAAM,SAAS;AACf,OAAI,OAAO,OAAO,QAAQ,UAAU,OAClC,QAAO,OAAO;;AAGlB,SAAO"}
|
|
@@ -6,6 +6,7 @@ type NextClawExtensionOptions = {
|
|
|
6
6
|
endpoint?: string;
|
|
7
7
|
token?: string;
|
|
8
8
|
extensionId?: string;
|
|
9
|
+
generation?: string;
|
|
9
10
|
fetch?: typeof fetch;
|
|
10
11
|
webSocketFactory?: (url: string, options?: NextClawExtensionWebSocketFactoryOptions) => NextClawExtensionWebSocketLike;
|
|
11
12
|
};
|
|
@@ -24,6 +25,7 @@ type NextClawExtensionWebSocketLike = {
|
|
|
24
25
|
type ExtensionTransportEnvelope<TPayload = unknown> = {
|
|
25
26
|
type: string;
|
|
26
27
|
extensionId: string;
|
|
28
|
+
generation: string;
|
|
27
29
|
payload: TPayload;
|
|
28
30
|
emittedAt?: string;
|
|
29
31
|
source?: string;
|
|
@@ -31,6 +33,7 @@ type ExtensionTransportEnvelope<TPayload = unknown> = {
|
|
|
31
33
|
type ExtensionRequest = {
|
|
32
34
|
requestId: string;
|
|
33
35
|
extensionId: string;
|
|
36
|
+
generation: string;
|
|
34
37
|
kind: string;
|
|
35
38
|
payload?: Record<string, unknown>;
|
|
36
39
|
};
|
|
@@ -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,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,OAAA,EAAS,QAAA;EACT,SAAA;EACA,MAAA;AAAA;AAAA,KAGU,gBAAA;EACV,SAAA;EACA,WAAA;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":";;;;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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/extension-sdk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.21",
|
|
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.
|
|
40
|
-
"@nextclaw/shared": "0.4.
|
|
39
|
+
"@nextclaw/ncp": "0.7.17",
|
|
40
|
+
"@nextclaw/shared": "0.4.21"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^20.17.6",
|