@cremini/skillpack 1.2.3 → 1.2.5-beta.1

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
@@ -267,6 +267,76 @@ var init_attachment_utils = __esm({
267
267
  }
268
268
  });
269
269
 
270
+ // src/runtime/commands/index.ts
271
+ function resolveCommand(input) {
272
+ const key = input.split(/\s/)[0].toLowerCase();
273
+ return COMMAND_ALIASES[key] ?? null;
274
+ }
275
+ function getVisibleCommands() {
276
+ return COMMAND_REGISTRY.filter((cmd) => cmd.visibleInHelp);
277
+ }
278
+ function getTelegramBotCommands() {
279
+ return COMMAND_REGISTRY.filter((cmd) => cmd.visibleInHelp).map((cmd) => ({
280
+ command: cmd.command,
281
+ description: cmd.description
282
+ }));
283
+ }
284
+ var COMMAND_REGISTRY, COMMAND_ALIASES;
285
+ var init_commands = __esm({
286
+ "src/runtime/commands/index.ts"() {
287
+ "use strict";
288
+ COMMAND_REGISTRY = [
289
+ {
290
+ command: "help",
291
+ description: "Show available commands, skills, and usage tips",
292
+ visibleInHelp: true
293
+ },
294
+ {
295
+ command: "clear",
296
+ description: "Clear current session and start fresh",
297
+ visibleInHelp: true
298
+ },
299
+ {
300
+ command: "restart",
301
+ description: "Restart the server process",
302
+ visibleInHelp: true
303
+ },
304
+ {
305
+ command: "shutdown",
306
+ description: "Shut down the server process",
307
+ visibleInHelp: true
308
+ },
309
+ // "new" is a hidden alias for "clear" — not shown in /help
310
+ {
311
+ command: "new",
312
+ description: "Start a new session (alias for /clear)",
313
+ visibleInHelp: false
314
+ }
315
+ ];
316
+ COMMAND_ALIASES = {
317
+ "/help": "help",
318
+ "/clear": "clear",
319
+ "/new": "clear",
320
+ "/restart": "restart",
321
+ "/shutdown": "shutdown"
322
+ };
323
+ }
324
+ });
325
+
326
+ // src/runtime/adapters/types.ts
327
+ var types_exports = {};
328
+ __export(types_exports, {
329
+ isMessageSender: () => isMessageSender
330
+ });
331
+ function isMessageSender(adapter) {
332
+ return typeof adapter.sendMessage === "function";
333
+ }
334
+ var init_types = __esm({
335
+ "src/runtime/adapters/types.ts"() {
336
+ "use strict";
337
+ }
338
+ });
339
+
270
340
  // src/runtime/adapters/markdown.ts
