@geminixiang/mama 0.2.0-beta.6 → 0.2.0-beta.8

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.
Files changed (173) hide show
  1. package/README.md +20 -14
  2. package/dist/adapter.d.ts +5 -2
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/adapters/discord/bot.d.ts +1 -0
  6. package/dist/adapters/discord/bot.d.ts.map +1 -1
  7. package/dist/adapters/discord/bot.js +29 -9
  8. package/dist/adapters/discord/bot.js.map +1 -1
  9. package/dist/adapters/discord/context.d.ts +1 -1
  10. package/dist/adapters/discord/context.d.ts.map +1 -1
  11. package/dist/adapters/discord/context.js +1 -2
  12. package/dist/adapters/discord/context.js.map +1 -1
  13. package/dist/adapters/slack/bot.d.ts +8 -0
  14. package/dist/adapters/slack/bot.d.ts.map +1 -1
  15. package/dist/adapters/slack/bot.js +177 -11
  16. package/dist/adapters/slack/bot.js.map +1 -1
  17. package/dist/adapters/slack/branch-manager.d.ts +1 -0
  18. package/dist/adapters/slack/branch-manager.d.ts.map +1 -1
  19. package/dist/adapters/slack/branch-manager.js +9 -8
  20. package/dist/adapters/slack/branch-manager.js.map +1 -1
  21. package/dist/adapters/slack/context.d.ts +1 -1
  22. package/dist/adapters/slack/context.d.ts.map +1 -1
  23. package/dist/adapters/slack/context.js +10 -8
  24. package/dist/adapters/slack/context.js.map +1 -1
  25. package/dist/adapters/slack/tools/attach.d.ts +1 -1
  26. package/dist/adapters/slack/tools/attach.d.ts.map +1 -1
  27. package/dist/adapters/slack/tools/attach.js.map +1 -1
  28. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  29. package/dist/adapters/telegram/bot.js +33 -2
  30. package/dist/adapters/telegram/bot.js.map +1 -1
  31. package/dist/agent.d.ts +1 -2
  32. package/dist/agent.d.ts.map +1 -1
  33. package/dist/agent.js +507 -422
  34. package/dist/agent.js.map +1 -1
  35. package/dist/commands/index.d.ts.map +1 -1
  36. package/dist/commands/index.js +2 -0
  37. package/dist/commands/index.js.map +1 -1
  38. package/dist/commands/login.d.ts.map +1 -1
  39. package/dist/commands/login.js +41 -2
  40. package/dist/commands/login.js.map +1 -1
  41. package/dist/commands/model.d.ts +1 -1
  42. package/dist/commands/model.d.ts.map +1 -1
  43. package/dist/commands/model.js +25 -7
  44. package/dist/commands/model.js.map +1 -1
  45. package/dist/commands/new.d.ts.map +1 -1
  46. package/dist/commands/new.js +1 -1
  47. package/dist/commands/new.js.map +1 -1
  48. package/dist/commands/sandbox.d.ts +10 -0
  49. package/dist/commands/sandbox.d.ts.map +1 -0
  50. package/dist/commands/sandbox.js +88 -0
  51. package/dist/commands/sandbox.js.map +1 -0
  52. package/dist/commands/session-view.d.ts.map +1 -1
  53. package/dist/commands/session-view.js +34 -10
  54. package/dist/commands/session-view.js.map +1 -1
  55. package/dist/commands/types.d.ts +1 -3
  56. package/dist/commands/types.d.ts.map +1 -1
  57. package/dist/commands/types.js.map +1 -1
  58. package/dist/commands/utils.d.ts +3 -0
  59. package/dist/commands/utils.d.ts.map +1 -1
  60. package/dist/commands/utils.js +5 -0
  61. package/dist/commands/utils.js.map +1 -1
  62. package/dist/config.d.ts +7 -1
  63. package/dist/config.d.ts.map +1 -1
  64. package/dist/config.js +64 -23
  65. package/dist/config.js.map +1 -1
  66. package/dist/context.d.ts +2 -44
  67. package/dist/context.d.ts.map +1 -1
  68. package/dist/context.js +7 -210
  69. package/dist/context.js.map +1 -1
  70. package/dist/events.d.ts.map +1 -1
  71. package/dist/events.js +15 -14
  72. package/dist/events.js.map +1 -1
  73. package/dist/execution-resolver.d.ts +3 -2
  74. package/dist/execution-resolver.d.ts.map +1 -1
  75. package/dist/execution-resolver.js +40 -7
  76. package/dist/execution-resolver.js.map +1 -1
  77. package/dist/file-guards.d.ts +6 -0
  78. package/dist/file-guards.d.ts.map +1 -0
  79. package/dist/file-guards.js +48 -0
  80. package/dist/file-guards.js.map +1 -0
  81. package/dist/log.d.ts +1 -5
  82. package/dist/log.d.ts.map +1 -1
  83. package/dist/log.js +13 -38
  84. package/dist/log.js.map +1 -1
  85. package/dist/login/index.d.ts +14 -2
  86. package/dist/login/index.d.ts.map +1 -1
  87. package/dist/login/index.js +40 -13
  88. package/dist/login/index.js.map +1 -1
  89. package/dist/login/portal.d.ts +2 -1
  90. package/dist/login/portal.d.ts.map +1 -1
  91. package/dist/login/portal.js +12 -12
  92. package/dist/login/portal.js.map +1 -1
  93. package/dist/main.d.ts.map +1 -1
  94. package/dist/main.js +33 -28
  95. package/dist/main.js.map +1 -1
  96. package/dist/provisioner.d.ts +12 -2
  97. package/dist/provisioner.d.ts.map +1 -1
  98. package/dist/provisioner.js +43 -14
  99. package/dist/provisioner.js.map +1 -1
  100. package/dist/runtime/conversation-orchestrator.d.ts +42 -0
  101. package/dist/runtime/conversation-orchestrator.d.ts.map +1 -0
  102. package/dist/runtime/conversation-orchestrator.js +150 -0
  103. package/dist/runtime/conversation-orchestrator.js.map +1 -0
  104. package/dist/runtime/session-runtime.d.ts +1 -1
  105. package/dist/runtime/session-runtime.d.ts.map +1 -1
  106. package/dist/runtime/session-runtime.js +49 -148
  107. package/dist/runtime/session-runtime.js.map +1 -1
  108. package/dist/sandbox/cloudflare.d.ts.map +1 -1
  109. package/dist/sandbox/cloudflare.js +2 -2
  110. package/dist/sandbox/cloudflare.js.map +1 -1
  111. package/dist/sandbox/container.d.ts.map +1 -1
  112. package/dist/sandbox/container.js +1 -1
  113. package/dist/sandbox/container.js.map +1 -1
  114. package/dist/sandbox/index.d.ts.map +1 -1
  115. package/dist/sandbox/index.js +4 -4
  116. package/dist/sandbox/index.js.map +1 -1
  117. package/dist/sentry.d.ts +1 -1
  118. package/dist/sentry.d.ts.map +1 -1
  119. package/dist/sentry.js +2 -2
  120. package/dist/sentry.js.map +1 -1
  121. package/dist/session-store.d.ts +2 -1
  122. package/dist/session-store.d.ts.map +1 -1
  123. package/dist/session-store.js +19 -15
  124. package/dist/session-store.js.map +1 -1
  125. package/dist/session-view/portal.d.ts +6 -1
  126. package/dist/session-view/portal.d.ts.map +1 -1
  127. package/dist/session-view/portal.js +829 -71
  128. package/dist/session-view/portal.js.map +1 -1
  129. package/dist/session-view/service.d.ts.map +1 -1
  130. package/dist/session-view/service.js +5 -4
  131. package/dist/session-view/service.js.map +1 -1
  132. package/dist/session-view/store.d.ts +2 -1
  133. package/dist/session-view/store.d.ts.map +1 -1
  134. package/dist/session-view/store.js +2 -1
  135. package/dist/session-view/store.js.map +1 -1
  136. package/dist/store.d.ts.map +1 -1
  137. package/dist/store.js +7 -13
  138. package/dist/store.js.map +1 -1
  139. package/dist/tool-diagnostics.d.ts +2 -0
  140. package/dist/tool-diagnostics.d.ts.map +1 -0
  141. package/dist/tool-diagnostics.js +7 -0
  142. package/dist/tool-diagnostics.js.map +1 -0
  143. package/dist/tools/bash.d.ts +1 -1
  144. package/dist/tools/bash.d.ts.map +1 -1
  145. package/dist/tools/bash.js.map +1 -1
  146. package/dist/tools/edit.d.ts +1 -1
  147. package/dist/tools/edit.d.ts.map +1 -1
  148. package/dist/tools/edit.js.map +1 -1
  149. package/dist/tools/event.d.ts +1 -1
  150. package/dist/tools/event.d.ts.map +1 -1
  151. package/dist/tools/event.js.map +1 -1
  152. package/dist/tools/index.d.ts +1 -1
  153. package/dist/tools/index.d.ts.map +1 -1
  154. package/dist/tools/index.js.map +1 -1
  155. package/dist/tools/read.d.ts +1 -1
  156. package/dist/tools/read.d.ts.map +1 -1
  157. package/dist/tools/read.js.map +1 -1
  158. package/dist/tools/write.d.ts +1 -1
  159. package/dist/tools/write.d.ts.map +1 -1
  160. package/dist/tools/write.js.map +1 -1
  161. package/dist/vault-routing.d.ts +0 -3
  162. package/dist/vault-routing.d.ts.map +1 -1
  163. package/dist/vault-routing.js +0 -24
  164. package/dist/vault-routing.js.map +1 -1
  165. package/dist/vault.d.ts +21 -57
  166. package/dist/vault.d.ts.map +1 -1
  167. package/dist/vault.js +114 -246
  168. package/dist/vault.js.map +1 -1
  169. package/package.json +6 -4
  170. package/dist/bindings.d.ts +0 -45
  171. package/dist/bindings.d.ts.map +0 -1
  172. package/dist/bindings.js +0 -75
  173. package/dist/bindings.js.map +0 -1
