loopable-cli 0.2.0 → 0.2.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.
Files changed (65) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/_connectorId-CkygNLny.js +1 -0
  3. package/.output/public/assets/_loopId-Cshlc5Ja.js +1 -0
  4. package/.output/public/assets/_taskId-BKDIJ_Fg.js +1 -0
  5. package/.output/public/assets/connector-icon-BUoK6Cru.js +1 -0
  6. package/.output/public/assets/connectors-CeLBtEtg.js +1 -0
  7. package/.output/public/assets/gaps-BtFoD7mF.js +1 -0
  8. package/.output/public/assets/{inbox-CLEXUXCI.js → inbox-BYNjkNDJ.js} +1 -1
  9. package/.output/public/assets/index-0yP3Jn_G.js +18 -0
  10. package/.output/public/assets/loop-form-DnSJNc1k.js +1 -0
  11. package/.output/public/assets/loops-1CvbqO6m.js +1 -0
  12. package/.output/public/assets/{new-DPEaXKAY.js → new-m5NlqYQT.js} +1 -1
  13. package/.output/public/assets/refresh-cw-C3xBnKfy.js +1 -0
  14. package/.output/public/assets/{runners-BVBggS5B.js → runners-D8QVl-Y6.js} +1 -1
  15. package/.output/public/assets/{setting-fields-BT1naVJX.js → setting-fields-bJ_gTG7Q.js} +1 -1
  16. package/.output/public/assets/styles-ClTGaXU0.css +2 -0
  17. package/.output/public/assets/{task-list-CrptnI1N.js → task-list-BAxjcVgr.js} +1 -1
  18. package/.output/server/{_connectorId-DcSnoRjk.mjs → _connectorId-C9yydJrC.mjs} +90 -9
  19. package/.output/server/{_loopId-G_TAkYtS.mjs → _loopId-B0VG-eBC.mjs} +42 -13
  20. package/.output/server/_ssr/{callback-D20cVD2w.mjs → access-DTttr563.mjs} +37 -20
  21. package/.output/server/_ssr/{agents-Dms_6TBF.mjs → agents-C7B6RJ6-.mjs} +3 -3
  22. package/.output/server/_ssr/{agents-CrUmIQpi.mjs → agents-DG643yoJ.mjs} +2 -2
  23. package/.output/server/_ssr/alert-DQvzE7bu.mjs +1 -1
  24. package/.output/server/_ssr/connection-status-BZr3Y-IC.mjs +1 -1
  25. package/.output/server/_ssr/{connections-ypex3qUd.mjs → connections-Df-aDpfJ.mjs} +814 -15
  26. package/.output/server/_ssr/connector-icon-WvxwZ5TF.mjs +56 -0
  27. package/.output/server/_ssr/{connectors-4yzKb0KG.mjs → connectors-BU0IiRge.mjs} +6 -5
  28. package/.output/server/_ssr/{connectors-BZly0yGF.mjs → connectors-e0mf2pMs.mjs} +14 -3
  29. package/.output/server/_ssr/{gaps-D2Zh8sEo.mjs → gaps-Cz76ZicM.mjs} +6 -6
  30. package/.output/server/_ssr/{inbox-C2VBGLa5.mjs → inbox-CAUBa1I3.mjs} +3 -3
  31. package/.output/server/_ssr/{loop-form-D-U-kU4k.mjs → loop-form-FqSaXiHl.mjs} +5 -5
  32. package/.output/server/_ssr/{loops-Og_G3KyZ.mjs → loops-D8WuK7Ke.mjs} +199 -6
  33. package/.output/server/_ssr/{loops-D0XJ8eBb.mjs → loops-Dqoebe8q.mjs} +6 -6
  34. package/.output/server/_ssr/{new-CajvgIog.mjs → new-bBsVaWD6.mjs} +5 -5
  35. package/.output/server/_ssr/{router-BbrBz36P.mjs → router-54IDWTVz.mjs} +24 -19
  36. package/.output/server/_ssr/{runners-B_WuF8UJ.mjs → runners-Cj6z9Mqh.mjs} +26 -3
  37. package/.output/server/_ssr/{runners-D-0_lfiq.mjs → runners-D2UAdLyj.mjs} +1 -1
  38. package/.output/server/_ssr/{runners-zqF_zbEc.mjs → runners-DGWaVvWi.mjs} +2 -2
  39. package/.output/server/_ssr/{setting-fields-mpXfGftI.mjs → setting-fields-DzBlslan.mjs} +2 -2
  40. package/.output/server/_ssr/ssr.mjs +42 -34
  41. package/.output/server/_ssr/{start-BgTj-h_C.mjs → start-Vq3Oan3v.mjs} +2 -3
  42. package/.output/server/_ssr/switch-1pnd_bPJ.mjs +1 -1
  43. package/.output/server/_ssr/{task-list-BnQJ94Dk.mjs → task-list-_qgBS9LU.mjs} +5 -5
  44. package/.output/server/_ssr/{tasks-B_QmDFVs.mjs → tasks-BDqznm57.mjs} +16 -5
  45. package/.output/server/_ssr/{tasks-LIi-W4cy.mjs → tasks-Qtml5TlK.mjs} +3 -3
  46. package/.output/server/_ssr/textarea-CHJTKtsJ.mjs +1 -1
  47. package/.output/server/{_tanstack-start-manifest_v-BCHAazE8.mjs → _tanstack-start-manifest_v-CvheE7ZF.mjs} +25 -23
  48. package/.output/server/{_taskId-CIv0ajIx.mjs → _taskId-V4QLBO2N.mjs} +4 -4
  49. package/.output/server/index.mjs +139 -132
  50. package/README.md +24 -19
  51. package/dist/cli.mjs +24 -59
  52. package/dist/dispatcher.mjs +811 -31
  53. package/package.json +21 -17
  54. package/.output/public/assets/_connectorId-m67WbiJ8.js +0 -1
  55. package/.output/public/assets/_loopId-Bf4K5sdd.js +0 -1
  56. package/.output/public/assets/_taskId-eCKkcVOx.js +0 -1
  57. package/.output/public/assets/connector-icon-qGpNsPgH.js +0 -1
  58. package/.output/public/assets/connectors-DxXlc9zF.js +0 -1
  59. package/.output/public/assets/gaps-CoTA-PzA.js +0 -1
  60. package/.output/public/assets/index-Cndsw84p.js +0 -16
  61. package/.output/public/assets/loop-form-BvizlZdR.js +0 -1
  62. package/.output/public/assets/loops-Dbe_mf5T.js +0 -1
  63. package/.output/public/assets/styles-D3sonCfX.css +0 -2
  64. package/.output/server/_ssr/access-B7TcOZ7F.mjs +0 -42
  65. package/.output/server/_ssr/connector-icon-CFrrHjzR.mjs +0 -37
@@ -407,11 +407,11 @@ function parse(line) {
407
407
  return null;
408
408
  }
409
409
  }
