@cremini/skillpack 1.2.7 → 1.2.8
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 +1729 -712
- package/package.json +6 -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
|
+
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,381 @@ 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
|
+
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
|
+
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 Database from "better-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;
|
|
2913
|
+
constructor(rootDir) {
|
|
2914
|
+
const dataDir = path10.resolve(rootDir, "data");
|
|
2915
|
+
fs10.mkdirSync(dataDir, { recursive: true });
|
|
2916
|
+
this.db = new Database(path10.join(dataDir, "result.db"));
|
|
2917
|
+
this.db.pragma("journal_mode = WAL");
|
|
2918
|
+
this.initialize();
|
|
2919
|
+
}
|
|
2920
|
+
initialize() {
|
|
2921
|
+
this.db.exec(`
|
|
2922
|
+
CREATE TABLE IF NOT EXISTS artifacts (
|
|
2923
|
+
artifact_id TEXT PRIMARY KEY,
|
|
2924
|
+
run_id TEXT NOT NULL,
|
|
2925
|
+
channel_id TEXT NOT NULL,
|
|
2926
|
+
original_path TEXT NOT NULL,
|
|
2927
|
+
snapshot_path TEXT NOT NULL,
|
|
2928
|
+
file_name TEXT NOT NULL,
|
|
2929
|
+
mime_type TEXT,
|
|
2930
|
+
size_bytes INTEGER NOT NULL,
|
|
2931
|
+
title TEXT,
|
|
2932
|
+
is_primary INTEGER NOT NULL DEFAULT 0,
|
|
2933
|
+
declared_at TEXT NOT NULL
|
|
2934
|
+
);
|
|
2935
|
+
|
|
2936
|
+
CREATE INDEX IF NOT EXISTS idx_artifacts_channel_declared_at
|
|
2937
|
+
ON artifacts(channel_id, declared_at DESC);
|
|
2938
|
+
`);
|
|
2939
|
+
}
|
|
2940
|
+
insertArtifacts(input) {
|
|
2941
|
+
if (input.artifacts.length === 0) {
|
|
2942
|
+
return;
|
|
2943
|
+
}
|
|
2944
|
+
const insertArtifact = this.db.prepare(`
|
|
2945
|
+
INSERT INTO artifacts (
|
|
2946
|
+
artifact_id,
|
|
2947
|
+
run_id,
|
|
2948
|
+
channel_id,
|
|
2949
|
+
original_path,
|
|
2950
|
+
snapshot_path,
|
|
2951
|
+
file_name,
|
|
2952
|
+
mime_type,
|
|
2953
|
+
size_bytes,
|
|
2954
|
+
title,
|
|
2955
|
+
is_primary,
|
|
2956
|
+
declared_at
|
|
2957
|
+
) VALUES (
|
|
2958
|
+
@artifactId,
|
|
2959
|
+
@runId,
|
|
2960
|
+
@channelId,
|
|
2961
|
+
@originalPath,
|
|
2962
|
+
@snapshotPath,
|
|
2963
|
+
@fileName,
|
|
2964
|
+
@mimeType,
|
|
2965
|
+
@sizeBytes,
|
|
2966
|
+
@title,
|
|
2967
|
+
@isPrimary,
|
|
2968
|
+
@declaredAt
|
|
2969
|
+
)
|
|
2970
|
+
`);
|
|
2971
|
+
const transaction = this.db.transaction((payload) => {
|
|
2972
|
+
for (const artifact of payload.artifacts) {
|
|
2973
|
+
insertArtifact.run({
|
|
2974
|
+
artifactId: randomUUID2(),
|
|
2975
|
+
runId: payload.runId,
|
|
2976
|
+
channelId: payload.channelId,
|
|
2977
|
+
originalPath: artifact.originalPath,
|
|
2978
|
+
snapshotPath: artifact.snapshotPath,
|
|
2979
|
+
fileName: artifact.fileName,
|
|
2980
|
+
mimeType: artifact.mimeType ?? null,
|
|
2981
|
+
sizeBytes: artifact.sizeBytes,
|
|
2982
|
+
title: artifact.title ?? null,
|
|
2983
|
+
isPrimary: artifact.isPrimary ? 1 : 0,
|
|
2984
|
+
declaredAt: artifact.declaredAt
|
|
2985
|
+
});
|
|
2986
|
+
}
|
|
2987
|
+
});
|
|
2988
|
+
transaction(input);
|
|
2989
|
+
}
|
|
2990
|
+
listRecentArtifacts(options = {}) {
|
|
2991
|
+
const limit = options.limit ?? 100;
|
|
2992
|
+
const offset = options.offset ?? 0;
|
|
2993
|
+
const conditions = [];
|
|
2994
|
+
const params = [];
|
|
2995
|
+
if (options.channelId) {
|
|
2996
|
+
conditions.push("channel_id = ?");
|
|
2997
|
+
params.push(options.channelId);
|
|
2998
|
+
}
|
|
2999
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3000
|
+
const rows = this.db.prepare(`
|
|
3001
|
+
SELECT *
|
|
3002
|
+
FROM artifacts
|
|
3003
|
+
${whereClause}
|
|
3004
|
+
ORDER BY declared_at DESC, rowid DESC
|
|
3005
|
+
LIMIT ? OFFSET ?
|
|
3006
|
+
`).all(...params, limit, offset);
|
|
3007
|
+
return rows.map(mapArtifactRow);
|
|
3008
|
+
}
|
|
3009
|
+
};
|
|
3010
|
+
|
|
3011
|
+
// src/runtime/artifacts/save-artifacts-tool.ts
|
|
3012
|
+
import { Type } from "@sinclair/typebox";
|
|
3013
|
+
var ArtifactItem = Type.Object({
|
|
3014
|
+
filePath: Type.String({
|
|
3015
|
+
description: "Absolute path to the artifact file. The file must exist, be readable, and be inside the current pack root."
|
|
3016
|
+
}),
|
|
3017
|
+
title: Type.Optional(
|
|
3018
|
+
Type.String({
|
|
3019
|
+
description: "Optional short title shown in the dashboard."
|
|
3020
|
+
})
|
|
3021
|
+
),
|
|
3022
|
+
isPrimary: Type.Optional(
|
|
3023
|
+
Type.Boolean({
|
|
3024
|
+
description: "Mark this artifact as a primary output."
|
|
3025
|
+
})
|
|
3026
|
+
)
|
|
3027
|
+
});
|
|
3028
|
+
var SaveArtifactsParams = Type.Object({
|
|
3029
|
+
artifacts: Type.Array(ArtifactItem, {
|
|
3030
|
+
minItems: 1,
|
|
3031
|
+
description: "The artifact files to save for this run."
|
|
3032
|
+
})
|
|
3033
|
+
});
|
|
3034
|
+
function textResult(text) {
|
|
3035
|
+
return { content: [{ type: "text", text }], details: void 0 };
|
|
3036
|
+
}
|
|
3037
|
+
function normalizeOptionalText(value) {
|
|
3038
|
+
const trimmed = value?.trim();
|
|
3039
|
+
return trimmed ? trimmed : void 0;
|
|
3040
|
+
}
|
|
3041
|
+
function createSaveArtifactsTool(rootDir, saveCallbackRef) {
|
|
3042
|
+
return {
|
|
3043
|
+
name: "save_artifacts",
|
|
3044
|
+
label: "Save Artifacts",
|
|
3045
|
+
description: [
|
|
3046
|
+
"Save the final output files produced by this run.",
|
|
3047
|
+
"Always use this for user-facing deliverables that are part of the final result.",
|
|
3048
|
+
"Do not use this for intermediate, temporary, draft, or scratch files.",
|
|
3049
|
+
"Each filePath must be an absolute path inside the current pack root."
|
|
3050
|
+
].join("\n"),
|
|
3051
|
+
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.",
|
|
3052
|
+
promptGuidelines: [
|
|
3053
|
+
"Whenever you create a final result file for the user, call `save_artifacts` before finishing the response.",
|
|
3054
|
+
"Do not call `save_artifacts` for intermediate, temporary, draft, or scratch files."
|
|
3055
|
+
],
|
|
3056
|
+
parameters: SaveArtifactsParams,
|
|
3057
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
3058
|
+
const saveArtifacts = saveCallbackRef.current;
|
|
3059
|
+
if (!saveArtifacts) {
|
|
3060
|
+
throw new Error("Artifact saving is not available for this run.");
|
|
3061
|
+
}
|
|
3062
|
+
const artifacts = params.artifacts.map((artifact) => {
|
|
3063
|
+
const metadata = resolvePackFile(rootDir, artifact.filePath);
|
|
3064
|
+
return {
|
|
3065
|
+
filePath: metadata.resolvedPath,
|
|
3066
|
+
fileName: metadata.fileName,
|
|
3067
|
+
mimeType: metadata.mimeType,
|
|
3068
|
+
sizeBytes: metadata.sizeBytes,
|
|
3069
|
+
title: normalizeOptionalText(artifact.title),
|
|
3070
|
+
isPrimary: artifact.isPrimary === true
|
|
3071
|
+
};
|
|
3072
|
+
});
|
|
3073
|
+
const savedCount = await saveArtifacts(artifacts);
|
|
3074
|
+
return textResult(`Saved ${savedCount} artifact(s).`);
|
|
3075
|
+
}
|
|
3076
|
+
};
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
// src/runtime/tools/send-file-tool.ts
|
|
3080
|
+
import fs11 from "fs";
|
|
3081
|
+
import path11 from "path";
|
|
3082
|
+
import { Type as Type2 } from "@sinclair/typebox";
|
|
3083
|
+
var SendFileParams = Type2.Object({
|
|
3084
|
+
filePath: Type2.String({
|
|
3085
|
+
description: "Absolute path to the file to send to the user. The file must exist and be readable."
|
|
3086
|
+
}),
|
|
3087
|
+
caption: Type2.Optional(
|
|
3088
|
+
Type2.String({
|
|
3089
|
+
description: "Optional caption or description to accompany the file."
|
|
3090
|
+
})
|
|
3091
|
+
)
|
|
3092
|
+
});
|
|
3093
|
+
function createSendFileTool(fileOutputCallbackRef) {
|
|
3094
|
+
return {
|
|
3095
|
+
name: "send_file",
|
|
3096
|
+
label: "Send File",
|
|
3097
|
+
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.",
|
|
3098
|
+
promptSnippet: "send_file: Send a file to the user ONLY when they explicitly request it. Never send files proactively or automatically.",
|
|
3099
|
+
parameters: SendFileParams,
|
|
3100
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
3101
|
+
const { filePath, caption } = params;
|
|
3102
|
+
if (!fs11.existsSync(filePath)) {
|
|
3103
|
+
return {
|
|
3104
|
+
content: [{ type: "text", text: `Error: File not found: ${filePath}` }],
|
|
3105
|
+
details: void 0
|
|
3106
|
+
};
|
|
3107
|
+
}
|
|
3108
|
+
const stats = fs11.statSync(filePath);
|
|
3109
|
+
if (!stats.isFile()) {
|
|
2555
3110
|
return {
|
|
2556
3111
|
content: [
|
|
2557
3112
|
{ type: "text", text: `Error: Path is not a file: ${filePath}` }
|
|
@@ -2559,7 +3114,7 @@ function createSendFileTool(fileOutputCallbackRef) {
|
|
|
2559
3114
|
details: void 0
|
|
2560
3115
|
};
|
|
2561
3116
|
}
|
|
2562
|
-
const filename =
|
|
3117
|
+
const filename = path11.basename(filePath);
|
|
2563
3118
|
const mimeType = detectMimeType(filePath);
|
|
2564
3119
|
const callback = fileOutputCallbackRef.current;
|
|
2565
3120
|
if (callback) {
|
|
@@ -2586,43 +3141,65 @@ function createSendFileTool(fileOutputCallbackRef) {
|
|
|
2586
3141
|
}
|
|
2587
3142
|
|
|
2588
3143
|
// src/runtime/tools/manage-schedule-tool.ts
|
|
2589
|
-
import { Type as
|
|
2590
|
-
var ManageScheduleParams =
|
|
2591
|
-
action:
|
|
3144
|
+
import { Type as Type3 } from "@sinclair/typebox";
|
|
3145
|
+
var ManageScheduleParams = Type3.Object({
|
|
3146
|
+
action: Type3.Union(
|
|
2592
3147
|
[
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
3148
|
+
Type3.Literal("add"),
|
|
3149
|
+
Type3.Literal("list"),
|
|
3150
|
+
Type3.Literal("remove"),
|
|
3151
|
+
Type3.Literal("trigger"),
|
|
3152
|
+
Type3.Literal("enable"),
|
|
3153
|
+
Type3.Literal("disable")
|
|
2599
3154
|
],
|
|
2600
3155
|
{ description: "The action to perform." }
|
|
2601
3156
|
),
|
|
2602
|
-
name:
|
|
2603
|
-
|
|
3157
|
+
name: Type3.Optional(
|
|
3158
|
+
Type3.String({
|
|
2604
3159
|
description: "Unique name for the scheduled task. Required for add/remove/trigger/enable/disable."
|
|
2605
3160
|
})
|
|
2606
3161
|
),
|
|
2607
|
-
cron:
|
|
2608
|
-
|
|
3162
|
+
cron: Type3.Optional(
|
|
3163
|
+
Type3.String({
|
|
2609
3164
|
description: "Cron expression (5 fields: minute hour day month weekday). Required for add."
|
|
2610
3165
|
})
|
|
2611
3166
|
),
|
|
2612
|
-
prompt:
|
|
2613
|
-
|
|
3167
|
+
prompt: Type3.Optional(
|
|
3168
|
+
Type3.String({
|
|
2614
3169
|
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
3170
|
})
|
|
2616
3171
|
),
|
|
2617
|
-
timezone:
|
|
2618
|
-
|
|
3172
|
+
timezone: Type3.Optional(
|
|
3173
|
+
Type3.String({
|
|
2619
3174
|
description: "Optional timezone for the cron schedule, e.g. 'Asia/Shanghai', 'America/New_York'."
|
|
2620
3175
|
})
|
|
3176
|
+
),
|
|
3177
|
+
notifyAdapter: Type3.Optional(
|
|
3178
|
+
Type3.String({
|
|
3179
|
+
description: "Optional target adapter for notifications. If omitted, the current chat is used when supported (Telegram, Slack, or Web)."
|
|
3180
|
+
})
|
|
3181
|
+
),
|
|
3182
|
+
notifyChannelId: Type3.Optional(
|
|
3183
|
+
Type3.String({
|
|
3184
|
+
description: "Optional target channelId for notifications. Must be provided together with notifyAdapter when overriding the default target."
|
|
3185
|
+
})
|
|
2621
3186
|
)
|
|
2622
3187
|
});
|
|
2623
|
-
function
|
|
3188
|
+
function textResult2(text) {
|
|
2624
3189
|
return { content: [{ type: "text", text }], details: void 0 };
|
|
2625
3190
|
}
|
|
3191
|
+
function getDefaultNotifyTarget(adapter, channelId) {
|
|
3192
|
+
if (adapter === "telegram" && channelId.startsWith("telegram-")) {
|
|
3193
|
+
return { adapter: "telegram", channelId };
|
|
3194
|
+
}
|
|
3195
|
+
if (adapter === "slack" && channelId.startsWith("slack-")) {
|
|
3196
|
+
return { adapter: "slack", channelId };
|
|
3197
|
+
}
|
|
3198
|
+
if (adapter === "web") {
|
|
3199
|
+
return { adapter: "web", channelId };
|
|
3200
|
+
}
|
|
3201
|
+
return null;
|
|
3202
|
+
}
|
|
2626
3203
|
function createManageScheduleTool(schedulerRef, adapter, channelId) {
|
|
2627
3204
|
return {
|
|
2628
3205
|
name: "manage_scheduled_task",
|
|
@@ -2631,7 +3208,7 @@ function createManageScheduleTool(schedulerRef, adapter, channelId) {
|
|
|
2631
3208
|
"Manage scheduled tasks (cron jobs) that automatically execute prompts and push results to IM channels.",
|
|
2632
3209
|
"",
|
|
2633
3210
|
"Actions:",
|
|
2634
|
-
"- add: Create a new scheduled task. Requires: name, cron, prompt.
|
|
3211
|
+
"- 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
3212
|
"- list: List all scheduled tasks with their status.",
|
|
2636
3213
|
"- remove: Remove a scheduled task by name.",
|
|
2637
3214
|
"- trigger: Manually trigger a scheduled task by name (runs immediately).",
|
|
@@ -2648,7 +3225,7 @@ function createManageScheduleTool(schedulerRef, adapter, channelId) {
|
|
|
2648
3225
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
2649
3226
|
const scheduler = schedulerRef.current;
|
|
2650
3227
|
if (!scheduler) {
|
|
2651
|
-
return
|
|
3228
|
+
return textResult2(
|
|
2652
3229
|
"Error: Scheduler is not available. The scheduled task system may not be initialized."
|
|
2653
3230
|
);
|
|
2654
3231
|
}
|
|
@@ -2656,79 +3233,85 @@ function createManageScheduleTool(schedulerRef, adapter, channelId) {
|
|
|
2656
3233
|
case "list": {
|
|
2657
3234
|
const jobs = scheduler.listJobs();
|
|
2658
3235
|
if (jobs.length === 0) {
|
|
2659
|
-
return
|
|
3236
|
+
return textResult2("No scheduled tasks configured.");
|
|
2660
3237
|
}
|
|
2661
3238
|
const lines = jobs.map(
|
|
2662
3239
|
(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
3240
|
);
|
|
2664
|
-
return
|
|
3241
|
+
return textResult2(
|
|
2665
3242
|
`Scheduled tasks (${jobs.length}):
|
|
2666
3243
|
${lines.join("\n")}`
|
|
2667
3244
|
);
|
|
2668
3245
|
}
|
|
2669
3246
|
case "add": {
|
|
2670
3247
|
if (!params.name || !params.cron || !params.prompt) {
|
|
2671
|
-
return
|
|
3248
|
+
return textResult2(
|
|
2672
3249
|
"Error: 'name', 'cron', and 'prompt' are required for adding a task."
|
|
2673
3250
|
);
|
|
2674
3251
|
}
|
|
2675
|
-
if (
|
|
2676
|
-
return
|
|
2677
|
-
"Error:
|
|
3252
|
+
if (params.notifyAdapter && !params.notifyChannelId || !params.notifyAdapter && params.notifyChannelId) {
|
|
3253
|
+
return textResult2(
|
|
3254
|
+
"Error: 'notifyAdapter' and 'notifyChannelId' must be provided together when overriding the notification target."
|
|
3255
|
+
);
|
|
3256
|
+
}
|
|
3257
|
+
const notify = params.notifyAdapter && params.notifyChannelId ? {
|
|
3258
|
+
adapter: params.notifyAdapter,
|
|
3259
|
+
channelId: params.notifyChannelId
|
|
3260
|
+
} : getDefaultNotifyTarget(adapter, channelId);
|
|
3261
|
+
if (!notify) {
|
|
3262
|
+
return textResult2(
|
|
3263
|
+
"Error: No default notification target is available for this chat. Provide 'notifyAdapter' and 'notifyChannelId'."
|
|
2678
3264
|
);
|
|
2679
3265
|
}
|
|
2680
3266
|
const jobConfig = {
|
|
2681
3267
|
name: params.name,
|
|
2682
3268
|
cron: params.cron,
|
|
2683
3269
|
prompt: params.prompt,
|
|
2684
|
-
notify
|
|
2685
|
-
adapter,
|
|
2686
|
-
channelId
|
|
2687
|
-
},
|
|
3270
|
+
notify,
|
|
2688
3271
|
enabled: true,
|
|
2689
3272
|
timezone: params.timezone
|
|
2690
3273
|
};
|
|
2691
3274
|
const result = scheduler.addJob(jobConfig);
|
|
2692
|
-
return
|
|
3275
|
+
return textResult2(result.message);
|
|
2693
3276
|
}
|
|
2694
3277
|
case "remove": {
|
|
2695
3278
|
if (!params.name) {
|
|
2696
|
-
return
|
|
3279
|
+
return textResult2(
|
|
2697
3280
|
"Error: 'name' is required for removing a task."
|
|
2698
3281
|
);
|
|
2699
3282
|
}
|
|
2700
3283
|
const result = scheduler.removeJob(params.name);
|
|
2701
|
-
return
|
|
3284
|
+
return textResult2(result.message);
|
|
2702
3285
|
}
|
|
2703
3286
|
case "trigger": {
|
|
2704
3287
|
if (!params.name) {
|
|
2705
|
-
return
|
|
3288
|
+
return textResult2(
|
|
2706
3289
|
"Error: 'name' is required for triggering a task."
|
|
2707
3290
|
);
|
|
2708
3291
|
}
|
|
2709
3292
|
const result = await scheduler.triggerJob(params.name);
|
|
2710
|
-
return
|
|
3293
|
+
return textResult2(result.message);
|
|
2711
3294
|
}
|
|
2712
3295
|
case "enable": {
|
|
2713
3296
|
if (!params.name) {
|
|
2714
|
-
return
|
|
3297
|
+
return textResult2(
|
|
2715
3298
|
"Error: 'name' is required for enabling a task."
|
|
2716
3299
|
);
|
|
2717
3300
|
}
|
|
2718
3301
|
const result = scheduler.setEnabled(params.name, true);
|
|
2719
|
-
return
|
|
3302
|
+
return textResult2(result.message);
|
|
2720
3303
|
}
|
|
2721
3304
|
case "disable": {
|
|
2722
3305
|
if (!params.name) {
|
|
2723
|
-
return
|
|
3306
|
+
return textResult2(
|
|
2724
3307
|
"Error: 'name' is required for disabling a task."
|
|
2725
3308
|
);
|
|
2726
3309
|
}
|
|
2727
3310
|
const result = scheduler.setEnabled(params.name, false);
|
|
2728
|
-
return
|
|
3311
|
+
return textResult2(result.message);
|
|
2729
3312
|
}
|
|
2730
3313
|
default:
|
|
2731
|
-
return
|
|
3314
|
+
return textResult2(
|
|
2732
3315
|
`Error: Unknown action '${params.action}'. Use: add, list, remove, trigger, enable, disable.`
|
|
2733
3316
|
);
|
|
2734
3317
|
}
|
|
@@ -2738,8 +3321,8 @@ ${lines.join("\n")}`
|
|
|
2738
3321
|
|
|
2739
3322
|
// src/runtime/commands/help-command.ts
|
|
2740
3323
|
init_commands();
|
|
2741
|
-
import
|
|
2742
|
-
import
|
|
3324
|
+
import fs12 from "fs";
|
|
3325
|
+
import path12 from "path";
|
|
2743
3326
|
function buildHelpMessage(rootDir) {
|
|
2744
3327
|
const sections = [];
|
|
2745
3328
|
const commands = getVisibleCommands();
|
|
@@ -2749,7 +3332,7 @@ function buildHelpMessage(rootDir) {
|
|
|
2749
3332
|
sections.push(`\u{1F4CB} **Available Commands**
|
|
2750
3333
|
|
|
2751
3334
|
${commandLines.join("\n")}`);
|
|
2752
|
-
const configPath =
|
|
3335
|
+
const configPath = path12.resolve(rootDir, "skillpack.json");
|
|
2753
3336
|
const skills = readInstalledSkills(configPath);
|
|
2754
3337
|
if (skills.length > 0) {
|
|
2755
3338
|
const skillLines = skills.map(
|
|
@@ -2785,11 +3368,11 @@ function handleHelpCommand(rootDir) {
|
|
|
2785
3368
|
};
|
|
2786
3369
|
}
|
|
2787
3370
|
function readInstalledSkills(configPath) {
|
|
2788
|
-
if (!
|
|
3371
|
+
if (!fs12.existsSync(configPath)) {
|
|
2789
3372
|
return [];
|
|
2790
3373
|
}
|
|
2791
3374
|
try {
|
|
2792
|
-
const raw =
|
|
3375
|
+
const raw = fs12.readFileSync(configPath, "utf-8");
|
|
2793
3376
|
const config = JSON.parse(raw);
|
|
2794
3377
|
return Array.isArray(config.skills) ? config.skills : [];
|
|
2795
3378
|
} catch {
|
|
@@ -2809,24 +3392,24 @@ var BUILTIN_SKILL_CREATOR_TEMPLATE_DIR = fileURLToPath(
|
|
|
2809
3392
|
var PACK_AGENTS_FILE = "AGENTS.md";
|
|
2810
3393
|
var PACK_SOUL_FILE = "SOUL.md";
|
|
2811
3394
|
function materializeBuiltinSkillCreator(rootDir, skillsPath) {
|
|
2812
|
-
if (!
|
|
3395
|
+
if (!fs13.existsSync(BUILTIN_SKILL_CREATOR_TEMPLATE_DIR)) {
|
|
2813
3396
|
log(
|
|
2814
3397
|
`[PackAgent] Built-in skill-creator template missing: ${BUILTIN_SKILL_CREATOR_TEMPLATE_DIR}`
|
|
2815
3398
|
);
|
|
2816
3399
|
return null;
|
|
2817
3400
|
}
|
|
2818
|
-
const packConfigPath =
|
|
2819
|
-
const skillDir =
|
|
2820
|
-
const skillPath =
|
|
3401
|
+
const packConfigPath = path13.resolve(rootDir, "skillpack.json");
|
|
3402
|
+
const skillDir = path13.resolve(skillsPath, BUILTIN_SKILL_CREATOR_NAME);
|
|
3403
|
+
const skillPath = path13.join(skillDir, "SKILL.md");
|
|
2821
3404
|
const renderTemplate = (content) => content.replaceAll("{{SKILLS_PATH}}", skillsPath).replaceAll("{{PACK_CONFIG_PATH}}", packConfigPath);
|
|
2822
3405
|
const copyDir = (srcDir, destDir) => {
|
|
2823
|
-
|
|
2824
|
-
for (const entry of
|
|
3406
|
+
fs13.mkdirSync(destDir, { recursive: true });
|
|
3407
|
+
for (const entry of fs13.readdirSync(srcDir, { withFileTypes: true })) {
|
|
2825
3408
|
if (entry.name === ".DS_Store") {
|
|
2826
3409
|
continue;
|
|
2827
3410
|
}
|
|
2828
|
-
const srcPath =
|
|
2829
|
-
const destPath =
|
|
3411
|
+
const srcPath = path13.join(srcDir, entry.name);
|
|
3412
|
+
const destPath = path13.join(destDir, entry.name);
|
|
2830
3413
|
if (entry.isDirectory()) {
|
|
2831
3414
|
copyDir(srcPath, destPath);
|
|
2832
3415
|
continue;
|
|
@@ -2835,17 +3418,17 @@ function materializeBuiltinSkillCreator(rootDir, skillsPath) {
|
|
|
2835
3418
|
continue;
|
|
2836
3419
|
}
|
|
2837
3420
|
if (entry.name.endsWith(".md") || entry.name.endsWith(".py")) {
|
|
2838
|
-
const content =
|
|
2839
|
-
|
|
3421
|
+
const content = fs13.readFileSync(srcPath, "utf-8");
|
|
3422
|
+
fs13.writeFileSync(destPath, renderTemplate(content), "utf-8");
|
|
2840
3423
|
continue;
|
|
2841
3424
|
}
|
|
2842
|
-
|
|
3425
|
+
fs13.copyFileSync(srcPath, destPath);
|
|
2843
3426
|
}
|
|
2844
3427
|
};
|
|
2845
|
-
if (!
|
|
3428
|
+
if (!fs13.existsSync(skillDir)) {
|
|
2846
3429
|
copyDir(BUILTIN_SKILL_CREATOR_TEMPLATE_DIR, skillDir);
|
|
2847
3430
|
}
|
|
2848
|
-
if (!
|
|
3431
|
+
if (!fs13.existsSync(skillPath)) {
|
|
2849
3432
|
log(
|
|
2850
3433
|
`[PackAgent] Materialized built-in skill-creator but SKILL.md is missing: ${skillPath}`
|
|
2851
3434
|
);
|
|
@@ -2873,11 +3456,11 @@ function overrideBuiltinSkillCreator(base, materializedSkill) {
|
|
|
2873
3456
|
};
|
|
2874
3457
|
}
|
|
2875
3458
|
function readOptionalPackPromptFile(filePath) {
|
|
2876
|
-
if (!
|
|
3459
|
+
if (!fs13.existsSync(filePath)) {
|
|
2877
3460
|
return void 0;
|
|
2878
3461
|
}
|
|
2879
3462
|
try {
|
|
2880
|
-
const content =
|
|
3463
|
+
const content = fs13.readFileSync(filePath, "utf-8").trim();
|
|
2881
3464
|
return content.length > 0 ? content : void 0;
|
|
2882
3465
|
} catch (error) {
|
|
2883
3466
|
console.warn(`[PackAgent] Warning: Could not read ${filePath}:`, error);
|
|
@@ -2885,8 +3468,8 @@ function readOptionalPackPromptFile(filePath) {
|
|
|
2885
3468
|
}
|
|
2886
3469
|
}
|
|
2887
3470
|
function buildPackPromptBlock(rootDir) {
|
|
2888
|
-
const agentsPath =
|
|
2889
|
-
const soulPath =
|
|
3471
|
+
const agentsPath = path13.resolve(rootDir, PACK_AGENTS_FILE);
|
|
3472
|
+
const soulPath = path13.resolve(rootDir, PACK_SOUL_FILE);
|
|
2890
3473
|
const agentsContent = readOptionalPackPromptFile(agentsPath);
|
|
2891
3474
|
const soulContent = readOptionalPackPromptFile(soulPath);
|
|
2892
3475
|
if (!agentsContent && !soulContent) {
|
|
@@ -2945,14 +3528,13 @@ var PackAgent = class {
|
|
|
2945
3528
|
options;
|
|
2946
3529
|
channels = /* @__PURE__ */ new Map();
|
|
2947
3530
|
pendingSessionCreations = /* @__PURE__ */ new Map();
|
|
2948
|
-
fileOutputCallbackRef = {
|
|
2949
|
-
current: null
|
|
2950
|
-
};
|
|
2951
3531
|
schedulerRef = { current: null };
|
|
2952
3532
|
authStorage;
|
|
3533
|
+
artifactPersistenceService;
|
|
2953
3534
|
constructor(options) {
|
|
2954
3535
|
this.options = options;
|
|
2955
|
-
|
|
3536
|
+
this.artifactPersistenceService = options.artifactPersistenceService;
|
|
3537
|
+
const configPath = path13.resolve(options.rootDir, "data", "config.json");
|
|
2956
3538
|
const backend = new ConfigFileAuthBackend(configPath);
|
|
2957
3539
|
this.authStorage = AuthStorage.fromStorage(backend);
|
|
2958
3540
|
const providerMeta = SUPPORTED_PROVIDERS[options.provider];
|
|
@@ -2979,9 +3561,12 @@ var PackAgent = class {
|
|
|
2979
3561
|
setScheduler(scheduler) {
|
|
2980
3562
|
this.schedulerRef.current = scheduler;
|
|
2981
3563
|
}
|
|
2982
|
-
createCustomTools(adapter, channelId) {
|
|
2983
|
-
const tools = [
|
|
2984
|
-
|
|
3564
|
+
createCustomTools(adapter, channelId, fileOutputCallbackRef, finalArtifactsSaveCallbackRef) {
|
|
3565
|
+
const tools = [
|
|
3566
|
+
createSendFileTool(fileOutputCallbackRef),
|
|
3567
|
+
createSaveArtifactsTool(this.options.rootDir, finalArtifactsSaveCallbackRef)
|
|
3568
|
+
];
|
|
3569
|
+
if (adapter !== "scheduler") {
|
|
2985
3570
|
tools.push(createManageScheduleTool(this.schedulerRef, adapter, channelId));
|
|
2986
3571
|
}
|
|
2987
3572
|
return tools;
|
|
@@ -3023,24 +3608,24 @@ var PackAgent = class {
|
|
|
3023
3608
|
if (resolvedModel && baseUrl) {
|
|
3024
3609
|
log(`[PackAgent] Resolved ${provider}/${modelId} api=${resolvedModel.api} baseUrl=${baseUrl}`);
|
|
3025
3610
|
}
|
|
3026
|
-
const sessionDir =
|
|
3611
|
+
const sessionDir = path13.resolve(
|
|
3027
3612
|
rootDir,
|
|
3028
3613
|
"data",
|
|
3029
3614
|
"sessions",
|
|
3030
3615
|
channelId
|
|
3031
3616
|
);
|
|
3032
|
-
|
|
3617
|
+
fs13.mkdirSync(sessionDir, { recursive: true });
|
|
3033
3618
|
const sessionManager = SessionManager.continueRecent(rootDir, sessionDir);
|
|
3034
3619
|
log(`[PackAgent] Session dir: ${sessionDir}`);
|
|
3035
|
-
const workspaceDir =
|
|
3620
|
+
const workspaceDir = path13.resolve(
|
|
3036
3621
|
rootDir,
|
|
3037
3622
|
"data",
|
|
3038
3623
|
"workspaces",
|
|
3039
3624
|
channelId
|
|
3040
3625
|
);
|
|
3041
|
-
|
|
3626
|
+
fs13.mkdirSync(workspaceDir, { recursive: true });
|
|
3042
3627
|
log(`[PackAgent] Workspace dir: ${workspaceDir}`);
|
|
3043
|
-
const skillsPath =
|
|
3628
|
+
const skillsPath = path13.resolve(rootDir, "skills");
|
|
3044
3629
|
log(`[PackAgent] Loading skills from: ${skillsPath}`);
|
|
3045
3630
|
const materializedSkillCreator = materializeBuiltinSkillCreator(
|
|
3046
3631
|
rootDir,
|
|
@@ -3075,7 +3660,18 @@ var PackAgent = class {
|
|
|
3075
3660
|
});
|
|
3076
3661
|
await resourceLoader.reload();
|
|
3077
3662
|
const tools = createCodingTools(workspaceDir);
|
|
3078
|
-
const
|
|
3663
|
+
const fileOutputCallbackRef = {
|
|
3664
|
+
current: null
|
|
3665
|
+
};
|
|
3666
|
+
const finalArtifactsSaveCallbackRef = {
|
|
3667
|
+
current: null
|
|
3668
|
+
};
|
|
3669
|
+
const customTools = this.createCustomTools(
|
|
3670
|
+
adapter,
|
|
3671
|
+
channelId,
|
|
3672
|
+
fileOutputCallbackRef,
|
|
3673
|
+
finalArtifactsSaveCallbackRef
|
|
3674
|
+
);
|
|
3079
3675
|
const { session } = await createAgentSession({
|
|
3080
3676
|
cwd: workspaceDir,
|
|
3081
3677
|
authStorage,
|
|
@@ -3089,7 +3685,9 @@ var PackAgent = class {
|
|
|
3089
3685
|
const channelSession = {
|
|
3090
3686
|
session,
|
|
3091
3687
|
running: false,
|
|
3092
|
-
pending: Promise.resolve()
|
|
3688
|
+
pending: Promise.resolve(),
|
|
3689
|
+
fileOutputCallbackRef,
|
|
3690
|
+
finalArtifactsSaveCallbackRef
|
|
3093
3691
|
};
|
|
3094
3692
|
this.channels.set(channelId, channelSession);
|
|
3095
3693
|
return channelSession;
|
|
@@ -3106,86 +3704,97 @@ var PackAgent = class {
|
|
|
3106
3704
|
const run = async () => {
|
|
3107
3705
|
cs.running = true;
|
|
3108
3706
|
let turnHadVisibleOutput = false;
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3707
|
+
const runId = randomUUID3();
|
|
3708
|
+
let unsubscribe = () => void 0;
|
|
3709
|
+
try {
|
|
3710
|
+
cs.fileOutputCallbackRef.current = (event) => {
|
|
3711
|
+
onEvent(event);
|
|
3712
|
+
};
|
|
3713
|
+
cs.finalArtifactsSaveCallbackRef.current = (artifacts) => {
|
|
3714
|
+
return this.artifactPersistenceService.saveArtifacts({
|
|
3715
|
+
runId,
|
|
3716
|
+
channelId,
|
|
3717
|
+
artifacts
|
|
3718
|
+
});
|
|
3719
|
+
};
|
|
3720
|
+
unsubscribe = cs.session.subscribe((event) => {
|
|
3721
|
+
switch (event.type) {
|
|
3722
|
+
case "agent_start":
|
|
3723
|
+
log("\n=== [AGENT SESSION START] ===");
|
|
3724
|
+
log("System Prompt:\n", cs.session.systemPrompt);
|
|
3725
|
+
log("============================\n");
|
|
3726
|
+
onEvent({ type: "agent_start" });
|
|
3727
|
+
break;
|
|
3728
|
+
case "message_start":
|
|
3729
|
+
log(`
|
|
3122
3730
|
--- [Message Start: ${event.message?.role}] ---`);
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3731
|
+
if (event.message?.role === "user") {
|
|
3732
|
+
log(JSON.stringify(event.message.content, null, 2));
|
|
3733
|
+
}
|
|
3734
|
+
onEvent({ type: "message_start", role: event.message?.role ?? "" });
|
|
3735
|
+
break;
|
|
3736
|
+
case "message_update":
|
|
3737
|
+
if (event.assistantMessageEvent?.type === "text_delta") {
|
|
3738
|
+
turnHadVisibleOutput = true;
|
|
3739
|
+
write(event.assistantMessageEvent.delta);
|
|
3740
|
+
onEvent({
|
|
3741
|
+
type: "text_delta",
|
|
3742
|
+
delta: event.assistantMessageEvent.delta
|
|
3743
|
+
});
|
|
3744
|
+
} else if (event.assistantMessageEvent?.type === "thinking_delta") {
|
|
3745
|
+
turnHadVisibleOutput = true;
|
|
3746
|
+
onEvent({
|
|
3747
|
+
type: "thinking_delta",
|
|
3748
|
+
delta: event.assistantMessageEvent.delta
|
|
3749
|
+
});
|
|
3750
|
+
}
|
|
3751
|
+
break;
|
|
3752
|
+
case "message_end":
|
|
3753
|
+
log(`
|
|
3754
|
+
--- [Message End: ${event.message?.role}] ---`);
|
|
3755
|
+
if (event.message?.role === "assistant") {
|
|
3756
|
+
const diagnostics2 = getAssistantDiagnostics(event.message);
|
|
3757
|
+
if (diagnostics2) {
|
|
3758
|
+
log(
|
|
3759
|
+
`[Assistant Diagnostics] stopReason=${diagnostics2.stopReason} text=${diagnostics2.hasText ? "yes" : "no"} toolCalls=${diagnostics2.toolCalls}`
|
|
3760
|
+
);
|
|
3761
|
+
if (diagnostics2.errorMessage) {
|
|
3762
|
+
log(`[Assistant Error] ${diagnostics2.errorMessage}`);
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
onEvent({ type: "message_end", role: event.message?.role ?? "" });
|
|
3767
|
+
break;
|
|
3768
|
+
case "tool_execution_start":
|
|
3130
3769
|
turnHadVisibleOutput = true;
|
|
3131
|
-
|
|
3770
|
+
log(`
|
|
3771
|
+
>>> [Tool Start: ${event.toolName}] >>>`);
|
|
3772
|
+
log("Args:", JSON.stringify(event.args, null, 2));
|
|
3132
3773
|
onEvent({
|
|
3133
|
-
type: "
|
|
3134
|
-
|
|
3774
|
+
type: "tool_start",
|
|
3775
|
+
toolCallId: event.toolCallId ?? "",
|
|
3776
|
+
toolName: event.toolName,
|
|
3777
|
+
toolInput: event.args
|
|
3135
3778
|
});
|
|
3136
|
-
|
|
3779
|
+
break;
|
|
3780
|
+
case "tool_execution_end":
|
|
3137
3781
|
turnHadVisibleOutput = true;
|
|
3782
|
+
log(`<<< [Tool End: ${event.toolName}] <<<`);
|
|
3783
|
+
log(`Error: ${event.isError ? "Yes" : "No"}`);
|
|
3138
3784
|
onEvent({
|
|
3139
|
-
type: "
|
|
3140
|
-
|
|
3785
|
+
type: "tool_end",
|
|
3786
|
+
toolCallId: event.toolCallId ?? "",
|
|
3787
|
+
toolName: event.toolName,
|
|
3788
|
+
isError: event.isError,
|
|
3789
|
+
result: event.result
|
|
3141
3790
|
});
|
|
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 {
|
|
3791
|
+
break;
|
|
3792
|
+
case "agent_end":
|
|
3793
|
+
log("\n=== [AGENT SESSION END] ===\n");
|
|
3794
|
+
onEvent({ type: "agent_end" });
|
|
3795
|
+
break;
|
|
3796
|
+
}
|
|
3797
|
+
});
|
|
3189
3798
|
let promptText = text;
|
|
3190
3799
|
const promptOptions = {};
|
|
3191
3800
|
if (attachments && attachments.length > 0) {
|
|
@@ -3213,15 +3822,17 @@ ${text}`;
|
|
|
3213
3822
|
};
|
|
3214
3823
|
}
|
|
3215
3824
|
if (diagnostics && !diagnostics.hasText && diagnostics.toolCalls === 0 && !turnHadVisibleOutput) {
|
|
3825
|
+
const errorMessage = "Assistant returned no visible output. Check the server logs for details.";
|
|
3216
3826
|
return {
|
|
3217
3827
|
stopReason: diagnostics.stopReason,
|
|
3218
|
-
errorMessage
|
|
3828
|
+
errorMessage
|
|
3219
3829
|
};
|
|
3220
3830
|
}
|
|
3221
3831
|
return { stopReason: diagnostics?.stopReason ?? "unknown" };
|
|
3222
3832
|
} finally {
|
|
3223
3833
|
cs.running = false;
|
|
3224
|
-
|
|
3834
|
+
cs.fileOutputCallbackRef.current = null;
|
|
3835
|
+
cs.finalArtifactsSaveCallbackRef.current = null;
|
|
3225
3836
|
unsubscribe();
|
|
3226
3837
|
}
|
|
3227
3838
|
};
|
|
@@ -3241,9 +3852,9 @@ ${text}`;
|
|
|
3241
3852
|
this.channels.delete(channelId);
|
|
3242
3853
|
}
|
|
3243
3854
|
const { rootDir } = this.options;
|
|
3244
|
-
const sessionDir =
|
|
3245
|
-
if (
|
|
3246
|
-
|
|
3855
|
+
const sessionDir = path13.resolve(rootDir, "data", "sessions", channelId);
|
|
3856
|
+
if (fs13.existsSync(sessionDir)) {
|
|
3857
|
+
fs13.rmSync(sessionDir, { recursive: true, force: true });
|
|
3247
3858
|
log(`[PackAgent] Cleared session dir: ${sessionDir}`);
|
|
3248
3859
|
}
|
|
3249
3860
|
return {
|
|
@@ -3265,42 +3876,437 @@ ${text}`;
|
|
|
3265
3876
|
return { success: false, message: `Unknown command: ${command}` };
|
|
3266
3877
|
}
|
|
3267
3878
|
}
|
|
3268
|
-
abort(channelId) {
|
|
3269
|
-
const cs = this.channels.get(channelId);
|
|
3270
|
-
if (cs?.running) {
|
|
3271
|
-
cs.session.abort?.();
|
|
3879
|
+
abort(channelId) {
|
|
3880
|
+
const cs = this.channels.get(channelId);
|
|
3881
|
+
if (cs?.running) {
|
|
3882
|
+
cs.session.abort?.();
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
isRunning(channelId) {
|
|
3886
|
+
return this.channels.get(channelId)?.running ?? false;
|
|
3887
|
+
}
|
|
3888
|
+
dispose(channelId) {
|
|
3889
|
+
const cs = this.channels.get(channelId);
|
|
3890
|
+
if (cs) {
|
|
3891
|
+
cs.session.dispose();
|
|
3892
|
+
this.channels.delete(channelId);
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
/** Reserved: list all sessions */
|
|
3896
|
+
listSessions() {
|
|
3897
|
+
return [];
|
|
3898
|
+
}
|
|
3899
|
+
/** Reserved: restore a historical session */
|
|
3900
|
+
async restoreSession(_sessionId) {
|
|
3901
|
+
}
|
|
3902
|
+
getActiveChannelIds() {
|
|
3903
|
+
return Array.from(this.channels.keys());
|
|
3904
|
+
}
|
|
3905
|
+
};
|
|
3906
|
+
|
|
3907
|
+
// src/runtime/adapters/web.ts
|
|
3908
|
+
import fs15 from "fs";
|
|
3909
|
+
import path15 from "path";
|
|
3910
|
+
import { WebSocketServer } from "ws";
|
|
3911
|
+
init_commands();
|
|
3912
|
+
|
|
3913
|
+
// src/runtime/services/conversation.ts
|
|
3914
|
+
import fs14 from "fs";
|
|
3915
|
+
import path14 from "path";
|
|
3916
|
+
import {
|
|
3917
|
+
parseSessionEntries
|
|
3918
|
+
} from "@mariozechner/pi-coding-agent";
|
|
3919
|
+
var DEFAULT_WEB_CHANNEL_ID = "web";
|
|
3920
|
+
var ConversationService = class {
|
|
3921
|
+
constructor(rootDir) {
|
|
3922
|
+
this.rootDir = rootDir;
|
|
3923
|
+
}
|
|
3924
|
+
/**
|
|
3925
|
+
* Scan data/sessions and return conversation summaries sorted by recency.
|
|
3926
|
+
*/
|
|
3927
|
+
listConversations(activeChannels, options = {}) {
|
|
3928
|
+
const {
|
|
3929
|
+
includeDefaultWeb = false,
|
|
3930
|
+
includeLegacyWeb = true,
|
|
3931
|
+
allowedPlatforms
|
|
3932
|
+
} = options;
|
|
3933
|
+
const sessionsDir = path14.resolve(this.rootDir, "data", "sessions");
|
|
3934
|
+
const channelIds = new Set(activeChannels);
|
|
3935
|
+
const allowedPlatformSet = allowedPlatforms ? new Set(allowedPlatforms) : null;
|
|
3936
|
+
if (includeDefaultWeb) {
|
|
3937
|
+
channelIds.add(DEFAULT_WEB_CHANNEL_ID);
|
|
3938
|
+
}
|
|
3939
|
+
if (fs14.existsSync(sessionsDir)) {
|
|
3940
|
+
for (const entry of fs14.readdirSync(sessionsDir)) {
|
|
3941
|
+
const channelDir = path14.join(sessionsDir, entry);
|
|
3942
|
+
try {
|
|
3943
|
+
if (!fs14.statSync(channelDir).isDirectory()) {
|
|
3944
|
+
continue;
|
|
3945
|
+
}
|
|
3946
|
+
const platform = this.detectPlatform(entry);
|
|
3947
|
+
if (allowedPlatformSet && !allowedPlatformSet.has(platform)) {
|
|
3948
|
+
continue;
|
|
3949
|
+
}
|
|
3950
|
+
if (!includeLegacyWeb && this.isLegacyWebConversation(entry)) {
|
|
3951
|
+
continue;
|
|
3952
|
+
}
|
|
3953
|
+
channelIds.add(entry);
|
|
3954
|
+
} catch {
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
const results = [];
|
|
3959
|
+
for (const channelId of channelIds) {
|
|
3960
|
+
const platform = this.detectPlatform(channelId);
|
|
3961
|
+
if (allowedPlatformSet && !allowedPlatformSet.has(platform)) {
|
|
3962
|
+
continue;
|
|
3963
|
+
}
|
|
3964
|
+
if (!includeLegacyWeb && this.isLegacyWebConversation(channelId)) {
|
|
3965
|
+
continue;
|
|
3966
|
+
}
|
|
3967
|
+
const channelDir = path14.join(sessionsDir, channelId);
|
|
3968
|
+
const sessionFile = this.findLatestSessionFile(channelDir);
|
|
3969
|
+
let messageCount = 0;
|
|
3970
|
+
let lastMessageAt = "";
|
|
3971
|
+
let lastMessagePreview = "";
|
|
3972
|
+
if (sessionFile) {
|
|
3973
|
+
const entries = this.loadEntries(sessionFile);
|
|
3974
|
+
const messages = entries.filter(
|
|
3975
|
+
(entry) => entry.type === "message"
|
|
3976
|
+
);
|
|
3977
|
+
messageCount = messages.length;
|
|
3978
|
+
const lastMessage = messages[messages.length - 1];
|
|
3979
|
+
if (lastMessage) {
|
|
3980
|
+
lastMessageAt = lastMessage.timestamp;
|
|
3981
|
+
lastMessagePreview = this.extractTextPreview(lastMessage, 100);
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
results.push({
|
|
3985
|
+
channelId,
|
|
3986
|
+
platform,
|
|
3987
|
+
sessionFile,
|
|
3988
|
+
messageCount,
|
|
3989
|
+
lastMessageAt,
|
|
3990
|
+
lastMessagePreview
|
|
3991
|
+
});
|
|
3992
|
+
}
|
|
3993
|
+
return results.sort((a, b) => {
|
|
3994
|
+
if (a.channelId === DEFAULT_WEB_CHANNEL_ID && b.channelId !== DEFAULT_WEB_CHANNEL_ID) {
|
|
3995
|
+
return -1;
|
|
3996
|
+
}
|
|
3997
|
+
if (b.channelId === DEFAULT_WEB_CHANNEL_ID && a.channelId !== DEFAULT_WEB_CHANNEL_ID) {
|
|
3998
|
+
return 1;
|
|
3999
|
+
}
|
|
4000
|
+
const recency = (b.lastMessageAt || "").localeCompare(
|
|
4001
|
+
a.lastMessageAt || ""
|
|
4002
|
+
);
|
|
4003
|
+
if (recency !== 0) return recency;
|
|
4004
|
+
return a.channelId.localeCompare(b.channelId);
|
|
4005
|
+
});
|
|
4006
|
+
}
|
|
4007
|
+
/**
|
|
4008
|
+
* Load latest messages for a channel in a simplified format.
|
|
4009
|
+
*/
|
|
4010
|
+
getMessages(channelId, limit = 100) {
|
|
4011
|
+
const channelDir = path14.resolve(
|
|
4012
|
+
this.rootDir,
|
|
4013
|
+
"data",
|
|
4014
|
+
"sessions",
|
|
4015
|
+
channelId
|
|
4016
|
+
);
|
|
4017
|
+
const sessionFile = this.findLatestSessionFile(channelDir);
|
|
4018
|
+
if (!sessionFile) return [];
|
|
4019
|
+
const safeLimit = Number.isFinite(limit) ? Math.max(0, Math.floor(limit)) : 100;
|
|
4020
|
+
if (safeLimit === 0) return [];
|
|
4021
|
+
const entries = this.loadEntries(sessionFile);
|
|
4022
|
+
const toolResultsById = this.collectToolResultStates(entries);
|
|
4023
|
+
const messages = [];
|
|
4024
|
+
let pendingBlocks = [];
|
|
4025
|
+
let pendingToolCalls = [];
|
|
4026
|
+
let pendingMessageId = "";
|
|
4027
|
+
let pendingTimestamp = "";
|
|
4028
|
+
const flushPendingAssistant = () => {
|
|
4029
|
+
if (pendingBlocks.length === 0 && pendingToolCalls.length === 0) {
|
|
4030
|
+
return;
|
|
4031
|
+
}
|
|
4032
|
+
messages.push({
|
|
4033
|
+
id: pendingMessageId || `assistant-${messages.length + 1}`,
|
|
4034
|
+
role: "assistant",
|
|
4035
|
+
text: "",
|
|
4036
|
+
timestamp: pendingTimestamp || (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
4037
|
+
toolCalls: pendingToolCalls.length > 0 ? pendingToolCalls : void 0,
|
|
4038
|
+
blocks: pendingBlocks.length > 0 ? pendingBlocks : void 0
|
|
4039
|
+
});
|
|
4040
|
+
pendingBlocks = [];
|
|
4041
|
+
pendingToolCalls = [];
|
|
4042
|
+
pendingMessageId = "";
|
|
4043
|
+
pendingTimestamp = "";
|
|
4044
|
+
};
|
|
4045
|
+
for (const entry of entries) {
|
|
4046
|
+
if (entry.type !== "message") continue;
|
|
4047
|
+
const role = entry.message?.role;
|
|
4048
|
+
if (role === "user") {
|
|
4049
|
+
flushPendingAssistant();
|
|
4050
|
+
const text2 = this.extractText(entry.message);
|
|
4051
|
+
if (!text2) continue;
|
|
4052
|
+
messages.push({
|
|
4053
|
+
id: entry.id,
|
|
4054
|
+
role,
|
|
4055
|
+
text: text2,
|
|
4056
|
+
timestamp: entry.timestamp
|
|
4057
|
+
});
|
|
4058
|
+
continue;
|
|
4059
|
+
}
|
|
4060
|
+
if (role !== "assistant") continue;
|
|
4061
|
+
const text = this.extractText(entry.message);
|
|
4062
|
+
const toolCalls = this.extractToolCalls(entry.message, toolResultsById);
|
|
4063
|
+
const blocks = this.extractBlocks(
|
|
4064
|
+
entry.id,
|
|
4065
|
+
entry.message,
|
|
4066
|
+
toolResultsById
|
|
4067
|
+
);
|
|
4068
|
+
if (!text) {
|
|
4069
|
+
if (blocks.length > 0) {
|
|
4070
|
+
pendingBlocks = [...pendingBlocks, ...blocks];
|
|
4071
|
+
}
|
|
4072
|
+
if (toolCalls?.length) {
|
|
4073
|
+
pendingToolCalls = this.mergeToolCalls(pendingToolCalls, toolCalls);
|
|
4074
|
+
}
|
|
4075
|
+
if (!pendingMessageId) {
|
|
4076
|
+
pendingMessageId = entry.id;
|
|
4077
|
+
pendingTimestamp = entry.timestamp;
|
|
4078
|
+
}
|
|
4079
|
+
continue;
|
|
4080
|
+
}
|
|
4081
|
+
const mergedBlocks = pendingBlocks.length > 0 ? [...pendingBlocks, ...blocks] : blocks;
|
|
4082
|
+
const mergedToolCalls = this.mergeToolCalls(pendingToolCalls, toolCalls);
|
|
4083
|
+
messages.push({
|
|
4084
|
+
id: entry.id,
|
|
4085
|
+
role,
|
|
4086
|
+
text,
|
|
4087
|
+
timestamp: entry.timestamp,
|
|
4088
|
+
toolCalls: mergedToolCalls.length > 0 ? mergedToolCalls : void 0,
|
|
4089
|
+
blocks: mergedBlocks.length > 0 ? mergedBlocks : void 0
|
|
4090
|
+
});
|
|
4091
|
+
pendingBlocks = [];
|
|
4092
|
+
pendingToolCalls = [];
|
|
4093
|
+
pendingMessageId = "";
|
|
4094
|
+
pendingTimestamp = "";
|
|
4095
|
+
}
|
|
4096
|
+
flushPendingAssistant();
|
|
4097
|
+
return messages.slice(-safeLimit);
|
|
4098
|
+
}
|
|
4099
|
+
findLatestSessionFile(channelDir) {
|
|
4100
|
+
if (!fs14.existsSync(channelDir)) return null;
|
|
4101
|
+
let stats;
|
|
4102
|
+
try {
|
|
4103
|
+
stats = fs14.statSync(channelDir);
|
|
4104
|
+
} catch {
|
|
4105
|
+
return null;
|
|
4106
|
+
}
|
|
4107
|
+
if (!stats.isDirectory()) return null;
|
|
4108
|
+
const files = fs14.readdirSync(channelDir).filter((file) => file.endsWith(".jsonl")).sort((a, b) => b.localeCompare(a));
|
|
4109
|
+
return files[0] ? path14.join(channelDir, files[0]) : null;
|
|
4110
|
+
}
|
|
4111
|
+
loadEntries(filePath) {
|
|
4112
|
+
try {
|
|
4113
|
+
const content = fs14.readFileSync(filePath, "utf-8");
|
|
4114
|
+
const fileEntries = parseSessionEntries(content);
|
|
4115
|
+
return fileEntries.filter(
|
|
4116
|
+
(entry) => entry.type !== "session"
|
|
4117
|
+
);
|
|
4118
|
+
} catch (err) {
|
|
4119
|
+
console.warn(`[ConversationService] Failed to load ${filePath}:`, err);
|
|
4120
|
+
return [];
|
|
4121
|
+
}
|
|
4122
|
+
}
|
|
4123
|
+
extractText(message) {
|
|
4124
|
+
if (!message?.content) return "";
|
|
4125
|
+
if (typeof message.content === "string") return message.content.trim();
|
|
4126
|
+
if (!Array.isArray(message.content)) return "";
|
|
4127
|
+
return message.content.filter((item) => item?.type === "text").map((item) => typeof item?.text === "string" ? item.text : "").join("").trim();
|
|
4128
|
+
}
|
|
4129
|
+
extractTextPreview(entry, maxLen) {
|
|
4130
|
+
const text = this.extractText(entry.message);
|
|
4131
|
+
return text.length > maxLen ? `${text.slice(0, maxLen)}\u2026` : text;
|
|
4132
|
+
}
|
|
4133
|
+
collectToolResultStates(entries) {
|
|
4134
|
+
const toolResultsById = /* @__PURE__ */ new Map();
|
|
4135
|
+
for (const entry of entries) {
|
|
4136
|
+
if (entry.type !== "message") continue;
|
|
4137
|
+
if (entry.message?.role !== "toolResult") continue;
|
|
4138
|
+
if (typeof entry.message?.toolCallId !== "string" || !entry.message.toolCallId) {
|
|
4139
|
+
continue;
|
|
4140
|
+
}
|
|
4141
|
+
toolResultsById.set(entry.message.toolCallId, {
|
|
4142
|
+
isError: entry.message?.isError === true,
|
|
4143
|
+
result: this.extractToolResultValue(entry.message)
|
|
4144
|
+
});
|
|
4145
|
+
}
|
|
4146
|
+
return toolResultsById;
|
|
4147
|
+
}
|
|
4148
|
+
extractToolCalls(message, toolResultsById) {
|
|
4149
|
+
if (!Array.isArray(message?.content)) return void 0;
|
|
4150
|
+
const toolCalls = message.content.filter((item) => item?.type === "toolCall").map((item) => {
|
|
4151
|
+
const id = typeof item?.id === "string" && item.id ? item.id : "unknown";
|
|
4152
|
+
const name = typeof item?.name === "string" && item.name ? item.name : "unknown";
|
|
4153
|
+
const toolCall = {
|
|
4154
|
+
id,
|
|
4155
|
+
name,
|
|
4156
|
+
isError: toolResultsById.get(id)?.isError === true
|
|
4157
|
+
};
|
|
4158
|
+
if (name === "send_file") {
|
|
4159
|
+
const args = this.extractSendFileArguments(item?.arguments);
|
|
4160
|
+
if (args) {
|
|
4161
|
+
toolCall.arguments = args;
|
|
4162
|
+
}
|
|
4163
|
+
}
|
|
4164
|
+
return toolCall;
|
|
4165
|
+
});
|
|
4166
|
+
return toolCalls.length > 0 ? toolCalls : void 0;
|
|
4167
|
+
}
|
|
4168
|
+
extractBlocks(messageId, message, toolResultsById) {
|
|
4169
|
+
if (!Array.isArray(message?.content)) {
|
|
4170
|
+
return [];
|
|
4171
|
+
}
|
|
4172
|
+
const blocks = [];
|
|
4173
|
+
message.content.forEach((item, index) => {
|
|
4174
|
+
if (item?.type === "thinking") {
|
|
4175
|
+
const thinkingText = this.extractThinkingText(item);
|
|
4176
|
+
if (thinkingText) {
|
|
4177
|
+
blocks.push({
|
|
4178
|
+
id: `${messageId}-thinking-${index}`,
|
|
4179
|
+
type: "thinking",
|
|
4180
|
+
text: thinkingText
|
|
4181
|
+
});
|
|
4182
|
+
}
|
|
4183
|
+
return;
|
|
4184
|
+
}
|
|
4185
|
+
if (item?.type !== "toolCall") {
|
|
4186
|
+
return;
|
|
4187
|
+
}
|
|
4188
|
+
const toolCallId = typeof item?.id === "string" && item.id ? item.id : `${messageId}-tool-${index}`;
|
|
4189
|
+
const toolName = typeof item?.name === "string" && item.name ? item.name : "unknown";
|
|
4190
|
+
const toolResult = toolResultsById.get(toolCallId);
|
|
4191
|
+
const sendFileArgs = toolName === "send_file" ? this.extractSendFileArguments(item?.arguments) : void 0;
|
|
4192
|
+
if (toolName === "send_file" && !toolResult?.isError && sendFileArgs?.filePath) {
|
|
4193
|
+
blocks.push({
|
|
4194
|
+
id: toolCallId,
|
|
4195
|
+
type: "file",
|
|
4196
|
+
filename: this.getFileBaseName(sendFileArgs.filePath),
|
|
4197
|
+
filePath: sendFileArgs.filePath,
|
|
4198
|
+
caption: sendFileArgs.caption
|
|
4199
|
+
});
|
|
4200
|
+
return;
|
|
4201
|
+
}
|
|
4202
|
+
blocks.push({
|
|
4203
|
+
id: toolCallId,
|
|
4204
|
+
type: "tool",
|
|
4205
|
+
toolCallId,
|
|
4206
|
+
toolName,
|
|
4207
|
+
toolInput: item?.arguments,
|
|
4208
|
+
result: toolResult?.result,
|
|
4209
|
+
isError: toolResult?.isError === true,
|
|
4210
|
+
status: toolResult ? "done" : "running"
|
|
4211
|
+
});
|
|
4212
|
+
});
|
|
4213
|
+
return blocks;
|
|
4214
|
+
}
|
|
4215
|
+
extractThinkingText(item) {
|
|
4216
|
+
if (typeof item?.thinking === "string" && item.thinking.trim()) {
|
|
4217
|
+
return item.thinking.trim();
|
|
4218
|
+
}
|
|
4219
|
+
if (typeof item?.thinkingSignature !== "string" || !item.thinkingSignature) {
|
|
4220
|
+
return "";
|
|
4221
|
+
}
|
|
4222
|
+
try {
|
|
4223
|
+
const parsed = JSON.parse(item.thinkingSignature);
|
|
4224
|
+
if (!Array.isArray(parsed?.summary)) {
|
|
4225
|
+
return "";
|
|
4226
|
+
}
|
|
4227
|
+
return parsed.summary.map(
|
|
4228
|
+
(summaryItem) => typeof summaryItem?.text === "string" ? summaryItem.text.trim() : ""
|
|
4229
|
+
).filter(Boolean).join("\n\n").trim();
|
|
4230
|
+
} catch {
|
|
4231
|
+
return "";
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4234
|
+
extractToolResultValue(message) {
|
|
4235
|
+
if (!message?.content) {
|
|
4236
|
+
return void 0;
|
|
4237
|
+
}
|
|
4238
|
+
if (typeof message.content === "string") {
|
|
4239
|
+
return this.parsePossibleJson(message.content);
|
|
4240
|
+
}
|
|
4241
|
+
if (!Array.isArray(message.content)) {
|
|
4242
|
+
return message.content;
|
|
4243
|
+
}
|
|
4244
|
+
const textContent = message.content.filter((item) => item?.type === "text").map((item) => typeof item?.text === "string" ? item.text : "").join("").trim();
|
|
4245
|
+
if (textContent) {
|
|
4246
|
+
return this.parsePossibleJson(textContent);
|
|
4247
|
+
}
|
|
4248
|
+
return message.content;
|
|
4249
|
+
}
|
|
4250
|
+
parsePossibleJson(value) {
|
|
4251
|
+
const trimmed = value.trim();
|
|
4252
|
+
if (!trimmed) {
|
|
4253
|
+
return "";
|
|
4254
|
+
}
|
|
4255
|
+
try {
|
|
4256
|
+
return JSON.parse(trimmed);
|
|
4257
|
+
} catch {
|
|
4258
|
+
return trimmed;
|
|
4259
|
+
}
|
|
4260
|
+
}
|
|
4261
|
+
extractSendFileArguments(rawArguments) {
|
|
4262
|
+
if (!rawArguments || typeof rawArguments !== "object") {
|
|
4263
|
+
return void 0;
|
|
3272
4264
|
}
|
|
4265
|
+
const maybeArgs = rawArguments;
|
|
4266
|
+
const filePath = typeof maybeArgs.filePath === "string" && maybeArgs.filePath ? maybeArgs.filePath : void 0;
|
|
4267
|
+
const caption = typeof maybeArgs.caption === "string" && maybeArgs.caption ? maybeArgs.caption : void 0;
|
|
4268
|
+
if (!filePath && !caption) {
|
|
4269
|
+
return void 0;
|
|
4270
|
+
}
|
|
4271
|
+
return {
|
|
4272
|
+
filePath,
|
|
4273
|
+
caption
|
|
4274
|
+
};
|
|
3273
4275
|
}
|
|
3274
|
-
|
|
3275
|
-
return
|
|
4276
|
+
hasVisibleSendFileToolCall(toolCalls) {
|
|
4277
|
+
return Boolean(
|
|
4278
|
+
toolCalls?.some(
|
|
4279
|
+
(toolCall) => toolCall.name === "send_file" && !toolCall.isError && typeof toolCall.arguments?.filePath === "string" && toolCall.arguments.filePath.length > 0
|
|
4280
|
+
)
|
|
4281
|
+
);
|
|
3276
4282
|
}
|
|
3277
|
-
|
|
3278
|
-
const
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
4283
|
+
mergeToolCalls(left, right) {
|
|
4284
|
+
const merged = [...left || [], ...right || []];
|
|
4285
|
+
const byId = /* @__PURE__ */ new Map();
|
|
4286
|
+
for (const toolCall of merged) {
|
|
4287
|
+
byId.set(toolCall.id, toolCall);
|
|
3282
4288
|
}
|
|
4289
|
+
return [...byId.values()];
|
|
3283
4290
|
}
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
4291
|
+
getFileBaseName(filePath) {
|
|
4292
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
4293
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
4294
|
+
return parts[parts.length - 1] || filePath;
|
|
3287
4295
|
}
|
|
3288
|
-
|
|
3289
|
-
|
|
4296
|
+
detectPlatform(channelId) {
|
|
4297
|
+
if (channelId.startsWith("telegram-")) return "telegram";
|
|
4298
|
+
if (channelId.startsWith("slack-")) return "slack";
|
|
4299
|
+
if (channelId.startsWith("scheduler-")) return "scheduler";
|
|
4300
|
+
return "web";
|
|
3290
4301
|
}
|
|
3291
|
-
|
|
3292
|
-
return
|
|
4302
|
+
isLegacyWebConversation(channelId) {
|
|
4303
|
+
return channelId.startsWith("web-");
|
|
3293
4304
|
}
|
|
3294
4305
|
};
|
|
3295
4306
|
|
|
3296
4307
|
// 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
4308
|
function getPackConfig(rootDir) {
|
|
3303
|
-
const raw =
|
|
4309
|
+
const raw = fs15.readFileSync(path15.join(rootDir, "skillpack.json"), "utf-8");
|
|
3304
4310
|
return JSON.parse(raw);
|
|
3305
4311
|
}
|
|
3306
4312
|
function parseCommand(text) {
|
|
@@ -3322,15 +4328,30 @@ function getRuntimeConfigSignature(config) {
|
|
|
3322
4328
|
slackAppToken: config.adapters?.slack?.appToken || ""
|
|
3323
4329
|
});
|
|
3324
4330
|
}
|
|
4331
|
+
function parsePositiveInt(value, fallback) {
|
|
4332
|
+
const parsed = Number(value);
|
|
4333
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
4334
|
+
return fallback;
|
|
4335
|
+
}
|
|
4336
|
+
return Math.floor(parsed);
|
|
4337
|
+
}
|
|
4338
|
+
function resolveDownloadFilePath(rootDir, filePath) {
|
|
4339
|
+
const resolvedPath = path15.isAbsolute(filePath) ? path15.resolve(filePath) : path15.resolve(rootDir, filePath);
|
|
4340
|
+
return isWithinDirectory(rootDir, resolvedPath) ? resolvedPath : null;
|
|
4341
|
+
}
|
|
3325
4342
|
var WebAdapter = class {
|
|
3326
4343
|
name = "web";
|
|
3327
4344
|
wss = null;
|
|
3328
4345
|
agent = null;
|
|
3329
4346
|
ipcBroadcaster = null;
|
|
4347
|
+
conversationService = null;
|
|
4348
|
+
socketsByChannel = /* @__PURE__ */ new Map();
|
|
3330
4349
|
async start(ctx) {
|
|
3331
4350
|
const { agent, server, app, rootDir, lifecycle } = ctx;
|
|
3332
4351
|
this.agent = agent;
|
|
3333
4352
|
this.ipcBroadcaster = ctx.ipcBroadcaster ?? null;
|
|
4353
|
+
this.conversationService = new ConversationService(rootDir);
|
|
4354
|
+
const resultsQueryService = ctx.resultsQueryService ?? null;
|
|
3334
4355
|
const currentConf = configManager.getConfig();
|
|
3335
4356
|
let apiKey = currentConf.apiKey || "";
|
|
3336
4357
|
let currentProvider = currentConf.provider || "openai";
|
|
@@ -3448,12 +4469,42 @@ var WebAdapter = class {
|
|
|
3448
4469
|
app.delete("/api/chat", (_req, res) => {
|
|
3449
4470
|
res.json({ success: true });
|
|
3450
4471
|
});
|
|
3451
|
-
|
|
3452
|
-
const
|
|
3453
|
-
|
|
4472
|
+
const getWebConversations = () => {
|
|
4473
|
+
const activeChannels = new Set(agent.getActiveChannelIds());
|
|
4474
|
+
return this.conversationService.listConversations(activeChannels, {
|
|
4475
|
+
includeDefaultWeb: true,
|
|
4476
|
+
includeLegacyWeb: false,
|
|
4477
|
+
allowedPlatforms: ["web"]
|
|
4478
|
+
});
|
|
4479
|
+
};
|
|
4480
|
+
app.get("/api/conversations", (_req, res) => {
|
|
4481
|
+
res.json(getWebConversations());
|
|
4482
|
+
});
|
|
4483
|
+
app.post("/api/conversations", (_req, res) => {
|
|
4484
|
+
res.json({ channelId: DEFAULT_WEB_CHANNEL_ID });
|
|
4485
|
+
});
|
|
4486
|
+
app.get("/api/conversations/:channelId/messages", (req, res) => {
|
|
4487
|
+
if (req.params.channelId !== DEFAULT_WEB_CHANNEL_ID) {
|
|
4488
|
+
res.status(404).json({ error: "Conversation not found" });
|
|
4489
|
+
return;
|
|
4490
|
+
}
|
|
4491
|
+
res.json(
|
|
4492
|
+
this.conversationService.getMessages(
|
|
4493
|
+
req.params.channelId,
|
|
4494
|
+
parsePositiveInt(req.query.limit, 100)
|
|
4495
|
+
)
|
|
4496
|
+
);
|
|
3454
4497
|
});
|
|
3455
|
-
app.get("/api/
|
|
3456
|
-
|
|
4498
|
+
app.get("/api/results/artifacts", (req, res) => {
|
|
4499
|
+
if (!resultsQueryService) {
|
|
4500
|
+
res.status(503).json({ error: "Results query service is not available" });
|
|
4501
|
+
return;
|
|
4502
|
+
}
|
|
4503
|
+
res.json(resultsQueryService.listRecentArtifacts({
|
|
4504
|
+
channelId: typeof req.query.channelId === "string" ? req.query.channelId : void 0,
|
|
4505
|
+
limit: parsePositiveInt(req.query.limit, 100),
|
|
4506
|
+
offset: parsePositiveInt(req.query.offset, 0)
|
|
4507
|
+
}));
|
|
3457
4508
|
});
|
|
3458
4509
|
app.get("/api/files", (req, res) => {
|
|
3459
4510
|
const filePath = req.query.path;
|
|
@@ -3461,23 +4512,22 @@ var WebAdapter = class {
|
|
|
3461
4512
|
res.status(400).json({ error: "Missing 'path' query parameter" });
|
|
3462
4513
|
return;
|
|
3463
4514
|
}
|
|
3464
|
-
const resolvedPath =
|
|
3465
|
-
|
|
3466
|
-
if (!resolvedPath.startsWith(dataDir)) {
|
|
4515
|
+
const resolvedPath = resolveDownloadFilePath(rootDir, filePath);
|
|
4516
|
+
if (!resolvedPath) {
|
|
3467
4517
|
res.status(403).json({ error: "Access denied" });
|
|
3468
4518
|
return;
|
|
3469
4519
|
}
|
|
3470
|
-
if (!
|
|
4520
|
+
if (!fs15.existsSync(resolvedPath)) {
|
|
3471
4521
|
res.status(404).json({ error: "File not found" });
|
|
3472
4522
|
return;
|
|
3473
4523
|
}
|
|
3474
|
-
const filename =
|
|
4524
|
+
const filename = path15.basename(resolvedPath);
|
|
3475
4525
|
res.setHeader("Content-Type", "application/octet-stream");
|
|
3476
4526
|
res.setHeader(
|
|
3477
4527
|
"Content-Disposition",
|
|
3478
4528
|
`attachment; filename="${filename}"`
|
|
3479
4529
|
);
|
|
3480
|
-
|
|
4530
|
+
fs15.createReadStream(resolvedPath).pipe(res);
|
|
3481
4531
|
});
|
|
3482
4532
|
const getScheduler = () => {
|
|
3483
4533
|
const schedulerAdapter = ctx.adapterMap?.get("scheduler");
|
|
@@ -3571,7 +4621,8 @@ var WebAdapter = class {
|
|
|
3571
4621
|
ws.close();
|
|
3572
4622
|
return;
|
|
3573
4623
|
}
|
|
3574
|
-
const
|
|
4624
|
+
const requestedChannelId = url.searchParams.get("channelId");
|
|
4625
|
+
const channelId = requestedChannelId && requestedChannelId === DEFAULT_WEB_CHANNEL_ID ? requestedChannelId : DEFAULT_WEB_CHANNEL_ID;
|
|
3575
4626
|
this.handleWsConnection(ws, channelId, agent);
|
|
3576
4627
|
});
|
|
3577
4628
|
console.log("[WebAdapter] Started");
|
|
@@ -3584,12 +4635,29 @@ var WebAdapter = class {
|
|
|
3584
4635
|
this.wss.close();
|
|
3585
4636
|
this.wss = null;
|
|
3586
4637
|
}
|
|
4638
|
+
this.socketsByChannel.clear();
|
|
3587
4639
|
console.log("[WebAdapter] Stopped");
|
|
3588
4640
|
}
|
|
4641
|
+
async sendMessage(channelId, text) {
|
|
4642
|
+
const sockets = this.socketsByChannel.get(channelId);
|
|
4643
|
+
const activeSockets = [...sockets || []].filter(
|
|
4644
|
+
(socket) => socket.readyState === socket.OPEN
|
|
4645
|
+
);
|
|
4646
|
+
if (activeSockets.length === 0) {
|
|
4647
|
+
throw new Error(`[Web] No active WebSocket clients for channelId: ${channelId}`);
|
|
4648
|
+
}
|
|
4649
|
+
for (const socket of activeSockets) {
|
|
4650
|
+
sendWsEvent(socket, { type: "message_start", role: "assistant" });
|
|
4651
|
+
sendWsEvent(socket, { type: "text_delta", delta: text });
|
|
4652
|
+
sendWsEvent(socket, { type: "message_end", role: "assistant" });
|
|
4653
|
+
socket.send(JSON.stringify({ done: true }));
|
|
4654
|
+
}
|
|
4655
|
+
}
|
|
3589
4656
|
// -------------------------------------------------------------------------
|
|
3590
4657
|
// WebSocket message handler
|
|
3591
4658
|
// -------------------------------------------------------------------------
|
|
3592
4659
|
handleWsConnection(ws, channelId, agent) {
|
|
4660
|
+
this.addSocket(channelId, ws);
|
|
3593
4661
|
ws.on("message", async (data) => {
|
|
3594
4662
|
try {
|
|
3595
4663
|
const payload = JSON.parse(data.toString());
|
|
@@ -3619,154 +4687,27 @@ var WebAdapter = class {
|
|
|
3619
4687
|
}
|
|
3620
4688
|
});
|
|
3621
4689
|
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
|
-
}
|
|
4690
|
+
this.removeSocket(channelId, ws);
|
|
4691
|
+
if (channelId !== DEFAULT_WEB_CHANNEL_ID) {
|
|
4692
|
+
agent.dispose(channelId);
|
|
3675
4693
|
}
|
|
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
4694
|
});
|
|
3690
4695
|
}
|
|
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);
|
|
4696
|
+
addSocket(channelId, ws) {
|
|
4697
|
+
const sockets = this.socketsByChannel.get(channelId) ?? /* @__PURE__ */ new Set();
|
|
4698
|
+
sockets.add(ws);
|
|
4699
|
+
this.socketsByChannel.set(channelId, sockets);
|
|
3723
4700
|
}
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
stats = fs11.statSync(channelDir);
|
|
3729
|
-
} catch {
|
|
3730
|
-
return null;
|
|
4701
|
+
removeSocket(channelId, ws) {
|
|
4702
|
+
const sockets = this.socketsByChannel.get(channelId);
|
|
4703
|
+
if (!sockets) {
|
|
4704
|
+
return;
|
|
3731
4705
|
}
|
|
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 [];
|
|
4706
|
+
sockets.delete(ws);
|
|
4707
|
+
if (sockets.size === 0) {
|
|
4708
|
+
this.socketsByChannel.delete(channelId);
|
|
3744
4709
|
}
|
|
3745
4710
|
}
|
|
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
4711
|
};
|
|
3771
4712
|
|
|
3772
4713
|
// src/runtime/adapters/ipc.ts
|
|
@@ -3777,6 +4718,7 @@ var IpcAdapter = class {
|
|
|
3777
4718
|
rootDir = "";
|
|
3778
4719
|
adapterMap = null;
|
|
3779
4720
|
conversationService = null;
|
|
4721
|
+
resultsQueryService = null;
|
|
3780
4722
|
createdChannels = /* @__PURE__ */ new Set();
|
|
3781
4723
|
messageListener;
|
|
3782
4724
|
started = false;
|
|
@@ -3788,6 +4730,7 @@ var IpcAdapter = class {
|
|
|
3788
4730
|
this.rootDir = ctx.rootDir;
|
|
3789
4731
|
this.adapterMap = ctx.adapterMap ?? null;
|
|
3790
4732
|
this.conversationService = new ConversationService(ctx.rootDir);
|
|
4733
|
+
this.resultsQueryService = ctx.resultsQueryService ?? null;
|
|
3791
4734
|
this.messageListener = (message) => {
|
|
3792
4735
|
if (!this.isIpcRequest(message)) return;
|
|
3793
4736
|
void this.handleRequest(message);
|
|
@@ -3846,12 +4789,15 @@ var IpcAdapter = class {
|
|
|
3846
4789
|
for (const channelId of this.createdChannels) {
|
|
3847
4790
|
activeChannels.add(channelId);
|
|
3848
4791
|
}
|
|
3849
|
-
const conversations = this.conversationService.listConversations(activeChannels
|
|
4792
|
+
const conversations = this.conversationService.listConversations(activeChannels, {
|
|
4793
|
+
includeDefaultWeb: true,
|
|
4794
|
+
includeLegacyWeb: false
|
|
4795
|
+
});
|
|
3850
4796
|
this.reply(request.id, conversations);
|
|
3851
4797
|
return;
|
|
3852
4798
|
}
|
|
3853
4799
|
case "create_conversation": {
|
|
3854
|
-
const channelId =
|
|
4800
|
+
const channelId = DEFAULT_WEB_CHANNEL_ID;
|
|
3855
4801
|
this.createdChannels.add(channelId);
|
|
3856
4802
|
this.reply(request.id, { channelId });
|
|
3857
4803
|
return;
|
|
@@ -3868,6 +4814,18 @@ var IpcAdapter = class {
|
|
|
3868
4814
|
this.reply(request.id, messages);
|
|
3869
4815
|
return;
|
|
3870
4816
|
}
|
|
4817
|
+
case "get_recent_artifacts": {
|
|
4818
|
+
if (!this.resultsQueryService) {
|
|
4819
|
+
this.replyError(request.id, "Results query service is not available");
|
|
4820
|
+
return;
|
|
4821
|
+
}
|
|
4822
|
+
this.reply(request.id, this.resultsQueryService.listRecentArtifacts({
|
|
4823
|
+
channelId: request.channelId,
|
|
4824
|
+
limit: request.limit,
|
|
4825
|
+
offset: request.offset
|
|
4826
|
+
}));
|
|
4827
|
+
return;
|
|
4828
|
+
}
|
|
3871
4829
|
case "send_message": {
|
|
3872
4830
|
if (!request.channelId || typeof request.channelId !== "string") {
|
|
3873
4831
|
this.replyError(request.id, "channelId is required");
|
|
@@ -3950,6 +4908,48 @@ var IpcAdapter = class {
|
|
|
3950
4908
|
this.reply(request.id, result);
|
|
3951
4909
|
return;
|
|
3952
4910
|
}
|
|
4911
|
+
case "update_scheduled_job": {
|
|
4912
|
+
const scheduler = this.getSchedulerAdapter();
|
|
4913
|
+
if (!scheduler) {
|
|
4914
|
+
this.replyError(request.id, "Scheduler adapter is not available");
|
|
4915
|
+
return;
|
|
4916
|
+
}
|
|
4917
|
+
const result = scheduler.updateJob(request.name, request.updates);
|
|
4918
|
+
if (!result.success) {
|
|
4919
|
+
this.replyError(request.id, result.message);
|
|
4920
|
+
return;
|
|
4921
|
+
}
|
|
4922
|
+
this.reply(request.id, result);
|
|
4923
|
+
return;
|
|
4924
|
+
}
|
|
4925
|
+
case "set_scheduled_job_enabled": {
|
|
4926
|
+
const scheduler = this.getSchedulerAdapter();
|
|
4927
|
+
if (!scheduler) {
|
|
4928
|
+
this.replyError(request.id, "Scheduler adapter is not available");
|
|
4929
|
+
return;
|
|
4930
|
+
}
|
|
4931
|
+
const result = scheduler.setEnabled(request.name, request.enabled);
|
|
4932
|
+
if (!result.success) {
|
|
4933
|
+
this.replyError(request.id, result.message);
|
|
4934
|
+
return;
|
|
4935
|
+
}
|
|
4936
|
+
this.reply(request.id, result);
|
|
4937
|
+
return;
|
|
4938
|
+
}
|
|
4939
|
+
case "trigger_scheduled_job": {
|
|
4940
|
+
const scheduler = this.getSchedulerAdapter();
|
|
4941
|
+
if (!scheduler) {
|
|
4942
|
+
this.replyError(request.id, "Scheduler adapter is not available");
|
|
4943
|
+
return;
|
|
4944
|
+
}
|
|
4945
|
+
const result = await scheduler.triggerJob(request.name);
|
|
4946
|
+
if (!result.success) {
|
|
4947
|
+
this.replyError(request.id, result.message);
|
|
4948
|
+
return;
|
|
4949
|
+
}
|
|
4950
|
+
this.reply(request.id, result);
|
|
4951
|
+
return;
|
|
4952
|
+
}
|
|
3953
4953
|
case "remove_scheduled_job": {
|
|
3954
4954
|
const scheduler = this.getSchedulerAdapter();
|
|
3955
4955
|
if (!scheduler) {
|
|
@@ -3996,9 +4996,6 @@ var IpcAdapter = class {
|
|
|
3996
4996
|
}
|
|
3997
4997
|
};
|
|
3998
4998
|
|
|
3999
|
-
// src/runtime/server.ts
|
|
4000
|
-
init_config();
|
|
4001
|
-
|
|
4002
4999
|
// src/runtime/lifecycle.ts
|
|
4003
5000
|
var SHUTDOWN_EXIT_CODE = 64;
|
|
4004
5001
|
var RESTART_EXIT_CODE = 75;
|
|
@@ -4072,28 +5069,28 @@ var Lifecycle = class {
|
|
|
4072
5069
|
|
|
4073
5070
|
// src/runtime/registry.ts
|
|
4074
5071
|
import crypto from "crypto";
|
|
4075
|
-
import
|
|
5072
|
+
import fs16 from "fs";
|
|
4076
5073
|
import os from "os";
|
|
4077
|
-
import
|
|
4078
|
-
var SKILLPACK_HOME =
|
|
4079
|
-
var LEGACY_REGISTRY_FILE =
|
|
4080
|
-
var REGISTRY_DIR =
|
|
5074
|
+
import path16 from "path";
|
|
5075
|
+
var SKILLPACK_HOME = path16.join(os.homedir(), ".skillpack");
|
|
5076
|
+
var LEGACY_REGISTRY_FILE = path16.join(SKILLPACK_HOME, "registry.json");
|
|
5077
|
+
var REGISTRY_DIR = path16.join(SKILLPACK_HOME, "registry.d");
|
|
4081
5078
|
var migrationChecked = false;
|
|
4082
5079
|
function ensureHomeDir() {
|
|
4083
|
-
if (!
|
|
4084
|
-
|
|
5080
|
+
if (!fs16.existsSync(SKILLPACK_HOME)) {
|
|
5081
|
+
fs16.mkdirSync(SKILLPACK_HOME, { recursive: true });
|
|
4085
5082
|
}
|
|
4086
5083
|
}
|
|
4087
5084
|
function ensureRegistryDir() {
|
|
4088
5085
|
ensureHomeDir();
|
|
4089
|
-
if (!
|
|
4090
|
-
|
|
5086
|
+
if (!fs16.existsSync(REGISTRY_DIR)) {
|
|
5087
|
+
fs16.mkdirSync(REGISTRY_DIR, { recursive: true });
|
|
4091
5088
|
}
|
|
4092
5089
|
}
|
|
4093
5090
|
function canonicalizeDir(dir) {
|
|
4094
|
-
const resolved =
|
|
5091
|
+
const resolved = path16.resolve(dir);
|
|
4095
5092
|
try {
|
|
4096
|
-
return
|
|
5093
|
+
return fs16.realpathSync(resolved);
|
|
4097
5094
|
} catch {
|
|
4098
5095
|
return resolved;
|
|
4099
5096
|
}
|
|
@@ -4102,7 +5099,7 @@ function hashDir(dir) {
|
|
|
4102
5099
|
return crypto.createHash("md5").update(canonicalizeDir(dir)).digest("hex");
|
|
4103
5100
|
}
|
|
4104
5101
|
function getEntryPathForCanonicalDir(dir) {
|
|
4105
|
-
return
|
|
5102
|
+
return path16.join(REGISTRY_DIR, `${hashDir(dir)}.json`);
|
|
4106
5103
|
}
|
|
4107
5104
|
function getEntryPath(dir) {
|
|
4108
5105
|
ensureRegistryReady();
|
|
@@ -4110,11 +5107,11 @@ function getEntryPath(dir) {
|
|
|
4110
5107
|
}
|
|
4111
5108
|
function listEntryFiles() {
|
|
4112
5109
|
ensureRegistryReady();
|
|
4113
|
-
return
|
|
5110
|
+
return fs16.readdirSync(REGISTRY_DIR).filter((file) => file.endsWith(".json")).sort().map((file) => path16.join(REGISTRY_DIR, file));
|
|
4114
5111
|
}
|
|
4115
5112
|
function readEntryFile(filePath) {
|
|
4116
5113
|
try {
|
|
4117
|
-
const raw =
|
|
5114
|
+
const raw = fs16.readFileSync(filePath, "utf-8");
|
|
4118
5115
|
const data = JSON.parse(raw);
|
|
4119
5116
|
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
5117
|
return null;
|
|
@@ -4147,8 +5144,8 @@ function writeEntryFile(entry) {
|
|
|
4147
5144
|
};
|
|
4148
5145
|
const entryPath = getEntryPathForCanonicalDir(normalized.dir);
|
|
4149
5146
|
const tmpPath = createTmpPath(entryPath);
|
|
4150
|
-
|
|
4151
|
-
|
|
5147
|
+
fs16.writeFileSync(tmpPath, JSON.stringify(normalized, null, 2), "utf-8");
|
|
5148
|
+
fs16.renameSync(tmpPath, entryPath);
|
|
4152
5149
|
}
|
|
4153
5150
|
function migrateLegacyRegistryIfNeeded() {
|
|
4154
5151
|
if (migrationChecked) {
|
|
@@ -4156,14 +5153,14 @@ function migrateLegacyRegistryIfNeeded() {
|
|
|
4156
5153
|
}
|
|
4157
5154
|
migrationChecked = true;
|
|
4158
5155
|
ensureRegistryDir();
|
|
4159
|
-
if (!
|
|
5156
|
+
if (!fs16.existsSync(LEGACY_REGISTRY_FILE)) {
|
|
4160
5157
|
return;
|
|
4161
5158
|
}
|
|
4162
5159
|
if (listEntryFiles().length > 0) {
|
|
4163
5160
|
return;
|
|
4164
5161
|
}
|
|
4165
5162
|
try {
|
|
4166
|
-
const raw =
|
|
5163
|
+
const raw = fs16.readFileSync(LEGACY_REGISTRY_FILE, "utf-8");
|
|
4167
5164
|
const data = JSON.parse(raw);
|
|
4168
5165
|
const packs = Array.isArray(data?.packs) ? data.packs : [];
|
|
4169
5166
|
for (const pack of packs) {
|
|
@@ -4176,7 +5173,7 @@ function migrateLegacyRegistryIfNeeded() {
|
|
|
4176
5173
|
} catch {
|
|
4177
5174
|
}
|
|
4178
5175
|
}
|
|
4179
|
-
|
|
5176
|
+
fs16.renameSync(LEGACY_REGISTRY_FILE, `${LEGACY_REGISTRY_FILE}.legacy`);
|
|
4180
5177
|
} catch (err) {
|
|
4181
5178
|
console.warn(" [Registry] Failed to migrate legacy registry.json:", err);
|
|
4182
5179
|
}
|
|
@@ -4229,13 +5226,13 @@ function deregister(dir, pid) {
|
|
|
4229
5226
|
}
|
|
4230
5227
|
|
|
4231
5228
|
// src/runtime/server.ts
|
|
4232
|
-
var __dirname =
|
|
5229
|
+
var __dirname = path18.dirname(fileURLToPath2(import.meta.url));
|
|
4233
5230
|
async function startServer(options) {
|
|
4234
5231
|
const {
|
|
4235
5232
|
rootDir,
|
|
4236
5233
|
host = process.env.HOST || "127.0.0.1",
|
|
4237
5234
|
port = Number(process.env.PORT) || 26313,
|
|
4238
|
-
|
|
5235
|
+
runtimeMode = process.env.SKILLPACK_RUNTIME_MODE === "embedded" ? "embedded" : "standalone"
|
|
4239
5236
|
} = options;
|
|
4240
5237
|
const dataConfig = configManager.load(rootDir);
|
|
4241
5238
|
const apiKey = dataConfig.apiKey || "";
|
|
@@ -4245,8 +5242,8 @@ async function startServer(options) {
|
|
|
4245
5242
|
const baseUrl = dataConfig.baseUrl?.trim() || void 0;
|
|
4246
5243
|
const modelId = dataConfig.modelId?.trim() || (SUPPORTED_PROVIDERS[provider]?.defaultModelId ?? SUPPORTED_PROVIDERS.openai.defaultModelId);
|
|
4247
5244
|
const apiProtocol = dataConfig.apiProtocol;
|
|
4248
|
-
const packageRoot =
|
|
4249
|
-
const webDir =
|
|
5245
|
+
const packageRoot = path18.resolve(__dirname, "..");
|
|
5246
|
+
const webDir = fs19.existsSync(path18.join(rootDir, "web")) ? path18.join(rootDir, "web") : path18.join(packageRoot, "web");
|
|
4250
5247
|
const app = express();
|
|
4251
5248
|
app.use(express.json());
|
|
4252
5249
|
app.use(express.static(webDir));
|
|
@@ -4264,6 +5261,13 @@ async function startServer(options) {
|
|
|
4264
5261
|
});
|
|
4265
5262
|
});
|
|
4266
5263
|
const lifecycle = new Lifecycle(server);
|
|
5264
|
+
const resultStore = new ResultStore(rootDir);
|
|
5265
|
+
const artifactSnapshotService = new ArtifactSnapshotService(rootDir);
|
|
5266
|
+
const artifactPersistenceService = new ArtifactPersistenceService(
|
|
5267
|
+
artifactSnapshotService,
|
|
5268
|
+
resultStore
|
|
5269
|
+
);
|
|
5270
|
+
const resultsQueryService = new ResultsQueryService(resultStore);
|
|
4267
5271
|
const agent = new PackAgent({
|
|
4268
5272
|
apiKey,
|
|
4269
5273
|
rootDir,
|
|
@@ -4271,12 +5275,14 @@ async function startServer(options) {
|
|
|
4271
5275
|
modelId,
|
|
4272
5276
|
baseUrl,
|
|
4273
5277
|
apiProtocol,
|
|
4274
|
-
lifecycleHandler: lifecycle
|
|
5278
|
+
lifecycleHandler: lifecycle,
|
|
5279
|
+
artifactPersistenceService
|
|
4275
5280
|
});
|
|
4276
5281
|
const adapters = [];
|
|
4277
5282
|
const adapterMap = /* @__PURE__ */ new Map();
|
|
4278
5283
|
const hasIpcChannel = typeof process.send === "function";
|
|
4279
5284
|
const ipcAdapter = new IpcAdapter();
|
|
5285
|
+
const webEnabled = runtimeMode === "standalone";
|
|
4280
5286
|
if (hasIpcChannel) {
|
|
4281
5287
|
await ipcAdapter.start({
|
|
4282
5288
|
agent,
|
|
@@ -4284,24 +5290,28 @@ async function startServer(options) {
|
|
|
4284
5290
|
app,
|
|
4285
5291
|
rootDir,
|
|
4286
5292
|
lifecycle,
|
|
4287
|
-
adapterMap
|
|
5293
|
+
adapterMap,
|
|
5294
|
+
resultsQueryService
|
|
4288
5295
|
});
|
|
4289
5296
|
adapters.push(ipcAdapter);
|
|
4290
5297
|
adapterMap.set(ipcAdapter.name, ipcAdapter);
|
|
4291
5298
|
}
|
|
4292
5299
|
const ipcBroadcaster = hasIpcChannel ? ipcAdapter : void 0;
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
5300
|
+
if (webEnabled) {
|
|
5301
|
+
const webAdapter = new WebAdapter();
|
|
5302
|
+
await webAdapter.start({
|
|
5303
|
+
agent,
|
|
5304
|
+
server,
|
|
5305
|
+
app,
|
|
5306
|
+
rootDir,
|
|
5307
|
+
lifecycle,
|
|
5308
|
+
adapterMap,
|
|
5309
|
+
ipcBroadcaster,
|
|
5310
|
+
resultsQueryService
|
|
5311
|
+
});
|
|
5312
|
+
adapters.push(webAdapter);
|
|
5313
|
+
adapterMap.set(webAdapter.name, webAdapter);
|
|
5314
|
+
}
|
|
4305
5315
|
if (dataConfig.adapters?.telegram?.token) {
|
|
4306
5316
|
try {
|
|
4307
5317
|
const { TelegramAdapter: TelegramAdapter2 } = await Promise.resolve().then(() => (init_telegram(), telegram_exports));
|
|
@@ -4315,7 +5325,8 @@ async function startServer(options) {
|
|
|
4315
5325
|
rootDir,
|
|
4316
5326
|
lifecycle,
|
|
4317
5327
|
adapterMap,
|
|
4318
|
-
ipcBroadcaster
|
|
5328
|
+
ipcBroadcaster,
|
|
5329
|
+
resultsQueryService
|
|
4319
5330
|
});
|
|
4320
5331
|
adapters.push(telegramAdapter);
|
|
4321
5332
|
adapterMap.set(telegramAdapter.name, telegramAdapter);
|
|
@@ -4343,7 +5354,8 @@ async function startServer(options) {
|
|
|
4343
5354
|
rootDir,
|
|
4344
5355
|
lifecycle,
|
|
4345
5356
|
adapterMap,
|
|
4346
|
-
ipcBroadcaster
|
|
5357
|
+
ipcBroadcaster,
|
|
5358
|
+
resultsQueryService
|
|
4347
5359
|
});
|
|
4348
5360
|
adapters.push(slackAdapter);
|
|
4349
5361
|
adapterMap.set(slackAdapter.name, slackAdapter);
|
|
@@ -4363,7 +5375,6 @@ async function startServer(options) {
|
|
|
4363
5375
|
}
|
|
4364
5376
|
await adapter.sendMessage(channelId, text);
|
|
4365
5377
|
};
|
|
4366
|
-
const scheduledJobs = dataConfig.scheduledJobs || [];
|
|
4367
5378
|
let schedulerAdapter = null;
|
|
4368
5379
|
try {
|
|
4369
5380
|
const { SchedulerAdapter: SchedulerAdapter2 } = await Promise.resolve().then(() => (init_scheduler(), scheduler_exports));
|
|
@@ -4375,13 +5386,11 @@ async function startServer(options) {
|
|
|
4375
5386
|
rootDir,
|
|
4376
5387
|
lifecycle,
|
|
4377
5388
|
notify: notifyFn,
|
|
4378
|
-
adapterMap
|
|
5389
|
+
adapterMap,
|
|
5390
|
+
resultsQueryService
|
|
4379
5391
|
});
|
|
4380
5392
|
adapters.push(schedulerAdapter);
|
|
4381
5393
|
adapterMap.set(schedulerAdapter.name, schedulerAdapter);
|
|
4382
|
-
if (scheduledJobs.length > 0) {
|
|
4383
|
-
console.log(`[Server] Scheduler started with ${scheduledJobs.length} job(s)`);
|
|
4384
|
-
}
|
|
4385
5394
|
} catch (err) {
|
|
4386
5395
|
console.error("[Scheduler] Failed to start:", err);
|
|
4387
5396
|
}
|
|
@@ -4389,34 +5398,35 @@ async function startServer(options) {
|
|
|
4389
5398
|
agent.setScheduler(schedulerAdapter);
|
|
4390
5399
|
}
|
|
4391
5400
|
lifecycle.registerAdapters(adapters);
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
console.log(`
|
|
5401
|
+
const announceReady = (actualPort) => {
|
|
5402
|
+
if (webEnabled) {
|
|
5403
|
+
const url = `http://${host}:${actualPort}`;
|
|
5404
|
+
console.log(`
|
|
4397
5405
|
Skills Pack Server`);
|
|
4398
|
-
|
|
5406
|
+
console.log(` Running at ${url}
|
|
4399
5407
|
`);
|
|
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) {
|
|
5408
|
+
try {
|
|
5409
|
+
register({
|
|
5410
|
+
dir: canonicalRootDir,
|
|
5411
|
+
name: packConfig.name,
|
|
5412
|
+
version: packConfig.version,
|
|
5413
|
+
port: actualPort
|
|
5414
|
+
});
|
|
5415
|
+
} catch (err) {
|
|
5416
|
+
console.warn(" [Registry] Could not register pack:", err);
|
|
5417
|
+
}
|
|
4414
5418
|
const cmd = process.platform === "darwin" ? `open ${url}` : process.platform === "win32" ? `start ${url}` : `xdg-open ${url}`;
|
|
4415
5419
|
exec(cmd, (err) => {
|
|
4416
5420
|
if (err) console.warn(` Could not open browser: ${err.message}`);
|
|
4417
5421
|
});
|
|
5422
|
+
} else {
|
|
5423
|
+
console.log("\n Skills Pack Server");
|
|
5424
|
+
console.log(" Running in embedded mode (IPC only)\n");
|
|
4418
5425
|
}
|
|
4419
|
-
|
|
5426
|
+
if (hasIpcChannel) {
|
|
5427
|
+
ipcAdapter.notifyReady(actualPort);
|
|
5428
|
+
}
|
|
5429
|
+
};
|
|
4420
5430
|
process.on("SIGINT", () => {
|
|
4421
5431
|
deregister(canonicalRootDir, process.pid);
|
|
4422
5432
|
void lifecycle.requestShutdown("signal");
|
|
@@ -4425,22 +5435,30 @@ async function startServer(options) {
|
|
|
4425
5435
|
deregister(canonicalRootDir, process.pid);
|
|
4426
5436
|
void lifecycle.requestShutdown("signal");
|
|
4427
5437
|
});
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
5438
|
+
if (webEnabled) {
|
|
5439
|
+
const actualPort = await new Promise((resolve, reject) => {
|
|
5440
|
+
function tryListen(listenPort) {
|
|
5441
|
+
server.listen(listenPort, host);
|
|
5442
|
+
server.once("error", (err) => {
|
|
5443
|
+
if (err.code === "EADDRINUSE") {
|
|
5444
|
+
console.log(` Port ${listenPort} is in use, trying ${listenPort + 1}...`);
|
|
5445
|
+
server.close();
|
|
5446
|
+
tryListen(listenPort + 1);
|
|
5447
|
+
} else {
|
|
5448
|
+
reject(err);
|
|
5449
|
+
}
|
|
5450
|
+
});
|
|
5451
|
+
server.once("listening", () => {
|
|
5452
|
+
const address = server.address();
|
|
5453
|
+
resolve(typeof address === "string" ? listenPort : address?.port ?? listenPort);
|
|
5454
|
+
});
|
|
5455
|
+
}
|
|
5456
|
+
tryListen(port);
|
|
5457
|
+
});
|
|
5458
|
+
announceReady(actualPort);
|
|
5459
|
+
} else {
|
|
5460
|
+
announceReady(0);
|
|
5461
|
+
}
|
|
4444
5462
|
await new Promise(() => {
|
|
4445
5463
|
});
|
|
4446
5464
|
}
|
|
@@ -4457,23 +5475,23 @@ function findMissingSkills(workDir, config) {
|
|
|
4457
5475
|
});
|
|
4458
5476
|
}
|
|
4459
5477
|
function copyStartTemplates2(workDir) {
|
|
4460
|
-
const templateDir =
|
|
5478
|
+
const templateDir = path19.resolve(
|
|
4461
5479
|
new URL("../templates", import.meta.url).pathname
|
|
4462
5480
|
);
|
|
4463
5481
|
for (const file of ["start.sh", "start.bat"]) {
|
|
4464
|
-
const src =
|
|
4465
|
-
const dest =
|
|
4466
|
-
if (
|
|
4467
|
-
|
|
5482
|
+
const src = path19.join(templateDir, file);
|
|
5483
|
+
const dest = path19.join(workDir, file);
|
|
5484
|
+
if (fs20.existsSync(src)) {
|
|
5485
|
+
fs20.copyFileSync(src, dest);
|
|
4468
5486
|
if (file === "start.sh") {
|
|
4469
|
-
|
|
5487
|
+
fs20.chmodSync(dest, 493);
|
|
4470
5488
|
}
|
|
4471
5489
|
}
|
|
4472
5490
|
}
|
|
4473
5491
|
}
|
|
4474
5492
|
async function runCommand(directory) {
|
|
4475
|
-
const workDir = directory ?
|
|
4476
|
-
|
|
5493
|
+
const workDir = directory ? path19.resolve(directory) : process.cwd();
|
|
5494
|
+
fs20.mkdirSync(workDir, { recursive: true });
|
|
4477
5495
|
if (!configExists(workDir)) {
|
|
4478
5496
|
console.log(chalk4.blue("\n No skillpack.json found. Let's set one up.\n"));
|
|
4479
5497
|
const { name, description } = await inquirer2.prompt([
|
|
@@ -4512,20 +5530,19 @@ async function runCommand(directory) {
|
|
|
4512
5530
|
syncSkillDescriptions(workDir, config);
|
|
4513
5531
|
saveConfig(workDir, config);
|
|
4514
5532
|
await startServer({
|
|
4515
|
-
rootDir: workDir
|
|
4516
|
-
daemonRun: process.env.DAEMON_RUN === "1"
|
|
5533
|
+
rootDir: workDir
|
|
4517
5534
|
});
|
|
4518
5535
|
}
|
|
4519
5536
|
|
|
4520
5537
|
// src/cli.ts
|
|
4521
|
-
import
|
|
4522
|
-
import
|
|
5538
|
+
import fs21 from "fs";
|
|
5539
|
+
import path20 from "path";
|
|
4523
5540
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4524
5541
|
var packageJson = JSON.parse(
|
|
4525
|
-
|
|
5542
|
+
fs21.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
|
|
4526
5543
|
);
|
|
4527
5544
|
var program = new Command();
|
|
4528
|
-
var cliFilePath =
|
|
5545
|
+
var cliFilePath = path20.resolve(fileURLToPath3(import.meta.url));
|
|
4529
5546
|
program.name("skillpack").description("Assemble, package, and run Agent Skills packs").version(packageJson.version);
|
|
4530
5547
|
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
5548
|
await createCommand(directory, options);
|
|
@@ -4535,7 +5552,7 @@ program.command("run [directory]").description("Start the SkillPack runtime serv
|
|
|
4535
5552
|
if (options?.host) process.env.HOST = options.host;
|
|
4536
5553
|
await runCommand(directory);
|
|
4537
5554
|
});
|
|
4538
|
-
program.command("zip").description("Package the current pack as a zip file (skillpack.json + skills/ + start scripts)").action(async () => {
|
|
5555
|
+
program.command("zip").description("Package the current pack as a zip file (skillpack.json + optional job.json + skills/ + start scripts)").action(async () => {
|
|
4539
5556
|
try {
|
|
4540
5557
|
await zipCommand(process.cwd());
|
|
4541
5558
|
} catch (err) {
|
|
@@ -4546,7 +5563,7 @@ program.command("zip").description("Package the current pack as a zip file (skil
|
|
|
4546
5563
|
function normalizeUserArgs(argv) {
|
|
4547
5564
|
if (argv.length === 0) return argv;
|
|
4548
5565
|
const firstArg = argv[0];
|
|
4549
|
-
if (firstArg &&
|
|
5566
|
+
if (firstArg && path20.resolve(firstArg) === cliFilePath) {
|
|
4550
5567
|
return argv.slice(1);
|
|
4551
5568
|
}
|
|
4552
5569
|
return argv;
|