@@ -15,6 +15,38 @@ function slackIsRateLimited(err) {
15
15
  return data?.error === "rate_limited" || data?.response?.status === 429;
16
16
  }
17
17
  const slackRetry = (fn) => withRetry(fn, { isRateLimited: slackIsRateLimited });
18
+ function collectSlackText(value, parts) {
19
+ if (value === null || value === undefined)
20
+ return;
21
+ if (typeof value === "string") {
22
+ const trimmed = value.trim();
23
+ if (trimmed)
24
+ parts.push(trimmed);
25
+ return;
26
+ }
27
+ if (Array.isArray(value)) {
28
+ for (const item of value)
29
+ collectSlackText(item, parts);
30
+ return;
31
+ }
32
+ if (typeof value !== "object")
33
+ return;
34
+ const obj = value;
35
+ for (const key of ["text", "fallback", "title", "value"]) {
36
+ collectSlackText(obj[key], parts);
37
+ }
38
+ collectSlackText(obj.fields, parts);
39
+ collectSlackText(obj.elements, parts);
40
+ collectSlackText(obj.blocks, parts);
41
+ }
42
+ function buildSlackAppMessageText(event) {
43
+ const parts = [];
44
+ collectSlackText(event.text, parts);
45
+ collectSlackText(event.blocks, parts);
46
+ collectSlackText(event.attachments, parts);
47
+ const deduped = parts.filter((part, index) => parts.indexOf(part) === index);
48
+ return deduped.join("\n");
49
+ }
18
50
  import { createSlackAdapters } from "./context.js";
