@rzx/ohs 1.1.0 → 1.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/frontend/index.js +13 -1
- package/dist/frontend/index.js.map +4 -4
- package/dist/index.js +783 -114
- package/dist/index.js.map +18 -14
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -196887,6 +196887,18 @@ class OpenHarnessClient {
|
|
|
196887
196887
|
signal: options.signal
|
|
196888
196888
|
});
|
|
196889
196889
|
}
|
|
196890
|
+
async listSkills(options = {}) {
|
|
196891
|
+
return await this.request("/skills", {
|
|
196892
|
+
signal: options.signal
|
|
196893
|
+
});
|
|
196894
|
+
}
|
|
196895
|
+
async removeSkill(id, input, options = {}) {
|
|
196896
|
+
return await this.request(`/skills/${encodeURIComponent(id)}`, {
|
|
196897
|
+
method: "DELETE",
|
|
196898
|
+
body: input,
|
|
196899
|
+
signal: options.signal
|
|
196900
|
+
});
|
|
196901
|
+
}
|
|
196890
196902
|
async listAgentPersonas(options = {}) {
|
|
196891
196903
|
const response = await this.request("/agent-personas", {
|
|
196892
196904
|
signal: options.signal
|
|
@@ -276984,6 +276996,8 @@ function buildAgentTranscript(messages, parts) {
|
|
|
276984
276996
|
const output = [];
|
|
276985
276997
|
const attachmentsByMessageId = {};
|
|
276986
276998
|
for (const message of [...messages].sort((a, b) => a.seq - b.seq)) {
|
|
276999
|
+
if (isPresentationOnlyMessage(message))
|
|
277000
|
+
continue;
|
|
276987
277001
|
const messageParts = (byMessage.get(message.id) ?? []).sort((a, b) => a.seq - b.seq);
|
|
276988
277002
|
const attachments = attachmentsFromParts(messageParts);
|
|
276989
277003
|
if (attachments.length > 0)
|
|
@@ -277023,6 +277037,14 @@ function buildAgentTranscript(messages, parts) {
|
|
|
277023
277037
|
}
|
|
277024
277038
|
return { messages: output, attachmentsByMessageId };
|
|
277025
277039
|
}
|
|
277040
|
+
function isPresentationOnlyMessage(message) {
|
|
277041
|
+
const presentation = message.metadata.presentation;
|
|
277042
|
+
if (!presentation || typeof presentation !== "object" || Array.isArray(presentation)) {
|
|
277043
|
+
return false;
|
|
277044
|
+
}
|
|
277045
|
+
const value = presentation;
|
|
277046
|
+
return value.kind === "model_switch" && typeof value.fromModel === "string" && Boolean(value.fromModel.trim()) && typeof value.toModel === "string" && Boolean(value.toModel.trim());
|
|
277047
|
+
}
|
|
277026
277048
|
function agentMessagesToTranscript(messages) {
|
|
277027
277049
|
const output = [];
|
|
277028
277050
|
for (const message of messages) {
|
|
@@ -280509,15 +280531,39 @@ class SessionApplicationService {
|
|
|
280509
280531
|
try {
|
|
280510
280532
|
const before = this.context.events.checkpoint();
|
|
280511
280533
|
const nextModel = metadata ? readSessionRuntimeConfig({ ...existing, metadata }).model : undefined;
|
|
280512
|
-
const
|
|
280513
|
-
|
|
280514
|
-
|
|
280515
|
-
|
|
280516
|
-
|
|
280534
|
+
const modelChanged = nextModel !== undefined && nextModel !== existing.model;
|
|
280535
|
+
const session = this.context.store.transaction(() => {
|
|
280536
|
+
const updated = this.context.store.updateSession(sessionId, {
|
|
280537
|
+
title: input.title,
|
|
280538
|
+
model: nextModel,
|
|
280539
|
+
agent: input.agent,
|
|
280540
|
+
metadata
|
|
280541
|
+
});
|
|
280542
|
+
if (!modelChanged)
|
|
280543
|
+
return updated;
|
|
280544
|
+
const message = this.context.store.createMessage({
|
|
280545
|
+
sessionId,
|
|
280546
|
+
role: "system",
|
|
280547
|
+
metadata: {
|
|
280548
|
+
presentation: {
|
|
280549
|
+
kind: "model_switch",
|
|
280550
|
+
fromModel: existing.model,
|
|
280551
|
+
toModel: nextModel
|
|
280552
|
+
}
|
|
280553
|
+
}
|
|
280554
|
+
});
|
|
280555
|
+
this.context.store.upsertMessagePart({
|
|
280556
|
+
sessionId,
|
|
280557
|
+
messageId: message.id,
|
|
280558
|
+
type: "text",
|
|
280559
|
+
status: "completed",
|
|
280560
|
+
text: `模型已切换 ${existing.model} → ${nextModel}`
|
|
280561
|
+
});
|
|
280562
|
+
return updated;
|
|
280517
280563
|
});
|
|
280518
280564
|
if (runtimeConfigurationChanged)
|
|
280519
280565
|
await this.context.agentPool.close(sessionId);
|
|
280520
|
-
if (
|
|
280566
|
+
if (modelChanged) {
|
|
280521
280567
|
this.context.contextUsageCache?.invalidate(sessionId);
|
|
280522
280568
|
}
|
|
280523
280569
|
this.context.events.publishSince(before);
|
|
@@ -286009,8 +286055,8 @@ function createDaemonImageToTextTool(options) {
|
|
|
286009
286055
|
};
|
|
286010
286056
|
}
|
|
286011
286057
|
async function recognizeAttachment(input, context, options) {
|
|
286012
|
-
if (Object.keys(input).some((key) => key !== "attachment_id")) {
|
|
286013
|
-
return errorResult4("attachment_id cannot be combined with
|
|
286058
|
+
if (Object.keys(input).some((key) => key !== "attachment_id" && key !== "prompt")) {
|
|
286059
|
+
return errorResult4("attachment_id cannot be combined with another image source", "command");
|
|
286014
286060
|
}
|
|
286015
286061
|
const assetId = typeof input.attachment_id === "string" ? input.attachment_id.trim() : "";
|
|
286016
286062
|
if (!assetId || !context.sessionId)
|
|
@@ -286913,6 +286959,148 @@ var init_default_node_application = __esm(() => {
|
|
|
286913
286959
|
init_daemon_application();
|
|
286914
286960
|
});
|
|
286915
286961
|
|
|
286962
|
+
// ../../packages/server/src/application/skill-management-service.ts
|
|
286963
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
286964
|
+
import { readFile as readFile17, realpath as realpath7, stat as stat9, unlink as unlink4 } from "node:fs/promises";
|
|
286965
|
+
import {
|
|
286966
|
+
basename as basename12,
|
|
286967
|
+
dirname as dirname20,
|
|
286968
|
+
isAbsolute as isAbsolute12,
|
|
286969
|
+
join as join39,
|
|
286970
|
+
relative as relative9,
|
|
286971
|
+
resolve as resolve26,
|
|
286972
|
+
sep as sep4
|
|
286973
|
+
} from "node:path";
|
|
286974
|
+
|
|
286975
|
+
class SkillManagementService {
|
|
286976
|
+
options;
|
|
286977
|
+
personalSkillsDir;
|
|
286978
|
+
configDir;
|
|
286979
|
+
bundledSkills;
|
|
286980
|
+
constructor(options) {
|
|
286981
|
+
this.options = options;
|
|
286982
|
+
const defaultSkillsDir = getSkillsDir();
|
|
286983
|
+
this.configDir = resolve26(options.configDir ?? dirname20(defaultSkillsDir));
|
|
286984
|
+
this.personalSkillsDir = options.configDir ? join39(this.configDir, "skills") : resolve26(defaultSkillsDir);
|
|
286985
|
+
this.bundledSkills = options.bundledSkills ?? BUNDLED_SKILLS;
|
|
286986
|
+
}
|
|
286987
|
+
async list() {
|
|
286988
|
+
const warnings = [];
|
|
286989
|
+
const projects = await validProjects(this.options.projects.list(), warnings);
|
|
286990
|
+
const skills = this.bundledSkills.map((skill) => desktopSkill(skill, "bundled"));
|
|
286991
|
+
const seen = new Set;
|
|
286992
|
+
for (const project of projects) {
|
|
286993
|
+
await this.load(join39(project.root, ".agents", "skills"), "agent", skills, seen, warnings, project);
|
|
286994
|
+
await this.load(join39(project.root, ".claude", "skills"), "agent", skills, seen, warnings, project);
|
|
286995
|
+
const managed = join39(project.root, ".openharness-ts", "skills");
|
|
286996
|
+
if (key(managed) !== key(this.personalSkillsDir)) {
|
|
286997
|
+
await this.load(managed, "project", skills, seen, warnings, project);
|
|
286998
|
+
}
|
|
286999
|
+
}
|
|
287000
|
+
await this.load(this.personalSkillsDir, "personal", skills, seen, warnings);
|
|
287001
|
+
return { skills, projects: projects.map(({ info }) => info), warnings };
|
|
287002
|
+
}
|
|
287003
|
+
async remove(input) {
|
|
287004
|
+
const snapshot = await this.list();
|
|
287005
|
+
const item = snapshot.skills.find((candidate) => candidate.id === input.id);
|
|
287006
|
+
if (!item)
|
|
287007
|
+
throw new Error("找不到指定技能,请刷新后重试。");
|
|
287008
|
+
if (item.readOnly || item.source !== "project" && item.source !== "personal") {
|
|
287009
|
+
throw new Error("此技能来自只读目录,不能删除。");
|
|
287010
|
+
}
|
|
287011
|
+
const target = await this.secureTarget(item);
|
|
287012
|
+
const current = await readFile17(target, "utf8");
|
|
287013
|
+
if (current !== input.expectedContent)
|
|
287014
|
+
throw new Error("技能已在其他位置修改,请刷新后重试。");
|
|
287015
|
+
await unlink4(target);
|
|
287016
|
+
return await this.list();
|
|
287017
|
+
}
|
|
287018
|
+
async secureTarget(item) {
|
|
287019
|
+
const target = await realpath7(item.path);
|
|
287020
|
+
if (item.source === "personal") {
|
|
287021
|
+
await assertTarget(this.configDir, this.personalSkillsDir, target);
|
|
287022
|
+
return target;
|
|
287023
|
+
}
|
|
287024
|
+
if (!item.projectPath)
|
|
287025
|
+
throw new Error("技能缺少所属项目。");
|
|
287026
|
+
const project = await realpath7(item.projectPath);
|
|
287027
|
+
await assertTarget(project, join39(project, ".openharness-ts", "skills"), target);
|
|
287028
|
+
return target;
|
|
287029
|
+
}
|
|
287030
|
+
async load(directory, source, output, seen, warnings, project) {
|
|
287031
|
+
const loader = new SkillLoader(new SkillRegistry);
|
|
287032
|
+
const loaded = await loader.loadFromDirectory(directory, {
|
|
287033
|
+
source: source === "personal" ? "user" : "project",
|
|
287034
|
+
recursive: true
|
|
287035
|
+
});
|
|
287036
|
+
for (const skill of loaded) {
|
|
287037
|
+
try {
|
|
287038
|
+
const path = await realpath7(skill.path);
|
|
287039
|
+
if (seen.has(key(path)))
|
|
287040
|
+
continue;
|
|
287041
|
+
seen.add(key(path));
|
|
287042
|
+
output.push(desktopSkill({ ...skill, path }, source, project?.info));
|
|
287043
|
+
} catch {
|
|
287044
|
+
warnings.push(`无法读取技能文件:${skill.path}`);
|
|
287045
|
+
}
|
|
287046
|
+
}
|
|
287047
|
+
}
|
|
287048
|
+
}
|
|
287049
|
+
function createSkillManagementService(options) {
|
|
287050
|
+
return new SkillManagementService(options);
|
|
287051
|
+
}
|
|
287052
|
+
async function validProjects(input, warnings) {
|
|
287053
|
+
const projects = [];
|
|
287054
|
+
const seen = new Set;
|
|
287055
|
+
for (const project of input) {
|
|
287056
|
+
try {
|
|
287057
|
+
const root = await realpath7(resolve26(project.path));
|
|
287058
|
+
if (!(await stat9(root)).isDirectory() || seen.has(key(root)))
|
|
287059
|
+
continue;
|
|
287060
|
+
seen.add(key(root));
|
|
287061
|
+
projects.push({
|
|
287062
|
+
root,
|
|
287063
|
+
info: { name: project.name.trim() || basename12(root), path: root }
|
|
287064
|
+
});
|
|
287065
|
+
} catch {
|
|
287066
|
+
warnings.push(`项目不存在或不是目录:${project.path}`);
|
|
287067
|
+
}
|
|
287068
|
+
}
|
|
287069
|
+
return projects;
|
|
287070
|
+
}
|
|
287071
|
+
function desktopSkill(skill, source, project) {
|
|
287072
|
+
return {
|
|
287073
|
+
id: `skill_${createHash12("sha256").update(source).update("\x00").update(key(skill.path)).update("\x00").update(skill.name).digest("hex").slice(0, 24)}`,
|
|
287074
|
+
name: skill.name,
|
|
287075
|
+
description: skill.description,
|
|
287076
|
+
content: skill.content,
|
|
287077
|
+
path: skill.path,
|
|
287078
|
+
source,
|
|
287079
|
+
readOnly: source === "bundled" || source === "agent",
|
|
287080
|
+
...project ? { projectPath: project.path, projectName: project.name } : {}
|
|
287081
|
+
};
|
|
287082
|
+
}
|
|
287083
|
+
async function assertTarget(owner, allowed, target) {
|
|
287084
|
+
const physicalOwner = await realpath7(owner);
|
|
287085
|
+
const physicalAllowed = await realpath7(allowed);
|
|
287086
|
+
if (!within(physicalOwner, physicalAllowed))
|
|
287087
|
+
throw new Error("技能目录通过符号链接指向了允许范围之外。");
|
|
287088
|
+
if (!within(physicalAllowed, target))
|
|
287089
|
+
throw new Error("技能文件位于允许范围之外。");
|
|
287090
|
+
}
|
|
287091
|
+
function within(root, target) {
|
|
287092
|
+
const path = relative9(root, target);
|
|
287093
|
+
return path !== "" && path !== ".." && !path.startsWith(`..${sep4}`) && !isAbsolute12(path);
|
|
287094
|
+
}
|
|
287095
|
+
function key(path) {
|
|
287096
|
+
const normalized = resolve26(path);
|
|
287097
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
287098
|
+
}
|
|
287099
|
+
var init_skill_management_service = __esm(() => {
|
|
287100
|
+
init_src();
|
|
287101
|
+
init_src2();
|
|
287102
|
+
});
|
|
287103
|
+
|
|
286916
287104
|
// ../../packages/server/src/shared/observability.ts
|
|
286917
287105
|
function writeStructuredLog(event) {
|
|
286918
287106
|
console.info(JSON.stringify({ timestamp: new Date().toISOString(), ...event }));
|
|
@@ -288121,7 +288309,9 @@ function createServiceRoutes(context) {
|
|
|
288121
288309
|
if (!context.outputStyleService)
|
|
288122
288310
|
return errorResponse(501, "Output style service is not configured");
|
|
288123
288311
|
try {
|
|
288124
|
-
return jsonResponse({
|
|
288312
|
+
return jsonResponse({
|
|
288313
|
+
styles: await context.outputStyleService.list()
|
|
288314
|
+
});
|
|
288125
288315
|
} catch (error) {
|
|
288126
288316
|
return errorResponse(500, error instanceof Error ? error.message : String(error));
|
|
288127
288317
|
}
|
|
@@ -288159,7 +288349,10 @@ function createServiceRoutes(context) {
|
|
|
288159
288349
|
if (!lease)
|
|
288160
288350
|
return errorResponse(409, "Cannot uninstall plugins while session runs are active");
|
|
288161
288351
|
try {
|
|
288162
|
-
const result = await context.pluginService.uninstall({
|
|
288352
|
+
const result = await context.pluginService.uninstall({
|
|
288353
|
+
cwd,
|
|
288354
|
+
id: c.req.param("id")
|
|
288355
|
+
});
|
|
288163
288356
|
if (result.restartRuntimes)
|
|
288164
288357
|
await context.control.closeAllRuntimes();
|
|
288165
288358
|
return jsonResponse({ message: result.message });
|
|
@@ -288191,6 +288384,36 @@ function createServiceRoutes(context) {
|
|
|
288191
288384
|
} finally {
|
|
288192
288385
|
lease.release();
|
|
288193
288386
|
}
|
|
288387
|
+
}).get("/skills", async () => {
|
|
288388
|
+
if (!context.skillService)
|
|
288389
|
+
return errorResponse(501, "Skill service is not configured");
|
|
288390
|
+
try {
|
|
288391
|
+
return jsonResponse(await context.skillService.list());
|
|
288392
|
+
} catch (error) {
|
|
288393
|
+
return errorResponse(500, error instanceof Error ? error.message : String(error));
|
|
288394
|
+
}
|
|
288395
|
+
}).delete("/skills/:id", async (c) => {
|
|
288396
|
+
if (!context.skillService)
|
|
288397
|
+
return errorResponse(501, "Skill service is not configured");
|
|
288398
|
+
const lease = context.control.acquireGlobalMutation();
|
|
288399
|
+
if (!lease)
|
|
288400
|
+
return errorResponse(409, "Cannot remove skills while session runs are active");
|
|
288401
|
+
try {
|
|
288402
|
+
const body = await readJson(c);
|
|
288403
|
+
if (typeof body.expectedContent !== "string") {
|
|
288404
|
+
return errorResponse(400, "expectedContent is required");
|
|
288405
|
+
}
|
|
288406
|
+
const result = await context.skillService.remove({
|
|
288407
|
+
id: c.req.param("id"),
|
|
288408
|
+
expectedContent: body.expectedContent
|
|
288409
|
+
});
|
|
288410
|
+
await context.control.closeAllRuntimes();
|
|
288411
|
+
return jsonResponse(result);
|
|
288412
|
+
} catch (error) {
|
|
288413
|
+
return errorResponse(400, error instanceof Error ? error.message : String(error));
|
|
288414
|
+
} finally {
|
|
288415
|
+
lease.release();
|
|
288416
|
+
}
|
|
288194
288417
|
}).get("/agent-personas", async () => {
|
|
288195
288418
|
if (!context.agentPersonaService)
|
|
288196
288419
|
return errorResponse(501, "Agent persona service is not configured");
|
|
@@ -288207,7 +288430,10 @@ function createServiceRoutes(context) {
|
|
|
288207
288430
|
return errorResponse(400, "cwd is required");
|
|
288208
288431
|
const sessionId = c.req.query("sessionId") ?? undefined;
|
|
288209
288432
|
try {
|
|
288210
|
-
const listed = await context.hooksService.list({
|
|
288433
|
+
const listed = await context.hooksService.list({
|
|
288434
|
+
cwd,
|
|
288435
|
+
...sessionId ? { sessionId } : {}
|
|
288436
|
+
});
|
|
288211
288437
|
const hooks = [...listed.hooks];
|
|
288212
288438
|
if (sessionId && context.control.runtimeInspectionAvailable) {
|
|
288213
288439
|
if (!context.control.sessionExists(sessionId)) {
|
|
@@ -288263,7 +288489,13 @@ async function installLocalPlugin(context, c, link) {
|
|
|
288263
288489
|
if (!lease)
|
|
288264
288490
|
return errorResponse(409, "Cannot install plugins while session runs are active");
|
|
288265
288491
|
try {
|
|
288266
|
-
const result = await context.pluginService.installLocal({
|
|
288492
|
+
const result = await context.pluginService.installLocal({
|
|
288493
|
+
cwd,
|
|
288494
|
+
sourcePath,
|
|
288495
|
+
scope,
|
|
288496
|
+
approvedPermissions,
|
|
288497
|
+
link
|
|
288498
|
+
});
|
|
288267
288499
|
if (result.restartRuntimes)
|
|
288268
288500
|
await context.control.closeAllRuntimes();
|
|
288269
288501
|
return jsonResponse({ message: result.message });
|
|
@@ -289615,6 +289847,7 @@ class OpenHarnessHttpServer {
|
|
|
289615
289847
|
outputStyleService: this.services.outputStyle,
|
|
289616
289848
|
projectInitService: this.services.projectInit,
|
|
289617
289849
|
pluginService: this.services.plugin,
|
|
289850
|
+
skillService: this.services.skill ?? createSkillManagementService({ projects: this.application.projects }),
|
|
289618
289851
|
agentPersonaService: this.services.agentPersona,
|
|
289619
289852
|
hooksService: this.services.hooks,
|
|
289620
289853
|
control: this.application.control
|
|
@@ -289680,6 +289913,7 @@ var init_server = __esm(() => {
|
|
|
289680
289913
|
init_dist2();
|
|
289681
289914
|
init_dist3();
|
|
289682
289915
|
init_default_node_application();
|
|
289916
|
+
init_skill_management_service();
|
|
289683
289917
|
init_support2();
|
|
289684
289918
|
init_auth();
|
|
289685
289919
|
init_attachment4();
|
|
@@ -289896,10 +290130,10 @@ var init_auth_service = __esm(() => {
|
|
|
289896
290130
|
|
|
289897
290131
|
// ../../packages/output-styles/src/index.ts
|
|
289898
290132
|
import { readdirSync as readdirSync6, readFileSync as readFileSync14, mkdirSync as mkdirSync15 } from "node:fs";
|
|
289899
|
-
import { join as
|
|
290133
|
+
import { join as join40 } from "node:path";
|
|
289900
290134
|
import { homedir as homedir11 } from "node:os";
|
|
289901
290135
|
function getOutputStylesDir() {
|
|
289902
|
-
const dir =
|
|
290136
|
+
const dir = join40(homedir11(), ".openharness-ts", "output_styles");
|
|
289903
290137
|
try {
|
|
289904
290138
|
mkdirSync15(dir, { recursive: true });
|
|
289905
290139
|
} catch {}
|
|
@@ -289916,7 +290150,7 @@ function loadOutputStyles() {
|
|
|
289916
290150
|
}
|
|
289917
290151
|
for (const file of files) {
|
|
289918
290152
|
try {
|
|
289919
|
-
const content = readFileSync14(
|
|
290153
|
+
const content = readFileSync14(join40(dir, file), "utf-8");
|
|
289920
290154
|
styles.push({ name: file.replace(/\.md$/, ""), content, source: "user" });
|
|
289921
290155
|
} catch {}
|
|
289922
290156
|
}
|
|
@@ -290575,14 +290809,14 @@ var init_profile_service = __esm(() => {
|
|
|
290575
290809
|
});
|
|
290576
290810
|
|
|
290577
290811
|
// ../../packages/server/src/application/default-services/project-init-service.ts
|
|
290578
|
-
import { join as
|
|
290812
|
+
import { join as join41 } from "node:path";
|
|
290579
290813
|
function createDefaultProjectInitService() {
|
|
290580
290814
|
return {
|
|
290581
290815
|
async init({ cwd }) {
|
|
290582
290816
|
const { writeFile, mkdir, access } = await import("node:fs/promises");
|
|
290583
290817
|
const files = [
|
|
290584
290818
|
{
|
|
290585
|
-
path:
|
|
290819
|
+
path: join41(cwd, "CLAUDE.md"),
|
|
290586
290820
|
content: `# Project Rules
|
|
290587
290821
|
|
|
290588
290822
|
Add your project-specific rules and instructions here.
|
|
@@ -290590,7 +290824,7 @@ Add your project-specific rules and instructions here.
|
|
|
290590
290824
|
label: "CLAUDE.md"
|
|
290591
290825
|
},
|
|
290592
290826
|
{
|
|
290593
|
-
path:
|
|
290827
|
+
path: join41(cwd, PROJECT_CONFIG_DIR_NAME, "README.md"),
|
|
290594
290828
|
content: `# OpenHarness Config
|
|
290595
290829
|
|
|
290596
290830
|
This directory contains OpenHarness project configuration.
|
|
@@ -290598,7 +290832,7 @@ This directory contains OpenHarness project configuration.
|
|
|
290598
290832
|
label: `${PROJECT_CONFIG_DIR_NAME}/README.md`
|
|
290599
290833
|
},
|
|
290600
290834
|
{
|
|
290601
|
-
path:
|
|
290835
|
+
path: join41(cwd, PROJECT_CONFIG_DIR_NAME, "memory", "MEMORY.md"),
|
|
290602
290836
|
content: `# Memory
|
|
290603
290837
|
|
|
290604
290838
|
This file stores project memory for the AI assistant.
|
|
@@ -290607,10 +290841,10 @@ This file stores project memory for the AI assistant.
|
|
|
290607
290841
|
}
|
|
290608
290842
|
];
|
|
290609
290843
|
const dirs = [
|
|
290610
|
-
|
|
290611
|
-
|
|
290612
|
-
|
|
290613
|
-
|
|
290844
|
+
join41(cwd, PROJECT_CONFIG_DIR_NAME),
|
|
290845
|
+
join41(cwd, PROJECT_CONFIG_DIR_NAME, "memory"),
|
|
290846
|
+
join41(cwd, PROJECT_CONFIG_DIR_NAME, "plugins"),
|
|
290847
|
+
join41(cwd, PROJECT_CONFIG_DIR_NAME, "skills")
|
|
290614
290848
|
];
|
|
290615
290849
|
const lines = ["Initializing OpenHarness project...", ""];
|
|
290616
290850
|
for (const dir of dirs) {
|
|
@@ -291041,6 +291275,7 @@ var init_application = __esm(() => {
|
|
|
291041
291275
|
init_support();
|
|
291042
291276
|
init_events2();
|
|
291043
291277
|
init_project_application_service();
|
|
291278
|
+
init_skill_management_service();
|
|
291044
291279
|
init_channel3();
|
|
291045
291280
|
init_application_backup();
|
|
291046
291281
|
init_application_retention_service();
|
|
@@ -291082,7 +291317,7 @@ var init_src23 = __esm(() => {
|
|
|
291082
291317
|
});
|
|
291083
291318
|
|
|
291084
291319
|
// src/ensure-daemon.ts
|
|
291085
|
-
import { stat as
|
|
291320
|
+
import { stat as stat10 } from "node:fs/promises";
|
|
291086
291321
|
async function ensureLocalDaemon(options = {}) {
|
|
291087
291322
|
const cliPath = options.cliPath ?? process.argv[1];
|
|
291088
291323
|
if (!cliPath)
|
|
@@ -291090,7 +291325,7 @@ async function ensureLocalDaemon(options = {}) {
|
|
|
291090
291325
|
const expectedVersion = options.expectedVersion ?? VERSION;
|
|
291091
291326
|
const daemonProbeOptions = {
|
|
291092
291327
|
expectedVersion,
|
|
291093
|
-
minimumStartedAt: (await
|
|
291328
|
+
minimumStartedAt: (await stat10(cliPath)).mtimeMs
|
|
291094
291329
|
};
|
|
291095
291330
|
await Promise.resolve().then(() => init_src23());
|
|
291096
291331
|
const waitForDaemonRegistry = async (spawned) => {
|
|
@@ -370332,13 +370567,13 @@ function createMcpCommand() {
|
|
|
370332
370567
|
// src/commands/plugin.ts
|
|
370333
370568
|
init_src15();
|
|
370334
370569
|
init_src12();
|
|
370335
|
-
import { join as
|
|
370336
|
-
import { mkdtemp as mkdtemp3, rm as
|
|
370570
|
+
import { join as join49, resolve as resolve31 } from "node:path";
|
|
370571
|
+
import { mkdtemp as mkdtemp3, rm as rm9 } from "node:fs/promises";
|
|
370337
370572
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
370338
370573
|
// ../../packages/plugin-converters/src/core/digest.ts
|
|
370339
|
-
import { createHash as
|
|
370340
|
-
import { readFile as
|
|
370341
|
-
import { join as
|
|
370574
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
370575
|
+
import { lstat as lstat6, readFile as readFile18, readdir as readdir9 } from "node:fs/promises";
|
|
370576
|
+
import { join as join43, relative as relative10 } from "node:path";
|
|
370342
370577
|
function stable(value) {
|
|
370343
370578
|
if (Array.isArray(value))
|
|
370344
370579
|
return `[${value.map(stable).join(",")}]`;
|
|
@@ -370347,14 +370582,14 @@ function stable(value) {
|
|
|
370347
370582
|
return JSON.stringify(value);
|
|
370348
370583
|
}
|
|
370349
370584
|
function digestValue(value) {
|
|
370350
|
-
return
|
|
370585
|
+
return createHash13("sha256").update(stable(value)).digest("hex");
|
|
370351
370586
|
}
|
|
370352
370587
|
async function files(root, dir = root) {
|
|
370353
370588
|
const out = [];
|
|
370354
370589
|
for (const entry of (await readdir9(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
370355
|
-
const full =
|
|
370590
|
+
const full = join43(dir, entry.name);
|
|
370356
370591
|
if (entry.isSymbolicLink())
|
|
370357
|
-
throw new Error(`Source symlink is not allowed: ${
|
|
370592
|
+
throw new Error(`Source symlink is not allowed: ${relative10(root, full)}`);
|
|
370358
370593
|
if (entry.isDirectory())
|
|
370359
370594
|
out.push(...await files(root, full));
|
|
370360
370595
|
else if (entry.isFile())
|
|
@@ -370363,12 +370598,20 @@ async function files(root, dir = root) {
|
|
|
370363
370598
|
return out;
|
|
370364
370599
|
}
|
|
370365
370600
|
async function digestSource(root) {
|
|
370366
|
-
|
|
370601
|
+
if ((await lstat6(root)).isSymbolicLink())
|
|
370602
|
+
throw new Error("Source root link is not allowed");
|
|
370603
|
+
const hash = createHash13("sha256");
|
|
370604
|
+
hash.update(`openharness-source-v2
|
|
370605
|
+
`);
|
|
370367
370606
|
for (const file of await files(root)) {
|
|
370368
|
-
|
|
370369
|
-
hash.update(
|
|
370370
|
-
|
|
370371
|
-
|
|
370607
|
+
const content = await readFile18(file);
|
|
370608
|
+
hash.update(JSON.stringify([
|
|
370609
|
+
relative10(root, file).replaceAll("\\", "/"),
|
|
370610
|
+
content.length,
|
|
370611
|
+
createHash13("sha256").update(content).digest("hex")
|
|
370612
|
+
]));
|
|
370613
|
+
hash.update(`
|
|
370614
|
+
`);
|
|
370372
370615
|
}
|
|
370373
370616
|
return hash.digest("hex");
|
|
370374
370617
|
}
|
|
@@ -370411,27 +370654,27 @@ class ConverterRegistry {
|
|
|
370411
370654
|
}
|
|
370412
370655
|
// ../../packages/plugin-converters/src/claude-code/detector.ts
|
|
370413
370656
|
import { access as access6 } from "node:fs/promises";
|
|
370414
|
-
import { join as
|
|
370657
|
+
import { join as join44 } from "node:path";
|
|
370415
370658
|
var exists4 = async (path) => access6(path).then(() => true, () => false);
|
|
370416
370659
|
async function detectClaudeCodePlugin(root) {
|
|
370417
370660
|
const evidence = [];
|
|
370418
|
-
if (await exists4(
|
|
370661
|
+
if (await exists4(join44(root, ".claude-plugin", "plugin.json")))
|
|
370419
370662
|
evidence.push(".claude-plugin/plugin.json");
|
|
370420
370663
|
for (const name of ["skills", "commands", "agents", "hooks", ".mcp.json", "SKILL.md"])
|
|
370421
|
-
if (await exists4(
|
|
370664
|
+
if (await exists4(join44(root, name)))
|
|
370422
370665
|
evidence.push(name);
|
|
370423
|
-
if (!evidence.length || evidence.length === 1 && evidence[0] === "SKILL.md" && !await exists4(
|
|
370666
|
+
if (!evidence.length || evidence.length === 1 && evidence[0] === "SKILL.md" && !await exists4(join44(root, "SKILL.md")))
|
|
370424
370667
|
return null;
|
|
370425
370668
|
return { converterId: "claude-code", confidence: evidence.includes(".claude-plugin/plugin.json") ? 1 : 0.7, evidence };
|
|
370426
370669
|
}
|
|
370427
370670
|
// ../../packages/plugin-converters/src/claude-code/parser.ts
|
|
370428
|
-
import { access as access7, lstat as
|
|
370429
|
-
import { basename as
|
|
370671
|
+
import { access as access7, lstat as lstat7, readFile as readFile19, readdir as readdir10 } from "node:fs/promises";
|
|
370672
|
+
import { basename as basename13, isAbsolute as isAbsolute13, join as join45, relative as relative11, resolve as resolve27 } from "node:path";
|
|
370430
370673
|
var exists5 = async (path) => access7(path).then(() => true, () => false);
|
|
370431
370674
|
async function collect(root, path, extensions) {
|
|
370432
370675
|
if (!await exists5(path))
|
|
370433
370676
|
return [];
|
|
370434
|
-
const info = await
|
|
370677
|
+
const info = await lstat7(path);
|
|
370435
370678
|
if (info.isSymbolicLink())
|
|
370436
370679
|
throw new Error(`Claude source symlink is not allowed: ${path}`);
|
|
370437
370680
|
if (info.isFile())
|
|
@@ -370440,7 +370683,7 @@ async function collect(root, path, extensions) {
|
|
|
370440
370683
|
for (const entry of (await readdir10(path, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
370441
370684
|
if (entry.isSymbolicLink())
|
|
370442
370685
|
throw new Error(`Claude source symlink is not allowed: ${entry.name}`);
|
|
370443
|
-
const full =
|
|
370686
|
+
const full = join45(path, entry.name);
|
|
370444
370687
|
if (entry.isDirectory())
|
|
370445
370688
|
result.push(...await collect(root, full, extensions));
|
|
370446
370689
|
else if (!extensions || extensions.some((x) => entry.name.toLowerCase().endsWith(x)))
|
|
@@ -370450,31 +370693,31 @@ async function collect(root, path, extensions) {
|
|
|
370450
370693
|
}
|
|
370451
370694
|
var asPaths = (value) => typeof value === "string" ? [value] : Array.isArray(value) && value.every((x) => typeof x === "string") ? value : [];
|
|
370452
370695
|
function safe(root, value) {
|
|
370453
|
-
const target =
|
|
370454
|
-
const rel =
|
|
370455
|
-
if (rel.startsWith("..") ||
|
|
370696
|
+
const target = resolve27(root, value);
|
|
370697
|
+
const rel = relative11(resolve27(root), target);
|
|
370698
|
+
if (rel.startsWith("..") || isAbsolute13(rel) || target === resolve27(root))
|
|
370456
370699
|
throw new Error(`Claude component path escapes source: ${value}`);
|
|
370457
370700
|
return target;
|
|
370458
370701
|
}
|
|
370459
370702
|
async function inspectClaudeCodePlugin(rootInput) {
|
|
370460
|
-
const root =
|
|
370703
|
+
const root = resolve27(rootInput);
|
|
370461
370704
|
let manifest = {};
|
|
370462
|
-
const manifestPath =
|
|
370705
|
+
const manifestPath = join45(root, ".claude-plugin", "plugin.json");
|
|
370463
370706
|
if (await exists5(manifestPath))
|
|
370464
|
-
manifest = JSON.parse(await
|
|
370465
|
-
const name = typeof manifest.name === "string" ? manifest.name :
|
|
370707
|
+
manifest = JSON.parse(await readFile19(manifestPath, "utf8"));
|
|
370708
|
+
const name = typeof manifest.name === "string" ? manifest.name : basename13(root).toLowerCase().replace(/[^a-z0-9-]+/g, "-");
|
|
370466
370709
|
const inventory = {};
|
|
370467
|
-
const skillRoots = [
|
|
370468
|
-
if (await exists5(
|
|
370469
|
-
skillRoots.push(
|
|
370710
|
+
const skillRoots = [join45(root, "skills"), ...asPaths(manifest.skills).map((x) => safe(root, x))];
|
|
370711
|
+
if (await exists5(join45(root, "SKILL.md")))
|
|
370712
|
+
skillRoots.push(join45(root, "SKILL.md"));
|
|
370470
370713
|
inventory.skills = (await Promise.all(skillRoots.map((x) => collect(root, x, ["skill.md"])))).flat();
|
|
370471
370714
|
for (const kind of ["commands", "agents"]) {
|
|
370472
370715
|
const custom = asPaths(manifest[kind]);
|
|
370473
|
-
const roots = custom.length ? custom.map((x) => safe(root, x)) : [
|
|
370716
|
+
const roots = custom.length ? custom.map((x) => safe(root, x)) : [join45(root, kind)];
|
|
370474
370717
|
inventory[kind] = (await Promise.all(roots.map((x) => collect(root, x, [".md"])))).flat();
|
|
370475
370718
|
}
|
|
370476
|
-
const hooks =
|
|
370477
|
-
const mcp =
|
|
370719
|
+
const hooks = join45(root, "hooks", "hooks.json");
|
|
370720
|
+
const mcp = join45(root, ".mcp.json");
|
|
370478
370721
|
inventory.hooks = await exists5(hooks) ? [hooks] : [];
|
|
370479
370722
|
inventory.mcpServers = await exists5(mcp) ? [mcp] : [];
|
|
370480
370723
|
return { root, format: "claude-code", identity: { id: `converted.claude.${name}`, name, version: typeof manifest.version === "string" ? manifest.version : "0.0.0" }, inventory, diagnostics: [] };
|
|
@@ -370584,34 +370827,34 @@ function convertClaudeAgentMarkdown(content) {
|
|
|
370584
370827
|
// ../../packages/plugin-converters/src/claude-code/converter.ts
|
|
370585
370828
|
init_src12();
|
|
370586
370829
|
import { randomUUID as randomUUID27 } from "node:crypto";
|
|
370587
|
-
import { access as access8, cp as cp3, mkdir as mkdir14, readFile as
|
|
370588
|
-
import { basename as
|
|
370589
|
-
var rel = (inspection, path) =>
|
|
370830
|
+
import { access as access8, cp as cp3, mkdir as mkdir14, readFile as readFile20, rename as rename4, rm as rm7, writeFile as writeFile11 } from "node:fs/promises";
|
|
370831
|
+
import { basename as basename14, dirname as dirname21, join as join46, relative as relative12, resolve as resolve28 } from "node:path";
|
|
370832
|
+
var rel = (inspection, path) => relative12(inspection.root, path).replaceAll("\\", "/");
|
|
370590
370833
|
function componentPath(inspection, sourcePath, directory) {
|
|
370591
370834
|
const parts = rel(inspection, sourcePath).split("/");
|
|
370592
370835
|
const directoryIndex = parts.indexOf(directory);
|
|
370593
370836
|
return (directoryIndex >= 0 ? parts.slice(directoryIndex + 1) : parts).join("/");
|
|
370594
370837
|
}
|
|
370595
370838
|
async function copySkillBundle(inspection, skillFile, targetRoot) {
|
|
370596
|
-
const sourceDirectory =
|
|
370597
|
-
const relativeDirectory =
|
|
370598
|
-
const targetRelative = relativeDirectory === "" ? inspection.identity.name :
|
|
370599
|
-
const targetDirectory =
|
|
370839
|
+
const sourceDirectory = dirname21(skillFile);
|
|
370840
|
+
const relativeDirectory = relative12(inspection.root, sourceDirectory);
|
|
370841
|
+
const targetRelative = relativeDirectory === "" ? inspection.identity.name : dirname21(componentPath(inspection, skillFile, "skills"));
|
|
370842
|
+
const targetDirectory = join46(targetRoot, targetRelative === "." ? inspection.identity.name : targetRelative);
|
|
370600
370843
|
if (sourceDirectory !== inspection.root) {
|
|
370601
370844
|
await cp3(sourceDirectory, targetDirectory, { recursive: true, errorOnExist: true, force: false });
|
|
370602
370845
|
return;
|
|
370603
370846
|
}
|
|
370604
370847
|
await mkdir14(targetDirectory, { recursive: true });
|
|
370605
|
-
await cp3(skillFile,
|
|
370848
|
+
await cp3(skillFile, join46(targetDirectory, "SKILL.md"), { errorOnExist: true, force: false });
|
|
370606
370849
|
for (const resourceDirectory of ["assets", "references", "scripts"]) {
|
|
370607
|
-
const sourceResource =
|
|
370850
|
+
const sourceResource = join46(sourceDirectory, resourceDirectory);
|
|
370608
370851
|
if (await access8(sourceResource).then(() => true, () => false)) {
|
|
370609
|
-
await cp3(sourceResource,
|
|
370852
|
+
await cp3(sourceResource, join46(targetDirectory, resourceDirectory), { recursive: true, errorOnExist: true, force: false });
|
|
370610
370853
|
}
|
|
370611
370854
|
}
|
|
370612
370855
|
}
|
|
370613
370856
|
async function convertHooks(source, target) {
|
|
370614
|
-
const raw = JSON.parse(await
|
|
370857
|
+
const raw = JSON.parse(await readFile20(source, "utf8"));
|
|
370615
370858
|
const rows = raw.hooks && typeof raw.hooks === "object" ? raw.hooks : raw;
|
|
370616
370859
|
const output = {};
|
|
370617
370860
|
for (const [sourceEvent, entries] of Object.entries(rows)) {
|
|
@@ -370628,12 +370871,12 @@ async function convertHooks(source, target) {
|
|
|
370628
370871
|
}
|
|
370629
370872
|
if (!Object.keys(output).length)
|
|
370630
370873
|
return false;
|
|
370631
|
-
await mkdir14(
|
|
370874
|
+
await mkdir14(dirname21(target), { recursive: true });
|
|
370632
370875
|
await writeFile11(target, JSON.stringify(output, null, 2));
|
|
370633
370876
|
return true;
|
|
370634
370877
|
}
|
|
370635
370878
|
async function convertMcp(source, target) {
|
|
370636
|
-
const raw = JSON.parse(await
|
|
370879
|
+
const raw = JSON.parse(await readFile20(source, "utf8"));
|
|
370637
370880
|
const servers = {};
|
|
370638
370881
|
for (const [name, value] of Object.entries(raw.mcpServers ?? {})) {
|
|
370639
370882
|
if (typeof value.command === "string")
|
|
@@ -370643,7 +370886,7 @@ async function convertMcp(source, target) {
|
|
|
370643
370886
|
else
|
|
370644
370887
|
throw new Error(`Ambiguous MCP server: ${name}`);
|
|
370645
370888
|
}
|
|
370646
|
-
await mkdir14(
|
|
370889
|
+
await mkdir14(dirname21(target), { recursive: true });
|
|
370647
370890
|
await writeFile11(target, JSON.stringify({ servers }, null, 2));
|
|
370648
370891
|
}
|
|
370649
370892
|
|
|
@@ -370668,7 +370911,7 @@ class ClaudeCodePluginConverter {
|
|
|
370668
370911
|
});
|
|
370669
370912
|
}
|
|
370670
370913
|
for (const hookFile of inspection.inventory.hooks ?? []) {
|
|
370671
|
-
const raw = JSON.parse(await
|
|
370914
|
+
const raw = JSON.parse(await readFile20(hookFile, "utf8"));
|
|
370672
370915
|
const rows = raw.hooks && typeof raw.hooks === "object" ? raw.hooks : raw;
|
|
370673
370916
|
if (Object.keys(rows).some((event) => CLAUDE_HOOK_EVENT_MAP[event])) {
|
|
370674
370917
|
items.push({
|
|
@@ -370707,7 +370950,7 @@ class ClaudeCodePluginConverter {
|
|
|
370707
370950
|
throw new Error(`Blocked conversion items require approval: ${blocked.map((x) => x.id).join(", ")}`);
|
|
370708
370951
|
if (await digestSource(input.inspection.root) !== input.plan.sourceDigest)
|
|
370709
370952
|
throw new Error("Source changed after conversion planning; inspect and plan again");
|
|
370710
|
-
const output =
|
|
370953
|
+
const output = resolve28(input.output);
|
|
370711
370954
|
if (await access8(output).then(() => true, () => false))
|
|
370712
370955
|
throw new Error(`Conversion output already exists: ${output}`);
|
|
370713
370956
|
const temporary = `${output}.tmp-${randomUUID27()}`;
|
|
@@ -370717,31 +370960,31 @@ class ClaudeCodePluginConverter {
|
|
|
370717
370960
|
const components = {};
|
|
370718
370961
|
const skills = input.inspection.inventory.skills ?? [];
|
|
370719
370962
|
for (const skill of skills) {
|
|
370720
|
-
await copySkillBundle(input.inspection, skill,
|
|
370963
|
+
await copySkillBundle(input.inspection, skill, join46(temporary, "skills"));
|
|
370721
370964
|
}
|
|
370722
370965
|
const commands = input.inspection.inventory.commands ?? [];
|
|
370723
370966
|
for (const command of commands) {
|
|
370724
|
-
const name =
|
|
370725
|
-
const target =
|
|
370726
|
-
await mkdir14(
|
|
370727
|
-
await writeFile11(target, await
|
|
370967
|
+
const name = basename14(command, ".md");
|
|
370968
|
+
const target = join46(temporary, "skills", name, "SKILL.md");
|
|
370969
|
+
await mkdir14(dirname21(target), { recursive: true });
|
|
370970
|
+
await writeFile11(target, await readFile20(command, "utf8"));
|
|
370728
370971
|
}
|
|
370729
370972
|
if (skills.length || commands.length)
|
|
370730
370973
|
components.skills = ["./skills"];
|
|
370731
370974
|
const agents = input.inspection.inventory.agents ?? [];
|
|
370732
370975
|
for (const agent of agents) {
|
|
370733
|
-
const target =
|
|
370734
|
-
await mkdir14(
|
|
370735
|
-
await writeFile11(target, convertClaudeAgentMarkdown(await
|
|
370976
|
+
const target = join46(temporary, "agents", componentPath(input.inspection, agent, "agents"));
|
|
370977
|
+
await mkdir14(dirname21(target), { recursive: true });
|
|
370978
|
+
await writeFile11(target, convertClaudeAgentMarkdown(await readFile20(agent, "utf8")));
|
|
370736
370979
|
}
|
|
370737
370980
|
if (agents.length)
|
|
370738
370981
|
components.agents = ["./agents"];
|
|
370739
370982
|
const hooks = input.inspection.inventory.hooks?.[0];
|
|
370740
|
-
if (hooks && await convertHooks(hooks,
|
|
370983
|
+
if (hooks && await convertHooks(hooks, join46(temporary, "hooks.json")))
|
|
370741
370984
|
components.hooks = ["./hooks.json"];
|
|
370742
370985
|
const mcp = input.inspection.inventory.mcpServers?.[0];
|
|
370743
370986
|
if (mcp) {
|
|
370744
|
-
await convertMcp(mcp,
|
|
370987
|
+
await convertMcp(mcp, join46(temporary, "mcp.json"));
|
|
370745
370988
|
components.mcpServers = ["./mcp.json"];
|
|
370746
370989
|
}
|
|
370747
370990
|
if (!Object.keys(components).length)
|
|
@@ -370754,13 +370997,13 @@ class ClaudeCodePluginConverter {
|
|
|
370754
370997
|
metadata: { origin: "converted", sourceFormat: this.sourceFormat, converterId: this.id, converterVersion: this.version },
|
|
370755
370998
|
components
|
|
370756
370999
|
};
|
|
370757
|
-
await mkdir14(
|
|
370758
|
-
await writeFile11(
|
|
371000
|
+
await mkdir14(join46(temporary, ".openharness-plugin"));
|
|
371001
|
+
await writeFile11(join46(temporary, ".openharness-plugin", "plugin.json"), JSON.stringify(manifest, null, 2));
|
|
370759
371002
|
const report = { schemaVersion: 1, status: input.plan.items.some((x) => x.fidelity === "unsupported") ? "partial" : "success", items: input.plan.items, diagnostics: input.plan.diagnostics };
|
|
370760
|
-
await mkdir14(
|
|
370761
|
-
await writeFile11(
|
|
370762
|
-
await writeFile11(
|
|
370763
|
-
await writeFile11(
|
|
371003
|
+
await mkdir14(join46(temporary, ".openharness-conversion"));
|
|
371004
|
+
await writeFile11(join46(temporary, ".openharness-conversion", "plan.json"), JSON.stringify(input.plan, null, 2));
|
|
371005
|
+
await writeFile11(join46(temporary, ".openharness-conversion", "report.json"), JSON.stringify(report, null, 2));
|
|
371006
|
+
await writeFile11(join46(temporary, ".openharness-conversion", "provenance.json"), JSON.stringify({ sourceFormat: this.sourceFormat, sourceDigest: input.plan.sourceDigest, converterId: this.id, converterVersion: this.version, mappingVersion: CLAUDE_MAPPING_VERSION }, null, 2));
|
|
370764
371007
|
const validation = await validateNativePlugin(temporary);
|
|
370765
371008
|
if (validation.status !== "valid")
|
|
370766
371009
|
throw new Error(validation.diagnostics.map((x) => x.message).join("; "));
|
|
@@ -370772,10 +371015,428 @@ class ClaudeCodePluginConverter {
|
|
|
370772
371015
|
}
|
|
370773
371016
|
}
|
|
370774
371017
|
}
|
|
371018
|
+
// ../../packages/plugin-converters/src/codex/converter.ts
|
|
371019
|
+
init_src12();
|
|
371020
|
+
import { randomUUID as randomUUID28 } from "node:crypto";
|
|
371021
|
+
import { copyFile as copyFile2, lstat as lstat9, mkdir as mkdir15, readFile as readFile23, realpath as realpath8, rename as rename5, rm as rm8, writeFile as writeFile12 } from "node:fs/promises";
|
|
371022
|
+
import { dirname as dirname24, join as join48, relative as relative14, resolve as resolve30 } from "node:path";
|
|
371023
|
+
|
|
371024
|
+
// ../../packages/plugin-converters/src/codex/source.ts
|
|
371025
|
+
import { lstat as lstat8, readFile as readFile21, readdir as readdir11 } from "node:fs/promises";
|
|
371026
|
+
import { dirname as dirname23, isAbsolute as isAbsolute14, join as join47, relative as relative13, resolve as resolve29 } from "node:path";
|
|
371027
|
+
var PORTABLE_PLUGIN_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json";
|
|
371028
|
+
var legacyManifest = ".codex-plugin/plugin.json";
|
|
371029
|
+
function object3(value, label) {
|
|
371030
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
371031
|
+
throw new Error(`${label} must be an object`);
|
|
371032
|
+
return value;
|
|
371033
|
+
}
|
|
371034
|
+
async function exists6(path) {
|
|
371035
|
+
try {
|
|
371036
|
+
await lstat8(path);
|
|
371037
|
+
return true;
|
|
371038
|
+
} catch (error) {
|
|
371039
|
+
if (error.code === "ENOENT")
|
|
371040
|
+
return false;
|
|
371041
|
+
throw error;
|
|
371042
|
+
}
|
|
371043
|
+
}
|
|
371044
|
+
async function sourceFiles(root, directory = root) {
|
|
371045
|
+
const info = await lstat8(directory);
|
|
371046
|
+
if (info.isSymbolicLink())
|
|
371047
|
+
throw new Error(`Codex source link is not allowed: ${directory}`);
|
|
371048
|
+
if (!info.isDirectory())
|
|
371049
|
+
throw new Error(`Codex source must be a directory: ${directory}`);
|
|
371050
|
+
const files = [];
|
|
371051
|
+
for (const entry of (await readdir11(directory, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
371052
|
+
const full = join47(directory, entry.name);
|
|
371053
|
+
if (entry.isSymbolicLink())
|
|
371054
|
+
throw new Error(`Codex source link is not allowed: ${full}`);
|
|
371055
|
+
if (entry.isDirectory())
|
|
371056
|
+
files.push(...await sourceFiles(root, full));
|
|
371057
|
+
else if (entry.isFile())
|
|
371058
|
+
files.push(relative13(root, full).replaceAll("\\", "/"));
|
|
371059
|
+
else
|
|
371060
|
+
throw new Error(`Unsupported Codex source file type: ${full}`);
|
|
371061
|
+
}
|
|
371062
|
+
return files;
|
|
371063
|
+
}
|
|
371064
|
+
function isWithin3(root, path) {
|
|
371065
|
+
const rel = relative13(resolve29(root), resolve29(path));
|
|
371066
|
+
return rel === "" || !isAbsolute14(rel) && rel !== ".." && !rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
|
|
371067
|
+
}
|
|
371068
|
+
function pathDeclaration(root, value) {
|
|
371069
|
+
if (!value.startsWith("./") || /[\\:\0]/.test(value) || value.split("/").includes("..")) {
|
|
371070
|
+
throw new Error(`Invalid Codex component path: ${value}`);
|
|
371071
|
+
}
|
|
371072
|
+
const full = resolve29(root, value);
|
|
371073
|
+
if (full === root || !isWithin3(root, full))
|
|
371074
|
+
throw new Error(`Codex component path escapes source: ${value}`);
|
|
371075
|
+
const path = relative13(root, full).replaceAll("\\", "/");
|
|
371076
|
+
if (path.split("/").some((part) => [".git", ".codex-plugin", ".claude-plugin", ".openharness-plugin", ".openharness-conversion"].includes(part))) {
|
|
371077
|
+
throw new Error(`Reserved Codex component path: ${value}`);
|
|
371078
|
+
}
|
|
371079
|
+
return path;
|
|
371080
|
+
}
|
|
371081
|
+
async function declaredPaths(root, value, label, defaults) {
|
|
371082
|
+
if (value === undefined)
|
|
371083
|
+
return (await Promise.all(defaults.map(async (path) => await exists6(join47(root, path)) ? path : undefined))).filter((path) => path !== undefined);
|
|
371084
|
+
const values = typeof value === "string" ? [value] : value;
|
|
371085
|
+
if (!Array.isArray(values) || !values.every((path) => typeof path === "string"))
|
|
371086
|
+
throw new Error(`${label} must be a path or path array`);
|
|
371087
|
+
const paths = values.map((path) => pathDeclaration(root, path));
|
|
371088
|
+
for (const path of paths)
|
|
371089
|
+
if (!await exists6(join47(root, path)))
|
|
371090
|
+
throw new Error(`Codex component path does not exist: ${path}`);
|
|
371091
|
+
return [...new Set(paths)];
|
|
371092
|
+
}
|
|
371093
|
+
async function readJson2(root, path) {
|
|
371094
|
+
return object3(JSON.parse(await readFile21(join47(root, path), "utf8")), path);
|
|
371095
|
+
}
|
|
371096
|
+
async function detectCodexPlugin(rootInput) {
|
|
371097
|
+
const root = resolve29(rootInput);
|
|
371098
|
+
if (await exists6(join47(root, legacyManifest)))
|
|
371099
|
+
return { converterId: "codex", confidence: 1, evidence: [legacyManifest] };
|
|
371100
|
+
if (!await exists6(join47(root, "plugin.json")))
|
|
371101
|
+
return null;
|
|
371102
|
+
await sourceFiles(root);
|
|
371103
|
+
const manifest = await readJson2(root, "plugin.json");
|
|
371104
|
+
return manifest.$schema === PORTABLE_PLUGIN_SCHEMA ? { converterId: "codex", confidence: 1, evidence: [`plugin.json:${PORTABLE_PLUGIN_SCHEMA}`] } : null;
|
|
371105
|
+
}
|
|
371106
|
+
async function readCodexSource(rootInput) {
|
|
371107
|
+
const root = resolve29(rootInput);
|
|
371108
|
+
const files = await sourceFiles(root);
|
|
371109
|
+
const portableCandidate = files.includes("plugin.json") ? await readJson2(root, "plugin.json") : undefined;
|
|
371110
|
+
if (typeof portableCandidate?.$schema === "string" && portableCandidate.$schema.startsWith("https://agent-plugins.org/") && portableCandidate.$schema !== PORTABLE_PLUGIN_SCHEMA) {
|
|
371111
|
+
throw new Error(`Unsupported portable plugin schema: ${portableCandidate.$schema}`);
|
|
371112
|
+
}
|
|
371113
|
+
const portable = portableCandidate?.$schema === PORTABLE_PLUGIN_SCHEMA;
|
|
371114
|
+
const extensions = portable && portableCandidate.extensions !== undefined ? object3(portableCandidate.extensions, "extensions") : {};
|
|
371115
|
+
const inline = extensions["com.openai"];
|
|
371116
|
+
const legacy = (!portable || inline === undefined) && files.includes(legacyManifest) ? await readJson2(root, legacyManifest) : undefined;
|
|
371117
|
+
const manifest = portable ? portableCandidate : legacy;
|
|
371118
|
+
if (!manifest)
|
|
371119
|
+
throw new Error("Codex manifest is missing or the portable schema is not supported");
|
|
371120
|
+
if (typeof manifest.name !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(manifest.name))
|
|
371121
|
+
throw new Error("Codex name must use kebab-case");
|
|
371122
|
+
if (manifest.version !== undefined && (typeof manifest.version !== "string" || !manifest.version.trim()))
|
|
371123
|
+
throw new Error("Codex version must be a nonempty string");
|
|
371124
|
+
const settings = portable ? extensions["com.openai"] === undefined ? legacy ?? {} : object3(extensions["com.openai"], "extensions.com.openai") : manifest;
|
|
371125
|
+
const inventory = {};
|
|
371126
|
+
const declaredSkillRoots = await declaredPaths(root, portable ? undefined : settings.skills, "skills", ["skills"]);
|
|
371127
|
+
const skillRoots = declaredSkillRoots.filter((path) => !declaredSkillRoots.some((parent) => parent !== path && path.startsWith(`${parent}/`)));
|
|
371128
|
+
inventory.skills = [];
|
|
371129
|
+
for (const path of skillRoots) {
|
|
371130
|
+
const skills = files.filter((file) => (file === path || file.startsWith(`${path}/`)) && file.split("/").at(-1) === "SKILL.md");
|
|
371131
|
+
if (!skills.length)
|
|
371132
|
+
throw new Error(`Codex skill path contains no SKILL.md: ${path}`);
|
|
371133
|
+
inventory.skills.push(...skills.map((file) => join47(root, file)));
|
|
371134
|
+
}
|
|
371135
|
+
inventory.skills = [...new Set(inventory.skills)];
|
|
371136
|
+
const mcp = [];
|
|
371137
|
+
const mcpDeclaration = portable ? undefined : settings.mcpServers;
|
|
371138
|
+
if (mcpDeclaration && typeof mcpDeclaration === "object" && !Array.isArray(mcpDeclaration)) {
|
|
371139
|
+
const raw = object3(mcpDeclaration, "mcpServers");
|
|
371140
|
+
mcp.push({ path: `${legacyManifest}#mcpServers`, servers: object3(raw.mcpServers ?? raw, "mcpServers"), portable: false });
|
|
371141
|
+
} else {
|
|
371142
|
+
for (const path of await declaredPaths(root, mcpDeclaration, "mcpServers", [portable ? "mcp.json" : ".mcp.json"])) {
|
|
371143
|
+
const raw = await readJson2(root, path);
|
|
371144
|
+
mcp.push({ path, servers: object3(raw.mcpServers, `${path}.mcpServers`), portable });
|
|
371145
|
+
}
|
|
371146
|
+
}
|
|
371147
|
+
inventory.mcpServers = mcp.map((value) => value.path);
|
|
371148
|
+
const unsupported = [];
|
|
371149
|
+
if (portable) {
|
|
371150
|
+
const portableFields = new Set(["$schema", "name", "version", "description", "author", "homepage", "repository", "license", "keywords", "extensions"]);
|
|
371151
|
+
for (const key of Object.keys(manifest).filter((key) => !portableFields.has(key))) {
|
|
371152
|
+
unsupported.push({ kind: "manifest", path: `plugin.json#${key}`, reason: `Unrecognized portable manifest field: ${key}` });
|
|
371153
|
+
}
|
|
371154
|
+
for (const [key, extension] of Object.entries(extensions)) {
|
|
371155
|
+
if (key === "com.openai")
|
|
371156
|
+
continue;
|
|
371157
|
+
object3(extension, `extensions.${key}`);
|
|
371158
|
+
unsupported.push({ kind: "extensions", path: `plugin.json#extensions.${key}`, reason: `Portable extension ${key} has no Native conversion` });
|
|
371159
|
+
}
|
|
371160
|
+
}
|
|
371161
|
+
for (const skill of inventory.skills) {
|
|
371162
|
+
const path = relative13(root, join47(dirname23(skill), "agents/openai.yaml")).replaceAll("\\", "/");
|
|
371163
|
+
if (files.includes(path))
|
|
371164
|
+
unsupported.push({ kind: "skillMetadata", path, reason: "Codex skill invocation policy, tool dependencies and presentation in agents/openai.yaml are not applied by Native" });
|
|
371165
|
+
}
|
|
371166
|
+
for (const kind of ["apps", "hooks", "agents", "commands", "lspServers", "tools", "workflows", "channels", "providers", "ui", "outputStyles", "themes", "monitors", "binaries"]) {
|
|
371167
|
+
const declaration = settings[kind];
|
|
371168
|
+
const defaults = kind === "hooks" ? ["hooks/hooks.json"] : [];
|
|
371169
|
+
let paths;
|
|
371170
|
+
if (declaration !== null && typeof declaration === "object" && (!Array.isArray(declaration) || declaration.some((item) => typeof item !== "string"))) {
|
|
371171
|
+
paths = [`${portable ? "plugin.json" : legacyManifest}#${kind}`];
|
|
371172
|
+
} else
|
|
371173
|
+
paths = await declaredPaths(root, declaration, kind, defaults);
|
|
371174
|
+
for (const path of paths)
|
|
371175
|
+
unsupported.push({ kind, path, reason: `Codex ${kind} has no supported Native conversion in this version` });
|
|
371176
|
+
if (paths.length)
|
|
371177
|
+
inventory[kind] = paths;
|
|
371178
|
+
}
|
|
371179
|
+
const known = new Set(["$schema", "name", "version", "description", "author", "homepage", "repository", "license", "keywords", "skills", "mcpServers", "interface", "extensions", ...unsupported.map((item) => item.kind)]);
|
|
371180
|
+
for (const key of Object.keys(settings).filter((key) => !known.has(key))) {
|
|
371181
|
+
unsupported.push({ kind: key, path: `${portable ? "plugin.json" : legacyManifest}#${key}`, reason: `Unrecognized Codex manifest field: ${key}` });
|
|
371182
|
+
}
|
|
371183
|
+
const ui = settings.interface === undefined ? {} : object3(settings.interface, "interface");
|
|
371184
|
+
return {
|
|
371185
|
+
inspection: { root, format: "codex", identity: { id: `converted.codex.${manifest.name}`, name: manifest.name, version: manifest.version ?? "0.0.0" }, inventory, diagnostics: [] },
|
|
371186
|
+
files,
|
|
371187
|
+
metadataPaths: [...portable ? ["plugin.json"] : [], ...mcp.map((item) => item.path), ...unsupported.map((item) => item.path)],
|
|
371188
|
+
skillRoots,
|
|
371189
|
+
mcp,
|
|
371190
|
+
unsupported,
|
|
371191
|
+
...typeof manifest.description === "string" ? { description: manifest.description } : {},
|
|
371192
|
+
...typeof ui.displayName === "string" && ui.displayName.trim() ? { displayName: ui.displayName } : {}
|
|
371193
|
+
};
|
|
371194
|
+
}
|
|
371195
|
+
|
|
371196
|
+
// ../../packages/plugin-converters/src/codex/mcp.ts
|
|
371197
|
+
function stringMap(value) {
|
|
371198
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((item) => typeof item === "string");
|
|
371199
|
+
}
|
|
371200
|
+
function mapCodexMcp(value, portable) {
|
|
371201
|
+
let row;
|
|
371202
|
+
try {
|
|
371203
|
+
row = object3(value, "MCP server");
|
|
371204
|
+
} catch {
|
|
371205
|
+
return { reason: "MCP server must be an object" };
|
|
371206
|
+
}
|
|
371207
|
+
const unknown = Object.keys(row).filter((key) => !["type", "command", "args", "env", "url", "headers"].includes(key));
|
|
371208
|
+
if (unknown.length)
|
|
371209
|
+
return { reason: `MCP fields have no verified Native equivalent: ${unknown.sort().join(", ")}` };
|
|
371210
|
+
if (/\$\{|\$[A-Z_][A-Z0-9_]*|%[A-Z_][A-Z0-9_]*%/.test(JSON.stringify(row)))
|
|
371211
|
+
return { reason: "MCP environment or plugin-root interpolation is not supported" };
|
|
371212
|
+
if (row.command !== undefined && row.url !== undefined)
|
|
371213
|
+
return { reason: "MCP command and url cannot be combined" };
|
|
371214
|
+
if (typeof row.url === "string") {
|
|
371215
|
+
if (row.type !== undefined && row.type !== "http" && row.type !== "streamable-http" && row.type !== "sse")
|
|
371216
|
+
return { reason: "Unsupported MCP transport" };
|
|
371217
|
+
if (portable && row.type !== "streamable-http")
|
|
371218
|
+
return { reason: "Portable HTTP MCP requires streamable-http transport" };
|
|
371219
|
+
if (row.args !== undefined || row.env !== undefined || row.headers !== undefined && !stringMap(row.headers))
|
|
371220
|
+
return { reason: "Unsupported HTTP MCP options" };
|
|
371221
|
+
let url;
|
|
371222
|
+
try {
|
|
371223
|
+
url = new URL(row.url);
|
|
371224
|
+
} catch {
|
|
371225
|
+
return { reason: "Invalid MCP URL" };
|
|
371226
|
+
}
|
|
371227
|
+
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.hash)
|
|
371228
|
+
return { reason: "MCP requires an HTTP(S) URL without credentials or a fragment" };
|
|
371229
|
+
return { config: { type: row.type === "sse" ? "sse" : "http", url: row.url, ...row.headers === undefined ? {} : { headers: row.headers } }, permissions: { network: [url.origin] }, reason: "MCP transport is adapted to Native; connection and authentication happen after installation" };
|
|
371230
|
+
}
|
|
371231
|
+
if (typeof row.command === "string" && /^[a-zA-Z0-9_-]+(?:\.exe)?$/.test(row.command)) {
|
|
371232
|
+
if (row.type !== undefined && row.type !== "stdio" || portable && row.type !== "stdio" || row.headers !== undefined)
|
|
371233
|
+
return { reason: "Unsupported stdio MCP options" };
|
|
371234
|
+
if (row.args !== undefined && (!Array.isArray(row.args) || !row.args.every((item) => typeof item === "string")))
|
|
371235
|
+
return { reason: "MCP args must be a string array" };
|
|
371236
|
+
if (row.env !== undefined && !stringMap(row.env))
|
|
371237
|
+
return { reason: "MCP env must contain strings" };
|
|
371238
|
+
const args = row.args ?? [];
|
|
371239
|
+
if (["npx", "npm", "pnpm", "yarn", "bunx", "uvx", "sh", "bash", "cmd", "powershell", "pwsh"].includes(row.command.toLowerCase().replace(/\.exe$/, "")) || args.some((arg) => /[\\/]|\.(?:[cm]?js|ts|py|sh|exe)$/.test(arg) || ["-e", "--eval", "-c", "--command"].includes(arg)))
|
|
371240
|
+
return { reason: "MCP local paths, shell code or dependency execution require explicit preparation not supported by this converter" };
|
|
371241
|
+
return { config: { type: "stdio", command: row.command, ...row.args === undefined ? {} : { args }, ...row.env === undefined ? {} : { env: row.env } }, permissions: { process: [row.command] }, reason: "MCP runs an already-installed executable from PATH; availability is checked when connecting" };
|
|
371242
|
+
}
|
|
371243
|
+
return { reason: "MCP requires a supported remote URL or an installed executable name" };
|
|
371244
|
+
}
|
|
371245
|
+
|
|
371246
|
+
// ../../packages/plugin-converters/src/codex/converter.ts
|
|
371247
|
+
var VERSION3 = "1.0.0";
|
|
371248
|
+
var MAPPING_VERSION = "1";
|
|
371249
|
+
async function exists7(path) {
|
|
371250
|
+
try {
|
|
371251
|
+
await lstat9(path);
|
|
371252
|
+
return true;
|
|
371253
|
+
} catch (error) {
|
|
371254
|
+
if (error.code === "ENOENT")
|
|
371255
|
+
return false;
|
|
371256
|
+
throw error;
|
|
371257
|
+
}
|
|
371258
|
+
}
|
|
371259
|
+
async function prepare(inspection) {
|
|
371260
|
+
const before = await digestSource(inspection.root);
|
|
371261
|
+
const source = await readCodexSource(inspection.root);
|
|
371262
|
+
if (digestValue(source.inspection) !== digestValue(inspection))
|
|
371263
|
+
throw new Error("Source inspection changed; inspect and plan again");
|
|
371264
|
+
const items = (inspection.inventory.skills ?? []).map((path) => {
|
|
371265
|
+
const sourcePath = relative14(inspection.root, path).replaceAll("\\", "/");
|
|
371266
|
+
return { id: `skills:${sourcePath}`, sourceKind: "skills", sourcePath, targetKind: "skills", fidelity: "exact" };
|
|
371267
|
+
});
|
|
371268
|
+
const servers = Object.create(null);
|
|
371269
|
+
const seenServers = new Set;
|
|
371270
|
+
const permissions = {};
|
|
371271
|
+
for (const contribution of source.mcp) {
|
|
371272
|
+
for (const [name, raw] of Object.entries(contribution.servers).sort(([a], [b]) => a.localeCompare(b))) {
|
|
371273
|
+
if (!name.trim() || ["__proto__", "prototype", "constructor"].includes(name) || seenServers.has(name))
|
|
371274
|
+
throw new Error(`Invalid or duplicate Codex MCP server name: ${name}`);
|
|
371275
|
+
seenServers.add(name);
|
|
371276
|
+
const mapped = mapCodexMcp(raw, contribution.portable);
|
|
371277
|
+
const id = `mcpServers:${contribution.path}#${name}`;
|
|
371278
|
+
const requiredApprovals = [id];
|
|
371279
|
+
if (mapped.config) {
|
|
371280
|
+
servers[name] = mapped.config;
|
|
371281
|
+
for (const category of ["network", "process"]) {
|
|
371282
|
+
for (const permission of mapped.permissions?.[category] ?? []) {
|
|
371283
|
+
permissions[category] = [...new Set([...permissions[category] ?? [], permission])].sort();
|
|
371284
|
+
requiredApprovals.push(`${category}:${permission}`);
|
|
371285
|
+
}
|
|
371286
|
+
}
|
|
371287
|
+
}
|
|
371288
|
+
items.push({
|
|
371289
|
+
id,
|
|
371290
|
+
sourceKind: "mcpServers",
|
|
371291
|
+
sourcePath: contribution.path,
|
|
371292
|
+
...mapped.config ? { targetKind: "mcpServers" } : {},
|
|
371293
|
+
fidelity: mapped.config ? "adapted" : "unsupported",
|
|
371294
|
+
reason: mapped.reason,
|
|
371295
|
+
requiredApprovals
|
|
371296
|
+
});
|
|
371297
|
+
}
|
|
371298
|
+
}
|
|
371299
|
+
for (const value of source.unsupported) {
|
|
371300
|
+
const id = `${value.kind}:${value.path}`;
|
|
371301
|
+
items.push({ id, sourceKind: value.kind, sourcePath: value.path, fidelity: "unsupported", reason: value.reason, requiredApprovals: [id] });
|
|
371302
|
+
}
|
|
371303
|
+
const sourceDigest = await digestSource(inspection.root);
|
|
371304
|
+
if (before !== sourceDigest)
|
|
371305
|
+
throw new Error("Source changed during conversion planning; inspect and plan again");
|
|
371306
|
+
return {
|
|
371307
|
+
source,
|
|
371308
|
+
servers,
|
|
371309
|
+
permissions,
|
|
371310
|
+
plan: {
|
|
371311
|
+
schemaVersion: 1,
|
|
371312
|
+
converterId: "codex",
|
|
371313
|
+
converterVersion: VERSION3,
|
|
371314
|
+
sourceFormat: "codex",
|
|
371315
|
+
sourceDigest,
|
|
371316
|
+
optionsDigest: digestValue({}),
|
|
371317
|
+
mappingVersion: MAPPING_VERSION,
|
|
371318
|
+
items: items.sort((a, b) => a.id.localeCompare(b.id)),
|
|
371319
|
+
diagnostics: []
|
|
371320
|
+
}
|
|
371321
|
+
};
|
|
371322
|
+
}
|
|
371323
|
+
async function checkOutput(sourceRoot, output) {
|
|
371324
|
+
if (isWithin3(sourceRoot, output))
|
|
371325
|
+
throw new Error("Conversion output cannot be inside the source directory");
|
|
371326
|
+
if (await exists7(output))
|
|
371327
|
+
throw new Error(`Conversion output already exists: ${output}`);
|
|
371328
|
+
let parent = dirname24(output);
|
|
371329
|
+
while (!await exists7(parent))
|
|
371330
|
+
parent = dirname24(parent);
|
|
371331
|
+
const canonicalOutput = resolve30(await realpath8(parent), relative14(parent, output));
|
|
371332
|
+
if (isWithin3(await realpath8(sourceRoot), canonicalOutput))
|
|
371333
|
+
throw new Error("Conversion output resolves inside the source directory");
|
|
371334
|
+
}
|
|
371335
|
+
function isSourceMetadata(path, source) {
|
|
371336
|
+
const lower = path.toLowerCase();
|
|
371337
|
+
const top = lower.split("/")[0];
|
|
371338
|
+
if ([".git", ".codex-plugin", ".claude-plugin", ".openharness-plugin", ".openharness-conversion"].includes(top))
|
|
371339
|
+
return true;
|
|
371340
|
+
return source.metadataPaths.some((item) => item.toLowerCase() === lower);
|
|
371341
|
+
}
|
|
371342
|
+
|
|
371343
|
+
class CodexPluginConverter {
|
|
371344
|
+
id = "codex";
|
|
371345
|
+
version = VERSION3;
|
|
371346
|
+
sourceFormat = "codex";
|
|
371347
|
+
detect = detectCodexPlugin;
|
|
371348
|
+
async inspect(root) {
|
|
371349
|
+
return (await readCodexSource(root)).inspection;
|
|
371350
|
+
}
|
|
371351
|
+
async plan(inspection, options = {}) {
|
|
371352
|
+
if (Object.keys(options).length)
|
|
371353
|
+
throw new Error("Codex conversion options are not supported in this version");
|
|
371354
|
+
return (await prepare(inspection)).plan;
|
|
371355
|
+
}
|
|
371356
|
+
async convert(input) {
|
|
371357
|
+
const output = resolve30(input.output);
|
|
371358
|
+
await checkOutput(input.inspection.root, output);
|
|
371359
|
+
await readCodexSource(input.inspection.root);
|
|
371360
|
+
if (await digestSource(input.inspection.root) !== input.plan.sourceDigest)
|
|
371361
|
+
throw new Error("Source changed after conversion planning; inspect and plan again");
|
|
371362
|
+
const prepared = await prepare(input.inspection);
|
|
371363
|
+
if (digestValue(prepared.plan) !== digestValue(input.plan))
|
|
371364
|
+
throw new Error("Conversion plan does not match the current source and converter; plan again");
|
|
371365
|
+
const missing = [...new Set(prepared.plan.items.flatMap((item) => item.requiredApprovals ?? []))].filter((item) => !input.approvals?.includes(item));
|
|
371366
|
+
if (missing.length)
|
|
371367
|
+
throw new Error(`Explicit conversion approval required: ${missing.join(", ")}`);
|
|
371368
|
+
const components = {};
|
|
371369
|
+
const skillFiles = prepared.plan.items.filter((item) => item.sourceKind === "skills" && item.fidelity === "exact");
|
|
371370
|
+
if (skillFiles.length)
|
|
371371
|
+
components.skills = skillFiles.map((item) => `./${item.sourcePath}`);
|
|
371372
|
+
if (Object.keys(prepared.servers).length)
|
|
371373
|
+
components.mcpServers = ["./mcp/servers.json"];
|
|
371374
|
+
if (!Object.keys(components).length)
|
|
371375
|
+
throw new Error("Codex source contains no convertible components; see unsupported plan items");
|
|
371376
|
+
const manifest = {
|
|
371377
|
+
schemaVersion: 1,
|
|
371378
|
+
...input.inspection.identity,
|
|
371379
|
+
...prepared.source.description === undefined ? {} : { description: prepared.source.description },
|
|
371380
|
+
...prepared.source.displayName === undefined ? {} : { displayName: prepared.source.displayName },
|
|
371381
|
+
components,
|
|
371382
|
+
...Object.keys(prepared.permissions).length ? { permissions: prepared.permissions } : {},
|
|
371383
|
+
metadata: { origin: "converted", sourceFormat: this.sourceFormat, converterId: this.id, converterVersion: this.version }
|
|
371384
|
+
};
|
|
371385
|
+
const report = { schemaVersion: 1, status: input.plan.items.some((item) => item.fidelity === "unsupported") ? "partial" : "success", items: input.plan.items, diagnostics: input.plan.diagnostics };
|
|
371386
|
+
const temporary = `${output}.tmp-${randomUUID28()}`;
|
|
371387
|
+
const copied = [];
|
|
371388
|
+
try {
|
|
371389
|
+
await mkdir15(temporary, { recursive: true });
|
|
371390
|
+
for (const path of prepared.source.files) {
|
|
371391
|
+
if (isSourceMetadata(path, prepared.source))
|
|
371392
|
+
continue;
|
|
371393
|
+
if (components.mcpServers && path.toLowerCase() === "mcp/servers.json")
|
|
371394
|
+
throw new Error("Source resource conflicts with generated Native MCP path");
|
|
371395
|
+
const sourcePath = join48(input.inspection.root, path);
|
|
371396
|
+
if (!(await lstat9(sourcePath)).isFile())
|
|
371397
|
+
throw new Error(`Source file changed during conversion: ${path}`);
|
|
371398
|
+
const target = join48(temporary, path);
|
|
371399
|
+
await mkdir15(dirname24(target), { recursive: true });
|
|
371400
|
+
await copyFile2(sourcePath, target);
|
|
371401
|
+
copied.push(path);
|
|
371402
|
+
}
|
|
371403
|
+
const writeJson = async (path, value) => {
|
|
371404
|
+
const target = join48(temporary, path);
|
|
371405
|
+
await mkdir15(dirname24(target), { recursive: true });
|
|
371406
|
+
await writeFile12(target, JSON.stringify(value, null, 2));
|
|
371407
|
+
};
|
|
371408
|
+
if (components.mcpServers)
|
|
371409
|
+
await writeJson("mcp/servers.json", { servers: prepared.servers });
|
|
371410
|
+
await writeJson(".openharness-plugin/plugin.json", manifest);
|
|
371411
|
+
await writeJson(".openharness-conversion/plan.json", input.plan);
|
|
371412
|
+
await writeJson(".openharness-conversion/report.json", report);
|
|
371413
|
+
await writeJson(".openharness-conversion/provenance.json", { sourceFormat: this.sourceFormat, sourceDigest: input.plan.sourceDigest, converterId: this.id, converterVersion: this.version, mappingVersion: MAPPING_VERSION });
|
|
371414
|
+
await readCodexSource(input.inspection.root);
|
|
371415
|
+
if (await digestSource(input.inspection.root) !== input.plan.sourceDigest)
|
|
371416
|
+
throw new Error("Source changed during conversion; inspect and plan again");
|
|
371417
|
+
for (const path of copied) {
|
|
371418
|
+
if (!(await readFile23(join48(temporary, path))).equals(await readFile23(join48(input.inspection.root, path))))
|
|
371419
|
+
throw new Error(`Source changed while copying: ${path}`);
|
|
371420
|
+
}
|
|
371421
|
+
const validation = await validateNativePlugin(temporary);
|
|
371422
|
+
if (validation.status !== "valid")
|
|
371423
|
+
throw new Error(validation.diagnostics.map((item) => item.message).join("; "));
|
|
371424
|
+
if (digestValue(requestedPluginPermissions(validation.plugin.manifest)) !== digestValue(requestedPluginPermissions(manifest)))
|
|
371425
|
+
throw new Error("Converted permissions changed during validation");
|
|
371426
|
+
await checkOutput(input.inspection.root, output);
|
|
371427
|
+
await rename5(temporary, output);
|
|
371428
|
+
return report;
|
|
371429
|
+
} catch (error) {
|
|
371430
|
+
await rm8(temporary, { recursive: true, force: true });
|
|
371431
|
+
throw error;
|
|
371432
|
+
}
|
|
371433
|
+
}
|
|
371434
|
+
}
|
|
370775
371435
|
// ../../packages/plugin-converters/src/index.ts
|
|
370776
371436
|
function createBuiltinConverterRegistry() {
|
|
370777
371437
|
const registry = new ConverterRegistry;
|
|
370778
371438
|
registry.register(new ClaudeCodePluginConverter);
|
|
371439
|
+
registry.register(new CodexPluginConverter);
|
|
370779
371440
|
return registry;
|
|
370780
371441
|
}
|
|
370781
371442
|
|
|
@@ -370787,6 +371448,15 @@ async function client() {
|
|
|
370787
371448
|
return new OpenHarnessClient({ baseUrl: daemon.url, token: daemon.token });
|
|
370788
371449
|
}
|
|
370789
371450
|
var collect2 = (value, previous) => [...previous, value];
|
|
371451
|
+
function formatConversionPlan(plan) {
|
|
371452
|
+
return plan.items.map((item) => [
|
|
371453
|
+
`${item.fidelity.padEnd(11)} ${item.id}`,
|
|
371454
|
+
...item.reason ? [` ${item.reason}`] : [],
|
|
371455
|
+
...(item.requiredApprovals ?? []).map((approval) => ` --approve ${JSON.stringify(approval)}`)
|
|
371456
|
+
].join(`
|
|
371457
|
+
`)).join(`
|
|
371458
|
+
`);
|
|
371459
|
+
}
|
|
370790
371460
|
var activationLabels = {
|
|
370791
371461
|
inactive: "inactive",
|
|
370792
371462
|
active: "active",
|
|
@@ -370856,11 +371526,11 @@ function formatPluginList(result, verbose = false) {
|
|
|
370856
371526
|
function createPluginCommand() {
|
|
370857
371527
|
const cmd = new Command3("plugin").description("Manage Native Plugins");
|
|
370858
371528
|
cmd.command("list").option("--cwd <path>").option("--verbose", "show permissions, contributions, runtime, and diagnostics").option("--json", "print the complete machine-readable response").action(async (options) => {
|
|
370859
|
-
const result = await (await client()).listPlugins({ cwd:
|
|
371529
|
+
const result = await (await client()).listPlugins({ cwd: resolve31(options.cwd ?? process.cwd()) });
|
|
370860
371530
|
console.log(options.json ? JSON.stringify(result, null, 2) : formatPluginList(result, options.verbose));
|
|
370861
371531
|
});
|
|
370862
371532
|
cmd.command("validate").argument("<path>").action(async (path) => {
|
|
370863
|
-
const result = await validateNativePlugin(
|
|
371533
|
+
const result = await validateNativePlugin(resolve31(path));
|
|
370864
371534
|
if (result.status === "valid")
|
|
370865
371535
|
console.log(`Valid Native Plugin: ${result.plugin.manifest.id}@${result.plugin.manifest.version}`);
|
|
370866
371536
|
else {
|
|
@@ -370871,7 +371541,7 @@ function createPluginCommand() {
|
|
|
370871
371541
|
});
|
|
370872
371542
|
for (const [name, link] of [["install-local", false], ["link", true]]) {
|
|
370873
371543
|
cmd.command(name).argument("<path>").option("--cwd <path>").option("--approve <permission>", "approve a requested permission", collect2, []).action(async (path, options) => {
|
|
370874
|
-
const sourcePath =
|
|
371544
|
+
const sourcePath = resolve31(path);
|
|
370875
371545
|
const validation = await validateNativePlugin(sourcePath);
|
|
370876
371546
|
if (!validation.plugin)
|
|
370877
371547
|
throw new Error(validation.diagnostics.map((item) => item.message).join("; "));
|
|
@@ -370881,7 +371551,7 @@ function createPluginCommand() {
|
|
|
370881
371551
|
if (missing.length)
|
|
370882
371552
|
throw new Error(`Explicit approval required: ${missing.join(", ")}`);
|
|
370883
371553
|
const result = await (await client()).installLocalPlugin({
|
|
370884
|
-
cwd:
|
|
371554
|
+
cwd: resolve31(options.cwd ?? process.cwd()),
|
|
370885
371555
|
sourcePath,
|
|
370886
371556
|
scope: "user",
|
|
370887
371557
|
approvedPermissions: approved,
|
|
@@ -370893,25 +371563,25 @@ function createPluginCommand() {
|
|
|
370893
371563
|
for (const action of ["enable", "disable"])
|
|
370894
371564
|
cmd.command(action).argument("<id>").option("--cwd <path>").action(async (id, options) => {
|
|
370895
371565
|
const api = await client();
|
|
370896
|
-
const input = { cwd:
|
|
371566
|
+
const input = { cwd: resolve31(options.cwd ?? process.cwd()) };
|
|
370897
371567
|
console.log((action === "enable" ? await api.enablePlugin(id, input) : await api.disablePlugin(id, input)).message);
|
|
370898
371568
|
});
|
|
370899
371569
|
cmd.command("uninstall").argument("<id>").option("--cwd <path>").action(async (id, options) => {
|
|
370900
|
-
console.log((await (await client()).uninstallPlugin(id, { cwd:
|
|
371570
|
+
console.log((await (await client()).uninstallPlugin(id, { cwd: resolve31(options.cwd ?? process.cwd()) })).message);
|
|
370901
371571
|
});
|
|
370902
371572
|
cmd.command("details").argument("<id>").option("--cwd <path>").action(async (id, options) => {
|
|
370903
|
-
const listed = await (await client()).listPlugins({ cwd:
|
|
371573
|
+
const listed = await (await client()).listPlugins({ cwd: resolve31(options.cwd ?? process.cwd()) });
|
|
370904
371574
|
const plugin = listed.plugins.find((item) => item.identity.id === id);
|
|
370905
371575
|
if (!plugin)
|
|
370906
371576
|
throw new Error(`Plugin not found: ${id}`);
|
|
370907
371577
|
console.log(JSON.stringify(plugin, null, 2));
|
|
370908
371578
|
});
|
|
370909
|
-
cmd.command("install").argument("<source>").requiredOption("--from <converter>").option("--cwd <path>").option("--approve <item>", "approve conversion item or permission", collect2, []).action(async (source, options) => {
|
|
370910
|
-
const temporaryRoot = await mkdtemp3(
|
|
370911
|
-
const output =
|
|
371579
|
+
cmd.command("install").argument("<source>").requiredOption("--from <converter>", "source converter: claude-code or codex").option("--cwd <path>").option("--approve <item>", "approve conversion item or permission", collect2, []).action(async (source, options) => {
|
|
371580
|
+
const temporaryRoot = await mkdtemp3(join49(tmpdir5(), "ohs-plugin-import-"));
|
|
371581
|
+
const output = join49(temporaryRoot, "native");
|
|
370912
371582
|
try {
|
|
370913
|
-
const { converter } = await createBuiltinConverterRegistry().detect(
|
|
370914
|
-
const inspection = await converter.inspect(
|
|
371583
|
+
const { converter } = await createBuiltinConverterRegistry().detect(resolve31(source), options.from);
|
|
371584
|
+
const inspection = await converter.inspect(resolve31(source));
|
|
370915
371585
|
const plan = await converter.plan(inspection, {});
|
|
370916
371586
|
await converter.convert({ inspection, plan, output, approvals: options.approve });
|
|
370917
371587
|
const validation = await validateNativePlugin(output);
|
|
@@ -370922,29 +371592,28 @@ function createPluginCommand() {
|
|
|
370922
371592
|
if (missing.length)
|
|
370923
371593
|
throw new Error(`Explicit permission approval required: ${missing.join(", ")}`);
|
|
370924
371594
|
const result = await (await client()).installLocalPlugin({
|
|
370925
|
-
cwd:
|
|
371595
|
+
cwd: resolve31(options.cwd ?? process.cwd()),
|
|
370926
371596
|
sourcePath: output,
|
|
370927
371597
|
scope: "user",
|
|
370928
371598
|
approvedPermissions: requested
|
|
370929
371599
|
});
|
|
370930
371600
|
console.log(result.message);
|
|
370931
371601
|
} finally {
|
|
370932
|
-
await
|
|
371602
|
+
await rm9(temporaryRoot, { recursive: true, force: true });
|
|
370933
371603
|
}
|
|
370934
371604
|
});
|
|
370935
|
-
cmd.command("convert").argument("<source>").option("--from <converter>").option("--output <path>").option("--dry-run").option("--json").option("--approve <item>", "approve a
|
|
371605
|
+
cmd.command("convert").argument("<source>").option("--from <converter>", "source converter: claude-code or codex").option("--output <path>").option("--dry-run").option("--json").option("--approve <item>", "approve a conversion change, omitted component, or permission shown in the plan", collect2, []).action(async (source, options) => {
|
|
370936
371606
|
const registry = createBuiltinConverterRegistry();
|
|
370937
|
-
const { converter, detection } = await registry.detect(
|
|
370938
|
-
const inspection = await converter.inspect(
|
|
371607
|
+
const { converter, detection } = await registry.detect(resolve31(source), options.from);
|
|
371608
|
+
const inspection = await converter.inspect(resolve31(source));
|
|
370939
371609
|
const plan = await converter.plan(inspection, {});
|
|
370940
371610
|
if (options.dryRun) {
|
|
370941
|
-
console.log(options.json ? JSON.stringify({ detection, inspection, plan }, null, 2) : plan
|
|
370942
|
-
`));
|
|
371611
|
+
console.log(options.json ? JSON.stringify({ detection, inspection, plan }, null, 2) : formatConversionPlan(plan));
|
|
370943
371612
|
return;
|
|
370944
371613
|
}
|
|
370945
371614
|
if (!options.output)
|
|
370946
371615
|
throw new Error("--output is required unless --dry-run is used");
|
|
370947
|
-
const report = await converter.convert({ inspection, plan, output:
|
|
371616
|
+
const report = await converter.convert({ inspection, plan, output: resolve31(options.output), approvals: options.approve });
|
|
370948
371617
|
console.log(options.json ? JSON.stringify(report, null, 2) : `Converted ${inspection.identity.id}: ${report.status}`);
|
|
370949
371618
|
});
|
|
370950
371619
|
return cmd;
|
|
@@ -371461,7 +372130,7 @@ function createSandboxCommand() {
|
|
|
371461
372130
|
init_src3();
|
|
371462
372131
|
init_src9();
|
|
371463
372132
|
import { readFileSync as readFileSync15 } from "node:fs";
|
|
371464
|
-
import { resolve as
|
|
372133
|
+
import { resolve as resolve33 } from "node:path";
|
|
371465
372134
|
import { Command as Command8 } from "commander";
|
|
371466
372135
|
function createWorkflowCommand() {
|
|
371467
372136
|
const cmd = new Command8("workflow").description("Inspect and manage persisted workflow runs");
|
|
@@ -371618,7 +372287,7 @@ function readJsonOption(path, inlineJson) {
|
|
|
371618
372287
|
if (inlineJson)
|
|
371619
372288
|
return JSON.parse(inlineJson);
|
|
371620
372289
|
if (path)
|
|
371621
|
-
return JSON.parse(readFileSync15(
|
|
372290
|
+
return JSON.parse(readFileSync15(resolve33(path), "utf-8"));
|
|
371622
372291
|
return;
|
|
371623
372292
|
}
|
|
371624
372293
|
function parseCsv(value) {
|
|
@@ -372215,4 +372884,4 @@ program.command("doctor").description("Check environment and dependencies").acti
|
|
|
372215
372884
|
});
|
|
372216
372885
|
program.parse();
|
|
372217
372886
|
|
|
372218
|
-
//# debugId=
|
|
372887
|
+
//# debugId=037007773B000ECC64756E2164756E21
|