@geminixiang/mikan 1.0.0-beta.60 → 1.0.0-beta.62

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 (111) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +3 -1
  3. package/dist/adapters/intake.js +12 -2
  4. package/dist/adapters/intake.js.map +1 -1
  5. package/dist/adapters/progressive-renderer.d.ts.map +1 -1
  6. package/dist/adapters/progressive-renderer.js +4 -0
  7. package/dist/adapters/progressive-renderer.js.map +1 -1
  8. package/dist/adapters/slack/bot.d.ts +9 -3
  9. package/dist/adapters/slack/bot.d.ts.map +1 -1
  10. package/dist/adapters/slack/bot.js +145 -16
  11. package/dist/adapters/slack/bot.js.map +1 -1
  12. package/dist/adapters/slack/task-status.d.ts +7 -0
  13. package/dist/adapters/slack/task-status.d.ts.map +1 -0
  14. package/dist/adapters/slack/task-status.js +101 -0
  15. package/dist/adapters/slack/task-status.js.map +1 -0
  16. package/dist/adapters/web/admin/portal.d.ts +1 -1
  17. package/dist/adapters/web/admin/portal.d.ts.map +1 -1
  18. package/dist/adapters/web/admin/portal.js +33 -25
  19. package/dist/adapters/web/admin/portal.js.map +1 -1
  20. package/dist/adapters/web/admin/types.d.ts +6 -3
  21. package/dist/adapters/web/admin/types.d.ts.map +1 -1
  22. package/dist/adapters/web/admin/types.js.map +1 -1
  23. package/dist/adapters/web/server.d.ts +3 -0
  24. package/dist/adapters/web/server.d.ts.map +1 -1
  25. package/dist/adapters/web/server.js +4 -6
  26. package/dist/adapters/web/server.js.map +1 -1
  27. package/dist/adapters/web/session-view/portal.js +2 -1
  28. package/dist/adapters/web/session-view/portal.js.map +1 -1
  29. package/dist/cli/boot.js +1 -1
  30. package/dist/cli/boot.js.map +1 -1
  31. package/dist/cli/office.d.ts.map +1 -1
  32. package/dist/cli/office.js +66 -0
  33. package/dist/cli/office.js.map +1 -1
  34. package/dist/config.d.ts.map +1 -1
  35. package/dist/config.js +4 -4
  36. package/dist/config.js.map +1 -1
  37. package/dist/env-manifest.js +3 -3
  38. package/dist/env-manifest.js.map +1 -1
  39. package/dist/events/index.d.ts +56 -34
  40. package/dist/events/index.d.ts.map +1 -1
  41. package/dist/events/index.js +114 -23
  42. package/dist/events/index.js.map +1 -1
  43. package/dist/events/scheduler.d.ts +34 -0
  44. package/dist/events/scheduler.d.ts.map +1 -0
  45. package/dist/events/scheduler.js +231 -0
  46. package/dist/events/scheduler.js.map +1 -0
  47. package/dist/harness/mcp.d.ts.map +1 -1
  48. package/dist/harness/mcp.js +1 -3
  49. package/dist/harness/mcp.js.map +1 -1
  50. package/dist/harness/open-connector.d.ts +21 -10
  51. package/dist/harness/open-connector.d.ts.map +1 -1
  52. package/dist/harness/open-connector.js +109 -135
  53. package/dist/harness/open-connector.js.map +1 -1
  54. package/dist/harness/presenter.d.ts +1 -0
  55. package/dist/harness/presenter.d.ts.map +1 -1
  56. package/dist/harness/presenter.js +37 -9
  57. package/dist/harness/presenter.js.map +1 -1
  58. package/dist/harness/prompt.d.ts.map +1 -1
  59. package/dist/harness/prompt.js +9 -4
  60. package/dist/harness/prompt.js.map +1 -1
  61. package/dist/harness/runner.d.ts.map +1 -1
  62. package/dist/harness/runner.js +81 -34
  63. package/dist/harness/runner.js.map +1 -1
  64. package/dist/harness/session.d.ts +3 -0
  65. package/dist/harness/session.d.ts.map +1 -1
  66. package/dist/harness/session.js +21 -1
  67. package/dist/harness/session.js.map +1 -1
  68. package/dist/harness/tools/event.d.ts +1 -1
  69. package/dist/harness/tools/event.d.ts.map +1 -1
  70. package/dist/harness/tools/event.js +19 -22
  71. package/dist/harness/tools/event.js.map +1 -1
  72. package/dist/harness/tools/index.d.ts +4 -1
  73. package/dist/harness/tools/index.d.ts.map +1 -1
  74. package/dist/harness/tools/index.js +6 -3
  75. package/dist/harness/tools/index.js.map +1 -1
  76. package/dist/harness/tools/task.d.ts +20 -0
  77. package/dist/harness/tools/task.d.ts.map +1 -0
  78. package/dist/harness/tools/task.js +79 -0
  79. package/dist/harness/tools/task.js.map +1 -0
  80. package/dist/main.js +17 -9
  81. package/dist/main.js.map +1 -1
  82. package/dist/office/index.d.ts +3 -2
  83. package/dist/office/index.d.ts.map +1 -1
  84. package/dist/office/index.js +3 -3
  85. package/dist/office/index.js.map +1 -1
  86. package/dist/office/projection.d.ts.map +1 -1
  87. package/dist/office/projection.js +0 -2
  88. package/dist/office/projection.js.map +1 -1
  89. package/dist/office/types.d.ts +0 -2
  90. package/dist/office/types.d.ts.map +1 -1
  91. package/dist/office/types.js.map +1 -1
  92. package/dist/runtime/conversation-runtime.d.ts.map +1 -1
  93. package/dist/runtime/conversation-runtime.js +35 -13
  94. package/dist/runtime/conversation-runtime.js.map +1 -1
  95. package/dist/runtime/types.d.ts +6 -2
  96. package/dist/runtime/types.d.ts.map +1 -1
  97. package/dist/runtime/types.js.map +1 -1
  98. package/dist/sessions/chat-history-sync.js +8 -3
  99. package/dist/sessions/chat-history-sync.js.map +1 -1
  100. package/dist/sessions/session-store.d.ts +8 -0
  101. package/dist/sessions/session-store.d.ts.map +1 -1
  102. package/dist/sessions/session-store.js +46 -21
  103. package/dist/sessions/session-store.js.map +1 -1
  104. package/dist/types.d.ts +26 -5
  105. package/dist/types.d.ts.map +1 -1
  106. package/dist/types.js.map +1 -1
  107. package/package.json +3 -2
  108. package/dist/events/watcher.d.ts +0 -43
  109. package/dist/events/watcher.d.ts.map +0 -1
  110. package/dist/events/watcher.js +0 -395
  111. package/dist/events/watcher.js.map +0 -1
