@roll-agent/core 0.3.1 → 0.3.3
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-add.d.ts +0 -1
- package/dist/cli/commands/agent-add.js +1 -166
- package/dist/cli/commands/agent-health.d.ts +0 -1
- package/dist/cli/commands/agent-health.js +1 -131
- package/dist/cli/commands/agent-info.d.ts +0 -1
- package/dist/cli/commands/agent-info.js +1 -57
- package/dist/cli/commands/agent-install.d.ts +0 -1
- package/dist/cli/commands/agent-install.js +1 -173
- package/dist/cli/commands/agent-list.d.ts +0 -1
- package/dist/cli/commands/agent-list.js +1 -39
- package/dist/cli/commands/agent-remove.d.ts +0 -1
- package/dist/cli/commands/agent-remove.js +1 -64
- package/dist/cli/commands/agent-start.d.ts +0 -1
- package/dist/cli/commands/agent-start.js +1 -71
- package/dist/cli/commands/agent-stop.d.ts +0 -1
- package/dist/cli/commands/agent-stop.js +1 -50
- package/dist/cli/commands/agent.d.ts +0 -1
- package/dist/cli/commands/agent.js +1 -28
- package/dist/cli/commands/ask.d.ts +0 -1
- package/dist/cli/commands/ask.js +1 -206
- package/dist/cli/commands/chat.d.ts +0 -1
- package/dist/cli/commands/chat.js +1 -33
- package/dist/cli/commands/config.d.ts +0 -1
- package/dist/cli/commands/config.js +1 -234
- package/dist/cli/commands/doctor.d.ts +0 -1
- package/dist/cli/commands/doctor.js +1 -186
- package/dist/cli/commands/run.d.ts +0 -1
- package/dist/cli/commands/run.js +1 -200
- package/dist/cli/commands/update.d.ts +0 -1
- package/dist/cli/commands/update.js +1 -477
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +1 -45
- package/dist/cli/utils/output.d.ts +0 -1
- package/dist/cli/utils/output.js +1 -52
- package/dist/cli/utils/prompt.d.ts +0 -1
- package/dist/cli/utils/prompt.js +1 -4
- package/dist/cli/utils/update-checker.d.ts +0 -1
- package/dist/cli/utils/update-checker.js +1 -145
- package/dist/config/defaults.d.ts +0 -1
- package/dist/config/defaults.js +1 -15
- package/dist/config/helpers.d.ts +0 -1
- package/dist/config/helpers.js +1 -70
- package/dist/config/index.d.ts +0 -1
- package/dist/config/index.js +1 -5
- package/dist/config/loader.d.ts +0 -1
- package/dist/config/loader.js +1 -289
- package/dist/config/migration.d.ts +0 -1
- package/dist/config/migration.js +1 -260
- package/dist/config/schema.d.ts +0 -1
- package/dist/config/schema.js +1 -25
- package/dist/llm/engine.d.ts +0 -1
- package/dist/llm/engine.js +1 -31
- package/dist/llm/index.d.ts +0 -1
- package/dist/llm/index.js +1 -3
- package/dist/llm/providers.d.ts +0 -1
- package/dist/llm/providers.js +1 -39
- package/dist/mcp/client-manager.d.ts +0 -1
- package/dist/mcp/client-manager.js +1 -82
- package/dist/mcp/index.d.ts +0 -1
- package/dist/mcp/index.js +1 -2
- package/dist/mcp/sampling-handler.d.ts +0 -1
- package/dist/mcp/sampling-handler.js +1 -58
- package/dist/registry/dev-spawn.d.ts +7 -0
- package/dist/registry/dev-spawn.js +1 -0
- package/dist/registry/discovery.d.ts +0 -1
- package/dist/registry/discovery.js +1 -313
- package/dist/registry/health-check.d.ts +0 -1
- package/dist/registry/health-check.js +1 -65
- package/dist/registry/index.d.ts +0 -1
- package/dist/registry/index.js +1 -5
- package/dist/registry/manifest.d.ts +0 -1
- package/dist/registry/manifest.js +1 -24
- package/dist/registry/process-manager.d.ts +0 -1
- package/dist/registry/process-manager.js +1 -204
- package/dist/registry/runtime-setup.d.ts +0 -1
- package/dist/registry/runtime-setup.js +1 -67
- package/dist/registry/source.d.ts +0 -1
- package/dist/registry/source.js +1 -139
- package/dist/registry/store.d.ts +0 -1
- package/dist/registry/store.js +1 -372
- package/dist/router/declarative.d.ts +0 -1
- package/dist/router/declarative.js +1 -9
- package/dist/router/index.d.ts +0 -1
- package/dist/router/index.js +1 -3
- package/dist/router/llm-router.d.ts +0 -1
- package/dist/router/llm-router.js +1 -58
- package/dist/tool-runtime/argument-extractor.d.ts +0 -1
- package/dist/tool-runtime/argument-extractor.js +1 -97
- package/dist/tool-runtime/extraction-schema.d.ts +0 -1
- package/dist/tool-runtime/extraction-schema.js +1 -220
- package/dist/tool-runtime/messages.d.ts +0 -1
- package/dist/tool-runtime/messages.js +1 -26
- package/dist/tool-runtime/preflight.d.ts +0 -1
- package/dist/tool-runtime/preflight.js +1 -130
- package/dist/tool-runtime/schema.d.ts +0 -1
- package/dist/tool-runtime/schema.js +1 -64
- package/dist/types/agent.d.ts +0 -1
- package/dist/types/agent.js +1 -22
- package/dist/types/ask.d.ts +0 -1
- package/dist/types/ask.js +1 -15
- package/dist/types/chat.d.ts +0 -1
- package/dist/types/chat.js +1 -9
- package/dist/types/config.d.ts +0 -1
- package/dist/types/config.js +1 -2
- package/dist/types/mcp.d.ts +0 -1
- package/dist/types/mcp.js +1 -2
- package/dist/types/router.d.ts +0 -1
- package/dist/types/router.js +1 -8
- package/package.json +2 -2
- package/dist/cli/commands/agent-add.d.ts.map +0 -1
- package/dist/cli/commands/agent-add.js.map +0 -1
- package/dist/cli/commands/agent-health.d.ts.map +0 -1
- package/dist/cli/commands/agent-health.js.map +0 -1
- package/dist/cli/commands/agent-info.d.ts.map +0 -1
- package/dist/cli/commands/agent-info.js.map +0 -1
- package/dist/cli/commands/agent-install.d.ts.map +0 -1
- package/dist/cli/commands/agent-install.js.map +0 -1
- package/dist/cli/commands/agent-list.d.ts.map +0 -1
- package/dist/cli/commands/agent-list.js.map +0 -1
- package/dist/cli/commands/agent-remove.d.ts.map +0 -1
- package/dist/cli/commands/agent-remove.js.map +0 -1
- package/dist/cli/commands/agent-start.d.ts.map +0 -1
- package/dist/cli/commands/agent-start.js.map +0 -1
- package/dist/cli/commands/agent-stop.d.ts.map +0 -1
- package/dist/cli/commands/agent-stop.js.map +0 -1
- package/dist/cli/commands/agent.d.ts.map +0 -1
- package/dist/cli/commands/agent.js.map +0 -1
- package/dist/cli/commands/ask.d.ts.map +0 -1
- package/dist/cli/commands/ask.js.map +0 -1
- package/dist/cli/commands/chat.d.ts.map +0 -1
- package/dist/cli/commands/chat.js.map +0 -1
- package/dist/cli/commands/config.d.ts.map +0 -1
- package/dist/cli/commands/config.js.map +0 -1
- package/dist/cli/commands/doctor.d.ts.map +0 -1
- package/dist/cli/commands/doctor.js.map +0 -1
- package/dist/cli/commands/run.d.ts.map +0 -1
- package/dist/cli/commands/run.js.map +0 -1
- package/dist/cli/commands/update.d.ts.map +0 -1
- package/dist/cli/commands/update.js.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/utils/output.d.ts.map +0 -1
- package/dist/cli/utils/output.js.map +0 -1
- package/dist/cli/utils/prompt.d.ts.map +0 -1
- package/dist/cli/utils/prompt.js.map +0 -1
- package/dist/cli/utils/update-checker.d.ts.map +0 -1
- package/dist/cli/utils/update-checker.js.map +0 -1
- package/dist/config/defaults.d.ts.map +0 -1
- package/dist/config/defaults.js.map +0 -1
- package/dist/config/helpers.d.ts.map +0 -1
- package/dist/config/helpers.js.map +0 -1
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/index.js.map +0 -1
- package/dist/config/loader.d.ts.map +0 -1
- package/dist/config/loader.js.map +0 -1
- package/dist/config/migration.d.ts.map +0 -1
- package/dist/config/migration.js.map +0 -1
- package/dist/config/schema.d.ts.map +0 -1
- package/dist/config/schema.js.map +0 -1
- package/dist/llm/engine.d.ts.map +0 -1
- package/dist/llm/engine.js.map +0 -1
- package/dist/llm/index.d.ts.map +0 -1
- package/dist/llm/index.js.map +0 -1
- package/dist/llm/providers.d.ts.map +0 -1
- package/dist/llm/providers.js.map +0 -1
- package/dist/mcp/client-manager.d.ts.map +0 -1
- package/dist/mcp/client-manager.js.map +0 -1
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js.map +0 -1
- package/dist/mcp/sampling-handler.d.ts.map +0 -1
- package/dist/mcp/sampling-handler.js.map +0 -1
- package/dist/registry/discovery.d.ts.map +0 -1
- package/dist/registry/discovery.js.map +0 -1
- package/dist/registry/health-check.d.ts.map +0 -1
- package/dist/registry/health-check.js.map +0 -1
- package/dist/registry/index.d.ts.map +0 -1
- package/dist/registry/index.js.map +0 -1
- package/dist/registry/manifest.d.ts.map +0 -1
- package/dist/registry/manifest.js.map +0 -1
- package/dist/registry/process-manager.d.ts.map +0 -1
- package/dist/registry/process-manager.js.map +0 -1
- package/dist/registry/runtime-setup.d.ts.map +0 -1
- package/dist/registry/runtime-setup.js.map +0 -1
- package/dist/registry/source.d.ts.map +0 -1
- package/dist/registry/source.js.map +0 -1
- package/dist/registry/store.d.ts.map +0 -1
- package/dist/registry/store.js.map +0 -1
- package/dist/router/declarative.d.ts.map +0 -1
- package/dist/router/declarative.js.map +0 -1
- package/dist/router/index.d.ts.map +0 -1
- package/dist/router/index.js.map +0 -1
- package/dist/router/llm-router.d.ts.map +0 -1
- package/dist/router/llm-router.js.map +0 -1
- package/dist/tool-runtime/argument-extractor.d.ts.map +0 -1
- package/dist/tool-runtime/argument-extractor.js.map +0 -1
- package/dist/tool-runtime/extraction-schema.d.ts.map +0 -1
- package/dist/tool-runtime/extraction-schema.js.map +0 -1
- package/dist/tool-runtime/messages.d.ts.map +0 -1
- package/dist/tool-runtime/messages.js.map +0 -1
- package/dist/tool-runtime/preflight.d.ts.map +0 -1
- package/dist/tool-runtime/preflight.js.map +0 -1
- package/dist/tool-runtime/schema.d.ts.map +0 -1
- package/dist/tool-runtime/schema.js.map +0 -1
- package/dist/types/agent.d.ts.map +0 -1
- package/dist/types/agent.js.map +0 -1
- package/dist/types/ask.d.ts.map +0 -1
- package/dist/types/ask.js.map +0 -1
- package/dist/types/chat.d.ts.map +0 -1
- package/dist/types/chat.js.map +0 -1
- package/dist/types/config.d.ts.map +0 -1
- package/dist/types/config.js.map +0 -1
- package/dist/types/mcp.d.ts.map +0 -1
- package/dist/types/mcp.js.map +0 -1
- package/dist/types/router.d.ts.map +0 -1
- package/dist/types/router.js.map +0 -1
|
@@ -1,313 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { resolve, sep } from "node:path";
|
|
3
|
-
import matter from "gray-matter";
|
|
4
|
-
import { z } from "zod";
|
|
5
|
-
import { parse as parseYaml } from "yaml";
|
|
6
|
-
import { createDefaultRuntimeForTransport } from "../types/agent.js";
|
|
7
|
-
/** SKILL.md 文件名 */
|
|
8
|
-
const SKILL_FILE_NAME = "SKILL.md";
|
|
9
|
-
const PACKAGE_JSON_FILE_NAME = "package.json";
|
|
10
|
-
const skillEnvDeclarationSchema = z.object({
|
|
11
|
-
name: z.string().min(1),
|
|
12
|
-
purpose: z.string().optional(),
|
|
13
|
-
example: z.string().optional(),
|
|
14
|
-
default: z.string().optional(),
|
|
15
|
-
});
|
|
16
|
-
const skillEnvDeclarationsSchema = z.object({
|
|
17
|
-
required: z.array(skillEnvDeclarationSchema).optional(),
|
|
18
|
-
optional: z.array(skillEnvDeclarationSchema).optional(),
|
|
19
|
-
});
|
|
20
|
-
/**
|
|
21
|
-
* 解析指定目录下的 SKILL.md,提取 Agent 描述和传输配置。
|
|
22
|
-
*
|
|
23
|
-
* @throws 找不到 SKILL.md 或缺少必需字段时抛出错误
|
|
24
|
-
*/
|
|
25
|
-
export function discoverAgent(agentDir) {
|
|
26
|
-
const absDir = resolve(agentDir);
|
|
27
|
-
const skillPath = resolve(absDir, SKILL_FILE_NAME);
|
|
28
|
-
if (!existsSync(skillPath)) {
|
|
29
|
-
throw new Error(`SKILL.md not found in ${absDir}`);
|
|
30
|
-
}
|
|
31
|
-
const raw = readFileSync(skillPath, "utf-8");
|
|
32
|
-
const { data, content: skillBody } = matter(raw);
|
|
33
|
-
const manifest = readRollAgentManifest(absDir);
|
|
34
|
-
// 校验必需字段
|
|
35
|
-
const frontmatter = data;
|
|
36
|
-
const name = frontmatter["name"];
|
|
37
|
-
const description = frontmatter["description"];
|
|
38
|
-
if (typeof name !== "string" || name.length === 0) {
|
|
39
|
-
throw new Error(`SKILL.md missing required field "name" in ${skillPath}`);
|
|
40
|
-
}
|
|
41
|
-
if (typeof description !== "string" || description.length === 0) {
|
|
42
|
-
throw new Error(`SKILL.md missing required field "description" in ${skillPath}`);
|
|
43
|
-
}
|
|
44
|
-
// 提取 metadata
|
|
45
|
-
const rawMetadata = (frontmatter["metadata"] ?? {});
|
|
46
|
-
const metadata = {};
|
|
47
|
-
for (const [key, value] of Object.entries(rawMetadata)) {
|
|
48
|
-
metadata[key] = String(value);
|
|
49
|
-
}
|
|
50
|
-
const license = typeof frontmatter["license"] === "string" ? frontmatter["license"] : undefined;
|
|
51
|
-
const compatibility = typeof frontmatter["compatibility"] === "string" ? frontmatter["compatibility"] : undefined;
|
|
52
|
-
const env = loadSkillEnv(absDir, metadata, skillPath);
|
|
53
|
-
const skill = {
|
|
54
|
-
name,
|
|
55
|
-
description,
|
|
56
|
-
...(license ? { license } : {}),
|
|
57
|
-
...(compatibility ? { compatibility } : {}),
|
|
58
|
-
metadata,
|
|
59
|
-
...(env ? { env } : {}),
|
|
60
|
-
};
|
|
61
|
-
const manifestResolution = manifest ? resolveRuntimeFromManifest(manifest) : undefined;
|
|
62
|
-
if (manifestResolution && hasLegacyRuntimeMetadata(metadata)) {
|
|
63
|
-
const legacyTransport = resolveTransport(metadata);
|
|
64
|
-
if (!sameTransport(legacyTransport, manifestResolution.transport)) {
|
|
65
|
-
throw new Error(`Conflicting runtime metadata in ${skillPath}: package.json#rollAgent and SKILL.md metadata disagree`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const transport = manifestResolution?.transport ?? resolveTransport(metadata);
|
|
69
|
-
const runtime = manifestResolution?.runtime ?? createDefaultRuntimeForTransport(transport);
|
|
70
|
-
return { skill, transport, runtime, skillPath, skillBody: skillBody.trim() };
|
|
71
|
-
}
|
|
72
|
-
function normalizeSkillEnv(value, skillPath) {
|
|
73
|
-
if (value === undefined) {
|
|
74
|
-
return undefined;
|
|
75
|
-
}
|
|
76
|
-
const parsed = skillEnvDeclarationsSchema.safeParse(value);
|
|
77
|
-
if (!parsed.success) {
|
|
78
|
-
const issues = parsed.error.issues
|
|
79
|
-
.map((issue) => {
|
|
80
|
-
const path = issue.path.length > 0 ? `env.${issue.path.join(".")}` : "env";
|
|
81
|
-
return `${path}: ${issue.message}`;
|
|
82
|
-
})
|
|
83
|
-
.join("; ");
|
|
84
|
-
throw new Error(`SKILL.md has invalid "env" declaration in ${skillPath}: ${issues}`);
|
|
85
|
-
}
|
|
86
|
-
const env = parsed.data;
|
|
87
|
-
if (!env.required && !env.optional) {
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
|
-
return {
|
|
91
|
-
...(env.required ? { required: env.required.map(normalizeParsedSkillEnvDeclaration) } : {}),
|
|
92
|
-
...(env.optional ? { optional: env.optional.map(normalizeParsedSkillEnvDeclaration) } : {}),
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
function loadSkillEnv(agentDir, metadata, skillPath) {
|
|
96
|
-
const envFile = metadata["roll-env-file"];
|
|
97
|
-
if (!envFile) {
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
|
-
const envPath = resolve(agentDir, envFile);
|
|
101
|
-
if (!isPathInside(agentDir, envPath)) {
|
|
102
|
-
throw new Error(`SKILL.md roll-env-file must stay within agent directory: ${skillPath}`);
|
|
103
|
-
}
|
|
104
|
-
if (!existsSync(envPath)) {
|
|
105
|
-
throw new Error(`SKILL.md roll-env-file not found: ${envPath}`);
|
|
106
|
-
}
|
|
107
|
-
const realAgentDir = realpathSync(agentDir);
|
|
108
|
-
const realEnvPath = realpathSync(envPath);
|
|
109
|
-
if (!isPathInside(realAgentDir, realEnvPath)) {
|
|
110
|
-
throw new Error(`SKILL.md roll-env-file must stay within agent directory: ${skillPath}`);
|
|
111
|
-
}
|
|
112
|
-
let parsed;
|
|
113
|
-
try {
|
|
114
|
-
parsed = parseYaml(readFileSync(realEnvPath, "utf-8"));
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
throw new Error(`Failed to parse roll-env-file for ${skillPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
118
|
-
}
|
|
119
|
-
return normalizeSkillEnv(parsed, realEnvPath);
|
|
120
|
-
}
|
|
121
|
-
function isPathInside(rootDir, targetPath) {
|
|
122
|
-
return targetPath === rootDir || targetPath.startsWith(`${rootDir}${sep}`);
|
|
123
|
-
}
|
|
124
|
-
function normalizeParsedSkillEnvDeclaration(value) {
|
|
125
|
-
return {
|
|
126
|
-
name: value.name,
|
|
127
|
-
...(value.purpose ? { purpose: value.purpose } : {}),
|
|
128
|
-
...(value.example ? { example: value.example } : {}),
|
|
129
|
-
...(value.default ? { default: value.default } : {}),
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
/** 根据 SKILL.md metadata 确定传输模式 */
|
|
133
|
-
function resolveTransport(meta) {
|
|
134
|
-
const rawTransport = meta["roll-transport"];
|
|
135
|
-
const transportType = rawTransport === "streamable-http" ? "streamable-http" : "stdio";
|
|
136
|
-
if (transportType === "streamable-http") {
|
|
137
|
-
const endpoint = meta["roll-endpoint"];
|
|
138
|
-
if (!endpoint) {
|
|
139
|
-
throw new Error(`SKILL.md declares streamable-http transport but missing "roll-endpoint"`);
|
|
140
|
-
}
|
|
141
|
-
return { type: "streamable-http", endpoint };
|
|
142
|
-
}
|
|
143
|
-
// stdio 模式:使用 roll-command 或默认 node 启动
|
|
144
|
-
const command = meta["roll-command"] ?? "node --experimental-strip-types src/index.ts";
|
|
145
|
-
const parts = command.split(/\s+/);
|
|
146
|
-
const executable = parts[0];
|
|
147
|
-
const args = parts.slice(1);
|
|
148
|
-
if (!executable) {
|
|
149
|
-
throw new Error(`Invalid roll-command in SKILL.md`);
|
|
150
|
-
}
|
|
151
|
-
if (args.length > 0) {
|
|
152
|
-
return { type: "stdio", command: executable, args };
|
|
153
|
-
}
|
|
154
|
-
return { type: "stdio", command: executable };
|
|
155
|
-
}
|
|
156
|
-
function readRollAgentManifest(agentDir) {
|
|
157
|
-
const packageJsonPath = resolve(agentDir, PACKAGE_JSON_FILE_NAME);
|
|
158
|
-
if (!existsSync(packageJsonPath)) {
|
|
159
|
-
return undefined;
|
|
160
|
-
}
|
|
161
|
-
let parsed;
|
|
162
|
-
try {
|
|
163
|
-
parsed = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
164
|
-
}
|
|
165
|
-
catch {
|
|
166
|
-
throw new Error(`Invalid package.json in ${agentDir}`);
|
|
167
|
-
}
|
|
168
|
-
if (!isJsonRecord(parsed) || !isJsonRecord(parsed["rollAgent"])) {
|
|
169
|
-
return undefined;
|
|
170
|
-
}
|
|
171
|
-
const rollAgent = parsed["rollAgent"];
|
|
172
|
-
const runtime = isJsonRecord(rollAgent["runtime"]) ? rollAgent["runtime"] : undefined;
|
|
173
|
-
if (!runtime) {
|
|
174
|
-
throw new Error(`package.json#rollAgent.runtime is required in ${packageJsonPath}`);
|
|
175
|
-
}
|
|
176
|
-
const manifest = {
|
|
177
|
-
runtime: {
|
|
178
|
-
ownership: readString(runtime["ownership"]) ?? "",
|
|
179
|
-
transport: readString(runtime["transport"]) ?? "",
|
|
180
|
-
},
|
|
181
|
-
};
|
|
182
|
-
if (isJsonRecord(rollAgent["start"])) {
|
|
183
|
-
const command = readString(rollAgent["start"]["command"]);
|
|
184
|
-
const args = normalizeStringArray(rollAgent["start"]["args"]);
|
|
185
|
-
if (command || args) {
|
|
186
|
-
manifest.start = {
|
|
187
|
-
...(command ? { command } : {}),
|
|
188
|
-
...(args ? { args } : {}),
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (isJsonRecord(rollAgent["endpoint"])) {
|
|
193
|
-
const url = readString(rollAgent["endpoint"]["url"]);
|
|
194
|
-
const path = readString(rollAgent["endpoint"]["path"]);
|
|
195
|
-
const port = typeof rollAgent["endpoint"]["port"] === "number" ? rollAgent["endpoint"]["port"] : undefined;
|
|
196
|
-
if (url || path || port !== undefined) {
|
|
197
|
-
manifest.endpoint = {
|
|
198
|
-
...(url ? { url } : {}),
|
|
199
|
-
...(path ? { path } : {}),
|
|
200
|
-
...(port !== undefined ? { port } : {}),
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
if (isJsonRecord(rollAgent["setup"]) && isJsonRecord(rollAgent["setup"]["playwright"])) {
|
|
205
|
-
const browsers = normalizeStringArray(rollAgent["setup"]["playwright"]["browsers"]);
|
|
206
|
-
if (browsers && browsers.length > 0) {
|
|
207
|
-
manifest.setup = {
|
|
208
|
-
playwright: {
|
|
209
|
-
browsers,
|
|
210
|
-
},
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return manifest;
|
|
215
|
-
}
|
|
216
|
-
function resolveRuntimeFromManifest(manifest) {
|
|
217
|
-
const { ownership, transport } = manifest.runtime;
|
|
218
|
-
if (ownership === "on-demand" && transport === "stdio") {
|
|
219
|
-
const command = manifest.start?.command;
|
|
220
|
-
if (!command) {
|
|
221
|
-
throw new Error(`package.json#rollAgent.start.command is required for stdio runtime`);
|
|
222
|
-
}
|
|
223
|
-
const args = manifest.start?.args;
|
|
224
|
-
return {
|
|
225
|
-
transport: args ? { type: "stdio", command, args } : { type: "stdio", command },
|
|
226
|
-
runtime: { ownership: "on-demand" },
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
if (ownership === "core-managed" && transport === "streamable-http") {
|
|
230
|
-
const command = manifest.start?.command;
|
|
231
|
-
const endpoint = resolveManifestHttpEndpoint(manifest);
|
|
232
|
-
const path = manifest.endpoint?.path;
|
|
233
|
-
const port = manifest.endpoint?.port;
|
|
234
|
-
if (!command || !path || port === undefined) {
|
|
235
|
-
throw new Error(`package.json#rollAgent for core-managed streamable-http requires start.command, endpoint.path and endpoint.port`);
|
|
236
|
-
}
|
|
237
|
-
const args = manifest.start?.args;
|
|
238
|
-
const setupBrowsers = manifest.setup?.playwright?.browsers;
|
|
239
|
-
return {
|
|
240
|
-
transport: {
|
|
241
|
-
type: "streamable-http",
|
|
242
|
-
endpoint,
|
|
243
|
-
},
|
|
244
|
-
runtime: {
|
|
245
|
-
ownership: "core-managed",
|
|
246
|
-
start: args ? { command, args } : { command },
|
|
247
|
-
endpoint: { path, port },
|
|
248
|
-
...(setupBrowsers && setupBrowsers.length > 0
|
|
249
|
-
? { setup: { playwright: { browsers: setupBrowsers } } }
|
|
250
|
-
: {}),
|
|
251
|
-
},
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
if (ownership === "external-managed" && transport === "streamable-http") {
|
|
255
|
-
return {
|
|
256
|
-
transport: {
|
|
257
|
-
type: "streamable-http",
|
|
258
|
-
endpoint: resolveManifestHttpEndpoint(manifest),
|
|
259
|
-
},
|
|
260
|
-
runtime: { ownership: "external-managed" },
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
throw new Error(`Unsupported package.json#rollAgent runtime combination: ${ownership}/${transport}`);
|
|
264
|
-
}
|
|
265
|
-
function resolveManifestHttpEndpoint(manifest) {
|
|
266
|
-
const url = manifest.endpoint?.url;
|
|
267
|
-
if (url) {
|
|
268
|
-
return url;
|
|
269
|
-
}
|
|
270
|
-
const path = manifest.endpoint?.path;
|
|
271
|
-
const port = manifest.endpoint?.port;
|
|
272
|
-
if (!path || port === undefined) {
|
|
273
|
-
throw new Error(`package.json#rollAgent.streamable-http requires endpoint.url or endpoint.path + endpoint.port`);
|
|
274
|
-
}
|
|
275
|
-
return buildLocalHttpEndpoint(path, port);
|
|
276
|
-
}
|
|
277
|
-
function buildLocalHttpEndpoint(path, port) {
|
|
278
|
-
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
279
|
-
return `http://127.0.0.1:${String(port)}${normalizedPath}`;
|
|
280
|
-
}
|
|
281
|
-
function hasLegacyRuntimeMetadata(meta) {
|
|
282
|
-
return (typeof meta["roll-transport"] === "string" ||
|
|
283
|
-
typeof meta["roll-endpoint"] === "string" ||
|
|
284
|
-
typeof meta["roll-command"] === "string");
|
|
285
|
-
}
|
|
286
|
-
function sameTransport(left, right) {
|
|
287
|
-
if (left.type !== right.type) {
|
|
288
|
-
return false;
|
|
289
|
-
}
|
|
290
|
-
if (left.type === "streamable-http" && right.type === "streamable-http") {
|
|
291
|
-
return left.endpoint === right.endpoint;
|
|
292
|
-
}
|
|
293
|
-
if (left.type !== "stdio" || right.type !== "stdio") {
|
|
294
|
-
return false;
|
|
295
|
-
}
|
|
296
|
-
const leftArgs = left.args ?? [];
|
|
297
|
-
const rightArgs = right.args ?? [];
|
|
298
|
-
return left.command === right.command && leftArgs.join("\u0000") === rightArgs.join("\u0000");
|
|
299
|
-
}
|
|
300
|
-
function readString(value) {
|
|
301
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
302
|
-
}
|
|
303
|
-
function normalizeStringArray(value) {
|
|
304
|
-
if (!Array.isArray(value)) {
|
|
305
|
-
return undefined;
|
|
306
|
-
}
|
|
307
|
-
const next = value.filter((entry) => typeof entry === "string");
|
|
308
|
-
return next.length > 0 ? next : undefined;
|
|
309
|
-
}
|
|
310
|
-
function isJsonRecord(value) {
|
|
311
|
-
return typeof value === "object" && value !== null;
|
|
312
|
-
}
|
|
313
|
-
//# sourceMappingURL=discovery.js.map
|
|
1
|
+
import{readFileSync as t,existsSync as r,realpathSync as n}from"node:fs";import{resolve as e,sep as o}from"node:path";import i from"gray-matter";import{z as a}from"zod";import{parse as s}from"yaml";import{createDefaultRuntimeForTransport as p}from"../types/agent.js";const d="SKILL.md",l="package.json",m=a.object({name:a.string().min(1),purpose:a.string().optional(),example:a.string().optional(),default:a.string().optional()}),c=a.object({required:a.array(m).optional(),optional:a.array(m).optional()});export function discoverAgent(n){const o=e(n),a=e(o,d);if(!r(a))throw new Error(`SKILL.md not found in ${o}`);const s=t(a,"utf-8"),{data:l,content:m}=i(s),c=y(o),u=l,g=u.name,h=u.description;if("string"!=typeof g||0===g.length)throw new Error(`SKILL.md missing required field "name" in ${a}`);if("string"!=typeof h||0===h.length)throw new Error(`SKILL.md missing required field "description" in ${a}`);const v=u.metadata??{},$={};for(const[t,r]of Object.entries(v))$[t]=String(r);const j="string"==typeof u.license?u.license:void 0,S="string"==typeof u.compatibility?u.compatibility:void 0,I=f(o,$,a),k={name:g,description:h,...j?{license:j}:{},...S?{compatibility:S}:{},metadata:$,...I?{env:I}:{}},q=c?b(c):void 0;if(q&&L($)){if(!E(w($),q.transport))throw new Error(`Conflicting runtime metadata in ${a}: package.json#rollAgent and SKILL.md metadata disagree`)}const A=q?.transport??w($);return{skill:k,transport:A,runtime:q?.runtime??p(A),skillPath:a,skillBody:m.trim()}}function u(t,r){if(void 0===t)return;const n=c.safeParse(t);if(!n.success){const t=n.error.issues.map(t=>`${t.path.length>0?`env.${t.path.join(".")}`:"env"}: ${t.message}`).join("; ");throw new Error(`SKILL.md has invalid "env" declaration in ${r}: ${t}`)}const e=n.data;return e.required||e.optional?{...e.required?{required:e.required.map(h)}:{},...e.optional?{optional:e.optional.map(h)}:{}}:void 0}function f(o,i,a){const p=i["roll-env-file"];if(!p)return;const d=e(o,p);if(!g(o,d))throw new Error(`SKILL.md roll-env-file must stay within agent directory: ${a}`);if(!r(d))throw new Error(`SKILL.md roll-env-file not found: ${d}`);const l=n(o),m=n(d);if(!g(l,m))throw new Error(`SKILL.md roll-env-file must stay within agent directory: ${a}`);let c;try{c=s(t(m,"utf-8"))}catch(t){throw new Error(`Failed to parse roll-env-file for ${a}: ${t instanceof Error?t.message:String(t)}`)}return u(c,m)}function g(t,r){return r===t||r.startsWith(`${t}${o}`)}function h(t){return{name:t.name,...t.purpose?{purpose:t.purpose}:{},...t.example?{example:t.example}:{},...t.default?{default:t.default}:{}}}function w(t){if("streamable-http"===("streamable-http"===t["roll-transport"]?"streamable-http":"stdio")){const r=t["roll-endpoint"];if(!r)throw new Error('SKILL.md declares streamable-http transport but missing "roll-endpoint"');return{type:"streamable-http",endpoint:r}}const r=(t["roll-command"]??"node --experimental-strip-types src/index.ts").split(/\s+/),n=r[0],e=r.slice(1);if(!n)throw new Error("Invalid roll-command in SKILL.md");return e.length>0?{type:"stdio",command:n,args:e}:{type:"stdio",command:n}}function y(n){const o=e(n,l);if(!r(o))return;let i;try{i=JSON.parse(t(o,"utf-8"))}catch{throw new Error(`Invalid package.json in ${n}`)}if(!I(i)||!I(i.rollAgent))return;const a=i.rollAgent,s=I(a.runtime)?a.runtime:void 0;if(!s)throw new Error(`package.json#rollAgent.runtime is required in ${o}`);const p={runtime:{ownership:j(s.ownership)??"",transport:j(s.transport)??""}};if(I(a.start)){const t=j(a.start.command),r=S(a.start.args);(t||r)&&(p.start={...t?{command:t}:{},...r?{args:r}:{}})}if(I(a.endpoint)){const t=j(a.endpoint.url),r=j(a.endpoint.path),n="number"==typeof a.endpoint.port?a.endpoint.port:void 0;(t||r||void 0!==n)&&(p.endpoint={...t?{url:t}:{},...r?{path:r}:{},...void 0!==n?{port:n}:{}})}if(I(a.setup)&&I(a.setup.playwright)){const t=S(a.setup.playwright.browsers);t&&t.length>0&&(p.setup={playwright:{browsers:t}})}return p}function b(t){const{ownership:r,transport:n}=t.runtime;if("on-demand"===r&&"stdio"===n){const r=t.start?.command;if(!r)throw new Error("package.json#rollAgent.start.command is required for stdio runtime");const n=t.start?.args;return{transport:n?{type:"stdio",command:r,args:n}:{type:"stdio",command:r},runtime:{ownership:"on-demand"}}}if("core-managed"===r&&"streamable-http"===n){const r=t.start?.command,n=v(t),e=t.endpoint?.path,o=t.endpoint?.port;if(!r||!e||void 0===o)throw new Error("package.json#rollAgent for core-managed streamable-http requires start.command, endpoint.path and endpoint.port");const i=t.start?.args,a=t.setup?.playwright?.browsers;return{transport:{type:"streamable-http",endpoint:n},runtime:{ownership:"core-managed",start:i?{command:r,args:i}:{command:r},endpoint:{path:e,port:o},...a&&a.length>0?{setup:{playwright:{browsers:a}}}:{}}}}if("external-managed"===r&&"streamable-http"===n)return{transport:{type:"streamable-http",endpoint:v(t)},runtime:{ownership:"external-managed"}};throw new Error(`Unsupported package.json#rollAgent runtime combination: ${r}/${n}`)}function v(t){const r=t.endpoint?.url;if(r)return r;const n=t.endpoint?.path,e=t.endpoint?.port;if(!n||void 0===e)throw new Error("package.json#rollAgent.streamable-http requires endpoint.url or endpoint.path + endpoint.port");return $(n,e)}function $(t,r){const n=t.startsWith("/")?t:`/${t}`;return`http://127.0.0.1:${String(r)}${n}`}function L(t){return"string"==typeof t["roll-transport"]||"string"==typeof t["roll-endpoint"]||"string"==typeof t["roll-command"]}function E(t,r){if(t.type!==r.type)return!1;if("streamable-http"===t.type&&"streamable-http"===r.type)return t.endpoint===r.endpoint;if("stdio"!==t.type||"stdio"!==r.type)return!1;const n=t.args??[],e=r.args??[];return t.command===r.command&&n.join("\0")===e.join("\0")}function j(t){return"string"==typeof t&&t.length>0?t:void 0}function S(t){if(!Array.isArray(t))return;const r=t.filter(t=>"string"==typeof t);return r.length>0?r:void 0}function I(t){return"object"==typeof t&&null!==t}
|
|
@@ -16,4 +16,3 @@ export declare function checkAgentHealth(store: AgentStore, dataDir: string, opt
|
|
|
16
16
|
readonly autoRestart: boolean;
|
|
17
17
|
readonly agentEnvMap?: Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
18
18
|
}): ReadonlyArray<HealthCheckResult>;
|
|
19
|
-
//# sourceMappingURL=health-check.d.ts.map
|
|
@@ -1,65 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
/**
|
|
3
|
-
* 检查所有状态为 "online" 的 Agent 是否仍在运行。
|
|
4
|
-
*
|
|
5
|
-
* - 如果进程已死且 autoRestart 为 true,尝试自动重启
|
|
6
|
-
* - 更新 store 中的 Agent 状态
|
|
7
|
-
*/
|
|
8
|
-
export function checkAgentHealth(store, dataDir, options = { autoRestart: false }) {
|
|
9
|
-
const agents = store.list();
|
|
10
|
-
const results = [];
|
|
11
|
-
for (const agent of agents) {
|
|
12
|
-
// 只检查 stdio 模式且预期在线的 Agent
|
|
13
|
-
if (agent.transport.type !== "stdio" || agent.status !== "online") {
|
|
14
|
-
continue;
|
|
15
|
-
}
|
|
16
|
-
const pid = getAgentPid(dataDir, agent.skill.name);
|
|
17
|
-
if (pid !== undefined) {
|
|
18
|
-
results.push({
|
|
19
|
-
agentName: agent.skill.name,
|
|
20
|
-
healthy: true,
|
|
21
|
-
restarted: false,
|
|
22
|
-
message: `运行中 (PID: ${String(pid)})`,
|
|
23
|
-
});
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
// 进程已死
|
|
27
|
-
if (options.autoRestart) {
|
|
28
|
-
const restarted = tryRestart(agent, store, dataDir, options.agentEnvMap?.[agent.skill.name]);
|
|
29
|
-
results.push(restarted);
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
store.updateStatus(agent.skill.name, "error");
|
|
33
|
-
results.push({
|
|
34
|
-
agentName: agent.skill.name,
|
|
35
|
-
healthy: false,
|
|
36
|
-
restarted: false,
|
|
37
|
-
message: "进程已退出,状态更新为 error",
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return results;
|
|
42
|
-
}
|
|
43
|
-
/** 尝试重启 Agent */
|
|
44
|
-
function tryRestart(agent, store, dataDir, env) {
|
|
45
|
-
try {
|
|
46
|
-
const newPid = startAgent(agent, dataDir, env);
|
|
47
|
-
store.updateStatus(agent.skill.name, "online");
|
|
48
|
-
return {
|
|
49
|
-
agentName: agent.skill.name,
|
|
50
|
-
healthy: true,
|
|
51
|
-
restarted: true,
|
|
52
|
-
message: `已自动重启 (PID: ${String(newPid)})`,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
catch (err) {
|
|
56
|
-
store.updateStatus(agent.skill.name, "error");
|
|
57
|
-
return {
|
|
58
|
-
agentName: agent.skill.name,
|
|
59
|
-
healthy: false,
|
|
60
|
-
restarted: false,
|
|
61
|
-
message: `重启失败: ${err instanceof Error ? err.message : String(err)}`,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
//# sourceMappingURL=health-check.js.map
|
|
1
|
+
import{getAgentPid as e,startAgent as t}from"./process-manager.js";export function checkAgentHealth(t,s,n={autoRestart:!1}){const r=t.list(),l=[];for(const o of r){if("stdio"!==o.transport.type||"online"!==o.status)continue;const r=e(s,o.skill.name);if(void 0===r)if(n.autoRestart){const e=a(o,t,s,n.agentEnvMap?.[o.skill.name]);l.push(e)}else t.updateStatus(o.skill.name,"error"),l.push({agentName:o.skill.name,healthy:!1,restarted:!1,message:"进程已退出,状态更新为 error"});else l.push({agentName:o.skill.name,healthy:!0,restarted:!1,message:`运行中 (PID: ${String(r)})`})}return l}function a(e,a,s,n){try{const r=t(e,s,n);return a.updateStatus(e.skill.name,"online"),{agentName:e.skill.name,healthy:!0,restarted:!0,message:`已自动重启 (PID: ${String(r)})`}}catch(t){return a.updateStatus(e.skill.name,"error"),{agentName:e.skill.name,healthy:!1,restarted:!1,message:`重启失败: ${t instanceof Error?t.message:String(t)}`}}}
|
package/dist/registry/index.d.ts
CHANGED
|
@@ -4,4 +4,3 @@ export { AgentStore } from "./store.ts";
|
|
|
4
4
|
export { startAgent, stopAgent, stopAgentGracefully, getAgentPid, getAgentLogPath, probeAgentEndpoint, waitForAgentReady, } from "./process-manager.ts";
|
|
5
5
|
export { runAgentSetup } from "./runtime-setup.ts";
|
|
6
6
|
export type { AgentSetupOptions, AgentSetupResult } from "./runtime-setup.ts";
|
|
7
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/registry/index.js
CHANGED
|
@@ -1,5 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export { AgentStore } from "./store.js";
|
|
3
|
-
export { startAgent, stopAgent, stopAgentGracefully, getAgentPid, getAgentLogPath, probeAgentEndpoint, waitForAgentReady, } from "./process-manager.js";
|
|
4
|
-
export { runAgentSetup } from "./runtime-setup.js";
|
|
5
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
export{discoverAgent}from"./discovery.js";export{AgentStore}from"./store.js";export{startAgent,stopAgent,stopAgentGracefully,getAgentPid,getAgentLogPath,probeAgentEndpoint,waitForAgentReady}from"./process-manager.js";export{runAgentSetup}from"./runtime-setup.js";
|
|
@@ -1,24 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
import { sanitizeInstallId } from "./source.js";
|
|
4
|
-
function yamlString(value) {
|
|
5
|
-
return JSON.stringify(value);
|
|
6
|
-
}
|
|
7
|
-
/** 为远程 MCP Agent 生成本地 manifest 目录,便于后续统一管理。 */
|
|
8
|
-
export function writeRemoteSkillManifest(input) {
|
|
9
|
-
const targetDir = resolve(input.dataDir, "remote", sanitizeInstallId(input.name));
|
|
10
|
-
mkdirSync(targetDir, { recursive: true });
|
|
11
|
-
const skillMd = `---
|
|
12
|
-
name: ${yamlString(input.name)}
|
|
13
|
-
description: ${yamlString(input.description)}
|
|
14
|
-
metadata:
|
|
15
|
-
roll-transport: streamable-http
|
|
16
|
-
roll-endpoint: ${yamlString(input.endpoint)}
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
Remote MCP Agent registered via \`roll agent add --remote\`.
|
|
20
|
-
`;
|
|
21
|
-
writeFileSync(resolve(targetDir, "SKILL.md"), skillMd, "utf-8");
|
|
22
|
-
return targetDir;
|
|
23
|
-
}
|
|
24
|
-
//# sourceMappingURL=manifest.js.map
|
|
1
|
+
import{mkdirSync as t,writeFileSync as e}from"node:fs";import{resolve as n}from"node:path";import{sanitizeInstallId as r}from"./source.js";function o(t){return JSON.stringify(t)}export function writeRemoteSkillManifest(i){const a=n(i.dataDir,"remote",r(i.name));t(a,{recursive:!0});const m=`---\nname: ${o(i.name)}\ndescription: ${o(i.description)}\nmetadata:\n roll-transport: streamable-http\n roll-endpoint: ${o(i.endpoint)}\n---\n\nRemote MCP Agent registered via \`roll agent add --remote\`.\n`;return e(n(a,"SKILL.md"),m,"utf-8"),a}
|
|
@@ -1,204 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
|
-
import { resolve, dirname, basename } from "node:path";
|
|
4
|
-
import { McpClientManager } from "../mcp/client-manager.js";
|
|
5
|
-
/** PID 文件存放目录 */
|
|
6
|
-
function pidFilePath(dataDir, agentName) {
|
|
7
|
-
return resolve(dataDir, "pids", `${agentName}.pid`);
|
|
8
|
-
}
|
|
9
|
-
function removePidFile(dataDir, agentName) {
|
|
10
|
-
const pidFile = pidFilePath(dataDir, agentName);
|
|
11
|
-
if (existsSync(pidFile)) {
|
|
12
|
-
unlinkSync(pidFile);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
/** Agent 日志文件路径 */
|
|
16
|
-
export function getAgentLogPath(dataDir, agentName) {
|
|
17
|
-
return resolve(dataDir, "logs", `${agentName}.log`);
|
|
18
|
-
}
|
|
19
|
-
/** 检查进程是否仍在运行 */
|
|
20
|
-
function isProcessAlive(pid) {
|
|
21
|
-
try {
|
|
22
|
-
process.kill(pid, 0);
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
/** 读取 Agent 的 PID,如果不存在或进程已死则返回 undefined */
|
|
30
|
-
export function getAgentPid(dataDir, agentName) {
|
|
31
|
-
const pidFile = pidFilePath(dataDir, agentName);
|
|
32
|
-
if (!existsSync(pidFile))
|
|
33
|
-
return undefined;
|
|
34
|
-
const pid = Number(readFileSync(pidFile, "utf-8").trim());
|
|
35
|
-
if (Number.isNaN(pid) || !isProcessAlive(pid)) {
|
|
36
|
-
// 清理过期的 PID 文件
|
|
37
|
-
removePidFile(dataDir, agentName);
|
|
38
|
-
return undefined;
|
|
39
|
-
}
|
|
40
|
-
return pid;
|
|
41
|
-
}
|
|
42
|
-
/** 检查 core-managed Agent 对应的 MCP endpoint 是否已就绪。 */
|
|
43
|
-
export async function probeAgentEndpoint(agent, options = {}) {
|
|
44
|
-
const clientManager = new McpClientManager();
|
|
45
|
-
try {
|
|
46
|
-
const client = await clientManager.connect(agent.skill.name, agent.transport, agent.installPath, options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {});
|
|
47
|
-
await client.listTools();
|
|
48
|
-
}
|
|
49
|
-
finally {
|
|
50
|
-
await clientManager.disconnectAll();
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/** 轮询等待 Agent MCP endpoint 就绪。 */
|
|
54
|
-
export async function waitForAgentReady(agent, options = {}) {
|
|
55
|
-
const startupTimeoutMs = options.startupTimeoutMs ?? 15_000;
|
|
56
|
-
const probeTimeoutMs = options.probeTimeoutMs ?? 2_000;
|
|
57
|
-
const intervalMs = options.intervalMs ?? 500;
|
|
58
|
-
const deadline = Date.now() + startupTimeoutMs;
|
|
59
|
-
let lastError;
|
|
60
|
-
while (Date.now() < deadline) {
|
|
61
|
-
try {
|
|
62
|
-
await probeAgentEndpoint(agent, { timeoutMs: probeTimeoutMs });
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
lastError = err;
|
|
67
|
-
await sleep(intervalMs);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
throw new Error(`Agent "${agent.skill.name}" did not become ready within ${startupTimeoutMs}ms${lastError ? `: ${lastError instanceof Error ? lastError.message : String(lastError)}` : ""}`);
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* 启动一个 Agent 为后台进程(detached)。
|
|
74
|
-
*
|
|
75
|
-
* - `stdio + on-demand`:保留给旧健康检查逻辑使用
|
|
76
|
-
* - `streamable-http + core-managed`:作为本地常驻后台服务启动
|
|
77
|
-
*/
|
|
78
|
-
export function startAgent(agent, dataDir, env) {
|
|
79
|
-
if (agent.transport.type === "streamable-http" && agent.runtime.ownership !== "core-managed") {
|
|
80
|
-
throw new Error(`Agent "${agent.skill.name}" 使用 streamable-http 传输且非 core-managed,请手动启动服务。` +
|
|
81
|
-
`\n 端点: ${agent.transport.endpoint}`);
|
|
82
|
-
}
|
|
83
|
-
const existingPid = getAgentPid(dataDir, agent.skill.name);
|
|
84
|
-
if (existingPid !== undefined) {
|
|
85
|
-
throw new Error(`Agent "${agent.skill.name}" 已在运行 (PID: ${String(existingPid)})`);
|
|
86
|
-
}
|
|
87
|
-
const spawnSpec = resolveSpawnSpec(agent);
|
|
88
|
-
const logPath = getAgentLogPath(dataDir, agent.skill.name);
|
|
89
|
-
const logDir = dirname(logPath);
|
|
90
|
-
if (!existsSync(logDir)) {
|
|
91
|
-
mkdirSync(logDir, { recursive: true });
|
|
92
|
-
}
|
|
93
|
-
const logFd = openSync(logPath, "a");
|
|
94
|
-
const child = spawn(spawnSpec.command, [...(spawnSpec.args ?? [])], {
|
|
95
|
-
cwd: agent.installPath,
|
|
96
|
-
detached: true,
|
|
97
|
-
stdio: ["ignore", logFd, logFd],
|
|
98
|
-
...(env ? { env: { ...process.env, ...env } } : {}),
|
|
99
|
-
});
|
|
100
|
-
closeSync(logFd);
|
|
101
|
-
child.unref();
|
|
102
|
-
if (!child.pid) {
|
|
103
|
-
throw new Error(`Failed to start agent "${agent.skill.name}"`);
|
|
104
|
-
}
|
|
105
|
-
// 写入 PID 文件
|
|
106
|
-
const pidFile = pidFilePath(dataDir, agent.skill.name);
|
|
107
|
-
const pidDir = dirname(pidFile);
|
|
108
|
-
if (!existsSync(pidDir)) {
|
|
109
|
-
mkdirSync(pidDir, { recursive: true });
|
|
110
|
-
}
|
|
111
|
-
writeFileSync(pidFile, String(child.pid), "utf-8");
|
|
112
|
-
return child.pid;
|
|
113
|
-
}
|
|
114
|
-
/** 停止一个后台运行的 Agent */
|
|
115
|
-
export function stopAgent(dataDir, agentName) {
|
|
116
|
-
const pid = getAgentPid(dataDir, agentName);
|
|
117
|
-
if (pid === undefined)
|
|
118
|
-
return false;
|
|
119
|
-
try {
|
|
120
|
-
process.kill(pid, "SIGTERM");
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
// 进程可能已退出
|
|
124
|
-
}
|
|
125
|
-
// 清理 PID 文件
|
|
126
|
-
removePidFile(dataDir, agentName);
|
|
127
|
-
return true;
|
|
128
|
-
}
|
|
129
|
-
export async function stopAgentGracefully(dataDir, agentName, options = {}) {
|
|
130
|
-
const pid = getAgentPid(dataDir, agentName);
|
|
131
|
-
if (pid === undefined)
|
|
132
|
-
return false;
|
|
133
|
-
try {
|
|
134
|
-
process.kill(pid, "SIGTERM");
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
removePidFile(dataDir, agentName);
|
|
138
|
-
return true;
|
|
139
|
-
}
|
|
140
|
-
const timeoutMs = options.timeoutMs ?? 15_000;
|
|
141
|
-
const intervalMs = options.intervalMs ?? 200;
|
|
142
|
-
const deadline = Date.now() + timeoutMs;
|
|
143
|
-
while (Date.now() < deadline) {
|
|
144
|
-
if (!isProcessAlive(pid)) {
|
|
145
|
-
removePidFile(dataDir, agentName);
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
148
|
-
await sleep(intervalMs);
|
|
149
|
-
}
|
|
150
|
-
throw new Error(`Agent "${agentName}" did not stop within ${timeoutMs}ms`);
|
|
151
|
-
}
|
|
152
|
-
function resolveSpawnSpec(agent) {
|
|
153
|
-
if (agent.transport.type === "stdio") {
|
|
154
|
-
return {
|
|
155
|
-
command: agent.transport.command,
|
|
156
|
-
...(agent.transport.args ? { args: agent.transport.args } : {}),
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
if (agent.runtime.ownership === "core-managed") {
|
|
160
|
-
const fallbackSpec = resolveManagedDevSpawnSpec(agent);
|
|
161
|
-
if (fallbackSpec) {
|
|
162
|
-
return fallbackSpec;
|
|
163
|
-
}
|
|
164
|
-
return {
|
|
165
|
-
command: agent.runtime.start.command,
|
|
166
|
-
...(agent.runtime.start.args ? { args: agent.runtime.start.args } : {}),
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
throw new Error(`Agent "${agent.skill.name}" does not have a managed runtime start command`);
|
|
170
|
-
}
|
|
171
|
-
function resolveManagedDevSpawnSpec(agent) {
|
|
172
|
-
if (agent.runtime.ownership !== "core-managed" ||
|
|
173
|
-
!isNodeCommand(agent.runtime.start.command) ||
|
|
174
|
-
!agent.runtime.start.args ||
|
|
175
|
-
agent.runtime.start.args.length !== 1) {
|
|
176
|
-
return undefined;
|
|
177
|
-
}
|
|
178
|
-
if (agent.source?.type === "installed-package" || agent.source?.type === "remote-manifest") {
|
|
179
|
-
return undefined;
|
|
180
|
-
}
|
|
181
|
-
const [entryArg] = agent.runtime.start.args;
|
|
182
|
-
if (!entryArg?.startsWith("dist/") || !entryArg.endsWith(".js")) {
|
|
183
|
-
return undefined;
|
|
184
|
-
}
|
|
185
|
-
const sourceEntry = entryArg.replace(/^dist\//, "src/").replace(/\.js$/, ".ts");
|
|
186
|
-
if (!existsSync(resolve(agent.installPath, sourceEntry))) {
|
|
187
|
-
return undefined;
|
|
188
|
-
}
|
|
189
|
-
// Local-path / git development sources may not have dist built yet.
|
|
190
|
-
return {
|
|
191
|
-
command: agent.runtime.start.command,
|
|
192
|
-
args: ["--experimental-strip-types", sourceEntry],
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
function isNodeCommand(command) {
|
|
196
|
-
const normalized = basename(command).toLowerCase();
|
|
197
|
-
return command === "node" || normalized === "node" || normalized === "node.exe";
|
|
198
|
-
}
|
|
199
|
-
function sleep(ms) {
|
|
200
|
-
return new Promise((resolve) => {
|
|
201
|
-
setTimeout(resolve, ms);
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
//# sourceMappingURL=process-manager.js.map
|
|
1
|
+
import{spawn as t}from"node:child_process";import{closeSync as n,existsSync as r,mkdirSync as e,openSync as o,readFileSync as i,unlinkSync as a,writeFileSync as s}from"node:fs";import{resolve as c,dirname as m}from"node:path";import{McpClientManager as u}from"../mcp/client-manager.js";import{resolveDevSpawnSpec as p}from"./dev-spawn.js";import{inferAgentSourceType as l}from"./source.js";function d(t,n){return c(t,"pids",`${n}.pid`)}function g(t,n){const e=d(t,n);r(e)&&a(e)}export function getAgentLogPath(t,n){return c(t,"logs",`${n}.log`)}function f(t){try{return process.kill(t,0),!0}catch{return!1}}export function getAgentPid(t,n){const e=d(t,n);if(!r(e))return;const o=Number(i(e,"utf-8").trim());if(!Number.isNaN(o)&&f(o))return o;g(t,n)}export async function probeAgentEndpoint(t,n={}){const r=new u;try{const e=await r.connect(t.skill.name,t.transport,t.installPath,void 0!==n.timeoutMs?{timeoutMs:n.timeoutMs}:{});await e.listTools()}finally{await r.disconnectAll()}}export async function waitForAgentReady(t,n={}){const r=n.startupTimeoutMs??15e3,e=n.probeTimeoutMs??2e3,o=n.intervalMs??500,i=Date.now()+r;let a;for(;Date.now()<i;)try{return void await probeAgentEndpoint(t,{timeoutMs:e})}catch(t){a=t,await h(o)}throw new Error(`Agent "${t.skill.name}" did not become ready within ${r}ms${a?`: ${a instanceof Error?a.message:String(a)}`:""}`)}export function startAgent(i,a,c){if("streamable-http"===i.transport.type&&"core-managed"!==i.runtime.ownership)throw new Error(`Agent "${i.skill.name}" 使用 streamable-http 传输且非 core-managed,请手动启动服务。\n 端点: ${i.transport.endpoint}`);const u=getAgentPid(a,i.skill.name);if(void 0!==u)throw new Error(`Agent "${i.skill.name}" 已在运行 (PID: ${String(u)})`);const p=w(i),l=getAgentLogPath(a,i.skill.name),g=m(l);r(g)||e(g,{recursive:!0});const f=o(l,"a"),h=t(p.command,[...p.args??[]],{cwd:i.installPath,detached:!0,stdio:["ignore",f,f],...c?{env:{...process.env,...c}}:{}});if(n(f),h.unref(),!h.pid)throw new Error(`Failed to start agent "${i.skill.name}"`);const A=d(a,i.skill.name),y=m(A);return r(y)||e(y,{recursive:!0}),s(A,String(h.pid),"utf-8"),h.pid}export function stopAgent(t,n){const r=getAgentPid(t,n);if(void 0===r)return!1;try{process.kill(r,"SIGTERM")}catch{}return g(t,n),!0}export async function stopAgentGracefully(t,n,r={}){const e=getAgentPid(t,n);if(void 0===e)return!1;try{process.kill(e,"SIGTERM")}catch{return g(t,n),!0}const o=r.timeoutMs??15e3,i=r.intervalMs??200,a=Date.now()+o;for(;Date.now()<a;){if(!f(e))return g(t,n),!0;await h(i)}throw new Error(`Agent "${n}" did not stop within ${o}ms`)}function w(t){if("stdio"===t.transport.type){const n=p(t.transport.command,t.transport.args,t.installPath,l(t));return n||{command:t.transport.command,...t.transport.args?{args:t.transport.args}:{}}}if("core-managed"===t.runtime.ownership){const n=p(t.runtime.start.command,t.runtime.start.args,t.installPath,l(t));return n||{command:t.runtime.start.command,...t.runtime.start.args?{args:t.runtime.start.args}:{}}}throw new Error(`Agent "${t.skill.name}" does not have a managed runtime start command`)}function h(t){return new Promise(n=>{setTimeout(n,t)})}
|