@cremini/skillpack 1.2.10 → 1.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +952 -728
- package/package.json +3 -3
- package/web/index.html +11 -0
- package/web/js/chat-apps-dialog.js +35 -4
package/dist/cli.js
CHANGED
|
@@ -42,6 +42,11 @@ function validateScheduledJobConfig(value, sourceLabel, index) {
|
|
|
42
42
|
);
|
|
43
43
|
}
|
|
44
44
|
const job = value;
|
|
45
|
+
if (typeof job.id !== "string" || !job.id.trim()) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Invalid job config from ${sourceLabel}: "jobs[${index}].id" is required`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
45
50
|
if (typeof job.name !== "string" || !job.name.trim()) {
|
|
46
51
|
throw new Error(
|
|
47
52
|
`Invalid job config from ${sourceLabel}: "jobs[${index}].name" is required`
|
|
@@ -92,16 +97,16 @@ function validateJobFileShape(value, sourceLabel) {
|
|
|
92
97
|
if (!Array.isArray(jobFile.jobs)) {
|
|
93
98
|
throw new Error(`Invalid job config from ${sourceLabel}: "jobs" must be an array`);
|
|
94
99
|
}
|
|
95
|
-
const
|
|
100
|
+
const ids = /* @__PURE__ */ new Set();
|
|
96
101
|
jobFile.jobs.forEach((job, index) => {
|
|
97
102
|
validateScheduledJobConfig(job, sourceLabel, index);
|
|
98
|
-
const
|
|
99
|
-
if (
|
|
103
|
+
const normalizedId = job.id.trim().toLowerCase();
|
|
104
|
+
if (ids.has(normalizedId)) {
|
|
100
105
|
throw new Error(
|
|
101
|
-
`Invalid job config from ${sourceLabel}: duplicate job
|
|
106
|
+
`Invalid job config from ${sourceLabel}: duplicate job id "${job.id}" is not allowed`
|
|
102
107
|
);
|
|
103
108
|
}
|
|
104
|
-
|
|
109
|
+
ids.add(normalizedId);
|
|
105
110
|
});
|
|
106
111
|
}
|
|
107
112
|
function normalizeJobFile(jobFile) {
|
|
@@ -115,6 +120,7 @@ function normalizeScheduledJobConfig(job) {
|
|
|
115
120
|
const normalizedCron = normalizeJobCron(job.cron);
|
|
116
121
|
const normalizedTimezone = typeof job.timezone === "string" && job.timezone.trim() ? job.timezone.trim() : void 0;
|
|
117
122
|
return {
|
|
123
|
+
id: job.id.trim(),
|
|
118
124
|
name: job.name.trim(),
|
|
119
125
|
...normalizedCron ? { cron: normalizedCron } : {},
|
|
120
126
|
prompt: job.prompt,
|
|
@@ -286,8 +292,16 @@ var init_commands = __esm({
|
|
|
286
292
|
// src/runtime/adapters/types.ts
|
|
287
293
|
var types_exports = {};
|
|
288
294
|
__export(types_exports, {
|
|
295
|
+
detectPlatformFromChannelId: () => detectPlatformFromChannelId,
|
|
289
296
|
isMessageSender: () => isMessageSender
|
|
290
297
|
});
|
|
298
|
+
function detectPlatformFromChannelId(channelId) {
|
|
299
|
+
if (channelId.startsWith("telegram-")) return "telegram";
|
|
300
|
+
if (channelId.startsWith("slack-")) return "slack";
|
|
301
|
+
if (channelId.startsWith("feishu-")) return "feishu";
|
|
302
|
+
if (channelId.startsWith("scheduler-")) return "scheduler";
|
|
303
|
+
return "web";
|
|
304
|
+
}
|
|
291
305
|
function isMessageSender(adapter) {
|
|
292
306
|
return typeof adapter.sendMessage === "function";
|
|
293
307
|
}
|
|
@@ -417,7 +431,7 @@ var telegram_exports = {};
|
|
|
417
431
|
__export(telegram_exports, {
|
|
418
432
|
TelegramAdapter: () => TelegramAdapter
|
|
419
433
|
});
|
|
420
|
-
import
|
|
434
|
+
import fs14 from "fs";
|
|
421
435
|
import TelegramBot from "node-telegram-bot-api";
|
|
422
436
|
var MAX_MESSAGE_LENGTH, ACK_REACTION, TelegramAdapter;
|
|
423
437
|
var init_telegram = __esm({
|
|
@@ -735,7 +749,7 @@ var init_telegram = __esm({
|
|
|
735
749
|
async sendFileSafe(chatId, filePath, caption) {
|
|
736
750
|
if (!this.bot) return;
|
|
737
751
|
try {
|
|
738
|
-
if (!
|
|
752
|
+
if (!fs14.existsSync(filePath)) {
|
|
739
753
|
console.error(`[Telegram] File not found for sending: ${filePath}`);
|
|
740
754
|
return;
|
|
741
755
|
}
|
|
@@ -755,8 +769,8 @@ var slack_exports = {};
|
|
|
755
769
|
__export(slack_exports, {
|
|
756
770
|
SlackAdapter: () => SlackAdapter
|
|
757
771
|
});
|
|
758
|
-
import
|
|
759
|
-
import
|
|
772
|
+
import fs15 from "fs";
|
|
773
|
+
import path15 from "path";
|
|
760
774
|
import { App, LogLevel } from "@slack/bolt";
|
|
761
775
|
var INLINE_COMMANDS, SLASH_COMMANDS, MAX_MESSAGE_LENGTH2, ACK_REACTION2, PROCESSING_MESSAGE, SlackAdapter;
|
|
762
776
|
var init_slack = __esm({
|
|
@@ -1391,12 +1405,12 @@ var init_slack = __esm({
|
|
|
1391
1405
|
*/
|
|
1392
1406
|
async sendFileSafe(client, route, filePath, caption) {
|
|
1393
1407
|
try {
|
|
1394
|
-
if (!
|
|
1408
|
+
if (!fs15.existsSync(filePath)) {
|
|
1395
1409
|
console.error(`[Slack] File not found for sending: ${filePath}`);
|
|
1396
1410
|
return;
|
|
1397
1411
|
}
|
|
1398
|
-
const filename =
|
|
1399
|
-
const fileContent =
|
|
1412
|
+
const filename = path15.basename(filePath);
|
|
1413
|
+
const fileContent = fs15.readFileSync(filePath);
|
|
1400
1414
|
await client.files.uploadV2({
|
|
1401
1415
|
channel_id: route.channel,
|
|
1402
1416
|
thread_ts: route.threadTs,
|
|
@@ -1413,6 +1427,297 @@ var init_slack = __esm({
|
|
|
1413
1427
|
}
|
|
1414
1428
|
});
|
|
1415
1429
|
|
|
1430
|
+
// src/runtime/adapters/feishu.ts
|
|
1431
|
+
var feishu_exports = {};
|
|
1432
|
+
__export(feishu_exports, {
|
|
1433
|
+
FeishuAdapter: () => FeishuAdapter,
|
|
1434
|
+
normalizeFeishuMessage: () => normalizeFeishuMessage,
|
|
1435
|
+
parseFeishuChannelId: () => parseFeishuChannelId
|
|
1436
|
+
});
|
|
1437
|
+
import fs16 from "fs";
|
|
1438
|
+
import path16 from "path";
|
|
1439
|
+
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
1440
|
+
function parseFeishuChannelId(channelId) {
|
|
1441
|
+
if (!channelId.startsWith("feishu-")) {
|
|
1442
|
+
throw new Error(`[Feishu] Invalid channelId: ${channelId}`);
|
|
1443
|
+
}
|
|
1444
|
+
const chatId = channelId.replace("feishu-", "").trim();
|
|
1445
|
+
if (!chatId) {
|
|
1446
|
+
throw new Error(`[Feishu] Invalid channelId: ${channelId}`);
|
|
1447
|
+
}
|
|
1448
|
+
return chatId;
|
|
1449
|
+
}
|
|
1450
|
+
function normalizeFeishuMessage(message) {
|
|
1451
|
+
if (message.chatType === "group" && !message.mentionedBot) {
|
|
1452
|
+
return { action: "ignore" };
|
|
1453
|
+
}
|
|
1454
|
+
if (message.rawContentType !== "text") {
|
|
1455
|
+
return { action: "unsupported" };
|
|
1456
|
+
}
|
|
1457
|
+
const text = message.content.trim();
|
|
1458
|
+
if (!text) {
|
|
1459
|
+
return { action: "ignore" };
|
|
1460
|
+
}
|
|
1461
|
+
return {
|
|
1462
|
+
action: "handle",
|
|
1463
|
+
text
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
var MAX_MESSAGE_LENGTH3, ACK_REACTION3, UNSUPPORTED_MESSAGE_REPLY, FILE_DELIVERY_FAILED_REPLY, FeishuAdapter;
|
|
1467
|
+
var init_feishu = __esm({
|
|
1468
|
+
"src/runtime/adapters/feishu.ts"() {
|
|
1469
|
+
"use strict";
|
|
1470
|
+
init_commands();
|
|
1471
|
+
MAX_MESSAGE_LENGTH3 = 1800;
|
|
1472
|
+
ACK_REACTION3 = "THUMBSUP";
|
|
1473
|
+
UNSUPPORTED_MESSAGE_REPLY = "Only text messages are currently supported. Please send plain text.";
|
|
1474
|
+
FILE_DELIVERY_FAILED_REPLY = "A file was generated, but Feishu could not deliver it.";
|
|
1475
|
+
FeishuAdapter = class {
|
|
1476
|
+
name = "feishu";
|
|
1477
|
+
channel = null;
|
|
1478
|
+
agent = null;
|
|
1479
|
+
ipcBroadcaster = null;
|
|
1480
|
+
options;
|
|
1481
|
+
constructor(options) {
|
|
1482
|
+
this.options = options;
|
|
1483
|
+
}
|
|
1484
|
+
async start(ctx) {
|
|
1485
|
+
this.agent = ctx.agent;
|
|
1486
|
+
this.ipcBroadcaster = ctx.ipcBroadcaster ?? null;
|
|
1487
|
+
this.channel = Lark.createLarkChannel({
|
|
1488
|
+
appId: this.options.appId,
|
|
1489
|
+
appSecret: this.options.appSecret,
|
|
1490
|
+
loggerLevel: Lark.LoggerLevel.info,
|
|
1491
|
+
transport: "websocket",
|
|
1492
|
+
source: "skillpack",
|
|
1493
|
+
policy: {
|
|
1494
|
+
dmMode: "open",
|
|
1495
|
+
requireMention: false
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
this.channel.on("message", (message) => {
|
|
1499
|
+
void this.handleIncomingMessage(message).catch((error) => {
|
|
1500
|
+
console.error("[Feishu] Error handling message:", error);
|
|
1501
|
+
});
|
|
1502
|
+
});
|
|
1503
|
+
this.channel.on("error", (error) => {
|
|
1504
|
+
console.error("[Feishu] Channel error:", error);
|
|
1505
|
+
});
|
|
1506
|
+
await this.channel.connect();
|
|
1507
|
+
const botName = this.channel.botIdentity?.name;
|
|
1508
|
+
console.log(
|
|
1509
|
+
botName ? `[FeishuAdapter] Started as ${botName}` : "[FeishuAdapter] Started"
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
async stop() {
|
|
1513
|
+
if (this.channel) {
|
|
1514
|
+
await this.channel.disconnect();
|
|
1515
|
+
this.channel = null;
|
|
1516
|
+
}
|
|
1517
|
+
console.log("[FeishuAdapter] Stopped");
|
|
1518
|
+
}
|
|
1519
|
+
async sendMessage(channelId, text) {
|
|
1520
|
+
if (!this.channel) {
|
|
1521
|
+
throw new Error("[Feishu] Channel not initialized");
|
|
1522
|
+
}
|
|
1523
|
+
const chatId = parseFeishuChannelId(channelId);
|
|
1524
|
+
await this.sendLongMessage(chatId, text);
|
|
1525
|
+
}
|
|
1526
|
+
async sendFile(channelId, filePath, caption) {
|
|
1527
|
+
if (!this.channel) {
|
|
1528
|
+
throw new Error("[Feishu] Channel not initialized");
|
|
1529
|
+
}
|
|
1530
|
+
const chatId = parseFeishuChannelId(channelId);
|
|
1531
|
+
const sent = await this.sendFileSafe(chatId, filePath, caption);
|
|
1532
|
+
if (!sent) {
|
|
1533
|
+
throw new Error(`[Feishu] Failed to send file: ${filePath}`);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
async handleIncomingMessage(message) {
|
|
1537
|
+
if (!this.channel || !this.agent) {
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
const decision = normalizeFeishuMessage(message);
|
|
1541
|
+
if (decision.action === "ignore") {
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
await this.tryAckReaction(message.messageId);
|
|
1545
|
+
const channelId = `feishu-${message.chatId}`;
|
|
1546
|
+
if (decision.action === "unsupported") {
|
|
1547
|
+
await this.sendSafe(message.chatId, UNSUPPORTED_MESSAGE_REPLY, message.messageId);
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
const userText = decision.text;
|
|
1551
|
+
this.ipcBroadcaster?.broadcastInbound(
|
|
1552
|
+
channelId,
|
|
1553
|
+
"feishu",
|
|
1554
|
+
{
|
|
1555
|
+
id: message.senderId,
|
|
1556
|
+
username: message.senderName || message.senderId
|
|
1557
|
+
},
|
|
1558
|
+
userText
|
|
1559
|
+
);
|
|
1560
|
+
const command = this.resolveCommand(userText);
|
|
1561
|
+
if (command) {
|
|
1562
|
+
const result = await this.agent.handleCommand(command, channelId);
|
|
1563
|
+
await this.sendSafe(
|
|
1564
|
+
message.chatId,
|
|
1565
|
+
result.message || `/${command} executed.`,
|
|
1566
|
+
message.messageId
|
|
1567
|
+
);
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
let finalText = "";
|
|
1571
|
+
let hasError = false;
|
|
1572
|
+
let errorMessage = "";
|
|
1573
|
+
const pendingFiles = [];
|
|
1574
|
+
const onEvent = (event) => {
|
|
1575
|
+
switch (event.type) {
|
|
1576
|
+
case "text_delta":
|
|
1577
|
+
finalText += event.delta;
|
|
1578
|
+
break;
|
|
1579
|
+
case "file_output":
|
|
1580
|
+
pendingFiles.push({
|
|
1581
|
+
filePath: event.filePath,
|
|
1582
|
+
caption: event.caption
|
|
1583
|
+
});
|
|
1584
|
+
break;
|
|
1585
|
+
}
|
|
1586
|
+
this.ipcBroadcaster?.broadcastAgentEvent(channelId, event);
|
|
1587
|
+
};
|
|
1588
|
+
try {
|
|
1589
|
+
const result = await this.agent.handleMessage(
|
|
1590
|
+
"feishu",
|
|
1591
|
+
channelId,
|
|
1592
|
+
userText,
|
|
1593
|
+
onEvent
|
|
1594
|
+
);
|
|
1595
|
+
if (result.errorMessage) {
|
|
1596
|
+
hasError = true;
|
|
1597
|
+
errorMessage = result.errorMessage;
|
|
1598
|
+
}
|
|
1599
|
+
} catch (error) {
|
|
1600
|
+
hasError = true;
|
|
1601
|
+
errorMessage = error instanceof Error ? error.message : String(error);
|
|
1602
|
+
}
|
|
1603
|
+
if (hasError) {
|
|
1604
|
+
await this.sendSafe(message.chatId, `Error: ${errorMessage}`, message.messageId);
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
if (finalText.trim()) {
|
|
1608
|
+
await this.sendLongMessage(message.chatId, finalText, message.messageId);
|
|
1609
|
+
} else if (pendingFiles.length === 0) {
|
|
1610
|
+
await this.sendSafe(message.chatId, "(No response generated)", message.messageId);
|
|
1611
|
+
}
|
|
1612
|
+
let sentFileCount = 0;
|
|
1613
|
+
for (const file of pendingFiles) {
|
|
1614
|
+
if (await this.sendFileSafe(
|
|
1615
|
+
message.chatId,
|
|
1616
|
+
file.filePath,
|
|
1617
|
+
file.caption,
|
|
1618
|
+
message.messageId
|
|
1619
|
+
)) {
|
|
1620
|
+
sentFileCount += 1;
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
if (pendingFiles.length > 0 && sentFileCount === 0) {
|
|
1624
|
+
await this.sendSafe(
|
|
1625
|
+
message.chatId,
|
|
1626
|
+
FILE_DELIVERY_FAILED_REPLY,
|
|
1627
|
+
message.messageId
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
resolveCommand(text) {
|
|
1632
|
+
return resolveCommand(text);
|
|
1633
|
+
}
|
|
1634
|
+
async sendLongMessage(chatId, text, replyTo) {
|
|
1635
|
+
for (const chunk of this.splitMessage(text)) {
|
|
1636
|
+
await this.sendSafe(chatId, chunk, replyTo);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
splitMessage(text) {
|
|
1640
|
+
if (text.length <= MAX_MESSAGE_LENGTH3) {
|
|
1641
|
+
return [text];
|
|
1642
|
+
}
|
|
1643
|
+
const chunks = [];
|
|
1644
|
+
let remaining = text;
|
|
1645
|
+
while (remaining.length > 0) {
|
|
1646
|
+
if (remaining.length <= MAX_MESSAGE_LENGTH3) {
|
|
1647
|
+
chunks.push(remaining);
|
|
1648
|
+
break;
|
|
1649
|
+
}
|
|
1650
|
+
let splitAt = remaining.lastIndexOf("\n\n", MAX_MESSAGE_LENGTH3);
|
|
1651
|
+
if (splitAt < MAX_MESSAGE_LENGTH3 * 0.5) {
|
|
1652
|
+
splitAt = remaining.lastIndexOf("\n", MAX_MESSAGE_LENGTH3);
|
|
1653
|
+
}
|
|
1654
|
+
if (splitAt < MAX_MESSAGE_LENGTH3 * 0.3) {
|
|
1655
|
+
splitAt = remaining.lastIndexOf(" ", MAX_MESSAGE_LENGTH3);
|
|
1656
|
+
}
|
|
1657
|
+
if (splitAt < 1) {
|
|
1658
|
+
splitAt = MAX_MESSAGE_LENGTH3;
|
|
1659
|
+
}
|
|
1660
|
+
chunks.push(remaining.slice(0, splitAt));
|
|
1661
|
+
remaining = remaining.slice(splitAt).trimStart();
|
|
1662
|
+
}
|
|
1663
|
+
return chunks;
|
|
1664
|
+
}
|
|
1665
|
+
async tryAckReaction(messageId) {
|
|
1666
|
+
if (!this.channel) {
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
try {
|
|
1670
|
+
await this.channel.addReaction(messageId, ACK_REACTION3);
|
|
1671
|
+
} catch (error) {
|
|
1672
|
+
console.error("[Feishu] Failed to add ack reaction:", error);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
async sendFileSafe(chatId, filePath, caption, replyTo) {
|
|
1676
|
+
if (!this.channel) {
|
|
1677
|
+
return false;
|
|
1678
|
+
}
|
|
1679
|
+
try {
|
|
1680
|
+
if (!fs16.existsSync(filePath)) {
|
|
1681
|
+
console.error(`[Feishu] File not found for sending: ${filePath}`);
|
|
1682
|
+
return false;
|
|
1683
|
+
}
|
|
1684
|
+
if (caption?.trim()) {
|
|
1685
|
+
await this.sendSafe(chatId, caption, replyTo);
|
|
1686
|
+
}
|
|
1687
|
+
await this.channel.send(
|
|
1688
|
+
chatId,
|
|
1689
|
+
{
|
|
1690
|
+
file: {
|
|
1691
|
+
source: filePath,
|
|
1692
|
+
fileName: path16.basename(filePath)
|
|
1693
|
+
}
|
|
1694
|
+
},
|
|
1695
|
+
replyTo ? { replyTo } : void 0
|
|
1696
|
+
);
|
|
1697
|
+
return true;
|
|
1698
|
+
} catch (error) {
|
|
1699
|
+
console.error("[Feishu] Failed to send file:", error);
|
|
1700
|
+
return false;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
async sendSafe(chatId, text, replyTo) {
|
|
1704
|
+
if (!this.channel) {
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
try {
|
|
1708
|
+
await this.channel.send(
|
|
1709
|
+
chatId,
|
|
1710
|
+
{ markdown: text },
|
|
1711
|
+
replyTo ? { replyTo } : void 0
|
|
1712
|
+
);
|
|
1713
|
+
} catch (error) {
|
|
1714
|
+
console.error("[Feishu] Failed to send message:", error);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
});
|
|
1720
|
+
|
|
1416
1721
|
// src/runtime/adapters/scheduler.ts
|
|
1417
1722
|
var scheduler_exports = {};
|
|
1418
1723
|
__export(scheduler_exports, {
|
|
@@ -1427,31 +1732,36 @@ function isValidTimezone(tz) {
|
|
|
1427
1732
|
return false;
|
|
1428
1733
|
}
|
|
1429
1734
|
}
|
|
1430
|
-
function
|
|
1431
|
-
return
|
|
1735
|
+
function isValidJobId(id) {
|
|
1736
|
+
return id.length > 0 && id.length <= 128 && !INVALID_JOB_ID_CHARS.test(id);
|
|
1432
1737
|
}
|
|
1433
1738
|
function isRecurringJob(jobConfig) {
|
|
1434
1739
|
return hasJobSchedule(jobConfig);
|
|
1435
1740
|
}
|
|
1436
|
-
var
|
|
1741
|
+
var INVALID_JOB_ID_CHARS, SchedulerAdapter;
|
|
1437
1742
|
var init_scheduler = __esm({
|
|
1438
1743
|
"src/runtime/adapters/scheduler.ts"() {
|
|
1439
1744
|
"use strict";
|
|
1440
1745
|
init_job_config();
|
|
1441
1746
|
init_job_schedule();
|
|
1442
|
-
|
|
1747
|
+
INVALID_JOB_ID_CHARS = /[\\/\r\n]/;
|
|
1443
1748
|
SchedulerAdapter = class {
|
|
1444
1749
|
name = "scheduler";
|
|
1445
1750
|
agent;
|
|
1446
1751
|
rootDir = "";
|
|
1752
|
+
ipcBroadcaster = null;
|
|
1447
1753
|
notifyFn = async () => {
|
|
1448
1754
|
};
|
|
1449
1755
|
jobs = /* @__PURE__ */ new Map();
|
|
1450
1756
|
async start(ctx) {
|
|
1451
1757
|
this.agent = ctx.agent;
|
|
1452
1758
|
this.rootDir = ctx.rootDir;
|
|
1759
|
+
this.ipcBroadcaster = ctx.ipcBroadcaster ?? null;
|
|
1453
1760
|
this.notifyFn = ctx.notify || (async () => {
|
|
1454
1761
|
});
|
|
1762
|
+
console.log(
|
|
1763
|
+
`[Scheduler] IPC broadcaster ${this.ipcBroadcaster ? "attached" : "not available"} for agent events`
|
|
1764
|
+
);
|
|
1455
1765
|
const jobConfigs = loadJobFile(this.rootDir).jobs;
|
|
1456
1766
|
let scheduledCount = 0;
|
|
1457
1767
|
let disabledCount = 0;
|
|
@@ -1488,8 +1798,8 @@ var init_scheduler = __esm({
|
|
|
1488
1798
|
registerJob(jobConfig) {
|
|
1489
1799
|
const normalizedConfig = normalizeScheduledJobConfig(jobConfig);
|
|
1490
1800
|
const normalizedCron = normalizeJobCron(normalizedConfig.cron);
|
|
1491
|
-
if (!
|
|
1492
|
-
const msg = `[Scheduler] Invalid job
|
|
1801
|
+
if (!isValidJobId(normalizedConfig.id)) {
|
|
1802
|
+
const msg = `[Scheduler] Invalid job id "${normalizedConfig.id}": must be non-empty, must not contain "/", "\\\\", or line breaks, and must be \u2264128 chars`;
|
|
1493
1803
|
console.error(msg);
|
|
1494
1804
|
return { registered: false, message: msg };
|
|
1495
1805
|
}
|
|
@@ -1505,7 +1815,7 @@ var init_scheduler = __esm({
|
|
|
1505
1815
|
return { registered: false, message: msg };
|
|
1506
1816
|
}
|
|
1507
1817
|
}
|
|
1508
|
-
this.removeFromMap(normalizedConfig.
|
|
1818
|
+
this.removeFromMap(normalizedConfig.id);
|
|
1509
1819
|
let task = null;
|
|
1510
1820
|
if (normalizedCron && normalizedConfig.enabled !== false) {
|
|
1511
1821
|
task = this.createCronTask(normalizedConfig);
|
|
@@ -1521,7 +1831,7 @@ var init_scheduler = __esm({
|
|
|
1521
1831
|
`[Scheduler] Job "${normalizedConfig.name}" registered as one-time (manual trigger only)`
|
|
1522
1832
|
);
|
|
1523
1833
|
}
|
|
1524
|
-
this.jobs.set(normalizedConfig.
|
|
1834
|
+
this.jobs.set(normalizedConfig.id, {
|
|
1525
1835
|
config: normalizedConfig,
|
|
1526
1836
|
task,
|
|
1527
1837
|
running: false,
|
|
@@ -1537,8 +1847,8 @@ var init_scheduler = __esm({
|
|
|
1537
1847
|
* Returns { text, notifyFailed } so callers can produce accurate status.
|
|
1538
1848
|
*/
|
|
1539
1849
|
async runJob(jobConfig) {
|
|
1540
|
-
const channelId = `scheduler-${jobConfig.
|
|
1541
|
-
const job = this.jobs.get(jobConfig.
|
|
1850
|
+
const channelId = `scheduler-${jobConfig.id}`;
|
|
1851
|
+
const job = this.jobs.get(jobConfig.id);
|
|
1542
1852
|
if (job?.running) {
|
|
1543
1853
|
console.warn(
|
|
1544
1854
|
`[Scheduler] Job "${jobConfig.name}" is already running, skipping this trigger`
|
|
@@ -1549,8 +1859,27 @@ var init_scheduler = __esm({
|
|
|
1549
1859
|
console.log(`[Scheduler] Running job "${jobConfig.name}"`);
|
|
1550
1860
|
let fullText = "";
|
|
1551
1861
|
let agentFailed = false;
|
|
1862
|
+
let loggedFirstTextDelta = false;
|
|
1863
|
+
let sawAgentStart = false;
|
|
1864
|
+
let sawAgentEnd = false;
|
|
1552
1865
|
const pendingFiles = [];
|
|
1553
1866
|
const onEvent = (event) => {
|
|
1867
|
+
if (event.type === "agent_start") {
|
|
1868
|
+
sawAgentStart = true;
|
|
1869
|
+
} else if (event.type === "agent_end") {
|
|
1870
|
+
sawAgentEnd = true;
|
|
1871
|
+
}
|
|
1872
|
+
if (event.type === "agent_start" || event.type === "agent_end") {
|
|
1873
|
+
console.log(
|
|
1874
|
+
`[Scheduler] Forwarding ${event.type} for ${channelId} (ipc=${this.ipcBroadcaster ? "yes" : "no"})`
|
|
1875
|
+
);
|
|
1876
|
+
} else if (event.type === "text_delta" && !loggedFirstTextDelta) {
|
|
1877
|
+
loggedFirstTextDelta = true;
|
|
1878
|
+
console.log(
|
|
1879
|
+
`[Scheduler] Forwarding first text_delta for ${channelId} (${event.delta.length} chars, ipc=${this.ipcBroadcaster ? "yes" : "no"})`
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
this.ipcBroadcaster?.broadcastAgentEvent(channelId, event);
|
|
1554
1883
|
if (event.type === "text_delta") fullText += event.delta;
|
|
1555
1884
|
if (event.type === "file_output") {
|
|
1556
1885
|
pendingFiles.push({
|
|
@@ -1568,6 +1897,7 @@ var init_scheduler = __esm({
|
|
|
1568
1897
|
onEvent,
|
|
1569
1898
|
void 0
|
|
1570
1899
|
);
|
|
1900
|
+
this.ensureTerminalAgentEvent(channelId, sawAgentStart, sawAgentEnd, onEvent);
|
|
1571
1901
|
if (result.errorMessage) {
|
|
1572
1902
|
fullText = `\u274C \u5B9A\u65F6\u4EFB\u52A1 "${jobConfig.name}" \u6267\u884C\u5931\u8D25\uFF1A${result.errorMessage}`;
|
|
1573
1903
|
agentFailed = true;
|
|
@@ -1576,6 +1906,7 @@ var init_scheduler = __esm({
|
|
|
1576
1906
|
if (job) job.lastError = void 0;
|
|
1577
1907
|
}
|
|
1578
1908
|
} catch (err) {
|
|
1909
|
+
this.ensureTerminalAgentEvent(channelId, sawAgentStart, sawAgentEnd, onEvent);
|
|
1579
1910
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1580
1911
|
fullText = `\u274C \u5B9A\u65F6\u4EFB\u52A1 "${jobConfig.name}" \u5F02\u5E38\uFF1A${errorMsg}`;
|
|
1581
1912
|
agentFailed = true;
|
|
@@ -1612,10 +1943,19 @@ var init_scheduler = __esm({
|
|
|
1612
1943
|
return { text: fullText, notifyFailed };
|
|
1613
1944
|
}
|
|
1614
1945
|
async clearJobContext(channelId) {
|
|
1946
|
+
console.log(`[Scheduler] Clearing context for ${channelId}`);
|
|
1615
1947
|
const result = await this.agent.handleCommand("clear", channelId);
|
|
1616
1948
|
if (!result.success) {
|
|
1617
1949
|
throw new Error(result.message || `Failed to clear context for ${channelId}`);
|
|
1618
1950
|
}
|
|
1951
|
+
console.log(`[Scheduler] Context cleared for ${channelId}`);
|
|
1952
|
+
}
|
|
1953
|
+
ensureTerminalAgentEvent(channelId, sawAgentStart, sawAgentEnd, onEvent) {
|
|
1954
|
+
if (!sawAgentStart || sawAgentEnd) {
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
console.log(`[Scheduler] Synthesizing agent_end for ${channelId}`);
|
|
1958
|
+
onEvent({ type: "agent_end" });
|
|
1619
1959
|
}
|
|
1620
1960
|
// -------------------------------------------------------------------------
|
|
1621
1961
|
// Dynamic management API
|
|
@@ -1624,7 +1964,7 @@ var init_scheduler = __esm({
|
|
|
1624
1964
|
* Add a new job, persist to job.json.
|
|
1625
1965
|
*/
|
|
1626
1966
|
addJob(jobConfig) {
|
|
1627
|
-
if (this.jobs.has(jobConfig.
|
|
1967
|
+
if (this.jobs.has(jobConfig.id)) {
|
|
1628
1968
|
return {
|
|
1629
1969
|
success: false,
|
|
1630
1970
|
message: `Job "${jobConfig.name}" already exists. Remove it first.`
|
|
@@ -1645,21 +1985,23 @@ var init_scheduler = __esm({
|
|
|
1645
1985
|
/**
|
|
1646
1986
|
* Remove a job and persist to job.json.
|
|
1647
1987
|
*/
|
|
1648
|
-
removeJob(
|
|
1649
|
-
|
|
1650
|
-
|
|
1988
|
+
removeJob(id) {
|
|
1989
|
+
const job = this.jobs.get(id);
|
|
1990
|
+
if (!job) {
|
|
1991
|
+
return { success: false, message: `Job "${id}" not found.` };
|
|
1651
1992
|
}
|
|
1652
|
-
this.removeFromMap(
|
|
1993
|
+
this.removeFromMap(id);
|
|
1653
1994
|
this.persistJobs();
|
|
1654
|
-
return { success: true, message: `Job "${name}" removed.` };
|
|
1995
|
+
return { success: true, message: `Job "${job.config.name}" removed.` };
|
|
1655
1996
|
}
|
|
1656
|
-
updateJob(
|
|
1657
|
-
const job = this.jobs.get(
|
|
1997
|
+
updateJob(id, updates) {
|
|
1998
|
+
const job = this.jobs.get(id);
|
|
1658
1999
|
if (!job) {
|
|
1659
|
-
return { success: false, message: `Job "${
|
|
2000
|
+
return { success: false, message: `Job "${id}" not found.` };
|
|
1660
2001
|
}
|
|
1661
2002
|
const nextConfig = {
|
|
1662
|
-
|
|
2003
|
+
id: job.config.id,
|
|
2004
|
+
name: job.config.name,
|
|
1663
2005
|
cron: updates.cron,
|
|
1664
2006
|
prompt: updates.prompt,
|
|
1665
2007
|
notify: updates.notify,
|
|
@@ -1673,21 +2015,21 @@ var init_scheduler = __esm({
|
|
|
1673
2015
|
this.persistJobs();
|
|
1674
2016
|
return {
|
|
1675
2017
|
success: true,
|
|
1676
|
-
message: `Job "${name}" updated.`
|
|
2018
|
+
message: `Job "${job.config.name}" updated.`
|
|
1677
2019
|
};
|
|
1678
2020
|
}
|
|
1679
2021
|
/**
|
|
1680
2022
|
* Enable or disable a job and persist.
|
|
1681
2023
|
*/
|
|
1682
|
-
setEnabled(
|
|
1683
|
-
const job = this.jobs.get(
|
|
2024
|
+
setEnabled(id, enabled) {
|
|
2025
|
+
const job = this.jobs.get(id);
|
|
1684
2026
|
if (!job) {
|
|
1685
|
-
return { success: false, message: `Job "${
|
|
2027
|
+
return { success: false, message: `Job "${id}" not found.` };
|
|
1686
2028
|
}
|
|
1687
2029
|
if (!isRecurringJob(job.config)) {
|
|
1688
2030
|
return {
|
|
1689
2031
|
success: false,
|
|
1690
|
-
message: `Job "${name}" does not have a schedule and cannot be enabled or disabled.`
|
|
2032
|
+
message: `Job "${job.config.name}" does not have a schedule and cannot be enabled or disabled.`
|
|
1691
2033
|
};
|
|
1692
2034
|
}
|
|
1693
2035
|
job.config.enabled = enabled;
|
|
@@ -1701,33 +2043,33 @@ var init_scheduler = __esm({
|
|
|
1701
2043
|
this.persistJobs();
|
|
1702
2044
|
return {
|
|
1703
2045
|
success: true,
|
|
1704
|
-
message: `Job "${name}" ${enabled ? "enabled" : "disabled"}.`
|
|
2046
|
+
message: `Job "${job.config.name}" ${enabled ? "enabled" : "disabled"}.`
|
|
1705
2047
|
};
|
|
1706
2048
|
}
|
|
1707
2049
|
/**
|
|
1708
2050
|
* Manually trigger a job (runs immediately, ignoring cron schedule).
|
|
1709
2051
|
*/
|
|
1710
|
-
async triggerJob(
|
|
1711
|
-
const job = this.jobs.get(
|
|
2052
|
+
async triggerJob(id) {
|
|
2053
|
+
const job = this.jobs.get(id);
|
|
1712
2054
|
if (!job) {
|
|
1713
|
-
return { success: false, message: `Job "${
|
|
2055
|
+
return { success: false, message: `Job "${id}" not found.` };
|
|
1714
2056
|
}
|
|
1715
2057
|
const { text, notifyFailed } = await this.runJob(job.config);
|
|
1716
2058
|
if (!text) {
|
|
1717
2059
|
return {
|
|
1718
2060
|
success: true,
|
|
1719
|
-
message: `Job "${name}" triggered but produced no output.`
|
|
2061
|
+
message: `Job "${job.config.name}" triggered but produced no output.`
|
|
1720
2062
|
};
|
|
1721
2063
|
}
|
|
1722
2064
|
if (notifyFailed) {
|
|
1723
2065
|
return {
|
|
1724
2066
|
success: true,
|
|
1725
|
-
message: `Job "${name}" executed, but notification to ${job.config.notify.adapter} failed. Check logs.`
|
|
2067
|
+
message: `Job "${job.config.name}" executed, but notification to ${job.config.notify.adapter} failed. Check logs.`
|
|
1726
2068
|
};
|
|
1727
2069
|
}
|
|
1728
2070
|
return {
|
|
1729
2071
|
success: true,
|
|
1730
|
-
message: `Job "${name}" triggered. Result sent to ${job.config.notify.adapter}.`
|
|
2072
|
+
message: `Job "${job.config.name}" triggered. Result sent to ${job.config.notify.adapter}.`
|
|
1731
2073
|
};
|
|
1732
2074
|
}
|
|
1733
2075
|
/**
|
|
@@ -1737,6 +2079,7 @@ var init_scheduler = __esm({
|
|
|
1737
2079
|
const result = [];
|
|
1738
2080
|
for (const [, job] of this.jobs) {
|
|
1739
2081
|
result.push({
|
|
2082
|
+
id: job.config.id,
|
|
1740
2083
|
name: job.config.name,
|
|
1741
2084
|
...job.config.cron ? { cron: job.config.cron } : {},
|
|
1742
2085
|
prompt: job.config.prompt,
|
|
@@ -1757,11 +2100,11 @@ var init_scheduler = __esm({
|
|
|
1757
2100
|
/**
|
|
1758
2101
|
* Stop the cron task and remove a job from the map (does NOT persist).
|
|
1759
2102
|
*/
|
|
1760
|
-
removeFromMap(
|
|
1761
|
-
const existing = this.jobs.get(
|
|
2103
|
+
removeFromMap(id) {
|
|
2104
|
+
const existing = this.jobs.get(id);
|
|
1762
2105
|
if (existing) {
|
|
1763
2106
|
existing.task?.stop();
|
|
1764
|
-
this.jobs.delete(
|
|
2107
|
+
this.jobs.delete(id);
|
|
1765
2108
|
}
|
|
1766
2109
|
}
|
|
1767
2110
|
/**
|
|
@@ -2480,23 +2823,23 @@ async function interactiveCreate(workDir) {
|
|
|
2480
2823
|
}
|
|
2481
2824
|
|
|
2482
2825
|
// src/commands/run.ts
|
|
2483
|
-
import
|
|
2484
|
-
import
|
|
2826
|
+
import path18 from "path";
|
|
2827
|
+
import fs18 from "fs";
|
|
2485
2828
|
import inquirer2 from "inquirer";
|
|
2486
2829
|
import chalk4 from "chalk";
|
|
2487
2830
|
|
|
2488
2831
|
// src/runtime/server.ts
|
|
2489
2832
|
import express from "express";
|
|
2490
|
-
import
|
|
2491
|
-
import
|
|
2833
|
+
import path17 from "path";
|
|
2834
|
+
import fs17 from "fs";
|
|
2492
2835
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2493
2836
|
import { createServer } from "http";
|
|
2494
2837
|
import { exec } from "child_process";
|
|
2495
2838
|
|
|
2496
2839
|
// src/runtime/agent.ts
|
|
2497
|
-
import
|
|
2498
|
-
import
|
|
2499
|
-
import { randomUUID as
|
|
2840
|
+
import path11 from "path";
|
|
2841
|
+
import fs10 from "fs";
|
|
2842
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
2500
2843
|
import { fileURLToPath } from "url";
|
|
2501
2844
|
import {
|
|
2502
2845
|
AuthStorage,
|
|
@@ -2726,40 +3069,238 @@ var ConfigFileAuthBackend = class {
|
|
|
2726
3069
|
// src/runtime/agent.ts
|
|
2727
3070
|
init_attachment_utils();
|
|
2728
3071
|
|
|
2729
|
-
// src/runtime/
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
3072
|
+
// src/runtime/custom-tools/delegated-custom-tool-client.ts
|
|
3073
|
+
import { randomUUID } from "crypto";
|
|
3074
|
+
function isObject(value) {
|
|
3075
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
3076
|
+
}
|
|
3077
|
+
function assertToolDefinitions(value) {
|
|
3078
|
+
if (!Array.isArray(value)) {
|
|
3079
|
+
throw new Error("Invalid delegated custom tool definitions response");
|
|
3080
|
+
}
|
|
3081
|
+
for (const definition of value) {
|
|
3082
|
+
if (!isObject(definition) || typeof definition.name !== "string" || typeof definition.label !== "string" || typeof definition.description !== "string" || !isObject(definition.parameters)) {
|
|
3083
|
+
throw new Error("Invalid delegated custom tool definition");
|
|
3084
|
+
}
|
|
2733
3085
|
}
|
|
2734
|
-
const normalized = Math.floor(limit);
|
|
2735
|
-
return Math.max(1, Math.min(normalized, max));
|
|
2736
3086
|
}
|
|
2737
|
-
function
|
|
2738
|
-
if (!
|
|
2739
|
-
|
|
3087
|
+
function assertToolResult(value) {
|
|
3088
|
+
if (!isObject(value) || !Array.isArray(value.content)) {
|
|
3089
|
+
throw new Error("Invalid delegated custom tool result");
|
|
2740
3090
|
}
|
|
2741
|
-
return Math.max(0, Math.floor(offset));
|
|
2742
3091
|
}
|
|
2743
|
-
var
|
|
2744
|
-
constructor(
|
|
2745
|
-
this.
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
3092
|
+
var DelegatedCustomToolClient = class {
|
|
3093
|
+
constructor(transport = process, options = {}) {
|
|
3094
|
+
this.transport = transport;
|
|
3095
|
+
this.timeoutMs = options.timeoutMs ?? 30 * 60 * 1e3;
|
|
3096
|
+
this.transport.on("message", this.handleMessage);
|
|
3097
|
+
}
|
|
3098
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
3099
|
+
timeoutMs;
|
|
3100
|
+
disposed = false;
|
|
3101
|
+
isAvailable() {
|
|
3102
|
+
return typeof this.transport.send === "function" && this.transport.connected !== false;
|
|
3103
|
+
}
|
|
3104
|
+
async listDefinitions() {
|
|
3105
|
+
if (!this.isAvailable()) {
|
|
3106
|
+
return [];
|
|
3107
|
+
}
|
|
3108
|
+
const data = await this.sendRequest("get_custom_tool_definitions");
|
|
3109
|
+
assertToolDefinitions(data);
|
|
3110
|
+
return data;
|
|
3111
|
+
}
|
|
3112
|
+
async executeTool(input) {
|
|
3113
|
+
const data = await this.sendRequest("execute_custom_tool", input);
|
|
3114
|
+
assertToolResult(data);
|
|
3115
|
+
return data;
|
|
3116
|
+
}
|
|
3117
|
+
dispose() {
|
|
3118
|
+
if (this.disposed) {
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
3121
|
+
this.disposed = true;
|
|
3122
|
+
this.transport.off("message", this.handleMessage);
|
|
3123
|
+
this.rejectAllPending(new Error("Delegated custom tool client disposed"));
|
|
3124
|
+
}
|
|
3125
|
+
sendRequest(type, payload) {
|
|
3126
|
+
if (!this.isAvailable()) {
|
|
3127
|
+
throw new Error("Delegated custom tool IPC channel is not available");
|
|
3128
|
+
}
|
|
3129
|
+
const id = randomUUID();
|
|
3130
|
+
const request = {
|
|
3131
|
+
id,
|
|
3132
|
+
type,
|
|
3133
|
+
...payload || {}
|
|
3134
|
+
};
|
|
3135
|
+
return new Promise((resolve, reject) => {
|
|
3136
|
+
const timer = setTimeout(() => {
|
|
3137
|
+
this.pendingRequests.delete(id);
|
|
3138
|
+
reject(new Error(`Delegated custom tool request timed out: ${type}`));
|
|
3139
|
+
}, this.timeoutMs);
|
|
3140
|
+
this.pendingRequests.set(id, { resolve, reject, timer });
|
|
3141
|
+
try {
|
|
3142
|
+
this.transport.send(request);
|
|
3143
|
+
} catch (error) {
|
|
3144
|
+
clearTimeout(timer);
|
|
3145
|
+
this.pendingRequests.delete(id);
|
|
3146
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
3147
|
+
}
|
|
2752
3148
|
});
|
|
2753
3149
|
}
|
|
3150
|
+
handleMessage = (message) => {
|
|
3151
|
+
if (!isObject(message)) {
|
|
3152
|
+
return;
|
|
3153
|
+
}
|
|
3154
|
+
const payload = message;
|
|
3155
|
+
if (typeof payload.id !== "string" || payload.type !== "result" && payload.type !== "error") {
|
|
3156
|
+
return;
|
|
3157
|
+
}
|
|
3158
|
+
const pending = this.pendingRequests.get(payload.id);
|
|
3159
|
+
if (!pending) {
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3162
|
+
clearTimeout(pending.timer);
|
|
3163
|
+
this.pendingRequests.delete(payload.id);
|
|
3164
|
+
if (payload.type === "error") {
|
|
3165
|
+
pending.reject(new Error(payload.message));
|
|
3166
|
+
return;
|
|
3167
|
+
}
|
|
3168
|
+
pending.resolve(payload.data);
|
|
3169
|
+
};
|
|
3170
|
+
rejectAllPending(error) {
|
|
3171
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
3172
|
+
clearTimeout(pending.timer);
|
|
3173
|
+
pending.reject(error);
|
|
3174
|
+
this.pendingRequests.delete(id);
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
2754
3177
|
};
|
|
2755
3178
|
|
|
2756
|
-
// src/runtime/
|
|
2757
|
-
|
|
2758
|
-
|
|
3179
|
+
// src/runtime/custom-tools/delegated-custom-tool-factory.ts
|
|
3180
|
+
function toAgentToolResult(result) {
|
|
3181
|
+
return {
|
|
3182
|
+
content: result.content,
|
|
3183
|
+
details: result.details
|
|
3184
|
+
};
|
|
3185
|
+
}
|
|
3186
|
+
function createDelegatedCustomTools(definitions, client, runContextRef) {
|
|
3187
|
+
return definitions.map((definition) => ({
|
|
3188
|
+
name: definition.name,
|
|
3189
|
+
label: definition.label,
|
|
3190
|
+
description: definition.description,
|
|
3191
|
+
promptSnippet: definition.promptSnippet,
|
|
3192
|
+
promptGuidelines: definition.promptGuidelines,
|
|
3193
|
+
parameters: definition.parameters,
|
|
3194
|
+
async execute(toolCallId, params) {
|
|
3195
|
+
const runContext = runContextRef.current;
|
|
3196
|
+
if (!runContext) {
|
|
3197
|
+
throw new Error(
|
|
3198
|
+
`Delegated custom tool ${definition.name} is not available outside an active run.`
|
|
3199
|
+
);
|
|
3200
|
+
}
|
|
3201
|
+
return toAgentToolResult(
|
|
3202
|
+
await client.executeTool({
|
|
3203
|
+
toolName: definition.name,
|
|
3204
|
+
toolCallId,
|
|
3205
|
+
runContext,
|
|
3206
|
+
params
|
|
3207
|
+
})
|
|
3208
|
+
);
|
|
3209
|
+
}
|
|
3210
|
+
}));
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
// src/runtime/host-ipc/host-ipc-client.ts
|
|
3214
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3215
|
+
function isObject2(value) {
|
|
3216
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
3217
|
+
}
|
|
3218
|
+
var HostIpcClient = class {
|
|
3219
|
+
constructor(transport = process, options = {}) {
|
|
3220
|
+
this.transport = transport;
|
|
3221
|
+
this.timeoutMs = options.timeoutMs ?? 30 * 60 * 1e3;
|
|
3222
|
+
this.transport.on("message", this.handleMessage);
|
|
3223
|
+
}
|
|
3224
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
3225
|
+
timeoutMs;
|
|
3226
|
+
disposed = false;
|
|
3227
|
+
isAvailable() {
|
|
3228
|
+
return typeof this.transport.send === "function" && this.transport.connected !== false;
|
|
3229
|
+
}
|
|
3230
|
+
async notifyChannelSessionCleared(input) {
|
|
3231
|
+
if (!this.isAvailable()) {
|
|
3232
|
+
return;
|
|
3233
|
+
}
|
|
3234
|
+
await this.sendRequest("channel_session_cleared", input);
|
|
3235
|
+
}
|
|
3236
|
+
dispose() {
|
|
3237
|
+
if (this.disposed) {
|
|
3238
|
+
return;
|
|
3239
|
+
}
|
|
3240
|
+
this.disposed = true;
|
|
3241
|
+
this.transport.off("message", this.handleMessage);
|
|
3242
|
+
this.rejectAllPending(new Error("Host IPC client disposed"));
|
|
3243
|
+
}
|
|
3244
|
+
sendRequest(type, payload) {
|
|
3245
|
+
if (!this.isAvailable()) {
|
|
3246
|
+
throw new Error("Host IPC channel is not available");
|
|
3247
|
+
}
|
|
3248
|
+
const id = randomUUID2();
|
|
3249
|
+
const request = {
|
|
3250
|
+
id,
|
|
3251
|
+
type,
|
|
3252
|
+
...payload || {}
|
|
3253
|
+
};
|
|
3254
|
+
return new Promise((resolve, reject) => {
|
|
3255
|
+
const timer = setTimeout(() => {
|
|
3256
|
+
this.pendingRequests.delete(id);
|
|
3257
|
+
reject(new Error(`Host IPC request timed out: ${type}`));
|
|
3258
|
+
}, this.timeoutMs);
|
|
3259
|
+
this.pendingRequests.set(id, { resolve, reject, timer });
|
|
3260
|
+
try {
|
|
3261
|
+
this.transport.send(request);
|
|
3262
|
+
} catch (error) {
|
|
3263
|
+
clearTimeout(timer);
|
|
3264
|
+
this.pendingRequests.delete(id);
|
|
3265
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
3266
|
+
}
|
|
3267
|
+
});
|
|
3268
|
+
}
|
|
3269
|
+
handleMessage = (message) => {
|
|
3270
|
+
if (!isObject2(message)) {
|
|
3271
|
+
return;
|
|
3272
|
+
}
|
|
3273
|
+
const payload = message;
|
|
3274
|
+
if (typeof payload.id !== "string" || payload.type !== "result" && payload.type !== "error") {
|
|
3275
|
+
return;
|
|
3276
|
+
}
|
|
3277
|
+
const pending = this.pendingRequests.get(payload.id);
|
|
3278
|
+
if (!pending) {
|
|
3279
|
+
return;
|
|
3280
|
+
}
|
|
3281
|
+
clearTimeout(pending.timer);
|
|
3282
|
+
this.pendingRequests.delete(payload.id);
|
|
3283
|
+
if (payload.type === "error") {
|
|
3284
|
+
pending.reject(new Error(payload.message));
|
|
3285
|
+
return;
|
|
3286
|
+
}
|
|
3287
|
+
pending.resolve(payload.data);
|
|
3288
|
+
};
|
|
3289
|
+
rejectAllPending(error) {
|
|
3290
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
3291
|
+
clearTimeout(pending.timer);
|
|
3292
|
+
pending.reject(error);
|
|
3293
|
+
this.pendingRequests.delete(id);
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
};
|
|
3297
|
+
|
|
3298
|
+
// src/runtime/tools/send-file-tool.ts
|
|
3299
|
+
import fs8 from "fs";
|
|
2759
3300
|
import path9 from "path";
|
|
3301
|
+
import { Type } from "@sinclair/typebox";
|
|
2760
3302
|
|
|
2761
3303
|
// src/runtime/files/metadata.ts
|
|
2762
|
-
import fs8 from "fs";
|
|
2763
3304
|
import path8 from "path";
|
|
2764
3305
|
var MIME_BY_EXT = {
|
|
2765
3306
|
".png": "image/png",
|
|
@@ -2789,419 +3330,14 @@ function isWithinDirectory(parentDir, targetPath) {
|
|
|
2789
3330
|
const relativePath = path8.relative(path8.resolve(parentDir), path8.resolve(targetPath));
|
|
2790
3331
|
return relativePath !== ".." && !relativePath.startsWith(`..${path8.sep}`) && !path8.isAbsolute(relativePath);
|
|
2791
3332
|
}
|
|
2792
|
-
function toPackRelativePath(rootDir, filePath) {
|
|
2793
|
-
const resolvedRoot = path8.resolve(rootDir);
|
|
2794
|
-
const resolvedFile = path8.resolve(filePath);
|
|
2795
|
-
if (!isWithinDirectory(resolvedRoot, resolvedFile)) {
|
|
2796
|
-
throw new Error(`Path is outside the pack root: ${resolvedFile}`);
|
|
2797
|
-
}
|
|
2798
|
-
return path8.relative(resolvedRoot, resolvedFile).split(path8.sep).join("/");
|
|
2799
|
-
}
|
|
2800
|
-
function resolvePackFile(rootDir, filePath) {
|
|
2801
|
-
if (!path8.isAbsolute(filePath)) {
|
|
2802
|
-
throw new Error(`filePath must be absolute: ${filePath}`);
|
|
2803
|
-
}
|
|
2804
|
-
const resolvedPath = path8.resolve(filePath);
|
|
2805
|
-
if (!isWithinDirectory(rootDir, resolvedPath)) {
|
|
2806
|
-
throw new Error(`File is outside the pack root: ${resolvedPath}`);
|
|
2807
|
-
}
|
|
2808
|
-
if (!fs8.existsSync(resolvedPath)) {
|
|
2809
|
-
throw new Error(`File not found: ${resolvedPath}`);
|
|
2810
|
-
}
|
|
2811
|
-
const stats = fs8.statSync(resolvedPath);
|
|
2812
|
-
if (!stats.isFile()) {
|
|
2813
|
-
throw new Error(`Path is not a file: ${resolvedPath}`);
|
|
2814
|
-
}
|
|
2815
|
-
fs8.accessSync(resolvedPath, fs8.constants.R_OK);
|
|
2816
|
-
return {
|
|
2817
|
-
resolvedPath,
|
|
2818
|
-
fileName: path8.basename(resolvedPath),
|
|
2819
|
-
mimeType: detectMimeType(resolvedPath),
|
|
2820
|
-
sizeBytes: stats.size
|
|
2821
|
-
};
|
|
2822
|
-
}
|
|
2823
|
-
|
|
2824
|
-
// src/runtime/artifacts/snapshot-service.ts
|
|
2825
|
-
function sanitizeFileName(fileName) {
|
|
2826
|
-
const sanitized = fileName.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
2827
|
-
return sanitized || "artifact";
|
|
2828
|
-
}
|
|
2829
|
-
function formatSnapshotStamp(isoDate) {
|
|
2830
|
-
const normalized = isoDate.replace(/\D+/g, "").slice(0, 14);
|
|
2831
|
-
return normalized || String(Date.now());
|
|
2832
|
-
}
|
|
2833
|
-
var ArtifactSnapshotService = class {
|
|
2834
|
-
constructor(rootDir) {
|
|
2835
|
-
this.rootDir = rootDir;
|
|
2836
|
-
}
|
|
2837
|
-
createSnapshots(runId, artifacts, declaredAt) {
|
|
2838
|
-
if (artifacts.length === 0) {
|
|
2839
|
-
return [];
|
|
2840
|
-
}
|
|
2841
|
-
const artifactsRoot = path9.resolve(this.rootDir, "data", "artifacts");
|
|
2842
|
-
const runDir = path9.join(artifactsRoot, runId);
|
|
2843
|
-
const tempDir = path9.join(
|
|
2844
|
-
artifactsRoot,
|
|
2845
|
-
`.tmp-${runId}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
|
2846
|
-
);
|
|
2847
|
-
const snapshots = [];
|
|
2848
|
-
const movedPaths = [];
|
|
2849
|
-
const snapshotNames = artifacts.map((artifact) => [
|
|
2850
|
-
formatSnapshotStamp(declaredAt),
|
|
2851
|
-
randomUUID(),
|
|
2852
|
-
sanitizeFileName(artifact.fileName)
|
|
2853
|
-
].join("-"));
|
|
2854
|
-
fs9.mkdirSync(artifactsRoot, { recursive: true });
|
|
2855
|
-
fs9.mkdirSync(tempDir, { recursive: true });
|
|
2856
|
-
try {
|
|
2857
|
-
snapshotNames.forEach((snapshotName, index) => {
|
|
2858
|
-
fs9.copyFileSync(
|
|
2859
|
-
artifacts[index].filePath,
|
|
2860
|
-
path9.join(tempDir, snapshotName)
|
|
2861
|
-
);
|
|
2862
|
-
});
|
|
2863
|
-
fs9.mkdirSync(runDir, { recursive: true });
|
|
2864
|
-
snapshotNames.forEach((snapshotName, index) => {
|
|
2865
|
-
const artifact = artifacts[index];
|
|
2866
|
-
const tempSnapshotPath = path9.join(tempDir, snapshotName);
|
|
2867
|
-
const finalSnapshotPath = path9.join(runDir, snapshotName);
|
|
2868
|
-
fs9.renameSync(tempSnapshotPath, finalSnapshotPath);
|
|
2869
|
-
movedPaths.push(finalSnapshotPath);
|
|
2870
|
-
snapshots.push({
|
|
2871
|
-
declaredAt,
|
|
2872
|
-
originalPath: toPackRelativePath(this.rootDir, artifact.filePath),
|
|
2873
|
-
snapshotPath: path9.join("data", "artifacts", runId, snapshotName).split(path9.sep).join("/"),
|
|
2874
|
-
fileName: artifact.fileName,
|
|
2875
|
-
mimeType: artifact.mimeType,
|
|
2876
|
-
sizeBytes: artifact.sizeBytes,
|
|
2877
|
-
title: artifact.title,
|
|
2878
|
-
isPrimary: artifact.isPrimary
|
|
2879
|
-
});
|
|
2880
|
-
});
|
|
2881
|
-
fs9.rmSync(tempDir, { recursive: true, force: true });
|
|
2882
|
-
return snapshots;
|
|
2883
|
-
} catch (error) {
|
|
2884
|
-
fs9.rmSync(tempDir, { recursive: true, force: true });
|
|
2885
|
-
movedPaths.forEach((filePath) => fs9.rmSync(filePath, { force: true }));
|
|
2886
|
-
this.removeEmptyRunDirectory(runDir);
|
|
2887
|
-
throw error;
|
|
2888
|
-
}
|
|
2889
|
-
}
|
|
2890
|
-
removeSnapshots(snapshotPaths) {
|
|
2891
|
-
const visitedRunDirs = /* @__PURE__ */ new Set();
|
|
2892
|
-
for (const snapshotPath of snapshotPaths) {
|
|
2893
|
-
const resolvedPath = path9.resolve(this.rootDir, snapshotPath);
|
|
2894
|
-
fs9.rmSync(resolvedPath, { force: true });
|
|
2895
|
-
visitedRunDirs.add(path9.dirname(resolvedPath));
|
|
2896
|
-
}
|
|
2897
|
-
visitedRunDirs.forEach((runDir) => this.removeEmptyRunDirectory(runDir));
|
|
2898
|
-
}
|
|
2899
|
-
removeEmptyRunDirectory(runDir) {
|
|
2900
|
-
try {
|
|
2901
|
-
if (!fs9.existsSync(runDir)) {
|
|
2902
|
-
return;
|
|
2903
|
-
}
|
|
2904
|
-
if (fs9.readdirSync(runDir).length === 0) {
|
|
2905
|
-
fs9.rmdirSync(runDir);
|
|
2906
|
-
}
|
|
2907
|
-
} catch {
|
|
2908
|
-
}
|
|
2909
|
-
}
|
|
2910
|
-
};
|
|
2911
|
-
|
|
2912
|
-
// src/runtime/artifacts/persistence-service.ts
|
|
2913
|
-
var ArtifactPersistenceService = class {
|
|
2914
|
-
constructor(snapshotService, resultStore) {
|
|
2915
|
-
this.snapshotService = snapshotService;
|
|
2916
|
-
this.resultStore = resultStore;
|
|
2917
|
-
}
|
|
2918
|
-
async saveArtifacts(input) {
|
|
2919
|
-
const declaredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2920
|
-
const snapshots = this.snapshotService.createSnapshots(
|
|
2921
|
-
input.runId,
|
|
2922
|
-
input.artifacts,
|
|
2923
|
-
declaredAt
|
|
2924
|
-
);
|
|
2925
|
-
try {
|
|
2926
|
-
await this.resultStore.insertArtifacts({
|
|
2927
|
-
runId: input.runId,
|
|
2928
|
-
channelId: input.channelId,
|
|
2929
|
-
artifacts: snapshots
|
|
2930
|
-
});
|
|
2931
|
-
return snapshots.length;
|
|
2932
|
-
} catch (error) {
|
|
2933
|
-
this.snapshotService.removeSnapshots(
|
|
2934
|
-
snapshots.map((artifact) => artifact.snapshotPath)
|
|
2935
|
-
);
|
|
2936
|
-
throw error;
|
|
2937
|
-
}
|
|
2938
|
-
}
|
|
2939
|
-
};
|
|
2940
|
-
|
|
2941
|
-
// src/runtime/artifacts/store.ts
|
|
2942
|
-
import fs10 from "fs";
|
|
2943
|
-
import path10 from "path";
|
|
2944
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
2945
|
-
import sqlite3 from "sqlite3";
|
|
2946
|
-
function mapArtifactRow(row) {
|
|
2947
|
-
return {
|
|
2948
|
-
artifactId: row.artifact_id,
|
|
2949
|
-
runId: row.run_id,
|
|
2950
|
-
channelId: row.channel_id,
|
|
2951
|
-
originalPath: row.original_path,
|
|
2952
|
-
snapshotPath: row.snapshot_path,
|
|
2953
|
-
fileName: row.file_name,
|
|
2954
|
-
mimeType: row.mime_type,
|
|
2955
|
-
sizeBytes: row.size_bytes,
|
|
2956
|
-
title: row.title,
|
|
2957
|
-
isPrimary: row.is_primary === 1,
|
|
2958
|
-
declaredAt: row.declared_at
|
|
2959
|
-
};
|
|
2960
|
-
}
|
|
2961
|
-
var ResultStore = class {
|
|
2962
|
-
db = null;
|
|
2963
|
-
ready;
|
|
2964
|
-
constructor(rootDir) {
|
|
2965
|
-
const dataDir = path10.resolve(rootDir, "data");
|
|
2966
|
-
fs10.mkdirSync(dataDir, { recursive: true });
|
|
2967
|
-
this.ready = this.initialize(path10.join(dataDir, "result-v2.db"));
|
|
2968
|
-
}
|
|
2969
|
-
async initialize(databasePath) {
|
|
2970
|
-
this.db = await openDatabase(databasePath);
|
|
2971
|
-
await this.exec("PRAGMA journal_mode = WAL");
|
|
2972
|
-
await this.exec(`
|
|
2973
|
-
CREATE TABLE IF NOT EXISTS artifacts (
|
|
2974
|
-
artifact_id TEXT PRIMARY KEY,
|
|
2975
|
-
run_id TEXT NOT NULL,
|
|
2976
|
-
channel_id TEXT NOT NULL,
|
|
2977
|
-
original_path TEXT NOT NULL,
|
|
2978
|
-
snapshot_path TEXT NOT NULL,
|
|
2979
|
-
file_name TEXT NOT NULL,
|
|
2980
|
-
mime_type TEXT,
|
|
2981
|
-
size_bytes INTEGER NOT NULL,
|
|
2982
|
-
title TEXT,
|
|
2983
|
-
is_primary INTEGER NOT NULL DEFAULT 0,
|
|
2984
|
-
declared_at TEXT NOT NULL
|
|
2985
|
-
);
|
|
2986
|
-
|
|
2987
|
-
CREATE INDEX IF NOT EXISTS idx_artifacts_channel_declared_at
|
|
2988
|
-
ON artifacts(channel_id, declared_at DESC);
|
|
2989
|
-
`);
|
|
2990
|
-
}
|
|
2991
|
-
async insertArtifacts(input) {
|
|
2992
|
-
await this.ready;
|
|
2993
|
-
if (input.artifacts.length === 0) {
|
|
2994
|
-
return;
|
|
2995
|
-
}
|
|
2996
|
-
const insertArtifact = `
|
|
2997
|
-
INSERT INTO artifacts (
|
|
2998
|
-
artifact_id,
|
|
2999
|
-
run_id,
|
|
3000
|
-
channel_id,
|
|
3001
|
-
original_path,
|
|
3002
|
-
snapshot_path,
|
|
3003
|
-
file_name,
|
|
3004
|
-
mime_type,
|
|
3005
|
-
size_bytes,
|
|
3006
|
-
title,
|
|
3007
|
-
is_primary,
|
|
3008
|
-
declared_at
|
|
3009
|
-
) VALUES (
|
|
3010
|
-
?,
|
|
3011
|
-
?,
|
|
3012
|
-
?,
|
|
3013
|
-
?,
|
|
3014
|
-
?,
|
|
3015
|
-
?,
|
|
3016
|
-
?,
|
|
3017
|
-
?,
|
|
3018
|
-
?,
|
|
3019
|
-
?,
|
|
3020
|
-
?
|
|
3021
|
-
)
|
|
3022
|
-
`;
|
|
3023
|
-
await this.exec("BEGIN");
|
|
3024
|
-
try {
|
|
3025
|
-
for (const artifact of input.artifacts) {
|
|
3026
|
-
await this.run(insertArtifact, [
|
|
3027
|
-
randomUUID2(),
|
|
3028
|
-
input.runId,
|
|
3029
|
-
input.channelId,
|
|
3030
|
-
artifact.originalPath,
|
|
3031
|
-
artifact.snapshotPath,
|
|
3032
|
-
artifact.fileName,
|
|
3033
|
-
artifact.mimeType ?? null,
|
|
3034
|
-
artifact.sizeBytes,
|
|
3035
|
-
artifact.title ?? null,
|
|
3036
|
-
artifact.isPrimary ? 1 : 0,
|
|
3037
|
-
artifact.declaredAt
|
|
3038
|
-
]);
|
|
3039
|
-
}
|
|
3040
|
-
await this.exec("COMMIT");
|
|
3041
|
-
} catch (error) {
|
|
3042
|
-
await this.rollback();
|
|
3043
|
-
throw error;
|
|
3044
|
-
}
|
|
3045
|
-
}
|
|
3046
|
-
async listRecentArtifacts(options = {}) {
|
|
3047
|
-
await this.ready;
|
|
3048
|
-
const limit = options.limit ?? 100;
|
|
3049
|
-
const offset = options.offset ?? 0;
|
|
3050
|
-
const conditions = [];
|
|
3051
|
-
const params = [];
|
|
3052
|
-
if (options.channelId) {
|
|
3053
|
-
conditions.push("channel_id = ?");
|
|
3054
|
-
params.push(options.channelId);
|
|
3055
|
-
}
|
|
3056
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
3057
|
-
const rows = await this.all(`
|
|
3058
|
-
SELECT *
|
|
3059
|
-
FROM artifacts
|
|
3060
|
-
${whereClause}
|
|
3061
|
-
ORDER BY declared_at DESC, rowid DESC
|
|
3062
|
-
LIMIT ? OFFSET ?
|
|
3063
|
-
`, [...params, limit, offset]);
|
|
3064
|
-
return rows.map(mapArtifactRow);
|
|
3065
|
-
}
|
|
3066
|
-
getDatabase() {
|
|
3067
|
-
if (!this.db) {
|
|
3068
|
-
throw new Error("Result store database is not ready");
|
|
3069
|
-
}
|
|
3070
|
-
return this.db;
|
|
3071
|
-
}
|
|
3072
|
-
exec(sql) {
|
|
3073
|
-
const db = this.getDatabase();
|
|
3074
|
-
return new Promise((resolve, reject) => {
|
|
3075
|
-
db.exec(sql, (error) => {
|
|
3076
|
-
if (error) {
|
|
3077
|
-
reject(error);
|
|
3078
|
-
return;
|
|
3079
|
-
}
|
|
3080
|
-
resolve();
|
|
3081
|
-
});
|
|
3082
|
-
});
|
|
3083
|
-
}
|
|
3084
|
-
run(sql, params = []) {
|
|
3085
|
-
const db = this.getDatabase();
|
|
3086
|
-
return new Promise((resolve, reject) => {
|
|
3087
|
-
db.run(sql, params, (error) => {
|
|
3088
|
-
if (error) {
|
|
3089
|
-
reject(error);
|
|
3090
|
-
return;
|
|
3091
|
-
}
|
|
3092
|
-
resolve();
|
|
3093
|
-
});
|
|
3094
|
-
});
|
|
3095
|
-
}
|
|
3096
|
-
all(sql, params = []) {
|
|
3097
|
-
const db = this.getDatabase();
|
|
3098
|
-
return new Promise((resolve, reject) => {
|
|
3099
|
-
db.all(sql, params, (error, rows) => {
|
|
3100
|
-
if (error) {
|
|
3101
|
-
reject(error);
|
|
3102
|
-
return;
|
|
3103
|
-
}
|
|
3104
|
-
resolve(rows);
|
|
3105
|
-
});
|
|
3106
|
-
});
|
|
3107
|
-
}
|
|
3108
|
-
async rollback() {
|
|
3109
|
-
try {
|
|
3110
|
-
await this.exec("ROLLBACK");
|
|
3111
|
-
} catch {
|
|
3112
|
-
}
|
|
3113
|
-
}
|
|
3114
|
-
};
|
|
3115
|
-
function openDatabase(databasePath) {
|
|
3116
|
-
return new Promise((resolve, reject) => {
|
|
3117
|
-
const db = new sqlite3.Database(databasePath, (error) => {
|
|
3118
|
-
if (error) {
|
|
3119
|
-
reject(error);
|
|
3120
|
-
return;
|
|
3121
|
-
}
|
|
3122
|
-
resolve(db);
|
|
3123
|
-
});
|
|
3124
|
-
});
|
|
3125
|
-
}
|
|
3126
|
-
|
|
3127
|
-
// src/runtime/artifacts/save-artifacts-tool.ts
|
|
3128
|
-
import { Type } from "@sinclair/typebox";
|
|
3129
|
-
var ArtifactItem = Type.Object({
|
|
3130
|
-
filePath: Type.String({
|
|
3131
|
-
description: "Absolute path to the artifact file. The file must exist, be readable, and be inside the current pack root."
|
|
3132
|
-
}),
|
|
3133
|
-
title: Type.Optional(
|
|
3134
|
-
Type.String({
|
|
3135
|
-
description: "Optional short title shown in the dashboard."
|
|
3136
|
-
})
|
|
3137
|
-
),
|
|
3138
|
-
isPrimary: Type.Optional(
|
|
3139
|
-
Type.Boolean({
|
|
3140
|
-
description: "Mark this artifact as a primary output."
|
|
3141
|
-
})
|
|
3142
|
-
)
|
|
3143
|
-
});
|
|
3144
|
-
var SaveArtifactsParams = Type.Object({
|
|
3145
|
-
artifacts: Type.Array(ArtifactItem, {
|
|
3146
|
-
minItems: 1,
|
|
3147
|
-
description: "The artifact files to save for this run."
|
|
3148
|
-
})
|
|
3149
|
-
});
|
|
3150
|
-
function textResult(text) {
|
|
3151
|
-
return { content: [{ type: "text", text }], details: void 0 };
|
|
3152
|
-
}
|
|
3153
|
-
function normalizeOptionalText(value) {
|
|
3154
|
-
const trimmed = value?.trim();
|
|
3155
|
-
return trimmed ? trimmed : void 0;
|
|
3156
|
-
}
|
|
3157
|
-
function createSaveArtifactsTool(rootDir, saveCallbackRef) {
|
|
3158
|
-
return {
|
|
3159
|
-
name: "save_artifacts",
|
|
3160
|
-
label: "Save Artifacts",
|
|
3161
|
-
description: [
|
|
3162
|
-
"Save the final output files produced by this run.",
|
|
3163
|
-
"Always use this for user-facing deliverables that are part of the final result.",
|
|
3164
|
-
"Do not use this for intermediate, temporary, draft, or scratch files.",
|
|
3165
|
-
"Each filePath must be an absolute path inside the current pack root."
|
|
3166
|
-
].join("\n"),
|
|
3167
|
-
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.",
|
|
3168
|
-
promptGuidelines: [
|
|
3169
|
-
"Whenever you create a final result file for the user, call `save_artifacts` before finishing the response.",
|
|
3170
|
-
"Do not call `save_artifacts` for intermediate, temporary, draft, or scratch files."
|
|
3171
|
-
],
|
|
3172
|
-
parameters: SaveArtifactsParams,
|
|
3173
|
-
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
3174
|
-
const saveArtifacts = saveCallbackRef.current;
|
|
3175
|
-
if (!saveArtifacts) {
|
|
3176
|
-
throw new Error("Artifact saving is not available for this run.");
|
|
3177
|
-
}
|
|
3178
|
-
const artifacts = params.artifacts.map((artifact) => {
|
|
3179
|
-
const metadata = resolvePackFile(rootDir, artifact.filePath);
|
|
3180
|
-
return {
|
|
3181
|
-
filePath: metadata.resolvedPath,
|
|
3182
|
-
fileName: metadata.fileName,
|
|
3183
|
-
mimeType: metadata.mimeType,
|
|
3184
|
-
sizeBytes: metadata.sizeBytes,
|
|
3185
|
-
title: normalizeOptionalText(artifact.title),
|
|
3186
|
-
isPrimary: artifact.isPrimary === true
|
|
3187
|
-
};
|
|
3188
|
-
});
|
|
3189
|
-
const savedCount = await saveArtifacts(artifacts);
|
|
3190
|
-
return textResult(`Saved ${savedCount} artifact(s).`);
|
|
3191
|
-
}
|
|
3192
|
-
};
|
|
3193
|
-
}
|
|
3194
3333
|
|
|
3195
3334
|
// src/runtime/tools/send-file-tool.ts
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
import { Type as Type2 } from "@sinclair/typebox";
|
|
3199
|
-
var SendFileParams = Type2.Object({
|
|
3200
|
-
filePath: Type2.String({
|
|
3335
|
+
var SendFileParams = Type.Object({
|
|
3336
|
+
filePath: Type.String({
|
|
3201
3337
|
description: "Absolute path to the file to send to the user. The file must exist and be readable."
|
|
3202
3338
|
}),
|
|
3203
|
-
caption:
|
|
3204
|
-
|
|
3339
|
+
caption: Type.Optional(
|
|
3340
|
+
Type.String({
|
|
3205
3341
|
description: "Optional caption or description to accompany the file."
|
|
3206
3342
|
})
|
|
3207
3343
|
)
|
|
@@ -3215,13 +3351,13 @@ function createSendFileTool(fileOutputCallbackRef) {
|
|
|
3215
3351
|
parameters: SendFileParams,
|
|
3216
3352
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
3217
3353
|
const { filePath, caption } = params;
|
|
3218
|
-
if (!
|
|
3354
|
+
if (!fs8.existsSync(filePath)) {
|
|
3219
3355
|
return {
|
|
3220
3356
|
content: [{ type: "text", text: `Error: File not found: ${filePath}` }],
|
|
3221
3357
|
details: void 0
|
|
3222
3358
|
};
|
|
3223
3359
|
}
|
|
3224
|
-
const stats =
|
|
3360
|
+
const stats = fs8.statSync(filePath);
|
|
3225
3361
|
if (!stats.isFile()) {
|
|
3226
3362
|
return {
|
|
3227
3363
|
content: [
|
|
@@ -3230,7 +3366,7 @@ function createSendFileTool(fileOutputCallbackRef) {
|
|
|
3230
3366
|
details: void 0
|
|
3231
3367
|
};
|
|
3232
3368
|
}
|
|
3233
|
-
const filename =
|
|
3369
|
+
const filename = path9.basename(filePath);
|
|
3234
3370
|
const mimeType = detectMimeType(filePath);
|
|
3235
3371
|
const callback = fileOutputCallbackRef.current;
|
|
3236
3372
|
if (callback) {
|
|
@@ -3257,53 +3393,75 @@ function createSendFileTool(fileOutputCallbackRef) {
|
|
|
3257
3393
|
}
|
|
3258
3394
|
|
|
3259
3395
|
// src/runtime/tools/manage-schedule-tool.ts
|
|
3260
|
-
import {
|
|
3261
|
-
|
|
3262
|
-
|
|
3396
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
3397
|
+
import { Type as Type2 } from "@sinclair/typebox";
|
|
3398
|
+
var ManageScheduleParams = Type2.Object({
|
|
3399
|
+
action: Type2.Union(
|
|
3263
3400
|
[
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3401
|
+
Type2.Literal("add"),
|
|
3402
|
+
Type2.Literal("list"),
|
|
3403
|
+
Type2.Literal("remove"),
|
|
3404
|
+
Type2.Literal("trigger"),
|
|
3405
|
+
Type2.Literal("enable"),
|
|
3406
|
+
Type2.Literal("disable")
|
|
3270
3407
|
],
|
|
3271
3408
|
{ description: "The action to perform." }
|
|
3272
3409
|
),
|
|
3273
|
-
name:
|
|
3274
|
-
|
|
3275
|
-
description: "
|
|
3410
|
+
name: Type2.Optional(
|
|
3411
|
+
Type2.String({
|
|
3412
|
+
description: "Display name for the scheduled task. Required for add/remove/trigger/enable/disable."
|
|
3276
3413
|
})
|
|
3277
3414
|
),
|
|
3278
|
-
cron:
|
|
3279
|
-
|
|
3415
|
+
cron: Type2.Optional(
|
|
3416
|
+
Type2.String({
|
|
3280
3417
|
description: "Cron expression (5 fields: minute hour day month weekday). Required for add."
|
|
3281
3418
|
})
|
|
3282
3419
|
),
|
|
3283
|
-
prompt:
|
|
3284
|
-
|
|
3420
|
+
prompt: Type2.Optional(
|
|
3421
|
+
Type2.String({
|
|
3285
3422
|
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."
|
|
3286
3423
|
})
|
|
3287
3424
|
),
|
|
3288
|
-
timezone:
|
|
3289
|
-
|
|
3425
|
+
timezone: Type2.Optional(
|
|
3426
|
+
Type2.String({
|
|
3290
3427
|
description: "Optional timezone for the cron schedule, e.g. 'Asia/Shanghai', 'America/New_York'."
|
|
3291
3428
|
})
|
|
3292
3429
|
),
|
|
3293
|
-
notifyAdapter:
|
|
3294
|
-
|
|
3295
|
-
description: "Optional target adapter for notifications. If omitted, the current chat is used when supported (Telegram, Slack, or Web)."
|
|
3430
|
+
notifyAdapter: Type2.Optional(
|
|
3431
|
+
Type2.String({
|
|
3432
|
+
description: "Optional target adapter for notifications. If omitted, the current chat is used when supported (Telegram, Slack, Feishu, or Web)."
|
|
3296
3433
|
})
|
|
3297
3434
|
),
|
|
3298
|
-
notifyChannelId:
|
|
3299
|
-
|
|
3435
|
+
notifyChannelId: Type2.Optional(
|
|
3436
|
+
Type2.String({
|
|
3300
3437
|
description: "Optional target channelId for notifications. Must be provided together with notifyAdapter when overriding the default target."
|
|
3301
3438
|
})
|
|
3302
3439
|
)
|
|
3303
3440
|
});
|
|
3304
|
-
function
|
|
3441
|
+
function textResult(text) {
|
|
3305
3442
|
return { content: [{ type: "text", text }], details: void 0 };
|
|
3306
3443
|
}
|
|
3444
|
+
function resolveJobId(scheduler, nameOrId) {
|
|
3445
|
+
const jobs = scheduler.listJobs();
|
|
3446
|
+
const byId = jobs.find((job) => job.id === nameOrId);
|
|
3447
|
+
if (byId) {
|
|
3448
|
+
return { ok: true, jobId: byId.id };
|
|
3449
|
+
}
|
|
3450
|
+
const byName = jobs.filter((job) => job.name === nameOrId);
|
|
3451
|
+
if (byName.length === 1) {
|
|
3452
|
+
return { ok: true, jobId: byName[0].id };
|
|
3453
|
+
}
|
|
3454
|
+
if (byName.length > 1) {
|
|
3455
|
+
return {
|
|
3456
|
+
ok: false,
|
|
3457
|
+
message: `Error: Multiple scheduled tasks share the name '${nameOrId}'. Rename one of them before using this action.`
|
|
3458
|
+
};
|
|
3459
|
+
}
|
|
3460
|
+
return {
|
|
3461
|
+
ok: false,
|
|
3462
|
+
message: `Error: Scheduled task '${nameOrId}' was not found.`
|
|
3463
|
+
};
|
|
3464
|
+
}
|
|
3307
3465
|
function getDefaultNotifyTarget(adapter, channelId) {
|
|
3308
3466
|
if (adapter === "telegram" && channelId.startsWith("telegram-")) {
|
|
3309
3467
|
return { adapter: "telegram", channelId };
|
|
@@ -3311,12 +3469,15 @@ function getDefaultNotifyTarget(adapter, channelId) {
|
|
|
3311
3469
|
if (adapter === "slack" && channelId.startsWith("slack-")) {
|
|
3312
3470
|
return { adapter: "slack", channelId };
|
|
3313
3471
|
}
|
|
3472
|
+
if (adapter === "feishu" && channelId.startsWith("feishu-")) {
|
|
3473
|
+
return { adapter: "feishu", channelId };
|
|
3474
|
+
}
|
|
3314
3475
|
if (adapter === "web") {
|
|
3315
3476
|
return { adapter: "web", channelId };
|
|
3316
3477
|
}
|
|
3317
3478
|
return null;
|
|
3318
3479
|
}
|
|
3319
|
-
function createManageScheduleTool(schedulerRef, adapter, channelId) {
|
|
3480
|
+
function createManageScheduleTool(schedulerRef, adapter, channelId, generateJobId = randomUUID3) {
|
|
3320
3481
|
return {
|
|
3321
3482
|
name: "manage_scheduled_task",
|
|
3322
3483
|
label: "Manage Scheduled Task",
|
|
@@ -3324,11 +3485,11 @@ function createManageScheduleTool(schedulerRef, adapter, channelId) {
|
|
|
3324
3485
|
"Manage scheduled tasks (cron jobs) that automatically execute prompts and push results to IM channels.",
|
|
3325
3486
|
"",
|
|
3326
3487
|
"Actions:",
|
|
3327
|
-
"- 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.",
|
|
3488
|
+
"- add: Create a new scheduled task. Requires: name, cron, prompt. Notifications default to the current Telegram, Slack, Feishu, 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.",
|
|
3328
3489
|
"- list: List all scheduled tasks with their status.",
|
|
3329
|
-
"- remove: Remove a scheduled task by name.",
|
|
3330
|
-
"- trigger: Manually trigger a scheduled task by name (runs immediately).",
|
|
3331
|
-
"- enable: Enable a disabled scheduled task.",
|
|
3490
|
+
"- remove: Remove a scheduled task by display name.",
|
|
3491
|
+
"- trigger: Manually trigger a scheduled task by display name (runs immediately).",
|
|
3492
|
+
"- enable: Enable a disabled scheduled task by display name.",
|
|
3332
3493
|
"- disable: Disable a scheduled task without removing it.",
|
|
3333
3494
|
"",
|
|
3334
3495
|
"Cron expression format: '* * * * *' (minute hour day month weekday)",
|
|
@@ -3341,7 +3502,7 @@ function createManageScheduleTool(schedulerRef, adapter, channelId) {
|
|
|
3341
3502
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
3342
3503
|
const scheduler = schedulerRef.current;
|
|
3343
3504
|
if (!scheduler) {
|
|
3344
|
-
return
|
|
3505
|
+
return textResult(
|
|
3345
3506
|
"Error: Scheduler is not available. The scheduled task system may not be initialized."
|
|
3346
3507
|
);
|
|
3347
3508
|
}
|
|
@@ -3349,24 +3510,24 @@ function createManageScheduleTool(schedulerRef, adapter, channelId) {
|
|
|
3349
3510
|
case "list": {
|
|
3350
3511
|
const jobs = scheduler.listJobs();
|
|
3351
3512
|
if (jobs.length === 0) {
|
|
3352
|
-
return
|
|
3513
|
+
return textResult("No scheduled tasks configured.");
|
|
3353
3514
|
}
|
|
3354
3515
|
const lines = jobs.map(
|
|
3355
3516
|
(j) => `- **${j.name}**: \`${j.cron}\` \u2192 ${j.notify.adapter}:${j.notify.channelId} [${j.enabled ? "enabled" : "disabled"}]${j.running ? " (running)" : ""}${j.lastRunAt ? ` (last: ${j.lastRunAt})` : ""}`
|
|
3356
3517
|
);
|
|
3357
|
-
return
|
|
3518
|
+
return textResult(
|
|
3358
3519
|
`Scheduled tasks (${jobs.length}):
|
|
3359
3520
|
${lines.join("\n")}`
|
|
3360
3521
|
);
|
|
3361
3522
|
}
|
|
3362
3523
|
case "add": {
|
|
3363
3524
|
if (!params.name || !params.cron || !params.prompt) {
|
|
3364
|
-
return
|
|
3525
|
+
return textResult(
|
|
3365
3526
|
"Error: 'name', 'cron', and 'prompt' are required for adding a task."
|
|
3366
3527
|
);
|
|
3367
3528
|
}
|
|
3368
3529
|
if (params.notifyAdapter && !params.notifyChannelId || !params.notifyAdapter && params.notifyChannelId) {
|
|
3369
|
-
return
|
|
3530
|
+
return textResult(
|
|
3370
3531
|
"Error: 'notifyAdapter' and 'notifyChannelId' must be provided together when overriding the notification target."
|
|
3371
3532
|
);
|
|
3372
3533
|
}
|
|
@@ -3375,11 +3536,12 @@ ${lines.join("\n")}`
|
|
|
3375
3536
|
channelId: params.notifyChannelId
|
|
3376
3537
|
} : getDefaultNotifyTarget(adapter, channelId);
|
|
3377
3538
|
if (!notify) {
|
|
3378
|
-
return
|
|
3539
|
+
return textResult(
|
|
3379
3540
|
"Error: No default notification target is available for this chat. Provide 'notifyAdapter' and 'notifyChannelId'."
|
|
3380
3541
|
);
|
|
3381
3542
|
}
|
|
3382
3543
|
const jobConfig = {
|
|
3544
|
+
id: generateJobId(),
|
|
3383
3545
|
name: params.name,
|
|
3384
3546
|
cron: params.cron,
|
|
3385
3547
|
prompt: params.prompt,
|
|
@@ -3388,46 +3550,62 @@ ${lines.join("\n")}`
|
|
|
3388
3550
|
timezone: params.timezone
|
|
3389
3551
|
};
|
|
3390
3552
|
const result = scheduler.addJob(jobConfig);
|
|
3391
|
-
return
|
|
3553
|
+
return textResult(result.message);
|
|
3392
3554
|
}
|
|
3393
3555
|
case "remove": {
|
|
3394
3556
|
if (!params.name) {
|
|
3395
|
-
return
|
|
3557
|
+
return textResult(
|
|
3396
3558
|
"Error: 'name' is required for removing a task."
|
|
3397
3559
|
);
|
|
3398
3560
|
}
|
|
3399
|
-
const
|
|
3400
|
-
|
|
3561
|
+
const resolved = resolveJobId(scheduler, params.name);
|
|
3562
|
+
if (!resolved.ok) {
|
|
3563
|
+
return textResult(resolved.message);
|
|
3564
|
+
}
|
|
3565
|
+
const result = scheduler.removeJob(resolved.jobId);
|
|
3566
|
+
return textResult(result.message);
|
|
3401
3567
|
}
|
|
3402
3568
|
case "trigger": {
|
|
3403
3569
|
if (!params.name) {
|
|
3404
|
-
return
|
|
3570
|
+
return textResult(
|
|
3405
3571
|
"Error: 'name' is required for triggering a task."
|
|
3406
3572
|
);
|
|
3407
3573
|
}
|
|
3408
|
-
const
|
|
3409
|
-
|
|
3574
|
+
const resolved = resolveJobId(scheduler, params.name);
|
|
3575
|
+
if (!resolved.ok) {
|
|
3576
|
+
return textResult(resolved.message);
|
|
3577
|
+
}
|
|
3578
|
+
const result = await scheduler.triggerJob(resolved.jobId);
|
|
3579
|
+
return textResult(result.message);
|
|
3410
3580
|
}
|
|
3411
3581
|
case "enable": {
|
|
3412
3582
|
if (!params.name) {
|
|
3413
|
-
return
|
|
3583
|
+
return textResult(
|
|
3414
3584
|
"Error: 'name' is required for enabling a task."
|
|
3415
3585
|
);
|
|
3416
3586
|
}
|
|
3417
|
-
const
|
|
3418
|
-
|
|
3587
|
+
const resolved = resolveJobId(scheduler, params.name);
|
|
3588
|
+
if (!resolved.ok) {
|
|
3589
|
+
return textResult(resolved.message);
|
|
3590
|
+
}
|
|
3591
|
+
const result = scheduler.setEnabled(resolved.jobId, true);
|
|
3592
|
+
return textResult(result.message);
|
|
3419
3593
|
}
|
|
3420
3594
|
case "disable": {
|
|
3421
3595
|
if (!params.name) {
|
|
3422
|
-
return
|
|
3596
|
+
return textResult(
|
|
3423
3597
|
"Error: 'name' is required for disabling a task."
|
|
3424
3598
|
);
|
|
3425
3599
|
}
|
|
3426
|
-
const
|
|
3427
|
-
|
|
3600
|
+
const resolved = resolveJobId(scheduler, params.name);
|
|
3601
|
+
if (!resolved.ok) {
|
|
3602
|
+
return textResult(resolved.message);
|
|
3603
|
+
}
|
|
3604
|
+
const result = scheduler.setEnabled(resolved.jobId, false);
|
|
3605
|
+
return textResult(result.message);
|
|
3428
3606
|
}
|
|
3429
3607
|
default:
|
|
3430
|
-
return
|
|
3608
|
+
return textResult(
|
|
3431
3609
|
`Error: Unknown action '${params.action}'. Use: add, list, remove, trigger, enable, disable.`
|
|
3432
3610
|
);
|
|
3433
3611
|
}
|
|
@@ -3437,8 +3615,8 @@ ${lines.join("\n")}`
|
|
|
3437
3615
|
|
|
3438
3616
|
// src/runtime/commands/help-command.ts
|
|
3439
3617
|
init_commands();
|
|
3440
|
-
import
|
|
3441
|
-
import
|
|
3618
|
+
import fs9 from "fs";
|
|
3619
|
+
import path10 from "path";
|
|
3442
3620
|
function buildHelpMessage(rootDir) {
|
|
3443
3621
|
const sections = [];
|
|
3444
3622
|
const commands = getVisibleCommands();
|
|
@@ -3448,7 +3626,7 @@ function buildHelpMessage(rootDir) {
|
|
|
3448
3626
|
sections.push(`\u{1F4CB} **Available Commands**
|
|
3449
3627
|
|
|
3450
3628
|
${commandLines.join("\n")}`);
|
|
3451
|
-
const configPath =
|
|
3629
|
+
const configPath = path10.resolve(rootDir, "skillpack.json");
|
|
3452
3630
|
const skills = readInstalledSkills(configPath);
|
|
3453
3631
|
if (skills.length > 0) {
|
|
3454
3632
|
const skillLines = skills.map(
|
|
@@ -3484,11 +3662,11 @@ function handleHelpCommand(rootDir) {
|
|
|
3484
3662
|
};
|
|
3485
3663
|
}
|
|
3486
3664
|
function readInstalledSkills(configPath) {
|
|
3487
|
-
if (!
|
|
3665
|
+
if (!fs9.existsSync(configPath)) {
|
|
3488
3666
|
return [];
|
|
3489
3667
|
}
|
|
3490
3668
|
try {
|
|
3491
|
-
const raw =
|
|
3669
|
+
const raw = fs9.readFileSync(configPath, "utf-8");
|
|
3492
3670
|
const config = JSON.parse(raw);
|
|
3493
3671
|
return Array.isArray(config.skills) ? config.skills : [];
|
|
3494
3672
|
} catch {
|
|
@@ -3497,6 +3675,7 @@ function readInstalledSkills(configPath) {
|
|
|
3497
3675
|
}
|
|
3498
3676
|
|
|
3499
3677
|
// src/runtime/agent.ts
|
|
3678
|
+
init_types();
|
|
3500
3679
|
var DEBUG = true;
|
|
3501
3680
|
var log = (...args) => DEBUG && console.log(...args);
|
|
3502
3681
|
var write = (data) => DEBUG && process.stdout.write(data);
|
|
@@ -3508,24 +3687,24 @@ var BUILTIN_SKILL_CREATOR_TEMPLATE_DIR = fileURLToPath(
|
|
|
3508
3687
|
var PACK_AGENTS_FILE = "AGENTS.md";
|
|
3509
3688
|
var PACK_SOUL_FILE = "SOUL.md";
|
|
3510
3689
|
function materializeBuiltinSkillCreator(rootDir, skillsPath) {
|
|
3511
|
-
if (!
|
|
3690
|
+
if (!fs10.existsSync(BUILTIN_SKILL_CREATOR_TEMPLATE_DIR)) {
|
|
3512
3691
|
log(
|
|
3513
3692
|
`[PackAgent] Built-in skill-creator template missing: ${BUILTIN_SKILL_CREATOR_TEMPLATE_DIR}`
|
|
3514
3693
|
);
|
|
3515
3694
|
return null;
|
|
3516
3695
|
}
|
|
3517
|
-
const packConfigPath =
|
|
3518
|
-
const skillDir =
|
|
3519
|
-
const skillPath =
|
|
3696
|
+
const packConfigPath = path11.resolve(rootDir, "skillpack.json");
|
|
3697
|
+
const skillDir = path11.resolve(skillsPath, BUILTIN_SKILL_CREATOR_NAME);
|
|
3698
|
+
const skillPath = path11.join(skillDir, "SKILL.md");
|
|
3520
3699
|
const renderTemplate = (content) => content.replaceAll("{{SKILLS_PATH}}", skillsPath).replaceAll("{{PACK_CONFIG_PATH}}", packConfigPath);
|
|
3521
3700
|
const copyDir = (srcDir, destDir) => {
|
|
3522
|
-
|
|
3523
|
-
for (const entry of
|
|
3701
|
+
fs10.mkdirSync(destDir, { recursive: true });
|
|
3702
|
+
for (const entry of fs10.readdirSync(srcDir, { withFileTypes: true })) {
|
|
3524
3703
|
if (entry.name === ".DS_Store") {
|
|
3525
3704
|
continue;
|
|
3526
3705
|
}
|
|
3527
|
-
const srcPath =
|
|
3528
|
-
const destPath =
|
|
3706
|
+
const srcPath = path11.join(srcDir, entry.name);
|
|
3707
|
+
const destPath = path11.join(destDir, entry.name);
|
|
3529
3708
|
if (entry.isDirectory()) {
|
|
3530
3709
|
copyDir(srcPath, destPath);
|
|
3531
3710
|
continue;
|
|
@@ -3534,17 +3713,17 @@ function materializeBuiltinSkillCreator(rootDir, skillsPath) {
|
|
|
3534
3713
|
continue;
|
|
3535
3714
|
}
|
|
3536
3715
|
if (entry.name.endsWith(".md") || entry.name.endsWith(".py")) {
|
|
3537
|
-
const content =
|
|
3538
|
-
|
|
3716
|
+
const content = fs10.readFileSync(srcPath, "utf-8");
|
|
3717
|
+
fs10.writeFileSync(destPath, renderTemplate(content), "utf-8");
|
|
3539
3718
|
continue;
|
|
3540
3719
|
}
|
|
3541
|
-
|
|
3720
|
+
fs10.copyFileSync(srcPath, destPath);
|
|
3542
3721
|
}
|
|
3543
3722
|
};
|
|
3544
|
-
if (!
|
|
3723
|
+
if (!fs10.existsSync(skillDir)) {
|
|
3545
3724
|
copyDir(BUILTIN_SKILL_CREATOR_TEMPLATE_DIR, skillDir);
|
|
3546
3725
|
}
|
|
3547
|
-
if (!
|
|
3726
|
+
if (!fs10.existsSync(skillPath)) {
|
|
3548
3727
|
log(
|
|
3549
3728
|
`[PackAgent] Materialized built-in skill-creator but SKILL.md is missing: ${skillPath}`
|
|
3550
3729
|
);
|
|
@@ -3572,11 +3751,11 @@ function overrideBuiltinSkillCreator(base, materializedSkill) {
|
|
|
3572
3751
|
};
|
|
3573
3752
|
}
|
|
3574
3753
|
function readOptionalPackPromptFile(filePath) {
|
|
3575
|
-
if (!
|
|
3754
|
+
if (!fs10.existsSync(filePath)) {
|
|
3576
3755
|
return void 0;
|
|
3577
3756
|
}
|
|
3578
3757
|
try {
|
|
3579
|
-
const content =
|
|
3758
|
+
const content = fs10.readFileSync(filePath, "utf-8").trim();
|
|
3580
3759
|
return content.length > 0 ? content : void 0;
|
|
3581
3760
|
} catch (error) {
|
|
3582
3761
|
console.warn(`[PackAgent] Warning: Could not read ${filePath}:`, error);
|
|
@@ -3584,8 +3763,8 @@ function readOptionalPackPromptFile(filePath) {
|
|
|
3584
3763
|
}
|
|
3585
3764
|
}
|
|
3586
3765
|
function buildPackPromptBlock(rootDir) {
|
|
3587
|
-
const agentsPath =
|
|
3588
|
-
const soulPath =
|
|
3766
|
+
const agentsPath = path11.resolve(rootDir, PACK_AGENTS_FILE);
|
|
3767
|
+
const soulPath = path11.resolve(rootDir, PACK_SOUL_FILE);
|
|
3589
3768
|
const agentsContent = readOptionalPackPromptFile(agentsPath);
|
|
3590
3769
|
const soulContent = readOptionalPackPromptFile(soulPath);
|
|
3591
3770
|
if (!agentsContent && !soulContent) {
|
|
@@ -3636,21 +3815,22 @@ function getAssistantDiagnostics(message) {
|
|
|
3636
3815
|
return { stopReason, errorMessage, hasText: text.length > 0, toolCalls };
|
|
3637
3816
|
}
|
|
3638
3817
|
function getLifecycleTrigger(channelId) {
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
return "web";
|
|
3818
|
+
const platform = detectPlatformFromChannelId(channelId);
|
|
3819
|
+
return platform === "scheduler" ? "web" : platform;
|
|
3642
3820
|
}
|
|
3643
3821
|
var PackAgent = class {
|
|
3644
3822
|
options;
|
|
3645
3823
|
channels = /* @__PURE__ */ new Map();
|
|
3646
3824
|
pendingSessionCreations = /* @__PURE__ */ new Map();
|
|
3647
|
-
schedulerRef = {
|
|
3825
|
+
schedulerRef = {
|
|
3826
|
+
current: null
|
|
3827
|
+
};
|
|
3648
3828
|
authStorage;
|
|
3649
|
-
|
|
3829
|
+
delegatedCustomToolClient = new DelegatedCustomToolClient();
|
|
3830
|
+
hostIpcClient = new HostIpcClient();
|
|
3650
3831
|
constructor(options) {
|
|
3651
3832
|
this.options = options;
|
|
3652
|
-
|
|
3653
|
-
const configPath = path13.resolve(options.rootDir, "data", "config.json");
|
|
3833
|
+
const configPath = path11.resolve(options.rootDir, "data", "config.json");
|
|
3654
3834
|
const backend = new ConfigFileAuthBackend(configPath);
|
|
3655
3835
|
this.authStorage = AuthStorage.fromStorage(backend);
|
|
3656
3836
|
const providerMeta = SUPPORTED_PROVIDERS[options.provider];
|
|
@@ -3677,13 +3857,20 @@ var PackAgent = class {
|
|
|
3677
3857
|
setScheduler(scheduler) {
|
|
3678
3858
|
this.schedulerRef.current = scheduler;
|
|
3679
3859
|
}
|
|
3680
|
-
createCustomTools(adapter, channelId, fileOutputCallbackRef,
|
|
3860
|
+
async createCustomTools(adapter, channelId, fileOutputCallbackRef, delegatedToolRunContextRef) {
|
|
3861
|
+
const delegatedDefinitions = await this.delegatedCustomToolClient.listDefinitions();
|
|
3681
3862
|
const tools = [
|
|
3682
3863
|
createSendFileTool(fileOutputCallbackRef),
|
|
3683
|
-
|
|
3864
|
+
...createDelegatedCustomTools(
|
|
3865
|
+
delegatedDefinitions,
|
|
3866
|
+
this.delegatedCustomToolClient,
|
|
3867
|
+
delegatedToolRunContextRef
|
|
3868
|
+
)
|
|
3684
3869
|
];
|
|
3685
3870
|
if (adapter !== "scheduler") {
|
|
3686
|
-
tools.push(
|
|
3871
|
+
tools.push(
|
|
3872
|
+
createManageScheduleTool(this.schedulerRef, adapter, channelId)
|
|
3873
|
+
);
|
|
3687
3874
|
}
|
|
3688
3875
|
return tools;
|
|
3689
3876
|
}
|
|
@@ -3701,7 +3888,9 @@ var PackAgent = class {
|
|
|
3701
3888
|
const modelRegistry = new ModelRegistry(authStorage);
|
|
3702
3889
|
if (baseUrl && modelId) {
|
|
3703
3890
|
const apiProtocol = this.options.apiProtocol ?? "openai-completions";
|
|
3704
|
-
log(
|
|
3891
|
+
log(
|
|
3892
|
+
`[PackAgent] Registering custom model ${provider}/${modelId} api=${apiProtocol} baseUrl=${baseUrl}`
|
|
3893
|
+
);
|
|
3705
3894
|
modelRegistry.registerProvider(provider, {
|
|
3706
3895
|
baseUrl,
|
|
3707
3896
|
apiKey: this.options.apiKey,
|
|
@@ -3722,26 +3911,23 @@ var PackAgent = class {
|
|
|
3722
3911
|
const resolvedModel = modelRegistry.find(provider, modelId);
|
|
3723
3912
|
const model = resolvedModel && baseUrl && !this.options.apiProtocol ? { ...resolvedModel, baseUrl } : resolvedModel;
|
|
3724
3913
|
if (resolvedModel && baseUrl) {
|
|
3725
|
-
log(
|
|
3914
|
+
log(
|
|
3915
|
+
`[PackAgent] Resolved ${provider}/${modelId} api=${resolvedModel.api} baseUrl=${baseUrl}`
|
|
3916
|
+
);
|
|
3726
3917
|
}
|
|
3727
|
-
const sessionDir =
|
|
3728
|
-
|
|
3729
|
-
"data",
|
|
3730
|
-
"sessions",
|
|
3731
|
-
channelId
|
|
3732
|
-
);
|
|
3733
|
-
fs13.mkdirSync(sessionDir, { recursive: true });
|
|
3918
|
+
const sessionDir = path11.resolve(rootDir, "data", "sessions", channelId);
|
|
3919
|
+
fs10.mkdirSync(sessionDir, { recursive: true });
|
|
3734
3920
|
const sessionManager = SessionManager.continueRecent(rootDir, sessionDir);
|
|
3735
3921
|
log(`[PackAgent] Session dir: ${sessionDir}`);
|
|
3736
|
-
const workspaceDir =
|
|
3922
|
+
const workspaceDir = path11.resolve(
|
|
3737
3923
|
rootDir,
|
|
3738
3924
|
"data",
|
|
3739
3925
|
"workspaces",
|
|
3740
3926
|
channelId
|
|
3741
3927
|
);
|
|
3742
|
-
|
|
3928
|
+
fs10.mkdirSync(workspaceDir, { recursive: true });
|
|
3743
3929
|
log(`[PackAgent] Workspace dir: ${workspaceDir}`);
|
|
3744
|
-
const skillsPath =
|
|
3930
|
+
const skillsPath = path11.resolve(rootDir, "skills");
|
|
3745
3931
|
log(`[PackAgent] Loading skills from: ${skillsPath}`);
|
|
3746
3932
|
const materializedSkillCreator = materializeBuiltinSkillCreator(
|
|
3747
3933
|
rootDir,
|
|
@@ -3754,14 +3940,22 @@ var PackAgent = class {
|
|
|
3754
3940
|
}
|
|
3755
3941
|
const packPromptFiles = buildPackPromptBlock(rootDir);
|
|
3756
3942
|
if (packPromptFiles.agentsContent) {
|
|
3757
|
-
log(
|
|
3943
|
+
log(
|
|
3944
|
+
`[PackAgent] Loaded pack policy from: ${packPromptFiles.agentsPath}`
|
|
3945
|
+
);
|
|
3758
3946
|
} else {
|
|
3759
|
-
log(
|
|
3947
|
+
log(
|
|
3948
|
+
`[PackAgent] No pack policy file found at: ${packPromptFiles.agentsPath}`
|
|
3949
|
+
);
|
|
3760
3950
|
}
|
|
3761
3951
|
if (packPromptFiles.soulContent) {
|
|
3762
|
-
log(
|
|
3952
|
+
log(
|
|
3953
|
+
`[PackAgent] Loaded pack persona from: ${packPromptFiles.soulPath}`
|
|
3954
|
+
);
|
|
3763
3955
|
} else {
|
|
3764
|
-
log(
|
|
3956
|
+
log(
|
|
3957
|
+
`[PackAgent] No pack persona file found at: ${packPromptFiles.soulPath}`
|
|
3958
|
+
);
|
|
3765
3959
|
}
|
|
3766
3960
|
log(
|
|
3767
3961
|
`[PackAgent] Pack prompt injection: ${packPromptFiles.promptBlock ? "enabled" : "disabled"}`
|
|
@@ -3779,14 +3973,14 @@ var PackAgent = class {
|
|
|
3779
3973
|
const fileOutputCallbackRef = {
|
|
3780
3974
|
current: null
|
|
3781
3975
|
};
|
|
3782
|
-
const
|
|
3976
|
+
const delegatedToolRunContextRef = {
|
|
3783
3977
|
current: null
|
|
3784
3978
|
};
|
|
3785
|
-
const customTools = this.createCustomTools(
|
|
3979
|
+
const customTools = await this.createCustomTools(
|
|
3786
3980
|
adapter,
|
|
3787
3981
|
channelId,
|
|
3788
3982
|
fileOutputCallbackRef,
|
|
3789
|
-
|
|
3983
|
+
delegatedToolRunContextRef
|
|
3790
3984
|
);
|
|
3791
3985
|
const { session } = await createAgentSession({
|
|
3792
3986
|
cwd: workspaceDir,
|
|
@@ -3803,7 +3997,7 @@ var PackAgent = class {
|
|
|
3803
3997
|
running: false,
|
|
3804
3998
|
pending: Promise.resolve(),
|
|
3805
3999
|
fileOutputCallbackRef,
|
|
3806
|
-
|
|
4000
|
+
delegatedToolRunContextRef
|
|
3807
4001
|
};
|
|
3808
4002
|
this.channels.set(channelId, channelSession);
|
|
3809
4003
|
return channelSession;
|
|
@@ -3820,18 +4014,16 @@ var PackAgent = class {
|
|
|
3820
4014
|
const run = async () => {
|
|
3821
4015
|
cs.running = true;
|
|
3822
4016
|
let turnHadVisibleOutput = false;
|
|
3823
|
-
const runId =
|
|
4017
|
+
const runId = randomUUID4();
|
|
3824
4018
|
let unsubscribe = () => void 0;
|
|
3825
4019
|
try {
|
|
3826
4020
|
cs.fileOutputCallbackRef.current = (event) => {
|
|
3827
4021
|
onEvent(event);
|
|
3828
4022
|
};
|
|
3829
|
-
cs.
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
artifacts
|
|
3834
|
-
});
|
|
4023
|
+
cs.delegatedToolRunContextRef.current = {
|
|
4024
|
+
runId,
|
|
4025
|
+
channelId,
|
|
4026
|
+
adapter
|
|
3835
4027
|
};
|
|
3836
4028
|
unsubscribe = cs.session.subscribe((event) => {
|
|
3837
4029
|
switch (event.type) {
|
|
@@ -3847,7 +4039,10 @@ var PackAgent = class {
|
|
|
3847
4039
|
if (event.message?.role === "user") {
|
|
3848
4040
|
log(JSON.stringify(event.message.content, null, 2));
|
|
3849
4041
|
}
|
|
3850
|
-
onEvent({
|
|
4042
|
+
onEvent({
|
|
4043
|
+
type: "message_start",
|
|
4044
|
+
role: event.message?.role ?? ""
|
|
4045
|
+
});
|
|
3851
4046
|
break;
|
|
3852
4047
|
case "message_update":
|
|
3853
4048
|
if (event.assistantMessageEvent?.type === "text_delta") {
|
|
@@ -3914,18 +4109,26 @@ var PackAgent = class {
|
|
|
3914
4109
|
let promptText = text;
|
|
3915
4110
|
const promptOptions = {};
|
|
3916
4111
|
if (attachments && attachments.length > 0) {
|
|
3917
|
-
const imageAttachments = attachments.filter(
|
|
3918
|
-
|
|
4112
|
+
const imageAttachments = attachments.filter(
|
|
4113
|
+
(a) => isImageMime(a.mimeType)
|
|
4114
|
+
);
|
|
4115
|
+
const nonImageAttachments = attachments.filter(
|
|
4116
|
+
(a) => !isImageMime(a.mimeType)
|
|
4117
|
+
);
|
|
3919
4118
|
if (imageAttachments.length > 0) {
|
|
3920
4119
|
promptOptions.images = attachmentsToImageContent(imageAttachments);
|
|
3921
|
-
log(
|
|
4120
|
+
log(
|
|
4121
|
+
`[PackAgent] Passing ${imageAttachments.length} image(s) to LLM`
|
|
4122
|
+
);
|
|
3922
4123
|
}
|
|
3923
4124
|
if (nonImageAttachments.length > 0) {
|
|
3924
4125
|
const attachmentPrompt = formatAttachmentsPrompt(nonImageAttachments);
|
|
3925
4126
|
promptText = `${attachmentPrompt}
|
|
3926
4127
|
|
|
3927
4128
|
${text}`;
|
|
3928
|
-
log(
|
|
4129
|
+
log(
|
|
4130
|
+
`[PackAgent] Injecting ${nonImageAttachments.length} non-image attachment(s) into prompt`
|
|
4131
|
+
);
|
|
3929
4132
|
}
|
|
3930
4133
|
}
|
|
3931
4134
|
await cs.session.prompt(promptText, promptOptions);
|
|
@@ -3948,12 +4151,15 @@ ${text}`;
|
|
|
3948
4151
|
} finally {
|
|
3949
4152
|
cs.running = false;
|
|
3950
4153
|
cs.fileOutputCallbackRef.current = null;
|
|
3951
|
-
cs.
|
|
4154
|
+
cs.delegatedToolRunContextRef.current = null;
|
|
3952
4155
|
unsubscribe();
|
|
3953
4156
|
}
|
|
3954
4157
|
};
|
|
3955
4158
|
const resultPromise = cs.pending.catch(() => void 0).then(run);
|
|
3956
|
-
cs.pending = resultPromise.then(
|
|
4159
|
+
cs.pending = resultPromise.then(
|
|
4160
|
+
() => void 0,
|
|
4161
|
+
() => void 0
|
|
4162
|
+
);
|
|
3957
4163
|
return resultPromise;
|
|
3958
4164
|
}
|
|
3959
4165
|
async handleCommand(command, channelId) {
|
|
@@ -3968,11 +4174,12 @@ ${text}`;
|
|
|
3968
4174
|
this.channels.delete(channelId);
|
|
3969
4175
|
}
|
|
3970
4176
|
const { rootDir } = this.options;
|
|
3971
|
-
const sessionDir =
|
|
3972
|
-
if (
|
|
3973
|
-
|
|
4177
|
+
const sessionDir = path11.resolve(rootDir, "data", "sessions", channelId);
|
|
4178
|
+
if (fs10.existsSync(sessionDir)) {
|
|
4179
|
+
fs10.rmSync(sessionDir, { recursive: true, force: true });
|
|
3974
4180
|
log(`[PackAgent] Cleared session dir: ${sessionDir}`);
|
|
3975
4181
|
}
|
|
4182
|
+
await this.hostIpcClient.notifyChannelSessionCleared({ channelId });
|
|
3976
4183
|
return {
|
|
3977
4184
|
success: true,
|
|
3978
4185
|
message: command === "new" ? "New session started." : "Session cleared."
|
|
@@ -4021,14 +4228,16 @@ ${text}`;
|
|
|
4021
4228
|
};
|
|
4022
4229
|
|
|
4023
4230
|
// src/runtime/adapters/web.ts
|
|
4024
|
-
import
|
|
4025
|
-
import
|
|
4231
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
4232
|
+
import fs12 from "fs";
|
|
4233
|
+
import path13 from "path";
|
|
4026
4234
|
import { WebSocketServer } from "ws";
|
|
4027
4235
|
init_commands();
|
|
4028
4236
|
|
|
4029
4237
|
// src/runtime/services/conversation.ts
|
|
4030
|
-
|
|
4031
|
-
import
|
|
4238
|
+
init_types();
|
|
4239
|
+
import fs11 from "fs";
|
|
4240
|
+
import path12 from "path";
|
|
4032
4241
|
import {
|
|
4033
4242
|
parseSessionEntries
|
|
4034
4243
|
} from "@mariozechner/pi-coding-agent";
|
|
@@ -4046,17 +4255,17 @@ var ConversationService = class {
|
|
|
4046
4255
|
includeLegacyWeb = true,
|
|
4047
4256
|
allowedPlatforms
|
|
4048
4257
|
} = options;
|
|
4049
|
-
const sessionsDir =
|
|
4258
|
+
const sessionsDir = path12.resolve(this.rootDir, "data", "sessions");
|
|
4050
4259
|
const channelIds = new Set(activeChannels);
|
|
4051
4260
|
const allowedPlatformSet = allowedPlatforms ? new Set(allowedPlatforms) : null;
|
|
4052
4261
|
if (includeDefaultWeb) {
|
|
4053
4262
|
channelIds.add(DEFAULT_WEB_CHANNEL_ID);
|
|
4054
4263
|
}
|
|
4055
|
-
if (
|
|
4056
|
-
for (const entry of
|
|
4057
|
-
const channelDir =
|
|
4264
|
+
if (fs11.existsSync(sessionsDir)) {
|
|
4265
|
+
for (const entry of fs11.readdirSync(sessionsDir)) {
|
|
4266
|
+
const channelDir = path12.join(sessionsDir, entry);
|
|
4058
4267
|
try {
|
|
4059
|
-
if (!
|
|
4268
|
+
if (!fs11.statSync(channelDir).isDirectory()) {
|
|
4060
4269
|
continue;
|
|
4061
4270
|
}
|
|
4062
4271
|
const platform = this.detectPlatform(entry);
|
|
@@ -4080,7 +4289,7 @@ var ConversationService = class {
|
|
|
4080
4289
|
if (!includeLegacyWeb && this.isLegacyWebConversation(channelId)) {
|
|
4081
4290
|
continue;
|
|
4082
4291
|
}
|
|
4083
|
-
const channelDir =
|
|
4292
|
+
const channelDir = path12.join(sessionsDir, channelId);
|
|
4084
4293
|
const sessionFile = this.findLatestSessionFile(channelDir);
|
|
4085
4294
|
let messageCount = 0;
|
|
4086
4295
|
let lastMessageAt = "";
|
|
@@ -4124,7 +4333,7 @@ var ConversationService = class {
|
|
|
4124
4333
|
* Load latest messages for a channel in a simplified format.
|
|
4125
4334
|
*/
|
|
4126
4335
|
getMessages(channelId, limit = 100) {
|
|
4127
|
-
const channelDir =
|
|
4336
|
+
const channelDir = path12.resolve(
|
|
4128
4337
|
this.rootDir,
|
|
4129
4338
|
"data",
|
|
4130
4339
|
"sessions",
|
|
@@ -4213,20 +4422,20 @@ var ConversationService = class {
|
|
|
4213
4422
|
return messages.slice(-safeLimit);
|
|
4214
4423
|
}
|
|
4215
4424
|
findLatestSessionFile(channelDir) {
|
|
4216
|
-
if (!
|
|
4425
|
+
if (!fs11.existsSync(channelDir)) return null;
|
|
4217
4426
|
let stats;
|
|
4218
4427
|
try {
|
|
4219
|
-
stats =
|
|
4428
|
+
stats = fs11.statSync(channelDir);
|
|
4220
4429
|
} catch {
|
|
4221
4430
|
return null;
|
|
4222
4431
|
}
|
|
4223
4432
|
if (!stats.isDirectory()) return null;
|
|
4224
|
-
const files =
|
|
4225
|
-
return files[0] ?
|
|
4433
|
+
const files = fs11.readdirSync(channelDir).filter((file) => file.endsWith(".jsonl")).sort((a, b) => b.localeCompare(a));
|
|
4434
|
+
return files[0] ? path12.join(channelDir, files[0]) : null;
|
|
4226
4435
|
}
|
|
4227
4436
|
loadEntries(filePath) {
|
|
4228
4437
|
try {
|
|
4229
|
-
const content =
|
|
4438
|
+
const content = fs11.readFileSync(filePath, "utf-8");
|
|
4230
4439
|
const fileEntries = parseSessionEntries(content);
|
|
4231
4440
|
return fileEntries.filter(
|
|
4232
4441
|
(entry) => entry.type !== "session"
|
|
@@ -4410,10 +4619,7 @@ var ConversationService = class {
|
|
|
4410
4619
|
return parts[parts.length - 1] || filePath;
|
|
4411
4620
|
}
|
|
4412
4621
|
detectPlatform(channelId) {
|
|
4413
|
-
|
|
4414
|
-
if (channelId.startsWith("slack-")) return "slack";
|
|
4415
|
-
if (channelId.startsWith("scheduler-")) return "scheduler";
|
|
4416
|
-
return "web";
|
|
4622
|
+
return detectPlatformFromChannelId(channelId);
|
|
4417
4623
|
}
|
|
4418
4624
|
isLegacyWebConversation(channelId) {
|
|
4419
4625
|
return channelId.startsWith("web-");
|
|
@@ -4422,7 +4628,7 @@ var ConversationService = class {
|
|
|
4422
4628
|
|
|
4423
4629
|
// src/runtime/adapters/web.ts
|
|
4424
4630
|
function getPackConfig(rootDir) {
|
|
4425
|
-
const raw =
|
|
4631
|
+
const raw = fs12.readFileSync(path13.join(rootDir, "skillpack.json"), "utf-8");
|
|
4426
4632
|
return JSON.parse(raw);
|
|
4427
4633
|
}
|
|
4428
4634
|
function parseCommand(text) {
|
|
@@ -4441,7 +4647,9 @@ function getRuntimeConfigSignature(config) {
|
|
|
4441
4647
|
apiProtocol: config.apiProtocol || "",
|
|
4442
4648
|
telegramToken: config.adapters?.telegram?.token || "",
|
|
4443
4649
|
slackBotToken: config.adapters?.slack?.botToken || "",
|
|
4444
|
-
slackAppToken: config.adapters?.slack?.appToken || ""
|
|
4650
|
+
slackAppToken: config.adapters?.slack?.appToken || "",
|
|
4651
|
+
feishuAppId: config.adapters?.feishu?.appId || "",
|
|
4652
|
+
feishuAppSecret: config.adapters?.feishu?.appSecret || ""
|
|
4445
4653
|
});
|
|
4446
4654
|
}
|
|
4447
4655
|
function parsePositiveInt(value, fallback) {
|
|
@@ -4452,7 +4660,7 @@ function parsePositiveInt(value, fallback) {
|
|
|
4452
4660
|
return Math.floor(parsed);
|
|
4453
4661
|
}
|
|
4454
4662
|
function resolveDownloadFilePath(rootDir, filePath) {
|
|
4455
|
-
const resolvedPath =
|
|
4663
|
+
const resolvedPath = path13.isAbsolute(filePath) ? path13.resolve(filePath) : path13.resolve(rootDir, filePath);
|
|
4456
4664
|
return isWithinDirectory(rootDir, resolvedPath) ? resolvedPath : null;
|
|
4457
4665
|
}
|
|
4458
4666
|
var WebAdapter = class {
|
|
@@ -4467,7 +4675,6 @@ var WebAdapter = class {
|
|
|
4467
4675
|
this.agent = agent;
|
|
4468
4676
|
this.ipcBroadcaster = ctx.ipcBroadcaster ?? null;
|
|
4469
4677
|
this.conversationService = new ConversationService(rootDir);
|
|
4470
|
-
const resultsQueryService = ctx.resultsQueryService ?? null;
|
|
4471
4678
|
const currentConf = configManager.getConfig();
|
|
4472
4679
|
let apiKey = currentConf.apiKey || "";
|
|
4473
4680
|
let currentProvider = currentConf.provider || "openai";
|
|
@@ -4611,17 +4818,6 @@ var WebAdapter = class {
|
|
|
4611
4818
|
)
|
|
4612
4819
|
);
|
|
4613
4820
|
});
|
|
4614
|
-
app.get("/api/results/artifacts", async (req, res) => {
|
|
4615
|
-
if (!resultsQueryService) {
|
|
4616
|
-
res.status(503).json({ error: "Results query service is not available" });
|
|
4617
|
-
return;
|
|
4618
|
-
}
|
|
4619
|
-
res.json(await resultsQueryService.listRecentArtifacts({
|
|
4620
|
-
channelId: typeof req.query.channelId === "string" ? req.query.channelId : void 0,
|
|
4621
|
-
limit: parsePositiveInt(req.query.limit, 100),
|
|
4622
|
-
offset: parsePositiveInt(req.query.offset, 0)
|
|
4623
|
-
}));
|
|
4624
|
-
});
|
|
4625
4821
|
app.get("/api/files", (req, res) => {
|
|
4626
4822
|
const filePath = req.query.path;
|
|
4627
4823
|
if (!filePath) {
|
|
@@ -4633,17 +4829,17 @@ var WebAdapter = class {
|
|
|
4633
4829
|
res.status(403).json({ error: "Access denied" });
|
|
4634
4830
|
return;
|
|
4635
4831
|
}
|
|
4636
|
-
if (!
|
|
4832
|
+
if (!fs12.existsSync(resolvedPath)) {
|
|
4637
4833
|
res.status(404).json({ error: "File not found" });
|
|
4638
4834
|
return;
|
|
4639
4835
|
}
|
|
4640
|
-
const filename =
|
|
4836
|
+
const filename = path13.basename(resolvedPath);
|
|
4641
4837
|
res.setHeader("Content-Type", "application/octet-stream");
|
|
4642
4838
|
res.setHeader(
|
|
4643
4839
|
"Content-Disposition",
|
|
4644
4840
|
`attachment; filename="${filename}"`
|
|
4645
4841
|
);
|
|
4646
|
-
|
|
4842
|
+
fs12.createReadStream(resolvedPath).pipe(res);
|
|
4647
4843
|
});
|
|
4648
4844
|
const getScheduler = () => {
|
|
4649
4845
|
const schedulerAdapter = ctx.adapterMap?.get("scheduler");
|
|
@@ -4664,7 +4860,15 @@ var WebAdapter = class {
|
|
|
4664
4860
|
res.status(503).json({ success: false, message: "Scheduler not available" });
|
|
4665
4861
|
return;
|
|
4666
4862
|
}
|
|
4667
|
-
const {
|
|
4863
|
+
const {
|
|
4864
|
+
id,
|
|
4865
|
+
name,
|
|
4866
|
+
cron: cronExpr,
|
|
4867
|
+
prompt,
|
|
4868
|
+
notify,
|
|
4869
|
+
enabled,
|
|
4870
|
+
timezone
|
|
4871
|
+
} = req.body;
|
|
4668
4872
|
if (!name || !prompt || !notify?.adapter || !notify?.channelId) {
|
|
4669
4873
|
res.status(400).json({
|
|
4670
4874
|
success: false,
|
|
@@ -4673,6 +4877,7 @@ var WebAdapter = class {
|
|
|
4673
4877
|
return;
|
|
4674
4878
|
}
|
|
4675
4879
|
const result = scheduler.addJob({
|
|
4880
|
+
id: typeof id === "string" && id.trim() ? id.trim() : randomUUID5(),
|
|
4676
4881
|
name,
|
|
4677
4882
|
...typeof cronExpr === "string" ? { cron: cronExpr } : {},
|
|
4678
4883
|
prompt,
|
|
@@ -4682,25 +4887,25 @@ var WebAdapter = class {
|
|
|
4682
4887
|
});
|
|
4683
4888
|
res.json(result);
|
|
4684
4889
|
});
|
|
4685
|
-
app.delete("/api/scheduler/jobs/:
|
|
4890
|
+
app.delete("/api/scheduler/jobs/:jobId", (req, res) => {
|
|
4686
4891
|
const scheduler = getScheduler();
|
|
4687
4892
|
if (!scheduler) {
|
|
4688
4893
|
res.status(503).json({ success: false, message: "Scheduler not available" });
|
|
4689
4894
|
return;
|
|
4690
4895
|
}
|
|
4691
|
-
const result = scheduler.removeJob(req.params.
|
|
4896
|
+
const result = scheduler.removeJob(req.params.jobId);
|
|
4692
4897
|
res.json(result);
|
|
4693
4898
|
});
|
|
4694
|
-
app.post("/api/scheduler/jobs/:
|
|
4899
|
+
app.post("/api/scheduler/jobs/:jobId/trigger", async (req, res) => {
|
|
4695
4900
|
const scheduler = getScheduler();
|
|
4696
4901
|
if (!scheduler) {
|
|
4697
4902
|
res.status(503).json({ success: false, message: "Scheduler not available" });
|
|
4698
4903
|
return;
|
|
4699
4904
|
}
|
|
4700
|
-
const result = await scheduler.triggerJob(req.params.
|
|
4905
|
+
const result = await scheduler.triggerJob(req.params.jobId);
|
|
4701
4906
|
res.json(result);
|
|
4702
4907
|
});
|
|
4703
|
-
app.patch("/api/scheduler/jobs/:
|
|
4908
|
+
app.patch("/api/scheduler/jobs/:jobId", (req, res) => {
|
|
4704
4909
|
const scheduler = getScheduler();
|
|
4705
4910
|
if (!scheduler) {
|
|
4706
4911
|
res.status(503).json({ success: false, message: "Scheduler not available" });
|
|
@@ -4711,7 +4916,7 @@ var WebAdapter = class {
|
|
|
4711
4916
|
res.status(400).json({ success: false, message: "Field 'enabled' (boolean) is required" });
|
|
4712
4917
|
return;
|
|
4713
4918
|
}
|
|
4714
|
-
const result = scheduler.setEnabled(req.params.
|
|
4919
|
+
const result = scheduler.setEnabled(req.params.jobId, enabled);
|
|
4715
4920
|
res.json(result);
|
|
4716
4921
|
});
|
|
4717
4922
|
this.wss = new WebSocketServer({ noServer: true });
|
|
@@ -4828,13 +5033,28 @@ var WebAdapter = class {
|
|
|
4828
5033
|
|
|
4829
5034
|
// src/runtime/adapters/ipc.ts
|
|
4830
5035
|
init_types();
|
|
5036
|
+
var IPC_REQUEST_TYPES = /* @__PURE__ */ new Set([
|
|
5037
|
+
"get_conversations",
|
|
5038
|
+
"create_conversation",
|
|
5039
|
+
"get_messages",
|
|
5040
|
+
"send_message",
|
|
5041
|
+
"command",
|
|
5042
|
+
"get_config",
|
|
5043
|
+
"update_config",
|
|
5044
|
+
"get_status",
|
|
5045
|
+
"get_scheduled_jobs",
|
|
5046
|
+
"add_scheduled_job",
|
|
5047
|
+
"update_scheduled_job",
|
|
5048
|
+
"set_scheduled_job_enabled",
|
|
5049
|
+
"trigger_scheduled_job",
|
|
5050
|
+
"remove_scheduled_job"
|
|
5051
|
+
]);
|
|
4831
5052
|
var IpcAdapter = class {
|
|
4832
5053
|
name = "ipc";
|
|
4833
5054
|
agent = null;
|
|
4834
5055
|
rootDir = "";
|
|
4835
5056
|
adapterMap = null;
|
|
4836
5057
|
conversationService = null;
|
|
4837
|
-
resultsQueryService = null;
|
|
4838
5058
|
createdChannels = /* @__PURE__ */ new Set();
|
|
4839
5059
|
messageListener;
|
|
4840
5060
|
started = false;
|
|
@@ -4846,7 +5066,6 @@ var IpcAdapter = class {
|
|
|
4846
5066
|
this.rootDir = ctx.rootDir;
|
|
4847
5067
|
this.adapterMap = ctx.adapterMap ?? null;
|
|
4848
5068
|
this.conversationService = new ConversationService(ctx.rootDir);
|
|
4849
|
-
this.resultsQueryService = ctx.resultsQueryService ?? null;
|
|
4850
5069
|
this.messageListener = (message) => {
|
|
4851
5070
|
if (!this.isIpcRequest(message)) return;
|
|
4852
5071
|
void this.handleRequest(message);
|
|
@@ -4891,7 +5110,7 @@ var IpcAdapter = class {
|
|
|
4891
5110
|
isIpcRequest(message) {
|
|
4892
5111
|
if (!message || typeof message !== "object") return false;
|
|
4893
5112
|
const maybe = message;
|
|
4894
|
-
return typeof maybe.id === "string" && typeof maybe.type === "string";
|
|
5113
|
+
return typeof maybe.id === "string" && typeof maybe.type === "string" && IPC_REQUEST_TYPES.has(maybe.type);
|
|
4895
5114
|
}
|
|
4896
5115
|
async handleRequest(request) {
|
|
4897
5116
|
if (!this.agent || !this.conversationService) {
|
|
@@ -4930,18 +5149,6 @@ var IpcAdapter = class {
|
|
|
4930
5149
|
this.reply(request.id, messages);
|
|
4931
5150
|
return;
|
|
4932
5151
|
}
|
|
4933
|
-
case "get_recent_artifacts": {
|
|
4934
|
-
if (!this.resultsQueryService) {
|
|
4935
|
-
this.replyError(request.id, "Results query service is not available");
|
|
4936
|
-
return;
|
|
4937
|
-
}
|
|
4938
|
-
this.reply(request.id, await this.resultsQueryService.listRecentArtifacts({
|
|
4939
|
-
channelId: request.channelId,
|
|
4940
|
-
limit: request.limit,
|
|
4941
|
-
offset: request.offset
|
|
4942
|
-
}));
|
|
4943
|
-
return;
|
|
4944
|
-
}
|
|
4945
5152
|
case "send_message": {
|
|
4946
5153
|
if (!request.channelId || typeof request.channelId !== "string") {
|
|
4947
5154
|
this.replyError(request.id, "channelId is required");
|
|
@@ -5030,7 +5237,7 @@ var IpcAdapter = class {
|
|
|
5030
5237
|
this.replyError(request.id, "Scheduler adapter is not available");
|
|
5031
5238
|
return;
|
|
5032
5239
|
}
|
|
5033
|
-
const result = scheduler.updateJob(request.
|
|
5240
|
+
const result = scheduler.updateJob(request.jobId, request.updates);
|
|
5034
5241
|
if (!result.success) {
|
|
5035
5242
|
this.replyError(request.id, result.message);
|
|
5036
5243
|
return;
|
|
@@ -5044,7 +5251,7 @@ var IpcAdapter = class {
|
|
|
5044
5251
|
this.replyError(request.id, "Scheduler adapter is not available");
|
|
5045
5252
|
return;
|
|
5046
5253
|
}
|
|
5047
|
-
const result = scheduler.setEnabled(request.
|
|
5254
|
+
const result = scheduler.setEnabled(request.jobId, request.enabled);
|
|
5048
5255
|
if (!result.success) {
|
|
5049
5256
|
this.replyError(request.id, result.message);
|
|
5050
5257
|
return;
|
|
@@ -5058,7 +5265,7 @@ var IpcAdapter = class {
|
|
|
5058
5265
|
this.replyError(request.id, "Scheduler adapter is not available");
|
|
5059
5266
|
return;
|
|
5060
5267
|
}
|
|
5061
|
-
const result = await scheduler.triggerJob(request.
|
|
5268
|
+
const result = await scheduler.triggerJob(request.jobId);
|
|
5062
5269
|
if (!result.success) {
|
|
5063
5270
|
this.replyError(request.id, result.message);
|
|
5064
5271
|
return;
|
|
@@ -5072,7 +5279,7 @@ var IpcAdapter = class {
|
|
|
5072
5279
|
this.replyError(request.id, "Scheduler adapter is not available");
|
|
5073
5280
|
return;
|
|
5074
5281
|
}
|
|
5075
|
-
const result = scheduler.removeJob(request.
|
|
5282
|
+
const result = scheduler.removeJob(request.jobId);
|
|
5076
5283
|
if (!result.success) {
|
|
5077
5284
|
this.replyError(request.id, result.message);
|
|
5078
5285
|
return;
|
|
@@ -5094,10 +5301,7 @@ var IpcAdapter = class {
|
|
|
5094
5301
|
return adapter;
|
|
5095
5302
|
}
|
|
5096
5303
|
detectPlatform(channelId) {
|
|
5097
|
-
|
|
5098
|
-
if (channelId.startsWith("slack-")) return "slack";
|
|
5099
|
-
if (channelId.startsWith("scheduler-")) return "scheduler";
|
|
5100
|
-
return "web";
|
|
5304
|
+
return detectPlatformFromChannelId(channelId);
|
|
5101
5305
|
}
|
|
5102
5306
|
sendIpc(payload) {
|
|
5103
5307
|
if (typeof process.send === "function") {
|
|
@@ -5185,28 +5389,28 @@ var Lifecycle = class {
|
|
|
5185
5389
|
|
|
5186
5390
|
// src/runtime/registry.ts
|
|
5187
5391
|
import crypto from "crypto";
|
|
5188
|
-
import
|
|
5392
|
+
import fs13 from "fs";
|
|
5189
5393
|
import os from "os";
|
|
5190
|
-
import
|
|
5191
|
-
var SKILLPACK_HOME =
|
|
5192
|
-
var LEGACY_REGISTRY_FILE =
|
|
5193
|
-
var REGISTRY_DIR =
|
|
5394
|
+
import path14 from "path";
|
|
5395
|
+
var SKILLPACK_HOME = path14.join(os.homedir(), ".skillpack");
|
|
5396
|
+
var LEGACY_REGISTRY_FILE = path14.join(SKILLPACK_HOME, "registry.json");
|
|
5397
|
+
var REGISTRY_DIR = path14.join(SKILLPACK_HOME, "registry.d");
|
|
5194
5398
|
var migrationChecked = false;
|
|
5195
5399
|
function ensureHomeDir() {
|
|
5196
|
-
if (!
|
|
5197
|
-
|
|
5400
|
+
if (!fs13.existsSync(SKILLPACK_HOME)) {
|
|
5401
|
+
fs13.mkdirSync(SKILLPACK_HOME, { recursive: true });
|
|
5198
5402
|
}
|
|
5199
5403
|
}
|
|
5200
5404
|
function ensureRegistryDir() {
|
|
5201
5405
|
ensureHomeDir();
|
|
5202
|
-
if (!
|
|
5203
|
-
|
|
5406
|
+
if (!fs13.existsSync(REGISTRY_DIR)) {
|
|
5407
|
+
fs13.mkdirSync(REGISTRY_DIR, { recursive: true });
|
|
5204
5408
|
}
|
|
5205
5409
|
}
|
|
5206
5410
|
function canonicalizeDir(dir) {
|
|
5207
|
-
const resolved =
|
|
5411
|
+
const resolved = path14.resolve(dir);
|
|
5208
5412
|
try {
|
|
5209
|
-
return
|
|
5413
|
+
return fs13.realpathSync(resolved);
|
|
5210
5414
|
} catch {
|
|
5211
5415
|
return resolved;
|
|
5212
5416
|
}
|
|
@@ -5215,7 +5419,7 @@ function hashDir(dir) {
|
|
|
5215
5419
|
return crypto.createHash("md5").update(canonicalizeDir(dir)).digest("hex");
|
|
5216
5420
|
}
|
|
5217
5421
|
function getEntryPathForCanonicalDir(dir) {
|
|
5218
|
-
return
|
|
5422
|
+
return path14.join(REGISTRY_DIR, `${hashDir(dir)}.json`);
|
|
5219
5423
|
}
|
|
5220
5424
|
function getEntryPath(dir) {
|
|
5221
5425
|
ensureRegistryReady();
|
|
@@ -5223,11 +5427,11 @@ function getEntryPath(dir) {
|
|
|
5223
5427
|
}
|
|
5224
5428
|
function listEntryFiles() {
|
|
5225
5429
|
ensureRegistryReady();
|
|
5226
|
-
return
|
|
5430
|
+
return fs13.readdirSync(REGISTRY_DIR).filter((file) => file.endsWith(".json")).sort().map((file) => path14.join(REGISTRY_DIR, file));
|
|
5227
5431
|
}
|
|
5228
5432
|
function readEntryFile(filePath) {
|
|
5229
5433
|
try {
|
|
5230
|
-
const raw =
|
|
5434
|
+
const raw = fs13.readFileSync(filePath, "utf-8");
|
|
5231
5435
|
const data = JSON.parse(raw);
|
|
5232
5436
|
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") {
|
|
5233
5437
|
return null;
|
|
@@ -5260,8 +5464,8 @@ function writeEntryFile(entry) {
|
|
|
5260
5464
|
};
|
|
5261
5465
|
const entryPath = getEntryPathForCanonicalDir(normalized.dir);
|
|
5262
5466
|
const tmpPath = createTmpPath(entryPath);
|
|
5263
|
-
|
|
5264
|
-
|
|
5467
|
+
fs13.writeFileSync(tmpPath, JSON.stringify(normalized, null, 2), "utf-8");
|
|
5468
|
+
fs13.renameSync(tmpPath, entryPath);
|
|
5265
5469
|
}
|
|
5266
5470
|
function migrateLegacyRegistryIfNeeded() {
|
|
5267
5471
|
if (migrationChecked) {
|
|
@@ -5269,14 +5473,14 @@ function migrateLegacyRegistryIfNeeded() {
|
|
|
5269
5473
|
}
|
|
5270
5474
|
migrationChecked = true;
|
|
5271
5475
|
ensureRegistryDir();
|
|
5272
|
-
if (!
|
|
5476
|
+
if (!fs13.existsSync(LEGACY_REGISTRY_FILE)) {
|
|
5273
5477
|
return;
|
|
5274
5478
|
}
|
|
5275
5479
|
if (listEntryFiles().length > 0) {
|
|
5276
5480
|
return;
|
|
5277
5481
|
}
|
|
5278
5482
|
try {
|
|
5279
|
-
const raw =
|
|
5483
|
+
const raw = fs13.readFileSync(LEGACY_REGISTRY_FILE, "utf-8");
|
|
5280
5484
|
const data = JSON.parse(raw);
|
|
5281
5485
|
const packs = Array.isArray(data?.packs) ? data.packs : [];
|
|
5282
5486
|
for (const pack of packs) {
|
|
@@ -5289,7 +5493,7 @@ function migrateLegacyRegistryIfNeeded() {
|
|
|
5289
5493
|
} catch {
|
|
5290
5494
|
}
|
|
5291
5495
|
}
|
|
5292
|
-
|
|
5496
|
+
fs13.renameSync(LEGACY_REGISTRY_FILE, `${LEGACY_REGISTRY_FILE}.legacy`);
|
|
5293
5497
|
} catch (err) {
|
|
5294
5498
|
console.warn(" [Registry] Failed to migrate legacy registry.json:", err);
|
|
5295
5499
|
}
|
|
@@ -5342,7 +5546,7 @@ function deregister(dir, pid) {
|
|
|
5342
5546
|
}
|
|
5343
5547
|
|
|
5344
5548
|
// src/runtime/server.ts
|
|
5345
|
-
var __dirname =
|
|
5549
|
+
var __dirname = path17.dirname(fileURLToPath2(import.meta.url));
|
|
5346
5550
|
async function startServer(options) {
|
|
5347
5551
|
const {
|
|
5348
5552
|
rootDir,
|
|
@@ -5358,8 +5562,8 @@ async function startServer(options) {
|
|
|
5358
5562
|
const baseUrl = dataConfig.baseUrl?.trim() || void 0;
|
|
5359
5563
|
const modelId = dataConfig.modelId?.trim() || (SUPPORTED_PROVIDERS[provider]?.defaultModelId ?? SUPPORTED_PROVIDERS.openai.defaultModelId);
|
|
5360
5564
|
const apiProtocol = dataConfig.apiProtocol;
|
|
5361
|
-
const packageRoot =
|
|
5362
|
-
const webDir =
|
|
5565
|
+
const packageRoot = path17.resolve(__dirname, "..");
|
|
5566
|
+
const webDir = fs17.existsSync(path17.join(rootDir, "web")) ? path17.join(rootDir, "web") : path17.join(packageRoot, "web");
|
|
5363
5567
|
const app = express();
|
|
5364
5568
|
app.use(express.json());
|
|
5365
5569
|
app.use(express.static(webDir));
|
|
@@ -5377,13 +5581,6 @@ async function startServer(options) {
|
|
|
5377
5581
|
});
|
|
5378
5582
|
});
|
|
5379
5583
|
const lifecycle = new Lifecycle(server);
|
|
5380
|
-
const resultStore = new ResultStore(rootDir);
|
|
5381
|
-
const artifactSnapshotService = new ArtifactSnapshotService(rootDir);
|
|
5382
|
-
const artifactPersistenceService = new ArtifactPersistenceService(
|
|
5383
|
-
artifactSnapshotService,
|
|
5384
|
-
resultStore
|
|
5385
|
-
);
|
|
5386
|
-
const resultsQueryService = new ResultsQueryService(resultStore);
|
|
5387
5584
|
const agent = new PackAgent({
|
|
5388
5585
|
apiKey,
|
|
5389
5586
|
rootDir,
|
|
@@ -5391,14 +5588,16 @@ async function startServer(options) {
|
|
|
5391
5588
|
modelId,
|
|
5392
5589
|
baseUrl,
|
|
5393
5590
|
apiProtocol,
|
|
5394
|
-
lifecycleHandler: lifecycle
|
|
5395
|
-
artifactPersistenceService
|
|
5591
|
+
lifecycleHandler: lifecycle
|
|
5396
5592
|
});
|
|
5397
5593
|
const adapters = [];
|
|
5398
5594
|
const adapterMap = /* @__PURE__ */ new Map();
|
|
5399
5595
|
const hasIpcChannel = typeof process.send === "function";
|
|
5400
5596
|
const ipcAdapter = new IpcAdapter();
|
|
5401
5597
|
const webEnabled = runtimeMode === "standalone";
|
|
5598
|
+
console.log(
|
|
5599
|
+
`[Runtime] IPC channel ${hasIpcChannel ? "available" : "not available"} (mode=${runtimeMode})`
|
|
5600
|
+
);
|
|
5402
5601
|
if (hasIpcChannel) {
|
|
5403
5602
|
await ipcAdapter.start({
|
|
5404
5603
|
agent,
|
|
@@ -5406,8 +5605,7 @@ async function startServer(options) {
|
|
|
5406
5605
|
app,
|
|
5407
5606
|
rootDir,
|
|
5408
5607
|
lifecycle,
|
|
5409
|
-
adapterMap
|
|
5410
|
-
resultsQueryService
|
|
5608
|
+
adapterMap
|
|
5411
5609
|
});
|
|
5412
5610
|
adapters.push(ipcAdapter);
|
|
5413
5611
|
adapterMap.set(ipcAdapter.name, ipcAdapter);
|
|
@@ -5422,8 +5620,7 @@ async function startServer(options) {
|
|
|
5422
5620
|
rootDir,
|
|
5423
5621
|
lifecycle,
|
|
5424
5622
|
adapterMap,
|
|
5425
|
-
ipcBroadcaster
|
|
5426
|
-
resultsQueryService
|
|
5623
|
+
ipcBroadcaster
|
|
5427
5624
|
});
|
|
5428
5625
|
adapters.push(webAdapter);
|
|
5429
5626
|
adapterMap.set(webAdapter.name, webAdapter);
|
|
@@ -5441,8 +5638,7 @@ async function startServer(options) {
|
|
|
5441
5638
|
rootDir,
|
|
5442
5639
|
lifecycle,
|
|
5443
5640
|
adapterMap,
|
|
5444
|
-
ipcBroadcaster
|
|
5445
|
-
resultsQueryService
|
|
5641
|
+
ipcBroadcaster
|
|
5446
5642
|
});
|
|
5447
5643
|
adapters.push(telegramAdapter);
|
|
5448
5644
|
adapterMap.set(telegramAdapter.name, telegramAdapter);
|
|
@@ -5470,8 +5666,7 @@ async function startServer(options) {
|
|
|
5470
5666
|
rootDir,
|
|
5471
5667
|
lifecycle,
|
|
5472
5668
|
adapterMap,
|
|
5473
|
-
ipcBroadcaster
|
|
5474
|
-
resultsQueryService
|
|
5669
|
+
ipcBroadcaster
|
|
5475
5670
|
});
|
|
5476
5671
|
adapters.push(slackAdapter);
|
|
5477
5672
|
adapterMap.set(slackAdapter.name, slackAdapter);
|
|
@@ -5480,6 +5675,35 @@ async function startServer(options) {
|
|
|
5480
5675
|
}
|
|
5481
5676
|
}
|
|
5482
5677
|
}
|
|
5678
|
+
const feishuConfig = dataConfig.adapters?.feishu;
|
|
5679
|
+
if (feishuConfig?.appId || feishuConfig?.appSecret) {
|
|
5680
|
+
if (!feishuConfig.appId || !feishuConfig.appSecret) {
|
|
5681
|
+
console.warn(
|
|
5682
|
+
"[Feishu] Skipped: both adapters.feishu.appId and adapters.feishu.appSecret are required."
|
|
5683
|
+
);
|
|
5684
|
+
} else {
|
|
5685
|
+
try {
|
|
5686
|
+
const { FeishuAdapter: FeishuAdapter2 } = await Promise.resolve().then(() => (init_feishu(), feishu_exports));
|
|
5687
|
+
const feishuAdapter = new FeishuAdapter2({
|
|
5688
|
+
appId: feishuConfig.appId,
|
|
5689
|
+
appSecret: feishuConfig.appSecret
|
|
5690
|
+
});
|
|
5691
|
+
await feishuAdapter.start({
|
|
5692
|
+
agent,
|
|
5693
|
+
server,
|
|
5694
|
+
app,
|
|
5695
|
+
rootDir,
|
|
5696
|
+
lifecycle,
|
|
5697
|
+
adapterMap,
|
|
5698
|
+
ipcBroadcaster
|
|
5699
|
+
});
|
|
5700
|
+
adapters.push(feishuAdapter);
|
|
5701
|
+
adapterMap.set(feishuAdapter.name, feishuAdapter);
|
|
5702
|
+
} catch (err) {
|
|
5703
|
+
console.error("[Feishu] Failed to start:", err);
|
|
5704
|
+
}
|
|
5705
|
+
}
|
|
5706
|
+
}
|
|
5483
5707
|
const { isMessageSender: isMessageSender2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
5484
5708
|
const notifyFn = async (adapterName, channelId, text) => {
|
|
5485
5709
|
const adapter = adapterMap.get(adapterName);
|
|
@@ -5503,7 +5727,7 @@ async function startServer(options) {
|
|
|
5503
5727
|
lifecycle,
|
|
5504
5728
|
notify: notifyFn,
|
|
5505
5729
|
adapterMap,
|
|
5506
|
-
|
|
5730
|
+
ipcBroadcaster
|
|
5507
5731
|
});
|
|
5508
5732
|
adapters.push(schedulerAdapter);
|
|
5509
5733
|
adapterMap.set(schedulerAdapter.name, schedulerAdapter);
|
|
@@ -5591,23 +5815,23 @@ function findMissingSkills(workDir, config) {
|
|
|
5591
5815
|
});
|
|
5592
5816
|
}
|
|
5593
5817
|
function copyStartTemplates2(workDir) {
|
|
5594
|
-
const templateDir =
|
|
5818
|
+
const templateDir = path18.resolve(
|
|
5595
5819
|
new URL("../templates", import.meta.url).pathname
|
|
5596
5820
|
);
|
|
5597
5821
|
for (const file of ["start.sh", "start.bat"]) {
|
|
5598
|
-
const src =
|
|
5599
|
-
const dest =
|
|
5600
|
-
if (
|
|
5601
|
-
|
|
5822
|
+
const src = path18.join(templateDir, file);
|
|
5823
|
+
const dest = path18.join(workDir, file);
|
|
5824
|
+
if (fs18.existsSync(src)) {
|
|
5825
|
+
fs18.copyFileSync(src, dest);
|
|
5602
5826
|
if (file === "start.sh") {
|
|
5603
|
-
|
|
5827
|
+
fs18.chmodSync(dest, 493);
|
|
5604
5828
|
}
|
|
5605
5829
|
}
|
|
5606
5830
|
}
|
|
5607
5831
|
}
|
|
5608
5832
|
async function runCommand(directory) {
|
|
5609
|
-
const workDir = directory ?
|
|
5610
|
-
|
|
5833
|
+
const workDir = directory ? path18.resolve(directory) : process.cwd();
|
|
5834
|
+
fs18.mkdirSync(workDir, { recursive: true });
|
|
5611
5835
|
if (!configExists(workDir)) {
|
|
5612
5836
|
console.log(chalk4.blue("\n No skillpack.json found. Let's set one up.\n"));
|
|
5613
5837
|
const { name, description } = await inquirer2.prompt([
|
|
@@ -5651,14 +5875,14 @@ async function runCommand(directory) {
|
|
|
5651
5875
|
}
|
|
5652
5876
|
|
|
5653
5877
|
// src/cli.ts
|
|
5654
|
-
import
|
|
5655
|
-
import
|
|
5878
|
+
import fs19 from "fs";
|
|
5879
|
+
import path19 from "path";
|
|
5656
5880
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5657
5881
|
var packageJson = JSON.parse(
|
|
5658
|
-
|
|
5882
|
+
fs19.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
|
|
5659
5883
|
);
|
|
5660
5884
|
var program = new Command();
|
|
5661
|
-
var cliFilePath =
|
|
5885
|
+
var cliFilePath = path19.resolve(fileURLToPath3(import.meta.url));
|
|
5662
5886
|
program.name("skillpack").description("Assemble, package, and run Agent Skills packs").version(packageJson.version);
|
|
5663
5887
|
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) => {
|
|
5664
5888
|
await createCommand(directory, options);
|
|
@@ -5679,7 +5903,7 @@ program.command("zip").description("Package the current pack as a zip file (skil
|
|
|
5679
5903
|
function normalizeUserArgs(argv) {
|
|
5680
5904
|
if (argv.length === 0) return argv;
|
|
5681
5905
|
const firstArg = argv[0];
|
|
5682
|
-
if (firstArg &&
|
|
5906
|
+
if (firstArg && path19.resolve(firstArg) === cliFilePath) {
|
|
5683
5907
|
return argv.slice(1);
|
|
5684
5908
|
}
|
|
5685
5909
|
return argv;
|