@cremini/skillpack 1.0.9 → 1.1.1
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/README.md +4 -4
- package/dist/cli.js +4 -0
- package/package.json +4 -3
- package/runtime/README.md +11 -1
- package/runtime/server/dist/adapters/markdown.js +74 -0
- package/runtime/server/dist/adapters/markdown.js.map +1 -0
- package/runtime/server/dist/adapters/slack.js +369 -0
- package/runtime/server/dist/adapters/slack.js.map +1 -0
- package/runtime/server/dist/adapters/telegram.js +199 -0
- package/runtime/server/dist/adapters/telegram.js.map +1 -0
- package/runtime/server/dist/adapters/types.js +2 -0
- package/runtime/server/dist/adapters/types.js.map +1 -0
- package/runtime/server/dist/adapters/web.js +201 -0
- package/runtime/server/dist/adapters/web.js.map +1 -0
- package/runtime/server/dist/agent.js +223 -0
- package/runtime/server/dist/agent.js.map +1 -0
- package/runtime/server/dist/config.js +79 -0
- package/runtime/server/dist/config.js.map +1 -0
- package/runtime/server/dist/index.js +146 -0
- package/runtime/server/dist/index.js.map +1 -0
- package/runtime/server/dist/lifecycle.js +85 -0
- package/runtime/server/dist/lifecycle.js.map +1 -0
- package/runtime/server/dist/memory.js +195 -0
- package/runtime/server/dist/memory.js.map +1 -0
- package/runtime/server/package-lock.json +4028 -244
- package/runtime/server/package.json +13 -3
- package/runtime/start.bat +40 -4
- package/runtime/start.sh +30 -2
- package/runtime/web/index.html +145 -18
- package/runtime/web/js/api-key-dialog.js +153 -0
- package/runtime/web/js/api.js +25 -0
- package/runtime/web/js/chat-apps-dialog.js +194 -0
- package/runtime/web/{app.js → js/chat.js} +112 -193
- package/runtime/web/js/config.js +16 -0
- package/runtime/web/js/main.js +56 -0
- package/runtime/web/js/settings.js +205 -0
- package/runtime/web/styles.css +311 -10
- package/runtime/server/chat-proxy.js +0 -229
- package/runtime/server/index.js +0 -63
- package/runtime/server/routes.js +0 -104
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
AuthStorage,
|
|
4
|
-
createAgentSession,
|
|
5
|
-
ModelRegistry,
|
|
6
|
-
SessionManager,
|
|
7
|
-
DefaultResourceLoader,
|
|
8
|
-
} from "@mariozechner/pi-coding-agent";
|
|
9
|
-
|
|
10
|
-
const DEBUG = true;
|
|
11
|
-
|
|
12
|
-
const log = (...args) => DEBUG && console.log(...args);
|
|
13
|
-
const write = (data) => DEBUG && process.stdout.write(data);
|
|
14
|
-
|
|
15
|
-
function getAssistantDiagnostics(message) {
|
|
16
|
-
if (!message || message.role !== "assistant") {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const stopReason = message.stopReason;
|
|
21
|
-
const errorMessage =
|
|
22
|
-
message.errorMessage ||
|
|
23
|
-
(stopReason === "error" || stopReason === "aborted"
|
|
24
|
-
? `Request ${stopReason}`
|
|
25
|
-
: "");
|
|
26
|
-
|
|
27
|
-
const content = Array.isArray(message.content) ? message.content : [];
|
|
28
|
-
const text = content
|
|
29
|
-
.filter((item) => item?.type === "text")
|
|
30
|
-
.map((item) => item.text || "")
|
|
31
|
-
.join("")
|
|
32
|
-
.trim();
|
|
33
|
-
const toolCalls = content.filter((item) => item?.type === "toolCall").length;
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
stopReason,
|
|
37
|
-
errorMessage,
|
|
38
|
-
hasText: text.length > 0,
|
|
39
|
-
toolCalls,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Handle incoming WebSocket connection using pi-coding-agent
|
|
45
|
-
* @param {import("ws").WebSocket} ws
|
|
46
|
-
* @param {object} options
|
|
47
|
-
* @param {string} options.apiKey - OpenAI API Key
|
|
48
|
-
* @param {string} options.rootDir - Pack root directory
|
|
49
|
-
*/
|
|
50
|
-
export async function handleWsConnection(
|
|
51
|
-
ws,
|
|
52
|
-
{ apiKey, rootDir, provider = "openai", modelId = "gpt-5.4" },
|
|
53
|
-
) {
|
|
54
|
-
try {
|
|
55
|
-
let turnHadVisibleOutput = false;
|
|
56
|
-
|
|
57
|
-
// Create an in-memory auth storage to avoid touching disk
|
|
58
|
-
const authStorage = AuthStorage.inMemory({
|
|
59
|
-
[provider]: { type: "api_key", key: apiKey },
|
|
60
|
-
});
|
|
61
|
-
authStorage.setRuntimeApiKey(provider, apiKey);
|
|
62
|
-
|
|
63
|
-
const modelRegistry = new ModelRegistry(authStorage);
|
|
64
|
-
const model = modelRegistry.find(provider, modelId);
|
|
65
|
-
|
|
66
|
-
const sessionManager = SessionManager.inMemory();
|
|
67
|
-
|
|
68
|
-
const skillsPath = path.resolve(rootDir, "skills");
|
|
69
|
-
log(`[ChatProxy] Loading additional skills from: ${skillsPath}`);
|
|
70
|
-
|
|
71
|
-
const resourceLoader = new DefaultResourceLoader({
|
|
72
|
-
cwd: rootDir,
|
|
73
|
-
additionalSkillPaths: [skillsPath], // 手动加载 rootDir/skills
|
|
74
|
-
});
|
|
75
|
-
await resourceLoader.reload();
|
|
76
|
-
|
|
77
|
-
const { session } = await createAgentSession({
|
|
78
|
-
cwd: rootDir, // Allow pi-coding-agent to find skills in this pack's directory
|
|
79
|
-
authStorage,
|
|
80
|
-
modelRegistry,
|
|
81
|
-
sessionManager,
|
|
82
|
-
resourceLoader,
|
|
83
|
-
model,
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// Stream agent events to the WebSocket
|
|
87
|
-
session.subscribe((event) => {
|
|
88
|
-
switch (event.type) {
|
|
89
|
-
case "agent_start":
|
|
90
|
-
log("\n=== [PI-CODING-AGENT SESSION START] ===");
|
|
91
|
-
log("System Prompt:\n", session.systemPrompt);
|
|
92
|
-
log("========================================\n");
|
|
93
|
-
ws.send(JSON.stringify({ type: "agent_start" }));
|
|
94
|
-
break;
|
|
95
|
-
|
|
96
|
-
case "message_start":
|
|
97
|
-
log(`\n--- [Message Start: ${event.message?.role}] ---`);
|
|
98
|
-
if (event.message?.role === "user") {
|
|
99
|
-
log(JSON.stringify(event.message.content, null, 2));
|
|
100
|
-
}
|
|
101
|
-
ws.send(
|
|
102
|
-
JSON.stringify({
|
|
103
|
-
type: "message_start",
|
|
104
|
-
role: event.message?.role,
|
|
105
|
-
}),
|
|
106
|
-
);
|
|
107
|
-
break;
|
|
108
|
-
|
|
109
|
-
case "message_update":
|
|
110
|
-
if (event.assistantMessageEvent?.type === "text_delta") {
|
|
111
|
-
turnHadVisibleOutput = true;
|
|
112
|
-
write(event.assistantMessageEvent.delta);
|
|
113
|
-
ws.send(
|
|
114
|
-
JSON.stringify({
|
|
115
|
-
type: "text_delta",
|
|
116
|
-
delta: event.assistantMessageEvent.delta,
|
|
117
|
-
}),
|
|
118
|
-
);
|
|
119
|
-
} else if (event.assistantMessageEvent?.type === "thinking_delta") {
|
|
120
|
-
turnHadVisibleOutput = true;
|
|
121
|
-
ws.send(
|
|
122
|
-
JSON.stringify({
|
|
123
|
-
type: "thinking_delta",
|
|
124
|
-
delta: event.assistantMessageEvent.delta,
|
|
125
|
-
}),
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
break;
|
|
129
|
-
|
|
130
|
-
case "message_end":
|
|
131
|
-
log(`\n--- [Message End: ${event.message?.role}] ---`);
|
|
132
|
-
if (event.message?.role === "assistant") {
|
|
133
|
-
const diagnostics = getAssistantDiagnostics(event.message);
|
|
134
|
-
if (diagnostics) {
|
|
135
|
-
log(
|
|
136
|
-
`[Assistant Diagnostics] stopReason=${diagnostics.stopReason || "unknown"} text=${diagnostics.hasText ? "yes" : "no"} toolCalls=${diagnostics.toolCalls}`,
|
|
137
|
-
);
|
|
138
|
-
if (diagnostics.errorMessage) {
|
|
139
|
-
log(`[Assistant Error] ${diagnostics.errorMessage}`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
ws.send(
|
|
144
|
-
JSON.stringify({
|
|
145
|
-
type: "message_end",
|
|
146
|
-
role: event.message?.role,
|
|
147
|
-
}),
|
|
148
|
-
);
|
|
149
|
-
break;
|
|
150
|
-
|
|
151
|
-
case "tool_execution_start":
|
|
152
|
-
turnHadVisibleOutput = true;
|
|
153
|
-
log(`\n>>> [Tool Execution Start: ${event.toolName}] >>>`);
|
|
154
|
-
log("Args:", JSON.stringify(event.args, null, 2));
|
|
155
|
-
ws.send(
|
|
156
|
-
JSON.stringify({
|
|
157
|
-
type: "tool_start",
|
|
158
|
-
toolName: event.toolName,
|
|
159
|
-
toolInput: event.args,
|
|
160
|
-
}),
|
|
161
|
-
);
|
|
162
|
-
break;
|
|
163
|
-
|
|
164
|
-
case "tool_execution_end":
|
|
165
|
-
turnHadVisibleOutput = true;
|
|
166
|
-
log(`<<< [Tool Execution End: ${event.toolName}] <<<`);
|
|
167
|
-
log(`Error: ${event.isError ? "Yes" : "No"}`);
|
|
168
|
-
ws.send(
|
|
169
|
-
JSON.stringify({
|
|
170
|
-
type: "tool_end",
|
|
171
|
-
toolName: event.toolName,
|
|
172
|
-
isError: event.isError,
|
|
173
|
-
result: event.result,
|
|
174
|
-
}),
|
|
175
|
-
);
|
|
176
|
-
break;
|
|
177
|
-
|
|
178
|
-
case "agent_end":
|
|
179
|
-
log("\n=== [PI-CODING-AGENT SESSION END] ===\n");
|
|
180
|
-
ws.send(JSON.stringify({ type: "agent_end" }));
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// Listen for incoming messages from the frontend
|
|
186
|
-
ws.on("message", async (data) => {
|
|
187
|
-
try {
|
|
188
|
-
const payload = JSON.parse(data.toString());
|
|
189
|
-
if (payload.text) {
|
|
190
|
-
turnHadVisibleOutput = false;
|
|
191
|
-
|
|
192
|
-
// Send prompt to the agent, the session will handle message history natively
|
|
193
|
-
await session.prompt(payload.text);
|
|
194
|
-
|
|
195
|
-
const lastMessage = session.state.messages.at(-1);
|
|
196
|
-
const diagnostics = getAssistantDiagnostics(lastMessage);
|
|
197
|
-
if (diagnostics?.errorMessage) {
|
|
198
|
-
ws.send(JSON.stringify({ error: diagnostics.errorMessage }));
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (
|
|
203
|
-
diagnostics &&
|
|
204
|
-
!diagnostics.hasText &&
|
|
205
|
-
diagnostics.toolCalls === 0 &&
|
|
206
|
-
!turnHadVisibleOutput
|
|
207
|
-
) {
|
|
208
|
-
const emptyResponseError =
|
|
209
|
-
"Assistant returned no visible output. Check the server logs for stopReason/provider details.";
|
|
210
|
-
log(`[Assistant Warning] ${emptyResponseError}`);
|
|
211
|
-
ws.send(JSON.stringify({ error: emptyResponseError }));
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
ws.send(JSON.stringify({ done: true }));
|
|
216
|
-
}
|
|
217
|
-
} catch (err) {
|
|
218
|
-
ws.send(JSON.stringify({ error: String(err) }));
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
ws.on("close", () => {
|
|
223
|
-
session.dispose();
|
|
224
|
-
});
|
|
225
|
-
} catch (err) {
|
|
226
|
-
ws.send(JSON.stringify({ error: String(err) }));
|
|
227
|
-
ws.close();
|
|
228
|
-
}
|
|
229
|
-
}
|
package/runtime/server/index.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import express from "express";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { createServer } from "node:http";
|
|
6
|
-
import { exec } from "node:child_process";
|
|
7
|
-
import { registerRoutes } from "./routes.js";
|
|
8
|
-
|
|
9
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
|
|
11
|
-
const rootDir = process.env.PACK_ROOT || path.join(__dirname, "..");
|
|
12
|
-
|
|
13
|
-
const webDir = fs.existsSync(path.join(rootDir, "web"))
|
|
14
|
-
? path.join(rootDir, "web")
|
|
15
|
-
: path.join(__dirname, "..", "web");
|
|
16
|
-
|
|
17
|
-
const app = express();
|
|
18
|
-
app.use(express.json());
|
|
19
|
-
|
|
20
|
-
// Static file serving
|
|
21
|
-
app.use(express.static(webDir));
|
|
22
|
-
|
|
23
|
-
const server = createServer(app);
|
|
24
|
-
|
|
25
|
-
// Register API routes
|
|
26
|
-
registerRoutes(app, server, rootDir);
|
|
27
|
-
|
|
28
|
-
const HOST = process.env.HOST || "127.0.0.1";
|
|
29
|
-
const DEFAULT_PORT = 26313;
|
|
30
|
-
|
|
31
|
-
server.once("listening", () => {
|
|
32
|
-
const address = server.address();
|
|
33
|
-
const actualPort = typeof address === "string" ? address : address.port;
|
|
34
|
-
const url = `http://${HOST}:${actualPort}`;
|
|
35
|
-
console.log(`\n Skills Pack Server`);
|
|
36
|
-
console.log(` Running at ${url}\n`);
|
|
37
|
-
|
|
38
|
-
// Open the browser automatically
|
|
39
|
-
const cmd =
|
|
40
|
-
process.platform === "darwin"
|
|
41
|
-
? `open ${url}`
|
|
42
|
-
: process.platform === "win32"
|
|
43
|
-
? `start ${url}`
|
|
44
|
-
: `xdg-open ${url}`;
|
|
45
|
-
exec(cmd, () => {});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
function tryListen(port) {
|
|
49
|
-
server.listen(port, HOST);
|
|
50
|
-
|
|
51
|
-
server.once("error", (err) => {
|
|
52
|
-
if (err.code === "EADDRINUSE") {
|
|
53
|
-
console.log(` Port ${port} is in use, trying ${port + 1}...`);
|
|
54
|
-
server.close();
|
|
55
|
-
tryListen(port + 1);
|
|
56
|
-
} else {
|
|
57
|
-
throw err;
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const startPort = Number(process.env.PORT) || DEFAULT_PORT;
|
|
63
|
-
tryListen(startPort);
|
package/runtime/server/routes.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { WebSocketServer } from "ws";
|
|
4
|
-
|
|
5
|
-
import { handleWsConnection } from "./chat-proxy.js";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Read the skillpack.json config.
|
|
9
|
-
* @param {string} rootDir
|
|
10
|
-
*/
|
|
11
|
-
function getPackConfig(rootDir) {
|
|
12
|
-
const raw = fs.readFileSync(path.join(rootDir, "skillpack.json"), "utf-8");
|
|
13
|
-
return JSON.parse(raw);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Register all API routes.
|
|
18
|
-
* @param {import("express").Express} app
|
|
19
|
-
* @param {import("node:http").Server} server
|
|
20
|
-
* @param {string} rootDir - Root directory containing skillpack.json and skills/
|
|
21
|
-
*/
|
|
22
|
-
export function registerRoutes(app, server, rootDir) {
|
|
23
|
-
// API key and provider are stored in runtime memory
|
|
24
|
-
let apiKey = "";
|
|
25
|
-
let currentProvider = "openai";
|
|
26
|
-
|
|
27
|
-
if (process.env.OPENAI_API_KEY) {
|
|
28
|
-
apiKey = process.env.OPENAI_API_KEY;
|
|
29
|
-
currentProvider = "openai";
|
|
30
|
-
} else if (process.env.ANTHROPIC_API_KEY) {
|
|
31
|
-
apiKey = process.env.ANTHROPIC_API_KEY;
|
|
32
|
-
currentProvider = "anthropic";
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Get pack config
|
|
36
|
-
app.get("/api/config", (req, res) => {
|
|
37
|
-
const config = getPackConfig(rootDir);
|
|
38
|
-
res.json({
|
|
39
|
-
name: config.name,
|
|
40
|
-
description: config.description,
|
|
41
|
-
prompts: config.prompts || [],
|
|
42
|
-
skills: config.skills || [],
|
|
43
|
-
hasApiKey: !!apiKey,
|
|
44
|
-
provider: currentProvider,
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// Get skills list
|
|
49
|
-
app.get("/api/skills", (req, res) => {
|
|
50
|
-
const config = getPackConfig(rootDir);
|
|
51
|
-
res.json(config.skills || []);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// Set API key
|
|
55
|
-
app.post("/api/config/key", (req, res) => {
|
|
56
|
-
const { key, provider } = req.body;
|
|
57
|
-
if (!key) {
|
|
58
|
-
return res.status(400).json({ error: "API key is required" });
|
|
59
|
-
}
|
|
60
|
-
apiKey = key;
|
|
61
|
-
if (provider) {
|
|
62
|
-
currentProvider = provider;
|
|
63
|
-
}
|
|
64
|
-
res.json({ success: true, provider: currentProvider });
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// WebSocket chat service
|
|
68
|
-
const wss = new WebSocketServer({ noServer: true });
|
|
69
|
-
|
|
70
|
-
server.on("upgrade", (request, socket, head) => {
|
|
71
|
-
if (request.url.startsWith("/api/chat")) {
|
|
72
|
-
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
73
|
-
wss.emit("connection", ws, request);
|
|
74
|
-
});
|
|
75
|
-
} else {
|
|
76
|
-
socket.destroy();
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
wss.on("connection", (ws, request) => {
|
|
81
|
-
const url = new URL(
|
|
82
|
-
request.url,
|
|
83
|
-
`http://${request.headers.host || "127.0.0.1"}`,
|
|
84
|
-
);
|
|
85
|
-
const reqProvider = url.searchParams.get("provider") || currentProvider;
|
|
86
|
-
|
|
87
|
-
if (!apiKey) {
|
|
88
|
-
ws.send(JSON.stringify({ error: "Please set an API key first" }));
|
|
89
|
-
ws.close();
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const config = getPackConfig(rootDir);
|
|
94
|
-
|
|
95
|
-
const modelId = reqProvider === "anthropic" ? "claude-opus-4-6" : "gpt-5.4";
|
|
96
|
-
|
|
97
|
-
handleWsConnection(ws, { apiKey, rootDir, provider: reqProvider, modelId });
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// Clear session
|
|
101
|
-
app.delete("/api/chat", (req, res) => {
|
|
102
|
-
res.json({ success: true });
|
|
103
|
-
});
|
|
104
|
-
}
|