@cremini/skillpack 1.2.10 → 1.2.12

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