@@ -11,7 +11,7 @@ import * as log from "../../log.js";
11
11
  import { isRecord, readTextFileIfExists } from "../../file-guards.js";
12
12
  import { PRODUCT_NAME, formatForceStopped } from "../../platform-messages.js";
13
13
  import { appendBotResponseLog, appendChannelLog, MessagingEventQueue, MessagingIntakeTracker, saveIncomingAttachments, withRetry, } from "../shared.js";
14
- import { processMessageIntake } from "../intake.js";
14
+ import { matchMagicWord, processMessageIntake } from "../intake.js";
15
15
  import { recordPlatformChannelKind } from "../../office/projection.js";
16
16
  import { AssistantThreadRegistry, handleAgentContextChanged, handleAgentDmOpened, handleAssistantThreadStarted, titleAssistantThread, } from "./assistant.js";
17
17
  import { createSlackAdapters } from "./context.js";
@@ -21,6 +21,7 @@ import { isSlackThreadSessionKey, planSlackAdapterSession, planSlackEventAnchorR
21
21
  import { reportUserFacingError } from "../../observability/index.js";
22
22
  import { recordSlackUpdate } from "./update-diagnostics.js";
23
23
  import { renderSlackBlocks, resolveSlackMentions } from "./blocks.js";
24
+ import { querySlackTasks, isTaskStatusQuestion, formatTaskStatus, readTaskRoots, } from "./task-status.js";
24
25
  import { StreamStartLimiter } from "./stream-limits.js";
25
26
  const SLACK_EVENT_ANCHOR_TEXT = "Working on it...";
26
27
  function hasMessageContent(message, includeBlocks = true) {
@@ -130,9 +131,87 @@ export class SlackMessagingBot {
130
131
  return (resolveConversationSettings(this.workspace.office(address)).slack?.replyMode ?? "top-level");
131
132
  }
132
133
  createContext(event) {
133
- return createSlackAdapters(event, this, {
134
+ const context = createSlackAdapters(event, this, {
134
135
  replyMode: this.resolveReplyMode(event.address),
135
136
  });
137
+ if (event.conversationKind === "direct") {
138
+ context.responder.getTaskStatus = (key) => querySlackTasks(this.conversationDir(event.channel), event.channel, this.handler
139
+ .getRunningSessions()
140
+ .filter((s) => s.address.platform === "slack" && s.address.conversationId === event.channel), key);
141
+ }
142
+ if (event.conversationKind === "direct" &&
143
+ event.thread_ts &&
144
+ this.isTaskThread(event.channel, event.thread_ts)) {
145
+ let notified = false;
146
+ context.responder.notifyCompletion = async () => {
147
+ if (notified)
148
+ return;
149
+ const text = `<@${event.user}> 這一輪處理已結束,請查看上方結果。`;
150
+ const ts = await this.postInThread(event.channel, event.thread_ts, text);
151
+ notified = true;
152
+ this.logBotResponse(event.channel, text, ts, event.thread_ts);
153
+ };
154
+ }
155
+ if (event.conversationKind === "direct" && !event.thread_ts) {
156
+ context.responder.startTask = async (message, task) => {
157
+ if (this.stopped)
158
+ throw new Error("Slack is shutting down; task was not started.");
159
+ await context.responder.deleteResponse();
160
+ const root = await this.postMessage(event.channel, message);
161
+ this.logToFile(event.channel, {
162
+ date: new Date().toISOString(),
163
+ ts: root,
164
+ user: "bot",
165
+ text: message,
166
+ isMessagingBot: true,
167
+ taskRoot: true,
168
+ });
169
+ const sessionKey = resolveSlackSessionKey(event.channel, root);
170
+ try {
171
+ registerThreadSession({
172
+ conversationDir: this.conversationDir(event.channel),
173
+ sessionKey,
174
+ });
175
+ const child = {
176
+ ...event,
177
+ ts: `task:${root}`,
178
+ thread_ts: root,
179
+ sessionKey,
180
+ text: task,
181
+ };
182
+ if (!this.getQueue(sessionKey).enqueue(async () => {
183
+ try {
184
+ await this.handler.handleEvent({
185
+ ...child,
186
+ attachments: child.attachments?.map((a) => ({
187
+ name: a.original,
188
+ localPath: a.localPath,
189
+ })),
190
+ }, this, this.createContext(child));
191
+ }
192
+ catch (error) {
193
+ reportUserFacingError(error, {
194
+ domain: "mikan",
195
+ surface: "task_handoff",
196
+ operation: "start_task_run",
197
+ severity: "error",
198
+ platform: "slack",
199
+ context: { conversationId: event.channel, sessionKey, threadTs: root },
200
+ });
201
+ await this.postInThread(event.channel, root, "Task could not start. Please reply here to try again.");
202
+ throw error;
203
+ }
204
+ }))
205
+ throw new Error("Task queue is closed.");
206
+ }
207
+ catch (error) {
208
+ await this.updateMessage(event.channel, root, `${message}\n\nTask could not start.`);
209
+ throw error;
210
+ }
211
+ return sessionKey;
212
+ };
213
+ }
214
+ return context;
136
215
  }
137
216
  constructor(handler, config) {
138
217
  this.statusUpdates = new Map();
@@ -155,7 +234,7 @@ export class SlackMessagingBot {
155
234
  this.stopped = false;
156
235
  this.queues = new Map();
157
236
  this.intake = new MessagingIntakeTracker("Slack");
158
- this.eventsWatcher = null;
237
+ this.eventScheduler = null;
159
238
  this.handler = handler;
160
239
  this.workspace = config.workspace;
161
240
  this.botToken = config.botToken;
@@ -172,8 +251,8 @@ export class SlackMessagingBot {
172
251
  rejectRateLimitedCalls: true,
173
252
  });
174
253
  }
175
- setEventsWatcher(watcher) {
176
- this.eventsWatcher = watcher;
254
+ setEventScheduler(scheduler) {
255
+ this.eventScheduler = scheduler;
177
256
  }
178
257
  // ==========================================================================
179
258
  // Public API
@@ -710,6 +789,9 @@ export class SlackMessagingBot {
710
789
  }
711
790
  return this.hasKnownThreadSession(conversationId, sessionKey) ? sessionKey : conversationId;
712
791
  }
792
+ isTaskThread(channel, root) {
793
+ return readTaskRoots(this.conversationDir(channel)).has(root);
794
+ }
713
795
  hasKnownThreadSession(conversationId, sessionKey) {
714
796
  return hasMaterializedChatSession({
715
797
  conversationDir: this.conversationDir(conversationId),
@@ -840,8 +922,14 @@ export class SlackMessagingBot {
840
922
  }
841
923
  }
842
924
  }
843
- appendScheduledJobs(blocks) {
844
- const periodicEvents = this.eventsWatcher?.getPeriodicEvents() ?? [];
925
+ /**
926
+ * Scheduled jobs of the opener's own DM office only: the App Home is a
927
+ * personal surface, so other offices' schedules are not listed here.
928
+ */
929
+ appendScheduledJobs(blocks, dmChannelId) {
930
+ const periodicEvents = dmChannelId && this.eventScheduler
931
+ ? this.eventScheduler.periodicEvents(createOfficeAddress("slack", dmChannelId))
932
+ : [];
845
933
  blocks.push({ type: "divider" }, {
846
934
  type: "header",
847
935
  text: {
@@ -878,7 +966,7 @@ export class SlackMessagingBot {
878
966
  });
879
967
  }
880
968
  }
881
- buildHomeView() {
969
+ buildHomeView(dmChannelId) {
882
970
  const blocks = [
883
971
  {
884
972
  type: "section",
@@ -894,7 +982,7 @@ export class SlackMessagingBot {
894
982
  },
895
983
  ];
896
984
  this.appendRunningTasks(blocks);
897
- this.appendScheduledJobs(blocks);
985
+ this.appendScheduledJobs(blocks, dmChannelId);
898
986
  blocks.push({ type: "divider" }, {
899
987
  type: "context",
900
988
  elements: [
@@ -1135,6 +1223,39 @@ export class SlackMessagingBot {
1135
1223
  ack();
1136
1224
  return false;
1137
1225
  }
1226
+ async deliverTaskUpdate(event, attachmentsPromise) {
1227
+ if (matchMagicWord(event.text) === "stop" || event.text.startsWith("/"))
1228
+ return false;
1229
+ event.attachments = await attachmentsPromise;
1230
+ const context = this.createContext(event);
1231
+ const post = (text) => event.thread_ts
1232
+ ? this.postInThread(event.channel, event.thread_ts, text)
1233
+ : this.postMessage(event.channel, text);
1234
+ try {
1235
+ if (!event.attachments?.length && isTaskStatusQuestion(event.text)) {
1236
+ const tasks = await context.responder.getTaskStatus(event.thread_ts ? event.sessionKey : undefined);
1237
+ if (!tasks.length)
1238
+ return false;
1239
+ const active = tasks.filter((t) => t.status === "running" || t.status === "stopping");
1240
+ const selection = event.thread_ts ? tasks : active.length ? active : tasks.slice(0, 1);
1241
+ const text = selection.length > 1
1242
+ ? "目前有多個任務在處理,請到你想查詢的任務對話串詢問,避免弄錯。"
1243
+ : formatTaskStatus(selection);
1244
+ const ts = await post(text);
1245
+ this.logBotResponse(event.channel, text, ts, event.thread_ts);
1246
+ return true;
1247
+ }
1248
+ if (event.thread_ts && (await this.handler.steer?.(context.message))) {
1249
+ await post("收到補充,會在下一個處理步驟納入;目前的操作不會立即中斷。");
1250
+ return true;
1251
+ }
1252
+ }
1253
+ catch (error) {
1254
+ await post(error instanceof Error ? error.message : "Could not deliver task update.");
1255
+ return true;
1256
+ }
1257
+ return false;
1258
+ }
1138
1259
  async handleMessageEvent({ event, ack, }) {
1139
1260
  const e = event;
1140
1261
  if (!this.admitHumanMessage(e, ack))
@@ -1182,7 +1303,7 @@ export class SlackMessagingBot {
1182
1303
  ack();
1183
1304
  return;
1184
1305
  }
1185
- if (!isDM && isThreadReply) {
1306
+ if (!isDM && isThreadReply && matchMagicWord(slackEvent.text) !== "stop") {
1186
1307
  void attachmentsPromise.catch((err) => {
1187
1308
  log.logWarning("Failed to log Slack message", String(err));
1188
1309
  });
@@ -1191,6 +1312,13 @@ export class SlackMessagingBot {
1191
1312
  }
1192
1313
  const activeSessionKey = slackEvent.sessionKey ?? resolveSlackSessionKey(e.channel, e.thread_ts);
1193
1314
  slackEvent.sessionKey = activeSessionKey;
1315
+ const taskThread = isDM && !!e.thread_ts && this.isTaskThread(e.channel, e.thread_ts);
1316
+ const taskControl = taskThread || (isDM && !e.thread_ts && isTaskStatusQuestion(slackEvent.text));
1317
+ if (taskControl) {
1318
+ ack();
1319
+ if (await this.deliverTaskUpdate(slackEvent, attachmentsPromise))
1320
+ return;
1321
+ }
1194
1322
  const autoReply = !isDM && slackConversationAutoReplyEnabled(this.workspace.office(slackEvent.address));
1195
1323
  const intake = this.processSlackMessageIntake({
1196
1324
  event: slackEvent,
@@ -1199,7 +1327,8 @@ export class SlackMessagingBot {
1199
1327
  addressed: isDM || autoReply,
1200
1328
  magicWordAddressed: isDM,
1201
1329
  });
1202
- ack();
1330
+ if (!taskControl)
1331
+ ack();
1203
1332
  await intake;
1204
1333
  }
1205
1334
  async handleSlashCommand({ body, ack, }) {
@@ -1246,7 +1375,7 @@ export class SlackMessagingBot {
1246
1375
  this.webClient.views
1247
1376
  .publish({
1248
1377
  user_id: e.user,
1249
- view: this.buildHomeView(),
1378
+ view: this.buildHomeView(e.channel),
1250
1379
  })
1251
1380
  .catch((err) => {
1252
1381
  log.logWarning(`Failed to publish App Home view`, String(err));
@@ -1277,11 +1406,11 @@ export class SlackMessagingBot {
1277
1406
  await this.postMessage(channelId, formatForceStopped("slack", userId ?? "unknown"));
1278
1407
  // Refresh home tab
1279
1408
  if (userId) {
1280
- this.webClient.views
1281
- .publish({
1409
+ this.openDirectConversation(userId)
1410
+ .then((dmChannelId) => this.webClient.views.publish({
1282
1411
  user_id: userId,
1283
- view: this.buildHomeView(),
1284
- })
1412
+ view: this.buildHomeView(dmChannelId),
1413
+ }))
1285
1414
  .catch((err) => {
1286
1415
  log.logWarning(`Failed to refresh App Home view`, String(err));
1287
1416
  });