@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 +2 -2
- package/dist/services/extension-client.service.d.ts +4 -1
- package/dist/services/extension-client.service.d.ts.map +1 -1
- package/dist/services/extension-client.service.js +8 -0
- package/dist/services/extension-client.service.js.map +1 -1
- package/dist/services/extension-desktop-host.service.js +30 -0
- package/dist/services/extension-desktop-host.service.js.map +1 -0
- package/dist/types/extension-sdk.types.d.ts +19 -1
- package/dist/types/extension-sdk.types.d.ts.map +1 -1
- package/package.json +3 -3
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":";;;;
|
|
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":";;;;;;AAiBA,MAAM,qCAAqC;AAE3C,IAAM,4BAAN,MAAgE;CAC9D,YACE,WACA,WACA;AAFiB,OAAA,YAAA;AACA,OAAA,YAAA;;CAGnB,iBAA0B,aAA8B;EACtD,MAAM,qBAAqB,UAAU,MAAM;AAC3C,MAAI,CAAC,mBACH,QAAO,YAAY;AAErB,SAAO,WAAW,SAAS,CACxB,OAAO,mBAAmB,CAC1B,OAAO,MAAM,CACb,MAAM,GAAG,GAAG;;CAGjB,OAA8C,OAAO,UAAU;EAC7D,MAAM,aAAa,IAAI,iBAAiB;EACxC,IAAI;AACJ,MAAI;AACF,SAAM,QAAQ,KAAK,CACjB,KAAK,UAAU,YACb,SAAS,YAAY,UAAU,eAAe,EAC9C,OACA,EAAE,QAAQ,WAAW,QAAQ,CAC9B,EACD,IAAI,SAAgB,UAAU,WAAW;AACvC,cAAU,iBAAiB;AACzB,gBAAW,OAAO;AAClB,4BAAO,IAAI,MAAM,2CAA2C,CAAC;OAC5D,KAAK,UAAU;AAClB,YAAQ,SAAS;KACjB,CACH,CAAC;AACF,UAAO;UACD;AACN,UAAO;YACC;AACR,OAAI,QACF,cAAa,QAAQ;;;;AAM7B,IAAM,2BAAN,MAA4D;CAC1D,2BAA4B,IAAI,KAAsC;CAEtE,YACE,QAIA;AAJiB,OAAA,SAAA;;CAMnB,OAAgB,cAA+C;EAC7D,MAAM,sBAAsB,UAAU,MAAM;AAC5C,MAAI,CAAC,oBACH,OAAM,IAAI,MAAM,yBAAyB;EAE3C,MAAM,WAAW,KAAK,SAAS,IAAI,oBAAoB;AACvD,MAAI,SACF,QAAO;EAET,MAAM,UAAU,IAAI,wBAAwB;GAC1C,WAAW;GACX,UAAU,KAAK,OAAO;GACtB,WAAW,KAAK,OAAO;GACxB,CAAC;AACF,OAAK,SAAS,IAAI,qBAAqB,QAAQ;AAC/C,SAAO;;;AAIX,SAAS,YAAY,SAA2C;AAC9D,KAAI,CAAC,WAAW,OAAO,YAAY,YAAY,MAAM,QAAQ,QAAQ,CACnE,QAAO;CAET,MAAM,SAAS;AACf,KACE,OAAO,OAAO,cAAc,YAC5B,OAAO,OAAO,gBAAgB,YAC9B,OAAO,OAAO,eAAe,YAC7B,OAAO,OAAO,SAAS,SAEvB,QAAO;AAET,QAAO;EACL,WAAW,OAAO;EAClB,aAAa,OAAO;EACpB,YAAY,OAAO;EACnB,MAAM,OAAO;EACb,SACE,OAAO,WACP,OAAO,OAAO,YAAY,YAC1B,CAAC,MAAM,QAAQ,OAAO,QAAQ,GACzB,OAAO,UACR,EAAE;EACT;;AAGH,SAAS,cAAc,OAAe,MAAsB;CAC1D,MAAM,aAAa,MAAM,MAAM;AAC/B,KAAI,CAAC,WACH,OAAM,IAAI,MAAM,GAAG,KAAK,eAAe;AAEzC,QAAO;;AAGT,eAAe,cACb,WACA,SACA,SACe;AACf,KAAI;EACF,MAAM,OAAO,MAAM,QAAQ,QAAQ;AACnC,QAAM,UAAU,iBAAiB;GAC/B,WAAW,QAAQ;GACnB,IAAI;GACJ;GACD,CAAC;UACK,OAAO;AACd,QAAM,UAAU,iBAAiB;GAC/B,WAAW,QAAQ;GACnB,IAAI;GACJ,OAAO,EACL,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,EAChE;GACF,CAAC;;;AAIN,SAAS,sBAAqC;CAC5C,MAAM,MAAM,QAAQ,IAAI,+BAA+B,MAAM;AAC7D,KAAI,CAAC,IACH,QAAO;CAET,MAAM,MAAM,OAAO,IAAI;AACvB,QAAO,OAAO,UAAU,IAAI,IAAI,MAAM,IAAI,MAAM;;AAGlD,SAAS,iBAAiB,KAAsB;AAC9C,KAAI;AACF,UAAQ,KAAK,KAAK,EAAE;AACpB,SAAO;UACA,OAAO;AACd,SAAQ,MAAgC,SAAS;;;AAIrD,IAAM,8BAAN,MAAmE;CACjE,YACE,QAKA;AALiB,OAAA,SAAA;;CAOnB,WAAoB,WAAmB,eAAqC;EAC1E,MAAM,sBAAsB,cAAc,WAAW,YAAY;EACjE,MAAM,sBAAsB,KAAK,aAAa,WAAW,CAAC,KACvD,CAAC,MAAM,aACN,KAAK,eAAe,GAAG,oBAAoB,GAAG,QAAQ,QAAQ,CACjE;AACD,MAAI,oBAAoB,WAAW,EACjC,OAAM,IAAI,MACR,eAAe,oBAAoB,4BACpC;AAEH,eAAa;AACX,QAAK,MAAM,eAAe,oBACxB,cAAa;;;CAKnB,kBACE,MACA,YACiB;EACjB,MAAM,iBAAiB,cAAc,MAAM,OAAO;AAClD,SAAO,KAAK,OAAO,SAAS,cAAc,UAAU;AAClD,OAAI,MAAM,SAAS,oBACjB;GAEF,MAAM,UAAU,YAAY,MAAM,QAAQ;AAC1C,OACE,CAAC,WACD,QAAQ,gBAAgB,KAAK,OAAO,eACpC,QAAQ,eAAe,KAAK,OAAO,UAAU,cAC7C,QAAQ,SAAS,eAEjB;AAEG,iBACH,KAAK,OAAO,WACZ,SACA,OAAO,mBACL,MAAM,QAAQ,eAAe,WAAW,EAAE,EAAE,eAAe,CAC9D;IACD;;CAGJ,gBACE,eAEA,OAAO,QAAQ,WAAW,CAAC,QACxB,UACC,OAAO,MAAM,OAAO,WACvB;;AAGL,IAAa,oBAAb,MAA+B;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,0BAGW;CACX,uBAAsE;CAEtE,YAAY,UAAoC,EAAE,EAAE;AAClD,OAAK,YAAY,IAAI,0BAA0B,QAAQ;AACvD,OAAK,cAAc,KAAK,UAAU;AAClC,OAAK,aAAa,KAAK,UAAU;AACjC,OAAK,WAAW,IAAI,SAAS;GAC3B,yBAAyB;AACvB,SAAK,4BAA4B,KAAK,UAAU,WAAW,UAAU;AACnE,UAAK,SAAS,aAAa,KAAK,mBAAmB,MAAM,CAAC;MAC1D;;GAEJ,uBAAuB;AACrB,SAAK,yBAAyB,OAAO;AACrC,SAAK,0BAA0B;;GAElC,CAAC;AACF,OAAK,WAAW,IAAI,yBAAyB;GAC3C,UAAU,KAAK;GACf,WAAW,KAAK;GACjB,CAAC;AACF,OAAK,eAAe,IAAI,4BAA4B;GAClD,UAAU,KAAK;GACf,aAAa,KAAK;GAClB,WAAW,KAAK;GACjB,CAAC;AACF,OAAK,eAAe,IAAI,4BAA4B;GAClD,UAAU,KAAK;GACf,aAAa,KAAK;GAClB,YAAY,KAAK;GACjB,WAAW,KAAK;GACjB,CAAC;AACF,OAAK,cAAc,IAAI,0BACrB,KAAK,WACL,KAAK,IAAI,IAAI,QAAQ,uBAAuB,IAAM,CACnD;AACD,OAAK,uBAAuB,KAAK,2BAA2B;;CAG9D,cAA6B;AAC3B,MAAI,KAAK,sBAAsB;AAC7B,iBAAc,KAAK,qBAAqB;AACxC,QAAK,uBAAuB;;AAE9B,OAAK,yBAAyB,OAAO;AACrC,OAAK,0BAA0B;AAC1B,OAAK,aAAa,OAAO;;CAGhC,QAAiB,YAA2B;EAC1C,MAAM,eAAe,KAAK;AAC1B,MAAI,CAAC,aACH,OAAM,IAAI,MACR,aAAa,KAAK,YAAY,oCAC/B;AAEH,QAAM,aAAa;AACnB,QAAM,KAAK,UAAU,YAAY,QAAQ,IAAI;;CAG/C,aAAsB,YACpB,KAAK,SAAS,cAAc,UAAU;AACpC,MAAI,MAAM,SAAS,oBACjB;EAEF,MAAM,UAAU,YAAY,MAAM,QAAQ;AAC1C,MACE,CAAC,WACD,QAAQ,gBAAgB,KAAK,eAC7B,QAAQ,eAAe,KAAK,WAE5B;AAEG,gBAAc,KAAK,WAAW,SAAS,QAAQ;GACpD;CAEJ,sBACE,WACgC;EAChC,GAAG;EACH,WAAW,MAAM,8BAAa,IAAI,MAAM,EAAC,aAAa;EACtD,QAAQ,MAAM,UAAU;EACzB;CAED,kCAEY;EACV,MAAM,YAAY,qBAAqB;AACvC,MAAI,CAAC,UACH,QAAO;EAET,MAAM,QAAQ,kBAAkB;AAC9B,OAAI,iBAAiB,UAAU,CAC7B;AAEF,QAAK,OAAO;AACZ,WAAQ,KAAK,EAAE;KACd,mCAAmC;AACrC,QAAyB,SAAS;AACnC,SAAO"}
|
|
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.
|
|
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/
|
|
40
|
-
"@nextclaw/
|
|
39
|
+
"@nextclaw/ncp": "0.10.0",
|
|
40
|
+
"@nextclaw/shared": "0.4.29"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "^20.17.6",
|