@cremini/skillpack 1.2.7 → 1.2.9
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 +3 -0
- package/dist/cli.js +1795 -712
- package/package.json +5 -2
- package/web/js/api-key-dialog.js +5 -0
- package/web/js/api.js +30 -0
- package/web/js/chat.js +290 -14
- package/web/js/main.js +7 -4
- package/web/styles.css +65 -0
package/dist/cli.js
CHANGED
|
@@ -9,212 +9,138 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// src/
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
12
|
+
// src/job-config.ts
|
|
13
|
+
import fs2 from "fs";
|
|
14
|
+
import path2 from "path";
|
|
15
|
+
function getJobFilePath(workDir) {
|
|
16
|
+
return path2.join(workDir, JOB_FILE);
|
|
17
|
+
}
|
|
18
|
+
function validateScheduledJobConfig(value, sourceLabel, index) {
|
|
19
|
+
if (!value || typeof value !== "object") {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Invalid job config from ${sourceLabel}: "jobs[${index}]" must be an object`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
const job = value;
|
|
25
|
+
if (typeof job.name !== "string" || !job.name.trim()) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`Invalid job config from ${sourceLabel}: "jobs[${index}].name" is required`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
if (typeof job.cron !== "string" || !job.cron.trim()) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Invalid job config from ${sourceLabel}: "jobs[${index}].cron" is required`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if (typeof job.prompt !== "string" || !job.prompt.trim()) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Invalid job config from ${sourceLabel}: "jobs[${index}].prompt" is required`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (!job.notify || typeof job.notify !== "object") {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`Invalid job config from ${sourceLabel}: "jobs[${index}].notify" must be an object`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const notify = job.notify;
|
|
46
|
+
if (typeof notify.adapter !== "string" || !notify.adapter.trim()) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Invalid job config from ${sourceLabel}: "jobs[${index}].notify.adapter" is required`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (typeof notify.channelId !== "string" || !notify.channelId.trim()) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Invalid job config from ${sourceLabel}: "jobs[${index}].notify.channelId" is required`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (job.enabled !== void 0 && typeof job.enabled !== "boolean") {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Invalid job config from ${sourceLabel}: "jobs[${index}].enabled" must be a boolean`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (job.timezone !== void 0 && typeof job.timezone !== "string") {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Invalid job config from ${sourceLabel}: "jobs[${index}].timezone" must be a string`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function validateJobFileShape(value, sourceLabel) {
|
|
68
|
+
if (!value || typeof value !== "object") {
|
|
69
|
+
throw new Error(`Invalid job config from ${sourceLabel}: expected a JSON object`);
|
|
70
|
+
}
|
|
71
|
+
const jobFile = value;
|
|
72
|
+
if (!Array.isArray(jobFile.jobs)) {
|
|
73
|
+
throw new Error(`Invalid job config from ${sourceLabel}: "jobs" must be an array`);
|
|
74
|
+
}
|
|
75
|
+
const names = /* @__PURE__ */ new Set();
|
|
76
|
+
jobFile.jobs.forEach((job, index) => {
|
|
77
|
+
validateScheduledJobConfig(job, sourceLabel, index);
|
|
78
|
+
const normalizedName = job.name.trim().toLowerCase();
|
|
79
|
+
if (names.has(normalizedName)) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`Invalid job config from ${sourceLabel}: duplicate job name "${job.name}" is not allowed`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
names.add(normalizedName);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function normalizeJobFile(jobFile) {
|
|
88
|
+
return {
|
|
89
|
+
jobs: jobFile.jobs.map((job) => ({
|
|
90
|
+
name: job.name.trim(),
|
|
91
|
+
cron: job.cron.trim(),
|
|
92
|
+
prompt: job.prompt,
|
|
93
|
+
notify: {
|
|
94
|
+
adapter: job.notify.adapter.trim(),
|
|
95
|
+
channelId: job.notify.channelId.trim()
|
|
45
96
|
},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
} catch (err) {
|
|
73
|
-
console.warn(" Warning: Failed to parse data/config.json:", err);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
let { apiKey = "", provider = "openai", baseUrl = "" } = this.configData;
|
|
77
|
-
if (!apiKey) {
|
|
78
|
-
if (process.env.OPENAI_API_KEY) {
|
|
79
|
-
apiKey = process.env.OPENAI_API_KEY;
|
|
80
|
-
provider = "openai";
|
|
81
|
-
} else if (process.env.ANTHROPIC_API_KEY) {
|
|
82
|
-
apiKey = process.env.ANTHROPIC_API_KEY;
|
|
83
|
-
provider = "anthropic";
|
|
84
|
-
} else if (process.env.GOOGLE_API_KEY) {
|
|
85
|
-
apiKey = process.env.GOOGLE_API_KEY;
|
|
86
|
-
provider = "google";
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
this.configData.apiKey = apiKey;
|
|
90
|
-
this.configData.provider = provider;
|
|
91
|
-
this.configData.baseUrl = baseUrl?.trim() || void 0;
|
|
92
|
-
return this.configData;
|
|
93
|
-
}
|
|
94
|
-
getConfig() {
|
|
95
|
-
return this.configData;
|
|
96
|
-
}
|
|
97
|
-
save(rootDir, updates) {
|
|
98
|
-
const configDir = path5.join(rootDir, "data");
|
|
99
|
-
if (!this.configPath) {
|
|
100
|
-
this.configPath = path5.join(rootDir, "data", "config.json");
|
|
101
|
-
}
|
|
102
|
-
if (!fs5.existsSync(configDir)) {
|
|
103
|
-
fs5.mkdirSync(configDir, { recursive: true });
|
|
104
|
-
}
|
|
105
|
-
if (updates.apiKey !== void 0) this.configData.apiKey = updates.apiKey;
|
|
106
|
-
if (updates.provider !== void 0) this.configData.provider = updates.provider;
|
|
107
|
-
if (updates.baseUrl !== void 0) {
|
|
108
|
-
this.configData.baseUrl = updates.baseUrl?.trim() || void 0;
|
|
109
|
-
}
|
|
110
|
-
if (updates.modelId !== void 0) {
|
|
111
|
-
this.configData.modelId = updates.modelId?.trim() || void 0;
|
|
112
|
-
}
|
|
113
|
-
if (updates.apiProtocol !== void 0) {
|
|
114
|
-
this.configData.apiProtocol = updates.apiProtocol || void 0;
|
|
115
|
-
}
|
|
116
|
-
if (updates.adapters !== void 0) {
|
|
117
|
-
const merged = { ...this.configData.adapters || {} };
|
|
118
|
-
for (const [adapterKey, adapterVal] of Object.entries(updates.adapters)) {
|
|
119
|
-
if (adapterVal === null || adapterVal === void 0) {
|
|
120
|
-
delete merged[adapterKey];
|
|
121
|
-
} else {
|
|
122
|
-
merged[adapterKey] = adapterVal;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
this.configData.adapters = merged;
|
|
126
|
-
}
|
|
127
|
-
if (updates.scheduledJobs !== void 0) {
|
|
128
|
-
this.configData.scheduledJobs = updates.scheduledJobs;
|
|
129
|
-
}
|
|
130
|
-
try {
|
|
131
|
-
fs5.writeFileSync(
|
|
132
|
-
this.configPath,
|
|
133
|
-
JSON.stringify(this.configData, null, 2),
|
|
134
|
-
"utf-8"
|
|
135
|
-
);
|
|
136
|
-
} catch (err) {
|
|
137
|
-
console.error("Failed to save config:", err);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
configManager = ConfigManager.getInstance();
|
|
142
|
-
ConfigFileAuthBackend = class {
|
|
143
|
-
constructor(configPath) {
|
|
144
|
-
this.configPath = configPath;
|
|
145
|
-
}
|
|
146
|
-
ensureFile() {
|
|
147
|
-
const dir = path5.dirname(this.configPath);
|
|
148
|
-
if (!fs5.existsSync(dir)) {
|
|
149
|
-
fs5.mkdirSync(dir, { recursive: true });
|
|
150
|
-
}
|
|
151
|
-
if (!fs5.existsSync(this.configPath)) {
|
|
152
|
-
fs5.writeFileSync(this.configPath, "{}", "utf-8");
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
readAuthJson() {
|
|
156
|
-
this.ensureFile();
|
|
157
|
-
try {
|
|
158
|
-
const raw = fs5.readFileSync(this.configPath, "utf-8");
|
|
159
|
-
const config = JSON.parse(raw);
|
|
160
|
-
if (config._auth && typeof config._auth === "object") {
|
|
161
|
-
return JSON.stringify(config._auth);
|
|
162
|
-
}
|
|
163
|
-
return void 0;
|
|
164
|
-
} catch {
|
|
165
|
-
return void 0;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
writeAuthJson(authJson) {
|
|
169
|
-
this.ensureFile();
|
|
170
|
-
try {
|
|
171
|
-
const raw = fs5.readFileSync(this.configPath, "utf-8");
|
|
172
|
-
const config = JSON.parse(raw);
|
|
173
|
-
config._auth = JSON.parse(authJson);
|
|
174
|
-
fs5.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
175
|
-
} catch {
|
|
176
|
-
const config = { _auth: JSON.parse(authJson) };
|
|
177
|
-
fs5.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
withLock(fn) {
|
|
181
|
-
const current = this.readAuthJson();
|
|
182
|
-
const { result, next } = fn(current);
|
|
183
|
-
if (next !== void 0) {
|
|
184
|
-
this.writeAuthJson(next);
|
|
185
|
-
}
|
|
186
|
-
return result;
|
|
187
|
-
}
|
|
188
|
-
async withLockAsync(fn) {
|
|
189
|
-
const current = this.readAuthJson();
|
|
190
|
-
const { result, next } = await fn(current);
|
|
191
|
-
if (next !== void 0) {
|
|
192
|
-
this.writeAuthJson(next);
|
|
193
|
-
}
|
|
194
|
-
return result;
|
|
195
|
-
}
|
|
196
|
-
};
|
|
97
|
+
...job.enabled !== void 0 ? { enabled: job.enabled } : {},
|
|
98
|
+
...job.timezone !== void 0 ? { timezone: job.timezone.trim() } : {}
|
|
99
|
+
}))
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function loadJobFile(workDir) {
|
|
103
|
+
const filePath = getJobFilePath(workDir);
|
|
104
|
+
if (!fs2.existsSync(filePath)) {
|
|
105
|
+
return { jobs: [] };
|
|
106
|
+
}
|
|
107
|
+
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
108
|
+
const parsed = JSON.parse(raw);
|
|
109
|
+
validateJobFileShape(parsed, filePath);
|
|
110
|
+
return normalizeJobFile(parsed);
|
|
111
|
+
}
|
|
112
|
+
function saveJobFile(workDir, jobFile) {
|
|
113
|
+
const filePath = getJobFilePath(workDir);
|
|
114
|
+
const normalized = normalizeJobFile(jobFile);
|
|
115
|
+
validateJobFileShape(normalized, filePath);
|
|
116
|
+
fs2.writeFileSync(filePath, JSON.stringify(normalized, null, 2) + "\n", "utf-8");
|
|
117
|
+
}
|
|
118
|
+
var JOB_FILE;
|
|
119
|
+
var init_job_config = __esm({
|
|
120
|
+
"src/job-config.ts"() {
|
|
121
|
+
"use strict";
|
|
122
|
+
JOB_FILE = "job.json";
|
|
197
123
|
}
|
|
198
124
|
});
|
|
199
125
|
|
|
200
126
|
// src/runtime/adapters/attachment-utils.ts
|
|
201
|
-
import
|
|
202
|
-
import
|
|
127
|
+
import fs7 from "fs";
|
|
128
|
+
import path7 from "path";
|
|
203
129
|
import { pipeline } from "stream/promises";
|
|
204
130
|
import { Readable } from "stream";
|
|
205
131
|
function getAttachmentDir(rootDir, channelId) {
|
|
206
|
-
return
|
|
132
|
+
return path7.resolve(rootDir, "data", "sessions", channelId, ATTACHMENTS_DIR);
|
|
207
133
|
}
|
|
208
134
|
function sanitizeFilename(name) {
|
|
209
135
|
return name.replace(/[/\\:*?"<>|]/g, "_").replace(/\s+/g, "_");
|
|
210
136
|
}
|
|
211
137
|
async function downloadAndSaveAttachment(rootDir, channelId, url, filename, mimeType, headers) {
|
|
212
138
|
const dir = getAttachmentDir(rootDir, channelId);
|
|
213
|
-
|
|
139
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
214
140
|
const ts = Date.now();
|
|
215
141
|
const safeName = sanitizeFilename(filename);
|
|
216
142
|
const storedName = `${ts}-${safeName}`;
|
|
217
|
-
const fullPath =
|
|
143
|
+
const fullPath = path7.join(dir, storedName);
|
|
218
144
|
const response = await fetch(url, { headers });
|
|
219
145
|
if (!response.ok) {
|
|
220
146
|
throw new Error(
|
|
@@ -226,9 +152,9 @@ async function downloadAndSaveAttachment(rootDir, channelId, url, filename, mime
|
|
|
226
152
|
throw new Error(`Empty response body when downloading ${url}`);
|
|
227
153
|
}
|
|
228
154
|
const nodeStream = Readable.fromWeb(body);
|
|
229
|
-
const writeStream =
|
|
155
|
+
const writeStream = fs7.createWriteStream(fullPath);
|
|
230
156
|
await pipeline(nodeStream, writeStream);
|
|
231
|
-
const stats =
|
|
157
|
+
const stats = fs7.statSync(fullPath);
|
|
232
158
|
const detectedMime = mimeType || response.headers.get("content-type")?.split(";")[0] || void 0;
|
|
233
159
|
return {
|
|
234
160
|
filename,
|
|
@@ -257,7 +183,7 @@ ${lines.join("\n")}`;
|
|
|
257
183
|
}
|
|
258
184
|
function attachmentsToImageContent(attachments) {
|
|
259
185
|
return attachments.filter((a) => isImageMime(a.mimeType)).map((a) => {
|
|
260
|
-
const buffer =
|
|
186
|
+
const buffer = fs7.readFileSync(a.localPath);
|
|
261
187
|
return {
|
|
262
188
|
type: "image",
|
|
263
189
|
data: buffer.toString("base64"),
|
|
@@ -463,7 +389,7 @@ var telegram_exports = {};
|
|
|
463
389
|
__export(telegram_exports, {
|
|
464
390
|
TelegramAdapter: () => TelegramAdapter
|
|
465
391
|
});
|
|
466
|
-
import
|
|
392
|
+
import fs17 from "fs";
|
|
467
393
|
import TelegramBot from "node-telegram-bot-api";
|
|
468
394
|
var MAX_MESSAGE_LENGTH, ACK_REACTION, TelegramAdapter;
|
|
469
395
|
var init_telegram = __esm({
|
|
@@ -781,7 +707,7 @@ var init_telegram = __esm({
|
|
|
781
707
|
async sendFileSafe(chatId, filePath, caption) {
|
|
782
708
|
if (!this.bot) return;
|
|
783
709
|
try {
|
|
784
|
-
if (!
|
|
710
|
+
if (!fs17.existsSync(filePath)) {
|
|
785
711
|
console.error(`[Telegram] File not found for sending: ${filePath}`);
|
|
786
712
|
return;
|
|
787
713
|
}
|
|
@@ -801,8 +727,8 @@ var slack_exports = {};
|
|
|
801
727
|
__export(slack_exports, {
|
|
802
728
|
SlackAdapter: () => SlackAdapter
|
|
803
729
|
});
|
|
804
|
-
import
|
|
805
|
-
import
|
|
730
|
+
import fs18 from "fs";
|
|
731
|
+
import path17 from "path";
|
|
806
732
|
import { App, LogLevel } from "@slack/bolt";
|
|
807
733
|
var INLINE_COMMANDS, SLASH_COMMANDS, MAX_MESSAGE_LENGTH2, ACK_REACTION2, PROCESSING_MESSAGE, SlackAdapter;
|
|
808
734
|
var init_slack = __esm({
|
|
@@ -1437,12 +1363,12 @@ var init_slack = __esm({
|
|
|
1437
1363
|
*/
|
|
1438
1364
|
async sendFileSafe(client, route, filePath, caption) {
|
|
1439
1365
|
try {
|
|
1440
|
-
if (!
|
|
1366
|
+
if (!fs18.existsSync(filePath)) {
|
|
1441
1367
|
console.error(`[Slack] File not found for sending: ${filePath}`);
|
|
1442
1368
|
return;
|
|
1443
1369
|
}
|
|
1444
|
-
const filename =
|
|
1445
|
-
const fileContent =
|
|
1370
|
+
const filename = path17.basename(filePath);
|
|
1371
|
+
const fileContent = fs18.readFileSync(filePath);
|
|
1446
1372
|
await client.files.uploadV2({
|
|
1447
1373
|
channel_id: route.channel,
|
|
1448
1374
|
thread_ts: route.threadTs,
|
|
@@ -1480,7 +1406,7 @@ var VALID_JOB_NAME, SchedulerAdapter;
|
|
|
1480
1406
|
var init_scheduler = __esm({
|
|
1481
1407
|
"src/runtime/adapters/scheduler.ts"() {
|
|
1482
1408
|
"use strict";
|
|
1483
|
-
|
|
1409
|
+
init_job_config();
|
|
1484
1410
|
VALID_JOB_NAME = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
|
|
1485
1411
|
SchedulerAdapter = class {
|
|
1486
1412
|
name = "scheduler";
|
|
@@ -1494,8 +1420,7 @@ var init_scheduler = __esm({
|
|
|
1494
1420
|
this.rootDir = ctx.rootDir;
|
|
1495
1421
|
this.notifyFn = ctx.notify || (async () => {
|
|
1496
1422
|
});
|
|
1497
|
-
const
|
|
1498
|
-
const jobConfigs = config.scheduledJobs || [];
|
|
1423
|
+
const jobConfigs = loadJobFile(this.rootDir).jobs;
|
|
1499
1424
|
let scheduledCount = 0;
|
|
1500
1425
|
let disabledCount = 0;
|
|
1501
1426
|
for (const jc of jobConfigs) {
|
|
@@ -1599,11 +1524,13 @@ var init_scheduler = __esm({
|
|
|
1599
1524
|
}
|
|
1600
1525
|
};
|
|
1601
1526
|
try {
|
|
1527
|
+
await this.clearJobContext(channelId);
|
|
1602
1528
|
const result = await this.agent.handleMessage(
|
|
1603
1529
|
"scheduler",
|
|
1604
1530
|
channelId,
|
|
1605
1531
|
jobConfig.prompt,
|
|
1606
|
-
onEvent
|
|
1532
|
+
onEvent,
|
|
1533
|
+
void 0
|
|
1607
1534
|
);
|
|
1608
1535
|
if (result.errorMessage) {
|
|
1609
1536
|
fullText = `\u274C \u5B9A\u65F6\u4EFB\u52A1 "${jobConfig.name}" \u6267\u884C\u5931\u8D25\uFF1A${result.errorMessage}`;
|
|
@@ -1648,11 +1575,17 @@ var init_scheduler = __esm({
|
|
|
1648
1575
|
}
|
|
1649
1576
|
return { text: fullText, notifyFailed };
|
|
1650
1577
|
}
|
|
1578
|
+
async clearJobContext(channelId) {
|
|
1579
|
+
const result = await this.agent.handleCommand("clear", channelId);
|
|
1580
|
+
if (!result.success) {
|
|
1581
|
+
throw new Error(result.message || `Failed to clear context for ${channelId}`);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1651
1584
|
// -------------------------------------------------------------------------
|
|
1652
1585
|
// Dynamic management API
|
|
1653
1586
|
// -------------------------------------------------------------------------
|
|
1654
1587
|
/**
|
|
1655
|
-
* Add a new job, persist to
|
|
1588
|
+
* Add a new job, persist to job.json.
|
|
1656
1589
|
*/
|
|
1657
1590
|
addJob(jobConfig) {
|
|
1658
1591
|
if (this.jobs.has(jobConfig.name)) {
|
|
@@ -1673,7 +1606,7 @@ var init_scheduler = __esm({
|
|
|
1673
1606
|
};
|
|
1674
1607
|
}
|
|
1675
1608
|
/**
|
|
1676
|
-
* Remove a job and persist to
|
|
1609
|
+
* Remove a job and persist to job.json.
|
|
1677
1610
|
*/
|
|
1678
1611
|
removeJob(name) {
|
|
1679
1612
|
if (!this.jobs.has(name)) {
|
|
@@ -1683,6 +1616,29 @@ var init_scheduler = __esm({
|
|
|
1683
1616
|
this.persistJobs();
|
|
1684
1617
|
return { success: true, message: `Job "${name}" removed.` };
|
|
1685
1618
|
}
|
|
1619
|
+
updateJob(name, updates) {
|
|
1620
|
+
const job = this.jobs.get(name);
|
|
1621
|
+
if (!job) {
|
|
1622
|
+
return { success: false, message: `Job "${name}" not found.` };
|
|
1623
|
+
}
|
|
1624
|
+
const nextConfig = {
|
|
1625
|
+
name,
|
|
1626
|
+
cron: updates.cron,
|
|
1627
|
+
prompt: updates.prompt,
|
|
1628
|
+
notify: updates.notify,
|
|
1629
|
+
enabled: updates.enabled,
|
|
1630
|
+
timezone: updates.timezone
|
|
1631
|
+
};
|
|
1632
|
+
const result = this.registerJob(nextConfig);
|
|
1633
|
+
if (!result.registered) {
|
|
1634
|
+
return { success: false, message: result.message };
|
|
1635
|
+
}
|
|
1636
|
+
this.persistJobs();
|
|
1637
|
+
return {
|
|
1638
|
+
success: true,
|
|
1639
|
+
message: `Job "${name}" updated.`
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1686
1642
|
/**
|
|
1687
1643
|
* Enable or disable a job and persist.
|
|
1688
1644
|
*/
|
|
@@ -1774,14 +1730,14 @@ var init_scheduler = __esm({
|
|
|
1774
1730
|
}
|
|
1775
1731
|
}
|
|
1776
1732
|
/**
|
|
1777
|
-
* Persist all current jobs to
|
|
1733
|
+
* Persist all current jobs to job.json.
|
|
1778
1734
|
*/
|
|
1779
1735
|
persistJobs() {
|
|
1780
1736
|
const configs = [];
|
|
1781
1737
|
for (const [, job] of this.jobs) {
|
|
1782
1738
|
configs.push(job.config);
|
|
1783
1739
|
}
|
|
1784
|
-
|
|
1740
|
+
saveJobFile(this.rootDir, { jobs: configs });
|
|
1785
1741
|
}
|
|
1786
1742
|
// -------------------------------------------------------------------------
|
|
1787
1743
|
// Lifecycle
|
|
@@ -1802,8 +1758,8 @@ import { Command } from "commander";
|
|
|
1802
1758
|
import chalk5 from "chalk";
|
|
1803
1759
|
|
|
1804
1760
|
// src/commands/create.ts
|
|
1805
|
-
import
|
|
1806
|
-
import
|
|
1761
|
+
import fs5 from "fs";
|
|
1762
|
+
import path5 from "path";
|
|
1807
1763
|
import inquirer from "inquirer";
|
|
1808
1764
|
import chalk3 from "chalk";
|
|
1809
1765
|
|
|
@@ -1911,22 +1867,23 @@ function configExists(workDir) {
|
|
|
1911
1867
|
}
|
|
1912
1868
|
|
|
1913
1869
|
// src/commands/zip.ts
|
|
1914
|
-
import
|
|
1915
|
-
import
|
|
1870
|
+
import fs4 from "fs";
|
|
1871
|
+
import path4 from "path";
|
|
1916
1872
|
import archiver from "archiver";
|
|
1917
1873
|
import chalk2 from "chalk";
|
|
1874
|
+
init_job_config();
|
|
1918
1875
|
|
|
1919
1876
|
// src/skill-manager.ts
|
|
1920
1877
|
import { spawnSync } from "child_process";
|
|
1921
|
-
import
|
|
1922
|
-
import
|
|
1878
|
+
import fs3 from "fs";
|
|
1879
|
+
import path3 from "path";
|
|
1923
1880
|
import chalk from "chalk";
|
|
1924
1881
|
var SKILLS_DIR = "skills";
|
|
1925
1882
|
function normalizeName(value) {
|
|
1926
1883
|
return value.trim().toLowerCase();
|
|
1927
1884
|
}
|
|
1928
1885
|
function getSkillsDir(workDir) {
|
|
1929
|
-
return
|
|
1886
|
+
return path3.join(workDir, SKILLS_DIR);
|
|
1930
1887
|
}
|
|
1931
1888
|
function groupSkillsBySource(skills) {
|
|
1932
1889
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -1983,13 +1940,13 @@ function installSkills(workDir, skills) {
|
|
|
1983
1940
|
function scanInstalledSkills(workDir) {
|
|
1984
1941
|
const installed = [];
|
|
1985
1942
|
const skillsDir = getSkillsDir(workDir);
|
|
1986
|
-
if (!
|
|
1943
|
+
if (!fs3.existsSync(skillsDir)) {
|
|
1987
1944
|
return installed;
|
|
1988
1945
|
}
|
|
1989
1946
|
function visit(dir) {
|
|
1990
|
-
const entries =
|
|
1947
|
+
const entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
1991
1948
|
for (const entry of entries) {
|
|
1992
|
-
const fullPath =
|
|
1949
|
+
const fullPath = path3.join(dir, entry.name);
|
|
1993
1950
|
if (entry.isDirectory()) {
|
|
1994
1951
|
visit(fullPath);
|
|
1995
1952
|
continue;
|
|
@@ -2008,7 +1965,7 @@ function scanInstalledSkills(workDir) {
|
|
|
2008
1965
|
}
|
|
2009
1966
|
function parseSkillMd(filePath) {
|
|
2010
1967
|
try {
|
|
2011
|
-
const content =
|
|
1968
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
2012
1969
|
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
2013
1970
|
if (!frontmatterMatch) {
|
|
2014
1971
|
return null;
|
|
@@ -2021,7 +1978,7 @@ function parseSkillMd(filePath) {
|
|
|
2021
1978
|
return {
|
|
2022
1979
|
name,
|
|
2023
1980
|
description: readFrontmatterField(frontmatter, "description") ?? "",
|
|
2024
|
-
dir:
|
|
1981
|
+
dir: path3.dirname(filePath)
|
|
2025
1982
|
};
|
|
2026
1983
|
} catch {
|
|
2027
1984
|
return null;
|
|
@@ -2185,12 +2142,12 @@ async function zipCommand(workDir) {
|
|
|
2185
2142
|
const config = loadConfig(workDir);
|
|
2186
2143
|
const slug = config.name.toLowerCase().replace(/\s+/g, "-");
|
|
2187
2144
|
const zipName = `${slug}.zip`;
|
|
2188
|
-
const zipPath =
|
|
2145
|
+
const zipPath = path4.join(workDir, zipName);
|
|
2189
2146
|
installConfiguredSkills(workDir, config);
|
|
2190
2147
|
syncSkillDescriptions(workDir, config);
|
|
2191
2148
|
saveConfig(workDir, config);
|
|
2192
2149
|
console.log(chalk2.blue(`Packaging ${config.name}...`));
|
|
2193
|
-
const output =
|
|
2150
|
+
const output = fs4.createWriteStream(zipPath);
|
|
2194
2151
|
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
2195
2152
|
return new Promise((resolve, reject) => {
|
|
2196
2153
|
output.on("close", () => {
|
|
@@ -2207,22 +2164,26 @@ async function zipCommand(workDir) {
|
|
|
2207
2164
|
archive.file(getPackPath(workDir), {
|
|
2208
2165
|
name: `${prefix}/${PACK_FILE}`
|
|
2209
2166
|
});
|
|
2167
|
+
const jobFilePath = getJobFilePath(workDir);
|
|
2168
|
+
if (fs4.existsSync(jobFilePath)) {
|
|
2169
|
+
archive.file(jobFilePath, { name: `${prefix}/${JOB_FILE}` });
|
|
2170
|
+
}
|
|
2210
2171
|
for (const file of ["AGENTS.md", "SOUL.md"]) {
|
|
2211
|
-
const filePath =
|
|
2212
|
-
if (
|
|
2172
|
+
const filePath = path4.join(workDir, file);
|
|
2173
|
+
if (fs4.existsSync(filePath)) {
|
|
2213
2174
|
archive.file(filePath, { name: `${prefix}/${file}` });
|
|
2214
2175
|
}
|
|
2215
2176
|
}
|
|
2216
|
-
const skillsDir =
|
|
2217
|
-
if (
|
|
2177
|
+
const skillsDir = path4.join(workDir, "skills");
|
|
2178
|
+
if (fs4.existsSync(skillsDir)) {
|
|
2218
2179
|
archive.directory(skillsDir, `${prefix}/skills`);
|
|
2219
2180
|
}
|
|
2220
|
-
const startSh =
|
|
2221
|
-
if (
|
|
2181
|
+
const startSh = path4.join(workDir, "start.sh");
|
|
2182
|
+
if (fs4.existsSync(startSh)) {
|
|
2222
2183
|
archive.file(startSh, { name: `${prefix}/start.sh`, mode: 493 });
|
|
2223
2184
|
}
|
|
2224
|
-
const startBat =
|
|
2225
|
-
if (
|
|
2185
|
+
const startBat = path4.join(workDir, "start.bat");
|
|
2186
|
+
if (fs4.existsSync(startBat)) {
|
|
2226
2187
|
archive.file(startBat, { name: `${prefix}/start.bat` });
|
|
2227
2188
|
}
|
|
2228
2189
|
archive.finalize();
|
|
@@ -2271,24 +2232,24 @@ async function readConfigSource(source) {
|
|
|
2271
2232
|
}
|
|
2272
2233
|
raw = await response.text();
|
|
2273
2234
|
} else {
|
|
2274
|
-
const filePath =
|
|
2275
|
-
raw =
|
|
2235
|
+
const filePath = path5.resolve(source);
|
|
2236
|
+
raw = fs5.readFileSync(filePath, "utf-8");
|
|
2276
2237
|
}
|
|
2277
2238
|
const parsed = JSON.parse(raw);
|
|
2278
2239
|
validateConfigShape(parsed, source);
|
|
2279
2240
|
return parsed;
|
|
2280
2241
|
}
|
|
2281
2242
|
function copyStartTemplates(workDir) {
|
|
2282
|
-
const templateDir =
|
|
2243
|
+
const templateDir = path5.resolve(
|
|
2283
2244
|
new URL("../templates", import.meta.url).pathname
|
|
2284
2245
|
);
|
|
2285
2246
|
for (const file of ["start.sh", "start.bat"]) {
|
|
2286
|
-
const src =
|
|
2287
|
-
const dest =
|
|
2288
|
-
if (
|
|
2289
|
-
|
|
2247
|
+
const src = path5.join(templateDir, file);
|
|
2248
|
+
const dest = path5.join(workDir, file);
|
|
2249
|
+
if (fs5.existsSync(src)) {
|
|
2250
|
+
fs5.copyFileSync(src, dest);
|
|
2290
2251
|
if (file === "start.sh") {
|
|
2291
|
-
|
|
2252
|
+
fs5.chmodSync(dest, 493);
|
|
2292
2253
|
}
|
|
2293
2254
|
} else {
|
|
2294
2255
|
console.warn(chalk3.yellow(` [warn] Template not found: ${src}`));
|
|
@@ -2296,9 +2257,9 @@ function copyStartTemplates(workDir) {
|
|
|
2296
2257
|
}
|
|
2297
2258
|
}
|
|
2298
2259
|
async function createCommand(directory, options = {}) {
|
|
2299
|
-
const workDir = directory ?
|
|
2260
|
+
const workDir = directory ? path5.resolve(directory) : process.cwd();
|
|
2300
2261
|
if (directory) {
|
|
2301
|
-
|
|
2262
|
+
fs5.mkdirSync(workDir, { recursive: true });
|
|
2302
2263
|
}
|
|
2303
2264
|
if (options.config) {
|
|
2304
2265
|
await initFromConfig(workDir, options.config);
|
|
@@ -2469,24 +2430,23 @@ async function interactiveCreate(workDir) {
|
|
|
2469
2430
|
}
|
|
2470
2431
|
|
|
2471
2432
|
// src/commands/run.ts
|
|
2472
|
-
import
|
|
2473
|
-
import
|
|
2433
|
+
import path19 from "path";
|
|
2434
|
+
import fs20 from "fs";
|
|
2474
2435
|
import inquirer2 from "inquirer";
|
|
2475
2436
|
import chalk4 from "chalk";
|
|
2476
2437
|
|
|
2477
2438
|
// src/runtime/server.ts
|
|
2478
2439
|
import express from "express";
|
|
2479
|
-
import
|
|
2480
|
-
import
|
|
2440
|
+
import path18 from "path";
|
|
2441
|
+
import fs19 from "fs";
|
|
2481
2442
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2482
2443
|
import { createServer } from "http";
|
|
2483
2444
|
import { exec } from "child_process";
|
|
2484
2445
|
|
|
2485
2446
|
// src/runtime/agent.ts
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
import
|
|
2489
|
-
import fs9 from "fs";
|
|
2447
|
+
import path13 from "path";
|
|
2448
|
+
import fs13 from "fs";
|
|
2449
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2490
2450
|
import { fileURLToPath } from "url";
|
|
2491
2451
|
import {
|
|
2492
2452
|
AuthStorage,
|
|
@@ -2497,20 +2457,260 @@ import {
|
|
|
2497
2457
|
DefaultResourceLoader
|
|
2498
2458
|
} from "@mariozechner/pi-coding-agent";
|
|
2499
2459
|
|
|
2500
|
-
// src/runtime/
|
|
2501
|
-
import
|
|
2502
|
-
import
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2460
|
+
// src/runtime/config.ts
|
|
2461
|
+
import fs6 from "fs";
|
|
2462
|
+
import path6 from "path";
|
|
2463
|
+
var SUPPORTED_PROVIDERS = {
|
|
2464
|
+
openai: {
|
|
2465
|
+
label: "OpenAI",
|
|
2466
|
+
defaultModelId: "gpt-5.4",
|
|
2467
|
+
authType: "api_key",
|
|
2468
|
+
envKey: "OPENAI_API_KEY",
|
|
2469
|
+
placeholder: "sk-proj-...",
|
|
2470
|
+
baseUrlPlaceholder: "https://api.openai.com/v1",
|
|
2471
|
+
supportsBaseUrl: true
|
|
2472
|
+
},
|
|
2473
|
+
anthropic: {
|
|
2474
|
+
label: "Anthropic",
|
|
2475
|
+
defaultModelId: "claude-opus-4-6",
|
|
2476
|
+
authType: "api_key",
|
|
2477
|
+
envKey: "ANTHROPIC_API_KEY",
|
|
2478
|
+
placeholder: "sk-ant-api03-...",
|
|
2479
|
+
baseUrlPlaceholder: "https://api.anthropic.com",
|
|
2480
|
+
supportsBaseUrl: true
|
|
2481
|
+
},
|
|
2482
|
+
google: {
|
|
2483
|
+
label: "Google (Gemini)",
|
|
2484
|
+
defaultModelId: "gemini-2.5-pro",
|
|
2485
|
+
authType: "api_key",
|
|
2486
|
+
envKey: "GOOGLE_API_KEY",
|
|
2487
|
+
placeholder: "AIza...",
|
|
2488
|
+
supportsBaseUrl: false
|
|
2489
|
+
},
|
|
2490
|
+
"openai-codex": {
|
|
2491
|
+
label: "OpenAI Codex",
|
|
2492
|
+
defaultModelId: "gpt-5.4",
|
|
2493
|
+
authType: "oauth",
|
|
2494
|
+
oauthProviderId: "openai-codex",
|
|
2495
|
+
supportsBaseUrl: false
|
|
2496
|
+
}
|
|
2497
|
+
};
|
|
2498
|
+
function normalizeDataConfig(value) {
|
|
2499
|
+
if (!value || typeof value !== "object") {
|
|
2500
|
+
return {};
|
|
2501
|
+
}
|
|
2502
|
+
const raw = value;
|
|
2503
|
+
const normalized = {};
|
|
2504
|
+
if (typeof raw.apiKey === "string") {
|
|
2505
|
+
normalized.apiKey = raw.apiKey;
|
|
2506
|
+
}
|
|
2507
|
+
if (typeof raw.provider === "string") {
|
|
2508
|
+
normalized.provider = raw.provider;
|
|
2509
|
+
}
|
|
2510
|
+
if (typeof raw.baseUrl === "string") {
|
|
2511
|
+
normalized.baseUrl = raw.baseUrl;
|
|
2512
|
+
}
|
|
2513
|
+
if (typeof raw.modelId === "string") {
|
|
2514
|
+
normalized.modelId = raw.modelId;
|
|
2515
|
+
}
|
|
2516
|
+
if (raw.apiProtocol === "openai-responses" || raw.apiProtocol === "openai-completions") {
|
|
2517
|
+
normalized.apiProtocol = raw.apiProtocol;
|
|
2518
|
+
}
|
|
2519
|
+
if (raw.adapters && typeof raw.adapters === "object" && !Array.isArray(raw.adapters)) {
|
|
2520
|
+
normalized.adapters = raw.adapters;
|
|
2521
|
+
}
|
|
2522
|
+
if (raw._auth && typeof raw._auth === "object" && !Array.isArray(raw._auth)) {
|
|
2523
|
+
normalized._auth = raw._auth;
|
|
2524
|
+
}
|
|
2525
|
+
return normalized;
|
|
2526
|
+
}
|
|
2527
|
+
var ConfigManager = class _ConfigManager {
|
|
2528
|
+
static instance;
|
|
2529
|
+
configData = {};
|
|
2530
|
+
configPath = "";
|
|
2531
|
+
constructor() {
|
|
2532
|
+
}
|
|
2533
|
+
static getInstance() {
|
|
2534
|
+
if (!_ConfigManager.instance) {
|
|
2535
|
+
_ConfigManager.instance = new _ConfigManager();
|
|
2536
|
+
}
|
|
2537
|
+
return _ConfigManager.instance;
|
|
2538
|
+
}
|
|
2539
|
+
load(rootDir) {
|
|
2540
|
+
this.configPath = path6.join(rootDir, "data", "config.json");
|
|
2541
|
+
this.configData = {};
|
|
2542
|
+
if (fs6.existsSync(this.configPath)) {
|
|
2543
|
+
try {
|
|
2544
|
+
const parsed = JSON.parse(fs6.readFileSync(this.configPath, "utf-8"));
|
|
2545
|
+
if (parsed && typeof parsed === "object" && "scheduledJobs" in parsed) {
|
|
2546
|
+
console.warn(
|
|
2547
|
+
' Warning: data/config.json contains deprecated "scheduledJobs". Move them to job.json at the pack root; the old field is ignored.'
|
|
2548
|
+
);
|
|
2549
|
+
}
|
|
2550
|
+
this.configData = normalizeDataConfig(parsed);
|
|
2551
|
+
console.log(" Loaded config from data/config.json");
|
|
2552
|
+
} catch (err) {
|
|
2553
|
+
console.warn(" Warning: Failed to parse data/config.json:", err);
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
let { apiKey = "", provider = "openai", baseUrl = "" } = this.configData;
|
|
2557
|
+
if (!apiKey) {
|
|
2558
|
+
if (process.env.OPENAI_API_KEY) {
|
|
2559
|
+
apiKey = process.env.OPENAI_API_KEY;
|
|
2560
|
+
provider = "openai";
|
|
2561
|
+
} else if (process.env.ANTHROPIC_API_KEY) {
|
|
2562
|
+
apiKey = process.env.ANTHROPIC_API_KEY;
|
|
2563
|
+
provider = "anthropic";
|
|
2564
|
+
} else if (process.env.GOOGLE_API_KEY) {
|
|
2565
|
+
apiKey = process.env.GOOGLE_API_KEY;
|
|
2566
|
+
provider = "google";
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
this.configData.apiKey = apiKey;
|
|
2570
|
+
this.configData.provider = provider;
|
|
2571
|
+
this.configData.baseUrl = baseUrl?.trim() || void 0;
|
|
2572
|
+
return this.configData;
|
|
2573
|
+
}
|
|
2574
|
+
getConfig() {
|
|
2575
|
+
return this.configData;
|
|
2576
|
+
}
|
|
2577
|
+
save(rootDir, updates) {
|
|
2578
|
+
const configDir = path6.join(rootDir, "data");
|
|
2579
|
+
if (!this.configPath) {
|
|
2580
|
+
this.configPath = path6.join(rootDir, "data", "config.json");
|
|
2581
|
+
}
|
|
2582
|
+
if (!fs6.existsSync(configDir)) {
|
|
2583
|
+
fs6.mkdirSync(configDir, { recursive: true });
|
|
2584
|
+
}
|
|
2585
|
+
if (updates.apiKey !== void 0) this.configData.apiKey = updates.apiKey;
|
|
2586
|
+
if (updates.provider !== void 0) this.configData.provider = updates.provider;
|
|
2587
|
+
if (updates.baseUrl !== void 0) {
|
|
2588
|
+
this.configData.baseUrl = updates.baseUrl?.trim() || void 0;
|
|
2589
|
+
}
|
|
2590
|
+
if (updates.modelId !== void 0) {
|
|
2591
|
+
this.configData.modelId = updates.modelId?.trim() || void 0;
|
|
2592
|
+
}
|
|
2593
|
+
if (updates.apiProtocol !== void 0) {
|
|
2594
|
+
this.configData.apiProtocol = updates.apiProtocol || void 0;
|
|
2595
|
+
}
|
|
2596
|
+
if (updates.adapters !== void 0) {
|
|
2597
|
+
const merged = { ...this.configData.adapters || {} };
|
|
2598
|
+
for (const [adapterKey, adapterVal] of Object.entries(updates.adapters)) {
|
|
2599
|
+
if (adapterVal === null || adapterVal === void 0) {
|
|
2600
|
+
delete merged[adapterKey];
|
|
2601
|
+
} else {
|
|
2602
|
+
merged[adapterKey] = adapterVal;
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
this.configData.adapters = merged;
|
|
2606
|
+
}
|
|
2607
|
+
try {
|
|
2608
|
+
this.configData = normalizeDataConfig(this.configData);
|
|
2609
|
+
fs6.writeFileSync(
|
|
2610
|
+
this.configPath,
|
|
2611
|
+
JSON.stringify(this.configData, null, 2),
|
|
2612
|
+
"utf-8"
|
|
2613
|
+
);
|
|
2614
|
+
} catch (err) {
|
|
2615
|
+
console.error("Failed to save config:", err);
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
};
|
|
2619
|
+
var configManager = ConfigManager.getInstance();
|
|
2620
|
+
var ConfigFileAuthBackend = class {
|
|
2621
|
+
constructor(configPath) {
|
|
2622
|
+
this.configPath = configPath;
|
|
2623
|
+
}
|
|
2624
|
+
ensureFile() {
|
|
2625
|
+
const dir = path6.dirname(this.configPath);
|
|
2626
|
+
if (!fs6.existsSync(dir)) {
|
|
2627
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
2628
|
+
}
|
|
2629
|
+
if (!fs6.existsSync(this.configPath)) {
|
|
2630
|
+
fs6.writeFileSync(this.configPath, "{}", "utf-8");
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
readAuthJson() {
|
|
2634
|
+
this.ensureFile();
|
|
2635
|
+
try {
|
|
2636
|
+
const raw = fs6.readFileSync(this.configPath, "utf-8");
|
|
2637
|
+
const config = JSON.parse(raw);
|
|
2638
|
+
if (config._auth && typeof config._auth === "object") {
|
|
2639
|
+
return JSON.stringify(config._auth);
|
|
2640
|
+
}
|
|
2641
|
+
return void 0;
|
|
2642
|
+
} catch {
|
|
2643
|
+
return void 0;
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
writeAuthJson(authJson) {
|
|
2647
|
+
this.ensureFile();
|
|
2648
|
+
try {
|
|
2649
|
+
const raw = fs6.readFileSync(this.configPath, "utf-8");
|
|
2650
|
+
const config = JSON.parse(raw);
|
|
2651
|
+
config._auth = JSON.parse(authJson);
|
|
2652
|
+
fs6.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
2653
|
+
} catch {
|
|
2654
|
+
const config = { _auth: JSON.parse(authJson) };
|
|
2655
|
+
fs6.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
withLock(fn) {
|
|
2659
|
+
const current = this.readAuthJson();
|
|
2660
|
+
const { result, next } = fn(current);
|
|
2661
|
+
if (next !== void 0) {
|
|
2662
|
+
this.writeAuthJson(next);
|
|
2663
|
+
}
|
|
2664
|
+
return result;
|
|
2665
|
+
}
|
|
2666
|
+
async withLockAsync(fn) {
|
|
2667
|
+
const current = this.readAuthJson();
|
|
2668
|
+
const { result, next } = await fn(current);
|
|
2669
|
+
if (next !== void 0) {
|
|
2670
|
+
this.writeAuthJson(next);
|
|
2671
|
+
}
|
|
2672
|
+
return result;
|
|
2673
|
+
}
|
|
2674
|
+
};
|
|
2675
|
+
|
|
2676
|
+
// src/runtime/agent.ts
|
|
2677
|
+
init_attachment_utils();
|
|
2678
|
+
|
|
2679
|
+
// src/runtime/artifacts/query-service.ts
|
|
2680
|
+
function clampLimit(limit, fallback, max) {
|
|
2681
|
+
if (!Number.isFinite(limit)) {
|
|
2682
|
+
return fallback;
|
|
2683
|
+
}
|
|
2684
|
+
const normalized = Math.floor(limit);
|
|
2685
|
+
return Math.max(1, Math.min(normalized, max));
|
|
2686
|
+
}
|
|
2687
|
+
function clampOffset(offset) {
|
|
2688
|
+
if (!Number.isFinite(offset)) {
|
|
2689
|
+
return 0;
|
|
2690
|
+
}
|
|
2691
|
+
return Math.max(0, Math.floor(offset));
|
|
2692
|
+
}
|
|
2693
|
+
var ResultsQueryService = class {
|
|
2694
|
+
constructor(resultStore) {
|
|
2695
|
+
this.resultStore = resultStore;
|
|
2696
|
+
}
|
|
2697
|
+
async listRecentArtifacts(options = {}) {
|
|
2698
|
+
return this.resultStore.listRecentArtifacts({
|
|
2699
|
+
channelId: options.channelId,
|
|
2700
|
+
limit: clampLimit(options.limit, 100, 500),
|
|
2701
|
+
offset: clampOffset(options.offset)
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
};
|
|
2705
|
+
|
|
2706
|
+
// src/runtime/artifacts/snapshot-service.ts
|
|
2707
|
+
import { randomUUID } from "crypto";
|
|
2708
|
+
import fs9 from "fs";
|
|
2709
|
+
import path9 from "path";
|
|
2710
|
+
|
|
2711
|
+
// src/runtime/files/metadata.ts
|
|
2712
|
+
import fs8 from "fs";
|
|
2713
|
+
import path8 from "path";
|
|
2514
2714
|
var MIME_BY_EXT = {
|
|
2515
2715
|
".png": "image/png",
|
|
2516
2716
|
".jpg": "image/jpeg",
|
|
@@ -2532,26 +2732,447 @@ var MIME_BY_EXT = {
|
|
|
2532
2732
|
".ogg": "audio/ogg"
|
|
2533
2733
|
};
|
|
2534
2734
|
function detectMimeType(filePath) {
|
|
2535
|
-
const ext =
|
|
2735
|
+
const ext = path8.extname(filePath).toLowerCase();
|
|
2536
2736
|
return MIME_BY_EXT[ext];
|
|
2537
2737
|
}
|
|
2538
|
-
function
|
|
2738
|
+
function isWithinDirectory(parentDir, targetPath) {
|
|
2739
|
+
const relativePath = path8.relative(path8.resolve(parentDir), path8.resolve(targetPath));
|
|
2740
|
+
return relativePath !== ".." && !relativePath.startsWith(`..${path8.sep}`) && !path8.isAbsolute(relativePath);
|
|
2741
|
+
}
|
|
2742
|
+
function toPackRelativePath(rootDir, filePath) {
|
|
2743
|
+
const resolvedRoot = path8.resolve(rootDir);
|
|
2744
|
+
const resolvedFile = path8.resolve(filePath);
|
|
2745
|
+
if (!isWithinDirectory(resolvedRoot, resolvedFile)) {
|
|
2746
|
+
throw new Error(`Path is outside the pack root: ${resolvedFile}`);
|
|
2747
|
+
}
|
|
2748
|
+
return path8.relative(resolvedRoot, resolvedFile).split(path8.sep).join("/");
|
|
2749
|
+
}
|
|
2750
|
+
function resolvePackFile(rootDir, filePath) {
|
|
2751
|
+
if (!path8.isAbsolute(filePath)) {
|
|
2752
|
+
throw new Error(`filePath must be absolute: ${filePath}`);
|
|
2753
|
+
}
|
|
2754
|
+
const resolvedPath = path8.resolve(filePath);
|
|
2755
|
+
if (!isWithinDirectory(rootDir, resolvedPath)) {
|
|
2756
|
+
throw new Error(`File is outside the pack root: ${resolvedPath}`);
|
|
2757
|
+
}
|
|
2758
|
+
if (!fs8.existsSync(resolvedPath)) {
|
|
2759
|
+
throw new Error(`File not found: ${resolvedPath}`);
|
|
2760
|
+
}
|
|
2761
|
+
const stats = fs8.statSync(resolvedPath);
|
|
2762
|
+
if (!stats.isFile()) {
|
|
2763
|
+
throw new Error(`Path is not a file: ${resolvedPath}`);
|
|
2764
|
+
}
|
|
2765
|
+
fs8.accessSync(resolvedPath, fs8.constants.R_OK);
|
|
2539
2766
|
return {
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2767
|
+
resolvedPath,
|
|
2768
|
+
fileName: path8.basename(resolvedPath),
|
|
2769
|
+
mimeType: detectMimeType(resolvedPath),
|
|
2770
|
+
sizeBytes: stats.size
|
|
2771
|
+
};
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
// src/runtime/artifacts/snapshot-service.ts
|
|
2775
|
+
function sanitizeFileName(fileName) {
|
|
2776
|
+
const sanitized = fileName.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
2777
|
+
return sanitized || "artifact";
|
|
2778
|
+
}
|
|
2779
|
+
function formatSnapshotStamp(isoDate) {
|
|
2780
|
+
const normalized = isoDate.replace(/\D+/g, "").slice(0, 14);
|
|
2781
|
+
return normalized || String(Date.now());
|
|
2782
|
+
}
|
|
2783
|
+
var ArtifactSnapshotService = class {
|
|
2784
|
+
constructor(rootDir) {
|
|
2785
|
+
this.rootDir = rootDir;
|
|
2786
|
+
}
|
|
2787
|
+
createSnapshots(runId, artifacts, declaredAt) {
|
|
2788
|
+
if (artifacts.length === 0) {
|
|
2789
|
+
return [];
|
|
2790
|
+
}
|
|
2791
|
+
const artifactsRoot = path9.resolve(this.rootDir, "data", "artifacts");
|
|
2792
|
+
const runDir = path9.join(artifactsRoot, runId);
|
|
2793
|
+
const tempDir = path9.join(
|
|
2794
|
+
artifactsRoot,
|
|
2795
|
+
`.tmp-${runId}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
|
2796
|
+
);
|
|
2797
|
+
const snapshots = [];
|
|
2798
|
+
const movedPaths = [];
|
|
2799
|
+
const snapshotNames = artifacts.map((artifact) => [
|
|
2800
|
+
formatSnapshotStamp(declaredAt),
|
|
2801
|
+
randomUUID(),
|
|
2802
|
+
sanitizeFileName(artifact.fileName)
|
|
2803
|
+
].join("-"));
|
|
2804
|
+
fs9.mkdirSync(artifactsRoot, { recursive: true });
|
|
2805
|
+
fs9.mkdirSync(tempDir, { recursive: true });
|
|
2806
|
+
try {
|
|
2807
|
+
snapshotNames.forEach((snapshotName, index) => {
|
|
2808
|
+
fs9.copyFileSync(
|
|
2809
|
+
artifacts[index].filePath,
|
|
2810
|
+
path9.join(tempDir, snapshotName)
|
|
2811
|
+
);
|
|
2812
|
+
});
|
|
2813
|
+
fs9.mkdirSync(runDir, { recursive: true });
|
|
2814
|
+
snapshotNames.forEach((snapshotName, index) => {
|
|
2815
|
+
const artifact = artifacts[index];
|
|
2816
|
+
const tempSnapshotPath = path9.join(tempDir, snapshotName);
|
|
2817
|
+
const finalSnapshotPath = path9.join(runDir, snapshotName);
|
|
2818
|
+
fs9.renameSync(tempSnapshotPath, finalSnapshotPath);
|
|
2819
|
+
movedPaths.push(finalSnapshotPath);
|
|
2820
|
+
snapshots.push({
|
|
2821
|
+
declaredAt,
|
|
2822
|
+
originalPath: toPackRelativePath(this.rootDir, artifact.filePath),
|
|
2823
|
+
snapshotPath: path9.join("data", "artifacts", runId, snapshotName).split(path9.sep).join("/"),
|
|
2824
|
+
fileName: artifact.fileName,
|
|
2825
|
+
mimeType: artifact.mimeType,
|
|
2826
|
+
sizeBytes: artifact.sizeBytes,
|
|
2827
|
+
title: artifact.title,
|
|
2828
|
+
isPrimary: artifact.isPrimary
|
|
2829
|
+
});
|
|
2830
|
+
});
|
|
2831
|
+
fs9.rmSync(tempDir, { recursive: true, force: true });
|
|
2832
|
+
return snapshots;
|
|
2833
|
+
} catch (error) {
|
|
2834
|
+
fs9.rmSync(tempDir, { recursive: true, force: true });
|
|
2835
|
+
movedPaths.forEach((filePath) => fs9.rmSync(filePath, { force: true }));
|
|
2836
|
+
this.removeEmptyRunDirectory(runDir);
|
|
2837
|
+
throw error;
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
removeSnapshots(snapshotPaths) {
|
|
2841
|
+
const visitedRunDirs = /* @__PURE__ */ new Set();
|
|
2842
|
+
for (const snapshotPath of snapshotPaths) {
|
|
2843
|
+
const resolvedPath = path9.resolve(this.rootDir, snapshotPath);
|
|
2844
|
+
fs9.rmSync(resolvedPath, { force: true });
|
|
2845
|
+
visitedRunDirs.add(path9.dirname(resolvedPath));
|
|
2846
|
+
}
|
|
2847
|
+
visitedRunDirs.forEach((runDir) => this.removeEmptyRunDirectory(runDir));
|
|
2848
|
+
}
|
|
2849
|
+
removeEmptyRunDirectory(runDir) {
|
|
2850
|
+
try {
|
|
2851
|
+
if (!fs9.existsSync(runDir)) {
|
|
2852
|
+
return;
|
|
2853
|
+
}
|
|
2854
|
+
if (fs9.readdirSync(runDir).length === 0) {
|
|
2855
|
+
fs9.rmdirSync(runDir);
|
|
2856
|
+
}
|
|
2857
|
+
} catch {
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
};
|
|
2861
|
+
|
|
2862
|
+
// src/runtime/artifacts/persistence-service.ts
|
|
2863
|
+
var ArtifactPersistenceService = class {
|
|
2864
|
+
constructor(snapshotService, resultStore) {
|
|
2865
|
+
this.snapshotService = snapshotService;
|
|
2866
|
+
this.resultStore = resultStore;
|
|
2867
|
+
}
|
|
2868
|
+
async saveArtifacts(input) {
|
|
2869
|
+
const declaredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2870
|
+
const snapshots = this.snapshotService.createSnapshots(
|
|
2871
|
+
input.runId,
|
|
2872
|
+
input.artifacts,
|
|
2873
|
+
declaredAt
|
|
2874
|
+
);
|
|
2875
|
+
try {
|
|
2876
|
+
await this.resultStore.insertArtifacts({
|
|
2877
|
+
runId: input.runId,
|
|
2878
|
+
channelId: input.channelId,
|
|
2879
|
+
artifacts: snapshots
|
|
2880
|
+
});
|
|
2881
|
+
return snapshots.length;
|
|
2882
|
+
} catch (error) {
|
|
2883
|
+
this.snapshotService.removeSnapshots(
|
|
2884
|
+
snapshots.map((artifact) => artifact.snapshotPath)
|
|
2885
|
+
);
|
|
2886
|
+
throw error;
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
};
|
|
2890
|
+
|
|
2891
|
+
// src/runtime/artifacts/store.ts
|
|
2892
|
+
import fs10 from "fs";
|
|
2893
|
+
import path10 from "path";
|
|
2894
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2895
|
+
import sqlite3 from "sqlite3";
|
|
2896
|
+
function mapArtifactRow(row) {
|
|
2897
|
+
return {
|
|
2898
|
+
artifactId: row.artifact_id,
|
|
2899
|
+
runId: row.run_id,
|
|
2900
|
+
channelId: row.channel_id,
|
|
2901
|
+
originalPath: row.original_path,
|
|
2902
|
+
snapshotPath: row.snapshot_path,
|
|
2903
|
+
fileName: row.file_name,
|
|
2904
|
+
mimeType: row.mime_type,
|
|
2905
|
+
sizeBytes: row.size_bytes,
|
|
2906
|
+
title: row.title,
|
|
2907
|
+
isPrimary: row.is_primary === 1,
|
|
2908
|
+
declaredAt: row.declared_at
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
var ResultStore = class {
|
|
2912
|
+
db = null;
|
|
2913
|
+
ready;
|
|
2914
|
+
constructor(rootDir) {
|
|
2915
|
+
const dataDir = path10.resolve(rootDir, "data");
|
|
2916
|
+
fs10.mkdirSync(dataDir, { recursive: true });
|
|
2917
|
+
this.ready = this.initialize(path10.join(dataDir, "result-v2.db"));
|
|
2918
|
+
}
|
|
2919
|
+
async initialize(databasePath) {
|
|
2920
|
+
this.db = await openDatabase(databasePath);
|
|
2921
|
+
await this.exec("PRAGMA journal_mode = WAL");
|
|
2922
|
+
await this.exec(`
|
|
2923
|
+
CREATE TABLE IF NOT EXISTS artifacts (
|
|
2924
|
+
artifact_id TEXT PRIMARY KEY,
|
|
2925
|
+
run_id TEXT NOT NULL,
|
|
2926
|
+
channel_id TEXT NOT NULL,
|
|
2927
|
+
original_path TEXT NOT NULL,
|
|
2928
|
+
snapshot_path TEXT NOT NULL,
|
|
2929
|
+
file_name TEXT NOT NULL,
|
|
2930
|
+
mime_type TEXT,
|
|
2931
|
+
size_bytes INTEGER NOT NULL,
|
|
2932
|
+
title TEXT,
|
|
2933
|
+
is_primary INTEGER NOT NULL DEFAULT 0,
|
|
2934
|
+
declared_at TEXT NOT NULL
|
|
2935
|
+
);
|
|
2936
|
+
|
|
2937
|
+
CREATE INDEX IF NOT EXISTS idx_artifacts_channel_declared_at
|
|
2938
|
+
ON artifacts(channel_id, declared_at DESC);
|
|
2939
|
+
`);
|
|
2940
|
+
}
|
|
2941
|
+
async insertArtifacts(input) {
|
|
2942
|
+
await this.ready;
|
|
2943
|
+
if (input.artifacts.length === 0) {
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2946
|
+
const insertArtifact = `
|
|
2947
|
+
INSERT INTO artifacts (
|
|
2948
|
+
artifact_id,
|
|
2949
|
+
run_id,
|
|
2950
|
+
channel_id,
|
|
2951
|
+
original_path,
|
|
2952
|
+
snapshot_path,
|
|
2953
|
+
file_name,
|
|
2954
|
+
mime_type,
|
|
2955
|
+
size_bytes,
|
|
2956
|
+
title,
|
|
2957
|
+
is_primary,
|
|
2958
|
+
declared_at
|
|
2959
|
+
) VALUES (
|
|
2960
|
+
?,
|
|
2961
|
+
?,
|
|
2962
|
+
?,
|
|
2963
|
+
?,
|
|
2964
|
+
?,
|
|
2965
|
+
?,
|
|
2966
|
+
?,
|
|
2967
|
+
?,
|
|
2968
|
+
?,
|
|
2969
|
+
?,
|
|
2970
|
+
?
|
|
2971
|
+
)
|
|
2972
|
+
`;
|
|
2973
|
+
await this.exec("BEGIN");
|
|
2974
|
+
try {
|
|
2975
|
+
for (const artifact of input.artifacts) {
|
|
2976
|
+
await this.run(insertArtifact, [
|
|
2977
|
+
randomUUID2(),
|
|
2978
|
+
input.runId,
|
|
2979
|
+
input.channelId,
|
|
2980
|
+
artifact.originalPath,
|
|
2981
|
+
artifact.snapshotPath,
|
|
2982
|
+
artifact.fileName,
|
|
2983
|
+
artifact.mimeType ?? null,
|
|
2984
|
+
artifact.sizeBytes,
|
|
2985
|
+
artifact.title ?? null,
|
|
2986
|
+
artifact.isPrimary ? 1 : 0,
|
|
2987
|
+
artifact.declaredAt
|
|
2988
|
+
]);
|
|
2989
|
+
}
|
|
2990
|
+
await this.exec("COMMIT");
|
|
2991
|
+
} catch (error) {
|
|
2992
|
+
await this.rollback();
|
|
2993
|
+
throw error;
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
async listRecentArtifacts(options = {}) {
|
|
2997
|
+
await this.ready;
|
|
2998
|
+
const limit = options.limit ?? 100;
|
|
2999
|
+
const offset = options.offset ?? 0;
|
|
3000
|
+
const conditions = [];
|
|
3001
|
+
const params = [];
|
|
3002
|
+
if (options.channelId) {
|
|
3003
|
+
conditions.push("channel_id = ?");
|
|
3004
|
+
params.push(options.channelId);
|
|
3005
|
+
}
|
|
3006
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3007
|
+
const rows = await this.all(`
|
|
3008
|
+
SELECT *
|
|
3009
|
+
FROM artifacts
|
|
3010
|
+
${whereClause}
|
|
3011
|
+
ORDER BY declared_at DESC, rowid DESC
|
|
3012
|
+
LIMIT ? OFFSET ?
|
|
3013
|
+
`, [...params, limit, offset]);
|
|
3014
|
+
return rows.map(mapArtifactRow);
|
|
3015
|
+
}
|
|
3016
|
+
getDatabase() {
|
|
3017
|
+
if (!this.db) {
|
|
3018
|
+
throw new Error("Result store database is not ready");
|
|
3019
|
+
}
|
|
3020
|
+
return this.db;
|
|
3021
|
+
}
|
|
3022
|
+
exec(sql) {
|
|
3023
|
+
const db = this.getDatabase();
|
|
3024
|
+
return new Promise((resolve, reject) => {
|
|
3025
|
+
db.exec(sql, (error) => {
|
|
3026
|
+
if (error) {
|
|
3027
|
+
reject(error);
|
|
3028
|
+
return;
|
|
3029
|
+
}
|
|
3030
|
+
resolve();
|
|
3031
|
+
});
|
|
3032
|
+
});
|
|
3033
|
+
}
|
|
3034
|
+
run(sql, params = []) {
|
|
3035
|
+
const db = this.getDatabase();
|
|
3036
|
+
return new Promise((resolve, reject) => {
|
|
3037
|
+
db.run(sql, params, (error) => {
|
|
3038
|
+
if (error) {
|
|
3039
|
+
reject(error);
|
|
3040
|
+
return;
|
|
3041
|
+
}
|
|
3042
|
+
resolve();
|
|
3043
|
+
});
|
|
3044
|
+
});
|
|
3045
|
+
}
|
|
3046
|
+
all(sql, params = []) {
|
|
3047
|
+
const db = this.getDatabase();
|
|
3048
|
+
return new Promise((resolve, reject) => {
|
|
3049
|
+
db.all(sql, params, (error, rows) => {
|
|
3050
|
+
if (error) {
|
|
3051
|
+
reject(error);
|
|
3052
|
+
return;
|
|
3053
|
+
}
|
|
3054
|
+
resolve(rows);
|
|
3055
|
+
});
|
|
3056
|
+
});
|
|
3057
|
+
}
|
|
3058
|
+
async rollback() {
|
|
3059
|
+
try {
|
|
3060
|
+
await this.exec("ROLLBACK");
|
|
3061
|
+
} catch {
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
};
|
|
3065
|
+
function openDatabase(databasePath) {
|
|
3066
|
+
return new Promise((resolve, reject) => {
|
|
3067
|
+
const db = new sqlite3.Database(databasePath, (error) => {
|
|
3068
|
+
if (error) {
|
|
3069
|
+
reject(error);
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
3072
|
+
resolve(db);
|
|
3073
|
+
});
|
|
3074
|
+
});
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
// src/runtime/artifacts/save-artifacts-tool.ts
|
|
3078
|
+
import { Type } from "@sinclair/typebox";
|
|
3079
|
+
var ArtifactItem = Type.Object({
|
|
3080
|
+
filePath: Type.String({
|
|
3081
|
+
description: "Absolute path to the artifact file. The file must exist, be readable, and be inside the current pack root."
|
|
3082
|
+
}),
|
|
3083
|
+
title: Type.Optional(
|
|
3084
|
+
Type.String({
|
|
3085
|
+
description: "Optional short title shown in the dashboard."
|
|
3086
|
+
})
|
|
3087
|
+
),
|
|
3088
|
+
isPrimary: Type.Optional(
|
|
3089
|
+
Type.Boolean({
|
|
3090
|
+
description: "Mark this artifact as a primary output."
|
|
3091
|
+
})
|
|
3092
|
+
)
|
|
3093
|
+
});
|
|
3094
|
+
var SaveArtifactsParams = Type.Object({
|
|
3095
|
+
artifacts: Type.Array(ArtifactItem, {
|
|
3096
|
+
minItems: 1,
|
|
3097
|
+
description: "The artifact files to save for this run."
|
|
3098
|
+
})
|
|
3099
|
+
});
|
|
3100
|
+
function textResult(text) {
|
|
3101
|
+
return { content: [{ type: "text", text }], details: void 0 };
|
|
3102
|
+
}
|
|
3103
|
+
function normalizeOptionalText(value) {
|
|
3104
|
+
const trimmed = value?.trim();
|
|
3105
|
+
return trimmed ? trimmed : void 0;
|
|
3106
|
+
}
|
|
3107
|
+
function createSaveArtifactsTool(rootDir, saveCallbackRef) {
|
|
3108
|
+
return {
|
|
3109
|
+
name: "save_artifacts",
|
|
3110
|
+
label: "Save Artifacts",
|
|
3111
|
+
description: [
|
|
3112
|
+
"Save the final output files produced by this run.",
|
|
3113
|
+
"Always use this for user-facing deliverables that are part of the final result.",
|
|
3114
|
+
"Do not use this for intermediate, temporary, draft, or scratch files.",
|
|
3115
|
+
"Each filePath must be an absolute path inside the current pack root."
|
|
3116
|
+
].join("\n"),
|
|
3117
|
+
promptSnippet: "save_artifacts: Save final result files for this task. Always call this for user-facing final deliverables, and never for intermediate files. Use absolute paths inside the current pack root.",
|
|
3118
|
+
promptGuidelines: [
|
|
3119
|
+
"Whenever you create a final result file for the user, call `save_artifacts` before finishing the response.",
|
|
3120
|
+
"Do not call `save_artifacts` for intermediate, temporary, draft, or scratch files."
|
|
3121
|
+
],
|
|
3122
|
+
parameters: SaveArtifactsParams,
|
|
3123
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
3124
|
+
const saveArtifacts = saveCallbackRef.current;
|
|
3125
|
+
if (!saveArtifacts) {
|
|
3126
|
+
throw new Error("Artifact saving is not available for this run.");
|
|
3127
|
+
}
|
|
3128
|
+
const artifacts = params.artifacts.map((artifact) => {
|
|
3129
|
+
const metadata = resolvePackFile(rootDir, artifact.filePath);
|
|
3130
|
+
return {
|
|
3131
|
+
filePath: metadata.resolvedPath,
|
|
3132
|
+
fileName: metadata.fileName,
|
|
3133
|
+
mimeType: metadata.mimeType,
|
|
3134
|
+
sizeBytes: metadata.sizeBytes,
|
|
3135
|
+
title: normalizeOptionalText(artifact.title),
|
|
3136
|
+
isPrimary: artifact.isPrimary === true
|
|
3137
|
+
};
|
|
3138
|
+
});
|
|
3139
|
+
const savedCount = await saveArtifacts(artifacts);
|
|
3140
|
+
return textResult(`Saved ${savedCount} artifact(s).`);
|
|
3141
|
+
}
|
|
3142
|
+
};
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
// src/runtime/tools/send-file-tool.ts
|
|
3146
|
+
import fs11 from "fs";
|
|
3147
|
+
import path11 from "path";
|
|
3148
|
+
import { Type as Type2 } from "@sinclair/typebox";
|
|
3149
|
+
var SendFileParams = Type2.Object({
|
|
3150
|
+
filePath: Type2.String({
|
|
3151
|
+
description: "Absolute path to the file to send to the user. The file must exist and be readable."
|
|
3152
|
+
}),
|
|
3153
|
+
caption: Type2.Optional(
|
|
3154
|
+
Type2.String({
|
|
3155
|
+
description: "Optional caption or description to accompany the file."
|
|
3156
|
+
})
|
|
3157
|
+
)
|
|
3158
|
+
});
|
|
3159
|
+
function createSendFileTool(fileOutputCallbackRef) {
|
|
3160
|
+
return {
|
|
3161
|
+
name: "send_file",
|
|
3162
|
+
label: "Send File",
|
|
3163
|
+
description: "Send a file to the user via the current chat channel (Telegram, Slack, or Web). IMPORTANT: Do NOT proactively send files. Only use this tool when the user EXPLICITLY asks you to send, share, or deliver a file (e.g. '\u628A\u6587\u4EF6\u53D1\u7ED9\u6211', 'send me the file', 'share the result'). Never send intermediate/temporary files. When the user asks, send only the specific file(s) the user requested, not all generated files.",
|
|
3164
|
+
promptSnippet: "send_file: Send a file to the user ONLY when they explicitly request it. Never send files proactively or automatically.",
|
|
3165
|
+
parameters: SendFileParams,
|
|
3166
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
3167
|
+
const { filePath, caption } = params;
|
|
3168
|
+
if (!fs11.existsSync(filePath)) {
|
|
3169
|
+
return {
|
|
3170
|
+
content: [{ type: "text", text: `Error: File not found: ${filePath}` }],
|
|
3171
|
+
details: void 0
|
|
3172
|
+
};
|
|
3173
|
+
}
|
|
3174
|
+
const stats = fs11.statSync(filePath);
|
|
3175
|
+
if (!stats.isFile()) {
|
|
2555
3176
|
return {
|
|
2556
3177
|
content: [
|
|
2557
3178
|
{ type: "text", text: `Error: Path is not a file: ${filePath}` }
|
|
@@ -2559,7 +3180,7 @@ function createSendFileTool(fileOutputCallbackRef) {
|
|
|
2559
3180
|
details: void 0
|
|
2560
3181
|
};
|
|
2561
3182
|
}
|
|
2562
|
-
const filename =
|
|
3183
|
+
const filename = path11.basename(filePath);
|
|
2563
3184
|
const mimeType = detectMimeType(filePath);
|
|
2564
3185
|
const callback = fileOutputCallbackRef.current;
|
|
2565
3186
|
if (callback) {
|
|
@@ -2586,43 +3207,65 @@ function createSendFileTool(fileOutputCallbackRef) {
|
|
|
2586
3207
|
}
|
|
2587
3208
|
|
|
2588
3209
|
// src/runtime/tools/manage-schedule-tool.ts
|
|
2589
|
-
import { Type as
|
|
2590
|
-
var ManageScheduleParams =
|
|
2591
|
-
action:
|
|
3210
|
+
import { Type as Type3 } from "@sinclair/typebox";
|
|
3211
|
+
var ManageScheduleParams = Type3.Object({
|
|
3212
|
+
action: Type3.Union(
|
|
2592
3213
|
[
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
3214
|
+
Type3.Literal("add"),
|
|
3215
|
+
Type3.Literal("list"),
|
|
3216
|
+
Type3.Literal("remove"),
|
|
3217
|
+
Type3.Literal("trigger"),
|
|
3218
|
+
Type3.Literal("enable"),
|
|
3219
|
+
Type3.Literal("disable")
|
|
2599
3220
|
],
|
|
2600
3221
|
{ description: "The action to perform." }
|
|
2601
3222
|
),
|
|
2602
|
-
name:
|
|
2603
|
-
|
|
3223
|
+
name: Type3.Optional(
|
|
3224
|
+
Type3.String({
|
|
2604
3225
|
description: "Unique name for the scheduled task. Required for add/remove/trigger/enable/disable."
|
|
2605
3226
|
})
|
|
2606
3227
|
),
|
|
2607
|
-
cron:
|
|
2608
|
-
|
|
3228
|
+
cron: Type3.Optional(
|
|
3229
|
+
Type3.String({
|
|
2609
3230
|
description: "Cron expression (5 fields: minute hour day month weekday). Required for add."
|
|
2610
3231
|
})
|
|
2611
3232
|
),
|
|
2612
|
-
prompt:
|
|
2613
|
-
|
|
3233
|
+
prompt: Type3.Optional(
|
|
3234
|
+
Type3.String({
|
|
2614
3235
|
description: "The work prompt to execute when the task triggers. Required for add. Describe only what to do each run; do not repeat timing, cron, or 'every N minutes' instructions here."
|
|
2615
3236
|
})
|
|
2616
3237
|
),
|
|
2617
|
-
timezone:
|
|
2618
|
-
|
|
3238
|
+
timezone: Type3.Optional(
|
|
3239
|
+
Type3.String({
|
|
2619
3240
|
description: "Optional timezone for the cron schedule, e.g. 'Asia/Shanghai', 'America/New_York'."
|
|
2620
3241
|
})
|
|
3242
|
+
),
|
|
3243
|
+
notifyAdapter: Type3.Optional(
|
|
3244
|
+
Type3.String({
|
|
3245
|
+
description: "Optional target adapter for notifications. If omitted, the current chat is used when supported (Telegram, Slack, or Web)."
|
|
3246
|
+
})
|
|
3247
|
+
),
|
|
3248
|
+
notifyChannelId: Type3.Optional(
|
|
3249
|
+
Type3.String({
|
|
3250
|
+
description: "Optional target channelId for notifications. Must be provided together with notifyAdapter when overriding the default target."
|
|
3251
|
+
})
|
|
2621
3252
|
)
|
|
2622
3253
|
});
|
|
2623
|
-
function
|
|
3254
|
+
function textResult2(text) {
|
|
2624
3255
|
return { content: [{ type: "text", text }], details: void 0 };
|
|
2625
3256
|
}
|
|
3257
|
+
function getDefaultNotifyTarget(adapter, channelId) {
|
|
3258
|
+
if (adapter === "telegram" && channelId.startsWith("telegram-")) {
|
|
3259
|
+
return { adapter: "telegram", channelId };
|
|
3260
|
+
}
|
|
3261
|
+
if (adapter === "slack" && channelId.startsWith("slack-")) {
|
|
3262
|
+
return { adapter: "slack", channelId };
|
|
3263
|
+
}
|
|
3264
|
+
if (adapter === "web") {
|
|
3265
|
+
return { adapter: "web", channelId };
|
|
3266
|
+
}
|
|
3267
|
+
return null;
|
|
3268
|
+
}
|
|
2626
3269
|
function createManageScheduleTool(schedulerRef, adapter, channelId) {
|
|
2627
3270
|
return {
|
|
2628
3271
|
name: "manage_scheduled_task",
|
|
@@ -2631,7 +3274,7 @@ function createManageScheduleTool(schedulerRef, adapter, channelId) {
|
|
|
2631
3274
|
"Manage scheduled tasks (cron jobs) that automatically execute prompts and push results to IM channels.",
|
|
2632
3275
|
"",
|
|
2633
3276
|
"Actions:",
|
|
2634
|
-
"- add: Create a new scheduled task. Requires: name, cron, prompt.
|
|
3277
|
+
"- add: Create a new scheduled task. Requires: name, cron, prompt. Notifications default to the current Telegram, Slack, or Web chat. You can override the destination with notifyAdapter + notifyChannelId. The prompt must describe only the work for each run, not the schedule itself.",
|
|
2635
3278
|
"- list: List all scheduled tasks with their status.",
|
|
2636
3279
|
"- remove: Remove a scheduled task by name.",
|
|
2637
3280
|
"- trigger: Manually trigger a scheduled task by name (runs immediately).",
|
|
@@ -2648,7 +3291,7 @@ function createManageScheduleTool(schedulerRef, adapter, channelId) {
|
|
|
2648
3291
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
2649
3292
|
const scheduler = schedulerRef.current;
|
|
2650
3293
|
if (!scheduler) {
|
|
2651
|
-
return
|
|
3294
|
+
return textResult2(
|
|
2652
3295
|
"Error: Scheduler is not available. The scheduled task system may not be initialized."
|
|
2653
3296
|
);
|
|
2654
3297
|
}
|
|
@@ -2656,79 +3299,85 @@ function createManageScheduleTool(schedulerRef, adapter, channelId) {
|
|
|
2656
3299
|
case "list": {
|
|
2657
3300
|
const jobs = scheduler.listJobs();
|
|
2658
3301
|
if (jobs.length === 0) {
|
|
2659
|
-
return
|
|
3302
|
+
return textResult2("No scheduled tasks configured.");
|
|
2660
3303
|
}
|
|
2661
3304
|
const lines = jobs.map(
|
|
2662
3305
|
(j) => `- **${j.name}**: \`${j.cron}\` \u2192 ${j.notify.adapter}:${j.notify.channelId} [${j.enabled ? "enabled" : "disabled"}]${j.running ? " (running)" : ""}${j.lastRunAt ? ` (last: ${j.lastRunAt})` : ""}`
|
|
2663
3306
|
);
|
|
2664
|
-
return
|
|
3307
|
+
return textResult2(
|
|
2665
3308
|
`Scheduled tasks (${jobs.length}):
|
|
2666
3309
|
${lines.join("\n")}`
|
|
2667
3310
|
);
|
|
2668
3311
|
}
|
|
2669
3312
|
case "add": {
|
|
2670
3313
|
if (!params.name || !params.cron || !params.prompt) {
|
|
2671
|
-
return
|
|
3314
|
+
return textResult2(
|
|
2672
3315
|
"Error: 'name', 'cron', and 'prompt' are required for adding a task."
|
|
2673
3316
|
);
|
|
2674
3317
|
}
|
|
2675
|
-
if (
|
|
2676
|
-
return
|
|
2677
|
-
"Error:
|
|
3318
|
+
if (params.notifyAdapter && !params.notifyChannelId || !params.notifyAdapter && params.notifyChannelId) {
|
|
3319
|
+
return textResult2(
|
|
3320
|
+
"Error: 'notifyAdapter' and 'notifyChannelId' must be provided together when overriding the notification target."
|
|
3321
|
+
);
|
|
3322
|
+
}
|
|
3323
|
+
const notify = params.notifyAdapter && params.notifyChannelId ? {
|
|
3324
|
+
adapter: params.notifyAdapter,
|
|
3325
|
+
channelId: params.notifyChannelId
|
|
3326
|
+
} : getDefaultNotifyTarget(adapter, channelId);
|
|
3327
|
+
if (!notify) {
|
|
3328
|
+
return textResult2(
|
|
3329
|
+
"Error: No default notification target is available for this chat. Provide 'notifyAdapter' and 'notifyChannelId'."
|
|
2678
3330
|
);
|
|
2679
3331
|
}
|
|
2680
3332
|
const jobConfig = {
|
|
2681
3333
|
name: params.name,
|
|
2682
3334
|
cron: params.cron,
|
|
2683
3335
|
prompt: params.prompt,
|
|
2684
|
-
notify
|
|
2685
|
-
adapter,
|
|
2686
|
-
channelId
|
|
2687
|
-
},
|
|
3336
|
+
notify,
|
|
2688
3337
|
enabled: true,
|
|
2689
3338
|
timezone: params.timezone
|
|
2690
3339
|
};
|
|
2691
3340
|
const result = scheduler.addJob(jobConfig);
|
|
2692
|
-
return
|
|
3341
|
+
return textResult2(result.message);
|
|
2693
3342
|
}
|
|
2694
3343
|
case "remove": {
|
|
2695
3344
|
if (!params.name) {
|
|
2696
|
-
return
|
|
3345
|
+
return textResult2(
|
|
2697
3346
|
"Error: 'name' is required for removing a task."
|
|
2698
3347
|
);
|
|
2699
3348
|
}
|
|
2700
3349
|
const result = scheduler.removeJob(params.name);
|
|
2701
|
-
return
|
|
3350
|
+
return textResult2(result.message);
|
|
2702
3351
|
}
|
|
2703
3352
|
case "trigger": {
|
|
2704
3353
|
if (!params.name) {
|
|
2705
|
-
return
|
|
3354
|
+
return textResult2(
|
|
2706
3355
|
"Error: 'name' is required for triggering a task."
|
|
2707
3356
|
);
|
|
2708
3357
|
}
|
|
2709
3358
|
const result = await scheduler.triggerJob(params.name);
|
|
2710
|
-
return
|
|
3359
|
+
return textResult2(result.message);
|
|
2711
3360
|
}
|
|
2712
3361
|
case "enable": {
|
|
2713
3362
|
if (!params.name) {
|
|
2714
|
-
return
|
|
3363
|
+
return textResult2(
|
|
2715
3364
|
"Error: 'name' is required for enabling a task."
|
|
2716
3365
|
);
|
|
2717
3366
|
}
|
|
2718
3367
|
const result = scheduler.setEnabled(params.name, true);
|
|
2719
|
-
return
|
|
3368
|
+
return textResult2(result.message);
|
|
2720
3369
|
}
|
|
2721
3370
|
case "disable": {
|
|
2722
3371
|
if (!params.name) {
|
|
2723
|
-
return
|
|
3372
|
+
return textResult2(
|
|
2724
3373
|
"Error: 'name' is required for disabling a task."
|
|
2725
3374
|
);
|
|
2726
3375
|
}
|
|
2727
3376
|
const result = scheduler.setEnabled(params.name, false);
|
|
2728
|
-
return
|
|
3377
|
+
return textResult2(result.message);
|
|
2729
3378
|
}
|
|
2730
3379
|
default:
|
|
2731
|
-
return
|
|
3380
|
+
return textResult2(
|
|
2732
3381
|
`Error: Unknown action '${params.action}'. Use: add, list, remove, trigger, enable, disable.`
|
|
2733
3382
|
);
|
|
2734
3383
|
}
|
|
@@ -2738,8 +3387,8 @@ ${lines.join("\n")}`
|
|
|
2738
3387
|
|
|
2739
3388
|
// src/runtime/commands/help-command.ts
|
|
2740
3389
|
init_commands();
|
|
2741
|
-
import
|
|
2742
|
-
import
|
|
3390
|
+
import fs12 from "fs";
|
|
3391
|
+
import path12 from "path";
|
|
2743
3392
|
function buildHelpMessage(rootDir) {
|
|
2744
3393
|
const sections = [];
|
|
2745
3394
|
const commands = getVisibleCommands();
|
|
@@ -2749,7 +3398,7 @@ function buildHelpMessage(rootDir) {
|
|
|
2749
3398
|
sections.push(`\u{1F4CB} **Available Commands**
|
|
2750
3399
|
|
|
2751
3400
|
${commandLines.join("\n")}`);
|
|
2752
|
-
const configPath =
|
|
3401
|
+
const configPath = path12.resolve(rootDir, "skillpack.json");
|
|
2753
3402
|
const skills = readInstalledSkills(configPath);
|
|
2754
3403
|
if (skills.length > 0) {
|
|
2755
3404
|
const skillLines = skills.map(
|
|
@@ -2785,11 +3434,11 @@ function handleHelpCommand(rootDir) {
|
|
|
2785
3434
|
};
|
|
2786
3435
|
}
|
|
2787
3436
|
function readInstalledSkills(configPath) {
|
|
2788
|
-
if (!
|
|
3437
|
+
if (!fs12.existsSync(configPath)) {
|
|
2789
3438
|
return [];
|
|
2790
3439
|
}
|
|
2791
3440
|
try {
|
|
2792
|
-
const raw =
|
|
3441
|
+
const raw = fs12.readFileSync(configPath, "utf-8");
|
|
2793
3442
|
const config = JSON.parse(raw);
|
|
2794
3443
|
return Array.isArray(config.skills) ? config.skills : [];
|
|
2795
3444
|
} catch {
|
|
@@ -2809,24 +3458,24 @@ var BUILTIN_SKILL_CREATOR_TEMPLATE_DIR = fileURLToPath(
|
|
|
2809
3458
|
var PACK_AGENTS_FILE = "AGENTS.md";
|
|
2810
3459
|
var PACK_SOUL_FILE = "SOUL.md";
|
|
2811
3460
|
function materializeBuiltinSkillCreator(rootDir, skillsPath) {
|
|
2812
|
-
if (!
|
|
3461
|
+
if (!fs13.existsSync(BUILTIN_SKILL_CREATOR_TEMPLATE_DIR)) {
|
|
2813
3462
|
log(
|
|
2814
3463
|
`[PackAgent] Built-in skill-creator template missing: ${BUILTIN_SKILL_CREATOR_TEMPLATE_DIR}`
|
|
2815
3464
|
);
|
|
2816
3465
|
return null;
|
|
2817
3466
|
}
|
|
2818
|
-
const packConfigPath =
|
|
2819
|
-
const skillDir =
|
|
2820
|
-
const skillPath =
|
|
3467
|
+
const packConfigPath = path13.resolve(rootDir, "skillpack.json");
|
|
3468
|
+
const skillDir = path13.resolve(skillsPath, BUILTIN_SKILL_CREATOR_NAME);
|
|
3469
|
+
const skillPath = path13.join(skillDir, "SKILL.md");
|
|
2821
3470
|
const renderTemplate = (content) => content.replaceAll("{{SKILLS_PATH}}", skillsPath).replaceAll("{{PACK_CONFIG_PATH}}", packConfigPath);
|
|
2822
3471
|
const copyDir = (srcDir, destDir) => {
|
|
2823
|
-
|
|
2824
|
-
for (const entry of
|
|
3472
|
+
fs13.mkdirSync(destDir, { recursive: true });
|
|
3473
|
+
for (const entry of fs13.readdirSync(srcDir, { withFileTypes: true })) {
|
|
2825
3474
|
if (entry.name === ".DS_Store") {
|
|
2826
3475
|
continue;
|
|
2827
3476
|
}
|
|
2828
|
-
const srcPath =
|
|
2829
|
-
const destPath =
|
|
3477
|
+
const srcPath = path13.join(srcDir, entry.name);
|
|
3478
|
+
const destPath = path13.join(destDir, entry.name);
|
|
2830
3479
|
if (entry.isDirectory()) {
|
|
2831
3480
|
copyDir(srcPath, destPath);
|
|
2832
3481
|
continue;
|
|
@@ -2835,17 +3484,17 @@ function materializeBuiltinSkillCreator(rootDir, skillsPath) {
|
|
|
2835
3484
|
continue;
|
|
2836
3485
|
}
|
|
2837
3486
|
if (entry.name.endsWith(".md") || entry.name.endsWith(".py")) {
|
|
2838
|
-
const content =
|
|
2839
|
-
|
|
3487
|
+
const content = fs13.readFileSync(srcPath, "utf-8");
|
|
3488
|
+
fs13.writeFileSync(destPath, renderTemplate(content), "utf-8");
|
|
2840
3489
|
continue;
|
|
2841
3490
|
}
|
|
2842
|
-
|
|
3491
|
+
fs13.copyFileSync(srcPath, destPath);
|
|
2843
3492
|
}
|
|
2844
3493
|
};
|
|
2845
|
-
if (!
|
|
3494
|
+
if (!fs13.existsSync(skillDir)) {
|
|
2846
3495
|
copyDir(BUILTIN_SKILL_CREATOR_TEMPLATE_DIR, skillDir);
|
|
2847
3496
|
}
|
|
2848
|
-
if (!
|
|
3497
|
+
if (!fs13.existsSync(skillPath)) {
|
|
2849
3498
|
log(
|
|
2850
3499
|
`[PackAgent] Materialized built-in skill-creator but SKILL.md is missing: ${skillPath}`
|
|
2851
3500
|
);
|
|
@@ -2873,11 +3522,11 @@ function overrideBuiltinSkillCreator(base, materializedSkill) {
|
|
|
2873
3522
|
};
|
|
2874
3523
|
}
|
|
2875
3524
|
function readOptionalPackPromptFile(filePath) {
|
|
2876
|
-
if (!
|
|
3525
|
+
if (!fs13.existsSync(filePath)) {
|
|
2877
3526
|
return void 0;
|
|
2878
3527
|
}
|
|
2879
3528
|
try {
|
|
2880
|
-
const content =
|
|
3529
|
+
const content = fs13.readFileSync(filePath, "utf-8").trim();
|
|
2881
3530
|
return content.length > 0 ? content : void 0;
|
|
2882
3531
|
} catch (error) {
|
|
2883
3532
|
console.warn(`[PackAgent] Warning: Could not read ${filePath}:`, error);
|
|
@@ -2885,8 +3534,8 @@ function readOptionalPackPromptFile(filePath) {
|
|
|
2885
3534
|
}
|
|
2886
3535
|
}
|
|
2887
3536
|
function buildPackPromptBlock(rootDir) {
|
|
2888
|
-
const agentsPath =
|
|
2889
|
-
const soulPath =
|
|
3537
|
+
const agentsPath = path13.resolve(rootDir, PACK_AGENTS_FILE);
|
|
3538
|
+
const soulPath = path13.resolve(rootDir, PACK_SOUL_FILE);
|
|
2890
3539
|
const agentsContent = readOptionalPackPromptFile(agentsPath);
|
|
2891
3540
|
const soulContent = readOptionalPackPromptFile(soulPath);
|
|
2892
3541
|
if (!agentsContent && !soulContent) {
|
|
@@ -2945,14 +3594,13 @@ var PackAgent = class {
|
|
|
2945
3594
|
options;
|
|
2946
3595
|
channels = /* @__PURE__ */ new Map();
|
|
2947
3596
|
pendingSessionCreations = /* @__PURE__ */ new Map();
|
|
2948
|
-
fileOutputCallbackRef = {
|
|
2949
|
-
current: null
|
|
2950
|
-
};
|
|
2951
3597
|
schedulerRef = { current: null };
|
|
2952
3598
|
authStorage;
|
|
3599
|
+
artifactPersistenceService;
|
|
2953
3600
|
constructor(options) {
|
|
2954
3601
|
this.options = options;
|
|
2955
|
-
|
|
3602
|
+
this.artifactPersistenceService = options.artifactPersistenceService;
|
|
3603
|
+
const configPath = path13.resolve(options.rootDir, "data", "config.json");
|
|
2956
3604
|
const backend = new ConfigFileAuthBackend(configPath);
|
|
2957
3605
|
this.authStorage = AuthStorage.fromStorage(backend);
|
|
2958
3606
|
const providerMeta = SUPPORTED_PROVIDERS[options.provider];
|
|
@@ -2979,9 +3627,12 @@ var PackAgent = class {
|
|
|
2979
3627
|
setScheduler(scheduler) {
|
|
2980
3628
|
this.schedulerRef.current = scheduler;
|
|
2981
3629
|
}
|
|
2982
|
-
createCustomTools(adapter, channelId) {
|
|
2983
|
-
const tools = [
|
|
2984
|
-
|
|
3630
|
+
createCustomTools(adapter, channelId, fileOutputCallbackRef, finalArtifactsSaveCallbackRef) {
|
|
3631
|
+
const tools = [
|
|
3632
|
+
createSendFileTool(fileOutputCallbackRef),
|
|
3633
|
+
createSaveArtifactsTool(this.options.rootDir, finalArtifactsSaveCallbackRef)
|
|
3634
|
+
];
|
|
3635
|
+
if (adapter !== "scheduler") {
|
|
2985
3636
|
tools.push(createManageScheduleTool(this.schedulerRef, adapter, channelId));
|
|
2986
3637
|
}
|
|
2987
3638
|
return tools;
|
|
@@ -3023,24 +3674,24 @@ var PackAgent = class {
|
|
|
3023
3674
|
if (resolvedModel && baseUrl) {
|
|
3024
3675
|
log(`[PackAgent] Resolved ${provider}/${modelId} api=${resolvedModel.api} baseUrl=${baseUrl}`);
|
|
3025
3676
|
}
|
|
3026
|
-
const sessionDir =
|
|
3677
|
+
const sessionDir = path13.resolve(
|
|
3027
3678
|
rootDir,
|
|
3028
3679
|
"data",
|
|
3029
3680
|
"sessions",
|
|
3030
3681
|
channelId
|
|
3031
3682
|
);
|
|
3032
|
-
|
|
3683
|
+
fs13.mkdirSync(sessionDir, { recursive: true });
|
|
3033
3684
|
const sessionManager = SessionManager.continueRecent(rootDir, sessionDir);
|
|
3034
3685
|
log(`[PackAgent] Session dir: ${sessionDir}`);
|
|
3035
|
-
const workspaceDir =
|
|
3686
|
+
const workspaceDir = path13.resolve(
|
|
3036
3687
|
rootDir,
|
|
3037
3688
|
"data",
|
|
3038
3689
|
"workspaces",
|
|
3039
3690
|
channelId
|
|
3040
3691
|
);
|
|
3041
|
-
|
|
3692
|
+
fs13.mkdirSync(workspaceDir, { recursive: true });
|
|
3042
3693
|
log(`[PackAgent] Workspace dir: ${workspaceDir}`);
|
|
3043
|
-
const skillsPath =
|
|
3694
|
+
const skillsPath = path13.resolve(rootDir, "skills");
|
|
3044
3695
|
log(`[PackAgent] Loading skills from: ${skillsPath}`);
|
|
3045
3696
|
const materializedSkillCreator = materializeBuiltinSkillCreator(
|
|
3046
3697
|
rootDir,
|
|
@@ -3075,7 +3726,18 @@ var PackAgent = class {
|
|
|
3075
3726
|
});
|
|
3076
3727
|
await resourceLoader.reload();
|
|
3077
3728
|
const tools = createCodingTools(workspaceDir);
|
|
3078
|
-
const
|
|
3729
|
+
const fileOutputCallbackRef = {
|
|
3730
|
+
current: null
|
|
3731
|
+
};
|
|
3732
|
+
const finalArtifactsSaveCallbackRef = {
|
|
3733
|
+
current: null
|
|
3734
|
+
};
|
|
3735
|
+
const customTools = this.createCustomTools(
|
|
3736
|
+
adapter,
|
|
3737
|
+
channelId,
|
|
3738
|
+
fileOutputCallbackRef,
|
|
3739
|
+
finalArtifactsSaveCallbackRef
|
|
3740
|
+
);
|
|
3079
3741
|
const { session } = await createAgentSession({
|
|
3080
3742
|
cwd: workspaceDir,
|
|
3081
3743
|
authStorage,
|
|
@@ -3089,7 +3751,9 @@ var PackAgent = class {
|
|
|
3089
3751
|
const channelSession = {
|
|
3090
3752
|
session,
|
|
3091
3753
|
running: false,
|
|
3092
|
-
pending: Promise.resolve()
|
|
3754
|
+
pending: Promise.resolve(),
|
|
3755
|
+
fileOutputCallbackRef,
|
|
3756
|
+
finalArtifactsSaveCallbackRef
|
|
3093
3757
|
};
|
|
3094
3758
|
this.channels.set(channelId, channelSession);
|
|
3095
3759
|
return channelSession;
|
|
@@ -3106,86 +3770,97 @@ var PackAgent = class {
|
|
|
3106
3770
|
const run = async () => {
|
|
3107
3771
|
cs.running = true;
|
|
3108
3772
|
let turnHadVisibleOutput = false;
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3773
|
+
const runId = randomUUID3();
|
|
3774
|
+
let unsubscribe = () => void 0;
|
|
3775
|
+
try {
|
|
3776
|
+
cs.fileOutputCallbackRef.current = (event) => {
|
|
3777
|
+
onEvent(event);
|
|
3778
|
+
};
|
|
3779
|
+
cs.finalArtifactsSaveCallbackRef.current = (artifacts) => {
|
|
3780
|
+
return this.artifactPersistenceService.saveArtifacts({
|
|
3781
|
+
runId,
|
|
3782
|
+
channelId,
|
|
3783
|
+
artifacts
|
|
3784
|
+
});
|
|
3785
|
+
};
|
|
3786
|
+
unsubscribe = cs.session.subscribe((event) => {
|
|
3787
|
+
switch (event.type) {
|
|
3788
|
+
case "agent_start":
|
|
3789
|
+
log("\n=== [AGENT SESSION START] ===");
|
|
3790
|
+
log("System Prompt:\n", cs.session.systemPrompt);
|
|
3791
|
+
log("============================\n");
|
|
3792
|
+
onEvent({ type: "agent_start" });
|
|
3793
|
+
break;
|
|
3794
|
+
case "message_start":
|
|
3795
|
+
log(`
|
|
3122
3796
|
--- [Message Start: ${event.message?.role}] ---`);
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3797
|
+
if (event.message?.role === "user") {
|
|
3798
|
+
log(JSON.stringify(event.message.content, null, 2));
|
|
3799
|
+
}
|
|
3800
|
+
onEvent({ type: "message_start", role: event.message?.role ?? "" });
|
|
3801
|
+
break;
|
|
3802
|
+
case "message_update":
|
|
3803
|
+
if (event.assistantMessageEvent?.type === "text_delta") {
|
|
3804
|
+
turnHadVisibleOutput = true;
|
|
3805
|
+
write(event.assistantMessageEvent.delta);
|
|
3806
|
+
onEvent({
|
|
3807
|
+
type: "text_delta",
|
|
3808
|
+
delta: event.assistantMessageEvent.delta
|
|
3809
|
+
});
|
|
3810
|
+
} else if (event.assistantMessageEvent?.type === "thinking_delta") {
|
|
3811
|
+
turnHadVisibleOutput = true;
|
|
3812
|
+
onEvent({
|
|
3813
|
+
type: "thinking_delta",
|
|
3814
|
+
delta: event.assistantMessageEvent.delta
|
|
3815
|
+
});
|
|
3816
|
+
}
|
|
3817
|
+
break;
|
|
3818
|
+
case "message_end":
|
|
3819
|
+
log(`
|
|
3820
|
+
--- [Message End: ${event.message?.role}] ---`);
|
|
3821
|
+
if (event.message?.role === "assistant") {
|
|
3822
|
+
const diagnostics2 = getAssistantDiagnostics(event.message);
|
|
3823
|
+
if (diagnostics2) {
|
|
3824
|
+
log(
|
|
3825
|
+
`[Assistant Diagnostics] stopReason=${diagnostics2.stopReason} text=${diagnostics2.hasText ? "yes" : "no"} toolCalls=${diagnostics2.toolCalls}`
|
|
3826
|
+
);
|
|
3827
|
+
if (diagnostics2.errorMessage) {
|
|
3828
|
+
log(`[Assistant Error] ${diagnostics2.errorMessage}`);
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
onEvent({ type: "message_end", role: event.message?.role ?? "" });
|
|
3833
|
+
break;
|
|
3834
|
+
case "tool_execution_start":
|
|
3130
3835
|
turnHadVisibleOutput = true;
|
|
3131
|
-
|
|
3836
|
+
log(`
|
|
3837
|
+
>>> [Tool Start: ${event.toolName}] >>>`);
|
|
3838
|
+
log("Args:", JSON.stringify(event.args, null, 2));
|
|
3132
3839
|
onEvent({
|
|
3133
|
-
type: "
|
|
3134
|
-
|
|
3840
|
+
type: "tool_start",
|
|
3841
|
+
toolCallId: event.toolCallId ?? "",
|
|
3842
|
+
toolName: event.toolName,
|
|
3843
|
+
toolInput: event.args
|
|
3135
3844
|
});
|
|
3136
|
-
|
|
3845
|
+
break;
|
|
3846
|
+
case "tool_execution_end":
|
|
3137
3847
|
turnHadVisibleOutput = true;
|
|
3848
|
+
log(`<<< [Tool End: ${event.toolName}] <<<`);
|
|
3849
|
+
log(`Error: ${event.isError ? "Yes" : "No"}`);
|
|
3138
3850
|
onEvent({
|
|
3139
|
-
type: "
|
|
3140
|
-
|
|
3851
|
+
type: "tool_end",
|
|
3852
|
+
toolCallId: event.toolCallId ?? "",
|
|
3853
|
+
toolName: event.toolName,
|
|
3854
|
+
isError: event.isError,
|
|
3855
|
+
result: event.result
|
|
3141
3856
|
});
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
if (diagnostics) {
|
|
3150
|
-
log(
|
|
3151
|
-
`[Assistant Diagnostics] stopReason=${diagnostics.stopReason} text=${diagnostics.hasText ? "yes" : "no"} toolCalls=${diagnostics.toolCalls}`
|
|
3152
|
-
);
|
|
3153
|
-
if (diagnostics.errorMessage) {
|
|
3154
|
-
log(`[Assistant Error] ${diagnostics.errorMessage}`);
|
|
3155
|
-
}
|
|
3156
|
-
}
|
|
3157
|
-
}
|
|
3158
|
-
onEvent({ type: "message_end", role: event.message?.role ?? "" });
|
|
3159
|
-
break;
|
|
3160
|
-
case "tool_execution_start":
|
|
3161
|
-
turnHadVisibleOutput = true;
|
|
3162
|
-
log(`
|
|
3163
|
-
>>> [Tool Start: ${event.toolName}] >>>`);
|
|
3164
|
-
log("Args:", JSON.stringify(event.args, null, 2));
|
|
3165
|
-
onEvent({
|
|
3166
|
-
type: "tool_start",
|
|
3167
|
-
toolName: event.toolName,
|
|
3168
|
-
toolInput: event.args
|
|
3169
|
-
});
|
|
3170
|
-
break;
|
|
3171
|
-
case "tool_execution_end":
|
|
3172
|
-
turnHadVisibleOutput = true;
|
|
3173
|
-
log(`<<< [Tool End: ${event.toolName}] <<<`);
|
|
3174
|
-
log(`Error: ${event.isError ? "Yes" : "No"}`);
|
|
3175
|
-
onEvent({
|
|
3176
|
-
type: "tool_end",
|
|
3177
|
-
toolName: event.toolName,
|
|
3178
|
-
isError: event.isError,
|
|
3179
|
-
result: event.result
|
|
3180
|
-
});
|
|
3181
|
-
break;
|
|
3182
|
-
case "agent_end":
|
|
3183
|
-
log("\n=== [AGENT SESSION END] ===\n");
|
|
3184
|
-
onEvent({ type: "agent_end" });
|
|
3185
|
-
break;
|
|
3186
|
-
}
|
|
3187
|
-
});
|
|
3188
|
-
try {
|
|
3857
|
+
break;
|
|
3858
|
+
case "agent_end":
|
|
3859
|
+
log("\n=== [AGENT SESSION END] ===\n");
|
|
3860
|
+
onEvent({ type: "agent_end" });
|
|
3861
|
+
break;
|
|
3862
|
+
}
|
|
3863
|
+
});
|
|
3189
3864
|
let promptText = text;
|
|
3190
3865
|
const promptOptions = {};
|
|
3191
3866
|
if (attachments && attachments.length > 0) {
|
|
@@ -3213,15 +3888,17 @@ ${text}`;
|
|
|
3213
3888
|
};
|
|
3214
3889
|
}
|
|
3215
3890
|
if (diagnostics && !diagnostics.hasText && diagnostics.toolCalls === 0 && !turnHadVisibleOutput) {
|
|
3891
|
+
const errorMessage = "Assistant returned no visible output. Check the server logs for details.";
|
|
3216
3892
|
return {
|
|
3217
3893
|
stopReason: diagnostics.stopReason,
|
|
3218
|
-
errorMessage
|
|
3894
|
+
errorMessage
|
|
3219
3895
|
};
|
|
3220
3896
|
}
|
|
3221
3897
|
return { stopReason: diagnostics?.stopReason ?? "unknown" };
|
|
3222
3898
|
} finally {
|
|
3223
3899
|
cs.running = false;
|
|
3224
|
-
|
|
3900
|
+
cs.fileOutputCallbackRef.current = null;
|
|
3901
|
+
cs.finalArtifactsSaveCallbackRef.current = null;
|
|
3225
3902
|
unsubscribe();
|
|
3226
3903
|
}
|
|
3227
3904
|
};
|
|
@@ -3241,9 +3918,9 @@ ${text}`;
|
|
|
3241
3918
|
this.channels.delete(channelId);
|
|
3242
3919
|
}
|
|
3243
3920
|
const { rootDir } = this.options;
|
|
3244
|
-
const sessionDir =
|
|
3245
|
-
if (
|
|
3246
|
-
|
|
3921
|
+
const sessionDir = path13.resolve(rootDir, "data", "sessions", channelId);
|
|
3922
|
+
if (fs13.existsSync(sessionDir)) {
|
|
3923
|
+
fs13.rmSync(sessionDir, { recursive: true, force: true });
|
|
3247
3924
|
log(`[PackAgent] Cleared session dir: ${sessionDir}`);
|
|
3248
3925
|
}
|
|
3249
3926
|
return {
|
|
@@ -3265,42 +3942,437 @@ ${text}`;
|
|
|
3265
3942
|
return { success: false, message: `Unknown command: ${command}` };
|
|
3266
3943
|
}
|
|
3267
3944
|
}
|
|
3268
|
-
abort(channelId) {
|
|
3269
|
-
const cs = this.channels.get(channelId);
|
|
3270
|
-
if (cs?.running) {
|
|
3271
|
-
cs.session.abort?.();
|
|
3945
|
+
abort(channelId) {
|
|
3946
|
+
const cs = this.channels.get(channelId);
|
|
3947
|
+
if (cs?.running) {
|
|
3948
|
+
cs.session.abort?.();
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
isRunning(channelId) {
|
|
3952
|
+
return this.channels.get(channelId)?.running ?? false;
|
|
3953
|
+
}
|
|
3954
|
+
dispose(channelId) {
|
|
3955
|
+
const cs = this.channels.get(channelId);
|
|
3956
|
+
if (cs) {
|
|
3957
|
+
cs.session.dispose();
|
|
3958
|
+
this.channels.delete(channelId);
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
/** Reserved: list all sessions */
|
|
3962
|
+
listSessions() {
|
|
3963
|
+
return [];
|
|
3964
|
+
}
|
|
3965
|
+
/** Reserved: restore a historical session */
|
|
3966
|
+
async restoreSession(_sessionId) {
|
|
3967
|
+
}
|
|
3968
|
+
getActiveChannelIds() {
|
|
3969
|
+
return Array.from(this.channels.keys());
|
|
3970
|
+
}
|
|
3971
|
+
};
|
|
3972
|
+
|
|
3973
|
+
// src/runtime/adapters/web.ts
|
|
3974
|
+
import fs15 from "fs";
|
|
3975
|
+
import path15 from "path";
|
|
3976
|
+
import { WebSocketServer } from "ws";
|
|
3977
|
+
init_commands();
|
|
3978
|
+
|
|
3979
|
+
// src/runtime/services/conversation.ts
|
|
3980
|
+
import fs14 from "fs";
|
|
3981
|
+
import path14 from "path";
|
|
3982
|
+
import {
|
|
3983
|
+
parseSessionEntries
|
|
3984
|
+
} from "@mariozechner/pi-coding-agent";
|
|
3985
|
+
var DEFAULT_WEB_CHANNEL_ID = "web";
|
|
3986
|
+
var ConversationService = class {
|
|
3987
|
+
constructor(rootDir) {
|
|
3988
|
+
this.rootDir = rootDir;
|
|
3989
|
+
}
|
|
3990
|
+
/**
|
|
3991
|
+
* Scan data/sessions and return conversation summaries sorted by recency.
|
|
3992
|
+
*/
|
|
3993
|
+
listConversations(activeChannels, options = {}) {
|
|
3994
|
+
const {
|
|
3995
|
+
includeDefaultWeb = false,
|
|
3996
|
+
includeLegacyWeb = true,
|
|
3997
|
+
allowedPlatforms
|
|
3998
|
+
} = options;
|
|
3999
|
+
const sessionsDir = path14.resolve(this.rootDir, "data", "sessions");
|
|
4000
|
+
const channelIds = new Set(activeChannels);
|
|
4001
|
+
const allowedPlatformSet = allowedPlatforms ? new Set(allowedPlatforms) : null;
|
|
4002
|
+
if (includeDefaultWeb) {
|
|
4003
|
+
channelIds.add(DEFAULT_WEB_CHANNEL_ID);
|
|
4004
|
+
}
|
|
4005
|
+
if (fs14.existsSync(sessionsDir)) {
|
|
4006
|
+
for (const entry of fs14.readdirSync(sessionsDir)) {
|
|
4007
|
+
const channelDir = path14.join(sessionsDir, entry);
|
|
4008
|
+
try {
|
|
4009
|
+
if (!fs14.statSync(channelDir).isDirectory()) {
|
|
4010
|
+
continue;
|
|
4011
|
+
}
|
|
4012
|
+
const platform = this.detectPlatform(entry);
|
|
4013
|
+
if (allowedPlatformSet && !allowedPlatformSet.has(platform)) {
|
|
4014
|
+
continue;
|
|
4015
|
+
}
|
|
4016
|
+
if (!includeLegacyWeb && this.isLegacyWebConversation(entry)) {
|
|
4017
|
+
continue;
|
|
4018
|
+
}
|
|
4019
|
+
channelIds.add(entry);
|
|
4020
|
+
} catch {
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
const results = [];
|
|
4025
|
+
for (const channelId of channelIds) {
|
|
4026
|
+
const platform = this.detectPlatform(channelId);
|
|
4027
|
+
if (allowedPlatformSet && !allowedPlatformSet.has(platform)) {
|
|
4028
|
+
continue;
|
|
4029
|
+
}
|
|
4030
|
+
if (!includeLegacyWeb && this.isLegacyWebConversation(channelId)) {
|
|
4031
|
+
continue;
|
|
4032
|
+
}
|
|
4033
|
+
const channelDir = path14.join(sessionsDir, channelId);
|
|
4034
|
+
const sessionFile = this.findLatestSessionFile(channelDir);
|
|
4035
|
+
let messageCount = 0;
|
|
4036
|
+
let lastMessageAt = "";
|
|
4037
|
+
let lastMessagePreview = "";
|
|
4038
|
+
if (sessionFile) {
|
|
4039
|
+
const entries = this.loadEntries(sessionFile);
|
|
4040
|
+
const messages = entries.filter(
|
|
4041
|
+
(entry) => entry.type === "message"
|
|
4042
|
+
);
|
|
4043
|
+
messageCount = messages.length;
|
|
4044
|
+
const lastMessage = messages[messages.length - 1];
|
|
4045
|
+
if (lastMessage) {
|
|
4046
|
+
lastMessageAt = lastMessage.timestamp;
|
|
4047
|
+
lastMessagePreview = this.extractTextPreview(lastMessage, 100);
|
|
4048
|
+
}
|
|
4049
|
+
}
|
|
4050
|
+
results.push({
|
|
4051
|
+
channelId,
|
|
4052
|
+
platform,
|
|
4053
|
+
sessionFile,
|
|
4054
|
+
messageCount,
|
|
4055
|
+
lastMessageAt,
|
|
4056
|
+
lastMessagePreview
|
|
4057
|
+
});
|
|
4058
|
+
}
|
|
4059
|
+
return results.sort((a, b) => {
|
|
4060
|
+
if (a.channelId === DEFAULT_WEB_CHANNEL_ID && b.channelId !== DEFAULT_WEB_CHANNEL_ID) {
|
|
4061
|
+
return -1;
|
|
4062
|
+
}
|
|
4063
|
+
if (b.channelId === DEFAULT_WEB_CHANNEL_ID && a.channelId !== DEFAULT_WEB_CHANNEL_ID) {
|
|
4064
|
+
return 1;
|
|
4065
|
+
}
|
|
4066
|
+
const recency = (b.lastMessageAt || "").localeCompare(
|
|
4067
|
+
a.lastMessageAt || ""
|
|
4068
|
+
);
|
|
4069
|
+
if (recency !== 0) return recency;
|
|
4070
|
+
return a.channelId.localeCompare(b.channelId);
|
|
4071
|
+
});
|
|
4072
|
+
}
|
|
4073
|
+
/**
|
|
4074
|
+
* Load latest messages for a channel in a simplified format.
|
|
4075
|
+
*/
|
|
4076
|
+
getMessages(channelId, limit = 100) {
|
|
4077
|
+
const channelDir = path14.resolve(
|
|
4078
|
+
this.rootDir,
|
|
4079
|
+
"data",
|
|
4080
|
+
"sessions",
|
|
4081
|
+
channelId
|
|
4082
|
+
);
|
|
4083
|
+
const sessionFile = this.findLatestSessionFile(channelDir);
|
|
4084
|
+
if (!sessionFile) return [];
|
|
4085
|
+
const safeLimit = Number.isFinite(limit) ? Math.max(0, Math.floor(limit)) : 100;
|
|
4086
|
+
if (safeLimit === 0) return [];
|
|
4087
|
+
const entries = this.loadEntries(sessionFile);
|
|
4088
|
+
const toolResultsById = this.collectToolResultStates(entries);
|
|
4089
|
+
const messages = [];
|
|
4090
|
+
let pendingBlocks = [];
|
|
4091
|
+
let pendingToolCalls = [];
|
|
4092
|
+
let pendingMessageId = "";
|
|
4093
|
+
let pendingTimestamp = "";
|
|
4094
|
+
const flushPendingAssistant = () => {
|
|
4095
|
+
if (pendingBlocks.length === 0 && pendingToolCalls.length === 0) {
|
|
4096
|
+
return;
|
|
4097
|
+
}
|
|
4098
|
+
messages.push({
|
|
4099
|
+
id: pendingMessageId || `assistant-${messages.length + 1}`,
|
|
4100
|
+
role: "assistant",
|
|
4101
|
+
text: "",
|
|
4102
|
+
timestamp: pendingTimestamp || (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
4103
|
+
toolCalls: pendingToolCalls.length > 0 ? pendingToolCalls : void 0,
|
|
4104
|
+
blocks: pendingBlocks.length > 0 ? pendingBlocks : void 0
|
|
4105
|
+
});
|
|
4106
|
+
pendingBlocks = [];
|
|
4107
|
+
pendingToolCalls = [];
|
|
4108
|
+
pendingMessageId = "";
|
|
4109
|
+
pendingTimestamp = "";
|
|
4110
|
+
};
|
|
4111
|
+
for (const entry of entries) {
|
|
4112
|
+
if (entry.type !== "message") continue;
|
|
4113
|
+
const role = entry.message?.role;
|
|
4114
|
+
if (role === "user") {
|
|
4115
|
+
flushPendingAssistant();
|
|
4116
|
+
const text2 = this.extractText(entry.message);
|
|
4117
|
+
if (!text2) continue;
|
|
4118
|
+
messages.push({
|
|
4119
|
+
id: entry.id,
|
|
4120
|
+
role,
|
|
4121
|
+
text: text2,
|
|
4122
|
+
timestamp: entry.timestamp
|
|
4123
|
+
});
|
|
4124
|
+
continue;
|
|
4125
|
+
}
|
|
4126
|
+
if (role !== "assistant") continue;
|
|
4127
|
+
const text = this.extractText(entry.message);
|
|
4128
|
+
const toolCalls = this.extractToolCalls(entry.message, toolResultsById);
|
|
4129
|
+
const blocks = this.extractBlocks(
|
|
4130
|
+
entry.id,
|
|
4131
|
+
entry.message,
|
|
4132
|
+
toolResultsById
|
|
4133
|
+
);
|
|
4134
|
+
if (!text) {
|
|
4135
|
+
if (blocks.length > 0) {
|
|
4136
|
+
pendingBlocks = [...pendingBlocks, ...blocks];
|
|
4137
|
+
}
|
|
4138
|
+
if (toolCalls?.length) {
|
|
4139
|
+
pendingToolCalls = this.mergeToolCalls(pendingToolCalls, toolCalls);
|
|
4140
|
+
}
|
|
4141
|
+
if (!pendingMessageId) {
|
|
4142
|
+
pendingMessageId = entry.id;
|
|
4143
|
+
pendingTimestamp = entry.timestamp;
|
|
4144
|
+
}
|
|
4145
|
+
continue;
|
|
4146
|
+
}
|
|
4147
|
+
const mergedBlocks = pendingBlocks.length > 0 ? [...pendingBlocks, ...blocks] : blocks;
|
|
4148
|
+
const mergedToolCalls = this.mergeToolCalls(pendingToolCalls, toolCalls);
|
|
4149
|
+
messages.push({
|
|
4150
|
+
id: entry.id,
|
|
4151
|
+
role,
|
|
4152
|
+
text,
|
|
4153
|
+
timestamp: entry.timestamp,
|
|
4154
|
+
toolCalls: mergedToolCalls.length > 0 ? mergedToolCalls : void 0,
|
|
4155
|
+
blocks: mergedBlocks.length > 0 ? mergedBlocks : void 0
|
|
4156
|
+
});
|
|
4157
|
+
pendingBlocks = [];
|
|
4158
|
+
pendingToolCalls = [];
|
|
4159
|
+
pendingMessageId = "";
|
|
4160
|
+
pendingTimestamp = "";
|
|
4161
|
+
}
|
|
4162
|
+
flushPendingAssistant();
|
|
4163
|
+
return messages.slice(-safeLimit);
|
|
4164
|
+
}
|
|
4165
|
+
findLatestSessionFile(channelDir) {
|
|
4166
|
+
if (!fs14.existsSync(channelDir)) return null;
|
|
4167
|
+
let stats;
|
|
4168
|
+
try {
|
|
4169
|
+
stats = fs14.statSync(channelDir);
|
|
4170
|
+
} catch {
|
|
4171
|
+
return null;
|
|
4172
|
+
}
|
|
4173
|
+
if (!stats.isDirectory()) return null;
|
|
4174
|
+
const files = fs14.readdirSync(channelDir).filter((file) => file.endsWith(".jsonl")).sort((a, b) => b.localeCompare(a));
|
|
4175
|
+
return files[0] ? path14.join(channelDir, files[0]) : null;
|
|
4176
|
+
}
|
|
4177
|
+
loadEntries(filePath) {
|
|
4178
|
+
try {
|
|
4179
|
+
const content = fs14.readFileSync(filePath, "utf-8");
|
|
4180
|
+
const fileEntries = parseSessionEntries(content);
|
|
4181
|
+
return fileEntries.filter(
|
|
4182
|
+
(entry) => entry.type !== "session"
|
|
4183
|
+
);
|
|
4184
|
+
} catch (err) {
|
|
4185
|
+
console.warn(`[ConversationService] Failed to load ${filePath}:`, err);
|
|
4186
|
+
return [];
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
extractText(message) {
|
|
4190
|
+
if (!message?.content) return "";
|
|
4191
|
+
if (typeof message.content === "string") return message.content.trim();
|
|
4192
|
+
if (!Array.isArray(message.content)) return "";
|
|
4193
|
+
return message.content.filter((item) => item?.type === "text").map((item) => typeof item?.text === "string" ? item.text : "").join("").trim();
|
|
4194
|
+
}
|
|
4195
|
+
extractTextPreview(entry, maxLen) {
|
|
4196
|
+
const text = this.extractText(entry.message);
|
|
4197
|
+
return text.length > maxLen ? `${text.slice(0, maxLen)}\u2026` : text;
|
|
4198
|
+
}
|
|
4199
|
+
collectToolResultStates(entries) {
|
|
4200
|
+
const toolResultsById = /* @__PURE__ */ new Map();
|
|
4201
|
+
for (const entry of entries) {
|
|
4202
|
+
if (entry.type !== "message") continue;
|
|
4203
|
+
if (entry.message?.role !== "toolResult") continue;
|
|
4204
|
+
if (typeof entry.message?.toolCallId !== "string" || !entry.message.toolCallId) {
|
|
4205
|
+
continue;
|
|
4206
|
+
}
|
|
4207
|
+
toolResultsById.set(entry.message.toolCallId, {
|
|
4208
|
+
isError: entry.message?.isError === true,
|
|
4209
|
+
result: this.extractToolResultValue(entry.message)
|
|
4210
|
+
});
|
|
4211
|
+
}
|
|
4212
|
+
return toolResultsById;
|
|
4213
|
+
}
|
|
4214
|
+
extractToolCalls(message, toolResultsById) {
|
|
4215
|
+
if (!Array.isArray(message?.content)) return void 0;
|
|
4216
|
+
const toolCalls = message.content.filter((item) => item?.type === "toolCall").map((item) => {
|
|
4217
|
+
const id = typeof item?.id === "string" && item.id ? item.id : "unknown";
|
|
4218
|
+
const name = typeof item?.name === "string" && item.name ? item.name : "unknown";
|
|
4219
|
+
const toolCall = {
|
|
4220
|
+
id,
|
|
4221
|
+
name,
|
|
4222
|
+
isError: toolResultsById.get(id)?.isError === true
|
|
4223
|
+
};
|
|
4224
|
+
if (name === "send_file") {
|
|
4225
|
+
const args = this.extractSendFileArguments(item?.arguments);
|
|
4226
|
+
if (args) {
|
|
4227
|
+
toolCall.arguments = args;
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
4230
|
+
return toolCall;
|
|
4231
|
+
});
|
|
4232
|
+
return toolCalls.length > 0 ? toolCalls : void 0;
|
|
4233
|
+
}
|
|
4234
|
+
extractBlocks(messageId, message, toolResultsById) {
|
|
4235
|
+
if (!Array.isArray(message?.content)) {
|
|
4236
|
+
return [];
|
|
4237
|
+
}
|
|
4238
|
+
const blocks = [];
|
|
4239
|
+
message.content.forEach((item, index) => {
|
|
4240
|
+
if (item?.type === "thinking") {
|
|
4241
|
+
const thinkingText = this.extractThinkingText(item);
|
|
4242
|
+
if (thinkingText) {
|
|
4243
|
+
blocks.push({
|
|
4244
|
+
id: `${messageId}-thinking-${index}`,
|
|
4245
|
+
type: "thinking",
|
|
4246
|
+
text: thinkingText
|
|
4247
|
+
});
|
|
4248
|
+
}
|
|
4249
|
+
return;
|
|
4250
|
+
}
|
|
4251
|
+
if (item?.type !== "toolCall") {
|
|
4252
|
+
return;
|
|
4253
|
+
}
|
|
4254
|
+
const toolCallId = typeof item?.id === "string" && item.id ? item.id : `${messageId}-tool-${index}`;
|
|
4255
|
+
const toolName = typeof item?.name === "string" && item.name ? item.name : "unknown";
|
|
4256
|
+
const toolResult = toolResultsById.get(toolCallId);
|
|
4257
|
+
const sendFileArgs = toolName === "send_file" ? this.extractSendFileArguments(item?.arguments) : void 0;
|
|
4258
|
+
if (toolName === "send_file" && !toolResult?.isError && sendFileArgs?.filePath) {
|
|
4259
|
+
blocks.push({
|
|
4260
|
+
id: toolCallId,
|
|
4261
|
+
type: "file",
|
|
4262
|
+
filename: this.getFileBaseName(sendFileArgs.filePath),
|
|
4263
|
+
filePath: sendFileArgs.filePath,
|
|
4264
|
+
caption: sendFileArgs.caption
|
|
4265
|
+
});
|
|
4266
|
+
return;
|
|
4267
|
+
}
|
|
4268
|
+
blocks.push({
|
|
4269
|
+
id: toolCallId,
|
|
4270
|
+
type: "tool",
|
|
4271
|
+
toolCallId,
|
|
4272
|
+
toolName,
|
|
4273
|
+
toolInput: item?.arguments,
|
|
4274
|
+
result: toolResult?.result,
|
|
4275
|
+
isError: toolResult?.isError === true,
|
|
4276
|
+
status: toolResult ? "done" : "running"
|
|
4277
|
+
});
|
|
4278
|
+
});
|
|
4279
|
+
return blocks;
|
|
4280
|
+
}
|
|
4281
|
+
extractThinkingText(item) {
|
|
4282
|
+
if (typeof item?.thinking === "string" && item.thinking.trim()) {
|
|
4283
|
+
return item.thinking.trim();
|
|
4284
|
+
}
|
|
4285
|
+
if (typeof item?.thinkingSignature !== "string" || !item.thinkingSignature) {
|
|
4286
|
+
return "";
|
|
4287
|
+
}
|
|
4288
|
+
try {
|
|
4289
|
+
const parsed = JSON.parse(item.thinkingSignature);
|
|
4290
|
+
if (!Array.isArray(parsed?.summary)) {
|
|
4291
|
+
return "";
|
|
4292
|
+
}
|
|
4293
|
+
return parsed.summary.map(
|
|
4294
|
+
(summaryItem) => typeof summaryItem?.text === "string" ? summaryItem.text.trim() : ""
|
|
4295
|
+
).filter(Boolean).join("\n\n").trim();
|
|
4296
|
+
} catch {
|
|
4297
|
+
return "";
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
extractToolResultValue(message) {
|
|
4301
|
+
if (!message?.content) {
|
|
4302
|
+
return void 0;
|
|
4303
|
+
}
|
|
4304
|
+
if (typeof message.content === "string") {
|
|
4305
|
+
return this.parsePossibleJson(message.content);
|
|
4306
|
+
}
|
|
4307
|
+
if (!Array.isArray(message.content)) {
|
|
4308
|
+
return message.content;
|
|
4309
|
+
}
|
|
4310
|
+
const textContent = message.content.filter((item) => item?.type === "text").map((item) => typeof item?.text === "string" ? item.text : "").join("").trim();
|
|
4311
|
+
if (textContent) {
|
|
4312
|
+
return this.parsePossibleJson(textContent);
|
|
4313
|
+
}
|
|
4314
|
+
return message.content;
|
|
4315
|
+
}
|
|
4316
|
+
parsePossibleJson(value) {
|
|
4317
|
+
const trimmed = value.trim();
|
|
4318
|
+
if (!trimmed) {
|
|
4319
|
+
return "";
|
|
4320
|
+
}
|
|
4321
|
+
try {
|
|
4322
|
+
return JSON.parse(trimmed);
|
|
4323
|
+
} catch {
|
|
4324
|
+
return trimmed;
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
extractSendFileArguments(rawArguments) {
|
|
4328
|
+
if (!rawArguments || typeof rawArguments !== "object") {
|
|
4329
|
+
return void 0;
|
|
3272
4330
|
}
|
|
4331
|
+
const maybeArgs = rawArguments;
|
|
4332
|
+
const filePath = typeof maybeArgs.filePath === "string" && maybeArgs.filePath ? maybeArgs.filePath : void 0;
|
|
4333
|
+
const caption = typeof maybeArgs.caption === "string" && maybeArgs.caption ? maybeArgs.caption : void 0;
|
|
4334
|
+
if (!filePath && !caption) {
|
|
4335
|
+
return void 0;
|
|
4336
|
+
}
|
|
4337
|
+
return {
|
|
4338
|
+
filePath,
|
|
4339
|
+
caption
|
|
4340
|
+
};
|
|
3273
4341
|
}
|
|
3274
|
-
|
|
3275
|
-
return
|
|
4342
|
+
hasVisibleSendFileToolCall(toolCalls) {
|
|
4343
|
+
return Boolean(
|
|
4344
|
+
toolCalls?.some(
|
|
4345
|
+
(toolCall) => toolCall.name === "send_file" && !toolCall.isError && typeof toolCall.arguments?.filePath === "string" && toolCall.arguments.filePath.length > 0
|
|
4346
|
+
)
|
|
4347
|
+
);
|
|
3276
4348
|
}
|
|
3277
|
-
|
|
3278
|
-
const
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
4349
|
+
mergeToolCalls(left, right) {
|
|
4350
|
+
const merged = [...left || [], ...right || []];
|
|
4351
|
+
const byId = /* @__PURE__ */ new Map();
|
|
4352
|
+
for (const toolCall of merged) {
|
|
4353
|
+
byId.set(toolCall.id, toolCall);
|
|
3282
4354
|
}
|
|
4355
|
+
return [...byId.values()];
|
|
3283
4356
|
}
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
4357
|
+
getFileBaseName(filePath) {
|
|
4358
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
4359
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
4360
|
+
return parts[parts.length - 1] || filePath;
|
|
3287
4361
|
}
|
|
3288
|
-
|
|
3289
|
-
|
|
4362
|
+
detectPlatform(channelId) {
|
|
4363
|
+
if (channelId.startsWith("telegram-")) return "telegram";
|
|
4364
|
+
if (channelId.startsWith("slack-")) return "slack";
|
|
4365
|
+
if (channelId.startsWith("scheduler-")) return "scheduler";
|
|
4366
|
+
return "web";
|
|
3290
4367
|
}
|
|
3291
|
-
|
|
3292
|
-
return
|
|
4368
|
+
isLegacyWebConversation(channelId) {
|
|
4369
|
+
return channelId.startsWith("web-");
|
|
3293
4370
|
}
|
|
3294
4371
|
};
|
|
3295
4372
|
|
|
3296
4373
|
// src/runtime/adapters/web.ts
|
|
3297
|
-
init_config();
|
|
3298
|
-
init_commands();
|
|
3299
|
-
import fs10 from "fs";
|
|
3300
|
-
import path10 from "path";
|
|
3301
|
-
import { WebSocketServer } from "ws";
|
|
3302
4374
|
function getPackConfig(rootDir) {
|
|
3303
|
-
const raw =
|
|
4375
|
+
const raw = fs15.readFileSync(path15.join(rootDir, "skillpack.json"), "utf-8");
|
|
3304
4376
|
return JSON.parse(raw);
|
|
3305
4377
|
}
|
|
3306
4378
|
function parseCommand(text) {
|
|
@@ -3322,15 +4394,30 @@ function getRuntimeConfigSignature(config) {
|
|
|
3322
4394
|
slackAppToken: config.adapters?.slack?.appToken || ""
|
|
3323
4395
|
});
|
|
3324
4396
|
}
|
|
4397
|
+
function parsePositiveInt(value, fallback) {
|
|
4398
|
+
const parsed = Number(value);
|
|
4399
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
4400
|
+
return fallback;
|
|
4401
|
+
}
|
|
4402
|
+
return Math.floor(parsed);
|
|
4403
|
+
}
|
|
4404
|
+
function resolveDownloadFilePath(rootDir, filePath) {
|
|
4405
|
+
const resolvedPath = path15.isAbsolute(filePath) ? path15.resolve(filePath) : path15.resolve(rootDir, filePath);
|
|
4406
|
+
return isWithinDirectory(rootDir, resolvedPath) ? resolvedPath : null;
|
|
4407
|
+
}
|
|
3325
4408
|
var WebAdapter = class {
|
|
3326
4409
|
name = "web";
|
|
3327
4410
|
wss = null;
|
|
3328
4411
|
agent = null;
|
|
3329
4412
|
ipcBroadcaster = null;
|
|
4413
|
+
conversationService = null;
|
|
4414
|
+
socketsByChannel = /* @__PURE__ */ new Map();
|
|
3330
4415
|
async start(ctx) {
|
|
3331
4416
|
const { agent, server, app, rootDir, lifecycle } = ctx;
|
|
3332
4417
|
this.agent = agent;
|
|
3333
4418
|
this.ipcBroadcaster = ctx.ipcBroadcaster ?? null;
|
|
4419
|
+
this.conversationService = new ConversationService(rootDir);
|
|
4420
|
+
const resultsQueryService = ctx.resultsQueryService ?? null;
|
|
3334
4421
|
const currentConf = configManager.getConfig();
|
|
3335
4422
|
let apiKey = currentConf.apiKey || "";
|
|
3336
4423
|
let currentProvider = currentConf.provider || "openai";
|
|
@@ -3448,12 +4535,42 @@ var WebAdapter = class {
|
|
|
3448
4535
|
app.delete("/api/chat", (_req, res) => {
|
|
3449
4536
|
res.json({ success: true });
|
|
3450
4537
|
});
|
|
3451
|
-
|
|
3452
|
-
const
|
|
3453
|
-
|
|
4538
|
+
const getWebConversations = () => {
|
|
4539
|
+
const activeChannels = new Set(agent.getActiveChannelIds());
|
|
4540
|
+
return this.conversationService.listConversations(activeChannels, {
|
|
4541
|
+
includeDefaultWeb: true,
|
|
4542
|
+
includeLegacyWeb: false,
|
|
4543
|
+
allowedPlatforms: ["web"]
|
|
4544
|
+
});
|
|
4545
|
+
};
|
|
4546
|
+
app.get("/api/conversations", (_req, res) => {
|
|
4547
|
+
res.json(getWebConversations());
|
|
4548
|
+
});
|
|
4549
|
+
app.post("/api/conversations", (_req, res) => {
|
|
4550
|
+
res.json({ channelId: DEFAULT_WEB_CHANNEL_ID });
|
|
4551
|
+
});
|
|
4552
|
+
app.get("/api/conversations/:channelId/messages", (req, res) => {
|
|
4553
|
+
if (req.params.channelId !== DEFAULT_WEB_CHANNEL_ID) {
|
|
4554
|
+
res.status(404).json({ error: "Conversation not found" });
|
|
4555
|
+
return;
|
|
4556
|
+
}
|
|
4557
|
+
res.json(
|
|
4558
|
+
this.conversationService.getMessages(
|
|
4559
|
+
req.params.channelId,
|
|
4560
|
+
parsePositiveInt(req.query.limit, 100)
|
|
4561
|
+
)
|
|
4562
|
+
);
|
|
3454
4563
|
});
|
|
3455
|
-
app.get("/api/
|
|
3456
|
-
|
|
4564
|
+
app.get("/api/results/artifacts", async (req, res) => {
|
|
4565
|
+
if (!resultsQueryService) {
|
|
4566
|
+
res.status(503).json({ error: "Results query service is not available" });
|
|
4567
|
+
return;
|
|
4568
|
+
}
|
|
4569
|
+
res.json(await resultsQueryService.listRecentArtifacts({
|
|
4570
|
+
channelId: typeof req.query.channelId === "string" ? req.query.channelId : void 0,
|
|
4571
|
+
limit: parsePositiveInt(req.query.limit, 100),
|
|
4572
|
+
offset: parsePositiveInt(req.query.offset, 0)
|
|
4573
|
+
}));
|
|
3457
4574
|
});
|
|
3458
4575
|
app.get("/api/files", (req, res) => {
|
|
3459
4576
|
const filePath = req.query.path;
|
|
@@ -3461,23 +4578,22 @@ var WebAdapter = class {
|
|
|
3461
4578
|
res.status(400).json({ error: "Missing 'path' query parameter" });
|
|
3462
4579
|
return;
|
|
3463
4580
|
}
|
|
3464
|
-
const resolvedPath =
|
|
3465
|
-
|
|
3466
|
-
if (!resolvedPath.startsWith(dataDir)) {
|
|
4581
|
+
const resolvedPath = resolveDownloadFilePath(rootDir, filePath);
|
|
4582
|
+
if (!resolvedPath) {
|
|
3467
4583
|
res.status(403).json({ error: "Access denied" });
|
|
3468
4584
|
return;
|
|
3469
4585
|
}
|
|
3470
|
-
if (!
|
|
4586
|
+
if (!fs15.existsSync(resolvedPath)) {
|
|
3471
4587
|
res.status(404).json({ error: "File not found" });
|
|
3472
4588
|
return;
|
|
3473
4589
|
}
|
|
3474
|
-
const filename =
|
|
4590
|
+
const filename = path15.basename(resolvedPath);
|
|
3475
4591
|
res.setHeader("Content-Type", "application/octet-stream");
|
|
3476
4592
|
res.setHeader(
|
|
3477
4593
|
"Content-Disposition",
|
|
3478
4594
|
`attachment; filename="${filename}"`
|
|
3479
4595
|
);
|
|
3480
|
-
|
|
4596
|
+
fs15.createReadStream(resolvedPath).pipe(res);
|
|
3481
4597
|
});
|
|
3482
4598
|
const getScheduler = () => {
|
|
3483
4599
|
const schedulerAdapter = ctx.adapterMap?.get("scheduler");
|
|
@@ -3571,7 +4687,8 @@ var WebAdapter = class {
|
|
|
3571
4687
|
ws.close();
|
|
3572
4688
|
return;
|
|
3573
4689
|
}
|
|
3574
|
-
const
|
|
4690
|
+
const requestedChannelId = url.searchParams.get("channelId");
|
|
4691
|
+
const channelId = requestedChannelId && requestedChannelId === DEFAULT_WEB_CHANNEL_ID ? requestedChannelId : DEFAULT_WEB_CHANNEL_ID;
|
|
3575
4692
|
this.handleWsConnection(ws, channelId, agent);
|
|
3576
4693
|
});
|
|
3577
4694
|
console.log("[WebAdapter] Started");
|
|
@@ -3584,12 +4701,29 @@ var WebAdapter = class {
|
|
|
3584
4701
|
this.wss.close();
|
|
3585
4702
|
this.wss = null;
|
|
3586
4703
|
}
|
|
4704
|
+
this.socketsByChannel.clear();
|
|
3587
4705
|
console.log("[WebAdapter] Stopped");
|
|
3588
4706
|
}
|
|
4707
|
+
async sendMessage(channelId, text) {
|
|
4708
|
+
const sockets = this.socketsByChannel.get(channelId);
|
|
4709
|
+
const activeSockets = [...sockets || []].filter(
|
|
4710
|
+
(socket) => socket.readyState === socket.OPEN
|
|
4711
|
+
);
|
|
4712
|
+
if (activeSockets.length === 0) {
|
|
4713
|
+
throw new Error(`[Web] No active WebSocket clients for channelId: ${channelId}`);
|
|
4714
|
+
}
|
|
4715
|
+
for (const socket of activeSockets) {
|
|
4716
|
+
sendWsEvent(socket, { type: "message_start", role: "assistant" });
|
|
4717
|
+
sendWsEvent(socket, { type: "text_delta", delta: text });
|
|
4718
|
+
sendWsEvent(socket, { type: "message_end", role: "assistant" });
|
|
4719
|
+
socket.send(JSON.stringify({ done: true }));
|
|
4720
|
+
}
|
|
4721
|
+
}
|
|
3589
4722
|
// -------------------------------------------------------------------------
|
|
3590
4723
|
// WebSocket message handler
|
|
3591
4724
|
// -------------------------------------------------------------------------
|
|
3592
4725
|
handleWsConnection(ws, channelId, agent) {
|
|
4726
|
+
this.addSocket(channelId, ws);
|
|
3593
4727
|
ws.on("message", async (data) => {
|
|
3594
4728
|
try {
|
|
3595
4729
|
const payload = JSON.parse(data.toString());
|
|
@@ -3619,154 +4753,27 @@ var WebAdapter = class {
|
|
|
3619
4753
|
}
|
|
3620
4754
|
});
|
|
3621
4755
|
ws.on("close", () => {
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
};
|
|
3626
|
-
|
|
3627
|
-
// src/runtime/adapters/ipc.ts
|
|
3628
|
-
init_config();
|
|
3629
|
-
|
|
3630
|
-
// src/runtime/services/conversation.ts
|
|
3631
|
-
import fs11 from "fs";
|
|
3632
|
-
import path11 from "path";
|
|
3633
|
-
import {
|
|
3634
|
-
parseSessionEntries
|
|
3635
|
-
} from "@mariozechner/pi-coding-agent";
|
|
3636
|
-
var ConversationService = class {
|
|
3637
|
-
constructor(rootDir) {
|
|
3638
|
-
this.rootDir = rootDir;
|
|
3639
|
-
}
|
|
3640
|
-
/**
|
|
3641
|
-
* Scan data/sessions and return conversation summaries sorted by recency.
|
|
3642
|
-
*/
|
|
3643
|
-
listConversations(activeChannels) {
|
|
3644
|
-
const sessionsDir = path11.resolve(this.rootDir, "data", "sessions");
|
|
3645
|
-
const channelIds = new Set(activeChannels);
|
|
3646
|
-
if (fs11.existsSync(sessionsDir)) {
|
|
3647
|
-
for (const entry of fs11.readdirSync(sessionsDir)) {
|
|
3648
|
-
const channelDir = path11.join(sessionsDir, entry);
|
|
3649
|
-
try {
|
|
3650
|
-
if (fs11.statSync(channelDir).isDirectory()) {
|
|
3651
|
-
channelIds.add(entry);
|
|
3652
|
-
}
|
|
3653
|
-
} catch {
|
|
3654
|
-
}
|
|
3655
|
-
}
|
|
3656
|
-
}
|
|
3657
|
-
const results = [];
|
|
3658
|
-
for (const channelId of channelIds) {
|
|
3659
|
-
const channelDir = path11.join(sessionsDir, channelId);
|
|
3660
|
-
const sessionFile = this.findLatestSessionFile(channelDir);
|
|
3661
|
-
let messageCount = 0;
|
|
3662
|
-
let lastMessageAt = "";
|
|
3663
|
-
let lastMessagePreview = "";
|
|
3664
|
-
if (sessionFile) {
|
|
3665
|
-
const entries = this.loadEntries(sessionFile);
|
|
3666
|
-
const messages = entries.filter(
|
|
3667
|
-
(entry) => entry.type === "message"
|
|
3668
|
-
);
|
|
3669
|
-
messageCount = messages.length;
|
|
3670
|
-
const lastMessage = messages[messages.length - 1];
|
|
3671
|
-
if (lastMessage) {
|
|
3672
|
-
lastMessageAt = lastMessage.timestamp;
|
|
3673
|
-
lastMessagePreview = this.extractTextPreview(lastMessage, 100);
|
|
3674
|
-
}
|
|
4756
|
+
this.removeSocket(channelId, ws);
|
|
4757
|
+
if (channelId !== DEFAULT_WEB_CHANNEL_ID) {
|
|
4758
|
+
agent.dispose(channelId);
|
|
3675
4759
|
}
|
|
3676
|
-
results.push({
|
|
3677
|
-
channelId,
|
|
3678
|
-
platform: this.detectPlatform(channelId),
|
|
3679
|
-
sessionFile,
|
|
3680
|
-
messageCount,
|
|
3681
|
-
lastMessageAt,
|
|
3682
|
-
lastMessagePreview
|
|
3683
|
-
});
|
|
3684
|
-
}
|
|
3685
|
-
return results.sort((a, b) => {
|
|
3686
|
-
const recency = (b.lastMessageAt || "").localeCompare(a.lastMessageAt || "");
|
|
3687
|
-
if (recency !== 0) return recency;
|
|
3688
|
-
return a.channelId.localeCompare(b.channelId);
|
|
3689
4760
|
});
|
|
3690
4761
|
}
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
const channelDir = path11.resolve(
|
|
3696
|
-
this.rootDir,
|
|
3697
|
-
"data",
|
|
3698
|
-
"sessions",
|
|
3699
|
-
channelId
|
|
3700
|
-
);
|
|
3701
|
-
const sessionFile = this.findLatestSessionFile(channelDir);
|
|
3702
|
-
if (!sessionFile) return [];
|
|
3703
|
-
const safeLimit = Number.isFinite(limit) ? Math.max(0, Math.floor(limit)) : 100;
|
|
3704
|
-
if (safeLimit === 0) return [];
|
|
3705
|
-
const entries = this.loadEntries(sessionFile);
|
|
3706
|
-
const messages = [];
|
|
3707
|
-
for (const entry of entries) {
|
|
3708
|
-
if (entry.type !== "message") continue;
|
|
3709
|
-
const role = entry.message?.role;
|
|
3710
|
-
if (role !== "user" && role !== "assistant") continue;
|
|
3711
|
-
const text = this.extractText(entry.message);
|
|
3712
|
-
if (!text) continue;
|
|
3713
|
-
const toolCalls = role === "assistant" ? this.extractToolCallSummaries(entry.message) : void 0;
|
|
3714
|
-
messages.push({
|
|
3715
|
-
id: entry.id,
|
|
3716
|
-
role,
|
|
3717
|
-
text,
|
|
3718
|
-
timestamp: entry.timestamp,
|
|
3719
|
-
toolCalls
|
|
3720
|
-
});
|
|
3721
|
-
}
|
|
3722
|
-
return messages.slice(-safeLimit);
|
|
4762
|
+
addSocket(channelId, ws) {
|
|
4763
|
+
const sockets = this.socketsByChannel.get(channelId) ?? /* @__PURE__ */ new Set();
|
|
4764
|
+
sockets.add(ws);
|
|
4765
|
+
this.socketsByChannel.set(channelId, sockets);
|
|
3723
4766
|
}
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
stats = fs11.statSync(channelDir);
|
|
3729
|
-
} catch {
|
|
3730
|
-
return null;
|
|
4767
|
+
removeSocket(channelId, ws) {
|
|
4768
|
+
const sockets = this.socketsByChannel.get(channelId);
|
|
4769
|
+
if (!sockets) {
|
|
4770
|
+
return;
|
|
3731
4771
|
}
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
}
|
|
3736
|
-
loadEntries(filePath) {
|
|
3737
|
-
try {
|
|
3738
|
-
const content = fs11.readFileSync(filePath, "utf-8");
|
|
3739
|
-
const fileEntries = parseSessionEntries(content);
|
|
3740
|
-
return fileEntries.filter((entry) => entry.type !== "session");
|
|
3741
|
-
} catch (err) {
|
|
3742
|
-
console.warn(`[ConversationService] Failed to load ${filePath}:`, err);
|
|
3743
|
-
return [];
|
|
4772
|
+
sockets.delete(ws);
|
|
4773
|
+
if (sockets.size === 0) {
|
|
4774
|
+
this.socketsByChannel.delete(channelId);
|
|
3744
4775
|
}
|
|
3745
4776
|
}
|
|
3746
|
-
extractText(message) {
|
|
3747
|
-
if (!message?.content) return "";
|
|
3748
|
-
if (typeof message.content === "string") return message.content.trim();
|
|
3749
|
-
if (!Array.isArray(message.content)) return "";
|
|
3750
|
-
return message.content.filter((item) => item?.type === "text").map((item) => typeof item?.text === "string" ? item.text : "").join("").trim();
|
|
3751
|
-
}
|
|
3752
|
-
extractTextPreview(entry, maxLen) {
|
|
3753
|
-
const text = this.extractText(entry.message);
|
|
3754
|
-
return text.length > maxLen ? `${text.slice(0, maxLen)}\u2026` : text;
|
|
3755
|
-
}
|
|
3756
|
-
extractToolCallSummaries(message) {
|
|
3757
|
-
if (!Array.isArray(message?.content)) return void 0;
|
|
3758
|
-
const toolCalls = message.content.filter((item) => item?.type === "toolCall").map((item) => ({
|
|
3759
|
-
name: typeof item?.name === "string" && item.name ? item.name : "unknown",
|
|
3760
|
-
isError: false
|
|
3761
|
-
}));
|
|
3762
|
-
return toolCalls.length > 0 ? toolCalls : void 0;
|
|
3763
|
-
}
|
|
3764
|
-
detectPlatform(channelId) {
|
|
3765
|
-
if (channelId.startsWith("telegram-")) return "telegram";
|
|
3766
|
-
if (channelId.startsWith("slack-")) return "slack";
|
|
3767
|
-
if (channelId.startsWith("scheduler-")) return "scheduler";
|
|
3768
|
-
return "web";
|
|
3769
|
-
}
|
|
3770
4777
|
};
|
|
3771
4778
|
|
|
3772
4779
|
// src/runtime/adapters/ipc.ts
|
|
@@ -3777,6 +4784,7 @@ var IpcAdapter = class {
|
|
|
3777
4784
|
rootDir = "";
|
|
3778
4785
|
adapterMap = null;
|
|
3779
4786
|
conversationService = null;
|
|
4787
|
+
resultsQueryService = null;
|
|
3780
4788
|
createdChannels = /* @__PURE__ */ new Set();
|
|
3781
4789
|
messageListener;
|
|
3782
4790
|
started = false;
|
|
@@ -3788,6 +4796,7 @@ var IpcAdapter = class {
|
|
|
3788
4796
|
this.rootDir = ctx.rootDir;
|
|
3789
4797
|
this.adapterMap = ctx.adapterMap ?? null;
|
|
3790
4798
|
this.conversationService = new ConversationService(ctx.rootDir);
|
|
4799
|
+
this.resultsQueryService = ctx.resultsQueryService ?? null;
|
|
3791
4800
|
this.messageListener = (message) => {
|
|
3792
4801
|
if (!this.isIpcRequest(message)) return;
|
|
3793
4802
|
void this.handleRequest(message);
|
|
@@ -3846,12 +4855,15 @@ var IpcAdapter = class {
|
|
|
3846
4855
|
for (const channelId of this.createdChannels) {
|
|
3847
4856
|
activeChannels.add(channelId);
|
|
3848
4857
|
}
|
|
3849
|
-
const conversations = this.conversationService.listConversations(activeChannels
|
|
4858
|
+
const conversations = this.conversationService.listConversations(activeChannels, {
|
|
4859
|
+
includeDefaultWeb: true,
|
|
4860
|
+
includeLegacyWeb: false
|
|
4861
|
+
});
|
|
3850
4862
|
this.reply(request.id, conversations);
|
|
3851
4863
|
return;
|
|
3852
4864
|
}
|
|
3853
4865
|
case "create_conversation": {
|
|
3854
|
-
const channelId =
|
|
4866
|
+
const channelId = DEFAULT_WEB_CHANNEL_ID;
|
|
3855
4867
|
this.createdChannels.add(channelId);
|
|
3856
4868
|
this.reply(request.id, { channelId });
|
|
3857
4869
|
return;
|
|
@@ -3868,6 +4880,18 @@ var IpcAdapter = class {
|
|
|
3868
4880
|
this.reply(request.id, messages);
|
|
3869
4881
|
return;
|
|
3870
4882
|
}
|
|
4883
|
+
case "get_recent_artifacts": {
|
|
4884
|
+
if (!this.resultsQueryService) {
|
|
4885
|
+
this.replyError(request.id, "Results query service is not available");
|
|
4886
|
+
return;
|
|
4887
|
+
}
|
|
4888
|
+
this.reply(request.id, await this.resultsQueryService.listRecentArtifacts({
|
|
4889
|
+
channelId: request.channelId,
|
|
4890
|
+
limit: request.limit,
|
|
4891
|
+
offset: request.offset
|
|
4892
|
+
}));
|
|
4893
|
+
return;
|
|
4894
|
+
}
|
|
3871
4895
|
case "send_message": {
|
|
3872
4896
|
if (!request.channelId || typeof request.channelId !== "string") {
|
|
3873
4897
|
this.replyError(request.id, "channelId is required");
|
|
@@ -3950,6 +4974,48 @@ var IpcAdapter = class {
|
|
|
3950
4974
|
this.reply(request.id, result);
|
|
3951
4975
|
return;
|
|
3952
4976
|
}
|
|
4977
|
+
case "update_scheduled_job": {
|
|
4978
|
+
const scheduler = this.getSchedulerAdapter();
|
|
4979
|
+
if (!scheduler) {
|
|
4980
|
+
this.replyError(request.id, "Scheduler adapter is not available");
|
|
4981
|
+
return;
|
|
4982
|
+
}
|
|
4983
|
+
const result = scheduler.updateJob(request.name, request.updates);
|
|
4984
|
+
if (!result.success) {
|
|
4985
|
+
this.replyError(request.id, result.message);
|
|
4986
|
+
return;
|
|
4987
|
+
}
|
|
4988
|
+
this.reply(request.id, result);
|
|
4989
|
+
return;
|
|
4990
|
+
}
|
|
4991
|
+
case "set_scheduled_job_enabled": {
|
|
4992
|
+
const scheduler = this.getSchedulerAdapter();
|
|
4993
|
+
if (!scheduler) {
|
|
4994
|
+
this.replyError(request.id, "Scheduler adapter is not available");
|
|
4995
|
+
return;
|
|
4996
|
+
}
|
|
4997
|
+
const result = scheduler.setEnabled(request.name, request.enabled);
|
|
4998
|
+
if (!result.success) {
|
|
4999
|
+
this.replyError(request.id, result.message);
|
|
5000
|
+
return;
|
|
5001
|
+
}
|
|
5002
|
+
this.reply(request.id, result);
|
|
5003
|
+
return;
|
|
5004
|
+
}
|
|
5005
|
+
case "trigger_scheduled_job": {
|
|
5006
|
+
const scheduler = this.getSchedulerAdapter();
|
|
5007
|
+
if (!scheduler) {
|
|
5008
|
+
this.replyError(request.id, "Scheduler adapter is not available");
|
|
5009
|
+
return;
|
|
5010
|
+
}
|
|
5011
|
+
const result = await scheduler.triggerJob(request.name);
|
|
5012
|
+
if (!result.success) {
|
|
5013
|
+
this.replyError(request.id, result.message);
|
|
5014
|
+
return;
|
|
5015
|
+
}
|
|
5016
|
+
this.reply(request.id, result);
|
|
5017
|
+
return;
|
|
5018
|
+
}
|
|
3953
5019
|
case "remove_scheduled_job": {
|
|
3954
5020
|
const scheduler = this.getSchedulerAdapter();
|
|
3955
5021
|
if (!scheduler) {
|
|
@@ -3996,9 +5062,6 @@ var IpcAdapter = class {
|
|
|
3996
5062
|
}
|
|
3997
5063
|
};
|
|
3998
5064
|
|
|
3999
|
-
// src/runtime/server.ts
|
|
4000
|
-
init_config();
|
|
4001
|
-
|
|
4002
5065
|
// src/runtime/lifecycle.ts
|
|
4003
5066
|
var SHUTDOWN_EXIT_CODE = 64;
|
|
4004
5067
|
var RESTART_EXIT_CODE = 75;
|
|
@@ -4072,28 +5135,28 @@ var Lifecycle = class {
|
|
|
4072
5135
|
|
|
4073
5136
|
// src/runtime/registry.ts
|
|
4074
5137
|
import crypto from "crypto";
|
|
4075
|
-
import
|
|
5138
|
+
import fs16 from "fs";
|
|
4076
5139
|
import os from "os";
|
|
4077
|
-
import
|
|
4078
|
-
var SKILLPACK_HOME =
|
|
4079
|
-
var LEGACY_REGISTRY_FILE =
|
|
4080
|
-
var REGISTRY_DIR =
|
|
5140
|
+
import path16 from "path";
|
|
5141
|
+
var SKILLPACK_HOME = path16.join(os.homedir(), ".skillpack");
|
|
5142
|
+
var LEGACY_REGISTRY_FILE = path16.join(SKILLPACK_HOME, "registry.json");
|
|
5143
|
+
var REGISTRY_DIR = path16.join(SKILLPACK_HOME, "registry.d");
|
|
4081
5144
|
var migrationChecked = false;
|
|
4082
5145
|
function ensureHomeDir() {
|
|
4083
|
-
if (!
|
|
4084
|
-
|
|
5146
|
+
if (!fs16.existsSync(SKILLPACK_HOME)) {
|
|
5147
|
+
fs16.mkdirSync(SKILLPACK_HOME, { recursive: true });
|
|
4085
5148
|
}
|
|
4086
5149
|
}
|
|
4087
5150
|
function ensureRegistryDir() {
|
|
4088
5151
|
ensureHomeDir();
|
|
4089
|
-
if (!
|
|
4090
|
-
|
|
5152
|
+
if (!fs16.existsSync(REGISTRY_DIR)) {
|
|
5153
|
+
fs16.mkdirSync(REGISTRY_DIR, { recursive: true });
|
|
4091
5154
|
}
|
|
4092
5155
|
}
|
|
4093
5156
|
function canonicalizeDir(dir) {
|
|
4094
|
-
const resolved =
|
|
5157
|
+
const resolved = path16.resolve(dir);
|
|
4095
5158
|
try {
|
|
4096
|
-
return
|
|
5159
|
+
return fs16.realpathSync(resolved);
|
|
4097
5160
|
} catch {
|
|
4098
5161
|
return resolved;
|
|
4099
5162
|
}
|
|
@@ -4102,7 +5165,7 @@ function hashDir(dir) {
|
|
|
4102
5165
|
return crypto.createHash("md5").update(canonicalizeDir(dir)).digest("hex");
|
|
4103
5166
|
}
|
|
4104
5167
|
function getEntryPathForCanonicalDir(dir) {
|
|
4105
|
-
return
|
|
5168
|
+
return path16.join(REGISTRY_DIR, `${hashDir(dir)}.json`);
|
|
4106
5169
|
}
|
|
4107
5170
|
function getEntryPath(dir) {
|
|
4108
5171
|
ensureRegistryReady();
|
|
@@ -4110,11 +5173,11 @@ function getEntryPath(dir) {
|
|
|
4110
5173
|
}
|
|
4111
5174
|
function listEntryFiles() {
|
|
4112
5175
|
ensureRegistryReady();
|
|
4113
|
-
return
|
|
5176
|
+
return fs16.readdirSync(REGISTRY_DIR).filter((file) => file.endsWith(".json")).sort().map((file) => path16.join(REGISTRY_DIR, file));
|
|
4114
5177
|
}
|
|
4115
5178
|
function readEntryFile(filePath) {
|
|
4116
5179
|
try {
|
|
4117
|
-
const raw =
|
|
5180
|
+
const raw = fs16.readFileSync(filePath, "utf-8");
|
|
4118
5181
|
const data = JSON.parse(raw);
|
|
4119
5182
|
if (typeof data?.dir !== "string" || typeof data?.name !== "string" || typeof data?.version !== "string" || typeof data?.port !== "number" || typeof data?.pid !== "number" && data?.pid !== null || data?.status !== "running" && data?.status !== "stopped") {
|
|
4120
5183
|
return null;
|
|
@@ -4147,8 +5210,8 @@ function writeEntryFile(entry) {
|
|
|
4147
5210
|
};
|
|
4148
5211
|
const entryPath = getEntryPathForCanonicalDir(normalized.dir);
|
|
4149
5212
|
const tmpPath = createTmpPath(entryPath);
|
|
4150
|
-
|
|
4151
|
-
|
|
5213
|
+
fs16.writeFileSync(tmpPath, JSON.stringify(normalized, null, 2), "utf-8");
|
|
5214
|
+
fs16.renameSync(tmpPath, entryPath);
|
|
4152
5215
|
}
|
|
4153
5216
|
function migrateLegacyRegistryIfNeeded() {
|
|
4154
5217
|
if (migrationChecked) {
|
|
@@ -4156,14 +5219,14 @@ function migrateLegacyRegistryIfNeeded() {
|
|
|
4156
5219
|
}
|
|
4157
5220
|
migrationChecked = true;
|
|
4158
5221
|
ensureRegistryDir();
|
|
4159
|
-
if (!
|
|
5222
|
+
if (!fs16.existsSync(LEGACY_REGISTRY_FILE)) {
|
|
4160
5223
|
return;
|
|
4161
5224
|
}
|
|
4162
5225
|
if (listEntryFiles().length > 0) {
|
|
4163
5226
|
return;
|
|
4164
5227
|
}
|
|
4165
5228
|
try {
|
|
4166
|
-
const raw =
|
|
5229
|
+
const raw = fs16.readFileSync(LEGACY_REGISTRY_FILE, "utf-8");
|
|
4167
5230
|
const data = JSON.parse(raw);
|
|
4168
5231
|
const packs = Array.isArray(data?.packs) ? data.packs : [];
|
|
4169
5232
|
for (const pack of packs) {
|
|
@@ -4176,7 +5239,7 @@ function migrateLegacyRegistryIfNeeded() {
|
|
|
4176
5239
|
} catch {
|
|
4177
5240
|
}
|
|
4178
5241
|
}
|
|
4179
|
-
|
|
5242
|
+
fs16.renameSync(LEGACY_REGISTRY_FILE, `${LEGACY_REGISTRY_FILE}.legacy`);
|
|
4180
5243
|
} catch (err) {
|
|
4181
5244
|
console.warn(" [Registry] Failed to migrate legacy registry.json:", err);
|
|
4182
5245
|
}
|
|
@@ -4229,13 +5292,13 @@ function deregister(dir, pid) {
|
|
|
4229
5292
|
}
|
|
4230
5293
|
|
|
4231
5294
|
// src/runtime/server.ts
|
|
4232
|
-
var __dirname =
|
|
5295
|
+
var __dirname = path18.dirname(fileURLToPath2(import.meta.url));
|
|
4233
5296
|
async function startServer(options) {
|
|
4234
5297
|
const {
|
|
4235
5298
|
rootDir,
|
|
4236
5299
|
host = process.env.HOST || "127.0.0.1",
|
|
4237
5300
|
port = Number(process.env.PORT) || 26313,
|
|
4238
|
-
|
|
5301
|
+
runtimeMode = process.env.SKILLPACK_RUNTIME_MODE === "embedded" ? "embedded" : "standalone"
|
|
4239
5302
|
} = options;
|
|
4240
5303
|
const dataConfig = configManager.load(rootDir);
|
|
4241
5304
|
const apiKey = dataConfig.apiKey || "";
|
|
@@ -4245,8 +5308,8 @@ async function startServer(options) {
|
|
|
4245
5308
|
const baseUrl = dataConfig.baseUrl?.trim() || void 0;
|
|
4246
5309
|
const modelId = dataConfig.modelId?.trim() || (SUPPORTED_PROVIDERS[provider]?.defaultModelId ?? SUPPORTED_PROVIDERS.openai.defaultModelId);
|
|
4247
5310
|
const apiProtocol = dataConfig.apiProtocol;
|
|
4248
|
-
const packageRoot =
|
|
4249
|
-
const webDir =
|
|
5311
|
+
const packageRoot = path18.resolve(__dirname, "..");
|
|
5312
|
+
const webDir = fs19.existsSync(path18.join(rootDir, "web")) ? path18.join(rootDir, "web") : path18.join(packageRoot, "web");
|
|
4250
5313
|
const app = express();
|
|
4251
5314
|
app.use(express.json());
|
|
4252
5315
|
app.use(express.static(webDir));
|
|
@@ -4264,6 +5327,13 @@ async function startServer(options) {
|
|
|
4264
5327
|
});
|
|
4265
5328
|
});
|
|
4266
5329
|
const lifecycle = new Lifecycle(server);
|
|
5330
|
+
const resultStore = new ResultStore(rootDir);
|
|
5331
|
+
const artifactSnapshotService = new ArtifactSnapshotService(rootDir);
|
|
5332
|
+
const artifactPersistenceService = new ArtifactPersistenceService(
|
|
5333
|
+
artifactSnapshotService,
|
|
5334
|
+
resultStore
|
|
5335
|
+
);
|
|
5336
|
+
const resultsQueryService = new ResultsQueryService(resultStore);
|
|
4267
5337
|
const agent = new PackAgent({
|
|
4268
5338
|
apiKey,
|
|
4269
5339
|
rootDir,
|
|
@@ -4271,12 +5341,14 @@ async function startServer(options) {
|
|
|
4271
5341
|
modelId,
|
|
4272
5342
|
baseUrl,
|
|
4273
5343
|
apiProtocol,
|
|
4274
|
-
lifecycleHandler: lifecycle
|
|
5344
|
+
lifecycleHandler: lifecycle,
|
|
5345
|
+
artifactPersistenceService
|
|
4275
5346
|
});
|
|
4276
5347
|
const adapters = [];
|
|
4277
5348
|
const adapterMap = /* @__PURE__ */ new Map();
|
|
4278
5349
|
const hasIpcChannel = typeof process.send === "function";
|
|
4279
5350
|
const ipcAdapter = new IpcAdapter();
|
|
5351
|
+
const webEnabled = runtimeMode === "standalone";
|
|
4280
5352
|
if (hasIpcChannel) {
|
|
4281
5353
|
await ipcAdapter.start({
|
|
4282
5354
|
agent,
|
|
@@ -4284,24 +5356,28 @@ async function startServer(options) {
|
|
|
4284
5356
|
app,
|
|
4285
5357
|
rootDir,
|
|
4286
5358
|
lifecycle,
|
|
4287
|
-
adapterMap
|
|
5359
|
+
adapterMap,
|
|
5360
|
+
resultsQueryService
|
|
4288
5361
|
});
|
|
4289
5362
|
adapters.push(ipcAdapter);
|
|
4290
5363
|
adapterMap.set(ipcAdapter.name, ipcAdapter);
|
|
4291
5364
|
}
|
|
4292
5365
|
const ipcBroadcaster = hasIpcChannel ? ipcAdapter : void 0;
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
5366
|
+
if (webEnabled) {
|
|
5367
|
+
const webAdapter = new WebAdapter();
|
|
5368
|
+
await webAdapter.start({
|
|
5369
|
+
agent,
|
|
5370
|
+
server,
|
|
5371
|
+
app,
|
|
5372
|
+
rootDir,
|
|
5373
|
+
lifecycle,
|
|
5374
|
+
adapterMap,
|
|
5375
|
+
ipcBroadcaster,
|
|
5376
|
+
resultsQueryService
|
|
5377
|
+
});
|
|
5378
|
+
adapters.push(webAdapter);
|
|
5379
|
+
adapterMap.set(webAdapter.name, webAdapter);
|
|
5380
|
+
}
|
|
4305
5381
|
if (dataConfig.adapters?.telegram?.token) {
|
|
4306
5382
|
try {
|
|
4307
5383
|
const { TelegramAdapter: TelegramAdapter2 } = await Promise.resolve().then(() => (init_telegram(), telegram_exports));
|
|
@@ -4315,7 +5391,8 @@ async function startServer(options) {
|
|
|
4315
5391
|
rootDir,
|
|
4316
5392
|
lifecycle,
|
|
4317
5393
|
adapterMap,
|
|
4318
|
-
ipcBroadcaster
|
|
5394
|
+
ipcBroadcaster,
|
|
5395
|
+
resultsQueryService
|
|
4319
5396
|
});
|
|
4320
5397
|
adapters.push(telegramAdapter);
|
|
4321
5398
|
adapterMap.set(telegramAdapter.name, telegramAdapter);
|
|
@@ -4343,7 +5420,8 @@ async function startServer(options) {
|
|
|
4343
5420
|
rootDir,
|
|
4344
5421
|
lifecycle,
|
|
4345
5422
|
adapterMap,
|
|
4346
|
-
ipcBroadcaster
|
|
5423
|
+
ipcBroadcaster,
|
|
5424
|
+
resultsQueryService
|
|
4347
5425
|
});
|
|
4348
5426
|
adapters.push(slackAdapter);
|
|
4349
5427
|
adapterMap.set(slackAdapter.name, slackAdapter);
|
|
@@ -4363,7 +5441,6 @@ async function startServer(options) {
|
|
|
4363
5441
|
}
|
|
4364
5442
|
await adapter.sendMessage(channelId, text);
|
|
4365
5443
|
};
|
|
4366
|
-
const scheduledJobs = dataConfig.scheduledJobs || [];
|
|
4367
5444
|
let schedulerAdapter = null;
|
|
4368
5445
|
try {
|
|
4369
5446
|
const { SchedulerAdapter: SchedulerAdapter2 } = await Promise.resolve().then(() => (init_scheduler(), scheduler_exports));
|
|
@@ -4375,13 +5452,11 @@ async function startServer(options) {
|
|
|
4375
5452
|
rootDir,
|
|
4376
5453
|
lifecycle,
|
|
4377
5454
|
notify: notifyFn,
|
|
4378
|
-
adapterMap
|
|
5455
|
+
adapterMap,
|
|
5456
|
+
resultsQueryService
|
|
4379
5457
|
});
|
|
4380
5458
|
adapters.push(schedulerAdapter);
|
|
4381
5459
|
adapterMap.set(schedulerAdapter.name, schedulerAdapter);
|
|
4382
|
-
if (scheduledJobs.length > 0) {
|
|
4383
|
-
console.log(`[Server] Scheduler started with ${scheduledJobs.length} job(s)`);
|
|
4384
|
-
}
|
|
4385
5460
|
} catch (err) {
|
|
4386
5461
|
console.error("[Scheduler] Failed to start:", err);
|
|
4387
5462
|
}
|
|
@@ -4389,34 +5464,35 @@ async function startServer(options) {
|
|
|
4389
5464
|
agent.setScheduler(schedulerAdapter);
|
|
4390
5465
|
}
|
|
4391
5466
|
lifecycle.registerAdapters(adapters);
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
console.log(`
|
|
5467
|
+
const announceReady = (actualPort) => {
|
|
5468
|
+
if (webEnabled) {
|
|
5469
|
+
const url = `http://${host}:${actualPort}`;
|
|
5470
|
+
console.log(`
|
|
4397
5471
|
Skills Pack Server`);
|
|
4398
|
-
|
|
5472
|
+
console.log(` Running at ${url}
|
|
4399
5473
|
`);
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
if (hasIpcChannel) {
|
|
4411
|
-
ipcAdapter.notifyReady(typeof actualPort === "number" ? actualPort : port);
|
|
4412
|
-
}
|
|
4413
|
-
if (!daemonRun) {
|
|
5474
|
+
try {
|
|
5475
|
+
register({
|
|
5476
|
+
dir: canonicalRootDir,
|
|
5477
|
+
name: packConfig.name,
|
|
5478
|
+
version: packConfig.version,
|
|
5479
|
+
port: actualPort
|
|
5480
|
+
});
|
|
5481
|
+
} catch (err) {
|
|
5482
|
+
console.warn(" [Registry] Could not register pack:", err);
|
|
5483
|
+
}
|
|
4414
5484
|
const cmd = process.platform === "darwin" ? `open ${url}` : process.platform === "win32" ? `start ${url}` : `xdg-open ${url}`;
|
|
4415
5485
|
exec(cmd, (err) => {
|
|
4416
5486
|
if (err) console.warn(` Could not open browser: ${err.message}`);
|
|
4417
5487
|
});
|
|
5488
|
+
} else {
|
|
5489
|
+
console.log("\n Skills Pack Server");
|
|
5490
|
+
console.log(" Running in embedded mode (IPC only)\n");
|
|
4418
5491
|
}
|
|
4419
|
-
|
|
5492
|
+
if (hasIpcChannel) {
|
|
5493
|
+
ipcAdapter.notifyReady(actualPort);
|
|
5494
|
+
}
|
|
5495
|
+
};
|
|
4420
5496
|
process.on("SIGINT", () => {
|
|
4421
5497
|
deregister(canonicalRootDir, process.pid);
|
|
4422
5498
|
void lifecycle.requestShutdown("signal");
|
|
@@ -4425,22 +5501,30 @@ async function startServer(options) {
|
|
|
4425
5501
|
deregister(canonicalRootDir, process.pid);
|
|
4426
5502
|
void lifecycle.requestShutdown("signal");
|
|
4427
5503
|
});
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
5504
|
+
if (webEnabled) {
|
|
5505
|
+
const actualPort = await new Promise((resolve, reject) => {
|
|
5506
|
+
function tryListen(listenPort) {
|
|
5507
|
+
server.listen(listenPort, host);
|
|
5508
|
+
server.once("error", (err) => {
|
|
5509
|
+
if (err.code === "EADDRINUSE") {
|
|
5510
|
+
console.log(` Port ${listenPort} is in use, trying ${listenPort + 1}...`);
|
|
5511
|
+
server.close();
|
|
5512
|
+
tryListen(listenPort + 1);
|
|
5513
|
+
} else {
|
|
5514
|
+
reject(err);
|
|
5515
|
+
}
|
|
5516
|
+
});
|
|
5517
|
+
server.once("listening", () => {
|
|
5518
|
+
const address = server.address();
|
|
5519
|
+
resolve(typeof address === "string" ? listenPort : address?.port ?? listenPort);
|
|
5520
|
+
});
|
|
5521
|
+
}
|
|
5522
|
+
tryListen(port);
|
|
5523
|
+
});
|
|
5524
|
+
announceReady(actualPort);
|
|
5525
|
+
} else {
|
|
5526
|
+
announceReady(0);
|
|
5527
|
+
}
|
|
4444
5528
|
await new Promise(() => {
|
|
4445
5529
|
});
|
|
4446
5530
|
}
|
|
@@ -4457,23 +5541,23 @@ function findMissingSkills(workDir, config) {
|
|
|
4457
5541
|
});
|
|
4458
5542
|
}
|
|
4459
5543
|
function copyStartTemplates2(workDir) {
|
|
4460
|
-
const templateDir =
|
|
5544
|
+
const templateDir = path19.resolve(
|
|
4461
5545
|
new URL("../templates", import.meta.url).pathname
|
|
4462
5546
|
);
|
|
4463
5547
|
for (const file of ["start.sh", "start.bat"]) {
|
|
4464
|
-
const src =
|
|
4465
|
-
const dest =
|
|
4466
|
-
if (
|
|
4467
|
-
|
|
5548
|
+
const src = path19.join(templateDir, file);
|
|
5549
|
+
const dest = path19.join(workDir, file);
|
|
5550
|
+
if (fs20.existsSync(src)) {
|
|
5551
|
+
fs20.copyFileSync(src, dest);
|
|
4468
5552
|
if (file === "start.sh") {
|
|
4469
|
-
|
|
5553
|
+
fs20.chmodSync(dest, 493);
|
|
4470
5554
|
}
|
|
4471
5555
|
}
|
|
4472
5556
|
}
|
|
4473
5557
|
}
|
|
4474
5558
|
async function runCommand(directory) {
|
|
4475
|
-
const workDir = directory ?
|
|
4476
|
-
|
|
5559
|
+
const workDir = directory ? path19.resolve(directory) : process.cwd();
|
|
5560
|
+
fs20.mkdirSync(workDir, { recursive: true });
|
|
4477
5561
|
if (!configExists(workDir)) {
|
|
4478
5562
|
console.log(chalk4.blue("\n No skillpack.json found. Let's set one up.\n"));
|
|
4479
5563
|
const { name, description } = await inquirer2.prompt([
|
|
@@ -4512,20 +5596,19 @@ async function runCommand(directory) {
|
|
|
4512
5596
|
syncSkillDescriptions(workDir, config);
|
|
4513
5597
|
saveConfig(workDir, config);
|
|
4514
5598
|
await startServer({
|
|
4515
|
-
rootDir: workDir
|
|
4516
|
-
daemonRun: process.env.DAEMON_RUN === "1"
|
|
5599
|
+
rootDir: workDir
|
|
4517
5600
|
});
|
|
4518
5601
|
}
|
|
4519
5602
|
|
|
4520
5603
|
// src/cli.ts
|
|
4521
|
-
import
|
|
4522
|
-
import
|
|
5604
|
+
import fs21 from "fs";
|
|
5605
|
+
import path20 from "path";
|
|
4523
5606
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4524
5607
|
var packageJson = JSON.parse(
|
|
4525
|
-
|
|
5608
|
+
fs21.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
|
|
4526
5609
|
);
|
|
4527
5610
|
var program = new Command();
|
|
4528
|
-
var cliFilePath =
|
|
5611
|
+
var cliFilePath = path20.resolve(fileURLToPath3(import.meta.url));
|
|
4529
5612
|
program.name("skillpack").description("Assemble, package, and run Agent Skills packs").version(packageJson.version);
|
|
4530
5613
|
program.command("create [directory]").description("Create a skills pack interactively").option("--config <path-or-url>", "Initialize from a local or remote skillpack.json").action(async (directory, options) => {
|
|
4531
5614
|
await createCommand(directory, options);
|
|
@@ -4535,7 +5618,7 @@ program.command("run [directory]").description("Start the SkillPack runtime serv
|
|
|
4535
5618
|
if (options?.host) process.env.HOST = options.host;
|
|
4536
5619
|
await runCommand(directory);
|
|
4537
5620
|
});
|
|
4538
|
-
program.command("zip").description("Package the current pack as a zip file (skillpack.json + skills/ + start scripts)").action(async () => {
|
|
5621
|
+
program.command("zip").description("Package the current pack as a zip file (skillpack.json + optional job.json + skills/ + start scripts)").action(async () => {
|
|
4539
5622
|
try {
|
|
4540
5623
|
await zipCommand(process.cwd());
|
|
4541
5624
|
} catch (err) {
|
|
@@ -4546,7 +5629,7 @@ program.command("zip").description("Package the current pack as a zip file (skil
|
|
|
4546
5629
|
function normalizeUserArgs(argv) {
|
|
4547
5630
|
if (argv.length === 0) return argv;
|
|
4548
5631
|
const firstArg = argv[0];
|
|
4549
|
-
if (firstArg &&
|
|
5632
|
+
if (firstArg && path20.resolve(firstArg) === cliFilePath) {
|
|
4550
5633
|
return argv.slice(1);
|
|
4551
5634
|
}
|
|
4552
5635
|
return argv;
|