19
51
  import { hasMaterializedSlackBranchSession } from "./branch-manager.js";
20
52
  import { resolveSlackSessionKey } from "./session.js";
@@ -24,6 +56,7 @@ import { resolveSlackSessionKey } from "./session.js";
24
56
  export class SlackBot {
25
57
  constructor(handler, config) {
26
58
  this.botUserId = null;
59
+ this.botId = null;
27
60
  this.ownMentionRegex = null;
28
61
  this.startupTs = null; // Messages older than this are just logged, not processed
29
62
  this.users = new Map();
@@ -33,7 +66,12 @@ export class SlackBot {
33
66
  this.handler = handler;
34
67
  this.workingDir = config.workingDir;
35
68
  this.store = config.store;
36
- this.socketClient = new SocketModeClient({ appToken: config.appToken });
69
+ this.socketClient = new SocketModeClient({
70
+ appToken: config.appToken,
71
+ // Default 5s is too tight: brief event-loop stalls (e.g. backfill, sync fs)
72
+ // cause false pong timeouts; 4 in a row makes Slack drop the socket.
73
+ clientPingTimeout: 12_000,
74
+ });
37
75
  this.webClient = new WebClient(config.botToken);
38
76
  }
39
77
  setEventsWatcher(watcher) {
@@ -45,6 +83,7 @@ export class SlackBot {
45
83
  async start() {
46
84
  const auth = await this.webClient.auth.test();
47
85
  this.botUserId = auth.user_id;
86
+ this.botId = typeof auth.bot_id === "string" ? auth.bot_id : null;
48
87
  await Promise.all([this.fetchUsers(), this.fetchChannels()]);
49
88
  log.logInfo(`Loaded ${this.channels.size} channels, ${this.users.size} users`);
50
89
  await this.backfillAllChannels();
@@ -52,7 +91,7 @@ export class SlackBot {
52
91
  await this.socketClient.start();
53
92
  // Record startup time - messages older than this are just logged, not processed
54
93
  this.startupTs = (Date.now() / 1000).toFixed(6);
55
- log.logConnected();
94
+ log.logConnected("Slack");
56
95
  }
57
96
  getUser(userId) {
58
97
  return this.users.get(userId);
@@ -86,9 +125,37 @@ export class SlackBot {
86
125
  await this.webClient.chat.postEphemeral({ channel, user, text });
87
126
  });
88
127
  }
128
+ async postEphemeralBlocks(channel, user, text, blocks) {
129
+ return slackRetry(async () => {
130
+ await this.webClient.chat.postEphemeral({ channel, user, text, blocks: blocks });
131
+ });
132
+ }
133
+ async postMessageBlocks(channel, text, blocks) {
134
+ return slackRetry(async () => {
135
+ const result = await this.webClient.chat.postMessage({
136
+ channel,
137
+ text,
138
+ blocks: blocks,
139
+ });
140
+ return result.ts;
141
+ });
142
+ }
89
143
  async postPrivate(conversationId, userId, text) {
90
144
  await this.postEphemeral(conversationId, userId, text);
91
145
  }
146
+ async postPrivateDiagnostic(conversationId, userId, text, options) {
147
+ if (options?.style !== "muted") {
148
+ await this.postPrivate(conversationId, userId, options?.style === "error" ? `_${text}_` : text);
149
+ return;
150
+ }
151
+ const CONTEXT_TEXT_LIMIT = 3000;
152
+ const blockText = text.length > CONTEXT_TEXT_LIMIT
153
+ ? text.substring(0, CONTEXT_TEXT_LIMIT - 20) + "\n_(truncated)_"
154
+ : text;
155
+ await this.postEphemeralBlocks(conversationId, userId, text, [
156
+ { type: "context", elements: [{ type: "mrkdwn", text: blockText }] },
157
+ ]);
158
+ }
92
159
  async openDirectMessage(userId) {
93
160
  return slackRetry(async () => {
94
161
  const result = await this.webClient.conversations.open({ users: userId });
@@ -419,10 +486,29 @@ export class SlackBot {
419
486
  const messageTs = await this.postMessage(conversationId, responseText);
420
487
  this.logBotResponse(conversationId, responseText, messageTs);
421
488
  };
489
+ const respondMuted = async (responseText) => {
490
+ const CONTEXT_TEXT_LIMIT = 3000;
491
+ const blockText = responseText.length > CONTEXT_TEXT_LIMIT
492
+ ? responseText.substring(0, CONTEXT_TEXT_LIMIT - 20) + "\n_(truncated)_"
493
+ : responseText;
494
+ const blocks = [{ type: "context", elements: [{ type: "mrkdwn", text: blockText }] }];
495
+ if (options.ephemeralChannelId) {
496
+ await this.postEphemeralBlocks(options.ephemeralChannelId, userId, responseText, blocks);
497
+ return;
498
+ }
499
+ const messageTs = await this.postMessageBlocks(conversationId, responseText, blocks);
500
+ this.logBotResponse(conversationId, responseText, messageTs);
501
+ };
422
502
  const responseCtx = {
423
503
  respond,
424
504
  replaceResponse: respond,
425
- respondDiagnostic: respond,
505
+ respondDiagnostic: async (responseText, responseOptions) => {
506
+ if (responseOptions?.style === "muted") {
507
+ await respondMuted(responseText);
508
+ return;
509
+ }
510
+ await respond(responseOptions?.style === "error" ? `_${responseText}_` : responseText);
511
+ },
426
512
  respondToolResult: async (result) => {
427
513
  const duration = (result.durationMs / 1000).toFixed(1);
428
514
  await respond(`${result.isError ? "Error" : "Done"} ${result.toolName} (${duration}s)\n${result.result}`);
@@ -512,7 +598,7 @@ export class SlackBot {
512
598
  isBot: false,
513
599
  });
514
600
  const commandBot = this.createSlashCommandBot(conversationId);
515
- await this.handler.handleNew(conversationId, conversationId, commandBot);
601
+ await this.handler.handleNewCommand(conversationId, conversationId, commandBot);
516
602
  }
517
603
  async routeSlashModelCommand(payload) {
518
604
  const conversationId = payload.channel_id;
@@ -545,6 +631,9 @@ export class SlackBot {
545
631
  const adapters = this.createCommandAdapters(conversationId, payload.user_id, userName, commandText, eventTs, isDirectMessage ? {} : { ephemeralChannelId: conversationId });
546
632
  await this.handler.handleEvent(event, this, adapters, false);
547
633
  }
634
+ async routeSlashSandboxCommand(payload) {
635
+ await this.routeSlashModelCommand(payload);
636
+ }
548
637
  async routeSlashSessionCommand(payload) {
549
638
  const conversationId = payload.channel_id;
550
639
  const isDirectMessage = conversationId.startsWith("D");
@@ -576,6 +665,15 @@ export class SlackBot {
576
665
  await this.handler.handleEvent(event, this, adapters, false);
577
666
  }
578
667
  setupEventHandlers() {
668
+ this.socketClient.on("disconnect", (err) => {
669
+ log.logWarning("Slack socket disconnect", err ? String(err) : "");
670
+ });
671
+ this.socketClient.on("error", (err) => {
672
+ log.logWarning("Slack socket error", err ? String(err) : "");
673
+ });
674
+ this.socketClient.on("unable_to_socket_mode_start", (err) => {
675
+ log.logWarning("Slack socket unable_to_start", err ? String(err) : "");
676
+ });
579
677
  // Channel @mentions
580
678
  this.socketClient.on("app_mention", ({ event, ack }) => {
581
679
  const e = event;
@@ -634,8 +732,30 @@ export class SlackBot {
634
732
  // All messages (for logging) + DMs (for triggering)
635
733
  this.socketClient.on("message", ({ event, ack }) => {
636
734
  const e = event;
637
- // Skip bot messages, edits, etc.
638
- if (e.bot_id || !e.user || e.user === this.botUserId) {
735
+ const hasFiles = !!e.files && e.files.length > 0;
736
+ const hasSlackContent = !!e.text || hasFiles || !!e.blocks?.length || !!e.attachments?.length;
737
+ const isOwnBotMessage = (!!e.user && e.user === this.botUserId) || (!!this.botId && e.bot_id === this.botId);
738
+ if (isOwnBotMessage) {
739
+ ack();
740
+ return;
741
+ }
742
+ const isExternalBotMessage = !!e.bot_id || e.subtype === "bot_message";
743
+ if (isExternalBotMessage) {
744
+ if (e.subtype !== undefined && e.subtype !== "bot_message" && e.subtype !== "file_share") {
745
+ ack();
746
+ return;
747
+ }
748
+ if (!hasSlackContent) {
749
+ ack();
750
+ return;
751
+ }
752
+ void this.logExternalBotMessage(e).catch((err) => {
753
+ log.logWarning("Failed to log Slack bot message", String(err));
754
+ });
755
+ ack();
756
+ return;
757
+ }
758
+ if (!e.user) {
639
759
  ack();
640
760
  return;
641
761
  }
@@ -643,7 +763,7 @@ export class SlackBot {
643
763
  ack();
644
764
  return;
645
765
  }
646
- if (!e.text && (!e.files || e.files.length === 0)) {
766
+ if (!hasSlackContent) {
647
767
  ack();
648
768
  return;
649
769
  }
@@ -762,7 +882,15 @@ export class SlackBot {
762
882
  user_id: payload.user_id,
763
883
  user_name: payload.user_name,
764
884
  })
765
- : null;
885
+ : payload.command === "/pi-sandbox"
886
+ ? this.routeSlashSandboxCommand({
887
+ command: payload.command,
888
+ text: payload.text,
889
+ channel_id: payload.channel_id,
890
+ user_id: payload.user_id,
891
+ user_name: payload.user_name,
892
+ })
893
+ : null;
766
894
  if (!handlerPromise) {
767
895
  return;
768
896
  }
@@ -835,6 +963,27 @@ export class SlackBot {
835
963
  });
836
964
  return attachments;
837
965
  }
966
+ async logExternalBotMessage(event) {
967
+ const attachments = event.files
968
+ ? await this.store.processAttachments(event.channel, event.files, event.ts)
969
+ : [];
970
+ const botName = event.username ?? event.bot_profile?.name ?? event.bot_profile?.real_name ?? event.bot_id;
971
+ this.logToFile(event.channel, {
972
+ date: new Date(parseFloat(event.ts) * 1000).toISOString(),
973
+ ts: event.ts,
974
+ threadTs: event.thread_ts,
975
+ user: event.bot_id ? `bot:${event.bot_id}` : "external-bot",
976
+ userName: botName,
977
+ displayName: botName,
978
+ text: buildSlackAppMessageText(event),
979
+ attachments,
980
+ isBot: true,
981
+ botId: event.bot_id,
982
+ appId: event.app_id ?? event.bot_profile?.app_id,
983
+ subtype: event.subtype,
984
+ });
985
+ return attachments;
986
+ }
838
987
  // ==========================================================================
839
988
  // Private - Backfill
840
989
  // ==========================================================================
@@ -883,14 +1032,26 @@ export class SlackBot {
883
1032
  cursor = result.response_metadata?.next_cursor;
884
1033
  pageCount++;
885
1034
  } while (cursor && pageCount < maxPages);
886
- // Filter: include mama's messages, exclude other bots, skip already logged
1035
+ // Filter: include mama's messages, external app/bot messages, and user messages.
887
1036
  const relevantMessages = allMessages.filter((msg) => {
888
1037
  if (!msg.ts || existingTs.has(msg.ts))
889
1038
  return false; // Skip duplicates
890
1039
  if (msg.user === this.botUserId)
891
1040
  return true;
892
- if (msg.bot_id)
893
- return false;
1041
+ const isExternalBotMessage = !!msg.bot_id || msg.subtype === "bot_message";
1042
+ if (isExternalBotMessage) {
1043
+ if (this.botId && msg.bot_id === this.botId)
1044
+ return false;
1045
+ if (msg.subtype !== undefined &&
1046
+ msg.subtype !== "bot_message" &&
1047
+ msg.subtype !== "file_share") {
1048
+ return false;
1049
+ }
1050
+ return (!!msg.text ||
1051
+ !!(msg.files && msg.files.length > 0) ||
1052
+ !!msg.blocks?.length ||
1053
+ !!msg.attachments?.length);
1054
+ }
894
1055
  if (msg.subtype !== undefined && msg.subtype !== "file_share")
895
1056
  return false;
896
1057
  if (!msg.user)
@@ -904,6 +1065,11 @@ export class SlackBot {
904
1065
  // Log each message to log.jsonl
905
1066
  for (const msg of relevantMessages) {
906
1067
  const isMamaMessage = msg.user === this.botUserId;
1068
+ const isExternalBotMessage = !isMamaMessage && (!!msg.bot_id || msg.subtype === "bot_message");
1069
+ if (isExternalBotMessage) {
1070
+ await this.logExternalBotMessage({ ...msg, channel: channelId, ts: msg.ts });
1071
+ continue;
1072
+ }
907
1073
  const user = this.users.get(msg.user);
908
1074
  const text = this.stripOwnMention(msg.text);
909
1075
  const attachments = msg.files