neoctl 0.1.19 → 0.1.21
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/agents/local-agent-task.js +2 -1
- package/dist/agents/local-agent-task.js.map +1 -1
- package/dist/agents/smoke-agents.js +21 -4
- package/dist/agents/smoke-agents.js.map +1 -1
- package/dist/context/prompts.js +4 -0
- package/dist/context/prompts.js.map +1 -1
- package/dist/core/image-storage.d.ts +6 -0
- package/dist/core/image-storage.js +38 -0
- package/dist/core/image-storage.js.map +1 -0
- package/dist/core/query-engine.d.ts +21 -1
- package/dist/core/query-engine.js +103 -13
- package/dist/core/query-engine.js.map +1 -1
- package/dist/core/query.d.ts +2 -1
- package/dist/core/query.js +60 -5
- package/dist/core/query.js.map +1 -1
- package/dist/core/smoke-core-loop.js +95 -6
- package/dist/core/smoke-core-loop.js.map +1 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +26 -1
- package/dist/index.js.map +1 -1
- package/dist/model/communication-logger.d.ts +2 -1
- package/dist/model/communication-logger.js +3 -0
- package/dist/model/communication-logger.js.map +1 -1
- package/dist/model/config.d.ts +10 -4
- package/dist/model/config.js +61 -12
- package/dist/model/config.js.map +1 -1
- package/dist/model/context-window.js +1 -0
- package/dist/model/context-window.js.map +1 -1
- package/dist/model/deepseek-adapter.d.ts +29 -0
- package/dist/model/deepseek-adapter.js +108 -0
- package/dist/model/deepseek-adapter.js.map +1 -0
- package/dist/model/env.js +35 -19
- package/dist/model/env.js.map +1 -1
- package/dist/model/kimi-adapter.d.ts +29 -0
- package/dist/model/kimi-adapter.js +108 -0
- package/dist/model/kimi-adapter.js.map +1 -0
- package/dist/model/model-metadata.json +726 -677
- package/dist/model/openai-adapter.d.ts +1 -1
- package/dist/model/openai-chat-mapper.d.ts +4 -1
- package/dist/model/openai-chat-mapper.js +30 -8
- package/dist/model/openai-chat-mapper.js.map +1 -1
- package/dist/model/openai-mappers.d.ts +5 -2
- package/dist/model/openai-mappers.js +33 -6
- package/dist/model/openai-mappers.js.map +1 -1
- package/dist/model/openai-responses-mapper.d.ts +1 -1
- package/dist/model/openai-responses-mapper.js +2 -1
- package/dist/model/openai-responses-mapper.js.map +1 -1
- package/dist/model/provider-factory.js +32 -0
- package/dist/model/provider-factory.js.map +1 -1
- package/dist/model/smoke-deepseek-mapper.d.ts +1 -0
- package/dist/model/smoke-deepseek-mapper.js +65 -0
- package/dist/model/smoke-deepseek-mapper.js.map +1 -0
- package/dist/model/smoke-openai.js +1 -1
- package/dist/model/smoke-openai.js.map +1 -1
- package/dist/model/smoke-responses-mapper.js +6 -6
- package/dist/model/smoke-responses-mapper.js.map +1 -1
- package/dist/open-directory.d.ts +1 -0
- package/dist/open-directory.js +26 -0
- package/dist/open-directory.js.map +1 -0
- package/dist/paths.d.ts +7 -0
- package/dist/paths.js +12 -0
- package/dist/paths.js.map +1 -0
- package/dist/repl/commands.d.ts +15 -0
- package/dist/repl/commands.js +58 -0
- package/dist/repl/commands.js.map +1 -1
- package/dist/repl/index.js +1012 -171
- package/dist/repl/index.js.map +1 -1
- package/dist/session/session-export.d.ts +33 -0
- package/dist/session/session-export.js +351 -0
- package/dist/session/session-export.js.map +1 -0
- package/dist/session/session-store.js +2 -2
- package/dist/session/session-store.js.map +1 -1
- package/dist/session/simple-session-runtime.d.ts +74 -0
- package/dist/session/simple-session-runtime.js +171 -0
- package/dist/session/simple-session-runtime.js.map +1 -0
- package/dist/session/smoke-session.js +22 -1
- package/dist/session/smoke-session.js.map +1 -1
- package/dist/skills/skill-filesystem.d.ts +32 -0
- package/dist/skills/skill-filesystem.js +371 -0
- package/dist/skills/skill-filesystem.js.map +1 -0
- package/dist/skills/skill-management-tools.d.ts +36 -0
- package/dist/skills/skill-management-tools.js +188 -0
- package/dist/skills/skill-management-tools.js.map +1 -0
- package/dist/skills/skill-tool.d.ts +85 -5
- package/dist/skills/skill-tool.js +173 -14
- package/dist/skills/skill-tool.js.map +1 -1
- package/dist/skills/smoke-skills.js +54 -5
- package/dist/skills/smoke-skills.js.map +1 -1
- package/dist/tips.d.ts +10 -0
- package/dist/tips.js +168 -0
- package/dist/tips.js.map +1 -0
- package/dist/tools/builtins/image-generation-tool.d.ts +96 -0
- package/dist/tools/builtins/image-generation-tool.js +471 -0
- package/dist/tools/builtins/image-generation-tool.js.map +1 -0
- package/dist/tools/builtins/search-providers.d.ts +15 -1
- package/dist/tools/builtins/search-providers.js +195 -1
- package/dist/tools/builtins/search-providers.js.map +1 -1
- package/dist/tools/builtins/search-tool.js +2 -2
- package/dist/tools/builtins/search-tool.js.map +1 -1
- package/dist/tools/registry.d.ts +1 -0
- package/dist/tools/registry.js +11 -0
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/run-tool-use.js +1 -1
- package/dist/tools/run-tool-use.js.map +1 -1
- package/dist/tools/smoke-tool-system.js +43 -9
- package/dist/tools/smoke-tool-system.js.map +1 -1
- package/dist/tools/tool.d.ts +9 -1
- package/dist/tools/tool.js.map +1 -1
- package/dist/types/messages.d.ts +5 -0
- package/dist/types/messages.js.map +1 -1
- package/dist/ui/display-message.d.ts +103 -0
- package/dist/ui/display-message.js +115 -0
- package/dist/ui/display-message.js.map +1 -0
- package/dist/web/html.d.ts +1 -0
- package/dist/web/html.js +862 -0
- package/dist/web/html.js.map +1 -0
- package/dist/web/index.d.ts +241 -0
- package/dist/web/index.js +1873 -0
- package/dist/web/index.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { QueryEngine } from "../core/query-engine.js";
|
|
2
|
+
import { extractMessageImages, toDisplayAgentEvent, toDisplayMessages } from "../ui/display-message.js";
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight session runtime for simple web/Vue integrations.
|
|
5
|
+
*
|
|
6
|
+
* It keeps exactly one active QueryEngine per session id, rejects concurrent writes
|
|
7
|
+
* to the same session by default, and can broadcast sendUserText events to listeners.
|
|
8
|
+
* Existing QueryEngine and SessionStore behavior is not changed.
|
|
9
|
+
*/
|
|
10
|
+
export class SimpleSessionRuntime {
|
|
11
|
+
options;
|
|
12
|
+
engines = new Map();
|
|
13
|
+
busy = new Set();
|
|
14
|
+
controllers = new Map();
|
|
15
|
+
listeners = new Set();
|
|
16
|
+
displayListeners = new Set();
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.options = options;
|
|
19
|
+
}
|
|
20
|
+
async getOrCreateEngine(sessionId, options = {}) {
|
|
21
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
22
|
+
const existing = this.engines.get(normalizedSessionId);
|
|
23
|
+
if (existing) {
|
|
24
|
+
await existing.initialized;
|
|
25
|
+
return existing.engine;
|
|
26
|
+
}
|
|
27
|
+
const engine = this.createEngine(normalizedSessionId, options.resume ?? true);
|
|
28
|
+
const initialized = engine.initialize();
|
|
29
|
+
const managed = { engine, initialized };
|
|
30
|
+
this.engines.set(normalizedSessionId, managed);
|
|
31
|
+
try {
|
|
32
|
+
await initialized;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
this.engines.delete(normalizedSessionId);
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
return engine;
|
|
39
|
+
}
|
|
40
|
+
async newSession(options = {}) {
|
|
41
|
+
const engine = this.createEngine(options.sessionId, false);
|
|
42
|
+
const snapshot = await engine.newSession();
|
|
43
|
+
this.engines.set(snapshot.sessionId, { engine, initialized: Promise.resolve() });
|
|
44
|
+
return snapshot;
|
|
45
|
+
}
|
|
46
|
+
async resumeSession(sessionId) {
|
|
47
|
+
const engine = await this.getOrCreateEngine(sessionId, { resume: true });
|
|
48
|
+
const snapshot = engine.snapshot().session;
|
|
49
|
+
if (!snapshot)
|
|
50
|
+
throw new Error("session transcripts are disabled");
|
|
51
|
+
return snapshot;
|
|
52
|
+
}
|
|
53
|
+
async listSessions(limit = 20) {
|
|
54
|
+
const engine = this.createEngine(undefined, false);
|
|
55
|
+
return engine.listSessions(limit);
|
|
56
|
+
}
|
|
57
|
+
async deleteSession(sessionId) {
|
|
58
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
59
|
+
if (this.busy.has(normalizedSessionId))
|
|
60
|
+
throw new Error(`session is busy: ${normalizedSessionId}`);
|
|
61
|
+
this.abort(normalizedSessionId);
|
|
62
|
+
this.engines.delete(normalizedSessionId);
|
|
63
|
+
const engine = this.createEngine(undefined, false);
|
|
64
|
+
return engine.deleteSession(normalizedSessionId);
|
|
65
|
+
}
|
|
66
|
+
async getMessages(sessionId) {
|
|
67
|
+
const engine = await this.getOrCreateEngine(sessionId, { resume: true });
|
|
68
|
+
return engine.getHistoryMessages();
|
|
69
|
+
}
|
|
70
|
+
async getDisplayMessages(sessionId, options = {}) {
|
|
71
|
+
return toDisplayMessages(await this.getMessages(sessionId), options);
|
|
72
|
+
}
|
|
73
|
+
async getDisplayImages(sessionId, options = { imageMode: "data-url" }) {
|
|
74
|
+
return extractMessageImages(await this.getMessages(sessionId), options);
|
|
75
|
+
}
|
|
76
|
+
async *sendUserText(sessionId, text, options = {}) {
|
|
77
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
78
|
+
if (this.busy.has(normalizedSessionId)) {
|
|
79
|
+
if (options.busyBehavior === "interrupt") {
|
|
80
|
+
this.abort(normalizedSessionId);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
throw new Error(`session is busy: ${normalizedSessionId}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const engine = await this.getOrCreateEngine(normalizedSessionId, { resume: true });
|
|
87
|
+
const controller = new AbortController();
|
|
88
|
+
const externalAbort = options.abortSignal;
|
|
89
|
+
const abortFromExternal = () => controller.abort(externalAbort?.reason);
|
|
90
|
+
if (externalAbort?.aborted)
|
|
91
|
+
abortFromExternal();
|
|
92
|
+
else
|
|
93
|
+
externalAbort?.addEventListener("abort", abortFromExternal, { once: true });
|
|
94
|
+
this.busy.add(normalizedSessionId);
|
|
95
|
+
this.controllers.set(normalizedSessionId, controller);
|
|
96
|
+
try {
|
|
97
|
+
for await (const event of engine.sendUserText(text, {
|
|
98
|
+
abortSignal: controller.signal,
|
|
99
|
+
blocks: options.blocks,
|
|
100
|
+
displayText: options.displayText,
|
|
101
|
+
})) {
|
|
102
|
+
await this.emit(event, { sessionId: normalizedSessionId });
|
|
103
|
+
yield event;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
externalAbort?.removeEventListener("abort", abortFromExternal);
|
|
108
|
+
if (this.controllers.get(normalizedSessionId) === controller)
|
|
109
|
+
this.controllers.delete(normalizedSessionId);
|
|
110
|
+
this.busy.delete(normalizedSessionId);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
abort(sessionId) {
|
|
114
|
+
const controller = this.controllers.get(normalizeSessionId(sessionId));
|
|
115
|
+
if (!controller)
|
|
116
|
+
return false;
|
|
117
|
+
controller.abort();
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
isBusy(sessionId) {
|
|
121
|
+
return this.busy.has(normalizeSessionId(sessionId));
|
|
122
|
+
}
|
|
123
|
+
onEvent(listener) {
|
|
124
|
+
this.listeners.add(listener);
|
|
125
|
+
return () => this.listeners.delete(listener);
|
|
126
|
+
}
|
|
127
|
+
onDisplayEvent(listener, options = { imageMode: "data-url" }) {
|
|
128
|
+
const entry = { listener, options };
|
|
129
|
+
this.displayListeners.add(entry);
|
|
130
|
+
return () => this.displayListeners.delete(entry);
|
|
131
|
+
}
|
|
132
|
+
release(sessionId) {
|
|
133
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
134
|
+
this.abort(normalizedSessionId);
|
|
135
|
+
this.engines.delete(normalizedSessionId);
|
|
136
|
+
this.busy.delete(normalizedSessionId);
|
|
137
|
+
}
|
|
138
|
+
snapshot() {
|
|
139
|
+
return {
|
|
140
|
+
activeSessions: [...this.engines.keys()],
|
|
141
|
+
busySessions: [...this.busy],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
createEngine(sessionId, resume) {
|
|
145
|
+
return new QueryEngine({
|
|
146
|
+
...this.options,
|
|
147
|
+
session: {
|
|
148
|
+
...this.options.session,
|
|
149
|
+
enabled: this.options.session?.enabled ?? true,
|
|
150
|
+
rootDir: this.options.sessionRootDir,
|
|
151
|
+
sessionId,
|
|
152
|
+
resume,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
async emit(event, context) {
|
|
157
|
+
for (const listener of this.listeners) {
|
|
158
|
+
await listener(event, context);
|
|
159
|
+
}
|
|
160
|
+
for (const { listener, options } of this.displayListeners) {
|
|
161
|
+
await listener(toDisplayAgentEvent(event, options), context);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function normalizeSessionId(sessionId) {
|
|
166
|
+
const normalized = sessionId.trim();
|
|
167
|
+
if (!normalized)
|
|
168
|
+
throw new Error("sessionId is required");
|
|
169
|
+
return normalized;
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=simple-session-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simple-session-runtime.js","sourceRoot":"","sources":["../../src/session/simple-session-runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { FileToolResultMemory, PERSISTED_OUTPUT_TAG } from "./tool-result-memory.js";
|
|
5
5
|
import { SessionStore } from "./session-store.js";
|
|
6
|
+
import { writeSessionMarkdownExport } from "./session-export.js";
|
|
6
7
|
async function main() {
|
|
7
8
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "agent-session-smoke-"));
|
|
8
9
|
const sessionId = "smoke-session";
|
|
@@ -22,6 +23,21 @@ async function main() {
|
|
|
22
23
|
const latest = await SessionStore.open({ agentId: "main", rootDir: root, resume: true });
|
|
23
24
|
const listed = await SessionStore.list({ agentId: "main", rootDir: root });
|
|
24
25
|
const resumedBudget = await resumed.toolResultMemory.applyBudget(resumed.getInitialMessages(), { maxSerializedLength: 180 });
|
|
26
|
+
const exportPath = path.join(root, "exports", "session.md");
|
|
27
|
+
const exportResult = await writeSessionMarkdownExport({
|
|
28
|
+
outputPath: exportPath,
|
|
29
|
+
session: resumed.snapshot(),
|
|
30
|
+
agentId: "main",
|
|
31
|
+
promptSnapshot: {
|
|
32
|
+
model: "smoke-model",
|
|
33
|
+
systemPrompt: "system prompt smoke",
|
|
34
|
+
userContextPrompt: "User context:\ncurrentDate: 2026-05-09",
|
|
35
|
+
toolDefinitions: [{ name: "alpha", description: "Alpha tool", inputSchema: { type: "object" } }],
|
|
36
|
+
commands: ["/export <absolute-md-path>"],
|
|
37
|
+
},
|
|
38
|
+
maxToolResultLines: 3,
|
|
39
|
+
});
|
|
40
|
+
const exportedMarkdown = await fs.readFile(exportPath, "utf8");
|
|
25
41
|
const afterReset = await SessionStore.open({ agentId: "main", rootDir: root, sessionId, resume: true });
|
|
26
42
|
afterReset.reset();
|
|
27
43
|
const resetResume = await SessionStore.open({ agentId: "main", rootDir: root, sessionId, resume: true });
|
|
@@ -45,10 +61,15 @@ async function main() {
|
|
|
45
61
|
listed[0]?.title === "Refined Smoke Session Title" &&
|
|
46
62
|
resumedBudget.records.length === 0 &&
|
|
47
63
|
resumedBudget.messages.some((message) => message.blocks.some((block) => block.type === "tool_result" && String(block.output).startsWith(PERSISTED_OUTPUT_TAG))) &&
|
|
64
|
+
exportResult.bytes > 0 &&
|
|
65
|
+
exportedMarkdown.includes("# Neo Session Export") &&
|
|
66
|
+
exportedMarkdown.includes("system prompt smoke") &&
|
|
67
|
+
exportedMarkdown.includes("## Transcript") &&
|
|
68
|
+
exportedMarkdown.includes("Tool use ID: call_a") &&
|
|
48
69
|
resetResume.snapshot().resumedMessages === 0 &&
|
|
49
70
|
deleted &&
|
|
50
71
|
listedAfterDelete.length === 0;
|
|
51
|
-
console.log(JSON.stringify({ ok, firstRecords: first.records.length, persistedBlocks: persistedBlocks.length, resumed: resumed.snapshot(), latest: latest.snapshot(), listed, reset: resetResume.snapshot(), deleted, listedAfterDelete }, null, 2));
|
|
72
|
+
console.log(JSON.stringify({ ok, firstRecords: first.records.length, persistedBlocks: persistedBlocks.length, exportResult, resumed: resumed.snapshot(), latest: latest.snapshot(), listed, reset: resetResume.snapshot(), deleted, listedAfterDelete }, null, 2));
|
|
52
73
|
if (!ok)
|
|
53
74
|
process.exitCode = 1;
|
|
54
75
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"smoke-session.js","sourceRoot":"","sources":["../../src/session/smoke-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"smoke-session.js","sourceRoot":"","sources":["../../src/session/smoke-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type MutableSkillCatalog, type SkillCatalog, type SkillDescriptor, type SkillSourceInfo, type SkillUpdatePatch, type SkillWriteOptions } from "./skill-tool.js";
|
|
2
|
+
export interface FileSystemSkillCatalogOptions {
|
|
3
|
+
roots: readonly SkillRoot[];
|
|
4
|
+
createRoot?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface SkillRoot {
|
|
7
|
+
root: string;
|
|
8
|
+
kind?: SkillSourceInfo["kind"];
|
|
9
|
+
plugin?: string;
|
|
10
|
+
readonly?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare class FileSystemSkillCatalog implements MutableSkillCatalog {
|
|
13
|
+
private readonly roots;
|
|
14
|
+
private readonly createRoot;
|
|
15
|
+
constructor(options: FileSystemSkillCatalogOptions | readonly string[]);
|
|
16
|
+
list(): Promise<SkillDescriptor[]>;
|
|
17
|
+
get(name: string): Promise<SkillDescriptor | undefined>;
|
|
18
|
+
create(skill: SkillDescriptor, options?: SkillWriteOptions): Promise<SkillDescriptor>;
|
|
19
|
+
update(name: string, patch: SkillUpdatePatch): Promise<SkillDescriptor>;
|
|
20
|
+
delete(name: string): Promise<boolean>;
|
|
21
|
+
private resolveCreateRoot;
|
|
22
|
+
private findWritableLocation;
|
|
23
|
+
}
|
|
24
|
+
export declare class CompositeSkillCatalog implements SkillCatalog {
|
|
25
|
+
private readonly catalogs;
|
|
26
|
+
constructor(catalogs: readonly SkillCatalog[]);
|
|
27
|
+
list(): Promise<SkillDescriptor[]>;
|
|
28
|
+
get(name: string): Promise<SkillDescriptor | undefined>;
|
|
29
|
+
}
|
|
30
|
+
export declare function loadSkillFromMarkdownFile(file: string, root?: SkillRoot): Promise<SkillDescriptor>;
|
|
31
|
+
export declare function parseSkillMarkdown(markdown: string, fallbackName?: string): SkillDescriptor;
|
|
32
|
+
export declare function serializeSkillMarkdown(skill: SkillDescriptor): string;
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { normalizeSkillDescriptor, requireSkillName, } from "./skill-tool.js";
|
|
4
|
+
const SKILL_FILE_NAME = "SKILL.md";
|
|
5
|
+
export class FileSystemSkillCatalog {
|
|
6
|
+
roots;
|
|
7
|
+
createRoot;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
if (isRootList(options)) {
|
|
10
|
+
this.roots = options.map((root) => ({ root, kind: "filesystem" }));
|
|
11
|
+
if (this.roots.length === 0)
|
|
12
|
+
throw new Error("FileSystemSkillCatalog requires at least one root");
|
|
13
|
+
this.createRoot = path.resolve(this.roots[0].root);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
this.roots = options.roots.map((root) => ({ ...root }));
|
|
17
|
+
if (this.roots.length === 0)
|
|
18
|
+
throw new Error("FileSystemSkillCatalog requires at least one root");
|
|
19
|
+
this.createRoot = path.resolve(options.createRoot ?? firstWritableRoot(this.roots).root);
|
|
20
|
+
}
|
|
21
|
+
async list() {
|
|
22
|
+
const byName = new Map();
|
|
23
|
+
for (const root of this.roots) {
|
|
24
|
+
for (const location of await discoverSkillLocations(root)) {
|
|
25
|
+
try {
|
|
26
|
+
const skill = await readSkillFromLocation(location);
|
|
27
|
+
if (skill.enabled === false)
|
|
28
|
+
continue;
|
|
29
|
+
if (!byName.has(skill.name))
|
|
30
|
+
byName.set(skill.name, skill);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Invalid plugin skills should not break the whole catalog. Use read/validate tooling for diagnostics.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return [...byName.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
38
|
+
}
|
|
39
|
+
async get(name) {
|
|
40
|
+
const normalized = requireSkillName(name);
|
|
41
|
+
for (const root of this.roots) {
|
|
42
|
+
const direct = await readIfExists({ root, directory: path.join(path.resolve(root.root), normalized), file: path.join(path.resolve(root.root), normalized, SKILL_FILE_NAME) });
|
|
43
|
+
if (direct)
|
|
44
|
+
return direct;
|
|
45
|
+
for (const location of await discoverSkillLocations(root)) {
|
|
46
|
+
const skill = await readIfExists(location);
|
|
47
|
+
if (skill?.name === normalized)
|
|
48
|
+
return skill;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
async create(skill, options = {}) {
|
|
54
|
+
const normalized = normalizeSkillDescriptor(skill, { requireEntrypoint: true });
|
|
55
|
+
const root = this.resolveCreateRoot();
|
|
56
|
+
const directory = path.join(root.root, normalized.name);
|
|
57
|
+
const file = path.join(directory, SKILL_FILE_NAME);
|
|
58
|
+
const exists = await pathExists(file);
|
|
59
|
+
if (exists && !options.overwrite)
|
|
60
|
+
throw new Error(`Skill already exists: ${normalized.name}`);
|
|
61
|
+
await fs.mkdir(directory, { recursive: true });
|
|
62
|
+
const now = new Date().toISOString();
|
|
63
|
+
const stored = normalizeSkillDescriptor({
|
|
64
|
+
...normalized,
|
|
65
|
+
source: sourceForLocation({ root, directory, file }),
|
|
66
|
+
createdAt: normalized.createdAt ?? now,
|
|
67
|
+
updatedAt: now,
|
|
68
|
+
});
|
|
69
|
+
await fs.writeFile(file, serializeSkillMarkdown(stored), "utf8");
|
|
70
|
+
return stored;
|
|
71
|
+
}
|
|
72
|
+
async update(name, patch) {
|
|
73
|
+
const normalizedName = requireSkillName(name);
|
|
74
|
+
const location = await this.findWritableLocation(normalizedName);
|
|
75
|
+
if (!location)
|
|
76
|
+
throw new Error(`Unknown writable skill: ${normalizedName}`);
|
|
77
|
+
const current = await readSkillFromLocation(location);
|
|
78
|
+
const updated = normalizeSkillDescriptor({
|
|
79
|
+
...current,
|
|
80
|
+
...patch,
|
|
81
|
+
name: normalizedName,
|
|
82
|
+
source: current.source,
|
|
83
|
+
createdAt: current.createdAt,
|
|
84
|
+
updatedAt: new Date().toISOString(),
|
|
85
|
+
});
|
|
86
|
+
await fs.writeFile(location.file, serializeSkillMarkdown(updated), "utf8");
|
|
87
|
+
return updated;
|
|
88
|
+
}
|
|
89
|
+
async delete(name) {
|
|
90
|
+
const location = await this.findWritableLocation(requireSkillName(name));
|
|
91
|
+
if (!location)
|
|
92
|
+
return false;
|
|
93
|
+
await fs.rm(location.directory, { recursive: true, force: true });
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
resolveCreateRoot() {
|
|
97
|
+
const root = this.roots.find((candidate) => path.resolve(candidate.root) === this.createRoot);
|
|
98
|
+
if (!root)
|
|
99
|
+
return { root: this.createRoot, kind: "filesystem" };
|
|
100
|
+
if (root.readonly)
|
|
101
|
+
throw new Error(`Skill root is readonly: ${root.root}`);
|
|
102
|
+
return { ...root, root: path.resolve(root.root) };
|
|
103
|
+
}
|
|
104
|
+
async findWritableLocation(name) {
|
|
105
|
+
for (const root of this.roots) {
|
|
106
|
+
if (root.readonly)
|
|
107
|
+
continue;
|
|
108
|
+
const file = path.join(path.resolve(root.root), name, SKILL_FILE_NAME);
|
|
109
|
+
if (await pathExists(file))
|
|
110
|
+
return { root: { ...root, root: path.resolve(root.root) }, directory: path.dirname(file), file };
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export class CompositeSkillCatalog {
|
|
116
|
+
catalogs;
|
|
117
|
+
constructor(catalogs) {
|
|
118
|
+
this.catalogs = catalogs;
|
|
119
|
+
}
|
|
120
|
+
async list() {
|
|
121
|
+
const byName = new Map();
|
|
122
|
+
for (const catalog of this.catalogs) {
|
|
123
|
+
for (const skill of await catalog.list())
|
|
124
|
+
if (!byName.has(skill.name))
|
|
125
|
+
byName.set(skill.name, skill);
|
|
126
|
+
}
|
|
127
|
+
return [...byName.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
128
|
+
}
|
|
129
|
+
async get(name) {
|
|
130
|
+
for (const catalog of this.catalogs) {
|
|
131
|
+
const skill = await catalog.get(name);
|
|
132
|
+
if (skill)
|
|
133
|
+
return skill;
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export async function loadSkillFromMarkdownFile(file, root) {
|
|
139
|
+
const resolvedFile = path.resolve(file);
|
|
140
|
+
return readSkillFromLocation({
|
|
141
|
+
root: root ? { ...root, root: path.resolve(root.root) } : { root: path.dirname(path.dirname(resolvedFile)), kind: "filesystem" },
|
|
142
|
+
directory: path.dirname(resolvedFile),
|
|
143
|
+
file: resolvedFile,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
export function parseSkillMarkdown(markdown, fallbackName) {
|
|
147
|
+
const { frontmatter, body } = parseFrontmatter(markdown);
|
|
148
|
+
const name = stringValue(frontmatter.name) ?? fallbackName;
|
|
149
|
+
const descriptor = {
|
|
150
|
+
name: requireSkillName(name),
|
|
151
|
+
title: stringValue(frontmatter.title),
|
|
152
|
+
description: stringValue(frontmatter.description) ?? firstBodyLine(body) ?? requireSkillName(name),
|
|
153
|
+
version: stringValue(frontmatter.version),
|
|
154
|
+
entrypoint: body.trim(),
|
|
155
|
+
execution: executionValue(frontmatter.execution) ?? "inline",
|
|
156
|
+
allowedTools: stringListValue(frontmatter["allowed-tools"] ?? frontmatter.allowedTools),
|
|
157
|
+
model: stringValue(frontmatter.model),
|
|
158
|
+
effort: effortValue(frontmatter.effort ?? frontmatter["reasoning-effort"]),
|
|
159
|
+
disableModelInvocation: booleanValue(frontmatter.disableModelInvocation ?? frontmatter["disable-model-invocation"]),
|
|
160
|
+
tags: stringListValue(frontmatter.tags),
|
|
161
|
+
argumentHint: stringValue(frontmatter["argument-hint"] ?? frontmatter.argumentHint),
|
|
162
|
+
enabled: booleanValue(frontmatter.enabled) ?? true,
|
|
163
|
+
trustLevel: trustLevelValue(frontmatter.trustLevel ?? frontmatter["trust-level"]),
|
|
164
|
+
metadata: recordValue(frontmatter.metadata),
|
|
165
|
+
};
|
|
166
|
+
return normalizeSkillDescriptor(descriptor, { requireEntrypoint: true });
|
|
167
|
+
}
|
|
168
|
+
export function serializeSkillMarkdown(skill) {
|
|
169
|
+
const frontmatter = {
|
|
170
|
+
name: skill.name,
|
|
171
|
+
title: skill.title,
|
|
172
|
+
description: skill.description,
|
|
173
|
+
version: skill.version,
|
|
174
|
+
execution: skill.execution,
|
|
175
|
+
"allowed-tools": skill.allowedTools,
|
|
176
|
+
model: skill.model,
|
|
177
|
+
effort: skill.effort,
|
|
178
|
+
tags: skill.tags,
|
|
179
|
+
"argument-hint": skill.argumentHint,
|
|
180
|
+
enabled: skill.enabled,
|
|
181
|
+
"trust-level": skill.trustLevel,
|
|
182
|
+
createdAt: skill.createdAt,
|
|
183
|
+
updatedAt: skill.updatedAt,
|
|
184
|
+
metadata: skill.metadata,
|
|
185
|
+
};
|
|
186
|
+
return `---\n${serializeFrontmatter(frontmatter)}---\n\n${skill.entrypoint.trim()}\n`;
|
|
187
|
+
}
|
|
188
|
+
async function discoverSkillLocations(root) {
|
|
189
|
+
const resolvedRoot = path.resolve(root.root);
|
|
190
|
+
if (!(await pathExists(resolvedRoot)))
|
|
191
|
+
return [];
|
|
192
|
+
const entries = await fs.readdir(resolvedRoot, { withFileTypes: true });
|
|
193
|
+
const locations = [];
|
|
194
|
+
const normalizedRoot = { ...root, root: resolvedRoot };
|
|
195
|
+
for (const entry of entries) {
|
|
196
|
+
if (!entry.isDirectory())
|
|
197
|
+
continue;
|
|
198
|
+
const directory = path.join(resolvedRoot, entry.name);
|
|
199
|
+
const file = path.join(directory, SKILL_FILE_NAME);
|
|
200
|
+
if (await pathExists(file))
|
|
201
|
+
locations.push({ root: normalizedRoot, directory, file });
|
|
202
|
+
}
|
|
203
|
+
return locations;
|
|
204
|
+
}
|
|
205
|
+
async function readIfExists(location) {
|
|
206
|
+
try {
|
|
207
|
+
return await readSkillFromLocation(location);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function readSkillFromLocation(location) {
|
|
214
|
+
const raw = await fs.readFile(location.file, "utf8");
|
|
215
|
+
const fallbackName = path.basename(location.directory);
|
|
216
|
+
const parsed = parseSkillMarkdown(raw, fallbackName);
|
|
217
|
+
return normalizeSkillDescriptor({
|
|
218
|
+
...parsed,
|
|
219
|
+
source: sourceForLocation(location),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
function sourceForLocation(location) {
|
|
223
|
+
return {
|
|
224
|
+
kind: location.root.kind ?? "filesystem",
|
|
225
|
+
root: path.resolve(location.root.root),
|
|
226
|
+
path: location.file,
|
|
227
|
+
plugin: location.root.plugin,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function parseFrontmatter(markdown) {
|
|
231
|
+
if (!markdown.startsWith("---"))
|
|
232
|
+
return { frontmatter: {}, body: markdown };
|
|
233
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(markdown);
|
|
234
|
+
if (!match)
|
|
235
|
+
return { frontmatter: {}, body: markdown };
|
|
236
|
+
return { frontmatter: parseYamlLikeFrontmatter(match[1]), body: match[2] };
|
|
237
|
+
}
|
|
238
|
+
function parseYamlLikeFrontmatter(raw) {
|
|
239
|
+
const result = {};
|
|
240
|
+
const lines = raw.split(/\r?\n/);
|
|
241
|
+
let index = 0;
|
|
242
|
+
while (index < lines.length) {
|
|
243
|
+
const line = lines[index];
|
|
244
|
+
index += 1;
|
|
245
|
+
if (!line.trim() || line.trimStart().startsWith("#"))
|
|
246
|
+
continue;
|
|
247
|
+
const match = /^([A-Za-z0-9_.-]+):\s*(.*)$/.exec(line);
|
|
248
|
+
if (!match)
|
|
249
|
+
continue;
|
|
250
|
+
const key = match[1];
|
|
251
|
+
const rest = match[2].trim();
|
|
252
|
+
if (!rest) {
|
|
253
|
+
const list = [];
|
|
254
|
+
while (index < lines.length) {
|
|
255
|
+
const child = lines[index];
|
|
256
|
+
const listMatch = /^\s*-\s*(.*)$/.exec(child);
|
|
257
|
+
if (!listMatch)
|
|
258
|
+
break;
|
|
259
|
+
list.push(unquote(listMatch[1].trim()));
|
|
260
|
+
index += 1;
|
|
261
|
+
}
|
|
262
|
+
result[key] = list.length ? list : "";
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
result[key] = parseScalar(rest);
|
|
266
|
+
}
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
function serializeFrontmatter(frontmatter) {
|
|
270
|
+
const lines = [];
|
|
271
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
272
|
+
if (value === undefined || value === null || value === "" || Array.isArray(value) && value.length === 0)
|
|
273
|
+
continue;
|
|
274
|
+
if (Array.isArray(value)) {
|
|
275
|
+
lines.push(`${key}:`);
|
|
276
|
+
for (const item of value)
|
|
277
|
+
lines.push(` - ${quoteIfNeeded(String(item))}`);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (typeof value === "object") {
|
|
281
|
+
lines.push(`${key}: ${JSON.stringify(value)}`);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
lines.push(`${key}: ${quoteIfNeeded(String(value))}`);
|
|
285
|
+
}
|
|
286
|
+
return `${lines.join("\n")}\n`;
|
|
287
|
+
}
|
|
288
|
+
function parseScalar(value) {
|
|
289
|
+
const unquoted = unquote(value);
|
|
290
|
+
if (unquoted === "true")
|
|
291
|
+
return true;
|
|
292
|
+
if (unquoted === "false")
|
|
293
|
+
return false;
|
|
294
|
+
if (value.startsWith("[") || value.startsWith("{")) {
|
|
295
|
+
try {
|
|
296
|
+
return JSON.parse(value);
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
return unquoted;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (value.includes(","))
|
|
303
|
+
return value.split(",").map((item) => unquote(item.trim())).filter(Boolean);
|
|
304
|
+
return unquoted;
|
|
305
|
+
}
|
|
306
|
+
function isRootList(options) {
|
|
307
|
+
return Array.isArray(options);
|
|
308
|
+
}
|
|
309
|
+
function firstWritableRoot(roots) {
|
|
310
|
+
const root = roots.find((candidate) => !candidate.readonly);
|
|
311
|
+
if (!root)
|
|
312
|
+
throw new Error("FileSystemSkillCatalog requires at least one writable root for create operations");
|
|
313
|
+
return root;
|
|
314
|
+
}
|
|
315
|
+
async function pathExists(file) {
|
|
316
|
+
try {
|
|
317
|
+
await fs.access(file);
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function stringValue(value) {
|
|
325
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
326
|
+
}
|
|
327
|
+
function booleanValue(value) {
|
|
328
|
+
if (typeof value === "boolean")
|
|
329
|
+
return value;
|
|
330
|
+
if (value === "true")
|
|
331
|
+
return true;
|
|
332
|
+
if (value === "false")
|
|
333
|
+
return false;
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
function stringListValue(value) {
|
|
337
|
+
if (Array.isArray(value))
|
|
338
|
+
return value.map(String).map((item) => item.trim()).filter(Boolean);
|
|
339
|
+
if (typeof value === "string")
|
|
340
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
function executionValue(value) {
|
|
344
|
+
return value === "inline" || value === "fork" ? value : undefined;
|
|
345
|
+
}
|
|
346
|
+
function effortValue(value) {
|
|
347
|
+
return value === "minimal" || value === "low" || value === "medium" || value === "high" ? value : undefined;
|
|
348
|
+
}
|
|
349
|
+
function trustLevelValue(value) {
|
|
350
|
+
return value === "builtin" || value === "workspace" || value === "user" || value === "plugin" || value === "generated" || value === "remote" ? value : undefined;
|
|
351
|
+
}
|
|
352
|
+
function recordValue(value) {
|
|
353
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
354
|
+
}
|
|
355
|
+
function firstBodyLine(body) {
|
|
356
|
+
return body.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
357
|
+
}
|
|
358
|
+
function unquote(value) {
|
|
359
|
+
const trimmed = value.trim();
|
|
360
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"'))
|
|
361
|
+
return trimmed.slice(1, -1).replace(/\\"/g, '"');
|
|
362
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
363
|
+
return trimmed.slice(1, -1).replace(/\\'/g, "'");
|
|
364
|
+
return trimmed;
|
|
365
|
+
}
|
|
366
|
+
function quoteIfNeeded(value) {
|
|
367
|
+
if (/^[A-Za-z0-9_.\-/]+$/.test(value))
|
|
368
|
+
return value;
|
|
369
|
+
return JSON.stringify(value);
|
|
370
|
+
}
|
|
371
|
+
//# sourceMappingURL=skill-filesystem.js.map
|