271
341
  function unwrapMarkdownSourceBlocks(text) {
272
342
  return text.replace(
@@ -307,6 +377,7 @@ function formatSlackInline(text) {
307
377
  );
308
378
  formatted = formatted.replace(/\*\*([^*\n]+)\*\*/g, "*$1*");
309
379
  formatted = formatted.replace(/__([^_\n]+)__/g, "*$1*");
380
+ formatted = formatted.replace(/^(?:-|\*) /gm, "\u2022 ");
310
381
  return formatted;
311
382
  }
312
383
  function formatTelegramInline(text) {
@@ -386,19 +457,15 @@ var telegram_exports = {};
386
457
  __export(telegram_exports, {
387
458
  TelegramAdapter: () => TelegramAdapter
388
459
  });
389
- import fs11 from "fs";
460
+ import fs13 from "fs";
390
461
  import TelegramBot from "node-telegram-bot-api";
391
- var COMMANDS2, MAX_MESSAGE_LENGTH, ACK_REACTION, TelegramAdapter;
462
+ var MAX_MESSAGE_LENGTH, ACK_REACTION, TelegramAdapter;
392
463
  var init_telegram = __esm({
393
464
  "src/runtime/adapters/telegram.ts"() {
394
465
  "use strict";
395
466
  init_markdown();
396
467
  init_attachment_utils();
397
- COMMANDS2 = {
398
- "/clear": "clear",
399
- "/restart": "restart",
400
- "/shutdown": "shutdown"
401
- };
468
+ init_commands();
402
469
  MAX_MESSAGE_LENGTH = 4096;
403
470
  ACK_REACTION = {
404
471
  type: "emoji",
@@ -410,23 +477,21 @@ var init_telegram = __esm({
410
477
  agent = null;
411
478
  options;
412
479
  rootDir = "";
480
+ ipcBroadcaster = null;
413
481
  constructor(options) {
414
482
  this.options = options;
415
483
  }
416
484
  async start(ctx) {
417
485
  this.agent = ctx.agent;
418
486
  this.rootDir = ctx.rootDir;
487
+ this.ipcBroadcaster = ctx.ipcBroadcaster ?? null;
419
488
  this.bot = new TelegramBot(this.options.token, { polling: true });
420
489
  this.bot.on("message", (msg) => {
421
490
  this.handleTelegramMessage(msg).catch((err) => {
422
491
  console.error("[Telegram] Error handling message:", err);
423
492
  });
424
493
  });
425
- await this.bot.setMyCommands([
426
- { command: "clear", description: "Clear current session and start new" },
427
- { command: "restart", description: "Restart the server process" },
428
- { command: "shutdown", description: "Shut down the server process" }
429
- ]);
494
+ await this.bot.setMyCommands(getTelegramBotCommands());
430
495
  const me = await this.bot.getMe();
431
496
  console.log(`[TelegramAdapter] Started as @${me.username}`);
432
497
  }
@@ -461,12 +526,21 @@ var init_telegram = __esm({
461
526
  const messageId = msg.message_id;
462
527
  const text = (msg.text || msg.caption || "").trim();
463
528
  const channelId = `telegram-${chatId}`;
529
+ this.ipcBroadcaster?.broadcastInbound(
530
+ channelId,
531
+ "telegram",
532
+ {
533
+ id: String(msg.from?.id || ""),
534
+ username: msg.from?.username || msg.from?.first_name || ""
535
+ },
536
+ text
537
+ );
464
538
  const attachments = await this.extractAttachments(msg, channelId);
465
539
  if (!text && attachments.length === 0) return;
466
540
  await this.tryAckReaction(chatId, messageId);
467
541
  if (text) {
468
542
  const commandKey = text.split(/\s/)[0].toLowerCase();
469
- const command = COMMANDS2[commandKey];
543
+ const command = this.resolveTelegramCommand(commandKey);
470
544
  if (command) {
471
545
  const result = await this.agent.handleCommand(command, channelId);
472
546
  await this.sendSafe(chatId, result.message || `/${command} executed.`);
@@ -490,6 +564,7 @@ var init_telegram = __esm({
490
564
  });
491
565
  break;
492
566
  }
567
+ this.ipcBroadcaster?.broadcastAgentEvent(channelId, event);
493
568
  };
494
569
  try {
495
570
  const userText = text || "(User sent an attachment)";
@@ -521,6 +596,9 @@ var init_telegram = __esm({
521
596
  await this.sendFileSafe(chatId, file.filePath, file.caption);
522
597
  }
523
598
  }
599
+ resolveTelegramCommand(commandKey) {
600
+ return resolveCommand(commandKey);
601
+ }
524
602
  // -------------------------------------------------------------------------
525
603
  // Send helpers
526
604
  // -------------------------------------------------------------------------
@@ -697,7 +775,7 @@ var init_telegram = __esm({
697
775
  async sendFileSafe(chatId, filePath, caption) {
698
776
  if (!this.bot) return;
699
777
  try {
700
- if (!fs11.existsSync(filePath)) {
778
+ if (!fs13.existsSync(filePath)) {
701
779
  console.error(`[Telegram] File not found for sending: ${filePath}`);
702
780
  return;
703
781
  }
@@ -717,16 +795,18 @@ var slack_exports = {};
717
795
  __export(slack_exports, {
718
796
  SlackAdapter: () => SlackAdapter
719
797
  });
720
- import fs12 from "fs";
721
- import path11 from "path";
798
+ import fs14 from "fs";
799
+ import path13 from "path";
722
800
  import { App, LogLevel } from "@slack/bolt";
723
- var INLINE_COMMANDS, SLASH_COMMANDS, MAX_MESSAGE_LENGTH2, ACK_REACTION2, SlackAdapter;
801
+ var INLINE_COMMANDS, SLASH_COMMANDS, MAX_MESSAGE_LENGTH2, ACK_REACTION2, PROCESSING_MESSAGE, SlackAdapter;
724
802
  var init_slack = __esm({
725
803
  "src/runtime/adapters/slack.ts"() {
726
804
  "use strict";
727
805
  init_markdown();
728
806
  init_attachment_utils();
807
+ init_commands();
729
808
  INLINE_COMMANDS = {
809
+ "/help": "help",
730
810
  "/clear": "clear",
731
811
  "/restart": "restart",
732
812
  "/shutdown": "shutdown"
@@ -738,6 +818,7 @@ var init_slack = __esm({
738
818
  };
739
819
  MAX_MESSAGE_LENGTH2 = 3500;
740
820
  ACK_REACTION2 = "eyes";
821
+ PROCESSING_MESSAGE = "_Processing..._";
741
822
  SlackAdapter = class {
742
823
  name = "slack";
743
824
  app = null;
@@ -746,12 +827,14 @@ var init_slack = __esm({
746
827
  botUserId = null;
747
828
  lastThreadByChannel = /* @__PURE__ */ new Map();
748
829
  rootDir = "";
830
+ ipcBroadcaster = null;
749
831
  constructor(options) {
750
832
  this.options = options;
751
833
  }
752
834
  async start(ctx) {
753
835
  this.agent = ctx.agent;
754
836
  this.rootDir = ctx.rootDir;
837
+ this.ipcBroadcaster = ctx.ipcBroadcaster ?? null;
755
838
  this.app = new App({
756
839
  token: this.options.botToken,
757
840
  appToken: this.options.appToken,
@@ -807,7 +890,7 @@ var init_slack = __esm({
807
890
  console.error("[Slack] Error handling mention:", err);
808
891
  }
809
892
  });
810
- for (const commandName of Object.keys(SLASH_COMMANDS)) {
893
+ for (const commandName of [...Object.keys(SLASH_COMMANDS), "/new"]) {
811
894
  app.command(commandName, async (args) => {
812
895
  try {
813
896
  await this.handleSlashCommand(args);
@@ -837,6 +920,15 @@ var init_slack = __esm({
837
920
  const teamId = this.getTeamId(body, context);
838
921
  const channelId = `slack-dm-${teamId}-${event.channel}`;
839
922
  const route = { channel: event.channel };
923
+ this.ipcBroadcaster?.broadcastInbound(
924
+ channelId,
925
+ "slack",
926
+ {
927
+ id: String(event.user || ""),
928
+ username: String(event.user || "")
929
+ },
930
+ text
931
+ );
840
932
  const attachments = await this.extractSlackFiles(event, channelId, client);
841
933
  if (!text && attachments.length === 0) return;
842
934
  await this.tryAckReaction(client, event);
@@ -867,12 +959,21 @@ var init_slack = __esm({
867
959
  threadTs
868
960
  );
869
961
  const text = this.stripBotMention(event.text || "").trim();
962
+ this.ipcBroadcaster?.broadcastInbound(
963
+ channelId,
964
+ "slack",
965
+ {
966
+ id: String(event.user || ""),
967
+ username: String(event.user || "")
968
+ },
969
+ text
970
+ );
870
971
  const attachments = await this.extractSlackFiles(event, channelId, client);
871
972
  if (!text && attachments.length === 0) {
872
973
  await this.sendSafe(
873
974
  client,
874
975
  route,
875
- "Mention me with a message, or use `/clear` to reset this thread."
976
+ "Mention me with a message, or use `/clear` or `/new` to reset this thread."
876
977
  );
877
978
  return;
878
979
  }
@@ -890,7 +991,7 @@ var init_slack = __esm({
890
991
  ack
891
992
  }) {
892
993
  const commandName = command?.command;
893
- const mapped = commandName ? SLASH_COMMANDS[commandName] : void 0;
994
+ const mapped = commandName ? this.resolveSlashCommand(commandName) : void 0;
894
995
  if (!this.agent || !mapped) {
895
996
  await this.safeAck(ack, "Unsupported slash command.");
896
997
  return;
@@ -916,6 +1017,7 @@ var init_slack = __esm({
916
1017
  let hasError = false;
917
1018
  let errorMessage = "";
918
1019
  const pendingFiles = [];
1020
+ const placeholder = await this.sendPlaceholderMessage(client, route);
919
1021
  const onEvent = (event) => {
920
1022
  if (event.type === "text_delta") {
921
1023
  finalText += event.delta;
@@ -925,6 +1027,7 @@ var init_slack = __esm({
925
1027
  caption: event.caption
926
1028
  });
927
1029
  }
1030
+ this.ipcBroadcaster?.broadcastAgentEvent(channelId, event);
928
1031
  };
929
1032
  try {
930
1033
  const result = await this.agent.handleMessage(
@@ -943,13 +1046,25 @@ var init_slack = __esm({
943
1046
  errorMessage = this.getErrorMessage(err);
944
1047
  }
945
1048
  if (hasError) {
946
- await this.sendSafe(client, route, `\u274C Error: ${errorMessage}`);
1049
+ await this.sendOrUpdateSafe(
1050
+ client,
1051
+ route,
1052
+ `\u274C Error: ${errorMessage}`,
1053
+ placeholder
1054
+ );
947
1055
  return;
948
1056
  }
949
1057
  if (finalText.trim()) {
950
- await this.sendLongMessage(client, route, finalText);
1058
+ await this.sendLongMessage(client, route, finalText, placeholder);
951
1059
  } else if (pendingFiles.length === 0) {
952
- await this.sendSafe(client, route, "(No response generated)");
1060
+ await this.sendOrUpdateSafe(
1061
+ client,
1062
+ route,
1063
+ "(No response generated)",
1064
+ placeholder
1065
+ );
1066
+ } else if (placeholder) {
1067
+ await this.deleteMessageSafe(client, route, placeholder.ts);
953
1068
  }
954
1069
  for (const file of pendingFiles) {
955
1070
  await this.sendFileSafe(client, route, file.filePath, file.caption);
@@ -961,7 +1076,7 @@ var init_slack = __esm({
961
1076
  async tryHandleInlineCommand(text, channelId, client, route) {
962
1077
  if (!this.agent) return false;
963
1078
  const commandKey = text.split(/\s/)[0].toLowerCase();
964
- const command = INLINE_COMMANDS[commandKey];
1079
+ const command = this.resolveInlineCommand(commandKey);
965
1080
  if (!command) return false;
966
1081
  const result = await this.agent.handleCommand(command, channelId);
967
1082
  await this.sendSafe(
@@ -971,6 +1086,19 @@ var init_slack = __esm({
971
1086
  );
972
1087
  return true;
973
1088
  }
1089
+ resolveInlineCommand(commandKey) {
1090
+ const resolved = resolveCommand(commandKey);
1091
+ if (resolved) {
1092
+ return resolved;
1093
+ }
1094
+ return INLINE_COMMANDS[commandKey];
1095
+ }
1096
+ resolveSlashCommand(commandName) {
1097
+ if (commandName === "/new") {
1098
+ return "clear";
1099
+ }
1100
+ return SLASH_COMMANDS[commandName];
1101
+ }
974
1102
  resolveSlashCommandTarget(payload, context) {
975
1103
  const teamId = this.getTeamId(payload, context);
976
1104
  const channel = payload?.channel_id;
@@ -988,7 +1116,7 @@ var init_slack = __esm({
988
1116
  );
989
1117
  if (!threadTs) {
990
1118
  return {
991
- message: "No active Skillpack thread found in this channel. Mention the bot first, or run the command inside the thread as `@bot /clear`."
1119
+ message: "No active Skillpack thread found in this channel. Mention the bot first, or run the command inside the thread as `@bot /clear` or `@bot /new`."
992
1120
  };
993
1121
  }
994
1122
  return {
@@ -1017,33 +1145,35 @@ var init_slack = __esm({
1017
1145
  return text.replace(mention, "");
1018
1146
  }
1019
1147
  splitMessage(text) {
1020
- if (text.length <= MAX_MESSAGE_LENGTH2) {
1148
+ if (this.isSlackMessageWithinLimit(text)) {
1021
1149
  return [text];
1022
1150
  }
1023
1151
  const chunks = [];
1024
1152
  let remaining = text;
1025
1153
  while (remaining.length > 0) {
1026
- if (remaining.length <= MAX_MESSAGE_LENGTH2) {
1154
+ if (this.isSlackMessageWithinLimit(remaining)) {
1027
1155
  chunks.push(remaining);
1028
1156
  break;
1029
1157
  }
1030
- let splitAt = remaining.lastIndexOf("\n\n", MAX_MESSAGE_LENGTH2);
1031
- if (splitAt < MAX_MESSAGE_LENGTH2 * 0.5) {
1032
- splitAt = remaining.lastIndexOf("\n", MAX_MESSAGE_LENGTH2);
1033
- }
1034
- if (splitAt < MAX_MESSAGE_LENGTH2 * 0.3) {
1035
- splitAt = remaining.lastIndexOf(" ", MAX_MESSAGE_LENGTH2);
1036
- }
1037
- if (splitAt < 1) {
1038
- splitAt = MAX_MESSAGE_LENGTH2;
1039
- }
1158
+ let splitAt = this.findSlackSafeSplitPoint(remaining);
1040
1159
  chunks.push(remaining.slice(0, splitAt));
1041
1160
  remaining = remaining.slice(splitAt).trimStart();
1042
1161
  }
1043
1162
  return chunks;
1044
1163
  }
1045
- async sendLongMessage(client, route, text) {
1046
- for (const chunk of this.splitMessage(text)) {
1164
+ async sendLongMessage(client, route, text, placeholder) {
1165
+ const chunks = this.splitMessage(text);
1166
+ if (chunks.length === 0) {
1167
+ return;
1168
+ }
1169
+ if (placeholder) {
1170
+ await this.updateMessageSafe(client, route, placeholder.ts, chunks[0]);
1171
+ for (const chunk of chunks.slice(1)) {
1172
+ await this.sendSafe(client, route, chunk);
1173
+ }
1174
+ return;
1175
+ }
1176
+ for (const chunk of chunks) {
1047
1177
  await this.sendWithRetry(client, route, chunk);
1048
1178
  }
1049
1179
  }
@@ -1054,17 +1184,35 @@ var init_slack = __esm({
1054
1184
  console.error("[Slack] Failed to send message:", err);
1055
1185
  }
1056
1186
  }
1187
+ async sendOrUpdateSafe(client, route, text, placeholder) {
1188
+ if (placeholder) {
1189
+ await this.updateMessageSafe(client, route, placeholder.ts, text);
1190
+ return;
1191
+ }
1192
+ await this.sendSafe(client, route, text);
1193
+ }
1194
+ async sendPlaceholderMessage(client, route) {
1195
+ try {
1196
+ return await this.sendWithRetry(client, route, PROCESSING_MESSAGE);
1197
+ } catch (err) {
1198
+ console.error("[Slack] Failed to send placeholder message:", err);
1199
+ return null;
1200
+ }
1201
+ }
1057
1202
  async sendWithRetry(client, route, text, maxRetries = 3) {
1058
1203
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
1059
1204
  try {
1060
- await client.chat.postMessage({
1205
+ const response = await client.chat.postMessage({
1061
1206
  channel: route.channel,
1062
1207
  text: formatSlackMessage(text),
1063
1208
  mrkdwn: true,
1064
1209
  thread_ts: route.threadTs,
1065
1210
  reply_broadcast: false
1066
1211
  });
1067
- return;
1212
+ if (typeof response.ts !== "string") {
1213
+ throw new Error("Slack postMessage response missing ts");
1214
+ }
1215
+ return { ts: response.ts };
1068
1216
  } catch (err) {
1069
1217
  const retryAfter = this.getRetryAfterSeconds(err);
1070
1218
  if (retryAfter && attempt < maxRetries) {
@@ -1079,6 +1227,93 @@ var init_slack = __esm({
1079
1227
  throw err;
1080
1228
  }
1081
1229
  }
1230
+ throw new Error("Slack postMessage failed after retries");
1231
+ }
1232
+ async updateMessageSafe(client, route, ts, text) {
1233
+ try {
1234
+ await this.updateWithRetry(client, route, ts, text);
1235
+ } catch (err) {
1236
+ console.error("[Slack] Failed to update message:", err);
1237
+ await this.deleteMessageSafe(client, route, ts);
1238
+ await this.sendSafe(client, route, text);
1239
+ }
1240
+ }
1241
+ async updateWithRetry(client, route, ts, text, maxRetries = 3) {
1242
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1243
+ try {
1244
+ await client.chat.update({
1245
+ channel: route.channel,
1246
+ ts,
1247
+ text: formatSlackMessage(text),
1248
+ mrkdwn: true
1249
+ });
1250
+ return;
1251
+ } catch (err) {
1252
+ const retryAfter = this.getRetryAfterSeconds(err);
1253
+ if (retryAfter && attempt < maxRetries) {
1254
+ console.log(
1255
+ `[Slack] Rate limited while updating, retrying after ${retryAfter}s...`
1256
+ );
1257
+ await new Promise(
1258
+ (resolve) => setTimeout(resolve, retryAfter * 1e3)
1259
+ );
1260
+ continue;
1261
+ }
1262
+ throw err;
1263
+ }
1264
+ }
1265
+ }
1266
+ async deleteMessageSafe(client, route, ts) {
1267
+ try {
1268
+ await client.chat.delete({
1269
+ channel: route.channel,
1270
+ ts
1271
+ });
1272
+ } catch (err) {
1273
+ console.error("[Slack] Failed to delete placeholder message:", err);
1274
+ }
1275
+ }
1276
+ isSlackMessageWithinLimit(text) {
1277
+ return formatSlackMessage(text).length <= MAX_MESSAGE_LENGTH2;
1278
+ }
1279
+ findSlackSafeSplitPoint(text) {
1280
+ const preferredBreaks = ["\n\n", "\n", " "];
1281
+ const minSplit = Math.floor(MAX_MESSAGE_LENGTH2 * 0.3);
1282
+ for (const token of preferredBreaks) {
1283
+ const index = this.findBestSlackSplitBefore(text, token);
1284
+ if (index >= minSplit) {
1285
+ return index;
1286
+ }
1287
+ }
1288
+ let low = 1;
1289
+ let high = text.length;
1290
+ let best = Math.min(text.length, MAX_MESSAGE_LENGTH2);
1291
+ while (low <= high) {
1292
+ const mid = Math.floor((low + high) / 2);
1293
+ const candidate = text.slice(0, mid);
1294
+ if (this.isSlackMessageWithinLimit(candidate)) {
1295
+ best = mid;
1296
+ low = mid + 1;
1297
+ } else {
1298
+ high = mid - 1;
1299
+ }
1300
+ }
1301
+ return Math.max(1, best);
1302
+ }
1303
+ findBestSlackSplitBefore(text, token) {
1304
+ let fromIndex = Math.min(text.length, MAX_MESSAGE_LENGTH2);
1305
+ while (fromIndex > 0) {
1306
+ const index = text.lastIndexOf(token, fromIndex);
1307
+ if (index < 0) {
1308
+ return -1;
1309
+ }
1310
+ const splitAt = index + token.length;
1311
+ if (this.isSlackMessageWithinLimit(text.slice(0, splitAt))) {
1312
+ return splitAt;
1313
+ }
1314
+ fromIndex = index - 1;
1315
+ }
1316
+ return -1;
1082
1317
  }
1083
1318
  async tryAckReaction(client, event) {
1084
1319
  try {
@@ -1196,12 +1431,12 @@ var init_slack = __esm({
1196
1431
  */
1197
1432
  async sendFileSafe(client, route, filePath, caption) {
1198
1433
  try {
1199
- if (!fs12.existsSync(filePath)) {
1434
+ if (!fs14.existsSync(filePath)) {
1200
1435
  console.error(`[Slack] File not found for sending: ${filePath}`);
1201
1436
  return;
1202
1437
  }
1203
- const filename = path11.basename(filePath);
1204
- const fileContent = fs12.readFileSync(filePath);
1438
+ const filename = path13.basename(filePath);
1439
+ const fileContent = fs14.readFileSync(filePath);
1205
1440
  await client.files.uploadV2({
1206
1441
  channel_id: route.channel,
1207
1442
  thread_ts: route.threadTs,
@@ -1218,20 +1453,6 @@ var init_slack = __esm({
1218
1453
  }
1219
1454
  });
1220
1455
 
1221
- // src/runtime/adapters/types.ts
1222
- var types_exports = {};
1223
- __export(types_exports, {
1224
- isMessageSender: () => isMessageSender
1225
- });
1226
- function isMessageSender(adapter) {
1227
- return typeof adapter.sendMessage === "function";
1228
- }
1229
- var init_types = __esm({
1230
- "src/runtime/adapters/types.ts"() {
1231
- "use strict";
1232
- }
1233
- });
1234
-
1235
1456
  // src/runtime/adapters/scheduler.ts
1236
1457
  var scheduler_exports = {};
1237
1458
  __export(scheduler_exports, {
@@ -2242,15 +2463,15 @@ async function interactiveCreate(workDir) {
2242
2463
  }
2243
2464
 
2244
2465
  // src/commands/run.ts
2245
- import path13 from "path";
2246
- import fs14 from "fs";
2466
+ import path15 from "path";
2467
+ import fs16 from "fs";
2247
2468
  import inquirer2 from "inquirer";
2248
2469
  import chalk4 from "chalk";
2249
2470
 
2250
2471
  // src/runtime/server.ts
2251
2472
  import express from "express";
2252
- import path12 from "path";
2253
- import fs13 from "fs";
2473
+ import path14 from "path";
2474
+ import fs15 from "fs";
2254
2475
  import { fileURLToPath as fileURLToPath2 } from "url";
2255
2476
  import { createServer } from "http";
2256
2477
  import { exec } from "child_process";
@@ -2258,8 +2479,8 @@ import { exec } from "child_process";
2258
2479
  // src/runtime/agent.ts
2259
2480
  init_config();
2260
2481
  init_attachment_utils();
2261
- import path8 from "path";
2262
- import fs8 from "fs";
2482
+ import path9 from "path";
2483
+ import fs9 from "fs";
2263
2484
  import { fileURLToPath } from "url";
2264
2485
  import {
2265
2486
  AuthStorage,
@@ -5115,6 +5336,67 @@ ${lines.join("\n")}`
5115
5336
  };
5116
5337
  }
5117
5338
 
5339
+ // src/runtime/commands/help-command.ts
5340
+ init_commands();
5341
+ import fs8 from "fs";
5342
+ import path8 from "path";
5343
+ function buildHelpMessage(rootDir) {
5344
+ const sections = [];
5345
+ const commands = getVisibleCommands();
5346
+ const commandLines = commands.map(
5347
+ (cmd) => `- \`/${cmd.command}\` \u2014 ${cmd.description}`
5348
+ );
5349
+ sections.push(`\u{1F4CB} **Available Commands**
5350
+
5351
+ ${commandLines.join("\n")}`);
5352
+ const configPath = path8.resolve(rootDir, "skillpack.json");
5353
+ const skills = readInstalledSkills(configPath);
5354
+ if (skills.length > 0) {
5355
+ const skillLines = skills.map(
5356
+ (skill) => `- **${skill.name}**${skill.description ? ` \u2014 ${skill.description}` : ""}`
5357
+ );
5358
+ sections.push(
5359
+ `\u{1F9E9} **Installed Skills** (${skills.length})
5360
+
5361
+ ${skillLines.join("\n")}`
5362
+ );
5363
+ } else {
5364
+ sections.push("\u{1F9E9} **Installed Skills**\nNo skills installed.");
5365
+ }
5366
+ sections.push(
5367
+ [
5368
+ "\u23F0 **Scheduled Tasks**",
5369
+ "",
5370
+ "You can set up recurring tasks using natural language. For example:",
5371
+ "",
5372
+ '- "Send me a daily market briefing every morning at 9 AM"',
5373
+ `- "Summarize this week's trading data every Friday at 6 PM"`,
5374
+ '- "Check for new announcements every 30 minutes"',
5375
+ "",
5376
+ "I will handle the cron scheduling automatically."
5377
+ ].join("\n")
5378
+ );
5379
+ return sections.join("\n\n");
5380
+ }
5381
+ function handleHelpCommand(rootDir) {
5382
+ return {
5383
+ success: true,
5384
+ message: buildHelpMessage(rootDir)
5385
+ };
5386
+ }
5387
+ function readInstalledSkills(configPath) {
5388
+ if (!fs8.existsSync(configPath)) {
5389
+ return [];
5390
+ }
5391
+ try {
5392
+ const raw = fs8.readFileSync(configPath, "utf-8");
5393
+ const config = JSON.parse(raw);
5394
+ return Array.isArray(config.skills) ? config.skills : [];
5395
+ } catch {
5396
+ return [];
5397
+ }
5398
+ }
5399
+
5118
5400
  // src/runtime/agent.ts
5119
5401
  var DEBUG = true;
5120
5402
  var log = (...args) => DEBUG && console.log(...args);
@@ -5127,24 +5409,24 @@ var BUILTIN_SKILL_CREATOR_TEMPLATE_DIR = fileURLToPath(
5127
5409
  var PACK_AGENTS_FILE = "AGENTS.md";
5128
5410
  var PACK_SOUL_FILE = "SOUL.md";
5129
5411
  function materializeBuiltinSkillCreator(rootDir, skillsPath) {
5130
- if (!fs8.existsSync(BUILTIN_SKILL_CREATOR_TEMPLATE_DIR)) {
5412
+ if (!fs9.existsSync(BUILTIN_SKILL_CREATOR_TEMPLATE_DIR)) {
5131
5413
  log(
5132
5414
  `[PackAgent] Built-in skill-creator template missing: ${BUILTIN_SKILL_CREATOR_TEMPLATE_DIR}`
5133
5415
  );
5134
5416
  return null;
5135
5417
  }
5136
- const packConfigPath = path8.resolve(rootDir, "skillpack.json");
5137
- const skillDir = path8.resolve(skillsPath, BUILTIN_SKILL_CREATOR_NAME);
5138
- const skillPath = path8.join(skillDir, "SKILL.md");
5418
+ const packConfigPath = path9.resolve(rootDir, "skillpack.json");
5419
+ const skillDir = path9.resolve(skillsPath, BUILTIN_SKILL_CREATOR_NAME);
5420
+ const skillPath = path9.join(skillDir, "SKILL.md");
5139
5421
  const renderTemplate = (content) => content.replaceAll("{{SKILLS_PATH}}", skillsPath).replaceAll("{{PACK_CONFIG_PATH}}", packConfigPath);
5140
5422
  const copyDir = (srcDir, destDir) => {
5141
- fs8.mkdirSync(destDir, { recursive: true });
5142
- for (const entry of fs8.readdirSync(srcDir, { withFileTypes: true })) {
5423
+ fs9.mkdirSync(destDir, { recursive: true });
5424
+ for (const entry of fs9.readdirSync(srcDir, { withFileTypes: true })) {
5143
5425
  if (entry.name === ".DS_Store") {
5144
5426
  continue;
5145
5427
  }
5146
- const srcPath = path8.join(srcDir, entry.name);
5147
- const destPath = path8.join(destDir, entry.name);
5428
+ const srcPath = path9.join(srcDir, entry.name);
5429
+ const destPath = path9.join(destDir, entry.name);
5148
5430
  if (entry.isDirectory()) {
5149
5431
  copyDir(srcPath, destPath);
5150
5432
  continue;
@@ -5153,17 +5435,17 @@ function materializeBuiltinSkillCreator(rootDir, skillsPath) {
5153
5435
  continue;
5154
5436
  }
5155
5437
  if (entry.name.endsWith(".md") || entry.name.endsWith(".py")) {
5156
- const content = fs8.readFileSync(srcPath, "utf-8");
5157
- fs8.writeFileSync(destPath, renderTemplate(content), "utf-8");
5438
+ const content = fs9.readFileSync(srcPath, "utf-8");
5439
+ fs9.writeFileSync(destPath, renderTemplate(content), "utf-8");
5158
5440
  continue;
5159
5441
  }
5160
- fs8.copyFileSync(srcPath, destPath);
5442
+ fs9.copyFileSync(srcPath, destPath);
5161
5443
  }
5162
5444
  };
5163
- if (!fs8.existsSync(skillDir)) {
5445
+ if (!fs9.existsSync(skillDir)) {
5164
5446
  copyDir(BUILTIN_SKILL_CREATOR_TEMPLATE_DIR, skillDir);
5165
5447
  }
5166
- if (!fs8.existsSync(skillPath)) {
5448
+ if (!fs9.existsSync(skillPath)) {
5167
5449
  log(
5168
5450
  `[PackAgent] Materialized built-in skill-creator but SKILL.md is missing: ${skillPath}`
5169
5451
  );
@@ -5191,11 +5473,11 @@ function overrideBuiltinSkillCreator(base, materializedSkill) {
5191
5473
  };
5192
5474
  }
5193
5475
  function readOptionalPackPromptFile(filePath) {
5194
- if (!fs8.existsSync(filePath)) {
5476
+ if (!fs9.existsSync(filePath)) {
5195
5477
  return void 0;
5196
5478
  }
5197
5479
  try {
5198
- const content = fs8.readFileSync(filePath, "utf-8").trim();
5480
+ const content = fs9.readFileSync(filePath, "utf-8").trim();
5199
5481
  return content.length > 0 ? content : void 0;
5200
5482
  } catch (error) {
5201
5483
  console.warn(`[PackAgent] Warning: Could not read ${filePath}:`, error);
@@ -5203,8 +5485,8 @@ function readOptionalPackPromptFile(filePath) {
5203
5485
  }
5204
5486
  }
5205
5487
  function buildPackPromptBlock(rootDir) {
5206
- const agentsPath = path8.resolve(rootDir, PACK_AGENTS_FILE);
5207
- const soulPath = path8.resolve(rootDir, PACK_SOUL_FILE);
5488
+ const agentsPath = path9.resolve(rootDir, PACK_AGENTS_FILE);
5489
+ const soulPath = path9.resolve(rootDir, PACK_SOUL_FILE);
5208
5490
  const agentsContent = readOptionalPackPromptFile(agentsPath);
5209
5491
  const soulContent = readOptionalPackPromptFile(soulPath);
5210
5492
  if (!agentsContent && !soulContent) {
@@ -5270,7 +5552,7 @@ var PackAgent = class {
5270
5552
  authStorage;
5271
5553
  constructor(options) {
5272
5554
  this.options = options;
5273
- const configPath = path8.resolve(options.rootDir, "data", "config.json");
5555
+ const configPath = path9.resolve(options.rootDir, "data", "config.json");
5274
5556
  const backend = new ConfigFileAuthBackend(configPath);
5275
5557
  this.authStorage = AuthStorage.fromStorage(backend);
5276
5558
  const providerMeta = SUPPORTED_PROVIDERS[options.provider];
@@ -5321,24 +5603,24 @@ var PackAgent = class {
5321
5603
  if (resolvedModel && baseUrl) {
5322
5604
  log(`[PackAgent] Overriding ${provider}/${modelId} baseUrl -> ${baseUrl}`);
5323
5605
  }
5324
- const sessionDir = path8.resolve(
5606
+ const sessionDir = path9.resolve(
5325
5607
  rootDir,
5326
5608
  "data",
5327
5609
  "sessions",
5328
5610
  channelId
5329
5611
  );
5330
- fs8.mkdirSync(sessionDir, { recursive: true });
5612
+ fs9.mkdirSync(sessionDir, { recursive: true });
5331
5613
  const sessionManager = SessionManager.continueRecent(rootDir, sessionDir);
5332
5614
  log(`[PackAgent] Session dir: ${sessionDir}`);
5333
- const workspaceDir = path8.resolve(
5615
+ const workspaceDir = path9.resolve(
5334
5616
  rootDir,
5335
5617
  "data",
5336
5618
  "workspaces",
5337
5619
  channelId
5338
5620
  );
5339
- fs8.mkdirSync(workspaceDir, { recursive: true });
5621
+ fs9.mkdirSync(workspaceDir, { recursive: true });
5340
5622
  log(`[PackAgent] Workspace dir: ${workspaceDir}`);
5341
- const skillsPath = path8.resolve(rootDir, "skills");
5623
+ const skillsPath = path9.resolve(rootDir, "skills");
5342
5624
  log(`[PackAgent] Loading skills from: ${skillsPath}`);
5343
5625
  const materializedSkillCreator = materializeBuiltinSkillCreator(
5344
5626
  rootDir,
@@ -5529,6 +5811,8 @@ ${text}`;
5529
5811
  }
5530
5812
  async handleCommand(command, channelId) {
5531
5813
  switch (command) {
5814
+ case "help":
5815
+ return handleHelpCommand(this.options.rootDir);
5532
5816
  case "new":
5533
5817
  case "clear": {
5534
5818
  const cs = this.channels.get(channelId);
@@ -5537,9 +5821,9 @@ ${text}`;
5537
5821
  this.channels.delete(channelId);
5538
5822
  }
5539
5823
  const { rootDir } = this.options;
5540
- const sessionDir = path8.resolve(rootDir, "data", "sessions", channelId);
5541
- if (fs8.existsSync(sessionDir)) {
5542
- fs8.rmSync(sessionDir, { recursive: true, force: true });
5824
+ const sessionDir = path9.resolve(rootDir, "data", "sessions", channelId);
5825
+ if (fs9.existsSync(sessionDir)) {
5826
+ fs9.rmSync(sessionDir, { recursive: true, force: true });
5543
5827
  log(`[PackAgent] Cleared session dir: ${sessionDir}`);
5544
5828
  }
5545
5829
  return {
@@ -5584,26 +5868,27 @@ ${text}`;
5584
5868
  /** Reserved: restore a historical session */
5585
5869
  async restoreSession(_sessionId) {
5586
5870
  }
5871
+ getActiveChannelIds() {
5872
+ return Array.from(this.channels.keys());
5873
+ }
5587
5874
  };
5588
5875
 
5589
5876
  // src/runtime/adapters/web.ts
5590
5877
  init_config();
5591
- import fs9 from "fs";
5592
- import path9 from "path";
5878
+ init_commands();
5879
+ import fs10 from "fs";
5880
+ import path10 from "path";
5593
5881
  import { WebSocketServer } from "ws";
5594
5882
  function getPackConfig(rootDir) {
5595
- const raw = fs9.readFileSync(path9.join(rootDir, "skillpack.json"), "utf-8");
5883
+ const raw = fs10.readFileSync(path10.join(rootDir, "skillpack.json"), "utf-8");
5596
5884
  return JSON.parse(raw);
5597
5885
  }
5598
- var COMMANDS = {
5599
- "/new": "new",
5600
- "/clear": "clear",
5601
- "/restart": "restart",
5602
- "/shutdown": "shutdown"
5603
- };
5604
5886
  function parseCommand(text) {
5605
- const trimmed = text.trim().toLowerCase();
5606
- return COMMANDS[trimmed] ?? null;
5887
+ return resolveCommand(text.trim().toLowerCase());
5888
+ }
5889
+ function sendWsEvent(ws, event) {
5890
+ if (ws.readyState !== ws.OPEN) return;
5891
+ ws.send(JSON.stringify(event));
5607
5892
  }
5608
5893
  function getRuntimeConfigSignature(config) {
5609
5894
  return JSON.stringify({
@@ -5619,9 +5904,11 @@ var WebAdapter = class {
5619
5904
  name = "web";
5620
5905
  wss = null;
5621
5906
  agent = null;
5907
+ ipcBroadcaster = null;
5622
5908
  async start(ctx) {
5623
5909
  const { agent, server, app, rootDir, lifecycle } = ctx;
5624
5910
  this.agent = agent;
5911
+ this.ipcBroadcaster = ctx.ipcBroadcaster ?? null;
5625
5912
  const currentConf = configManager.getConfig();
5626
5913
  let apiKey = currentConf.apiKey || "";
5627
5914
  let currentProvider = currentConf.provider || "openai";
@@ -5744,23 +6031,23 @@ var WebAdapter = class {
5744
6031
  res.status(400).json({ error: "Missing 'path' query parameter" });
5745
6032
  return;
5746
6033
  }
5747
- const resolvedPath = path9.resolve(filePath);
5748
- const dataDir = path9.resolve(rootDir, "data");
6034
+ const resolvedPath = path10.resolve(filePath);
6035
+ const dataDir = path10.resolve(rootDir, "data");
5749
6036
  if (!resolvedPath.startsWith(dataDir)) {
5750
6037
  res.status(403).json({ error: "Access denied" });
5751
6038
  return;
5752
6039
  }
5753
- if (!fs9.existsSync(resolvedPath)) {
6040
+ if (!fs10.existsSync(resolvedPath)) {
5754
6041
  res.status(404).json({ error: "File not found" });
5755
6042
  return;
5756
6043
  }
5757
- const filename = path9.basename(resolvedPath);
6044
+ const filename = path10.basename(resolvedPath);
5758
6045
  res.setHeader("Content-Type", "application/octet-stream");
5759
6046
  res.setHeader(
5760
6047
  "Content-Disposition",
5761
6048
  `attachment; filename="${filename}"`
5762
6049
  );
5763
- fs9.createReadStream(resolvedPath).pipe(res);
6050
+ fs10.createReadStream(resolvedPath).pipe(res);
5764
6051
  });
5765
6052
  const getScheduler = () => {
5766
6053
  const schedulerAdapter = ctx.adapterMap?.get("scheduler");
@@ -5881,21 +6168,15 @@ var WebAdapter = class {
5881
6168
  const command = parseCommand(text);
5882
6169
  if (command) {
5883
6170
  const result2 = await agent.handleCommand(command, channelId);
5884
- ws.send(
5885
- JSON.stringify({
5886
- type: "command_result",
5887
- command,
5888
- ...result2
5889
- })
5890
- );
5891
- if (command === "clear" || command === "new") {
5892
- ws.send(JSON.stringify({ done: true }));
6171
+ if (result2.message) {
6172
+ sendWsEvent(ws, { type: "text_delta", delta: result2.message });
5893
6173
  }
6174
+ ws.send(JSON.stringify({ done: true }));
5894
6175
  return;
5895
6176
  }
5896
6177
  const onEvent = (event) => {
5897
- if (ws.readyState !== ws.OPEN) return;
5898
- ws.send(JSON.stringify(event));
6178
+ sendWsEvent(ws, event);
6179
+ this.ipcBroadcaster?.broadcastAgentEvent(channelId, event);
5899
6180
  };
5900
6181
  const result = await agent.handleMessage("web", channelId, text, onEvent);
5901
6182
  if (result.errorMessage) {
@@ -5913,6 +6194,367 @@ var WebAdapter = class {
5913
6194
  }
5914
6195
  };
5915
6196
 
6197
+ // src/runtime/adapters/ipc.ts
6198
+ init_config();
6199
+
6200
+ // src/runtime/services/conversation.ts
6201
+ import fs11 from "fs";
6202
+ import path11 from "path";
6203
+ import {
6204
+ parseSessionEntries
6205
+ } from "@mariozechner/pi-coding-agent";
6206
+ var ConversationService = class {
6207
+ constructor(rootDir) {
6208
+ this.rootDir = rootDir;
6209
+ }
6210
+ /**
6211
+ * Scan data/sessions and return conversation summaries sorted by recency.
6212
+ */
6213
+ listConversations(activeChannels) {
6214
+ const sessionsDir = path11.resolve(this.rootDir, "data", "sessions");
6215
+ const channelIds = new Set(activeChannels);
6216
+ if (fs11.existsSync(sessionsDir)) {
6217
+ for (const entry of fs11.readdirSync(sessionsDir)) {
6218
+ const channelDir = path11.join(sessionsDir, entry);
6219
+ try {
6220
+ if (fs11.statSync(channelDir).isDirectory()) {
6221
+ channelIds.add(entry);
6222
+ }
6223
+ } catch {
6224
+ }
6225
+ }
6226
+ }
6227
+ const results = [];
6228
+ for (const channelId of channelIds) {
6229
+ const channelDir = path11.join(sessionsDir, channelId);
6230
+ const sessionFile = this.findLatestSessionFile(channelDir);
6231
+ let messageCount = 0;
6232
+ let lastMessageAt = "";
6233
+ let lastMessagePreview = "";
6234
+ if (sessionFile) {
6235
+ const entries = this.loadEntries(sessionFile);
6236
+ const messages = entries.filter(
6237
+ (entry) => entry.type === "message"
6238
+ );
6239
+ messageCount = messages.length;
6240
+ const lastMessage = messages[messages.length - 1];
6241
+ if (lastMessage) {
6242
+ lastMessageAt = lastMessage.timestamp;
6243
+ lastMessagePreview = this.extractTextPreview(lastMessage, 100);
6244
+ }
6245
+ }
6246
+ results.push({
6247
+ channelId,
6248
+ platform: this.detectPlatform(channelId),
6249
+ sessionFile,
6250
+ messageCount,
6251
+ lastMessageAt,
6252
+ lastMessagePreview
6253
+ });
6254
+ }
6255
+ return results.sort((a, b) => {
6256
+ const recency = (b.lastMessageAt || "").localeCompare(a.lastMessageAt || "");
6257
+ if (recency !== 0) return recency;
6258
+ return a.channelId.localeCompare(b.channelId);
6259
+ });
6260
+ }
6261
+ /**
6262
+ * Load latest messages for a channel in a simplified format.
6263
+ */
6264
+ getMessages(channelId, limit = 100) {
6265
+ const channelDir = path11.resolve(
6266
+ this.rootDir,
6267
+ "data",
6268
+ "sessions",
6269
+ channelId
6270
+ );
6271
+ const sessionFile = this.findLatestSessionFile(channelDir);
6272
+ if (!sessionFile) return [];
6273
+ const safeLimit = Number.isFinite(limit) ? Math.max(0, Math.floor(limit)) : 100;
6274
+ if (safeLimit === 0) return [];
6275
+ const entries = this.loadEntries(sessionFile);
6276
+ const messages = [];
6277
+ for (const entry of entries) {
6278
+ if (entry.type !== "message") continue;
6279
+ const role = entry.message?.role;
6280
+ if (role !== "user" && role !== "assistant") continue;
6281
+ const text = this.extractText(entry.message);
6282
+ if (!text) continue;
6283
+ const toolCalls = role === "assistant" ? this.extractToolCallSummaries(entry.message) : void 0;
6284
+ messages.push({
6285
+ id: entry.id,
6286
+ role,
6287
+ text,
6288
+ timestamp: entry.timestamp,
6289
+ toolCalls
6290
+ });
6291
+ }
6292
+ return messages.slice(-safeLimit);
6293
+ }
6294
+ findLatestSessionFile(channelDir) {
6295
+ if (!fs11.existsSync(channelDir)) return null;
6296
+ let stats;
6297
+ try {
6298
+ stats = fs11.statSync(channelDir);
6299
+ } catch {
6300
+ return null;
6301
+ }
6302
+ if (!stats.isDirectory()) return null;
6303
+ const files = fs11.readdirSync(channelDir).filter((file) => file.endsWith(".jsonl")).sort((a, b) => b.localeCompare(a));
6304
+ return files[0] ? path11.join(channelDir, files[0]) : null;
6305
+ }
6306
+ loadEntries(filePath) {
6307
+ try {
6308
+ const content = fs11.readFileSync(filePath, "utf-8");
6309
+ const fileEntries = parseSessionEntries(content);
6310
+ return fileEntries.filter((entry) => entry.type !== "session");
6311
+ } catch (err) {
6312
+ console.warn(`[ConversationService] Failed to load ${filePath}:`, err);
6313
+ return [];
6314
+ }
6315
+ }
6316
+ extractText(message) {
6317
+ if (!message?.content) return "";
6318
+ if (typeof message.content === "string") return message.content.trim();
6319
+ if (!Array.isArray(message.content)) return "";
6320
+ return message.content.filter((item) => item?.type === "text").map((item) => typeof item?.text === "string" ? item.text : "").join("").trim();
6321
+ }
6322
+ extractTextPreview(entry, maxLen) {
6323
+ const text = this.extractText(entry.message);
6324
+ return text.length > maxLen ? `${text.slice(0, maxLen)}\u2026` : text;
6325
+ }
6326
+ extractToolCallSummaries(message) {
6327
+ if (!Array.isArray(message?.content)) return void 0;
6328
+ const toolCalls = message.content.filter((item) => item?.type === "toolCall").map((item) => ({
6329
+ name: typeof item?.name === "string" && item.name ? item.name : "unknown",
6330
+ isError: false
6331
+ }));
6332
+ return toolCalls.length > 0 ? toolCalls : void 0;
6333
+ }
6334
+ detectPlatform(channelId) {
6335
+ if (channelId.startsWith("telegram-")) return "telegram";
6336
+ if (channelId.startsWith("slack-")) return "slack";
6337
+ if (channelId.startsWith("scheduler-")) return "scheduler";
6338
+ return "web";
6339
+ }
6340
+ };
6341
+
6342
+ // src/runtime/adapters/ipc.ts
6343
+ init_types();
6344
+ var IpcAdapter = class {
6345
+ name = "ipc";
6346
+ agent = null;
6347
+ rootDir = "";
6348
+ adapterMap = null;
6349
+ conversationService = null;
6350
+ messageListener;
6351
+ started = false;
6352
+ async start(ctx) {
6353
+ if (typeof process.send !== "function") {
6354
+ return;
6355
+ }
6356
+ this.agent = ctx.agent;
6357
+ this.rootDir = ctx.rootDir;
6358
+ this.adapterMap = ctx.adapterMap ?? null;
6359
+ this.conversationService = new ConversationService(ctx.rootDir);
6360
+ this.messageListener = (message) => {
6361
+ if (!this.isIpcRequest(message)) return;
6362
+ void this.handleRequest(message);
6363
+ };
6364
+ process.on("message", this.messageListener);
6365
+ this.started = true;
6366
+ console.log("[IpcAdapter] Started");
6367
+ }
6368
+ async stop() {
6369
+ if (this.messageListener) {
6370
+ process.off("message", this.messageListener);
6371
+ this.messageListener = void 0;
6372
+ }
6373
+ if (this.started) {
6374
+ console.log("[IpcAdapter] Stopped");
6375
+ }
6376
+ this.started = false;
6377
+ }
6378
+ notifyReady(port) {
6379
+ this.sendIpc({
6380
+ type: "ready",
6381
+ port
6382
+ });
6383
+ }
6384
+ broadcastInbound(channelId, platform, sender, text) {
6385
+ this.sendIpc({
6386
+ type: "inbound_message",
6387
+ channelId,
6388
+ platform,
6389
+ sender,
6390
+ text,
6391
+ timestamp: Date.now()
6392
+ });
6393
+ }
6394
+ broadcastAgentEvent(channelId, event) {
6395
+ this.sendIpc({
6396
+ type: "agent_event",
6397
+ channelId,
6398
+ event
6399
+ });
6400
+ }
6401
+ isIpcRequest(message) {
6402
+ if (!message || typeof message !== "object") return false;
6403
+ const maybe = message;
6404
+ return typeof maybe.id === "string" && typeof maybe.type === "string";
6405
+ }
6406
+ async handleRequest(request) {
6407
+ if (!this.agent || !this.conversationService) {
6408
+ this.replyError(request.id, "IPC adapter is not ready yet");
6409
+ return;
6410
+ }
6411
+ try {
6412
+ switch (request.type) {
6413
+ case "get_conversations": {
6414
+ const activeChannels = new Set(this.agent.getActiveChannelIds());
6415
+ const conversations = this.conversationService.listConversations(activeChannels);
6416
+ this.reply(request.id, conversations);
6417
+ return;
6418
+ }
6419
+ case "get_messages": {
6420
+ if (!request.channelId || typeof request.channelId !== "string") {
6421
+ this.replyError(request.id, "channelId is required");
6422
+ return;
6423
+ }
6424
+ const messages = this.conversationService.getMessages(
6425
+ request.channelId,
6426
+ request.limit ?? 100
6427
+ );
6428
+ this.reply(request.id, messages);
6429
+ return;
6430
+ }
6431
+ case "send_message": {
6432
+ if (!request.channelId || typeof request.channelId !== "string") {
6433
+ this.replyError(request.id, "channelId is required");
6434
+ return;
6435
+ }
6436
+ if (typeof request.text !== "string") {
6437
+ this.replyError(request.id, "text is required");
6438
+ return;
6439
+ }
6440
+ const platform = this.detectPlatform(request.channelId);
6441
+ let fullText = "";
6442
+ const result = await this.agent.handleMessage(
6443
+ platform,
6444
+ request.channelId,
6445
+ request.text,
6446
+ (event) => {
6447
+ if (event.type === "text_delta") {
6448
+ fullText += event.delta;
6449
+ }
6450
+ this.broadcastAgentEvent(request.channelId, event);
6451
+ }
6452
+ );
6453
+ if (fullText.trim() && platform !== "web" && platform !== "scheduler") {
6454
+ const adapter = this.adapterMap?.get(platform);
6455
+ if (adapter && isMessageSender(adapter)) {
6456
+ await adapter.sendMessage(request.channelId, fullText);
6457
+ }
6458
+ }
6459
+ this.reply(request.id, {
6460
+ ...result,
6461
+ text: fullText
6462
+ });
6463
+ return;
6464
+ }
6465
+ case "command": {
6466
+ if (!request.channelId || typeof request.channelId !== "string") {
6467
+ this.replyError(request.id, "channelId is required");
6468
+ return;
6469
+ }
6470
+ const result = await this.agent.handleCommand(request.command, request.channelId);
6471
+ this.reply(request.id, result);
6472
+ return;
6473
+ }
6474
+ case "get_config": {
6475
+ this.reply(request.id, configManager.getConfig());
6476
+ return;
6477
+ }
6478
+ case "update_config": {
6479
+ configManager.save(this.rootDir, request.updates || {});
6480
+ const updated = configManager.getConfig();
6481
+ const provider = updated.provider || "openai";
6482
+ this.agent.updateAuth(provider, updated.apiKey);
6483
+ this.reply(request.id, updated);
6484
+ return;
6485
+ }
6486
+ case "get_status": {
6487
+ this.reply(request.id, {
6488
+ status: "running",
6489
+ pid: process.pid
6490
+ });
6491
+ return;
6492
+ }
6493
+ case "get_scheduled_jobs": {
6494
+ const scheduler = this.getSchedulerAdapter();
6495
+ this.reply(request.id, scheduler ? scheduler.listJobs() : []);
6496
+ return;
6497
+ }
6498
+ case "add_scheduled_job": {
6499
+ const scheduler = this.getSchedulerAdapter();
6500
+ if (!scheduler) {
6501
+ this.replyError(request.id, "Scheduler adapter is not available");
6502
+ return;
6503
+ }
6504
+ const result = scheduler.addJob(request.job);
6505
+ if (!result.success) {
6506
+ this.replyError(request.id, result.message);
6507
+ return;
6508
+ }
6509
+ this.reply(request.id, result);
6510
+ return;
6511
+ }
6512
+ case "remove_scheduled_job": {
6513
+ const scheduler = this.getSchedulerAdapter();
6514
+ if (!scheduler) {
6515
+ this.replyError(request.id, "Scheduler adapter is not available");
6516
+ return;
6517
+ }
6518
+ const result = scheduler.removeJob(request.name);
6519
+ if (!result.success) {
6520
+ this.replyError(request.id, result.message);
6521
+ return;
6522
+ }
6523
+ this.reply(request.id, result);
6524
+ return;
6525
+ }
6526
+ }
6527
+ } catch (err) {
6528
+ this.replyError(
6529
+ request.id,
6530
+ err instanceof Error ? err.message : String(err)
6531
+ );
6532
+ }
6533
+ }
6534
+ getSchedulerAdapter() {
6535
+ const adapter = this.adapterMap?.get("scheduler");
6536
+ if (!adapter) return null;
6537
+ return adapter;
6538
+ }
6539
+ detectPlatform(channelId) {
6540
+ if (channelId.startsWith("telegram-")) return "telegram";
6541
+ if (channelId.startsWith("slack-")) return "slack";
6542
+ if (channelId.startsWith("scheduler-")) return "scheduler";
6543
+ return "web";
6544
+ }
6545
+ sendIpc(payload) {
6546
+ if (typeof process.send === "function") {
6547
+ process.send(payload);
6548
+ }
6549
+ }
6550
+ reply(id, data) {
6551
+ this.sendIpc({ id, type: "result", data });
6552
+ }
6553
+ replyError(id, message) {
6554
+ this.sendIpc({ id, type: "error", message });
6555
+ }
6556
+ };
6557
+
5916
6558
  // src/runtime/server.ts
5917
6559
  init_config();
5918
6560
 
@@ -5989,28 +6631,28 @@ var Lifecycle = class {
5989
6631
 
5990
6632
  // src/runtime/registry.ts
5991
6633
  import crypto from "crypto";
5992
- import fs10 from "fs";
6634
+ import fs12 from "fs";
5993
6635
  import os from "os";
5994
- import path10 from "path";
5995
- var SKILLPACK_HOME = path10.join(os.homedir(), ".skillpack");
5996
- var LEGACY_REGISTRY_FILE = path10.join(SKILLPACK_HOME, "registry.json");
5997
- var REGISTRY_DIR = path10.join(SKILLPACK_HOME, "registry.d");
6636
+ import path12 from "path";
6637
+ var SKILLPACK_HOME = path12.join(os.homedir(), ".skillpack");
6638
+ var LEGACY_REGISTRY_FILE = path12.join(SKILLPACK_HOME, "registry.json");
6639
+ var REGISTRY_DIR = path12.join(SKILLPACK_HOME, "registry.d");
5998
6640
  var migrationChecked = false;
5999
6641
  function ensureHomeDir() {
6000
- if (!fs10.existsSync(SKILLPACK_HOME)) {
6001
- fs10.mkdirSync(SKILLPACK_HOME, { recursive: true });
6642
+ if (!fs12.existsSync(SKILLPACK_HOME)) {
6643
+ fs12.mkdirSync(SKILLPACK_HOME, { recursive: true });
6002
6644
  }
6003
6645
  }
6004
6646
  function ensureRegistryDir() {
6005
6647
  ensureHomeDir();
6006
- if (!fs10.existsSync(REGISTRY_DIR)) {
6007
- fs10.mkdirSync(REGISTRY_DIR, { recursive: true });
6648
+ if (!fs12.existsSync(REGISTRY_DIR)) {
6649
+ fs12.mkdirSync(REGISTRY_DIR, { recursive: true });
6008
6650
  }
6009
6651
  }
6010
6652
  function canonicalizeDir(dir) {
6011
- const resolved = path10.resolve(dir);
6653
+ const resolved = path12.resolve(dir);
6012
6654
  try {
6013
- return fs10.realpathSync(resolved);
6655
+ return fs12.realpathSync(resolved);
6014
6656
  } catch {
6015
6657
  return resolved;
6016
6658
  }
@@ -6019,7 +6661,7 @@ function hashDir(dir) {
6019
6661
  return crypto.createHash("md5").update(canonicalizeDir(dir)).digest("hex");
6020
6662
  }
6021
6663
  function getEntryPathForCanonicalDir(dir) {
6022
- return path10.join(REGISTRY_DIR, `${hashDir(dir)}.json`);
6664
+ return path12.join(REGISTRY_DIR, `${hashDir(dir)}.json`);
6023
6665
  }
6024
6666
  function getEntryPath(dir) {
6025
6667
  ensureRegistryReady();
@@ -6027,11 +6669,11 @@ function getEntryPath(dir) {
6027
6669
  }
6028
6670
  function listEntryFiles() {
6029
6671
  ensureRegistryReady();
6030
- return fs10.readdirSync(REGISTRY_DIR).filter((file) => file.endsWith(".json")).sort().map((file) => path10.join(REGISTRY_DIR, file));
6672
+ return fs12.readdirSync(REGISTRY_DIR).filter((file) => file.endsWith(".json")).sort().map((file) => path12.join(REGISTRY_DIR, file));
6031
6673
  }
6032
6674
  function readEntryFile(filePath) {
6033
6675
  try {
6034
- const raw = fs10.readFileSync(filePath, "utf-8");
6676
+ const raw = fs12.readFileSync(filePath, "utf-8");
6035
6677
  const data = JSON.parse(raw);
6036
6678
  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") {
6037
6679
  return null;
@@ -6064,8 +6706,8 @@ function writeEntryFile(entry) {
6064
6706
  };
6065
6707
  const entryPath = getEntryPathForCanonicalDir(normalized.dir);
6066
6708
  const tmpPath = createTmpPath(entryPath);
6067
- fs10.writeFileSync(tmpPath, JSON.stringify(normalized, null, 2), "utf-8");
6068
- fs10.renameSync(tmpPath, entryPath);
6709
+ fs12.writeFileSync(tmpPath, JSON.stringify(normalized, null, 2), "utf-8");
6710
+ fs12.renameSync(tmpPath, entryPath);
6069
6711
  }
6070
6712
  function migrateLegacyRegistryIfNeeded() {
6071
6713
  if (migrationChecked) {
@@ -6073,14 +6715,14 @@ function migrateLegacyRegistryIfNeeded() {
6073
6715
  }
6074
6716
  migrationChecked = true;
6075
6717
  ensureRegistryDir();
6076
- if (!fs10.existsSync(LEGACY_REGISTRY_FILE)) {
6718
+ if (!fs12.existsSync(LEGACY_REGISTRY_FILE)) {
6077
6719
  return;
6078
6720
  }
6079
6721
  if (listEntryFiles().length > 0) {
6080
6722
  return;
6081
6723
  }
6082
6724
  try {
6083
- const raw = fs10.readFileSync(LEGACY_REGISTRY_FILE, "utf-8");
6725
+ const raw = fs12.readFileSync(LEGACY_REGISTRY_FILE, "utf-8");
6084
6726
  const data = JSON.parse(raw);
6085
6727
  const packs = Array.isArray(data?.packs) ? data.packs : [];
6086
6728
  for (const pack of packs) {
@@ -6093,7 +6735,7 @@ function migrateLegacyRegistryIfNeeded() {
6093
6735
  } catch {
6094
6736
  }
6095
6737
  }
6096
- fs10.renameSync(LEGACY_REGISTRY_FILE, `${LEGACY_REGISTRY_FILE}.legacy`);
6738
+ fs12.renameSync(LEGACY_REGISTRY_FILE, `${LEGACY_REGISTRY_FILE}.legacy`);
6097
6739
  } catch (err) {
6098
6740
  console.warn(" [Registry] Failed to migrate legacy registry.json:", err);
6099
6741
  }
@@ -6146,7 +6788,7 @@ function deregister(dir, pid) {
6146
6788
  }
6147
6789
 
6148
6790
  // src/runtime/server.ts
6149
- var __dirname = path12.dirname(fileURLToPath2(import.meta.url));
6791
+ var __dirname = path14.dirname(fileURLToPath2(import.meta.url));
6150
6792
  async function startServer(options) {
6151
6793
  const {
6152
6794
  rootDir,
@@ -6161,8 +6803,8 @@ async function startServer(options) {
6161
6803
  const packConfig = loadConfig(canonicalRootDir);
6162
6804
  const baseUrl = dataConfig.baseUrl?.trim() || void 0;
6163
6805
  const modelId = SUPPORTED_PROVIDERS[provider]?.defaultModelId ?? SUPPORTED_PROVIDERS.openai.defaultModelId;
6164
- const packageRoot = path12.resolve(__dirname, "..");
6165
- const webDir = fs13.existsSync(path12.join(rootDir, "web")) ? path12.join(rootDir, "web") : path12.join(packageRoot, "web");
6806
+ const packageRoot = path14.resolve(__dirname, "..");
6807
+ const webDir = fs15.existsSync(path14.join(rootDir, "web")) ? path14.join(rootDir, "web") : path14.join(packageRoot, "web");
6166
6808
  const app = express();
6167
6809
  app.use(express.json());
6168
6810
  app.use(express.static(webDir));
@@ -6190,8 +6832,31 @@ async function startServer(options) {
6190
6832
  });
6191
6833
  const adapters = [];
6192
6834
  const adapterMap = /* @__PURE__ */ new Map();
6835
+ const hasIpcChannel = typeof process.send === "function";
6836
+ const ipcAdapter = new IpcAdapter();
6837
+ if (hasIpcChannel) {
6838
+ await ipcAdapter.start({
6839
+ agent,
6840
+ server,
6841
+ app,
6842
+ rootDir,
6843
+ lifecycle,
6844
+ adapterMap
6845
+ });
6846
+ adapters.push(ipcAdapter);
6847
+ adapterMap.set(ipcAdapter.name, ipcAdapter);
6848
+ }
6849
+ const ipcBroadcaster = hasIpcChannel ? ipcAdapter : void 0;
6193
6850
  const webAdapter = new WebAdapter();
6194
- await webAdapter.start({ agent, server, app, rootDir, lifecycle, adapterMap });
6851
+ await webAdapter.start({
6852
+ agent,
6853
+ server,
6854
+ app,
6855
+ rootDir,
6856
+ lifecycle,
6857
+ adapterMap,
6858
+ ipcBroadcaster
6859
+ });
6195
6860
  adapters.push(webAdapter);
6196
6861
  adapterMap.set(webAdapter.name, webAdapter);
6197
6862
  if (dataConfig.adapters?.telegram?.token) {
@@ -6200,7 +6865,15 @@ async function startServer(options) {
6200
6865
  const telegramAdapter = new TelegramAdapter2({
6201
6866
  token: dataConfig.adapters.telegram.token
6202
6867
  });
6203
- await telegramAdapter.start({ agent, server, app, rootDir, lifecycle });
6868
+ await telegramAdapter.start({
6869
+ agent,
6870
+ server,
6871
+ app,
6872
+ rootDir,
6873
+ lifecycle,
6874
+ adapterMap,
6875
+ ipcBroadcaster
6876
+ });
6204
6877
  adapters.push(telegramAdapter);
6205
6878
  adapterMap.set(telegramAdapter.name, telegramAdapter);
6206
6879
  } catch (err) {
@@ -6220,7 +6893,15 @@ async function startServer(options) {
6220
6893
  botToken: slackConfig.botToken,
6221
6894
  appToken: slackConfig.appToken
6222
6895
  });
6223
- await slackAdapter.start({ agent, server, app, rootDir, lifecycle });
6896
+ await slackAdapter.start({
6897
+ agent,
6898
+ server,
6899
+ app,
6900
+ rootDir,
6901
+ lifecycle,
6902
+ adapterMap,
6903
+ ipcBroadcaster
6904
+ });
6224
6905
  adapters.push(slackAdapter);
6225
6906
  adapterMap.set(slackAdapter.name, slackAdapter);
6226
6907
  } catch (err) {
@@ -6283,6 +6964,9 @@ async function startServer(options) {
6283
6964
  } catch (err) {
6284
6965
  console.warn(" [Registry] Could not register pack:", err);
6285
6966
  }
6967
+ if (hasIpcChannel) {
6968
+ ipcAdapter.notifyReady(typeof actualPort === "number" ? actualPort : port);
6969
+ }
6286
6970
  if (!daemonRun) {
6287
6971
  const cmd = process.platform === "darwin" ? `open ${url}` : process.platform === "win32" ? `start ${url}` : `xdg-open ${url}`;
6288
6972
  exec(cmd, (err) => {
@@ -6330,23 +7014,23 @@ function findMissingSkills(workDir, config) {
6330
7014
  });
6331
7015
  }
6332
7016
  function copyStartTemplates2(workDir) {
6333
- const templateDir = path13.resolve(
7017
+ const templateDir = path15.resolve(
6334
7018
  new URL("../templates", import.meta.url).pathname
6335
7019
  );
6336
7020
  for (const file of ["start.sh", "start.bat"]) {
6337
- const src = path13.join(templateDir, file);
6338
- const dest = path13.join(workDir, file);
6339
- if (fs14.existsSync(src)) {
6340
- fs14.copyFileSync(src, dest);
7021
+ const src = path15.join(templateDir, file);
7022
+ const dest = path15.join(workDir, file);
7023
+ if (fs16.existsSync(src)) {
7024
+ fs16.copyFileSync(src, dest);
6341
7025
  if (file === "start.sh") {
6342
- fs14.chmodSync(dest, 493);
7026
+ fs16.chmodSync(dest, 493);
6343
7027
  }
6344
7028
  }
6345
7029
  }
6346
7030
  }
6347
7031
  async function runCommand(directory) {
6348
- const workDir = directory ? path13.resolve(directory) : process.cwd();
6349
- fs14.mkdirSync(workDir, { recursive: true });
7032
+ const workDir = directory ? path15.resolve(directory) : process.cwd();
7033
+ fs16.mkdirSync(workDir, { recursive: true });
6350
7034
  if (!configExists(workDir)) {
6351
7035
  console.log(chalk4.blue("\n No skillpack.json found. Let's set one up.\n"));
6352
7036
  const { name, description } = await inquirer2.prompt([
@@ -6391,9 +7075,9 @@ async function runCommand(directory) {
6391
7075
  }
6392
7076
 
6393
7077
  // src/cli.ts
6394
- import fs15 from "fs";
7078
+ import fs17 from "fs";
6395
7079
  var packageJson = JSON.parse(
6396
- fs15.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
7080
+ fs17.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
6397
7081
  );
6398
7082
  var program = new Command();
6399
7083
  program.name("skillpack").description("Assemble, package, and run Agent Skills packs").version(packageJson.version);