@nextclaw/extension-sdk 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,6 +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, ExtensionDiagnostics, ExtensionObservationEmitInput, ExtensionObservationHandlers, ExtensionObservations, 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, DesktopHost, DesktopHostEvent, DesktopHostInvokeInput, ExtensionCapabilities, ExtensionCapabilityHandler, ExtensionCapabilityPayload, ExtensionChannel, ExtensionChannelCommands, ExtensionChannelConfig, ExtensionChannels, ExtensionDiagnostics, ExtensionObservationEmitInput, ExtensionObservationHandlers, ExtensionObservations, 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
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 ExtensionObservationEmitInput, type ExtensionObservationHandlers, type ExtensionObservations, type ExtensionRequest, type ExtensionRequestHandler, type ExtensionRequestResponse, type ExtensionTransportEnvelope, NextClawExtension, type NextClawExtensionOptions, type NextClawExtensionWebSocketLike, classifyDiagnosticError, startBusChannelExtension, startChannelExtension, warnNcpEventError };
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 DesktopHost, type DesktopHostEvent, type DesktopHostInvokeInput, type DiagnosticErrorClassification, type ExtensionCapabilities, type ExtensionCapabilityHandler, type ExtensionCapabilityPayload, type ExtensionChannel, type ExtensionChannelAdapter, type ExtensionChannelCommands, type ExtensionChannelConfig, ExtensionChannelController, type ExtensionChannels, type ExtensionDiagnostics, type ExtensionObservationEmitInput, type ExtensionObservationHandlers, type ExtensionObservations, type ExtensionRequest, type ExtensionRequestHandler, type ExtensionRequestResponse, type ExtensionTransportEnvelope, NextClawExtension, type NextClawExtensionOptions, type NextClawExtensionWebSocketLike, classifyDiagnosticError, startBusChannelExtension, startChannelExtension, warnNcpEventError };
@@ -1,4 +1,4 @@
1
- import { ExtensionCapabilities, ExtensionChannels, ExtensionDiagnostics, ExtensionObservations, ExtensionRequestHandler, NextClawExtensionOptions } from "../types/extension-sdk.types.js";
1
+ import { DesktopHost, ExtensionCapabilities, ExtensionChannels, ExtensionDiagnostics, ExtensionObservations, 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
@@ -7,6 +7,9 @@ declare class NextClawExtension {
7
7
  readonly channels: ExtensionChannels;
8
8
  readonly capabilities: ExtensionCapabilities;
9
9
  readonly diagnostics: ExtensionDiagnostics;
10
+ readonly host: {
11
+ desktop: DesktopHost;
12
+ };
10
13
  readonly observations: ExtensionObservations;
11
14
  readonly extensionId: string;
12
15
  readonly generation: string;
@@ -1 +1 @@
1
- {"version":3,"file":"extension-client.service.d.ts","names":[],"sources":["../../src/services/extension-client.service.ts"],"mappings":";;;;cAyOa,iBAAA;EAAA,SACF,QAAA,EAAU,QAAA;EAAA,SACV,QAAA,EAAU,iBAAA;EAAA,SACV,YAAA,EAAc,qBAAA;EAAA,SACd,WAAA,EAAa,oBAAA;EAAA,SACb,YAAA,EAAc,qBAAA;EAAA,SACd,WAAA;EAAA,SACA,UAAA;EAAA,iBACQ,SAAA;EAAA,QACT,uBAAA;EAAA,QAIA,oBAAA;cAEI,OAAA,GAAS,wBAAA;EAAA,SAqCZ,KAAA;EAAA,SAUA,KAAA,QAAkB,OAAA;EAAA,SAWlB,SAAA,GAAa,OAAA,EAAS,uBAAA;EAAA,iBAgBd,kBAAA;EAAA,iBAQA,yBAAA;AAAA"}
1
+ {"version":3,"file":"extension-client.service.d.ts","names":[],"sources":["../../src/services/extension-client.service.ts"],"mappings":";;;;cA2Oa,iBAAA;EAAA,SACF,QAAA,EAAU,QAAA;EAAA,SACV,QAAA,EAAU,iBAAA;EAAA,SACV,YAAA,EAAc,qBAAA;EAAA,SACd,WAAA,EAAa,oBAAA;EAAA,SACb,IAAA;IAAQ,OAAA,EAAS,WAAA;EAAA;EAAA,SACjB,YAAA,EAAc,qBAAA;EAAA,SACd,WAAA;EAAA,SACA,UAAA;EAAA,iBACQ,SAAA;EAAA,QACT,uBAAA;EAAA,QAIA,oBAAA;cAEI,OAAA,GAAS,wBAAA;EAAA,SA6CZ,KAAA;EAAA,SAUA,KAAA,QAAkB,OAAA;EAAA,SAWlB,SAAA,GAAa,OAAA,EAAS,uBAAA;EAAA,iBAgBd,kBAAA;EAAA,iBAQA,yBAAA;AAAA"}
@@ -1,6 +1,7 @@
1
1
  import { ExtensionChannelService } from "./extension-channel.service.js";
2
2
  import { ExtensionTransportService } from "./extension-transport.service.js";
3
3
  import { ExtensionObservationService } from "./extension-observation.service.js";
4
+ import { ExtensionDesktopHostService } from "./extension-desktop-host.service.js";
4
5
  import { EventBus, getKeyId, ingressKeys } from "@nextclaw/shared";
5
6
  import { createHash, randomUUID } from "node:crypto";
6
7
  //#region src/services/extension-client.service.ts
@@ -128,6 +129,7 @@ var NextClawExtension = class {
128
129
  channels;
129
130
  capabilities;
130
131
  diagnostics;
132
+ host;
131
133
  observations;
132
134
  extensionId;
133
135
  generation;
@@ -158,6 +160,12 @@ var NextClawExtension = class {
158
160
  extensionId: this.extensionId,
159
161
  transport: this.transport
160
162
  });
163
+ this.host = { desktop: new ExtensionDesktopHostService({
164
+ eventBus: this.eventBus,
165
+ extensionId: this.extensionId,
166
+ generation: this.generation,
167
+ transport: this.transport
168
+ }) };
161
169
  this.observations = new ExtensionObservationService({
162
170
  eventBus: this.eventBus,
163
171
  extensionId: this.extensionId,
@@ -1 +1 @@
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 ExtensionObservations,\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\";\nimport { ExtensionObservationService } from \"./extension-observation.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:\n record.payload &&\n typeof record.payload === \"object\" &&\n !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(\n ([name, handler]) =>\n this.provideHandler(`${normalizedNamespace}.${name}`, handler),\n );\n if (unsubscribeHandlers.length === 0) {\n throw new Error(\n `capability '${normalizedNamespace}' has no callable methods.`,\n );\n }\n return () => {\n for (const unsubscribe of unsubscribeHandlers) {\n unsubscribe();\n }\n };\n };\n\n readonly provideHandler = (\n kind: string,\n handler: ExtensionCapabilityHandler,\n ): (() => 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(\n this.params.transport,\n request,\n async (matchedRequest) =>\n await handler(matchedRequest.payload ?? {}, matchedRequest),\n );\n });\n };\n\n private readonly readHandlers = (\n capability: object,\n ): Array<[string, ExtensionCapabilityHandler]> =>\n Object.entries(capability).filter(\n (entry): entry is [string, ExtensionCapabilityHandler] =>\n 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 observations: ExtensionObservations;\n readonly extensionId: string;\n readonly generation: string;\n private readonly transport: ExtensionTransportService;\n private eventStreamSubscription: {\n close: () => void;\n ready: Promise<void>;\n } | 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.observations = new ExtensionObservationService({\n eventBus: this.eventBus,\n extensionId: this.extensionId,\n generation: this.generation,\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 void this.observations.close();\n };\n\n readonly ready = async (): Promise<void> => {\n const subscription = this.eventStreamSubscription;\n if (!subscription) {\n throw new Error(\n `Extension ${this.extensionId} has no event-stream subscription.`,\n );\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 (\n !request ||\n request.extensionId !== this.extensionId ||\n request.generation !== this.generation\n ) {\n return;\n }\n void handleRequest(this.transport, request, handler);\n });\n\n private readonly toEventBusEnvelope = (\n event: ExtensionTransportEnvelope,\n ): ExtensionTransportEnvelope => ({\n ...event,\n emittedAt: event.emittedAt ?? new Date().toISOString(),\n source: event.source ?? \"event-stream\",\n });\n\n private readonly startParentProcessWatcher = (): ReturnType<\n typeof setInterval\n > | 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"],"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 DesktopHost,\n ExtensionDiagnostics,\n ExtensionRequest,\n ExtensionRequestHandler,\n ExtensionObservations,\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\";\nimport { ExtensionObservationService } from \"./extension-observation.service.js\";\nimport { ExtensionDesktopHostService } from \"./extension-desktop-host.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:\n record.payload &&\n typeof record.payload === \"object\" &&\n !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(\n ([name, handler]) =>\n this.provideHandler(`${normalizedNamespace}.${name}`, handler),\n );\n if (unsubscribeHandlers.length === 0) {\n throw new Error(\n `capability '${normalizedNamespace}' has no callable methods.`,\n );\n }\n return () => {\n for (const unsubscribe of unsubscribeHandlers) {\n unsubscribe();\n }\n };\n };\n\n readonly provideHandler = (\n kind: string,\n handler: ExtensionCapabilityHandler,\n ): (() => 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(\n this.params.transport,\n request,\n async (matchedRequest) =>\n await handler(matchedRequest.payload ?? {}, matchedRequest),\n );\n });\n };\n\n private readonly readHandlers = (\n capability: object,\n ): Array<[string, ExtensionCapabilityHandler]> =>\n Object.entries(capability).filter(\n (entry): entry is [string, ExtensionCapabilityHandler] =>\n 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 host: { desktop: DesktopHost };\n readonly observations: ExtensionObservations;\n readonly extensionId: string;\n readonly generation: string;\n private readonly transport: ExtensionTransportService;\n private eventStreamSubscription: {\n close: () => void;\n ready: Promise<void>;\n } | 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.host = {\n desktop: new ExtensionDesktopHostService({\n eventBus: this.eventBus,\n extensionId: this.extensionId,\n generation: this.generation,\n transport: this.transport,\n }),\n };\n this.observations = new ExtensionObservationService({\n eventBus: this.eventBus,\n extensionId: this.extensionId,\n generation: this.generation,\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 void this.observations.close();\n };\n\n readonly ready = async (): Promise<void> => {\n const subscription = this.eventStreamSubscription;\n if (!subscription) {\n throw new Error(\n `Extension ${this.extensionId} has no event-stream subscription.`,\n );\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 (\n !request ||\n request.extensionId !== this.extensionId ||\n request.generation !== this.generation\n ) {\n return;\n }\n void handleRequest(this.transport, request, handler);\n });\n\n private readonly toEventBusEnvelope = (\n event: ExtensionTransportEnvelope,\n ): ExtensionTransportEnvelope => ({\n ...event,\n emittedAt: event.emittedAt ?? new Date().toISOString(),\n source: event.source ?? \"event-stream\",\n });\n\n private readonly startParentProcessWatcher = (): ReturnType<\n typeof setInterval\n > | 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"],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { getKeyId, ingressKeys } from "@nextclaw/shared";
2
+ //#region src/services/extension-desktop-host.service.ts
3
+ function asRecord(value) {
4
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
5
+ }
6
+ var ExtensionDesktopHostService = class {
7
+ constructor(params) {
8
+ this.params = params;
9
+ }
10
+ invoke = async (input) => await this.params.transport.postIngress(getKeyId(ingressKeys.extension.desktopHostInvoke), input);
11
+ status = async () => await this.invoke({
12
+ method: "host.status",
13
+ payload: {}
14
+ });
15
+ onEvent = (handler) => this.params.eventBus.subscribeAll((envelope) => {
16
+ if (envelope.type !== "extension.host.desktop.event") return;
17
+ const payload = asRecord(envelope.payload);
18
+ if (payload.extensionId !== this.params.extensionId) return;
19
+ if (payload.generation !== this.params.generation) return;
20
+ if (typeof payload.watchId !== "string" || !payload.watchId.trim()) return;
21
+ handler({
22
+ watchId: payload.watchId,
23
+ event: payload.event
24
+ });
25
+ });
26
+ };
27
+ //#endregion
28
+ export { ExtensionDesktopHostService };
29
+
30
+ //# sourceMappingURL=extension-desktop-host.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension-desktop-host.service.js","names":[],"sources":["../../src/services/extension-desktop-host.service.ts"],"sourcesContent":["import { getKeyId, ingressKeys, type Unsubscribe } from \"@nextclaw/shared\";\nimport type {\n DesktopHost,\n DesktopHostEvent,\n DesktopHostInvokeInput,\n} from \"../types/extension-sdk.types.js\";\nimport type { ExtensionTransportService } from \"./extension-transport.service.js\";\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : {};\n}\n\nexport class ExtensionDesktopHostService implements DesktopHost {\n constructor(private readonly params: {\n eventBus: {\n subscribeAll: (\n handler: (event: { type: string; payload: unknown }) => void,\n ) => Unsubscribe;\n };\n extensionId: string;\n generation: string;\n transport: ExtensionTransportService;\n }) {}\n\n readonly invoke = async <T = unknown>(\n input: DesktopHostInvokeInput,\n ): Promise<T> =>\n await this.params.transport.postIngress<T>(\n getKeyId(ingressKeys.extension.desktopHostInvoke),\n input,\n );\n\n readonly status = async <T = unknown>(): Promise<T> =>\n await this.invoke<T>({ method: \"host.status\", payload: {} });\n\n readonly onEvent = (\n handler: (event: DesktopHostEvent) => void | Promise<void>,\n ): Unsubscribe =>\n this.params.eventBus.subscribeAll((envelope) => {\n if (envelope.type !== \"extension.host.desktop.event\") return;\n const payload = asRecord(envelope.payload);\n if (payload.extensionId !== this.params.extensionId) return;\n if (payload.generation !== this.params.generation) return;\n if (typeof payload.watchId !== \"string\" || !payload.watchId.trim()) return;\n void handler({\n watchId: payload.watchId,\n event: payload.event,\n });\n });\n}\n"],"mappings":";;AAQA,SAAS,SAAS,OAAyC;AACzD,QAAO,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM,GAC9D,QACA,EAAE;;AAGR,IAAa,8BAAb,MAAgE;CAC9D,YAAY,QAST;AAT0B,OAAA,SAAA;;CAW7B,SAAkB,OAChB,UAEA,MAAM,KAAK,OAAO,UAAU,YAC1B,SAAS,YAAY,UAAU,kBAAkB,EACjD,MACD;CAEH,SAAkB,YAChB,MAAM,KAAK,OAAU;EAAE,QAAQ;EAAe,SAAS,EAAE;EAAE,CAAC;CAE9D,WACE,YAEA,KAAK,OAAO,SAAS,cAAc,aAAa;AAC9C,MAAI,SAAS,SAAS,+BAAgC;EACtD,MAAM,UAAU,SAAS,SAAS,QAAQ;AAC1C,MAAI,QAAQ,gBAAgB,KAAK,OAAO,YAAa;AACrD,MAAI,QAAQ,eAAe,KAAK,OAAO,WAAY;AACnD,MAAI,OAAO,QAAQ,YAAY,YAAY,CAAC,QAAQ,QAAQ,MAAM,CAAE;AAC/D,UAAQ;GACX,SAAS,QAAQ;GACjB,OAAO,QAAQ;GAChB,CAAC;GACF"}
@@ -112,6 +112,24 @@ type ExtensionCapabilities = {
112
112
  provide: (namespace: string, capability: object) => Unsubscribe;
113
113
  provideHandler: (kind: string, handler: ExtensionCapabilityHandler) => Unsubscribe;
114
114
  };
115
+ type DesktopHostInvokeInput = {
116
+ method: string;
117
+ payload?: Record<string, unknown>;
118
+ caller?: {
119
+ sessionId?: string;
120
+ agentRunId?: string;
121
+ subscriptionId?: string;
122
+ };
123
+ };
124
+ type DesktopHostEvent = {
125
+ watchId: string;
126
+ event: unknown;
127
+ };
128
+ type DesktopHost = {
129
+ invoke: <T = unknown>(input: DesktopHostInvokeInput) => Promise<T>;
130
+ status: <T = unknown>() => Promise<T>;
131
+ onEvent: (handler: (event: DesktopHostEvent) => void | Promise<void>) => Unsubscribe;
132
+ };
115
133
  //#endregion
116
- 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, ExtensionObservationEmitInput, ExtensionObservationHandlers, ExtensionObservations, ExtensionRequest, ExtensionRequestHandler, type ExtensionRequestResponse, ExtensionTransportEnvelope, NextClawExtensionOptions, NextClawExtensionWebSocketLike };
134
+ 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, DesktopHost, DesktopHostEvent, DesktopHostInvokeInput, ExtensionCapabilities, ExtensionCapabilityHandler, ExtensionCapabilityPayload, ExtensionChannel, ExtensionChannelCommands, ExtensionChannelConfig, ExtensionChannels, ExtensionDiagnostics, ExtensionObservationEmitInput, ExtensionObservationHandlers, ExtensionObservations, ExtensionRequest, ExtensionRequestHandler, type ExtensionRequestResponse, ExtensionTransportEnvelope, NextClawExtensionOptions, NextClawExtensionWebSocketLike };
117
135
  //# 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":";;;;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,GACE,KAAA,EAAO,IAAA,CAAK,2CAAA,mBACT,OAAA;EACL,UAAA,GACE,OAAA,GAAU,KAAA,EAAO,gBAAA,YAA4B,OAAA,WAC1C,WAAA;EACL,MAAA,EAAQ,sBAAA;EACR,QAAA,EAAU,wBAAA;AAAA;AAAA,KAKA,iBAAA;EACV,GAAA,GAAM,SAAA,aAAsB,gBAAA;AAAA;AAAA,KAGlB,uBAAA,IACV,OAAA,EAAS,gBAAA,eACI,OAAA;AAAA,KAEH,0BAAA,GAA6B,MAAA;AAAA,KAE7B,0BAAA,kBACO,0BAAA,GAA6B,0BAAA,KAE9C,OAAA,EAAS,QAAA,EACT,OAAA,EAAS,gBAAA,eACI,OAAA;AAAA,KAEH,6BAAA;EACV,EAAA;EACA,IAAA;EACA,UAAA;EACA,UAAA;EACA,MAAA;EACA,SAAA;EACA,OAAA;EACA,UAAA;EACA,WAAA;EACA,aAAA;AAAA;AAAA,KAGU,4BAAA;EACV,IAAA,IAAQ,KAAA;IACN,MAAA;IACA,MAAA,EAAQ,WAAA;EAAA,gBACM,OAAA;EAChB,SAAA,IAAa,KAAA;IACX,cAAA;IACA,MAAA;IACA,MAAA;IACA,IAAA,GAAO,KAAA,EAAO,6BAAA,KAAkC,OAAA;IAChD,MAAA,EAAQ,WAAA;EAAA,2BAGQ,OAAA,UACd,OAAA,sBAA6B,OAAA;EACjC,MAAA;AAAA;AAAA,KAGU,qBAAA;EACV,OAAA,GAAU,QAAA,EAAU,4BAAA,KAAiC,WAAA;EACrD,KAAA,QAAa,OAAA;AAAA;AAAA,KAGH,qBAAA;EACV,OAAA,GAAU,SAAA,UAAmB,UAAA,aAAuB,WAAA;EACpD,cAAA,GACE,IAAA,UACA,OAAA,EAAS,0BAAA,KACN,WAAA;AAAA"}
1
+ {"version":3,"file":"extension-sdk.types.d.ts","names":[],"sources":["../../src/types/extension-sdk.types.ts"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/extension-sdk",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
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/shared": "0.4.28",
40
- "@nextclaw/ncp": "0.10.0"
39
+ "@nextclaw/ncp": "0.10.0",
40
+ "@nextclaw/shared": "0.4.29"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "^20.17.6",