@cremini/skillpack 1.2.11 → 1.2.13
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 +524 -105
- 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,12 +4014,36 @@ var PackAgent = class {
|
|
|
3664
4014
|
const run = async () => {
|
|
3665
4015
|
cs.running = true;
|
|
3666
4016
|
let turnHadVisibleOutput = false;
|
|
3667
|
-
|
|
4017
|
+
let sawAgentStart = false;
|
|
4018
|
+
let sawAgentEnd = false;
|
|
4019
|
+
const runId = randomUUID4();
|
|
3668
4020
|
let unsubscribe = () => void 0;
|
|
4021
|
+
const waitForQueuedAgentEvents = async () => {
|
|
4022
|
+
const maybeQueue = cs.session._agentEventQueue;
|
|
4023
|
+
if (!maybeQueue || typeof maybeQueue.then !== "function") {
|
|
4024
|
+
return;
|
|
4025
|
+
}
|
|
4026
|
+
try {
|
|
4027
|
+
await maybeQueue;
|
|
4028
|
+
} catch (error) {
|
|
4029
|
+
log("[PackAgent] Waiting for queued agent events failed:", error);
|
|
4030
|
+
}
|
|
4031
|
+
};
|
|
3669
4032
|
try {
|
|
3670
|
-
|
|
4033
|
+
const forwardAgentEvent = (event) => {
|
|
4034
|
+
if (event.type === "agent_start") {
|
|
4035
|
+
sawAgentStart = true;
|
|
4036
|
+
} else if (event.type === "agent_end") {
|
|
4037
|
+
if (sawAgentEnd) {
|
|
4038
|
+
return;
|
|
4039
|
+
}
|
|
4040
|
+
sawAgentEnd = true;
|
|
4041
|
+
}
|
|
3671
4042
|
onEvent(event);
|
|
3672
4043
|
};
|
|
4044
|
+
cs.fileOutputCallbackRef.current = (event) => {
|
|
4045
|
+
forwardAgentEvent(event);
|
|
4046
|
+
};
|
|
3673
4047
|
cs.delegatedToolRunContextRef.current = {
|
|
3674
4048
|
runId,
|
|
3675
4049
|
channelId,
|
|
@@ -3681,7 +4055,7 @@ var PackAgent = class {
|
|
|
3681
4055
|
log("\n=== [AGENT SESSION START] ===");
|
|
3682
4056
|
log("System Prompt:\n", cs.session.systemPrompt);
|
|
3683
4057
|
log("============================\n");
|
|
3684
|
-
|
|
4058
|
+
forwardAgentEvent({ type: "agent_start" });
|
|
3685
4059
|
break;
|
|
3686
4060
|
case "message_start":
|
|
3687
4061
|
log(`
|
|
@@ -3689,7 +4063,7 @@ var PackAgent = class {
|
|
|
3689
4063
|
if (event.message?.role === "user") {
|
|
3690
4064
|
log(JSON.stringify(event.message.content, null, 2));
|
|
3691
4065
|
}
|
|
3692
|
-
|
|
4066
|
+
forwardAgentEvent({
|
|
3693
4067
|
type: "message_start",
|
|
3694
4068
|
role: event.message?.role ?? ""
|
|
3695
4069
|
});
|
|
@@ -3698,13 +4072,13 @@ var PackAgent = class {
|
|
|
3698
4072
|
if (event.assistantMessageEvent?.type === "text_delta") {
|
|
3699
4073
|
turnHadVisibleOutput = true;
|
|
3700
4074
|
write(event.assistantMessageEvent.delta);
|
|
3701
|
-
|
|
4075
|
+
forwardAgentEvent({
|
|
3702
4076
|
type: "text_delta",
|
|
3703
4077
|
delta: event.assistantMessageEvent.delta
|
|
3704
4078
|
});
|
|
3705
4079
|
} else if (event.assistantMessageEvent?.type === "thinking_delta") {
|
|
3706
4080
|
turnHadVisibleOutput = true;
|
|
3707
|
-
|
|
4081
|
+
forwardAgentEvent({
|
|
3708
4082
|
type: "thinking_delta",
|
|
3709
4083
|
delta: event.assistantMessageEvent.delta
|
|
3710
4084
|
});
|
|
@@ -3724,14 +4098,17 @@ var PackAgent = class {
|
|
|
3724
4098
|
}
|
|
3725
4099
|
}
|
|
3726
4100
|
}
|
|
3727
|
-
|
|
4101
|
+
forwardAgentEvent({
|
|
4102
|
+
type: "message_end",
|
|
4103
|
+
role: event.message?.role ?? ""
|
|
4104
|
+
});
|
|
3728
4105
|
break;
|
|
3729
4106
|
case "tool_execution_start":
|
|
3730
4107
|
turnHadVisibleOutput = true;
|
|
3731
4108
|
log(`
|
|
3732
4109
|
>>> [Tool Start: ${event.toolName}] >>>`);
|
|
3733
4110
|
log("Args:", JSON.stringify(event.args, null, 2));
|
|
3734
|
-
|
|
4111
|
+
forwardAgentEvent({
|
|
3735
4112
|
type: "tool_start",
|
|
3736
4113
|
toolCallId: event.toolCallId ?? "",
|
|
3737
4114
|
toolName: event.toolName,
|
|
@@ -3742,7 +4119,7 @@ var PackAgent = class {
|
|
|
3742
4119
|
turnHadVisibleOutput = true;
|
|
3743
4120
|
log(`<<< [Tool End: ${event.toolName}] <<<`);
|
|
3744
4121
|
log(`Error: ${event.isError ? "Yes" : "No"}`);
|
|
3745
|
-
|
|
4122
|
+
forwardAgentEvent({
|
|
3746
4123
|
type: "tool_end",
|
|
3747
4124
|
toolCallId: event.toolCallId ?? "",
|
|
3748
4125
|
toolName: event.toolName,
|
|
@@ -3752,7 +4129,7 @@ var PackAgent = class {
|
|
|
3752
4129
|
break;
|
|
3753
4130
|
case "agent_end":
|
|
3754
4131
|
log("\n=== [AGENT SESSION END] ===\n");
|
|
3755
|
-
|
|
4132
|
+
forwardAgentEvent({ type: "agent_end" });
|
|
3756
4133
|
break;
|
|
3757
4134
|
}
|
|
3758
4135
|
});
|
|
@@ -3799,6 +4176,12 @@ ${text}`;
|
|
|
3799
4176
|
}
|
|
3800
4177
|
return { stopReason: diagnostics?.stopReason ?? "unknown" };
|
|
3801
4178
|
} finally {
|
|
4179
|
+
await waitForQueuedAgentEvents();
|
|
4180
|
+
if (sawAgentStart && !sawAgentEnd) {
|
|
4181
|
+
sawAgentEnd = true;
|
|
4182
|
+
log(`[PackAgent] Synthesizing terminal agent_end for ${channelId}`);
|
|
4183
|
+
onEvent({ type: "agent_end" });
|
|
4184
|
+
}
|
|
3802
4185
|
cs.running = false;
|
|
3803
4186
|
cs.fileOutputCallbackRef.current = null;
|
|
3804
4187
|
cs.delegatedToolRunContextRef.current = null;
|
|
@@ -3878,12 +4261,14 @@ ${text}`;
|
|
|
3878
4261
|
};
|
|
3879
4262
|
|
|
3880
4263
|
// src/runtime/adapters/web.ts
|
|
4264
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
3881
4265
|
import fs12 from "fs";
|
|
3882
4266
|
import path13 from "path";
|
|
3883
4267
|
import { WebSocketServer } from "ws";
|
|
3884
4268
|
init_commands();
|
|
3885
4269
|
|
|
3886
4270
|
// src/runtime/services/conversation.ts
|
|
4271
|
+
init_types();
|
|
3887
4272
|
import fs11 from "fs";
|
|
3888
4273
|
import path12 from "path";
|
|
3889
4274
|
import {
|
|
@@ -4267,10 +4652,7 @@ var ConversationService = class {
|
|
|
4267
4652
|
return parts[parts.length - 1] || filePath;
|
|
4268
4653
|
}
|
|
4269
4654
|
detectPlatform(channelId) {
|
|
4270
|
-
|
|
4271
|
-
if (channelId.startsWith("slack-")) return "slack";
|
|
4272
|
-
if (channelId.startsWith("scheduler-")) return "scheduler";
|
|
4273
|
-
return "web";
|
|
4655
|
+
return detectPlatformFromChannelId(channelId);
|
|
4274
4656
|
}
|
|
4275
4657
|
isLegacyWebConversation(channelId) {
|
|
4276
4658
|
return channelId.startsWith("web-");
|
|
@@ -4298,7 +4680,9 @@ function getRuntimeConfigSignature(config) {
|
|
|
4298
4680
|
apiProtocol: config.apiProtocol || "",
|
|
4299
4681
|
telegramToken: config.adapters?.telegram?.token || "",
|
|
4300
4682
|
slackBotToken: config.adapters?.slack?.botToken || "",
|
|
4301
|
-
slackAppToken: config.adapters?.slack?.appToken || ""
|
|
4683
|
+
slackAppToken: config.adapters?.slack?.appToken || "",
|
|
4684
|
+
feishuAppId: config.adapters?.feishu?.appId || "",
|
|
4685
|
+
feishuAppSecret: config.adapters?.feishu?.appSecret || ""
|
|
4302
4686
|
});
|
|
4303
4687
|
}
|
|
4304
4688
|
function parsePositiveInt(value, fallback) {
|
|
@@ -4509,7 +4893,15 @@ var WebAdapter = class {
|
|
|
4509
4893
|
res.status(503).json({ success: false, message: "Scheduler not available" });
|
|
4510
4894
|
return;
|
|
4511
4895
|
}
|
|
4512
|
-
const {
|
|
4896
|
+
const {
|
|
4897
|
+
id,
|
|
4898
|
+
name,
|
|
4899
|
+
cron: cronExpr,
|
|
4900
|
+
prompt,
|
|
4901
|
+
notify,
|
|
4902
|
+
enabled,
|
|
4903
|
+
timezone
|
|
4904
|
+
} = req.body;
|
|
4513
4905
|
if (!name || !prompt || !notify?.adapter || !notify?.channelId) {
|
|
4514
4906
|
res.status(400).json({
|
|
4515
4907
|
success: false,
|
|
@@ -4518,6 +4910,7 @@ var WebAdapter = class {
|
|
|
4518
4910
|
return;
|
|
4519
4911
|
}
|
|
4520
4912
|
const result = scheduler.addJob({
|
|
4913
|
+
id: typeof id === "string" && id.trim() ? id.trim() : randomUUID5(),
|
|
4521
4914
|
name,
|
|
4522
4915
|
...typeof cronExpr === "string" ? { cron: cronExpr } : {},
|
|
4523
4916
|
prompt,
|
|
@@ -4527,25 +4920,25 @@ var WebAdapter = class {
|
|
|
4527
4920
|
});
|
|
4528
4921
|
res.json(result);
|
|
4529
4922
|
});
|
|
4530
|
-
app.delete("/api/scheduler/jobs/:
|
|
4923
|
+
app.delete("/api/scheduler/jobs/:jobId", (req, res) => {
|
|
4531
4924
|
const scheduler = getScheduler();
|
|
4532
4925
|
if (!scheduler) {
|
|
4533
4926
|
res.status(503).json({ success: false, message: "Scheduler not available" });
|
|
4534
4927
|
return;
|
|
4535
4928
|
}
|
|
4536
|
-
const result = scheduler.removeJob(req.params.
|
|
4929
|
+
const result = scheduler.removeJob(req.params.jobId);
|
|
4537
4930
|
res.json(result);
|
|
4538
4931
|
});
|
|
4539
|
-
app.post("/api/scheduler/jobs/:
|
|
4932
|
+
app.post("/api/scheduler/jobs/:jobId/trigger", async (req, res) => {
|
|
4540
4933
|
const scheduler = getScheduler();
|
|
4541
4934
|
if (!scheduler) {
|
|
4542
4935
|
res.status(503).json({ success: false, message: "Scheduler not available" });
|
|
4543
4936
|
return;
|
|
4544
4937
|
}
|
|
4545
|
-
const result = await scheduler.triggerJob(req.params.
|
|
4938
|
+
const result = await scheduler.triggerJob(req.params.jobId);
|
|
4546
4939
|
res.json(result);
|
|
4547
4940
|
});
|
|
4548
|
-
app.patch("/api/scheduler/jobs/:
|
|
4941
|
+
app.patch("/api/scheduler/jobs/:jobId", (req, res) => {
|
|
4549
4942
|
const scheduler = getScheduler();
|
|
4550
4943
|
if (!scheduler) {
|
|
4551
4944
|
res.status(503).json({ success: false, message: "Scheduler not available" });
|
|
@@ -4556,7 +4949,7 @@ var WebAdapter = class {
|
|
|
4556
4949
|
res.status(400).json({ success: false, message: "Field 'enabled' (boolean) is required" });
|
|
4557
4950
|
return;
|
|
4558
4951
|
}
|
|
4559
|
-
const result = scheduler.setEnabled(req.params.
|
|
4952
|
+
const result = scheduler.setEnabled(req.params.jobId, enabled);
|
|
4560
4953
|
res.json(result);
|
|
4561
4954
|
});
|
|
4562
4955
|
this.wss = new WebSocketServer({ noServer: true });
|
|
@@ -4877,7 +5270,7 @@ var IpcAdapter = class {
|
|
|
4877
5270
|
this.replyError(request.id, "Scheduler adapter is not available");
|
|
4878
5271
|
return;
|
|
4879
5272
|
}
|
|
4880
|
-
const result = scheduler.updateJob(request.
|
|
5273
|
+
const result = scheduler.updateJob(request.jobId, request.updates);
|
|
4881
5274
|
if (!result.success) {
|
|
4882
5275
|
this.replyError(request.id, result.message);
|
|
4883
5276
|
return;
|
|
@@ -4891,7 +5284,7 @@ var IpcAdapter = class {
|
|
|
4891
5284
|
this.replyError(request.id, "Scheduler adapter is not available");
|
|
4892
5285
|
return;
|
|
4893
5286
|
}
|
|
4894
|
-
const result = scheduler.setEnabled(request.
|
|
5287
|
+
const result = scheduler.setEnabled(request.jobId, request.enabled);
|
|
4895
5288
|
if (!result.success) {
|
|
4896
5289
|
this.replyError(request.id, result.message);
|
|
4897
5290
|
return;
|
|
@@ -4905,7 +5298,7 @@ var IpcAdapter = class {
|
|
|
4905
5298
|
this.replyError(request.id, "Scheduler adapter is not available");
|
|
4906
5299
|
return;
|
|
4907
5300
|
}
|
|
4908
|
-
const result = await scheduler.triggerJob(request.
|
|
5301
|
+
const result = await scheduler.triggerJob(request.jobId);
|
|
4909
5302
|
if (!result.success) {
|
|
4910
5303
|
this.replyError(request.id, result.message);
|
|
4911
5304
|
return;
|
|
@@ -4919,7 +5312,7 @@ var IpcAdapter = class {
|
|
|
4919
5312
|
this.replyError(request.id, "Scheduler adapter is not available");
|
|
4920
5313
|
return;
|
|
4921
5314
|
}
|
|
4922
|
-
const result = scheduler.removeJob(request.
|
|
5315
|
+
const result = scheduler.removeJob(request.jobId);
|
|
4923
5316
|
if (!result.success) {
|
|
4924
5317
|
this.replyError(request.id, result.message);
|
|
4925
5318
|
return;
|
|
@@ -4941,10 +5334,7 @@ var IpcAdapter = class {
|
|
|
4941
5334
|
return adapter;
|
|
4942
5335
|
}
|
|
4943
5336
|
detectPlatform(channelId) {
|
|
4944
|
-
|
|
4945
|
-
if (channelId.startsWith("slack-")) return "slack";
|
|
4946
|
-
if (channelId.startsWith("scheduler-")) return "scheduler";
|
|
4947
|
-
return "web";
|
|
5337
|
+
return detectPlatformFromChannelId(channelId);
|
|
4948
5338
|
}
|
|
4949
5339
|
sendIpc(payload) {
|
|
4950
5340
|
if (typeof process.send === "function") {
|
|
@@ -5189,7 +5579,7 @@ function deregister(dir, pid) {
|
|
|
5189
5579
|
}
|
|
5190
5580
|
|
|
5191
5581
|
// src/runtime/server.ts
|
|
5192
|
-
var __dirname =
|
|
5582
|
+
var __dirname = path17.dirname(fileURLToPath2(import.meta.url));
|
|
5193
5583
|
async function startServer(options) {
|
|
5194
5584
|
const {
|
|
5195
5585
|
rootDir,
|
|
@@ -5205,8 +5595,8 @@ async function startServer(options) {
|
|
|
5205
5595
|
const baseUrl = dataConfig.baseUrl?.trim() || void 0;
|
|
5206
5596
|
const modelId = dataConfig.modelId?.trim() || (SUPPORTED_PROVIDERS[provider]?.defaultModelId ?? SUPPORTED_PROVIDERS.openai.defaultModelId);
|
|
5207
5597
|
const apiProtocol = dataConfig.apiProtocol;
|
|
5208
|
-
const packageRoot =
|
|
5209
|
-
const webDir =
|
|
5598
|
+
const packageRoot = path17.resolve(__dirname, "..");
|
|
5599
|
+
const webDir = fs17.existsSync(path17.join(rootDir, "web")) ? path17.join(rootDir, "web") : path17.join(packageRoot, "web");
|
|
5210
5600
|
const app = express();
|
|
5211
5601
|
app.use(express.json());
|
|
5212
5602
|
app.use(express.static(webDir));
|
|
@@ -5318,6 +5708,35 @@ async function startServer(options) {
|
|
|
5318
5708
|
}
|
|
5319
5709
|
}
|
|
5320
5710
|
}
|
|
5711
|
+
const feishuConfig = dataConfig.adapters?.feishu;
|
|
5712
|
+
if (feishuConfig?.appId || feishuConfig?.appSecret) {
|
|
5713
|
+
if (!feishuConfig.appId || !feishuConfig.appSecret) {
|
|
5714
|
+
console.warn(
|
|
5715
|
+
"[Feishu] Skipped: both adapters.feishu.appId and adapters.feishu.appSecret are required."
|
|
5716
|
+
);
|
|
5717
|
+
} else {
|
|
5718
|
+
try {
|
|
5719
|
+
const { FeishuAdapter: FeishuAdapter2 } = await Promise.resolve().then(() => (init_feishu(), feishu_exports));
|
|
5720
|
+
const feishuAdapter = new FeishuAdapter2({
|
|
5721
|
+
appId: feishuConfig.appId,
|
|
5722
|
+
appSecret: feishuConfig.appSecret
|
|
5723
|
+
});
|
|
5724
|
+
await feishuAdapter.start({
|
|
5725
|
+
agent,
|
|
5726
|
+
server,
|
|
5727
|
+
app,
|
|
5728
|
+
rootDir,
|
|
5729
|
+
lifecycle,
|
|
5730
|
+
adapterMap,
|
|
5731
|
+
ipcBroadcaster
|
|
5732
|
+
});
|
|
5733
|
+
adapters.push(feishuAdapter);
|
|
5734
|
+
adapterMap.set(feishuAdapter.name, feishuAdapter);
|
|
5735
|
+
} catch (err) {
|
|
5736
|
+
console.error("[Feishu] Failed to start:", err);
|
|
5737
|
+
}
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5321
5740
|
const { isMessageSender: isMessageSender2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
5322
5741
|
const notifyFn = async (adapterName, channelId, text) => {
|
|
5323
5742
|
const adapter = adapterMap.get(adapterName);
|
|
@@ -5429,23 +5848,23 @@ function findMissingSkills(workDir, config) {
|
|
|
5429
5848
|
});
|
|
5430
5849
|
}
|
|
5431
5850
|
function copyStartTemplates2(workDir) {
|
|
5432
|
-
const templateDir =
|
|
5851
|
+
const templateDir = path18.resolve(
|
|
5433
5852
|
new URL("../templates", import.meta.url).pathname
|
|
5434
5853
|
);
|
|
5435
5854
|
for (const file of ["start.sh", "start.bat"]) {
|
|
5436
|
-
const src =
|
|
5437
|
-
const dest =
|
|
5438
|
-
if (
|
|
5439
|
-
|
|
5855
|
+
const src = path18.join(templateDir, file);
|
|
5856
|
+
const dest = path18.join(workDir, file);
|
|
5857
|
+
if (fs18.existsSync(src)) {
|
|
5858
|
+
fs18.copyFileSync(src, dest);
|
|
5440
5859
|
if (file === "start.sh") {
|
|
5441
|
-
|
|
5860
|
+
fs18.chmodSync(dest, 493);
|
|
5442
5861
|
}
|
|
5443
5862
|
}
|
|
5444
5863
|
}
|
|
5445
5864
|
}
|
|
5446
5865
|
async function runCommand(directory) {
|
|
5447
|
-
const workDir = directory ?
|
|
5448
|
-
|
|
5866
|
+
const workDir = directory ? path18.resolve(directory) : process.cwd();
|
|
5867
|
+
fs18.mkdirSync(workDir, { recursive: true });
|
|
5449
5868
|
if (!configExists(workDir)) {
|
|
5450
5869
|
console.log(chalk4.blue("\n No skillpack.json found. Let's set one up.\n"));
|
|
5451
5870
|
const { name, description } = await inquirer2.prompt([
|
|
@@ -5489,14 +5908,14 @@ async function runCommand(directory) {
|
|
|
5489
5908
|
}
|
|
5490
5909
|
|
|
5491
5910
|
// src/cli.ts
|
|
5492
|
-
import
|
|
5493
|
-
import
|
|
5911
|
+
import fs19 from "fs";
|
|
5912
|
+
import path19 from "path";
|
|
5494
5913
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5495
5914
|
var packageJson = JSON.parse(
|
|
5496
|
-
|
|
5915
|
+
fs19.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
|
|
5497
5916
|
);
|
|
5498
5917
|
var program = new Command();
|
|
5499
|
-
var cliFilePath =
|
|
5918
|
+
var cliFilePath = path19.resolve(fileURLToPath3(import.meta.url));
|
|
5500
5919
|
program.name("skillpack").description("Assemble, package, and run Agent Skills packs").version(packageJson.version);
|
|
5501
5920
|
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
5921
|
await createCommand(directory, options);
|
|
@@ -5517,7 +5936,7 @@ program.command("zip").description("Package the current pack as a zip file (skil
|
|
|
5517
5936
|
function normalizeUserArgs(argv) {
|
|
5518
5937
|
if (argv.length === 0) return argv;
|
|
5519
5938
|
const firstArg = argv[0];
|
|
5520
|
-
if (firstArg &&
|
|
5939
|
+
if (firstArg && path19.resolve(firstArg) === cliFilePath) {
|
|
5521
5940
|
return argv.slice(1);
|
|
5522
5941
|
}
|
|
5523
5942
|
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.13",
|
|
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 };
|