@nextclaw/service 0.1.11 → 0.1.12
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/cli/commands/agent/agent-runtime.utils.d.ts +1 -2
- package/dist/cli/commands/agent/agent-runtime.utils.js +6 -50
- package/dist/cli/commands/agent/cli-agent-runner.utils.d.ts +0 -2
- package/dist/cli/commands/agent/cli-agent-runner.utils.js +7 -9
- package/dist/cli/commands/agent/services/agent-commands.service.js +0 -1
- package/dist/cli/commands/skills/index.js +1 -1
- package/dist/cli/commands/skills/marketplace-client.d.ts +11 -1
- package/dist/cli/commands/skills/marketplace-client.js +39 -1
- package/dist/cli/commands/skills/marketplace-command-options.utils.d.ts +1 -1
- package/dist/cli/commands/skills/marketplace.metadata.d.ts +3 -12
- package/dist/cli/commands/skills/marketplace.metadata.js +1 -87
- package/dist/cli/commands/skills/{marketplace.service.d.ts → marketplace.utils.d.ts} +1 -1
- package/dist/cli/commands/skills/{marketplace.service.js → marketplace.utils.js} +11 -47
- package/dist/cli/commands/skills/skills-query.service.d.ts +5 -37
- package/dist/cli/commands/skills/skills-query.service.js +16 -98
- package/dist/cli/commands/usage/services/llm-usage-command.service.d.ts +3 -5
- package/dist/cli/commands/usage/services/llm-usage-command.service.js +16 -26
- package/dist/commands/channel/channel-list-view.service.d.ts +0 -1
- package/dist/commands/channel/channel-list-view.service.js +6 -23
- package/dist/commands/channel/index.js +0 -1
- package/dist/commands/plugin/index.d.ts +2 -4
- package/dist/commands/plugin/index.js +8 -20
- package/dist/commands/plugin/{plugin-command-utils.d.ts → plugin-command.utils.d.ts} +1 -2
- package/dist/commands/plugin/{plugin-command-utils.js → plugin-command.utils.js} +2 -4
- package/dist/commands/plugin/{plugin-mutation-actions.d.ts → plugin-mutation-actions.utils.d.ts} +1 -1
- package/dist/commands/plugin/{plugin-mutation-actions.js → plugin-mutation-actions.utils.js} +2 -2
- package/dist/commands/service/services/autostart/linux-systemd-autostart.service.js +1 -1
- package/dist/commands/service/services/autostart/macos-launch-agent-autostart.service.js +1 -1
- package/dist/commands/service/services/autostart/windows-task-autostart.service.js +1 -1
- package/dist/launcher/npm-runtime-launcher.service.js +1 -1
- package/dist/service-runtime.service.d.ts +1 -1
- package/dist/service-runtime.service.js +7 -15
- package/dist/shared/controllers/gateway.controller.d.ts +3 -11
- package/dist/shared/controllers/gateway.controller.js +24 -180
- package/dist/shared/services/gateway/managers/gateway-plugin.manager.d.ts +3 -9
- package/dist/shared/services/gateway/managers/gateway-plugin.manager.js +30 -88
- package/dist/shared/services/gateway/nextclaw-app.service.d.ts +2 -7
- package/dist/shared/services/gateway/nextclaw-app.service.js +6 -16
- package/dist/shared/services/gateway/nextclaw-gateway-runtime.service.d.ts +4 -9
- package/dist/shared/services/gateway/nextclaw-gateway-runtime.service.js +12 -46
- package/dist/shared/services/gateway/{cron-job-handler.service.d.ts → utils/cron-job-handler.utils.d.ts} +3 -6
- package/dist/shared/services/gateway/utils/cron-job-handler.utils.js +57 -0
- package/dist/shared/services/marketplace/service-marketplace-installer.service.js +3 -3
- package/dist/shared/services/plugin/utils/plugin-runtime-bridge.utils.js +1 -1
- package/dist/shared/services/runtime/runtime-command.service.js +2 -2
- package/dist/shared/services/runtime/service-managed-startup.service.js +1 -1
- package/dist/shared/services/ui/companion-runtime.service.js +1 -1
- package/dist/shared/services/workspace/workspace-manager.service.js +8 -10
- package/dist/shared/utils/cli.utils.js +1 -1
- package/package.json +20 -20
- package/dist/cli/commands/usage/services/llm-usage-query.service.d.ts +0 -43
- package/dist/cli/commands/usage/services/llm-usage-query.service.js +0 -85
- package/dist/commands/plugin/development-source/dev-plugin-overrides.utils.d.ts +0 -18
- package/dist/commands/plugin/development-source/dev-plugin-overrides.utils.js +0 -111
- package/dist/commands/plugin/development-source/first-party-plugin-load-paths.utils.d.ts +0 -9
- package/dist/commands/plugin/development-source/first-party-plugin-load-paths.utils.js +0 -183
- package/dist/commands/plugin/plugin-extension-registry.d.ts +0 -10
- package/dist/commands/plugin/plugin-extension-registry.js +0 -35
- package/dist/commands/plugin/plugin-registry-loader.utils.d.ts +0 -14
- package/dist/commands/plugin/plugin-registry-loader.utils.js +0 -43
- package/dist/commands/plugin/plugin-reload.d.ts +0 -13
- package/dist/commands/plugin/plugin-reload.js +0 -42
- package/dist/shared/services/extensions/extension-lifecycle.service.d.ts +0 -63
- package/dist/shared/services/extensions/extension-lifecycle.service.js +0 -174
- package/dist/shared/services/extensions/service-extension-runtime.service.d.ts +0 -52
- package/dist/shared/services/extensions/service-extension-runtime.service.js +0 -325
- package/dist/shared/services/gateway/cron-job-handler.service.js +0 -100
- package/dist/shared/services/runtime/utils/skills-loader.utils.d.ts +0 -12
- package/dist/shared/services/runtime/utils/skills-loader.utils.js +0 -9
- package/dist/shared/services/session/service-deferred-ncp-agent.service.d.ts +0 -14
- package/dist/shared/services/session/service-deferred-ncp-agent.service.js +0 -85
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { readFileSync, readdirSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
4
|
-
import { readFile, readdir } from "node:fs/promises";
|
|
5
|
-
//#region src/shared/services/extensions/extension-lifecycle.service.ts
|
|
6
|
-
const EXTENSION_MANIFEST_FILE = "nextclaw.extension.json";
|
|
7
|
-
function readString(value) {
|
|
8
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
9
|
-
}
|
|
10
|
-
function readStringArray(value) {
|
|
11
|
-
if (!Array.isArray(value)) return;
|
|
12
|
-
return value.every((item) => typeof item === "string") ? value : void 0;
|
|
13
|
-
}
|
|
14
|
-
function readStringRecord(value) {
|
|
15
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
16
|
-
const entries = Object.entries(value);
|
|
17
|
-
if (!entries.every(([, item]) => typeof item === "string")) return;
|
|
18
|
-
return Object.fromEntries(entries);
|
|
19
|
-
}
|
|
20
|
-
function sanitizeExtensionNodeOptions(value) {
|
|
21
|
-
if (!value?.trim()) return;
|
|
22
|
-
const tokens = value.split(/\s+/).filter(Boolean);
|
|
23
|
-
const sanitized = [];
|
|
24
|
-
for (let index = 0; index < tokens.length; index += 1) {
|
|
25
|
-
const token = tokens[index];
|
|
26
|
-
if (token === "--conditions=development" || token === "-C=development") continue;
|
|
27
|
-
if ((token === "--conditions" || token === "-C") && tokens[index + 1] === "development") {
|
|
28
|
-
index += 1;
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
sanitized.push(token);
|
|
32
|
-
}
|
|
33
|
-
return sanitized.length > 0 ? sanitized.join(" ") : void 0;
|
|
34
|
-
}
|
|
35
|
-
function toManifest(value, rootDir) {
|
|
36
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("extension manifest must be an object");
|
|
37
|
-
const record = value;
|
|
38
|
-
const server = record.server;
|
|
39
|
-
if (!server || typeof server !== "object" || Array.isArray(server)) throw new Error("extension manifest server is required");
|
|
40
|
-
const serverRecord = server;
|
|
41
|
-
const id = readString(record.id);
|
|
42
|
-
const command = readString(serverRecord.command);
|
|
43
|
-
if (!id) throw new Error("extension manifest id is required");
|
|
44
|
-
if (serverRecord.type !== "stdio") throw new Error("extension server.type must be stdio");
|
|
45
|
-
if (!command) throw new Error("extension server.command is required");
|
|
46
|
-
return {
|
|
47
|
-
id,
|
|
48
|
-
rootDir,
|
|
49
|
-
...readString(record.name) ? { name: readString(record.name) } : {},
|
|
50
|
-
...readString(record.version) ? { version: readString(record.version) } : {},
|
|
51
|
-
server: {
|
|
52
|
-
type: "stdio",
|
|
53
|
-
command,
|
|
54
|
-
...readStringArray(serverRecord.args) ? { args: readStringArray(serverRecord.args) } : {},
|
|
55
|
-
...readStringRecord(serverRecord.env) ? { env: readStringRecord(serverRecord.env) } : {}
|
|
56
|
-
},
|
|
57
|
-
...record.contributes && typeof record.contributes === "object" && !Array.isArray(record.contributes) ? { contributes: record.contributes } : {}
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
var ExtensionManifestDiscoveryService = class {
|
|
61
|
-
discover = async (roots) => {
|
|
62
|
-
const manifests = [];
|
|
63
|
-
for (const root of roots) manifests.push(...await this.discoverRoot(root));
|
|
64
|
-
return manifests;
|
|
65
|
-
};
|
|
66
|
-
discoverSync = (roots) => {
|
|
67
|
-
const manifests = [];
|
|
68
|
-
for (const root of roots) manifests.push(...this.discoverRootSync(root));
|
|
69
|
-
return manifests;
|
|
70
|
-
};
|
|
71
|
-
discoverRoot = async (root) => {
|
|
72
|
-
const directManifest = await this.readManifestIfExists(join(root, EXTENSION_MANIFEST_FILE));
|
|
73
|
-
if (directManifest) return [directManifest];
|
|
74
|
-
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
|
|
75
|
-
return (await Promise.all(entries.filter((entry) => entry.isDirectory()).map((entry) => this.readManifestIfExists(join(root, entry.name, EXTENSION_MANIFEST_FILE))))).filter((manifest) => Boolean(manifest));
|
|
76
|
-
};
|
|
77
|
-
discoverRootSync = (root) => {
|
|
78
|
-
const directManifest = this.readManifestIfExistsSync(join(root, EXTENSION_MANIFEST_FILE));
|
|
79
|
-
if (directManifest) return [directManifest];
|
|
80
|
-
return this.readDirectoriesSync(root).map((entry) => this.readManifestIfExistsSync(join(root, entry, EXTENSION_MANIFEST_FILE))).filter((manifest) => Boolean(manifest));
|
|
81
|
-
};
|
|
82
|
-
readManifestIfExists = async (path) => {
|
|
83
|
-
try {
|
|
84
|
-
return toManifest(JSON.parse(await readFile(path, "utf-8")), dirname(path));
|
|
85
|
-
} catch (error) {
|
|
86
|
-
if (error.code === "ENOENT") return null;
|
|
87
|
-
throw error;
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
readManifestIfExistsSync = (path) => {
|
|
91
|
-
try {
|
|
92
|
-
return toManifest(JSON.parse(readFileSync(path, "utf-8")), dirname(path));
|
|
93
|
-
} catch (error) {
|
|
94
|
-
if (error.code === "ENOENT") return null;
|
|
95
|
-
throw error;
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
readDirectoriesSync = (root) => {
|
|
99
|
-
try {
|
|
100
|
-
return readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
101
|
-
} catch {
|
|
102
|
-
return [];
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
};
|
|
106
|
-
var ExtensionLifecycleService = class {
|
|
107
|
-
processes = /* @__PURE__ */ new Map();
|
|
108
|
-
spawnProcess;
|
|
109
|
-
logger;
|
|
110
|
-
constructor(options) {
|
|
111
|
-
this.options = options;
|
|
112
|
-
this.spawnProcess = options.spawnProcess ?? spawn;
|
|
113
|
-
this.logger = options.logger ?? console;
|
|
114
|
-
}
|
|
115
|
-
startAll = async (manifests) => {
|
|
116
|
-
const started = [];
|
|
117
|
-
for (const manifest of manifests) try {
|
|
118
|
-
started.push(this.start(manifest));
|
|
119
|
-
} catch (error) {
|
|
120
|
-
this.logger.warn(`Extension ${manifest.id} failed to start: ${error instanceof Error ? error.message : String(error)}`);
|
|
121
|
-
}
|
|
122
|
-
return started;
|
|
123
|
-
};
|
|
124
|
-
start = (manifest) => {
|
|
125
|
-
const existing = this.processes.get(manifest.id);
|
|
126
|
-
if (existing) return existing;
|
|
127
|
-
const child = this.spawnProcess(manifest.server.command === "node" || manifest.server.command === "node.exe" ? process.execPath : manifest.server.command, manifest.server.args ?? [], {
|
|
128
|
-
cwd: manifest.rootDir,
|
|
129
|
-
env: {
|
|
130
|
-
...process.env,
|
|
131
|
-
NODE_OPTIONS: sanitizeExtensionNodeOptions(process.env.NODE_OPTIONS),
|
|
132
|
-
...manifest.server.env,
|
|
133
|
-
NEXTCLAW_EXTENSION_ID: manifest.id,
|
|
134
|
-
NEXTCLAW_EXTENSION_ENDPOINT: this.options.endpoint,
|
|
135
|
-
NEXTCLAW_EXTENSION_TOKEN: this.options.token
|
|
136
|
-
},
|
|
137
|
-
stdio: [
|
|
138
|
-
"ignore",
|
|
139
|
-
"ignore",
|
|
140
|
-
"inherit"
|
|
141
|
-
],
|
|
142
|
-
windowsHide: true
|
|
143
|
-
});
|
|
144
|
-
const running = {
|
|
145
|
-
manifest,
|
|
146
|
-
process: child
|
|
147
|
-
};
|
|
148
|
-
this.processes.set(manifest.id, running);
|
|
149
|
-
child.once("exit", () => {
|
|
150
|
-
if (this.processes.get(manifest.id)?.process === child) this.processes.delete(manifest.id);
|
|
151
|
-
this.logger.warn(`Extension ${manifest.id} exited.`);
|
|
152
|
-
});
|
|
153
|
-
child.once("error", (error) => {
|
|
154
|
-
this.logger.warn(`Extension ${manifest.id} failed: ${error.message}`);
|
|
155
|
-
});
|
|
156
|
-
return running;
|
|
157
|
-
};
|
|
158
|
-
stopAll = async () => {
|
|
159
|
-
const running = Array.from(this.processes.values());
|
|
160
|
-
this.processes.clear();
|
|
161
|
-
await Promise.all(running.map((entry) => this.stopProcess(entry.process)));
|
|
162
|
-
};
|
|
163
|
-
list = () => Array.from(this.processes.values());
|
|
164
|
-
stopProcess = async (child) => {
|
|
165
|
-
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
166
|
-
await new Promise((resolve) => {
|
|
167
|
-
child.once("exit", () => resolve());
|
|
168
|
-
child.kill();
|
|
169
|
-
setTimeout(resolve, 1e3).unref();
|
|
170
|
-
});
|
|
171
|
-
};
|
|
172
|
-
};
|
|
173
|
-
//#endregion
|
|
174
|
-
export { ExtensionLifecycleService, ExtensionManifestDiscoveryService };
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { ExtensionLifecycleService, ExtensionManifestDiscoveryService, RunningExtensionProcess } from "./extension-lifecycle.service.js";
|
|
2
|
-
import { NextclawGatewayRuntime } from "../gateway/nextclaw-gateway-runtime.service.js";
|
|
3
|
-
import * as NextclawCore from "@nextclaw/core";
|
|
4
|
-
import { PluginChannelBinding, PluginUiMetadata } from "@nextclaw/openclaw-compat";
|
|
5
|
-
import { Ingress } from "@nextclaw/shared";
|
|
6
|
-
|
|
7
|
-
//#region src/shared/services/extensions/service-extension-runtime.service.d.ts
|
|
8
|
-
type Config$1 = NextclawCore.Config;
|
|
9
|
-
type ExtensionRuntimeContributions = {
|
|
10
|
-
channelBindings: PluginChannelBinding[];
|
|
11
|
-
uiMetadata: PluginUiMetadata[];
|
|
12
|
-
};
|
|
13
|
-
declare function resolveBuiltinExtensionManifestRoots(): string[];
|
|
14
|
-
declare function resolveExtensionManifestRoots(params: {
|
|
15
|
-
config: Config$1;
|
|
16
|
-
workspace: string;
|
|
17
|
-
}): string[];
|
|
18
|
-
declare function startDiscoveredExtensions(params: {
|
|
19
|
-
config: Config$1;
|
|
20
|
-
workspace: string;
|
|
21
|
-
endpoint: string;
|
|
22
|
-
token: string;
|
|
23
|
-
discovery?: ExtensionManifestDiscoveryService;
|
|
24
|
-
lifecycle?: ExtensionLifecycleService;
|
|
25
|
-
}): Promise<{
|
|
26
|
-
lifecycle: ExtensionLifecycleService;
|
|
27
|
-
running: RunningExtensionProcess[];
|
|
28
|
-
}>;
|
|
29
|
-
declare class ServiceExtensionRuntime {
|
|
30
|
-
private readonly gateway;
|
|
31
|
-
readonly token: `${string}-${string}-${string}-${string}-${string}`;
|
|
32
|
-
private lifecycle;
|
|
33
|
-
private manifests;
|
|
34
|
-
private readonly pendingRequests;
|
|
35
|
-
constructor(gateway: NextclawGatewayRuntime);
|
|
36
|
-
readonly registerIngressHandlers: (ingress: Ingress) => void;
|
|
37
|
-
readonly loadContributions: () => Promise<ExtensionRuntimeContributions>;
|
|
38
|
-
readonly start: () => Promise<void>;
|
|
39
|
-
readonly stop: () => Promise<void>;
|
|
40
|
-
private readonly handleChannelConfigGet;
|
|
41
|
-
private readonly handleChannelMessageSubmit;
|
|
42
|
-
private readonly handleExtensionResponse;
|
|
43
|
-
private readonly discoverManifests;
|
|
44
|
-
private readonly toContributions;
|
|
45
|
-
private readonly readConfigUiHints;
|
|
46
|
-
private readonly createChannelAuth;
|
|
47
|
-
private readonly createChannelOutbound;
|
|
48
|
-
private readonly requestExtension;
|
|
49
|
-
private readonly assertAuthorized;
|
|
50
|
-
}
|
|
51
|
-
//#endregion
|
|
52
|
-
export { ServiceExtensionRuntime, resolveBuiltinExtensionManifestRoots, resolveExtensionManifestRoots, startDiscoveredExtensions };
|
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
import { resolveDevFirstPartyPluginDir } from "../../../commands/plugin/development-source/first-party-plugin-load-paths.utils.js";
|
|
2
|
-
import { ExtensionLifecycleService, ExtensionManifestDiscoveryService } from "./extension-lifecycle.service.js";
|
|
3
|
-
import { createRequire } from "node:module";
|
|
4
|
-
import { getDataPath } from "@nextclaw/core";
|
|
5
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
-
import { dirname, join, resolve } from "node:path";
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { randomUUID } from "node:crypto";
|
|
9
|
-
//#region src/shared/services/extensions/service-extension-runtime.service.ts
|
|
10
|
-
const EXTENSION_CONFIG_GET_INGRESS_TYPE = "extension.channel.config.get";
|
|
11
|
-
const EXTENSION_MESSAGE_SUBMIT_INGRESS_TYPE = "extension.channel.message.submit";
|
|
12
|
-
const EXTENSION_REQUEST_EVENT_TYPE = "extension.request";
|
|
13
|
-
const EXTENSION_RESPONSE_INGRESS_TYPE = "extension.response";
|
|
14
|
-
const serviceRequire = createRequire(import.meta.url);
|
|
15
|
-
const EXTENSION_REQUEST_TIMEOUT_MS = 6e4;
|
|
16
|
-
var ExtensionChannelClient = class {
|
|
17
|
-
constructor(params) {
|
|
18
|
-
this.params = params;
|
|
19
|
-
}
|
|
20
|
-
login = async ({ accountId, baseUrl, verbose }) => await this.params.request({
|
|
21
|
-
extensionId: this.params.extensionId,
|
|
22
|
-
kind: "channel.auth.login",
|
|
23
|
-
payload: {
|
|
24
|
-
channelId: this.params.channelId,
|
|
25
|
-
accountId,
|
|
26
|
-
baseUrl,
|
|
27
|
-
verbose
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
start = async (params) => await this.params.request({
|
|
31
|
-
extensionId: this.params.extensionId,
|
|
32
|
-
kind: "channel.auth.start",
|
|
33
|
-
payload: {
|
|
34
|
-
channelId: this.params.channelId,
|
|
35
|
-
accountId: params.accountId,
|
|
36
|
-
baseUrl: params.baseUrl,
|
|
37
|
-
domain: params.domain
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
poll = async ({ sessionId }) => await this.params.request({
|
|
41
|
-
extensionId: this.params.extensionId,
|
|
42
|
-
kind: "channel.auth.poll",
|
|
43
|
-
payload: {
|
|
44
|
-
channelId: this.params.channelId,
|
|
45
|
-
sessionId
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
sendText = async ({ to, text, accountId }) => await this.params.request({
|
|
49
|
-
extensionId: this.params.extensionId,
|
|
50
|
-
kind: "channel.outbound.sendText",
|
|
51
|
-
payload: {
|
|
52
|
-
channelId: this.params.channelId,
|
|
53
|
-
to,
|
|
54
|
-
text,
|
|
55
|
-
accountId
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
};
|
|
59
|
-
function uniquePaths(paths) {
|
|
60
|
-
const seen = /* @__PURE__ */ new Set();
|
|
61
|
-
const unique = [];
|
|
62
|
-
for (const path of paths) {
|
|
63
|
-
const normalized = resolve(path);
|
|
64
|
-
if (seen.has(normalized)) continue;
|
|
65
|
-
seen.add(normalized);
|
|
66
|
-
unique.push(normalized);
|
|
67
|
-
}
|
|
68
|
-
return unique;
|
|
69
|
-
}
|
|
70
|
-
function findExtensionManifestRoot(startPath) {
|
|
71
|
-
let current = resolve(startPath);
|
|
72
|
-
while (true) {
|
|
73
|
-
if (existsSync(join(current, "nextclaw.extension.json"))) return current;
|
|
74
|
-
const parent = dirname(current);
|
|
75
|
-
if (parent === current) return;
|
|
76
|
-
current = parent;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
function readBuiltinExtensionPackages() {
|
|
80
|
-
if (process.env.NEXTCLAW_DISABLE_BUILTIN_EXTENSIONS === "1") return [];
|
|
81
|
-
const packageJsonPath = resolve(dirname(fileURLToPath(import.meta.url)), "../../../..", "package.json");
|
|
82
|
-
try {
|
|
83
|
-
const packages = JSON.parse(readFileSync(packageJsonPath, "utf-8")).nextclaw?.builtinExtensions;
|
|
84
|
-
return Array.isArray(packages) ? packages.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
|
|
85
|
-
} catch {
|
|
86
|
-
return [];
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
function resolveBuiltinExtensionManifestRoots() {
|
|
90
|
-
const roots = [];
|
|
91
|
-
for (const packageName of readBuiltinExtensionPackages()) try {
|
|
92
|
-
const root = findExtensionManifestRoot(dirname(serviceRequire.resolve(packageName)));
|
|
93
|
-
if (root) roots.push(root);
|
|
94
|
-
} catch {}
|
|
95
|
-
return uniquePaths(roots);
|
|
96
|
-
}
|
|
97
|
-
function readRecord(value) {
|
|
98
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
99
|
-
}
|
|
100
|
-
function readRequiredString(value, name) {
|
|
101
|
-
if (typeof value !== "string" || !value.trim()) throw new Error(`${name} is required`);
|
|
102
|
-
return value.trim();
|
|
103
|
-
}
|
|
104
|
-
function readTextContent(value) {
|
|
105
|
-
const content = readRecord(value);
|
|
106
|
-
if (content.type !== "text" || typeof content.text !== "string") throw new Error("only text channel messages are supported by the first ingress bridge");
|
|
107
|
-
return content.text;
|
|
108
|
-
}
|
|
109
|
-
function readOptionalString(value) {
|
|
110
|
-
if (typeof value !== "string") return;
|
|
111
|
-
return value.trim() || void 0;
|
|
112
|
-
}
|
|
113
|
-
function readOptionalNumber(value) {
|
|
114
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
115
|
-
}
|
|
116
|
-
function readInboundAttachments(value) {
|
|
117
|
-
if (!Array.isArray(value)) return [];
|
|
118
|
-
return value.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))).map((entry) => ({
|
|
119
|
-
...readOptionalString(entry.id) ? { id: readOptionalString(entry.id) } : {},
|
|
120
|
-
...readOptionalString(entry.name) ? { name: readOptionalString(entry.name) } : {},
|
|
121
|
-
...readOptionalString(entry.path) ? { path: readOptionalString(entry.path) } : {},
|
|
122
|
-
...readOptionalString(entry.url) ? { url: readOptionalString(entry.url) } : {},
|
|
123
|
-
...readOptionalString(entry.assetUri) ? { assetUri: readOptionalString(entry.assetUri) } : {},
|
|
124
|
-
...readOptionalString(entry.mimeType) ? { mimeType: readOptionalString(entry.mimeType) } : {},
|
|
125
|
-
...readOptionalNumber(entry.size) !== void 0 ? { size: readOptionalNumber(entry.size) } : {},
|
|
126
|
-
...readOptionalString(entry.source) ? { source: readOptionalString(entry.source) } : {},
|
|
127
|
-
...entry.status === "ready" || entry.status === "remote-only" ? { status: entry.status } : {},
|
|
128
|
-
...readOptionalString(entry.errorCode) ? { errorCode: readOptionalString(entry.errorCode) } : {}
|
|
129
|
-
}));
|
|
130
|
-
}
|
|
131
|
-
function toInboundMessage(payload) {
|
|
132
|
-
const metadata = readRecord(payload.metadata);
|
|
133
|
-
return {
|
|
134
|
-
channel: readRequiredString(payload.channelId, "channelId"),
|
|
135
|
-
chatId: readRequiredString(payload.conversationId, "conversationId"),
|
|
136
|
-
senderId: readRequiredString(payload.senderId, "senderId"),
|
|
137
|
-
content: readTextContent(payload.content),
|
|
138
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
139
|
-
attachments: readInboundAttachments(payload.attachments),
|
|
140
|
-
metadata
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
function resolveExtensionManifestRoots(params) {
|
|
144
|
-
const devExtensionsDir = resolveDevFirstPartyPluginDir(process.env.NEXTCLAW_DEV_FIRST_PARTY_PLUGIN_DIR);
|
|
145
|
-
return uniquePaths([
|
|
146
|
-
join(getDataPath(), "extensions"),
|
|
147
|
-
join(params.workspace, ".nextclaw", "extensions"),
|
|
148
|
-
...devExtensionsDir ? [devExtensionsDir] : [],
|
|
149
|
-
...resolveBuiltinExtensionManifestRoots(),
|
|
150
|
-
...params.config.plugins.load?.paths ?? []
|
|
151
|
-
]);
|
|
152
|
-
}
|
|
153
|
-
async function startDiscoveredExtensions(params) {
|
|
154
|
-
const { config, discovery: providedDiscovery, endpoint, lifecycle: providedLifecycle, token, workspace } = params;
|
|
155
|
-
const discovery = providedDiscovery ?? new ExtensionManifestDiscoveryService();
|
|
156
|
-
const lifecycle = providedLifecycle ?? new ExtensionLifecycleService({
|
|
157
|
-
endpoint,
|
|
158
|
-
token
|
|
159
|
-
});
|
|
160
|
-
const manifests = await discovery.discover(resolveExtensionManifestRoots({
|
|
161
|
-
config,
|
|
162
|
-
workspace
|
|
163
|
-
}));
|
|
164
|
-
return {
|
|
165
|
-
lifecycle,
|
|
166
|
-
running: await lifecycle.startAll(manifests)
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
var ServiceExtensionRuntime = class {
|
|
170
|
-
token = randomUUID();
|
|
171
|
-
lifecycle = null;
|
|
172
|
-
manifests = [];
|
|
173
|
-
pendingRequests = /* @__PURE__ */ new Map();
|
|
174
|
-
constructor(gateway) {
|
|
175
|
-
this.gateway = gateway;
|
|
176
|
-
}
|
|
177
|
-
registerIngressHandlers = (ingress) => {
|
|
178
|
-
ingress.addHandler(EXTENSION_CONFIG_GET_INGRESS_TYPE, this.handleChannelConfigGet);
|
|
179
|
-
ingress.addHandler(EXTENSION_MESSAGE_SUBMIT_INGRESS_TYPE, this.handleChannelMessageSubmit);
|
|
180
|
-
ingress.addHandler(EXTENSION_RESPONSE_INGRESS_TYPE, this.handleExtensionResponse);
|
|
181
|
-
};
|
|
182
|
-
loadContributions = async () => {
|
|
183
|
-
this.manifests = await this.discoverManifests();
|
|
184
|
-
return this.toContributions(this.manifests);
|
|
185
|
-
};
|
|
186
|
-
start = async () => {
|
|
187
|
-
const endpoint = this.gateway.uiStartup.endpoint || null;
|
|
188
|
-
if (!endpoint) return;
|
|
189
|
-
const lifecycle = this.lifecycle ?? new ExtensionLifecycleService({
|
|
190
|
-
endpoint,
|
|
191
|
-
token: this.token
|
|
192
|
-
});
|
|
193
|
-
const manifests = this.manifests.length > 0 ? this.manifests : await this.discoverManifests();
|
|
194
|
-
const running = await lifecycle.startAll(manifests);
|
|
195
|
-
this.lifecycle = lifecycle;
|
|
196
|
-
if (running.length > 0) console.log(`✓ Extensions started: ${running.map((entry) => entry.manifest.id).join(", ")}`);
|
|
197
|
-
};
|
|
198
|
-
stop = async () => {
|
|
199
|
-
await this.lifecycle?.stopAll();
|
|
200
|
-
this.lifecycle = null;
|
|
201
|
-
for (const [requestId, request] of this.pendingRequests) {
|
|
202
|
-
clearTimeout(request.timeout);
|
|
203
|
-
request.reject(/* @__PURE__ */ new Error(`Extension request cancelled: ${requestId}`));
|
|
204
|
-
}
|
|
205
|
-
this.pendingRequests.clear();
|
|
206
|
-
};
|
|
207
|
-
handleChannelConfigGet = (envelope, context) => {
|
|
208
|
-
this.assertAuthorized(context);
|
|
209
|
-
const channelId = readRequiredString(readRecord(envelope.payload).channelId, "channelId");
|
|
210
|
-
return { config: this.gateway.configManager.loadConfig().channels[channelId] ?? {} };
|
|
211
|
-
};
|
|
212
|
-
handleChannelMessageSubmit = async (envelope, context) => {
|
|
213
|
-
this.assertAuthorized(context);
|
|
214
|
-
await this.gateway.messageBus.publishInbound(toInboundMessage(readRecord(envelope.payload)));
|
|
215
|
-
return { accepted: true };
|
|
216
|
-
};
|
|
217
|
-
handleExtensionResponse = (envelope, context) => {
|
|
218
|
-
this.assertAuthorized(context);
|
|
219
|
-
const payload = readRecord(envelope.payload);
|
|
220
|
-
const requestId = readRequiredString(payload.requestId, "requestId");
|
|
221
|
-
const pending = this.pendingRequests.get(requestId);
|
|
222
|
-
if (!pending) return { accepted: false };
|
|
223
|
-
this.pendingRequests.delete(requestId);
|
|
224
|
-
clearTimeout(pending.timeout);
|
|
225
|
-
if (payload.ok === false) {
|
|
226
|
-
const error = readRecord(payload.error);
|
|
227
|
-
const message = typeof error.message === "string" && error.message.trim() ? error.message.trim() : "Extension request failed";
|
|
228
|
-
pending.reject(new Error(message));
|
|
229
|
-
return { accepted: true };
|
|
230
|
-
}
|
|
231
|
-
pending.resolve(payload.data);
|
|
232
|
-
return { accepted: true };
|
|
233
|
-
};
|
|
234
|
-
discoverManifests = async () => {
|
|
235
|
-
return await new ExtensionManifestDiscoveryService().discover(resolveExtensionManifestRoots({
|
|
236
|
-
config: this.gateway.configManager.loadConfig(),
|
|
237
|
-
workspace: this.gateway.workspace
|
|
238
|
-
}));
|
|
239
|
-
};
|
|
240
|
-
toContributions = (manifests) => {
|
|
241
|
-
const channelBindings = [];
|
|
242
|
-
const uiMetadata = [];
|
|
243
|
-
for (const manifest of manifests) {
|
|
244
|
-
const channels = manifest.contributes?.channels ?? [];
|
|
245
|
-
for (const channel of channels) {
|
|
246
|
-
const channelId = readOptionalString(channel.id);
|
|
247
|
-
if (!channelId) continue;
|
|
248
|
-
const configUiHints = this.readConfigUiHints(channel.configUiHints);
|
|
249
|
-
channelBindings.push({
|
|
250
|
-
pluginId: manifest.id,
|
|
251
|
-
channelId,
|
|
252
|
-
channel: {
|
|
253
|
-
id: channelId,
|
|
254
|
-
meta: {
|
|
255
|
-
label: channel.name ?? channelId,
|
|
256
|
-
selectionLabel: channel.name ?? channelId,
|
|
257
|
-
...channel.description ? { blurb: channel.description } : {},
|
|
258
|
-
...channel.meta ?? {}
|
|
259
|
-
},
|
|
260
|
-
...channel.configSchema ? { configSchema: {
|
|
261
|
-
schema: channel.configSchema,
|
|
262
|
-
...configUiHints ? { uiHints: configUiHints } : {}
|
|
263
|
-
} } : {},
|
|
264
|
-
...channel.auth ? { auth: this.createChannelAuth(manifest.id, channelId) } : {},
|
|
265
|
-
...channel.outbound?.text ? { outbound: this.createChannelOutbound(manifest.id, channelId) } : {}
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
uiMetadata.push({
|
|
269
|
-
id: manifest.id,
|
|
270
|
-
configSchema: channel.configSchema,
|
|
271
|
-
...configUiHints ? { configUiHints } : {}
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return {
|
|
276
|
-
channelBindings,
|
|
277
|
-
uiMetadata
|
|
278
|
-
};
|
|
279
|
-
};
|
|
280
|
-
readConfigUiHints = (value) => {
|
|
281
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
282
|
-
return value;
|
|
283
|
-
};
|
|
284
|
-
createChannelAuth = (extensionId, channelId) => new ExtensionChannelClient({
|
|
285
|
-
extensionId,
|
|
286
|
-
channelId,
|
|
287
|
-
request: this.requestExtension
|
|
288
|
-
});
|
|
289
|
-
createChannelOutbound = (extensionId, channelId) => new ExtensionChannelClient({
|
|
290
|
-
extensionId,
|
|
291
|
-
channelId,
|
|
292
|
-
request: this.requestExtension
|
|
293
|
-
});
|
|
294
|
-
requestExtension = async (params) => {
|
|
295
|
-
const requestId = randomUUID();
|
|
296
|
-
const result = new Promise((resolvePromise, rejectPromise) => {
|
|
297
|
-
const timeout = setTimeout(() => {
|
|
298
|
-
this.pendingRequests.delete(requestId);
|
|
299
|
-
rejectPromise(/* @__PURE__ */ new Error(`Extension request timed out: ${params.kind}`));
|
|
300
|
-
}, EXTENSION_REQUEST_TIMEOUT_MS);
|
|
301
|
-
this.pendingRequests.set(requestId, {
|
|
302
|
-
resolve: (value) => resolvePromise(value),
|
|
303
|
-
reject: rejectPromise,
|
|
304
|
-
timeout
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
this.gateway.appEventBus.emitEnvelope({
|
|
308
|
-
type: EXTENSION_REQUEST_EVENT_TYPE,
|
|
309
|
-
payload: {
|
|
310
|
-
requestId,
|
|
311
|
-
extensionId: params.extensionId,
|
|
312
|
-
kind: params.kind,
|
|
313
|
-
payload: params.payload
|
|
314
|
-
},
|
|
315
|
-
emittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
316
|
-
source: "backend"
|
|
317
|
-
});
|
|
318
|
-
return await result;
|
|
319
|
-
};
|
|
320
|
-
assertAuthorized = (context) => {
|
|
321
|
-
if (context.token !== this.token) throw new Error("Unauthorized ingress token");
|
|
322
|
-
};
|
|
323
|
-
};
|
|
324
|
-
//#endregion
|
|
325
|
-
export { ServiceExtensionRuntime, resolveBuiltinExtensionManifestRoots, resolveExtensionManifestRoots, startDiscoveredExtensions };
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { NcpEventType } from "@nextclaw/ncp";
|
|
2
|
-
//#region src/shared/services/gateway/cron-job-handler.service.ts
|
|
3
|
-
function normalizeOptionalString(value) {
|
|
4
|
-
if (typeof value !== "string") return;
|
|
5
|
-
return value.trim() || void 0;
|
|
6
|
-
}
|
|
7
|
-
function buildCronSessionMetadata(params) {
|
|
8
|
-
const { job, agentId, accountId } = params;
|
|
9
|
-
const channel = normalizeOptionalString(job.payload.channel) ?? "cli";
|
|
10
|
-
const chatId = normalizeOptionalString(job.payload.to) ?? "direct";
|
|
11
|
-
const metadata = {
|
|
12
|
-
agentId,
|
|
13
|
-
agent_id: agentId,
|
|
14
|
-
channel,
|
|
15
|
-
chatId,
|
|
16
|
-
chat_id: chatId,
|
|
17
|
-
label: job.name,
|
|
18
|
-
cron_job_id: job.id,
|
|
19
|
-
cron_job_name: job.name,
|
|
20
|
-
session_origin: "cron"
|
|
21
|
-
};
|
|
22
|
-
if (accountId) {
|
|
23
|
-
metadata.accountId = accountId;
|
|
24
|
-
metadata.account_id = accountId;
|
|
25
|
-
}
|
|
26
|
-
return metadata;
|
|
27
|
-
}
|
|
28
|
-
function buildCronUserMessage(params) {
|
|
29
|
-
const { sessionId, content, metadata } = params;
|
|
30
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
31
|
-
return {
|
|
32
|
-
id: `${sessionId}:user:cron:${timestamp}`,
|
|
33
|
-
sessionId,
|
|
34
|
-
role: "user",
|
|
35
|
-
status: "final",
|
|
36
|
-
timestamp,
|
|
37
|
-
parts: [{
|
|
38
|
-
type: "text",
|
|
39
|
-
text: content
|
|
40
|
-
}],
|
|
41
|
-
metadata: structuredClone(metadata)
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
function extractMessageText(message) {
|
|
45
|
-
return message.parts.flatMap((part) => {
|
|
46
|
-
if (part.type === "text" || part.type === "rich-text") return [part.text];
|
|
47
|
-
return [];
|
|
48
|
-
}).map((text) => text.trim()).filter((text) => text.length > 0).join("\n\n");
|
|
49
|
-
}
|
|
50
|
-
async function runJobOverNcp(params) {
|
|
51
|
-
const { agent, sessionId, message, metadata, missingCompletedMessageError, runErrorMessage } = params;
|
|
52
|
-
let completedMessage;
|
|
53
|
-
for await (const event of agent.runApi.send({
|
|
54
|
-
sessionId,
|
|
55
|
-
message,
|
|
56
|
-
metadata
|
|
57
|
-
})) {
|
|
58
|
-
if (event.type === NcpEventType.MessageFailed) throw new Error(event.payload.error.message);
|
|
59
|
-
if (event.type === NcpEventType.RunError) throw new Error(event.payload.error ?? runErrorMessage);
|
|
60
|
-
if (event.type === NcpEventType.MessageCompleted) completedMessage = event.payload.message;
|
|
61
|
-
}
|
|
62
|
-
if (!completedMessage) throw new Error(missingCompletedMessageError);
|
|
63
|
-
return extractMessageText(completedMessage);
|
|
64
|
-
}
|
|
65
|
-
function createCronJobHandler(params) {
|
|
66
|
-
return async (job) => {
|
|
67
|
-
const ncpAgent = params.resolveNcpAgent();
|
|
68
|
-
if (!ncpAgent) throw new Error("NCP agent is not ready for cron execution.");
|
|
69
|
-
const accountId = normalizeOptionalString(job.payload.accountId);
|
|
70
|
-
const agentId = normalizeOptionalString(job.payload.agentId) ?? "main";
|
|
71
|
-
const sessionId = normalizeOptionalString(job.payload.sessionId) ?? `cron:${job.id}`;
|
|
72
|
-
const metadata = buildCronSessionMetadata({
|
|
73
|
-
job,
|
|
74
|
-
agentId,
|
|
75
|
-
accountId
|
|
76
|
-
});
|
|
77
|
-
const response = await runJobOverNcp({
|
|
78
|
-
agent: ncpAgent,
|
|
79
|
-
sessionId,
|
|
80
|
-
message: buildCronUserMessage({
|
|
81
|
-
sessionId,
|
|
82
|
-
content: job.payload.message,
|
|
83
|
-
metadata
|
|
84
|
-
}),
|
|
85
|
-
metadata,
|
|
86
|
-
missingCompletedMessageError: "cron job completed without a final assistant message",
|
|
87
|
-
runErrorMessage: "cron job failed"
|
|
88
|
-
});
|
|
89
|
-
if (job.payload.deliver && job.payload.to) await params.bus.publishOutbound({
|
|
90
|
-
channel: job.payload.channel ?? "cli",
|
|
91
|
-
chatId: job.payload.to,
|
|
92
|
-
content: response,
|
|
93
|
-
media: [],
|
|
94
|
-
metadata
|
|
95
|
-
});
|
|
96
|
-
return response;
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
//#endregion
|
|
100
|
-
export { createCronJobHandler };
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
//#region src/shared/services/runtime/utils/skills-loader.utils.d.ts
|
|
2
|
-
type SkillInfo = {
|
|
3
|
-
name: string;
|
|
4
|
-
path: string;
|
|
5
|
-
source: "workspace" | "builtin";
|
|
6
|
-
};
|
|
7
|
-
type SkillsLoaderInstance = {
|
|
8
|
-
listSkills: (filterUnavailable?: boolean) => SkillInfo[];
|
|
9
|
-
};
|
|
10
|
-
declare function createSkillsLoader(workspace: string): SkillsLoaderInstance | null;
|
|
11
|
-
//#endregion
|
|
12
|
-
export { createSkillsLoader };
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import * as NextclawCore from "@nextclaw/core";
|
|
2
|
-
//#region src/shared/services/runtime/utils/skills-loader.utils.ts
|
|
3
|
-
function createSkillsLoader(workspace) {
|
|
4
|
-
const ctor = NextclawCore.SkillsLoader;
|
|
5
|
-
if (!ctor) return null;
|
|
6
|
-
return new ctor(workspace);
|
|
7
|
-
}
|
|
8
|
-
//#endregion
|
|
9
|
-
export { createSkillsLoader };
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { AgentRuntimeHandle } from "@nextclaw/kernel";
|
|
2
|
-
import { UiNcpAgent } from "@nextclaw/server";
|
|
3
|
-
|
|
4
|
-
//#region src/shared/services/session/service-deferred-ncp-agent.service.d.ts
|
|
5
|
-
type DeferredUiNcpAgentController = {
|
|
6
|
-
agent: UiNcpAgent;
|
|
7
|
-
activate: (agent: AgentRuntimeHandle) => void;
|
|
8
|
-
clear: () => void;
|
|
9
|
-
close: () => Promise<void>;
|
|
10
|
-
isReady: () => boolean;
|
|
11
|
-
};
|
|
12
|
-
declare function createDeferredUiNcpAgent(basePath?: string): DeferredUiNcpAgentController;
|
|
13
|
-
//#endregion
|
|
14
|
-
export { DeferredUiNcpAgentController, createDeferredUiNcpAgent };
|