410
- function textOf2(event) {
410
+ function textOf3(event) {
411
411
  return (event.message?.content ?? []).filter((part) => part.type === "text").map((part) => part.text ?? "").join("");
412
412
  }
413
- function describeTool(call2, cwd) {
414
- const [name, body] = Object.entries(call2 ?? {})[0] ?? [];
413
+ function describeTool(call3, cwd) {
414
+ const [name, body] = Object.entries(call3 ?? {})[0] ?? [];
415
415
  if (!name) return "a tool";
416
416
  const verb = name.replace(/ToolCall$/, "").replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase();
417
417
  const args3 = body?.args ?? {};
@@ -447,9 +447,9 @@ function answerFrom(stdout) {
447
447
  const whole = events.findLastIndex(
448
448
  (event) => event?.type === "assistant" && event.timestamp_ms === void 0
449
449
  );
450
- if (whole >= 0) return { text: textOf2(events[whole]).trim(), failed };
450
+ if (whole >= 0) return { text: textOf3(events[whole]).trim(), failed };
451
451
  const lastTool = events.findLastIndex((event) => event?.type === "tool_call");
452
- const partial = events.slice(lastTool + 1).filter((event) => event?.type === "assistant").map((event) => textOf2(event));
452
+ const partial = events.slice(lastTool + 1).filter((event) => event?.type === "assistant").map((event) => textOf3(event));
453
453
  if (partial.length > 0) return { text: partial.join("").trim(), failed };
454
454
  return { text: (result?.result ?? "").trim(), failed };
455
455
  }
@@ -2935,7 +2935,7 @@ var scheduleManifest = defineManifest({
2935
2935
  ]
2936
2936
  });
2937
2937
 
2938
- // src/connectors/wechat/manifest.ts
2938
+ // src/connectors/slack/manifest.ts
2939
2939
  var folder2 = {
2940
2940
  key: "folder",
2941
2941
  kind: "text",
@@ -2943,6 +2943,173 @@ var folder2 = {
2943
2943
  help: "An absolute path to a checkout on this host. The agent runs here, so it can read the code you are asking about.",
2944
2944
  placeholder: "/Users/you/code/your-project"
2945
2945
  };
2946
+ var channel = {
2947
+ key: "channel",
2948
+ kind: "text",
2949
+ label: "Channel",
2950
+ help: "Leave empty to reply in the thread that asked. Set a channel ID when the work started somewhere else.",
2951
+ placeholder: "C0123456789"
2952
+ };
2953
+ var ASK_PROMPT = [
2954
+ "Someone has sent you a message in Slack. Do what it asks, working in this directory,",
2955
+ "and then answer them.",
2956
+ "",
2957
+ "You are answering into Slack. Be brief: a couple of short paragraphs at most,",
2958
+ "no headings, no bullet lists unless you are genuinely listing things. Plain sentences.",
2959
+ "",
2960
+ "If the message asks a question about this code, read enough of it to answer",
2961
+ "properly rather than guessing. If it asks for a change, make the change and",
2962
+ "say in one line what you did. If the message is too vague to act on, say what",
2963
+ "you need to know instead of assuming."
2964
+ ].join("\n");
2965
+ var slackManifest = defineManifest({
2966
+ id: "slack",
2967
+ name: "Slack bot",
2968
+ tagline: "Paste a bot token from a Slack app \u2014 not a Slack login. @mention or DM the bot, and it answers in the thread.",
2969
+ docsUrl: "https://api.slack.com/apps",
2970
+ icon: "Slack",
2971
+ accent: "bg-[#4a154b] text-white",
2972
+ auth: {
2973
+ kind: "token",
2974
+ helpUrl: "https://api.slack.com/apps",
2975
+ note: "This is a Slack app in the workspace, not a Slack account sign-in. Create an app at api.slack.com/apps, add bot scopes (channels:history, channels:read, groups:history, groups:read, im:history, im:read, mpim:history, mpim:read, chat:write), Install to Workspace, then paste the Bot User OAuth Token. Invite the bot to any channel it should watch.",
2976
+ fields: [
2977
+ {
2978
+ key: "botToken",
2979
+ label: "Bot User OAuth Token",
2980
+ placeholder: "xoxb-...",
2981
+ secret: true
2982
+ }
2983
+ ]
2984
+ },
2985
+ workflows: [
2986
+ {
2987
+ id: "slack.ask",
2988
+ name: "Do what I ask the bot",
2989
+ summary: "Runs your agent when someone @mentions the bot or DMs it, and answers in the thread.",
2990
+ trigger: "the bot is @mentioned, or someone DMs it",
2991
+ writes: "a reply in the Slack thread",
2992
+ runsIn: "folder",
2993
+ settings: [folder2],
2994
+ prompt: ASK_PROMPT,
2995
+ guidancePlaceholder: "Anything the agent should know about this project before it starts. For example: run pnpm test before saying a change works.",
2996
+ answer: "text",
2997
+ actionId: "slack.reply",
2998
+ pollEveryMs: 6e4
2999
+ }
3000
+ ],
3001
+ actions: [
3002
+ {
3003
+ id: "slack.reply",
3004
+ name: "Reply in Slack",
3005
+ summary: "Post a message back to the thread that asked, or to a channel you name.",
3006
+ target: [channel],
3007
+ accepts: ["text"]
3008
+ }
3009
+ ],
3010
+ settings: [],
3011
+ allowsMultipleAccounts: false,
3012
+ byHand: { kind: "none" }
3013
+ });
3014
+
3015
+ // src/connectors/feishu/manifest.ts
3016
+ var folder3 = {
3017
+ key: "folder",
3018
+ kind: "text",
3019
+ label: "Folder to work in",
3020
+ help: "An absolute path to a checkout on this host. The agent runs here, so it can read the code you are asking about.",
3021
+ placeholder: "/Users/you/code/your-project"
3022
+ };
3023
+ var chat = {
3024
+ key: "chat",
3025
+ kind: "text",
3026
+ label: "Chat ID",
3027
+ help: "Leave empty to reply to the message that asked. Set a chat ID (oc_\u2026) when the work started somewhere else.",
3028
+ placeholder: "oc_..."
3029
+ };
3030
+ var ASK_PROMPT2 = [
3031
+ "Someone has sent you a message in Feishu. Do what it asks, working in this directory,",
3032
+ "and then answer them.",
3033
+ "",
3034
+ "You are answering into Feishu. Be brief: a couple of short paragraphs at most,",
3035
+ "no headings, no bullet lists unless you are genuinely listing things. Plain sentences.",
3036
+ "",
3037
+ "If the message asks a question about this code, read enough of it to answer",
3038
+ "properly rather than guessing. If it asks for a change, make the change and",
3039
+ "say in one line what you did. If the message is too vague to act on, say what",
3040
+ "you need to know instead of assuming."
3041
+ ].join("\n");
3042
+ var feishuManifest = defineManifest({
3043
+ id: "feishu",
3044
+ name: "Feishu / Lark bot",
3045
+ tagline: "Paste App ID and App Secret from a Feishu or Lark app \u2014 not a Feishu login. @mention the bot in a group, and it answers in the thread.",
3046
+ docsUrl: "https://open.feishu.cn/app",
3047
+ icon: "Feishu",
3048
+ accent: "bg-[#3370ff] text-white",
3049
+ auth: {
3050
+ kind: "token",
3051
+ helpUrl: "https://open.feishu.cn/app",
3052
+ note: "This is a custom Feishu or Lark app, not a Feishu account sign-in. Create an app, enable Bot, add permissions (im:chat:readonly, im:message, im:message.group_msg, im:message:send_as_bot), create a version and publish it, then paste App ID and App Secret. Invite the bot to any group it should watch. Put lark in Open platform for Lark (international).",
3053
+ fields: [
3054
+ {
3055
+ key: "appId",
3056
+ label: "App ID",
3057
+ placeholder: "cli_...",
3058
+ secret: false
3059
+ },
3060
+ {
3061
+ key: "appSecret",
3062
+ label: "App Secret",
3063
+ placeholder: "App Secret",
3064
+ secret: true
3065
+ },
3066
+ {
3067
+ key: "host",
3068
+ label: "Open platform",
3069
+ placeholder: "feishu or lark",
3070
+ secret: false,
3071
+ optional: true
3072
+ }
3073
+ ]
3074
+ },
3075
+ workflows: [
3076
+ {
3077
+ id: "feishu.ask",
3078
+ name: "Do what I ask the bot",
3079
+ summary: "Runs your agent when someone @mentions the bot in a group it is in, and answers in the thread.",
3080
+ trigger: "the bot is @mentioned in a group it is in",
3081
+ writes: "a reply in the Feishu thread",
3082
+ runsIn: "folder",
3083
+ settings: [folder3],
3084
+ prompt: ASK_PROMPT2,
3085
+ guidancePlaceholder: "Anything the agent should know about this project before it starts. For example: run pnpm test before saying a change works.",
3086
+ answer: "text",
3087
+ actionId: "feishu.reply",
3088
+ pollEveryMs: 6e4
3089
+ }
3090
+ ],
3091
+ actions: [
3092
+ {
3093
+ id: "feishu.reply",
3094
+ name: "Reply in Feishu",
3095
+ summary: "Post a message back to the thread that asked, or to a chat you name.",
3096
+ target: [chat],
3097
+ accepts: ["text"]
3098
+ }
3099
+ ],
3100
+ settings: [],
3101
+ allowsMultipleAccounts: false,
3102
+ byHand: { kind: "none" }
3103
+ });
3104
+
3105
+ // src/connectors/wechat/manifest.ts
3106
+ var folder4 = {
3107
+ key: "folder",
3108
+ kind: "text",
3109
+ label: "Folder to work in",
3110
+ help: "An absolute path to a checkout on this host. The agent runs here, so it can read the code you are asking about.",
3111
+ placeholder: "/Users/you/code/your-project"
3112
+ };
2946
3113
  var askers = {
2947
3114
  key: "askers",
2948
3115
  kind: "select",
@@ -2964,7 +3131,7 @@ var recipient = {
2964
3131
  ],
2965
3132
  default: "source"
2966
3133
  };
2967
- var ASK_PROMPT = [
3134
+ var ASK_PROMPT3 = [
2968
3135
  "Someone has sent you a message. Do what it asks, working in this directory,",
2969
3136
  "and then answer them.",
2970
3137
  "",
@@ -2998,8 +3165,8 @@ var wechatManifest = {
2998
3165
  // by asking, so there is nothing here that could be checked.
2999
3166
  writes: "a reply in the chat",
3000
3167
  runsIn: "folder",
3001
- settings: [folder2, askers],
3002
- prompt: ASK_PROMPT,
3168
+ settings: [folder4, askers],
3169
+ prompt: ASK_PROMPT3,
3003
3170
  guidancePlaceholder: "Anything the agent should know about this project before it starts. For example: run pnpm test before saying a change works.",
3004
3171
  answer: "text",
3005
3172
  actionId: "wechat.reply"
@@ -3027,6 +3194,8 @@ var wechatManifest = {
3027
3194
  // src/connectors/manifests.ts
3028
3195
  var CONNECTOR_MANIFESTS = [
3029
3196
  githubManifest,
3197
+ slackManifest,
3198
+ feishuManifest,
3030
3199
  gmailManifest,
3031
3200
  wechatManifest,
3032
3201
  scheduleManifest
@@ -4203,6 +4372,615 @@ var scheduleRuntime = {
4203
4372
  }
4204
4373
  };
4205
4374
 
4375
+ // src/connectors/slack/api.ts
4376
+ var API = "https://slack.com/api";
4377
+ async function slack(path, token, init = {}) {
4378
+ const url = new URL(`${API}/${path}`);
4379
+ for (const [key, value] of Object.entries(init.query ?? {})) {
4380
+ url.searchParams.set(key, value);
4381
+ }
4382
+ let response;
4383
+ try {
4384
+ response = await fetch(url.href, {
4385
+ method: init.method ?? "GET",
4386
+ headers: {
4387
+ Authorization: `Bearer ${token}`,
4388
+ ...init.body ? { "Content-Type": "application/json" } : {}
4389
+ },
4390
+ body: init.body ? JSON.stringify(init.body) : void 0
4391
+ });
4392
+ } catch (error) {
4393
+ throw new TransientError(`Slack is unreachable: ${error.message}`);
4394
+ }
4395
+ if (response.status >= 500 || response.status === 429) {
4396
+ throw new TransientError(`Slack answered ${response.status}`);
4397
+ }
4398
+ if (!response.ok) throw new Error(`Slack answered ${response.status}`);
4399
+ return await response.json();
4400
+ }
4401
+ function refuse(error) {
4402
+ if (error === "invalid_auth" || error === "not_authed" || error === "token_revoked" || error === "account_inactive") {
4403
+ throw new Error("Slack did not accept that token.");
4404
+ }
4405
+ throw new Error(error ? `Slack refused: ${error}` : "Slack refused the request.");
4406
+ }
4407
+ async function authTest(botToken) {
4408
+ const body = await slack("auth.test", botToken);
4409
+ if (!body.ok) refuse(body.error);
4410
+ const teamUrl = typeof body.url === "string" ? body.url : "";
4411
+ const team = typeof body.team === "string" ? body.team : "";
4412
+ const teamId = typeof body.team_id === "string" ? body.team_id : "";
4413
+ const botUserId = typeof body.user_id === "string" ? body.user_id : "";
4414
+ const botId = typeof body.bot_id === "string" ? body.bot_id : "";
4415
+ if (!teamId || !botUserId) throw new Error("Slack did not return which workspace this bot belongs to.");
4416
+ return { botToken, teamId, team, teamUrl, botUserId, botId };
4417
+ }
4418
+ async function listConversations(credential) {
4419
+ const body = await slack("conversations.list", credential.botToken, {
4420
+ query: {
4421
+ types: "public_channel,private_channel,im,mpim",
4422
+ exclude_archived: "true",
4423
+ limit: "200"
4424
+ }
4425
+ });
4426
+ if (!body.ok) refuse(body.error);
4427
+ const channels = Array.isArray(body.channels) ? body.channels : [];
4428
+ return channels.map((raw) => {
4429
+ const channel2 = raw;
4430
+ if (!channel2.id) return null;
4431
+ return { id: channel2.id, name: channel2.name ?? "", isIm: channel2.is_im === true };
4432
+ }).filter((channel2) => channel2 !== null);
4433
+ }
4434
+ async function history(credential, channelId, oldest) {
4435
+ const query2 = {
4436
+ channel: channelId,
4437
+ limit: "100",
4438
+ inclusive: "false"
4439
+ };
4440
+ if (oldest) query2.oldest = oldest;
4441
+ const body = await slack("conversations.history", credential.botToken, { query: query2 });
4442
+ if (!body.ok) {
4443
+ if (body.error === "not_in_channel" || body.error === "channel_not_found") return [];
4444
+ refuse(body.error);
4445
+ }
4446
+ const messages = Array.isArray(body.messages) ? body.messages : [];
4447
+ const parsed = [];
4448
+ for (const raw of messages) {
4449
+ const message = raw;
4450
+ if (!message.ts) continue;
4451
+ parsed.push({
4452
+ ts: message.ts,
4453
+ ...message.user ? { user: message.user } : {},
4454
+ ...message.bot_id ? { botId: message.bot_id } : {},
4455
+ text: message.text ?? "",
4456
+ ...message.thread_ts ? { threadTs: message.thread_ts } : {}
4457
+ });
4458
+ }
4459
+ return parsed.sort((a, b) => Number(a.ts) - Number(b.ts));
4460
+ }
4461
+ async function postMessage(credential, input) {
4462
+ const body = await slack("chat.postMessage", credential.botToken, {
4463
+ method: "POST",
4464
+ body: {
4465
+ channel: input.channel,
4466
+ text: input.text,
4467
+ ...input.threadTs ? { thread_ts: input.threadTs } : {}
4468
+ }
4469
+ });
4470
+ if (!body.ok) refuse(body.error);
4471
+ const ts = typeof body.ts === "string" ? body.ts : "";
4472
+ const channel2 = typeof body.channel === "string" ? body.channel : input.channel;
4473
+ if (!ts) throw new Error("Slack accepted the message but did not say where it landed.");
4474
+ return { ts, channel: channel2 };
4475
+ }
4476
+ function permalink2(credential, channel2, ts) {
4477
+ const host = credential.teamUrl.replace(/\/$/, "");
4478
+ return `${host}/archives/${channel2}/p${ts.replace(".", "")}`;
4479
+ }
4480
+
4481
+ // src/connectors/slack/runtime.ts
4482
+ function asAsked(value) {
4483
+ const record = value;
4484
+ if (!record || typeof record.channel !== "string" || typeof record.ts !== "string") {
4485
+ throw new Error("This task has lost the message it was about.");
4486
+ }
4487
+ return {
4488
+ channel: record.channel,
4489
+ ts: record.ts,
4490
+ user: typeof record.user === "string" ? record.user : void 0,
4491
+ text: typeof record.text === "string" ? record.text : "",
4492
+ threadTs: typeof record.threadTs === "string" ? record.threadTs : record.ts
4493
+ };
4494
+ }
4495
+ function asCursor(value) {
4496
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
4497
+ const out = {};
4498
+ for (const [key, entry] of Object.entries(value)) {
4499
+ if (typeof entry === "string") out[key] = entry;
4500
+ }
4501
+ return out;
4502
+ }
4503
+ function summarise2(text3) {
4504
+ const line = text3.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
4505
+ if (!line) return "Slack message";
4506
+ return line.length > 80 ? `${line.slice(0, 79)}\u2026` : line;
4507
+ }
4508
+ function isAsk(input) {
4509
+ if (input.user === input.botUserId || input.messageBotId && input.messageBotId === input.botId) {
4510
+ return false;
4511
+ }
4512
+ if (input.isIm) return Boolean(input.text.trim());
4513
+ return input.text.includes(`<@${input.botUserId}>`);
4514
+ }
4515
+ function destination(target, source) {
4516
+ if (typeof target.channel === "string" && target.channel.trim()) {
4517
+ return { channel: target.channel.trim() };
4518
+ }
4519
+ if (source.connectorId !== "slack") {
4520
+ throw new Error("There is no Slack channel behind this task. Choose one on the loop.");
4521
+ }
4522
+ const carry = source.carry ?? {};
4523
+ if (!carry.channel) throw new Error("There is no Slack channel behind this task. Choose one on the loop.");
4524
+ return { channel: carry.channel, threadTs: carry.ts };
4525
+ }
4526
+ function accountOf2(credential) {
4527
+ return {
4528
+ id: credential.teamId,
4529
+ label: credential.team || credential.teamId,
4530
+ url: credential.teamUrl || void 0
4531
+ };
4532
+ }
4533
+ var slackRuntime = {
4534
+ async readiness() {
4535
+ return { ready: true };
4536
+ },
4537
+ async poll({ workflowId, credential, cursor }) {
4538
+ if (workflowId !== "slack.ask") {
4539
+ throw new Error(`Slack cannot watch for ${workflowId}.`);
4540
+ }
4541
+ const account2 = credential;
4542
+ const previous = asCursor(cursor);
4543
+ const channels = await listConversations(account2);
4544
+ const next = { ...previous };
4545
+ const signals2 = [];
4546
+ for (const channel2 of channels) {
4547
+ const oldest = previous[channel2.id];
4548
+ const messages = await history(account2, channel2.id, oldest);
4549
+ if (messages.length === 0) {
4550
+ if (!next[channel2.id]) next[channel2.id] = String(Date.now() / 1e3);
4551
+ continue;
4552
+ }
4553
+ next[channel2.id] = messages[messages.length - 1].ts;
4554
+ if (!oldest) continue;
4555
+ for (const message of messages) {
4556
+ if (!isAsk({
4557
+ text: message.text,
4558
+ user: message.user,
4559
+ messageBotId: message.botId,
4560
+ isIm: channel2.isIm,
4561
+ botUserId: account2.botUserId,
4562
+ botId: account2.botId
4563
+ })) {
4564
+ continue;
4565
+ }
4566
+ const asked = {
4567
+ channel: channel2.id,
4568
+ ts: message.ts,
4569
+ user: message.user,
4570
+ text: message.text,
4571
+ threadTs: message.threadTs ?? message.ts
4572
+ };
4573
+ signals2.push({
4574
+ key: `${channel2.id}:${message.ts}`,
4575
+ kind: "message",
4576
+ ref: `${channel2.id}:${message.ts}`,
4577
+ title: summarise2(message.text),
4578
+ url: permalink2(account2, channel2.id, message.ts),
4579
+ payload: asked
4580
+ });
4581
+ }
4582
+ }
4583
+ return { signals: signals2, cursor: next };
4584
+ },
4585
+ async resolveWorkItem({ payload }) {
4586
+ const asked = asAsked(payload);
4587
+ return {
4588
+ kind: "message",
4589
+ ref: `${asked.channel}:${asked.ts}`,
4590
+ title: summarise2(asked.text),
4591
+ url: "",
4592
+ carry: { channel: asked.channel, ts: asked.threadTs },
4593
+ context: [
4594
+ {
4595
+ name: "MESSAGE.md",
4596
+ body: [`# What you were asked`, ``, asked.text, ``].join("\n")
4597
+ }
4598
+ ]
4599
+ };
4600
+ },
4601
+ async applyAction({ actionId, target, source, body, credential }) {
4602
+ if (actionId !== "slack.reply") throw new Error(`Slack cannot ${actionId}.`);
4603
+ const account2 = credential;
4604
+ const to = destination(target, source);
4605
+ const posted = await postMessage(account2, {
4606
+ channel: to.channel,
4607
+ text: body.trim(),
4608
+ threadTs: to.threadTs
4609
+ });
4610
+ return { url: permalink2(account2, posted.channel, posted.ts) };
4611
+ },
4612
+ auth: {
4613
+ async connectWithFields(fields) {
4614
+ const botToken = fields.botToken?.trim() ?? "";
4615
+ if (!botToken.startsWith("xoxb-")) {
4616
+ throw new Error("Paste the Bot User OAuth Token from your Slack app. It starts with xoxb-.");
4617
+ }
4618
+ const credential = await authTest(botToken);
4619
+ return { credential, account: accountOf2(credential) };
4620
+ },
4621
+ async identity(credential) {
4622
+ const stored = credential;
4623
+ const fresh = await authTest(stored.botToken);
4624
+ return { account: accountOf2(fresh) };
4625
+ }
4626
+ }
4627
+ };
4628
+
4629
+ // src/connectors/feishu/api.ts
4630
+ var API2 = {
4631
+ feishu: "https://open.feishu.cn/open-apis",
4632
+ lark: "https://open.larksuite.com/open-apis"
4633
+ };
4634
+ var APPLINK = {
4635
+ feishu: "https://applink.feishu.cn/client/chat/open",
4636
+ lark: "https://applink.larksuite.com/client/chat/open"
4637
+ };
4638
+ function parseHost(value) {
4639
+ const raw = (value ?? "feishu").trim().toLowerCase();
4640
+ if (!raw || raw === "feishu" || raw === "feishu.cn" || raw.includes("feishu.cn")) return "feishu";
4641
+ if (raw === "lark" || raw === "larksuite" || raw.includes("larksuite.com") || raw.includes("larkoffice.com")) {
4642
+ return "lark";
4643
+ }
4644
+ throw new Error("Open platform must be feishu (China) or lark (international).");
4645
+ }
4646
+ function refuse2(code, msg) {
4647
+ if (code === 10003 || code === 10012 || code === 10014) {
4648
+ throw new Error("Feishu did not accept those app credentials.");
4649
+ }
4650
+ throw new Error(msg ? `Feishu refused: ${msg}` : "Feishu refused the request.");
4651
+ }
4652
+ async function call(host, path, init = {}) {
4653
+ const url = new URL(`${API2[host]}${path}`);
4654
+ for (const [key, value] of Object.entries(init.query ?? {})) {
4655
+ url.searchParams.set(key, value);
4656
+ }
4657
+ let response;
4658
+ try {
4659
+ response = await fetch(url.href, {
4660
+ method: init.method ?? "GET",
4661
+ headers: {
4662
+ "Content-Type": "application/json; charset=utf-8",
4663
+ ...init.token ? { Authorization: `Bearer ${init.token}` } : {}
4664
+ },
4665
+ body: init.body ? JSON.stringify(init.body) : void 0
4666
+ });
4667
+ } catch (error) {
4668
+ throw new TransientError(`Feishu is unreachable: ${error.message}`);
4669
+ }
4670
+ if (response.status >= 500 || response.status === 429) {
4671
+ throw new TransientError(`Feishu answered ${response.status}`);
4672
+ }
4673
+ let body;
4674
+ try {
4675
+ body = await response.json();
4676
+ } catch {
4677
+ throw new Error(`Feishu answered ${response.status}`);
4678
+ }
4679
+ return body;
4680
+ }
4681
+ function dataOf(body) {
4682
+ if (body.code && body.code !== 0) refuse2(body.code, body.msg);
4683
+ return body.data ?? {};
4684
+ }
4685
+ async function tenantAccessToken(host, appId, appSecret) {
4686
+ const body = await call(host, "/auth/v3/tenant_access_token/internal", {
4687
+ method: "POST",
4688
+ body: { app_id: appId, app_secret: appSecret }
4689
+ });
4690
+ if (body.code && body.code !== 0) refuse2(body.code, body.msg);
4691
+ const token = body.tenant_access_token ?? "";
4692
+ const expire = typeof body.expire === "number" ? body.expire : 7200;
4693
+ if (!token) throw new Error("Feishu did not return a tenant token.");
4694
+ return { token, expiresAt: Date.now() + expire * 1e3 - 6e4 };
4695
+ }
4696
+ async function botInfo(credential) {
4697
+ const body = await call(credential.host, "/bot/v3/info", { token: credential.tenantAccessToken });
4698
+ if (body.code && body.code !== 0) refuse2(body.code, body.msg);
4699
+ const bot = body.bot ?? {};
4700
+ const openId = typeof bot.open_id === "string" ? bot.open_id : "";
4701
+ const name = typeof bot.app_name === "string" ? bot.app_name : "";
4702
+ const avatarUrl = typeof bot.avatar_url === "string" ? bot.avatar_url : void 0;
4703
+ if (!openId) throw new Error("Feishu did not return which bot this app is.");
4704
+ return { openId, name, ...avatarUrl ? { avatarUrl } : {} };
4705
+ }
4706
+ async function listChats(credential) {
4707
+ const chats = [];
4708
+ let pageToken = "";
4709
+ do {
4710
+ const query2 = { page_size: "100" };
4711
+ if (pageToken) query2.page_token = pageToken;
4712
+ const body = await call(credential.host, "/im/v1/chats", {
4713
+ token: credential.tenantAccessToken,
4714
+ query: query2
4715
+ });
4716
+ const data = dataOf(body);
4717
+ const items = Array.isArray(data.items) ? data.items : [];
4718
+ for (const raw of items) {
4719
+ const chat2 = raw;
4720
+ if (!chat2.chat_id) continue;
4721
+ chats.push({ id: chat2.chat_id, name: chat2.name ?? "" });
4722
+ }
4723
+ pageToken = data.has_more === true && typeof data.page_token === "string" ? data.page_token : "";
4724
+ } while (pageToken);
4725
+ return chats;
4726
+ }
4727
+ function textOf(content) {
4728
+ if (!content) return "";
4729
+ try {
4730
+ const parsed = JSON.parse(content);
4731
+ return typeof parsed.text === "string" ? parsed.text : content;
4732
+ } catch {
4733
+ return content;
4734
+ }
4735
+ }
4736
+ function mentionsOf(raw) {
4737
+ if (!Array.isArray(raw)) return [];
4738
+ return raw.map((entry) => {
4739
+ const mention = entry;
4740
+ return typeof mention.id === "string" ? mention.id : "";
4741
+ }).filter(Boolean);
4742
+ }
4743
+ async function history2(credential, chatId, oldestMs) {
4744
+ const parsed = [];
4745
+ let pageToken = "";
4746
+ do {
4747
+ const query2 = {
4748
+ container_id_type: "chat",
4749
+ container_id: chatId,
4750
+ sort_type: oldestMs ? "ByCreateTimeAsc" : "ByCreateTimeDesc",
4751
+ page_size: oldestMs ? "50" : "1"
4752
+ };
4753
+ if (oldestMs) query2.start_time = String(Math.floor(Number(oldestMs) / 1e3));
4754
+ if (pageToken) query2.page_token = pageToken;
4755
+ const body = await call(credential.host, "/im/v1/messages", {
4756
+ token: credential.tenantAccessToken,
4757
+ query: query2
4758
+ });
4759
+ if (body.code && body.code !== 0) {
4760
+ if (body.code === 230002 || body.code === 231203) return parsed;
4761
+ refuse2(body.code, body.msg);
4762
+ }
4763
+ const data = body.data ?? {};
4764
+ const items = Array.isArray(data.items) ? data.items : [];
4765
+ for (const raw of items) {
4766
+ const message = raw;
4767
+ if (!message.message_id || message.deleted) continue;
4768
+ parsed.push({
4769
+ id: message.message_id,
4770
+ chatId: message.chat_id ?? chatId,
4771
+ createTime: message.create_time ?? "",
4772
+ text: textOf(message.body?.content),
4773
+ ...message.sender?.id ? { senderId: message.sender.id } : {},
4774
+ ...message.sender?.sender_type ? { senderType: message.sender.sender_type } : {},
4775
+ mentionIds: mentionsOf(message.mentions)
4776
+ });
4777
+ }
4778
+ pageToken = oldestMs && data.has_more === true && typeof data.page_token === "string" ? data.page_token : "";
4779
+ } while (pageToken);
4780
+ return parsed.sort((a, b) => Number(a.createTime) - Number(b.createTime));
4781
+ }
4782
+ async function replyMessage(credential, messageId, text3) {
4783
+ const body = await call(credential.host, `/im/v1/messages/${messageId}/reply`, {
4784
+ token: credential.tenantAccessToken,
4785
+ method: "POST",
4786
+ body: { msg_type: "text", content: JSON.stringify({ text: text3 }) }
4787
+ });
4788
+ const data = dataOf(body);
4789
+ const id = typeof data.message_id === "string" ? data.message_id : "";
4790
+ const chatId = typeof data.chat_id === "string" ? data.chat_id : "";
4791
+ if (!id) throw new Error("Feishu accepted the reply but did not say where it landed.");
4792
+ return { id, chatId };
4793
+ }
4794
+ async function sendMessage(credential, chatId, text3) {
4795
+ const body = await call(credential.host, "/im/v1/messages", {
4796
+ token: credential.tenantAccessToken,
4797
+ method: "POST",
4798
+ query: { receive_id_type: "chat_id" },
4799
+ body: { receive_id: chatId, msg_type: "text", content: JSON.stringify({ text: text3 }) }
4800
+ });
4801
+ const data = dataOf(body);
4802
+ const id = typeof data.message_id === "string" ? data.message_id : "";
4803
+ const landed = typeof data.chat_id === "string" ? data.chat_id : chatId;
4804
+ if (!id) throw new Error("Feishu accepted the message but did not say where it landed.");
4805
+ return { id, chatId: landed };
4806
+ }
4807
+ function permalink3(host, chatId) {
4808
+ return `${APPLINK[host]}?openChatId=${chatId}`;
4809
+ }
4810
+
4811
+ // src/connectors/feishu/runtime.ts
4812
+ function asAsked2(value) {
4813
+ const record = value;
4814
+ if (!record || typeof record.chatId !== "string" || typeof record.messageId !== "string") {
4815
+ throw new Error("This task has lost the message it was about.");
4816
+ }
4817
+ return {
4818
+ chatId: record.chatId,
4819
+ messageId: record.messageId,
4820
+ user: typeof record.user === "string" ? record.user : void 0,
4821
+ text: typeof record.text === "string" ? record.text : ""
4822
+ };
4823
+ }
4824
+ function asCursor2(value) {
4825
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
4826
+ const out = {};
4827
+ for (const [key, entry] of Object.entries(value)) {
4828
+ if (typeof entry === "string") out[key] = entry;
4829
+ }
4830
+ return out;
4831
+ }
4832
+ function summarise3(text3) {
4833
+ const line = text3.replace(/@_user_\d+/g, "").replace(/\s+/g, " ").trim();
4834
+ if (!line) return "Feishu message";
4835
+ return line.length > 80 ? `${line.slice(0, 79)}\u2026` : line;
4836
+ }
4837
+ function isAsk2(input) {
4838
+ if (input.senderType === "app" || input.senderId === input.botOpenId) return false;
4839
+ return input.mentionIds.includes(input.botOpenId);
4840
+ }
4841
+ function destination2(target, source) {
4842
+ if (typeof target.chat === "string" && target.chat.trim()) {
4843
+ return { chatId: target.chat.trim() };
4844
+ }
4845
+ if (source.connectorId !== "feishu") {
4846
+ throw new Error("There is no Feishu chat behind this task. Choose one on the loop.");
4847
+ }
4848
+ const carry = source.carry ?? {};
4849
+ if (!carry.chatId && !carry.messageId) {
4850
+ throw new Error("There is no Feishu chat behind this task. Choose one on the loop.");
4851
+ }
4852
+ return { chatId: carry.chatId, messageId: carry.messageId };
4853
+ }
4854
+ function accountOf3(credential) {
4855
+ return {
4856
+ id: credential.botOpenId,
4857
+ label: credential.botName || credential.appId,
4858
+ ...credential.avatarUrl ? { avatarUrl: credential.avatarUrl } : {}
4859
+ };
4860
+ }
4861
+ async function usable(credential) {
4862
+ if (Date.now() < credential.expiresAt) return { credential };
4863
+ const minted = await tenantAccessToken(credential.host, credential.appId, credential.appSecret);
4864
+ const refreshed = {
4865
+ ...credential,
4866
+ tenantAccessToken: minted.token,
4867
+ expiresAt: minted.expiresAt
4868
+ };
4869
+ return { credential: refreshed, refreshed };
4870
+ }
4871
+ var feishuRuntime = {
4872
+ async readiness() {
4873
+ return { ready: true };
4874
+ },
4875
+ async poll({ workflowId, credential, cursor }) {
4876
+ if (workflowId !== "feishu.ask") {
4877
+ throw new Error(`Feishu cannot watch for ${workflowId}.`);
4878
+ }
4879
+ const { credential: account2 } = await usable(credential);
4880
+ const previous = asCursor2(cursor);
4881
+ const chats = await listChats(account2);
4882
+ const next = { ...previous };
4883
+ const signals2 = [];
4884
+ for (const chat2 of chats) {
4885
+ const oldest = previous[chat2.id];
4886
+ const messages = await history2(account2, chat2.id, oldest);
4887
+ if (messages.length === 0) {
4888
+ if (!next[chat2.id]) next[chat2.id] = String(Date.now());
4889
+ continue;
4890
+ }
4891
+ next[chat2.id] = messages[messages.length - 1].createTime;
4892
+ if (!oldest) continue;
4893
+ for (const message of messages) {
4894
+ if (oldest && Number(message.createTime) <= Number(oldest)) continue;
4895
+ if (!isAsk2({
4896
+ senderId: message.senderId,
4897
+ senderType: message.senderType,
4898
+ mentionIds: message.mentionIds,
4899
+ botOpenId: account2.botOpenId
4900
+ })) {
4901
+ continue;
4902
+ }
4903
+ const asked = {
4904
+ chatId: message.chatId,
4905
+ messageId: message.id,
4906
+ user: message.senderId,
4907
+ text: message.text
4908
+ };
4909
+ signals2.push({
4910
+ key: `${message.chatId}:${message.id}`,
4911
+ kind: "message",
4912
+ ref: message.id,
4913
+ title: summarise3(message.text),
4914
+ url: permalink3(account2.host, message.chatId),
4915
+ payload: asked
4916
+ });
4917
+ }
4918
+ }
4919
+ return { signals: signals2, cursor: next };
4920
+ },
4921
+ async resolveWorkItem({ payload }) {
4922
+ const asked = asAsked2(payload);
4923
+ return {
4924
+ kind: "message",
4925
+ ref: asked.messageId,
4926
+ title: summarise3(asked.text),
4927
+ url: "",
4928
+ carry: { chatId: asked.chatId, messageId: asked.messageId },
4929
+ context: [
4930
+ {
4931
+ name: "MESSAGE.md",
4932
+ body: [`# What you were asked`, ``, asked.text, ``].join("\n")
4933
+ }
4934
+ ]
4935
+ };
4936
+ },
4937
+ async applyAction({ actionId, target, source, body, credential }) {
4938
+ if (actionId !== "feishu.reply") throw new Error(`Feishu cannot ${actionId}.`);
4939
+ const { credential: account2 } = await usable(credential);
4940
+ const to = destination2(target, source);
4941
+ const text3 = body.trim();
4942
+ const posted = to.messageId && source.connectorId === "feishu" ? await replyMessage(account2, to.messageId, text3) : await sendMessage(account2, to.chatId ?? "", text3);
4943
+ return { url: permalink3(account2.host, posted.chatId || to.chatId || "") };
4944
+ },
4945
+ auth: {
4946
+ async connectWithFields(fields) {
4947
+ const appId = fields.appId?.trim() ?? "";
4948
+ const appSecret = fields.appSecret?.trim() ?? "";
4949
+ if (!appId) throw new Error("Paste the App ID from your Feishu or Lark app.");
4950
+ if (!appSecret) throw new Error("Paste the App Secret from your Feishu or Lark app.");
4951
+ const host = parseHost(fields.host);
4952
+ const minted = await tenantAccessToken(host, appId, appSecret);
4953
+ const bot = await botInfo({ host, tenantAccessToken: minted.token });
4954
+ const stored = {
4955
+ appId,
4956
+ appSecret,
4957
+ host,
4958
+ botOpenId: bot.openId,
4959
+ botName: bot.name,
4960
+ ...bot.avatarUrl ? { avatarUrl: bot.avatarUrl } : {},
4961
+ tenantAccessToken: minted.token,
4962
+ expiresAt: minted.expiresAt
4963
+ };
4964
+ return { credential: stored, account: accountOf3(stored) };
4965
+ },
4966
+ async identity(credential) {
4967
+ const stored = credential;
4968
+ const { credential: fresh, refreshed } = await usable(stored);
4969
+ const bot = await botInfo(fresh);
4970
+ const updated = {
4971
+ ...fresh,
4972
+ botOpenId: bot.openId,
4973
+ botName: bot.name,
4974
+ ...bot.avatarUrl ? { avatarUrl: bot.avatarUrl } : {}
4975
+ };
4976
+ return {
4977
+ account: accountOf3(updated),
4978
+ ...refreshed ? { renewedCredential: updated } : {}
4979
+ };
4980
+ }
4981
+ }
4982
+ };
4983
+
4206
4984
  // src/connectors/wechat/runtime.ts
4207
4985
  import { randomUUID } from "node:crypto";
4208
4986
 
@@ -4227,7 +5005,7 @@ function postHeaders(token) {
4227
5005
  ...token ? { Authorization: `Bearer ${token}` } : {}
4228
5006
  };
4229
5007
  }
4230
- async function call(url, init) {
5008
+ async function call2(url, init) {
4231
5009
  let response;
4232
5010
  try {
4233
5011
  response = await fetch(url, init);
@@ -4241,7 +5019,7 @@ async function call(url, init) {
4241
5019
  return await response.json();
4242
5020
  }
4243
5021
  async function requestQrCode() {
4244
- const body = await call(
5022
+ const body = await call2(
4245
5023
  `${WECHAT_API}/ilink/bot/get_bot_qrcode?bot_type=3`,
4246
5024
  {
4247
5025
  method: "POST",
@@ -4261,7 +5039,7 @@ function absolute(host) {
4261
5039
  async function qrCodeStatus(qrcode, host) {
4262
5040
  let body;
4263
5041
  try {
4264
- body = await call(
5042
+ body = await call2(
4265
5043
  `${absolute(host)}/ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrcode)}`,
4266
5044
  { headers: APP_HEADERS }
4267
5045
  );
@@ -4314,7 +5092,7 @@ function refusal(body) {
4314
5092
  return body.errmsg || `WeChat refused the request (${bad}).`;
4315
5093
  }
4316
5094
  async function notifyStart(credential) {
4317
- const body = await call(`${credential.baseUrl}/ilink/bot/msg/notifystart`, {
5095
+ const body = await call2(`${credential.baseUrl}/ilink/bot/msg/notifystart`, {
4318
5096
  method: "POST",
4319
5097
  headers: postHeaders(credential.botToken),
4320
5098
  body: JSON.stringify({ base_info: BASE_INFO })
@@ -4324,7 +5102,7 @@ async function notifyStart(credential) {
4324
5102
  }
4325
5103
  var FROM_USER = 1;
4326
5104
  var TEXT_ITEM = 1;
4327
- function textOf(message) {
5105
+ function textOf2(message) {
4328
5106
  return (message.item_list ?? []).filter((item) => item.type === TEXT_ITEM).map((item) => item.text_item?.text?.trim()).filter((text3) => Boolean(text3)).join("\n").trim();
4329
5107
  }
4330
5108
  function isFromPerson(message) {
@@ -4336,7 +5114,7 @@ async function getUpdates(credential, cursor) {
4336
5114
  const timer = setTimeout(() => abort.abort(), WAIT_MS);
4337
5115
  let body;
4338
5116
  try {
4339
- body = await call(`${credential.baseUrl}/ilink/bot/getupdates`, {
5117
+ body = await call2(`${credential.baseUrl}/ilink/bot/getupdates`, {
4340
5118
  method: "POST",
4341
5119
  headers: postHeaders(credential.botToken),
4342
5120
  body: JSON.stringify({ get_updates_buf: cursor, base_info: BASE_INFO }),
@@ -4352,8 +5130,8 @@ async function getUpdates(credential, cursor) {
4352
5130
  if (refused) throw new Error(refused);
4353
5131
  return { messages: body.msgs ?? [], cursor: body.get_updates_buf || cursor };
4354
5132
  }
4355
- async function sendMessage(credential, input) {
4356
- const body = await call(`${credential.baseUrl}/ilink/bot/sendmessage`, {
5133
+ async function sendMessage2(credential, input) {
5134
+ const body = await call2(`${credential.baseUrl}/ilink/bot/sendmessage`, {
4357
5135
  method: "POST",
4358
5136
  headers: postHeaders(credential.botToken),
4359
5137
  body: JSON.stringify({
@@ -4388,7 +5166,7 @@ function asAttempt(value) {
4388
5166
  }
4389
5167
  return { qrcode: record.qrcode, host: record.host };
4390
5168
  }
4391
- function asAsked(value) {
5169
+ function asAsked3(value) {
4392
5170
  const record = value;
4393
5171
  if (!record || typeof record.fromUserId !== "string" || typeof record.text !== "string") {
4394
5172
  throw new Error("This task has lost the message it was about.");
@@ -4400,7 +5178,7 @@ function asAsked(value) {
4400
5178
  text: record.text
4401
5179
  };
4402
5180
  }
4403
- function summarise2(text3) {
5181
+ function summarise4(text3) {
4404
5182
  const line = text3.split("\n")[0].trim();
4405
5183
  return line.length > 80 ? `${line.slice(0, 79)}\u2026` : line;
4406
5184
  }
@@ -4427,7 +5205,7 @@ function recipientFor(target, source, account2) {
4427
5205
  if (!carry.toUserId) throw new Error("There is nobody to reply to on this task.");
4428
5206
  return { userId: carry.toUserId, contextToken: carry.contextToken };
4429
5207
  }
4430
- function accountOf2(credential) {
5208
+ function accountOf4(credential) {
4431
5209
  return {
4432
5210
  id: credential.botId || credential.userId,
4433
5211
  label: credential.botId ? `WeChat bot ${credential.botId}` : "WeChat bot"
@@ -4455,7 +5233,7 @@ var wechatRuntime = {
4455
5233
  const signals2 = [];
4456
5234
  for (const message of messages) {
4457
5235
  if (!isFromPerson(message)) continue;
4458
- const text3 = textOf(message);
5236
+ const text3 = textOf2(message);
4459
5237
  if (!text3) continue;
4460
5238
  const fromUserId = message.from_user_id ?? "";
4461
5239
  const asked = {
@@ -4468,7 +5246,7 @@ var wechatRuntime = {
4468
5246
  key: `msg#${asked.messageId}`,
4469
5247
  kind: "message",
4470
5248
  ref: `msg#${asked.messageId}`,
4471
- title: summarise2(text3),
5249
+ title: summarise4(text3),
4472
5250
  // A message in a chat is not somewhere a browser can be sent.
4473
5251
  url: "",
4474
5252
  // Held rather than dropped, so a message that was sent and never
@@ -4481,11 +5259,11 @@ var wechatRuntime = {
4481
5259
  return { signals: signals2, cursor: next };
4482
5260
  },
4483
5261
  async resolveWorkItem({ payload }) {
4484
- const asked = asAsked(payload);
5262
+ const asked = asAsked3(payload);
4485
5263
  return {
4486
5264
  kind: "message",
4487
5265
  ref: `msg#${asked.messageId}`,
4488
- title: summarise2(asked.text),
5266
+ title: summarise4(asked.text),
4489
5267
  url: "",
4490
5268
  carry: { toUserId: asked.fromUserId, contextToken: asked.contextToken },
4491
5269
  context: [
@@ -4501,7 +5279,7 @@ var wechatRuntime = {
4501
5279
  const account2 = credential;
4502
5280
  const to = recipientFor(target, source, account2);
4503
5281
  for (const part of chunked(body)) {
4504
- await sendMessage(account2, {
5282
+ await sendMessage2(account2, {
4505
5283
  toUserId: to.userId,
4506
5284
  contextToken: to.contextToken,
4507
5285
  // Unique per piece, so a retry cannot post half an answer twice.
@@ -4527,7 +5305,7 @@ var wechatRuntime = {
4527
5305
  case "confirmed":
4528
5306
  return {
4529
5307
  state: "confirmed",
4530
- result: { credential: status.credential, account: accountOf2(status.credential) }
5308
+ result: { credential: status.credential, account: accountOf4(status.credential) }
4531
5309
  };
4532
5310
  case "expired":
4533
5311
  return { state: "expired", reason: status.reason };
@@ -4542,7 +5320,7 @@ var wechatRuntime = {
4542
5320
  async identity(credential) {
4543
5321
  const stored = credential;
4544
5322
  await notifyStart(stored);
4545
- return { account: accountOf2(stored) };
5323
+ return { account: accountOf4(stored) };
4546
5324
  }
4547
5325
  }
4548
5326
  };
@@ -4550,6 +5328,8 @@ var wechatRuntime = {
4550
5328
  // src/connectors/runtimes.ts
4551
5329
  var RUNTIMES = {
4552
5330
  github: githubRuntime,
5331
+ slack: slackRuntime,
5332
+ feishu: feishuRuntime,
4553
5333
  gmail: gmailRuntime,
4554
5334
  wechat: wechatRuntime,
4555
5335
  schedule: scheduleRuntime
@@ -8629,13 +9409,13 @@ function migrationsFolder() {
8629
9409
  join3(here, "../../../drizzle"),
8630
9410
  join3(process.cwd(), "drizzle")
8631
9411
  ];
8632
- return candidates2.find((folder3) => existsSync2(folder3)) ?? join3(process.cwd(), "drizzle");
9412
+ return candidates2.find((folder5) => existsSync2(folder5)) ?? join3(process.cwd(), "drizzle");
8633
9413
  }
8634
9414
  async function migrateIfNeeded() {
8635
9415
  if (process.env.LOOPABLE_SKIP_MIGRATE === "1") return;
8636
- const folder3 = migrationsFolder();
8637
- if (!existsSync2(folder3)) return;
8638
- await migrate(db(), { migrationsFolder: folder3 });
9416
+ const folder5 = migrationsFolder();
9417
+ if (!existsSync2(folder5)) return;
9418
+ await migrate(db(), { migrationsFolder: folder5 });
8639
9419
  }
8640
9420
  function rowsChanged(result) {
8641
9421
  return Number(result.rowsAffected ?? result.changes ?? 0);