@cremini/skillpack 1.2.11 → 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 +482 -96
- package/package.json +3 -2
- 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
|
}
|
|
@@ -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,19 +1732,19 @@ 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;
|
|
@@ -1493,8 +1798,8 @@ var init_scheduler = __esm({
|
|
|
1493
1798
|
registerJob(jobConfig) {
|
|
1494
1799
|
const normalizedConfig = normalizeScheduledJobConfig(jobConfig);
|
|
1495
1800
|
const normalizedCron = normalizeJobCron(normalizedConfig.cron);
|
|
1496
|
-
if (!
|
|
1497
|
-
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`;
|
|
1498
1803
|
console.error(msg);
|
|
1499
1804
|
return { registered: false, message: msg };
|
|
1500
1805
|
}
|
|
@@ -1510,7 +1815,7 @@ var init_scheduler = __esm({
|
|
|
1510
1815
|
return { registered: false, message: msg };
|
|
1511
1816
|
}
|
|
1512
1817
|
}
|
|
1513
|
-
this.removeFromMap(normalizedConfig.
|
|
1818
|
+
this.removeFromMap(normalizedConfig.id);
|
|
1514
1819
|
let task = null;
|
|
1515
1820
|
if (normalizedCron && normalizedConfig.enabled !== false) {
|
|
1516
1821
|
task = this.createCronTask(normalizedConfig);
|
|
@@ -1526,7 +1831,7 @@ var init_scheduler = __esm({
|
|
|
1526
1831
|
`[Scheduler] Job "${normalizedConfig.name}" registered as one-time (manual trigger only)`
|
|
1527
1832
|
);
|
|
1528
1833
|
}
|
|
1529
|
-
this.jobs.set(normalizedConfig.
|
|
1834
|
+
this.jobs.set(normalizedConfig.id, {
|
|
1530
1835
|
config: normalizedConfig,
|
|
1531
1836
|
task,
|
|
1532
1837
|
running: false,
|
|
@@ -1542,8 +1847,8 @@ var init_scheduler = __esm({
|
|
|
1542
1847
|
* Returns { text, notifyFailed } so callers can produce accurate status.
|
|
1543
1848
|
*/
|
|
1544
1849
|
async runJob(jobConfig) {
|
|
1545
|
-
const channelId = `scheduler-${jobConfig.
|
|
1546
|
-
const job = this.jobs.get(jobConfig.
|
|
1850
|
+
const channelId = `scheduler-${jobConfig.id}`;
|
|
1851
|
+
const job = this.jobs.get(jobConfig.id);
|
|
1547
1852
|
if (job?.running) {
|
|
1548
1853
|
console.warn(
|
|
1549
1854
|
`[Scheduler] Job "${jobConfig.name}" is already running, skipping this trigger`
|
|
@@ -1659,7 +1964,7 @@ var init_scheduler = __esm({
|
|
|
1659
1964
|
* Add a new job, persist to job.json.
|
|
1660
1965
|
*/
|
|
1661
1966
|
addJob(jobConfig) {
|
|
1662
|
-
if (this.jobs.has(jobConfig.
|
|
1967
|
+
if (this.jobs.has(jobConfig.id)) {
|
|
1663
1968
|
return {
|
|
1664
1969
|
success: false,
|
|
1665
1970
|
message: `Job "${jobConfig.name}" already exists. Remove it first.`
|
|
@@ -1680,21 +1985,23 @@ var init_scheduler = __esm({
|
|
|
1680
1985
|
/**
|
|
1681
1986
|
* Remove a job and persist to job.json.
|
|
1682
1987
|
*/
|
|
1683
|
-
removeJob(
|
|
1684
|
-
|
|
1685
|
-
|
|
1988
|
+
removeJob(id) {
|
|
1989
|
+
const job = this.jobs.get(id);
|
|
1990
|
+
if (!job) {
|
|
1991
|
+
return { success: false, message: `Job "${id}" not found.` };
|
|
1686
1992
|
}
|
|
1687
|
-
this.removeFromMap(
|
|
1993
|
+
this.removeFromMap(id);
|
|
1688
1994
|
this.persistJobs();
|
|
1689
|
-
return { success: true, message: `Job "${name}" removed.` };
|
|
1995
|
+
return { success: true, message: `Job "${job.config.name}" removed.` };
|
|
1690
1996
|
}
|
|
1691
|
-
updateJob(
|
|
1692
|
-
const job = this.jobs.get(
|
|
1997
|
+
updateJob(id, updates) {
|
|
1998
|
+
const job = this.jobs.get(id);
|
|
1693
1999
|
if (!job) {
|
|
1694
|
-
return { success: false, message: `Job "${
|
|
2000
|
+
return { success: false, message: `Job "${id}" not found.` };
|
|
1695
2001
|
}
|
|
1696
2002
|
const nextConfig = {
|
|
1697
|
-
|
|
2003
|
+
id: job.config.id,
|
|
2004
|
+
name: job.config.name,
|
|
1698
2005
|
cron: updates.cron,
|
|
1699
2006
|
prompt: updates.prompt,
|
|
1700
2007
|
notify: updates.notify,
|
|
@@ -1708,21 +2015,21 @@ var init_scheduler = __esm({
|
|
|
1708
2015
|
this.persistJobs();
|
|
1709
2016
|
return {
|
|
1710
2017
|
success: true,
|
|
1711
|
-
message: `Job "${name}" updated.`
|
|
2018
|
+
message: `Job "${job.config.name}" updated.`
|
|
1712
2019
|
};
|
|
1713
2020
|
}
|
|
1714
2021
|
/**
|
|
1715
2022
|
* Enable or disable a job and persist.
|
|
1716
2023
|
*/
|
|
1717
|
-
setEnabled(
|
|
1718
|
-
const job = this.jobs.get(
|
|
2024
|
+
setEnabled(id, enabled) {
|
|
2025
|
+
const job = this.jobs.get(id);
|
|
1719
2026
|
if (!job) {
|
|
1720
|
-
return { success: false, message: `Job "${
|
|
2027
|
+
return { success: false, message: `Job "${id}" not found.` };
|
|
1721
2028
|
}
|
|
1722
2029
|
if (!isRecurringJob(job.config)) {
|
|
1723
2030
|
return {
|
|
1724
2031
|
success: false,
|
|
1725
|
-
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.`
|
|
1726
2033
|
};
|
|
1727
2034
|
}
|
|
1728
2035
|
job.config.enabled = enabled;
|
|
@@ -1736,33 +2043,33 @@ var init_scheduler = __esm({
|
|
|
1736
2043
|
this.persistJobs();
|
|
1737
2044
|
return {
|
|
1738
2045
|
success: true,
|
|
1739
|
-
message: `Job "${name}" ${enabled ? "enabled" : "disabled"}.`
|
|
2046
|
+
message: `Job "${job.config.name}" ${enabled ? "enabled" : "disabled"}.`
|
|
1740
2047
|
};
|
|
1741
2048
|
}
|
|
1742
2049
|
/**
|
|
1743
2050
|
* Manually trigger a job (runs immediately, ignoring cron schedule).
|
|
1744
2051
|
*/
|
|
1745
|
-
async triggerJob(
|
|
1746
|
-
const job = this.jobs.get(
|
|
2052
|
+
async triggerJob(id) {
|
|
2053
|
+
const job = this.jobs.get(id);
|
|
1747
2054
|
if (!job) {
|
|
1748
|
-
return { success: false, message: `Job "${
|
|
2055
|
+
return { success: false, message: `Job "${id}" not found.` };
|
|
1749
2056
|
}
|
|
1750
2057
|
const { text, notifyFailed } = await this.runJob(job.config);
|
|
1751
2058
|
if (!text) {
|
|
1752
2059
|
return {
|
|
1753
2060
|
success: true,
|
|
1754
|
-
message: `Job "${name}" triggered but produced no output.`
|
|
2061
|
+
message: `Job "${job.config.name}" triggered but produced no output.`
|
|
1755
2062
|
};
|
|
1756
2063
|
}
|
|
1757
2064
|
if (notifyFailed) {
|
|
1758
2065
|
return {
|
|
1759
2066
|
success: true,
|
|
1760
|
-
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.`
|
|
1761
2068
|
};
|
|
1762
2069
|
}
|
|
1763
2070
|
return {
|
|
1764
2071
|
success: true,
|
|
1765
|
-
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}.`
|
|
1766
2073
|
};
|
|
1767
2074
|
}
|
|
1768
2075
|
/**
|
|
@@ -1772,6 +2079,7 @@ var init_scheduler = __esm({
|
|
|
1772
2079
|
const result = [];
|
|
1773
2080
|
for (const [, job] of this.jobs) {
|
|
1774
2081
|
result.push({
|
|
2082
|
+
id: job.config.id,
|
|
1775
2083
|
name: job.config.name,
|
|
1776
2084
|
...job.config.cron ? { cron: job.config.cron } : {},
|
|
1777
2085
|
prompt: job.config.prompt,
|
|
@@ -1792,11 +2100,11 @@ var init_scheduler = __esm({
|
|
|
1792
2100
|
/**
|
|
1793
2101
|
* Stop the cron task and remove a job from the map (does NOT persist).
|
|
1794
2102
|
*/
|
|
1795
|
-
removeFromMap(
|
|
1796
|
-
const existing = this.jobs.get(
|
|
2103
|
+
removeFromMap(id) {
|
|
2104
|
+
const existing = this.jobs.get(id);
|
|
1797
2105
|
if (existing) {
|
|
1798
2106
|
existing.task?.stop();
|
|
1799
|
-
this.jobs.delete(
|
|
2107
|
+
this.jobs.delete(id);
|
|
1800
2108
|
}
|
|
1801
2109
|
}
|
|
1802
2110
|
/**
|
|
@@ -2515,15 +2823,15 @@ async function interactiveCreate(workDir) {
|
|
|
2515
2823
|
}
|
|
2516
2824
|
|
|
2517
2825
|
// src/commands/run.ts
|
|
2518
|
-
import
|
|
2519
|
-
import
|
|
2826
|
+
import path18 from "path";
|
|
2827
|
+
import fs18 from "fs";
|
|
2520
2828
|
import inquirer2 from "inquirer";
|
|
2521
2829
|
import chalk4 from "chalk";
|
|
2522
2830
|
|
|
2523
2831
|
// src/runtime/server.ts
|
|
2524
2832
|
import express from "express";
|
|
2525
|
-
import
|
|
2526
|
-
import
|
|
2833
|
+
import path17 from "path";
|
|
2834
|
+
import fs17 from "fs";
|
|
2527
2835
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2528
2836
|
import { createServer } from "http";
|
|
2529
2837
|
import { exec } from "child_process";
|
|
@@ -2531,7 +2839,7 @@ import { exec } from "child_process";
|
|
|
2531
2839
|
// src/runtime/agent.ts
|
|
2532
2840
|
import path11 from "path";
|
|
2533
2841
|
import fs10 from "fs";
|
|
2534
|
-
import { randomUUID as
|
|
2842
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
2535
2843
|
import { fileURLToPath } from "url";
|
|
2536
2844
|
import {
|
|
2537
2845
|
AuthStorage,
|
|
@@ -3085,6 +3393,7 @@ function createSendFileTool(fileOutputCallbackRef) {
|
|
|
3085
3393
|
}
|
|
3086
3394
|
|
|
3087
3395
|
// src/runtime/tools/manage-schedule-tool.ts
|
|
3396
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
3088
3397
|
import { Type as Type2 } from "@sinclair/typebox";
|
|
3089
3398
|
var ManageScheduleParams = Type2.Object({
|
|
3090
3399
|
action: Type2.Union(
|
|
@@ -3100,7 +3409,7 @@ var ManageScheduleParams = Type2.Object({
|
|
|
3100
3409
|
),
|
|
3101
3410
|
name: Type2.Optional(
|
|
3102
3411
|
Type2.String({
|
|
3103
|
-
description: "
|
|
3412
|
+
description: "Display name for the scheduled task. Required for add/remove/trigger/enable/disable."
|
|
3104
3413
|
})
|
|
3105
3414
|
),
|
|
3106
3415
|
cron: Type2.Optional(
|
|
@@ -3120,7 +3429,7 @@ var ManageScheduleParams = Type2.Object({
|
|
|
3120
3429
|
),
|
|
3121
3430
|
notifyAdapter: Type2.Optional(
|
|
3122
3431
|
Type2.String({
|
|
3123
|
-
description: "Optional target adapter for notifications. If omitted, the current chat is used when supported (Telegram, Slack, or Web)."
|
|
3432
|
+
description: "Optional target adapter for notifications. If omitted, the current chat is used when supported (Telegram, Slack, Feishu, or Web)."
|
|
3124
3433
|
})
|
|
3125
3434
|
),
|
|
3126
3435
|
notifyChannelId: Type2.Optional(
|
|
@@ -3132,6 +3441,27 @@ var ManageScheduleParams = Type2.Object({
|
|
|
3132
3441
|
function textResult(text) {
|
|
3133
3442
|
return { content: [{ type: "text", text }], details: void 0 };
|
|
3134
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
|
+
}
|
|
3135
3465
|
function getDefaultNotifyTarget(adapter, channelId) {
|
|
3136
3466
|
if (adapter === "telegram" && channelId.startsWith("telegram-")) {
|
|
3137
3467
|
return { adapter: "telegram", channelId };
|
|
@@ -3139,12 +3469,15 @@ function getDefaultNotifyTarget(adapter, channelId) {
|
|
|
3139
3469
|
if (adapter === "slack" && channelId.startsWith("slack-")) {
|
|
3140
3470
|
return { adapter: "slack", channelId };
|
|
3141
3471
|
}
|
|
3472
|
+
if (adapter === "feishu" && channelId.startsWith("feishu-")) {
|
|
3473
|
+
return { adapter: "feishu", channelId };
|
|
3474
|
+
}
|
|
3142
3475
|
if (adapter === "web") {
|
|
3143
3476
|
return { adapter: "web", channelId };
|
|
3144
3477
|
}
|
|
3145
3478
|
return null;
|
|
3146
3479
|
}
|
|
3147
|
-
function createManageScheduleTool(schedulerRef, adapter, channelId) {
|
|
3480
|
+
function createManageScheduleTool(schedulerRef, adapter, channelId, generateJobId = randomUUID3) {
|
|
3148
3481
|
return {
|
|
3149
3482
|
name: "manage_scheduled_task",
|
|
3150
3483
|
label: "Manage Scheduled Task",
|
|
@@ -3152,11 +3485,11 @@ function createManageScheduleTool(schedulerRef, adapter, channelId) {
|
|
|
3152
3485
|
"Manage scheduled tasks (cron jobs) that automatically execute prompts and push results to IM channels.",
|
|
3153
3486
|
"",
|
|
3154
3487
|
"Actions:",
|
|
3155
|
-
"- 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.",
|
|
3156
3489
|
"- list: List all scheduled tasks with their status.",
|
|
3157
|
-
"- remove: Remove a scheduled task by name.",
|
|
3158
|
-
"- trigger: Manually trigger a scheduled task by name (runs immediately).",
|
|
3159
|
-
"- 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.",
|
|
3160
3493
|
"- disable: Disable a scheduled task without removing it.",
|
|
3161
3494
|
"",
|
|
3162
3495
|
"Cron expression format: '* * * * *' (minute hour day month weekday)",
|
|
@@ -3208,6 +3541,7 @@ ${lines.join("\n")}`
|
|
|
3208
3541
|
);
|
|
3209
3542
|
}
|
|
3210
3543
|
const jobConfig = {
|
|
3544
|
+
id: generateJobId(),
|
|
3211
3545
|
name: params.name,
|
|
3212
3546
|
cron: params.cron,
|
|
3213
3547
|
prompt: params.prompt,
|
|
@@ -3224,7 +3558,11 @@ ${lines.join("\n")}`
|
|
|
3224
3558
|
"Error: 'name' is required for removing a task."
|
|
3225
3559
|
);
|
|
3226
3560
|
}
|
|
3227
|
-
const
|
|
3561
|
+
const resolved = resolveJobId(scheduler, params.name);
|
|
3562
|
+
if (!resolved.ok) {
|
|
3563
|
+
return textResult(resolved.message);
|
|
3564
|
+
}
|
|
3565
|
+
const result = scheduler.removeJob(resolved.jobId);
|
|
3228
3566
|
return textResult(result.message);
|
|
3229
3567
|
}
|
|
3230
3568
|
case "trigger": {
|
|
@@ -3233,7 +3571,11 @@ ${lines.join("\n")}`
|
|
|
3233
3571
|
"Error: 'name' is required for triggering a task."
|
|
3234
3572
|
);
|
|
3235
3573
|
}
|
|
3236
|
-
const
|
|
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);
|
|
3237
3579
|
return textResult(result.message);
|
|
3238
3580
|
}
|
|
3239
3581
|
case "enable": {
|
|
@@ -3242,7 +3584,11 @@ ${lines.join("\n")}`
|
|
|
3242
3584
|
"Error: 'name' is required for enabling a task."
|
|
3243
3585
|
);
|
|
3244
3586
|
}
|
|
3245
|
-
const
|
|
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);
|
|
3246
3592
|
return textResult(result.message);
|
|
3247
3593
|
}
|
|
3248
3594
|
case "disable": {
|
|
@@ -3251,7 +3597,11 @@ ${lines.join("\n")}`
|
|
|
3251
3597
|
"Error: 'name' is required for disabling a task."
|
|
3252
3598
|
);
|
|
3253
3599
|
}
|
|
3254
|
-
const
|
|
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);
|
|
3255
3605
|
return textResult(result.message);
|
|
3256
3606
|
}
|
|
3257
3607
|
default:
|
|
@@ -3325,6 +3675,7 @@ function readInstalledSkills(configPath) {
|
|
|
3325
3675
|
}
|
|
3326
3676
|
|
|
3327
3677
|
// src/runtime/agent.ts
|
|
3678
|
+
init_types();
|
|
3328
3679
|
var DEBUG = true;
|
|
3329
3680
|
var log = (...args) => DEBUG && console.log(...args);
|
|
3330
3681
|
var write = (data) => DEBUG && process.stdout.write(data);
|
|
@@ -3464,9 +3815,8 @@ function getAssistantDiagnostics(message) {
|
|
|
3464
3815
|
return { stopReason, errorMessage, hasText: text.length > 0, toolCalls };
|
|
3465
3816
|
}
|
|
3466
3817
|
function getLifecycleTrigger(channelId) {
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
return "web";
|
|
3818
|
+
const platform = detectPlatformFromChannelId(channelId);
|
|
3819
|
+
return platform === "scheduler" ? "web" : platform;
|
|
3470
3820
|
}
|
|
3471
3821
|
var PackAgent = class {
|
|
3472
3822
|
options;
|
|
@@ -3664,7 +4014,7 @@ var PackAgent = class {
|
|
|
3664
4014
|
const run = async () => {
|
|
3665
4015
|
cs.running = true;
|
|
3666
4016
|
let turnHadVisibleOutput = false;
|
|
3667
|
-
const runId =
|
|
4017
|
+
const runId = randomUUID4();
|
|
3668
4018
|
let unsubscribe = () => void 0;
|
|
3669
4019
|
try {
|
|
3670
4020
|
cs.fileOutputCallbackRef.current = (event) => {
|
|
@@ -3878,12 +4228,14 @@ ${text}`;
|
|
|
3878
4228
|
};
|
|
3879
4229
|
|
|
3880
4230
|
// src/runtime/adapters/web.ts
|
|
4231
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
3881
4232
|
import fs12 from "fs";
|
|
3882
4233
|
import path13 from "path";
|
|
3883
4234
|
import { WebSocketServer } from "ws";
|
|
3884
4235
|
init_commands();
|
|
3885
4236
|
|
|
3886
4237
|
// src/runtime/services/conversation.ts
|
|
4238
|
+
init_types();
|
|
3887
4239
|
import fs11 from "fs";
|
|
3888
4240
|
import path12 from "path";
|
|
3889
4241
|
import {
|
|
@@ -4267,10 +4619,7 @@ var ConversationService = class {
|
|
|
4267
4619
|
return parts[parts.length - 1] || filePath;
|
|
4268
4620
|
}
|
|
4269
4621
|
detectPlatform(channelId) {
|
|
4270
|
-
|
|
4271
|
-
if (channelId.startsWith("slack-")) return "slack";
|
|
4272
|
-
if (channelId.startsWith("scheduler-")) return "scheduler";
|
|
4273
|
-
return "web";
|
|
4622
|
+
return detectPlatformFromChannelId(channelId);
|
|
4274
4623
|
}
|
|
4275
4624
|
isLegacyWebConversation(channelId) {
|
|
4276
4625
|
return channelId.startsWith("web-");
|
|
@@ -4298,7 +4647,9 @@ function getRuntimeConfigSignature(config) {
|
|
|
4298
4647
|
apiProtocol: config.apiProtocol || "",
|
|
4299
4648
|
telegramToken: config.adapters?.telegram?.token || "",
|
|
4300
4649
|
slackBotToken: config.adapters?.slack?.botToken || "",
|
|
4301
|
-
slackAppToken: config.adapters?.slack?.appToken || ""
|
|
4650
|
+
slackAppToken: config.adapters?.slack?.appToken || "",
|
|
4651
|
+
feishuAppId: config.adapters?.feishu?.appId || "",
|
|
4652
|
+
feishuAppSecret: config.adapters?.feishu?.appSecret || ""
|
|
4302
4653
|
});
|
|
4303
4654
|
}
|
|
4304
4655
|
function parsePositiveInt(value, fallback) {
|
|
@@ -4509,7 +4860,15 @@ var WebAdapter = class {
|
|
|
4509
4860
|
res.status(503).json({ success: false, message: "Scheduler not available" });
|
|
4510
4861
|
return;
|
|
4511
4862
|
}
|
|
4512
|
-
const {
|
|
4863
|
+
const {
|
|
4864
|
+
id,
|
|
4865
|
+
name,
|
|
4866
|
+
cron: cronExpr,
|
|
4867
|
+
prompt,
|
|
4868
|
+
notify,
|
|
4869
|
+
enabled,
|
|
4870
|
+
timezone
|
|
4871
|
+
} = req.body;
|
|
4513
4872
|
if (!name || !prompt || !notify?.adapter || !notify?.channelId) {
|
|
4514
4873
|
res.status(400).json({
|
|
4515
4874
|
success: false,
|
|
@@ -4518,6 +4877,7 @@ var WebAdapter = class {
|
|
|
4518
4877
|
return;
|
|
4519
4878
|
}
|
|
4520
4879
|
const result = scheduler.addJob({
|
|
4880
|
+
id: typeof id === "string" && id.trim() ? id.trim() : randomUUID5(),
|
|
4521
4881
|
name,
|
|
4522
4882
|
...typeof cronExpr === "string" ? { cron: cronExpr } : {},
|
|
4523
4883
|
prompt,
|
|
@@ -4527,25 +4887,25 @@ var WebAdapter = class {
|
|
|
4527
4887
|
});
|
|
4528
4888
|
res.json(result);
|
|
4529
4889
|
});
|
|
4530
|
-
app.delete("/api/scheduler/jobs/:
|
|
4890
|
+
app.delete("/api/scheduler/jobs/:jobId", (req, res) => {
|
|
4531
4891
|
const scheduler = getScheduler();
|
|
4532
4892
|
if (!scheduler) {
|
|
4533
4893
|
res.status(503).json({ success: false, message: "Scheduler not available" });
|
|
4534
4894
|
return;
|
|
4535
4895
|
}
|
|
4536
|
-
const result = scheduler.removeJob(req.params.
|
|
4896
|
+
const result = scheduler.removeJob(req.params.jobId);
|
|
4537
4897
|
res.json(result);
|
|
4538
4898
|
});
|
|
4539
|
-
app.post("/api/scheduler/jobs/:
|
|
4899
|
+
app.post("/api/scheduler/jobs/:jobId/trigger", async (req, res) => {
|
|
4540
4900
|
const scheduler = getScheduler();
|
|
4541
4901
|
if (!scheduler) {
|
|
4542
4902
|
res.status(503).json({ success: false, message: "Scheduler not available" });
|
|
4543
4903
|
return;
|
|
4544
4904
|
}
|
|
4545
|
-
const result = await scheduler.triggerJob(req.params.
|
|
4905
|
+
const result = await scheduler.triggerJob(req.params.jobId);
|
|
4546
4906
|
res.json(result);
|
|
4547
4907
|
});
|
|
4548
|
-
app.patch("/api/scheduler/jobs/:
|
|
4908
|
+
app.patch("/api/scheduler/jobs/:jobId", (req, res) => {
|
|
4549
4909
|
const scheduler = getScheduler();
|
|
4550
4910
|
if (!scheduler) {
|
|
4551
4911
|
res.status(503).json({ success: false, message: "Scheduler not available" });
|
|
@@ -4556,7 +4916,7 @@ var WebAdapter = class {
|
|
|
4556
4916
|
res.status(400).json({ success: false, message: "Field 'enabled' (boolean) is required" });
|
|
4557
4917
|
return;
|
|
4558
4918
|
}
|
|
4559
|
-
const result = scheduler.setEnabled(req.params.
|
|
4919
|
+
const result = scheduler.setEnabled(req.params.jobId, enabled);
|
|
4560
4920
|
res.json(result);
|
|
4561
4921
|
});
|
|
4562
4922
|
this.wss = new WebSocketServer({ noServer: true });
|
|
@@ -4877,7 +5237,7 @@ var IpcAdapter = class {
|
|
|
4877
5237
|
this.replyError(request.id, "Scheduler adapter is not available");
|
|
4878
5238
|
return;
|
|
4879
5239
|
}
|
|
4880
|
-
const result = scheduler.updateJob(request.
|
|
5240
|
+
const result = scheduler.updateJob(request.jobId, request.updates);
|
|
4881
5241
|
if (!result.success) {
|
|
4882
5242
|
this.replyError(request.id, result.message);
|
|
4883
5243
|
return;
|
|
@@ -4891,7 +5251,7 @@ var IpcAdapter = class {
|
|
|
4891
5251
|
this.replyError(request.id, "Scheduler adapter is not available");
|
|
4892
5252
|
return;
|
|
4893
5253
|
}
|
|
4894
|
-
const result = scheduler.setEnabled(request.
|
|
5254
|
+
const result = scheduler.setEnabled(request.jobId, request.enabled);
|
|
4895
5255
|
if (!result.success) {
|
|
4896
5256
|
this.replyError(request.id, result.message);
|
|
4897
5257
|
return;
|
|
@@ -4905,7 +5265,7 @@ var IpcAdapter = class {
|
|
|
4905
5265
|
this.replyError(request.id, "Scheduler adapter is not available");
|
|
4906
5266
|
return;
|
|
4907
5267
|
}
|
|
4908
|
-
const result = await scheduler.triggerJob(request.
|
|
5268
|
+
const result = await scheduler.triggerJob(request.jobId);
|
|
4909
5269
|
if (!result.success) {
|
|
4910
5270
|
this.replyError(request.id, result.message);
|
|
4911
5271
|
return;
|
|
@@ -4919,7 +5279,7 @@ var IpcAdapter = class {
|
|
|
4919
5279
|
this.replyError(request.id, "Scheduler adapter is not available");
|
|
4920
5280
|
return;
|
|
4921
5281
|
}
|
|
4922
|
-
const result = scheduler.removeJob(request.
|
|
5282
|
+
const result = scheduler.removeJob(request.jobId);
|
|
4923
5283
|
if (!result.success) {
|
|
4924
5284
|
this.replyError(request.id, result.message);
|
|
4925
5285
|
return;
|
|
@@ -4941,10 +5301,7 @@ var IpcAdapter = class {
|
|
|
4941
5301
|
return adapter;
|
|
4942
5302
|
}
|
|
4943
5303
|
detectPlatform(channelId) {
|
|
4944
|
-
|
|
4945
|
-
if (channelId.startsWith("slack-")) return "slack";
|
|
4946
|
-
if (channelId.startsWith("scheduler-")) return "scheduler";
|
|
4947
|
-
return "web";
|
|
5304
|
+
return detectPlatformFromChannelId(channelId);
|
|
4948
5305
|
}
|
|
4949
5306
|
sendIpc(payload) {
|
|
4950
5307
|
if (typeof process.send === "function") {
|
|
@@ -5189,7 +5546,7 @@ function deregister(dir, pid) {
|
|
|
5189
5546
|
}
|
|
5190
5547
|
|
|
5191
5548
|
// src/runtime/server.ts
|
|
5192
|
-
var __dirname =
|
|
5549
|
+
var __dirname = path17.dirname(fileURLToPath2(import.meta.url));
|
|
5193
5550
|
async function startServer(options) {
|
|
5194
5551
|
const {
|
|
5195
5552
|
rootDir,
|
|
@@ -5205,8 +5562,8 @@ async function startServer(options) {
|
|
|
5205
5562
|
const baseUrl = dataConfig.baseUrl?.trim() || void 0;
|
|
5206
5563
|
const modelId = dataConfig.modelId?.trim() || (SUPPORTED_PROVIDERS[provider]?.defaultModelId ?? SUPPORTED_PROVIDERS.openai.defaultModelId);
|
|
5207
5564
|
const apiProtocol = dataConfig.apiProtocol;
|
|
5208
|
-
const packageRoot =
|
|
5209
|
-
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");
|
|
5210
5567
|
const app = express();
|
|
5211
5568
|
app.use(express.json());
|
|
5212
5569
|
app.use(express.static(webDir));
|
|
@@ -5318,6 +5675,35 @@ async function startServer(options) {
|
|
|
5318
5675
|
}
|
|
5319
5676
|
}
|
|
5320
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
|
+
}
|
|
5321
5707
|
const { isMessageSender: isMessageSender2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
5322
5708
|
const notifyFn = async (adapterName, channelId, text) => {
|
|
5323
5709
|
const adapter = adapterMap.get(adapterName);
|
|
@@ -5429,23 +5815,23 @@ function findMissingSkills(workDir, config) {
|
|
|
5429
5815
|
});
|
|
5430
5816
|
}
|
|
5431
5817
|
function copyStartTemplates2(workDir) {
|
|
5432
|
-
const templateDir =
|
|
5818
|
+
const templateDir = path18.resolve(
|
|
5433
5819
|
new URL("../templates", import.meta.url).pathname
|
|
5434
5820
|
);
|
|
5435
5821
|
for (const file of ["start.sh", "start.bat"]) {
|
|
5436
|
-
const src =
|
|
5437
|
-
const dest =
|
|
5438
|
-
if (
|
|
5439
|
-
|
|
5822
|
+
const src = path18.join(templateDir, file);
|
|
5823
|
+
const dest = path18.join(workDir, file);
|
|
5824
|
+
if (fs18.existsSync(src)) {
|
|
5825
|
+
fs18.copyFileSync(src, dest);
|
|
5440
5826
|
if (file === "start.sh") {
|
|
5441
|
-
|
|
5827
|
+
fs18.chmodSync(dest, 493);
|
|
5442
5828
|
}
|
|
5443
5829
|
}
|
|
5444
5830
|
}
|
|
5445
5831
|
}
|
|
5446
5832
|
async function runCommand(directory) {
|
|
5447
|
-
const workDir = directory ?
|
|
5448
|
-
|
|
5833
|
+
const workDir = directory ? path18.resolve(directory) : process.cwd();
|
|
5834
|
+
fs18.mkdirSync(workDir, { recursive: true });
|
|
5449
5835
|
if (!configExists(workDir)) {
|
|
5450
5836
|
console.log(chalk4.blue("\n No skillpack.json found. Let's set one up.\n"));
|
|
5451
5837
|
const { name, description } = await inquirer2.prompt([
|
|
@@ -5489,14 +5875,14 @@ async function runCommand(directory) {
|
|
|
5489
5875
|
}
|
|
5490
5876
|
|
|
5491
5877
|
// src/cli.ts
|
|
5492
|
-
import
|
|
5493
|
-
import
|
|
5878
|
+
import fs19 from "fs";
|
|
5879
|
+
import path19 from "path";
|
|
5494
5880
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5495
5881
|
var packageJson = JSON.parse(
|
|
5496
|
-
|
|
5882
|
+
fs19.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
|
|
5497
5883
|
);
|
|
5498
5884
|
var program = new Command();
|
|
5499
|
-
var cliFilePath =
|
|
5885
|
+
var cliFilePath = path19.resolve(fileURLToPath3(import.meta.url));
|
|
5500
5886
|
program.name("skillpack").description("Assemble, package, and run Agent Skills packs").version(packageJson.version);
|
|
5501
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) => {
|
|
5502
5888
|
await createCommand(directory, options);
|
|
@@ -5517,7 +5903,7 @@ program.command("zip").description("Package the current pack as a zip file (skil
|
|
|
5517
5903
|
function normalizeUserArgs(argv) {
|
|
5518
5904
|
if (argv.length === 0) return argv;
|
|
5519
5905
|
const firstArg = argv[0];
|
|
5520
|
-
if (firstArg &&
|
|
5906
|
+
if (firstArg && path19.resolve(firstArg) === cliFilePath) {
|
|
5521
5907
|
return argv.slice(1);
|
|
5522
5908
|
}
|
|
5523
5909
|
return argv;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cremini/skillpack",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.12",
|
|
4
4
|
"description": "Pack AI Skills into Local Agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"author": "CreminiAI",
|
|
45
45
|
"license": "MIT",
|
|
46
46
|
"dependencies": {
|
|
47
|
+
"@larksuiteoapi/node-sdk": "^1.63.1",
|
|
47
48
|
"@mariozechner/pi-coding-agent": "^0.57.1",
|
|
48
49
|
"@sinclair/typebox": "^0.34.41",
|
|
49
50
|
"@slack/bolt": "^4.6.0",
|
|
@@ -70,4 +71,4 @@
|
|
|
70
71
|
"tsx": "^4.21.0",
|
|
71
72
|
"typescript": "^5.9.3"
|
|
72
73
|
}
|
|
73
|
-
}
|
|
74
|
+
}
|
package/web/index.html
CHANGED
|
@@ -164,6 +164,17 @@
|
|
|
164
164
|
<input type="password" id="chatapps-slack-app-token" placeholder="xapp-..." class="form-input" />
|
|
165
165
|
</div>
|
|
166
166
|
</div>
|
|
167
|
+
<div class="settings-section">
|
|
168
|
+
<h3 class="section-title">Feishu</h3>
|
|
169
|
+
<div class="form-group">
|
|
170
|
+
<label>App ID</label>
|
|
171
|
+
<input type="password" id="chatapps-feishu-app-id" placeholder="cli_a1b2c3d4..." class="form-input" />
|
|
172
|
+
</div>
|
|
173
|
+
<div class="form-group">
|
|
174
|
+
<label>App Secret</label>
|
|
175
|
+
<input type="password" id="chatapps-feishu-app-secret" placeholder="your-feishu-app-secret" class="form-input" />
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
167
178
|
</div>
|
|
168
179
|
</div>
|
|
169
180
|
<div class="settings-modal-footer">
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Chat Apps (IM Bots) Dialog Module
|
|
3
3
|
*
|
|
4
|
-
* 负责 IM Bots(Telegram / Slack
|
|
4
|
+
* 负责 IM Bots(Telegram / Slack / Feishu)配置管理。
|
|
5
5
|
* 独立的 Dialog,从原 SettingDialog 的 IM Bots 部分拆分出来。
|
|
6
6
|
*/
|
|
7
7
|
import { state } from "./config.js";
|
|
@@ -16,8 +16,22 @@ let restartBtn;
|
|
|
16
16
|
let telegramTokenInput;
|
|
17
17
|
let slackBotTokenInput;
|
|
18
18
|
let slackAppTokenInput;
|
|
19
|
+
let feishuAppIdInput;
|
|
20
|
+
let feishuAppSecretInput;
|
|
19
21
|
let statusEl;
|
|
20
22
|
|
|
23
|
+
function hasConfiguredChatApp(adapters = {}) {
|
|
24
|
+
const telegramConfigured = Boolean(adapters.telegram?.token);
|
|
25
|
+
const slackConfigured = Boolean(
|
|
26
|
+
adapters.slack?.botToken && adapters.slack?.appToken,
|
|
27
|
+
);
|
|
28
|
+
const feishuConfigured = Boolean(
|
|
29
|
+
adapters.feishu?.appId && adapters.feishu?.appSecret,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return telegramConfigured || slackConfigured || feishuConfigured;
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
// --- Public API ---
|
|
22
36
|
|
|
23
37
|
export function initChatAppsDialog() {
|
|
@@ -29,6 +43,8 @@ export function initChatAppsDialog() {
|
|
|
29
43
|
telegramTokenInput = document.getElementById("chatapps-telegram-token");
|
|
30
44
|
slackBotTokenInput = document.getElementById("chatapps-slack-bot-token");
|
|
31
45
|
slackAppTokenInput = document.getElementById("chatapps-slack-app-token");
|
|
46
|
+
feishuAppIdInput = document.getElementById("chatapps-feishu-app-id");
|
|
47
|
+
feishuAppSecretInput = document.getElementById("chatapps-feishu-app-secret");
|
|
32
48
|
statusEl = document.getElementById("chatapps-status");
|
|
33
49
|
|
|
34
50
|
if (!dialog) return;
|
|
@@ -54,9 +70,7 @@ export function updateChatAppsButton() {
|
|
|
54
70
|
if (!openBtn) return;
|
|
55
71
|
const config = state.config;
|
|
56
72
|
const adapters = config?.adapters || {};
|
|
57
|
-
const hasAnyToken =
|
|
58
|
-
(adapters.telegram && adapters.telegram.token) ||
|
|
59
|
-
(adapters.slack && (adapters.slack.botToken || adapters.slack.appToken));
|
|
73
|
+
const hasAnyToken = hasConfiguredChatApp(adapters);
|
|
60
74
|
|
|
61
75
|
if (hasAnyToken) {
|
|
62
76
|
openBtn.classList.add("connected");
|
|
@@ -99,6 +113,14 @@ function populateForm() {
|
|
|
99
113
|
slackAppTokenInput.value = "";
|
|
100
114
|
}
|
|
101
115
|
|
|
116
|
+
if (adapters.feishu) {
|
|
117
|
+
feishuAppIdInput.value = adapters.feishu.appId || "";
|
|
118
|
+
feishuAppSecretInput.value = adapters.feishu.appSecret || "";
|
|
119
|
+
} else {
|
|
120
|
+
feishuAppIdInput.value = "";
|
|
121
|
+
feishuAppSecretInput.value = "";
|
|
122
|
+
}
|
|
123
|
+
|
|
102
124
|
// Restart required status
|
|
103
125
|
if (state.restartRequired) {
|
|
104
126
|
setStatus(
|
|
@@ -116,6 +138,8 @@ async function handleSave() {
|
|
|
116
138
|
const telegramToken = telegramTokenInput.value.trim();
|
|
117
139
|
const slackBotToken = slackBotTokenInput.value.trim();
|
|
118
140
|
const slackAppToken = slackAppTokenInput.value.trim();
|
|
141
|
+
const feishuAppId = feishuAppIdInput.value.trim();
|
|
142
|
+
const feishuAppSecret = feishuAppSecretInput.value.trim();
|
|
119
143
|
|
|
120
144
|
// 始终写入所有 adapter 键,空值也要显式传递,让后端能感知「清空」操作
|
|
121
145
|
const adapters = {
|
|
@@ -127,6 +151,13 @@ async function handleSave() {
|
|
|
127
151
|
appToken: slackAppToken || undefined,
|
|
128
152
|
}
|
|
129
153
|
: null,
|
|
154
|
+
feishu:
|
|
155
|
+
feishuAppId || feishuAppSecret
|
|
156
|
+
? {
|
|
157
|
+
appId: feishuAppId || undefined,
|
|
158
|
+
appSecret: feishuAppSecret || undefined,
|
|
159
|
+
}
|
|
160
|
+
: null,
|
|
130
161
|
};
|
|
131
162
|
|
|
132
163
|
const updates = { adapters };
|