loopable-cli 0.1.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.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/_connectorId-CkygNLny.js +1 -0
- package/.output/public/assets/_loopId-Cshlc5Ja.js +1 -0
- package/.output/public/assets/_taskId-BKDIJ_Fg.js +1 -0
- package/.output/public/assets/alert-BT2EVMJP.js +1 -0
- package/.output/public/assets/arrow-left-CWUZt6k2.js +1 -0
- package/.output/public/assets/arrow-right-dy_oOg_c.js +1 -0
- package/.output/public/assets/connection-status-DCqqmY_6.js +1 -0
- package/.output/public/assets/connector-icon-BUoK6Cru.js +1 -0
- package/.output/public/assets/connectors-CeLBtEtg.js +1 -0
- package/.output/public/assets/{external-link-D46X-bXa.js → external-link-DC11orFA.js} +1 -1
- package/.output/public/assets/gaps-BtFoD7mF.js +1 -0
- package/.output/public/assets/inbox-BYNjkNDJ.js +1 -0
- package/.output/public/assets/index-0yP3Jn_G.js +18 -0
- package/.output/public/assets/{loader-CcBl2u8m.js → loader-B4Epchm_.js} +1 -1
- package/.output/public/assets/loop-form-DnSJNc1k.js +1 -0
- package/.output/public/assets/loops-1CvbqO6m.js +1 -0
- package/.output/public/assets/new-m5NlqYQT.js +1 -0
- package/.output/public/assets/play-DD4xQgvi.js +1 -0
- package/.output/public/assets/preload-helper-CfdAYKD7.js +3 -0
- package/.output/public/assets/refresh-cw-C3xBnKfy.js +1 -0
- package/.output/public/assets/rolldown-runtime-CbXtAM7H.js +1 -0
- package/.output/public/assets/runners-D8QVl-Y6.js +1 -0
- package/.output/public/assets/select-Cppu-hti.js +1 -0
- package/.output/public/assets/setting-fields-bJ_gTG7Q.js +3 -0
- package/.output/public/assets/styles-ClTGaXU0.css +2 -0
- package/.output/public/assets/switch-hRIbNGOj.js +1 -0
- package/.output/public/assets/task-list-BAxjcVgr.js +1 -0
- package/.output/public/assets/textarea-Fyuzi6Vp.js +1 -0
- package/.output/public/assets/{trash-2-D6u6NpzO.js → trash-2-BXZzBsoV.js} +1 -1
- package/.output/public/assets/useValueChanged-VVyq4hb9.js +1 -0
- package/.output/server/{_connectorId-8nb5yOzG.mjs → _connectorId-C9yydJrC.mjs} +92 -11
- package/.output/server/_libs/@base-ui/react+[...].mjs +427 -427
- package/.output/server/{_loopId-BsUL1SeL.mjs → _loopId-B0VG-eBC.mjs} +44 -15
- package/.output/server/_ssr/{callback-CUINvl7M.mjs → access-DTttr563.mjs} +39 -5
- package/.output/server/_ssr/{agents-DAOZPgO2.mjs → agents-C7B6RJ6-.mjs} +3 -3
- package/.output/server/_ssr/{agents-Btql102J.mjs → agents-DG643yoJ.mjs} +4 -4
- package/.output/server/_ssr/{alert-NEiOCTOl.mjs → alert-DQvzE7bu.mjs} +1 -1
- package/.output/server/_ssr/{client-CKkOfh-s.mjs → client-BtVRVhQ2.mjs} +4 -2
- package/.output/server/_ssr/{connection-status-Bi5peUAk.mjs → connection-status-BZr3Y-IC.mjs} +2 -2
- package/.output/server/_ssr/{connections-qnw2GVlP.mjs → connections-Df-aDpfJ.mjs} +816 -17
- package/.output/server/_ssr/connector-icon-WvxwZ5TF.mjs +56 -0
- package/.output/server/_ssr/{connectors-BHNwuIMj.mjs → connectors-BU0IiRge.mjs} +7 -6
- package/.output/server/_ssr/{connectors-C28g0i4c.mjs → connectors-e0mf2pMs.mjs} +14 -3
- package/.output/server/_ssr/{gaps-RAFSXLZf.mjs → gaps-Cz76ZicM.mjs} +6 -6
- package/.output/server/_ssr/inbox-CAUBa1I3.mjs +114 -0
- package/.output/server/_ssr/{loop-form-DlObSewZ.mjs → loop-form-FqSaXiHl.mjs} +8 -7
- package/.output/server/_ssr/{loops-APFcP0y6.mjs → loops-D8WuK7Ke.mjs} +200 -7
- package/.output/server/_ssr/{loops-xt2Bn7_q.mjs → loops-Dqoebe8q.mjs} +8 -8
- package/.output/server/_ssr/{new-DfKyd2Wa.mjs → new-bBsVaWD6.mjs} +5 -5
- package/.output/server/_ssr/{router-3WmpE5b_.mjs → router-54IDWTVz.mjs} +68 -62
- package/.output/server/_ssr/{runners-By-mwc3F.mjs → runners-Cj6z9Mqh.mjs} +26 -3
- package/.output/server/_ssr/{runners-DtXs4Q8a.mjs → runners-D2UAdLyj.mjs} +3 -3
- package/.output/server/_ssr/{runners-DI_NxpHC.mjs → runners-DGWaVvWi.mjs} +4 -4
- package/.output/server/_ssr/{secrets-Hqti-niS.mjs → secrets-CRnpwGBh.mjs} +2 -2
- package/.output/server/_ssr/{setting-fields-8vKFj4df.mjs → setting-fields-DzBlslan.mjs} +6 -13
- package/.output/server/_ssr/ssr.mjs +45 -33
- package/.output/server/_ssr/{start-Bdc2GJUI.mjs → start-Vq3Oan3v.mjs} +3 -4
- package/.output/server/_ssr/{switch-ReNglgH6.mjs → switch-1pnd_bPJ.mjs} +2 -2
- package/.output/server/_ssr/{task-list-CUITIqkb.mjs → task-list-_qgBS9LU.mjs} +9 -8
- package/.output/server/_ssr/{tasks-vcoodp_D.mjs → tasks-BDqznm57.mjs} +98 -8
- package/.output/server/_ssr/{tasks-SAjNovX6.mjs → tasks-Qtml5TlK.mjs} +15 -6
- package/.output/server/_ssr/textarea-CHJTKtsJ.mjs +14 -0
- package/.output/server/{_tanstack-start-manifest_v-C0cP5F0M.mjs → _tanstack-start-manifest_v-CvheE7ZF.mjs} +54 -48
- package/.output/server/{_taskId-CXS3lod0.mjs → _taskId-V4QLBO2N.mjs} +17 -10
- package/.output/server/index.mjs +173 -166
- package/README.md +26 -21
- package/dist/cli.mjs +7146 -16
- package/dist/dispatcher.mjs +844 -33
- package/drizzle/0024_prompt_tasks.sql +44 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +21 -17
- package/.output/public/assets/_connectorId-BWjYn5tm.js +0 -1
- package/.output/public/assets/_loopId-Ds4RkyEc.js +0 -1
- package/.output/public/assets/_taskId-BOzICXDf.js +0 -1
- package/.output/public/assets/alert-Bn9-Zm0l.js +0 -1
- package/.output/public/assets/arrow-left-BF9juV9T.js +0 -1
- package/.output/public/assets/arrow-right-CzqvnOY1.js +0 -1
- package/.output/public/assets/button-HCY6DCY-.js +0 -1
- package/.output/public/assets/connection-status-CYZB8kLy.js +0 -1
- package/.output/public/assets/connector-icon-BZ_MUJV_.js +0 -1
- package/.output/public/assets/connectors-BvzttfSu.js +0 -1
- package/.output/public/assets/connectors-DdJNwRN_.js +0 -1
- package/.output/public/assets/gaps-D-I6xiDc.js +0 -1
- package/.output/public/assets/inbox-BUUu9xxE.js +0 -1
- package/.output/public/assets/index-ztzex0FN.js +0 -10
- package/.output/public/assets/loop-form-dB3CLcZb.js +0 -1
- package/.output/public/assets/loops-DRunQrxL.js +0 -1
- package/.output/public/assets/loops-oSdqMd29.js +0 -1
- package/.output/public/assets/manifests-DNMqf7Vi.js +0 -7
- package/.output/public/assets/new-uyFxVjuB.js +0 -1
- package/.output/public/assets/preload-helper-ORbuV86A.js +0 -3
- package/.output/public/assets/runners-DSU3r5Ww.js +0 -1
- package/.output/public/assets/setting-fields-ixkpdBb-.js +0 -3
- package/.output/public/assets/styles-DZL5V1o7.css +0 -2
- package/.output/public/assets/switch-d5ummmHY.js +0 -1
- package/.output/public/assets/task-list-BpOStt9v.js +0 -1
- package/.output/public/assets/tasks-B8MsKgic.js +0 -1
- package/.output/server/_ssr/access-zIl15o8y.mjs +0 -42
- package/.output/server/_ssr/connector-icon-DZXQLLFo.mjs +0 -37
- package/.output/server/_ssr/inbox-k5zHergU.mjs +0 -35
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { d as and, f as eq, h as lt, l as asc } from "../_libs/drizzle-orm.mjs";
|
|
2
|
-
import { a as connections, i as authAttempts, s as db } from "./client-
|
|
3
|
-
import { i as readCredential, s as writeCredential, t as deleteCredential } from "./secrets-
|
|
2
|
+
import { a as connections, i as authAttempts, s as db } from "./client-BtVRVhQ2.mjs";
|
|
3
|
+
import { i as readCredential, s as writeCredential, t as deleteCredential } from "./secrets-CRnpwGBh.mjs";
|
|
4
4
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { join } from "node:path";
|
|
7
|
-
//#region node_modules/.nitro/vite/services/ssr/assets/connections-
|
|
7
|
+
//#region node_modules/.nitro/vite/services/ssr/assets/connections-Df-aDpfJ.js
|
|
8
8
|
function defineManifest(manifest) {
|
|
9
9
|
return manifest;
|
|
10
10
|
}
|
|
@@ -468,7 +468,7 @@ var weekday = {
|
|
|
468
468
|
})),
|
|
469
469
|
default: "1"
|
|
470
470
|
};
|
|
471
|
-
var folder$
|
|
471
|
+
var folder$3 = {
|
|
472
472
|
key: "folder",
|
|
473
473
|
kind: "text",
|
|
474
474
|
label: "Folder to work in",
|
|
@@ -515,7 +515,7 @@ var scheduleManifest = defineManifest({
|
|
|
515
515
|
every,
|
|
516
516
|
at,
|
|
517
517
|
weekday,
|
|
518
|
-
folder$
|
|
518
|
+
folder$3
|
|
519
519
|
],
|
|
520
520
|
prompt: RECURRING_PROMPT,
|
|
521
521
|
guidancePlaceholder: "Anything that holds every time this runs. For example: this repository releases on Thursdays, so say if anything is unmerged by Wednesday evening.",
|
|
@@ -530,6 +530,159 @@ var scheduleManifest = defineManifest({
|
|
|
530
530
|
accepts: ["text", "review"]
|
|
531
531
|
}]
|
|
532
532
|
});
|
|
533
|
+
var folder$2 = {
|
|
534
|
+
key: "folder",
|
|
535
|
+
kind: "text",
|
|
536
|
+
label: "Folder to work in",
|
|
537
|
+
help: "An absolute path to a checkout on this host. The agent runs here, so it can read the code you are asking about.",
|
|
538
|
+
placeholder: "/Users/you/code/your-project"
|
|
539
|
+
};
|
|
540
|
+
var channel = {
|
|
541
|
+
key: "channel",
|
|
542
|
+
kind: "text",
|
|
543
|
+
label: "Channel",
|
|
544
|
+
help: "Leave empty to reply in the thread that asked. Set a channel ID when the work started somewhere else.",
|
|
545
|
+
placeholder: "C0123456789"
|
|
546
|
+
};
|
|
547
|
+
var ASK_PROMPT$2 = [
|
|
548
|
+
"Someone has sent you a message in Slack. Do what it asks, working in this directory,",
|
|
549
|
+
"and then answer them.",
|
|
550
|
+
"",
|
|
551
|
+
"You are answering into Slack. Be brief: a couple of short paragraphs at most,",
|
|
552
|
+
"no headings, no bullet lists unless you are genuinely listing things. Plain sentences.",
|
|
553
|
+
"",
|
|
554
|
+
"If the message asks a question about this code, read enough of it to answer",
|
|
555
|
+
"properly rather than guessing. If it asks for a change, make the change and",
|
|
556
|
+
"say in one line what you did. If the message is too vague to act on, say what",
|
|
557
|
+
"you need to know instead of assuming."
|
|
558
|
+
].join("\n");
|
|
559
|
+
var slackManifest = defineManifest({
|
|
560
|
+
id: "slack",
|
|
561
|
+
name: "Slack bot",
|
|
562
|
+
tagline: "Paste a bot token from a Slack app — not a Slack login. @mention or DM the bot, and it answers in the thread.",
|
|
563
|
+
docsUrl: "https://api.slack.com/apps",
|
|
564
|
+
icon: "Slack",
|
|
565
|
+
accent: "bg-[#4a154b] text-white",
|
|
566
|
+
auth: {
|
|
567
|
+
kind: "token",
|
|
568
|
+
helpUrl: "https://api.slack.com/apps",
|
|
569
|
+
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.",
|
|
570
|
+
fields: [{
|
|
571
|
+
key: "botToken",
|
|
572
|
+
label: "Bot User OAuth Token",
|
|
573
|
+
placeholder: "xoxb-...",
|
|
574
|
+
secret: true
|
|
575
|
+
}]
|
|
576
|
+
},
|
|
577
|
+
workflows: [{
|
|
578
|
+
id: "slack.ask",
|
|
579
|
+
name: "Do what I ask the bot",
|
|
580
|
+
summary: "Runs your agent when someone @mentions the bot or DMs it, and answers in the thread.",
|
|
581
|
+
trigger: "the bot is @mentioned, or someone DMs it",
|
|
582
|
+
writes: "a reply in the Slack thread",
|
|
583
|
+
runsIn: "folder",
|
|
584
|
+
settings: [folder$2],
|
|
585
|
+
prompt: ASK_PROMPT$2,
|
|
586
|
+
guidancePlaceholder: "Anything the agent should know about this project before it starts. For example: run pnpm test before saying a change works.",
|
|
587
|
+
answer: "text",
|
|
588
|
+
actionId: "slack.reply",
|
|
589
|
+
pollEveryMs: 6e4
|
|
590
|
+
}],
|
|
591
|
+
actions: [{
|
|
592
|
+
id: "slack.reply",
|
|
593
|
+
name: "Reply in Slack",
|
|
594
|
+
summary: "Post a message back to the thread that asked, or to a channel you name.",
|
|
595
|
+
target: [channel],
|
|
596
|
+
accepts: ["text"]
|
|
597
|
+
}],
|
|
598
|
+
settings: [],
|
|
599
|
+
allowsMultipleAccounts: false,
|
|
600
|
+
byHand: { kind: "none" }
|
|
601
|
+
});
|
|
602
|
+
var folder$1 = {
|
|
603
|
+
key: "folder",
|
|
604
|
+
kind: "text",
|
|
605
|
+
label: "Folder to work in",
|
|
606
|
+
help: "An absolute path to a checkout on this host. The agent runs here, so it can read the code you are asking about.",
|
|
607
|
+
placeholder: "/Users/you/code/your-project"
|
|
608
|
+
};
|
|
609
|
+
var chat = {
|
|
610
|
+
key: "chat",
|
|
611
|
+
kind: "text",
|
|
612
|
+
label: "Chat ID",
|
|
613
|
+
help: "Leave empty to reply to the message that asked. Set a chat ID (oc_…) when the work started somewhere else.",
|
|
614
|
+
placeholder: "oc_..."
|
|
615
|
+
};
|
|
616
|
+
var ASK_PROMPT$1 = [
|
|
617
|
+
"Someone has sent you a message in Feishu. Do what it asks, working in this directory,",
|
|
618
|
+
"and then answer them.",
|
|
619
|
+
"",
|
|
620
|
+
"You are answering into Feishu. Be brief: a couple of short paragraphs at most,",
|
|
621
|
+
"no headings, no bullet lists unless you are genuinely listing things. Plain sentences.",
|
|
622
|
+
"",
|
|
623
|
+
"If the message asks a question about this code, read enough of it to answer",
|
|
624
|
+
"properly rather than guessing. If it asks for a change, make the change and",
|
|
625
|
+
"say in one line what you did. If the message is too vague to act on, say what",
|
|
626
|
+
"you need to know instead of assuming."
|
|
627
|
+
].join("\n");
|
|
628
|
+
var feishuManifest = defineManifest({
|
|
629
|
+
id: "feishu",
|
|
630
|
+
name: "Feishu / Lark bot",
|
|
631
|
+
tagline: "Paste App ID and App Secret from a Feishu or Lark app — not a Feishu login. @mention the bot in a group, and it answers in the thread.",
|
|
632
|
+
docsUrl: "https://open.feishu.cn/app",
|
|
633
|
+
icon: "Feishu",
|
|
634
|
+
accent: "bg-[#3370ff] text-white",
|
|
635
|
+
auth: {
|
|
636
|
+
kind: "token",
|
|
637
|
+
helpUrl: "https://open.feishu.cn/app",
|
|
638
|
+
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).",
|
|
639
|
+
fields: [
|
|
640
|
+
{
|
|
641
|
+
key: "appId",
|
|
642
|
+
label: "App ID",
|
|
643
|
+
placeholder: "cli_...",
|
|
644
|
+
secret: false
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
key: "appSecret",
|
|
648
|
+
label: "App Secret",
|
|
649
|
+
placeholder: "App Secret",
|
|
650
|
+
secret: true
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
key: "host",
|
|
654
|
+
label: "Open platform",
|
|
655
|
+
placeholder: "feishu or lark",
|
|
656
|
+
secret: false,
|
|
657
|
+
optional: true
|
|
658
|
+
}
|
|
659
|
+
]
|
|
660
|
+
},
|
|
661
|
+
workflows: [{
|
|
662
|
+
id: "feishu.ask",
|
|
663
|
+
name: "Do what I ask the bot",
|
|
664
|
+
summary: "Runs your agent when someone @mentions the bot in a group it is in, and answers in the thread.",
|
|
665
|
+
trigger: "the bot is @mentioned in a group it is in",
|
|
666
|
+
writes: "a reply in the Feishu thread",
|
|
667
|
+
runsIn: "folder",
|
|
668
|
+
settings: [folder$1],
|
|
669
|
+
prompt: ASK_PROMPT$1,
|
|
670
|
+
guidancePlaceholder: "Anything the agent should know about this project before it starts. For example: run pnpm test before saying a change works.",
|
|
671
|
+
answer: "text",
|
|
672
|
+
actionId: "feishu.reply",
|
|
673
|
+
pollEveryMs: 6e4
|
|
674
|
+
}],
|
|
675
|
+
actions: [{
|
|
676
|
+
id: "feishu.reply",
|
|
677
|
+
name: "Reply in Feishu",
|
|
678
|
+
summary: "Post a message back to the thread that asked, or to a chat you name.",
|
|
679
|
+
target: [chat],
|
|
680
|
+
accepts: ["text"]
|
|
681
|
+
}],
|
|
682
|
+
settings: [],
|
|
683
|
+
allowsMultipleAccounts: false,
|
|
684
|
+
byHand: { kind: "none" }
|
|
685
|
+
});
|
|
533
686
|
/**
|
|
534
687
|
* Where the agent runs. Asking a phone "what broke the build" is only worth
|
|
535
688
|
* anything if the agent can read the build, so this workflow works in a real
|
|
@@ -600,6 +753,8 @@ var ASK_PROMPT = [
|
|
|
600
753
|
*/
|
|
601
754
|
var CONNECTOR_MANIFESTS = [
|
|
602
755
|
githubManifest,
|
|
756
|
+
slackManifest,
|
|
757
|
+
feishuManifest,
|
|
603
758
|
gmailManifest,
|
|
604
759
|
{
|
|
605
760
|
id: "wechat",
|
|
@@ -642,15 +797,22 @@ function connectorManifest(id) {
|
|
|
642
797
|
return CONNECTOR_MANIFESTS.find((manifest) => manifest.id === id);
|
|
643
798
|
}
|
|
644
799
|
/**
|
|
645
|
-
* Whether this connector is somewhere you have
|
|
800
|
+
* Whether this connector is somewhere you have a Connection.
|
|
646
801
|
*
|
|
647
802
|
* Asked in three places that would otherwise each assume every connector is:
|
|
648
|
-
* the page listing
|
|
803
|
+
* the page listing connections, the check for what stands between a loop and
|
|
649
804
|
* running, and the fetch of a credential for a run.
|
|
650
805
|
*/
|
|
651
806
|
function needsAccount(id) {
|
|
652
807
|
return connectorManifest(id)?.auth.kind !== "none";
|
|
653
808
|
}
|
|
809
|
+
/**
|
|
810
|
+
* What one Connection of this connector is called in the UI.
|
|
811
|
+
* A Slack or Feishu bot is a pasted app credential, not a login.
|
|
812
|
+
*/
|
|
813
|
+
function connectionNoun(id) {
|
|
814
|
+
return connectorManifest(id)?.auth.kind === "token" ? "bot" : "account";
|
|
815
|
+
}
|
|
654
816
|
function connectorWorkflow(connectorId, workflowId) {
|
|
655
817
|
return connectorManifest(connectorId)?.workflows.find((entry) => entry.id === workflowId);
|
|
656
818
|
}
|
|
@@ -1464,7 +1626,7 @@ function mailRef(id) {
|
|
|
1464
1626
|
* Where to send someone who wants to see the mail itself. Gmail has no
|
|
1465
1627
|
* per-message API link, but the web client will open one by id.
|
|
1466
1628
|
*/
|
|
1467
|
-
function permalink(id) {
|
|
1629
|
+
function permalink$2(id) {
|
|
1468
1630
|
return `https://mail.google.com/mail/u/0/#all/${id}`;
|
|
1469
1631
|
}
|
|
1470
1632
|
function headerOf(message, name) {
|
|
@@ -1532,7 +1694,7 @@ function attachmentNames(message) {
|
|
|
1532
1694
|
return names;
|
|
1533
1695
|
}
|
|
1534
1696
|
/** Enough of a message to pick it out of a list. */
|
|
1535
|
-
function summarise$
|
|
1697
|
+
function summarise$3(message) {
|
|
1536
1698
|
const subject = headerOf(message, "Subject") || "(no subject)";
|
|
1537
1699
|
const who = senderName(headerOf(message, "From"));
|
|
1538
1700
|
const title = who ? `${who}: ${subject}` : subject;
|
|
@@ -1611,8 +1773,8 @@ async function pollGmail(input) {
|
|
|
1611
1773
|
key: mailRef(id),
|
|
1612
1774
|
kind: "email",
|
|
1613
1775
|
ref: mailRef(id),
|
|
1614
|
-
title: summarise$
|
|
1615
|
-
url: permalink(id),
|
|
1776
|
+
title: summarise$3(message),
|
|
1777
|
+
url: permalink$2(id),
|
|
1616
1778
|
preview: (message.snippet ?? "").trim() || void 0,
|
|
1617
1779
|
payload: { messageId: id }
|
|
1618
1780
|
});
|
|
@@ -1737,7 +1899,7 @@ async function usableToken(credential) {
|
|
|
1737
1899
|
};
|
|
1738
1900
|
}
|
|
1739
1901
|
/** The mailbox, as a person would recognise it. */
|
|
1740
|
-
function accountOf$
|
|
1902
|
+
function accountOf$3(emailAddress) {
|
|
1741
1903
|
return {
|
|
1742
1904
|
id: emailAddress,
|
|
1743
1905
|
label: emailAddress
|
|
@@ -1778,8 +1940,8 @@ var gmailRuntime = defineRuntime({
|
|
|
1778
1940
|
return {
|
|
1779
1941
|
kind: "email",
|
|
1780
1942
|
ref: mailRef(message.id),
|
|
1781
|
-
title: summarise$
|
|
1782
|
-
url: permalink(message.id),
|
|
1943
|
+
title: summarise$3(message),
|
|
1944
|
+
url: permalink$2(message.id),
|
|
1783
1945
|
context: [mailContext(message)]
|
|
1784
1946
|
};
|
|
1785
1947
|
},
|
|
@@ -1817,12 +1979,12 @@ var gmailRuntime = defineRuntime({
|
|
|
1817
1979
|
});
|
|
1818
1980
|
return {
|
|
1819
1981
|
credential,
|
|
1820
|
-
account: accountOf$
|
|
1982
|
+
account: accountOf$3((await getProfile(credential.accessToken)).emailAddress)
|
|
1821
1983
|
};
|
|
1822
1984
|
},
|
|
1823
1985
|
async identity(credential) {
|
|
1824
1986
|
const { accessToken, refreshed } = await usableToken(credential);
|
|
1825
|
-
const account = accountOf$
|
|
1987
|
+
const account = accountOf$3((await getProfile(accessToken)).emailAddress);
|
|
1826
1988
|
return refreshed ? {
|
|
1827
1989
|
account,
|
|
1828
1990
|
renewedCredential: refreshed
|
|
@@ -1930,6 +2092,641 @@ var scheduleRuntime = {
|
|
|
1930
2092
|
}
|
|
1931
2093
|
};
|
|
1932
2094
|
/**
|
|
2095
|
+
* Slack Web API, called with a bot token pasted from a workspace-installed app.
|
|
2096
|
+
* Loopable polls; Slack never has to reach this machine.
|
|
2097
|
+
*/
|
|
2098
|
+
var API$1 = "https://slack.com/api";
|
|
2099
|
+
async function slack(path, token, init = {}) {
|
|
2100
|
+
const url = new URL(`${API$1}/${path}`);
|
|
2101
|
+
for (const [key, value] of Object.entries(init.query ?? {})) url.searchParams.set(key, value);
|
|
2102
|
+
let response;
|
|
2103
|
+
try {
|
|
2104
|
+
response = await fetch(url.href, {
|
|
2105
|
+
method: init.method ?? "GET",
|
|
2106
|
+
headers: {
|
|
2107
|
+
Authorization: `Bearer ${token}`,
|
|
2108
|
+
...init.body ? { "Content-Type": "application/json" } : {}
|
|
2109
|
+
},
|
|
2110
|
+
body: init.body ? JSON.stringify(init.body) : void 0
|
|
2111
|
+
});
|
|
2112
|
+
} catch (error) {
|
|
2113
|
+
throw new TransientError(`Slack is unreachable: ${error.message}`);
|
|
2114
|
+
}
|
|
2115
|
+
if (response.status >= 500 || response.status === 429) throw new TransientError(`Slack answered ${response.status}`);
|
|
2116
|
+
if (!response.ok) throw new Error(`Slack answered ${response.status}`);
|
|
2117
|
+
return await response.json();
|
|
2118
|
+
}
|
|
2119
|
+
function refuse$1(error) {
|
|
2120
|
+
if (error === "invalid_auth" || error === "not_authed" || error === "token_revoked" || error === "account_inactive") throw new Error("Slack did not accept that token.");
|
|
2121
|
+
throw new Error(error ? `Slack refused: ${error}` : "Slack refused the request.");
|
|
2122
|
+
}
|
|
2123
|
+
async function authTest(botToken) {
|
|
2124
|
+
const body = await slack("auth.test", botToken);
|
|
2125
|
+
if (!body.ok) refuse$1(body.error);
|
|
2126
|
+
const teamUrl = typeof body.url === "string" ? body.url : "";
|
|
2127
|
+
const team = typeof body.team === "string" ? body.team : "";
|
|
2128
|
+
const teamId = typeof body.team_id === "string" ? body.team_id : "";
|
|
2129
|
+
const botUserId = typeof body.user_id === "string" ? body.user_id : "";
|
|
2130
|
+
const botId = typeof body.bot_id === "string" ? body.bot_id : "";
|
|
2131
|
+
if (!teamId || !botUserId) throw new Error("Slack did not return which workspace this bot belongs to.");
|
|
2132
|
+
return {
|
|
2133
|
+
botToken,
|
|
2134
|
+
teamId,
|
|
2135
|
+
team,
|
|
2136
|
+
teamUrl,
|
|
2137
|
+
botUserId,
|
|
2138
|
+
botId
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
async function listConversations(credential) {
|
|
2142
|
+
const body = await slack("conversations.list", credential.botToken, { query: {
|
|
2143
|
+
types: "public_channel,private_channel,im,mpim",
|
|
2144
|
+
exclude_archived: "true",
|
|
2145
|
+
limit: "200"
|
|
2146
|
+
} });
|
|
2147
|
+
if (!body.ok) refuse$1(body.error);
|
|
2148
|
+
return (Array.isArray(body.channels) ? body.channels : []).map((raw) => {
|
|
2149
|
+
const channel = raw;
|
|
2150
|
+
if (!channel.id) return null;
|
|
2151
|
+
return {
|
|
2152
|
+
id: channel.id,
|
|
2153
|
+
name: channel.name ?? "",
|
|
2154
|
+
isIm: channel.is_im === true
|
|
2155
|
+
};
|
|
2156
|
+
}).filter((channel) => channel !== null);
|
|
2157
|
+
}
|
|
2158
|
+
async function history$1(credential, channelId, oldest) {
|
|
2159
|
+
const query = {
|
|
2160
|
+
channel: channelId,
|
|
2161
|
+
limit: "100",
|
|
2162
|
+
inclusive: "false"
|
|
2163
|
+
};
|
|
2164
|
+
if (oldest) query.oldest = oldest;
|
|
2165
|
+
const body = await slack("conversations.history", credential.botToken, { query });
|
|
2166
|
+
if (!body.ok) {
|
|
2167
|
+
if (body.error === "not_in_channel" || body.error === "channel_not_found") return [];
|
|
2168
|
+
refuse$1(body.error);
|
|
2169
|
+
}
|
|
2170
|
+
const messages = Array.isArray(body.messages) ? body.messages : [];
|
|
2171
|
+
const parsed = [];
|
|
2172
|
+
for (const raw of messages) {
|
|
2173
|
+
const message = raw;
|
|
2174
|
+
if (!message.ts) continue;
|
|
2175
|
+
parsed.push({
|
|
2176
|
+
ts: message.ts,
|
|
2177
|
+
...message.user ? { user: message.user } : {},
|
|
2178
|
+
...message.bot_id ? { botId: message.bot_id } : {},
|
|
2179
|
+
text: message.text ?? "",
|
|
2180
|
+
...message.thread_ts ? { threadTs: message.thread_ts } : {}
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
return parsed.sort((a, b) => Number(a.ts) - Number(b.ts));
|
|
2184
|
+
}
|
|
2185
|
+
async function postMessage(credential, input) {
|
|
2186
|
+
const body = await slack("chat.postMessage", credential.botToken, {
|
|
2187
|
+
method: "POST",
|
|
2188
|
+
body: {
|
|
2189
|
+
channel: input.channel,
|
|
2190
|
+
text: input.text,
|
|
2191
|
+
...input.threadTs ? { thread_ts: input.threadTs } : {}
|
|
2192
|
+
}
|
|
2193
|
+
});
|
|
2194
|
+
if (!body.ok) refuse$1(body.error);
|
|
2195
|
+
const ts = typeof body.ts === "string" ? body.ts : "";
|
|
2196
|
+
const channel = typeof body.channel === "string" ? body.channel : input.channel;
|
|
2197
|
+
if (!ts) throw new Error("Slack accepted the message but did not say where it landed.");
|
|
2198
|
+
return {
|
|
2199
|
+
ts,
|
|
2200
|
+
channel
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
function permalink$1(credential, channel, ts) {
|
|
2204
|
+
return `${credential.teamUrl.replace(/\/$/, "")}/archives/${channel}/p${ts.replace(".", "")}`;
|
|
2205
|
+
}
|
|
2206
|
+
function asAsked$2(value) {
|
|
2207
|
+
const record = value;
|
|
2208
|
+
if (!record || typeof record.channel !== "string" || typeof record.ts !== "string") throw new Error("This task has lost the message it was about.");
|
|
2209
|
+
return {
|
|
2210
|
+
channel: record.channel,
|
|
2211
|
+
ts: record.ts,
|
|
2212
|
+
user: typeof record.user === "string" ? record.user : void 0,
|
|
2213
|
+
text: typeof record.text === "string" ? record.text : "",
|
|
2214
|
+
threadTs: typeof record.threadTs === "string" ? record.threadTs : record.ts
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
2217
|
+
function asCursor$1(value) {
|
|
2218
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
2219
|
+
const out = {};
|
|
2220
|
+
for (const [key, entry] of Object.entries(value)) if (typeof entry === "string") out[key] = entry;
|
|
2221
|
+
return out;
|
|
2222
|
+
}
|
|
2223
|
+
function summarise$2(text) {
|
|
2224
|
+
const line = text.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
2225
|
+
if (!line) return "Slack message";
|
|
2226
|
+
return line.length > 80 ? `${line.slice(0, 79)}…` : line;
|
|
2227
|
+
}
|
|
2228
|
+
function isAsk$1(input) {
|
|
2229
|
+
if (input.user === input.botUserId || input.messageBotId && input.messageBotId === input.botId) return false;
|
|
2230
|
+
if (input.isIm) return Boolean(input.text.trim());
|
|
2231
|
+
return input.text.includes(`<@${input.botUserId}>`);
|
|
2232
|
+
}
|
|
2233
|
+
function destination$1(target, source) {
|
|
2234
|
+
if (typeof target.channel === "string" && target.channel.trim()) return { channel: target.channel.trim() };
|
|
2235
|
+
if (source.connectorId !== "slack") throw new Error("There is no Slack channel behind this task. Choose one on the loop.");
|
|
2236
|
+
const carry = source.carry ?? {};
|
|
2237
|
+
if (!carry.channel) throw new Error("There is no Slack channel behind this task. Choose one on the loop.");
|
|
2238
|
+
return {
|
|
2239
|
+
channel: carry.channel,
|
|
2240
|
+
threadTs: carry.ts
|
|
2241
|
+
};
|
|
2242
|
+
}
|
|
2243
|
+
function accountOf$2(credential) {
|
|
2244
|
+
return {
|
|
2245
|
+
id: credential.teamId,
|
|
2246
|
+
label: credential.team || credential.teamId,
|
|
2247
|
+
url: credential.teamUrl || void 0
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
var slackRuntime = {
|
|
2251
|
+
async readiness() {
|
|
2252
|
+
return { ready: true };
|
|
2253
|
+
},
|
|
2254
|
+
async poll({ workflowId, credential, cursor }) {
|
|
2255
|
+
if (workflowId !== "slack.ask") throw new Error(`Slack cannot watch for ${workflowId}.`);
|
|
2256
|
+
const account = credential;
|
|
2257
|
+
const previous = asCursor$1(cursor);
|
|
2258
|
+
const channels = await listConversations(account);
|
|
2259
|
+
const next = { ...previous };
|
|
2260
|
+
const signals = [];
|
|
2261
|
+
for (const channel of channels) {
|
|
2262
|
+
const oldest = previous[channel.id];
|
|
2263
|
+
const messages = await history$1(account, channel.id, oldest);
|
|
2264
|
+
if (messages.length === 0) {
|
|
2265
|
+
if (!next[channel.id]) next[channel.id] = String(Date.now() / 1e3);
|
|
2266
|
+
continue;
|
|
2267
|
+
}
|
|
2268
|
+
next[channel.id] = messages[messages.length - 1].ts;
|
|
2269
|
+
if (!oldest) continue;
|
|
2270
|
+
for (const message of messages) {
|
|
2271
|
+
if (!isAsk$1({
|
|
2272
|
+
text: message.text,
|
|
2273
|
+
user: message.user,
|
|
2274
|
+
messageBotId: message.botId,
|
|
2275
|
+
isIm: channel.isIm,
|
|
2276
|
+
botUserId: account.botUserId,
|
|
2277
|
+
botId: account.botId
|
|
2278
|
+
})) continue;
|
|
2279
|
+
const asked = {
|
|
2280
|
+
channel: channel.id,
|
|
2281
|
+
ts: message.ts,
|
|
2282
|
+
user: message.user,
|
|
2283
|
+
text: message.text,
|
|
2284
|
+
threadTs: message.threadTs ?? message.ts
|
|
2285
|
+
};
|
|
2286
|
+
signals.push({
|
|
2287
|
+
key: `${channel.id}:${message.ts}`,
|
|
2288
|
+
kind: "message",
|
|
2289
|
+
ref: `${channel.id}:${message.ts}`,
|
|
2290
|
+
title: summarise$2(message.text),
|
|
2291
|
+
url: permalink$1(account, channel.id, message.ts),
|
|
2292
|
+
payload: asked
|
|
2293
|
+
});
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
return {
|
|
2297
|
+
signals,
|
|
2298
|
+
cursor: next
|
|
2299
|
+
};
|
|
2300
|
+
},
|
|
2301
|
+
async resolveWorkItem({ payload }) {
|
|
2302
|
+
const asked = asAsked$2(payload);
|
|
2303
|
+
return {
|
|
2304
|
+
kind: "message",
|
|
2305
|
+
ref: `${asked.channel}:${asked.ts}`,
|
|
2306
|
+
title: summarise$2(asked.text),
|
|
2307
|
+
url: "",
|
|
2308
|
+
carry: {
|
|
2309
|
+
channel: asked.channel,
|
|
2310
|
+
ts: asked.threadTs
|
|
2311
|
+
},
|
|
2312
|
+
context: [{
|
|
2313
|
+
name: "MESSAGE.md",
|
|
2314
|
+
body: [
|
|
2315
|
+
`# What you were asked`,
|
|
2316
|
+
``,
|
|
2317
|
+
asked.text,
|
|
2318
|
+
``
|
|
2319
|
+
].join("\n")
|
|
2320
|
+
}]
|
|
2321
|
+
};
|
|
2322
|
+
},
|
|
2323
|
+
async applyAction({ actionId, target, source, body, credential }) {
|
|
2324
|
+
if (actionId !== "slack.reply") throw new Error(`Slack cannot ${actionId}.`);
|
|
2325
|
+
const account = credential;
|
|
2326
|
+
const to = destination$1(target, source);
|
|
2327
|
+
const posted = await postMessage(account, {
|
|
2328
|
+
channel: to.channel,
|
|
2329
|
+
text: body.trim(),
|
|
2330
|
+
threadTs: to.threadTs
|
|
2331
|
+
});
|
|
2332
|
+
return { url: permalink$1(account, posted.channel, posted.ts) };
|
|
2333
|
+
},
|
|
2334
|
+
auth: {
|
|
2335
|
+
async connectWithFields(fields) {
|
|
2336
|
+
const botToken = fields.botToken?.trim() ?? "";
|
|
2337
|
+
if (!botToken.startsWith("xoxb-")) throw new Error("Paste the Bot User OAuth Token from your Slack app. It starts with xoxb-.");
|
|
2338
|
+
const credential = await authTest(botToken);
|
|
2339
|
+
return {
|
|
2340
|
+
credential,
|
|
2341
|
+
account: accountOf$2(credential)
|
|
2342
|
+
};
|
|
2343
|
+
},
|
|
2344
|
+
async identity(credential) {
|
|
2345
|
+
return { account: accountOf$2(await authTest(credential.botToken)) };
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
};
|
|
2349
|
+
/**
|
|
2350
|
+
* Feishu / Lark Open API, called with a custom app's id and secret.
|
|
2351
|
+
* Loopable polls groups the bot is in; Feishu never has to reach this machine.
|
|
2352
|
+
*/
|
|
2353
|
+
var API = {
|
|
2354
|
+
feishu: "https://open.feishu.cn/open-apis",
|
|
2355
|
+
lark: "https://open.larksuite.com/open-apis"
|
|
2356
|
+
};
|
|
2357
|
+
var APPLINK = {
|
|
2358
|
+
feishu: "https://applink.feishu.cn/client/chat/open",
|
|
2359
|
+
lark: "https://applink.larksuite.com/client/chat/open"
|
|
2360
|
+
};
|
|
2361
|
+
function parseHost(value) {
|
|
2362
|
+
const raw = (value ?? "feishu").trim().toLowerCase();
|
|
2363
|
+
if (!raw || raw === "feishu" || raw === "feishu.cn" || raw.includes("feishu.cn")) return "feishu";
|
|
2364
|
+
if (raw === "lark" || raw === "larksuite" || raw.includes("larksuite.com") || raw.includes("larkoffice.com")) return "lark";
|
|
2365
|
+
throw new Error("Open platform must be feishu (China) or lark (international).");
|
|
2366
|
+
}
|
|
2367
|
+
function refuse(code, msg) {
|
|
2368
|
+
if (code === 10003 || code === 10012 || code === 10014) throw new Error("Feishu did not accept those app credentials.");
|
|
2369
|
+
throw new Error(msg ? `Feishu refused: ${msg}` : "Feishu refused the request.");
|
|
2370
|
+
}
|
|
2371
|
+
async function call$1(host, path, init = {}) {
|
|
2372
|
+
const url = new URL(`${API[host]}${path}`);
|
|
2373
|
+
for (const [key, value] of Object.entries(init.query ?? {})) url.searchParams.set(key, value);
|
|
2374
|
+
let response;
|
|
2375
|
+
try {
|
|
2376
|
+
response = await fetch(url.href, {
|
|
2377
|
+
method: init.method ?? "GET",
|
|
2378
|
+
headers: {
|
|
2379
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
2380
|
+
...init.token ? { Authorization: `Bearer ${init.token}` } : {}
|
|
2381
|
+
},
|
|
2382
|
+
body: init.body ? JSON.stringify(init.body) : void 0
|
|
2383
|
+
});
|
|
2384
|
+
} catch (error) {
|
|
2385
|
+
throw new TransientError(`Feishu is unreachable: ${error.message}`);
|
|
2386
|
+
}
|
|
2387
|
+
if (response.status >= 500 || response.status === 429) throw new TransientError(`Feishu answered ${response.status}`);
|
|
2388
|
+
let body;
|
|
2389
|
+
try {
|
|
2390
|
+
body = await response.json();
|
|
2391
|
+
} catch {
|
|
2392
|
+
throw new Error(`Feishu answered ${response.status}`);
|
|
2393
|
+
}
|
|
2394
|
+
return body;
|
|
2395
|
+
}
|
|
2396
|
+
function dataOf(body) {
|
|
2397
|
+
if (body.code && body.code !== 0) refuse(body.code, body.msg);
|
|
2398
|
+
return body.data ?? {};
|
|
2399
|
+
}
|
|
2400
|
+
async function tenantAccessToken(host, appId, appSecret) {
|
|
2401
|
+
const body = await call$1(host, "/auth/v3/tenant_access_token/internal", {
|
|
2402
|
+
method: "POST",
|
|
2403
|
+
body: {
|
|
2404
|
+
app_id: appId,
|
|
2405
|
+
app_secret: appSecret
|
|
2406
|
+
}
|
|
2407
|
+
});
|
|
2408
|
+
if (body.code && body.code !== 0) refuse(body.code, body.msg);
|
|
2409
|
+
const token = body.tenant_access_token ?? "";
|
|
2410
|
+
const expire = typeof body.expire === "number" ? body.expire : 7200;
|
|
2411
|
+
if (!token) throw new Error("Feishu did not return a tenant token.");
|
|
2412
|
+
return {
|
|
2413
|
+
token,
|
|
2414
|
+
expiresAt: Date.now() + expire * 1e3 - 6e4
|
|
2415
|
+
};
|
|
2416
|
+
}
|
|
2417
|
+
async function botInfo(credential) {
|
|
2418
|
+
const body = await call$1(credential.host, "/bot/v3/info", { token: credential.tenantAccessToken });
|
|
2419
|
+
if (body.code && body.code !== 0) refuse(body.code, body.msg);
|
|
2420
|
+
const bot = body.bot ?? {};
|
|
2421
|
+
const openId = typeof bot.open_id === "string" ? bot.open_id : "";
|
|
2422
|
+
const name = typeof bot.app_name === "string" ? bot.app_name : "";
|
|
2423
|
+
const avatarUrl = typeof bot.avatar_url === "string" ? bot.avatar_url : void 0;
|
|
2424
|
+
if (!openId) throw new Error("Feishu did not return which bot this app is.");
|
|
2425
|
+
return {
|
|
2426
|
+
openId,
|
|
2427
|
+
name,
|
|
2428
|
+
...avatarUrl ? { avatarUrl } : {}
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
async function listChats(credential) {
|
|
2432
|
+
const chats = [];
|
|
2433
|
+
let pageToken = "";
|
|
2434
|
+
do {
|
|
2435
|
+
const query = { page_size: "100" };
|
|
2436
|
+
if (pageToken) query.page_token = pageToken;
|
|
2437
|
+
const data = dataOf(await call$1(credential.host, "/im/v1/chats", {
|
|
2438
|
+
token: credential.tenantAccessToken,
|
|
2439
|
+
query
|
|
2440
|
+
}));
|
|
2441
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
2442
|
+
for (const raw of items) {
|
|
2443
|
+
const chat = raw;
|
|
2444
|
+
if (!chat.chat_id) continue;
|
|
2445
|
+
chats.push({
|
|
2446
|
+
id: chat.chat_id,
|
|
2447
|
+
name: chat.name ?? ""
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
pageToken = data.has_more === true && typeof data.page_token === "string" ? data.page_token : "";
|
|
2451
|
+
} while (pageToken);
|
|
2452
|
+
return chats;
|
|
2453
|
+
}
|
|
2454
|
+
function textOf$1(content) {
|
|
2455
|
+
if (!content) return "";
|
|
2456
|
+
try {
|
|
2457
|
+
const parsed = JSON.parse(content);
|
|
2458
|
+
return typeof parsed.text === "string" ? parsed.text : content;
|
|
2459
|
+
} catch {
|
|
2460
|
+
return content;
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
function mentionsOf(raw) {
|
|
2464
|
+
if (!Array.isArray(raw)) return [];
|
|
2465
|
+
return raw.map((entry) => {
|
|
2466
|
+
const mention = entry;
|
|
2467
|
+
return typeof mention.id === "string" ? mention.id : "";
|
|
2468
|
+
}).filter(Boolean);
|
|
2469
|
+
}
|
|
2470
|
+
async function history(credential, chatId, oldestMs) {
|
|
2471
|
+
const parsed = [];
|
|
2472
|
+
let pageToken = "";
|
|
2473
|
+
do {
|
|
2474
|
+
const query = {
|
|
2475
|
+
container_id_type: "chat",
|
|
2476
|
+
container_id: chatId,
|
|
2477
|
+
sort_type: oldestMs ? "ByCreateTimeAsc" : "ByCreateTimeDesc",
|
|
2478
|
+
page_size: oldestMs ? "50" : "1"
|
|
2479
|
+
};
|
|
2480
|
+
if (oldestMs) query.start_time = String(Math.floor(Number(oldestMs) / 1e3));
|
|
2481
|
+
if (pageToken) query.page_token = pageToken;
|
|
2482
|
+
const body = await call$1(credential.host, "/im/v1/messages", {
|
|
2483
|
+
token: credential.tenantAccessToken,
|
|
2484
|
+
query
|
|
2485
|
+
});
|
|
2486
|
+
if (body.code && body.code !== 0) {
|
|
2487
|
+
if (body.code === 230002 || body.code === 231203) return parsed;
|
|
2488
|
+
refuse(body.code, body.msg);
|
|
2489
|
+
}
|
|
2490
|
+
const data = body.data ?? {};
|
|
2491
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
2492
|
+
for (const raw of items) {
|
|
2493
|
+
const message = raw;
|
|
2494
|
+
if (!message.message_id || message.deleted) continue;
|
|
2495
|
+
parsed.push({
|
|
2496
|
+
id: message.message_id,
|
|
2497
|
+
chatId: message.chat_id ?? chatId,
|
|
2498
|
+
createTime: message.create_time ?? "",
|
|
2499
|
+
text: textOf$1(message.body?.content),
|
|
2500
|
+
...message.sender?.id ? { senderId: message.sender.id } : {},
|
|
2501
|
+
...message.sender?.sender_type ? { senderType: message.sender.sender_type } : {},
|
|
2502
|
+
mentionIds: mentionsOf(message.mentions)
|
|
2503
|
+
});
|
|
2504
|
+
}
|
|
2505
|
+
pageToken = oldestMs && data.has_more === true && typeof data.page_token === "string" ? data.page_token : "";
|
|
2506
|
+
} while (pageToken);
|
|
2507
|
+
return parsed.sort((a, b) => Number(a.createTime) - Number(b.createTime));
|
|
2508
|
+
}
|
|
2509
|
+
async function replyMessage(credential, messageId, text) {
|
|
2510
|
+
const data = dataOf(await call$1(credential.host, `/im/v1/messages/${messageId}/reply`, {
|
|
2511
|
+
token: credential.tenantAccessToken,
|
|
2512
|
+
method: "POST",
|
|
2513
|
+
body: {
|
|
2514
|
+
msg_type: "text",
|
|
2515
|
+
content: JSON.stringify({ text })
|
|
2516
|
+
}
|
|
2517
|
+
}));
|
|
2518
|
+
const id = typeof data.message_id === "string" ? data.message_id : "";
|
|
2519
|
+
const chatId = typeof data.chat_id === "string" ? data.chat_id : "";
|
|
2520
|
+
if (!id) throw new Error("Feishu accepted the reply but did not say where it landed.");
|
|
2521
|
+
return {
|
|
2522
|
+
id,
|
|
2523
|
+
chatId
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
async function sendMessage$1(credential, chatId, text) {
|
|
2527
|
+
const data = dataOf(await call$1(credential.host, "/im/v1/messages", {
|
|
2528
|
+
token: credential.tenantAccessToken,
|
|
2529
|
+
method: "POST",
|
|
2530
|
+
query: { receive_id_type: "chat_id" },
|
|
2531
|
+
body: {
|
|
2532
|
+
receive_id: chatId,
|
|
2533
|
+
msg_type: "text",
|
|
2534
|
+
content: JSON.stringify({ text })
|
|
2535
|
+
}
|
|
2536
|
+
}));
|
|
2537
|
+
const id = typeof data.message_id === "string" ? data.message_id : "";
|
|
2538
|
+
const landed = typeof data.chat_id === "string" ? data.chat_id : chatId;
|
|
2539
|
+
if (!id) throw new Error("Feishu accepted the message but did not say where it landed.");
|
|
2540
|
+
return {
|
|
2541
|
+
id,
|
|
2542
|
+
chatId: landed
|
|
2543
|
+
};
|
|
2544
|
+
}
|
|
2545
|
+
function permalink(host, chatId) {
|
|
2546
|
+
return `${APPLINK[host]}?openChatId=${chatId}`;
|
|
2547
|
+
}
|
|
2548
|
+
function asAsked$1(value) {
|
|
2549
|
+
const record = value;
|
|
2550
|
+
if (!record || typeof record.chatId !== "string" || typeof record.messageId !== "string") throw new Error("This task has lost the message it was about.");
|
|
2551
|
+
return {
|
|
2552
|
+
chatId: record.chatId,
|
|
2553
|
+
messageId: record.messageId,
|
|
2554
|
+
user: typeof record.user === "string" ? record.user : void 0,
|
|
2555
|
+
text: typeof record.text === "string" ? record.text : ""
|
|
2556
|
+
};
|
|
2557
|
+
}
|
|
2558
|
+
function asCursor(value) {
|
|
2559
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
2560
|
+
const out = {};
|
|
2561
|
+
for (const [key, entry] of Object.entries(value)) if (typeof entry === "string") out[key] = entry;
|
|
2562
|
+
return out;
|
|
2563
|
+
}
|
|
2564
|
+
function summarise$1(text) {
|
|
2565
|
+
const line = text.replace(/@_user_\d+/g, "").replace(/\s+/g, " ").trim();
|
|
2566
|
+
if (!line) return "Feishu message";
|
|
2567
|
+
return line.length > 80 ? `${line.slice(0, 79)}…` : line;
|
|
2568
|
+
}
|
|
2569
|
+
function isAsk(input) {
|
|
2570
|
+
if (input.senderType === "app" || input.senderId === input.botOpenId) return false;
|
|
2571
|
+
return input.mentionIds.includes(input.botOpenId);
|
|
2572
|
+
}
|
|
2573
|
+
function destination(target, source) {
|
|
2574
|
+
if (typeof target.chat === "string" && target.chat.trim()) return { chatId: target.chat.trim() };
|
|
2575
|
+
if (source.connectorId !== "feishu") throw new Error("There is no Feishu chat behind this task. Choose one on the loop.");
|
|
2576
|
+
const carry = source.carry ?? {};
|
|
2577
|
+
if (!carry.chatId && !carry.messageId) throw new Error("There is no Feishu chat behind this task. Choose one on the loop.");
|
|
2578
|
+
return {
|
|
2579
|
+
chatId: carry.chatId,
|
|
2580
|
+
messageId: carry.messageId
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
function accountOf$1(credential) {
|
|
2584
|
+
return {
|
|
2585
|
+
id: credential.botOpenId,
|
|
2586
|
+
label: credential.botName || credential.appId,
|
|
2587
|
+
...credential.avatarUrl ? { avatarUrl: credential.avatarUrl } : {}
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
/**
|
|
2591
|
+
* Tenant tokens last two hours. Minting on every poll would trip Feishu's
|
|
2592
|
+
* rate limit, so a still-valid one is reused and a fresh one is stored.
|
|
2593
|
+
*/
|
|
2594
|
+
async function usable(credential) {
|
|
2595
|
+
if (Date.now() < credential.expiresAt) return { credential };
|
|
2596
|
+
const minted = await tenantAccessToken(credential.host, credential.appId, credential.appSecret);
|
|
2597
|
+
const refreshed = {
|
|
2598
|
+
...credential,
|
|
2599
|
+
tenantAccessToken: minted.token,
|
|
2600
|
+
expiresAt: minted.expiresAt
|
|
2601
|
+
};
|
|
2602
|
+
return {
|
|
2603
|
+
credential: refreshed,
|
|
2604
|
+
refreshed
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
2607
|
+
var feishuRuntime = {
|
|
2608
|
+
async readiness() {
|
|
2609
|
+
return { ready: true };
|
|
2610
|
+
},
|
|
2611
|
+
async poll({ workflowId, credential, cursor }) {
|
|
2612
|
+
if (workflowId !== "feishu.ask") throw new Error(`Feishu cannot watch for ${workflowId}.`);
|
|
2613
|
+
const { credential: account } = await usable(credential);
|
|
2614
|
+
const previous = asCursor(cursor);
|
|
2615
|
+
const chats = await listChats(account);
|
|
2616
|
+
const next = { ...previous };
|
|
2617
|
+
const signals = [];
|
|
2618
|
+
for (const chat of chats) {
|
|
2619
|
+
const oldest = previous[chat.id];
|
|
2620
|
+
const messages = await history(account, chat.id, oldest);
|
|
2621
|
+
if (messages.length === 0) {
|
|
2622
|
+
if (!next[chat.id]) next[chat.id] = String(Date.now());
|
|
2623
|
+
continue;
|
|
2624
|
+
}
|
|
2625
|
+
next[chat.id] = messages[messages.length - 1].createTime;
|
|
2626
|
+
if (!oldest) continue;
|
|
2627
|
+
for (const message of messages) {
|
|
2628
|
+
if (oldest && Number(message.createTime) <= Number(oldest)) continue;
|
|
2629
|
+
if (!isAsk({
|
|
2630
|
+
senderId: message.senderId,
|
|
2631
|
+
senderType: message.senderType,
|
|
2632
|
+
mentionIds: message.mentionIds,
|
|
2633
|
+
botOpenId: account.botOpenId
|
|
2634
|
+
})) continue;
|
|
2635
|
+
const asked = {
|
|
2636
|
+
chatId: message.chatId,
|
|
2637
|
+
messageId: message.id,
|
|
2638
|
+
user: message.senderId,
|
|
2639
|
+
text: message.text
|
|
2640
|
+
};
|
|
2641
|
+
signals.push({
|
|
2642
|
+
key: `${message.chatId}:${message.id}`,
|
|
2643
|
+
kind: "message",
|
|
2644
|
+
ref: message.id,
|
|
2645
|
+
title: summarise$1(message.text),
|
|
2646
|
+
url: permalink(account.host, message.chatId),
|
|
2647
|
+
payload: asked
|
|
2648
|
+
});
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
return {
|
|
2652
|
+
signals,
|
|
2653
|
+
cursor: next
|
|
2654
|
+
};
|
|
2655
|
+
},
|
|
2656
|
+
async resolveWorkItem({ payload }) {
|
|
2657
|
+
const asked = asAsked$1(payload);
|
|
2658
|
+
return {
|
|
2659
|
+
kind: "message",
|
|
2660
|
+
ref: asked.messageId,
|
|
2661
|
+
title: summarise$1(asked.text),
|
|
2662
|
+
url: "",
|
|
2663
|
+
carry: {
|
|
2664
|
+
chatId: asked.chatId,
|
|
2665
|
+
messageId: asked.messageId
|
|
2666
|
+
},
|
|
2667
|
+
context: [{
|
|
2668
|
+
name: "MESSAGE.md",
|
|
2669
|
+
body: [
|
|
2670
|
+
`# What you were asked`,
|
|
2671
|
+
``,
|
|
2672
|
+
asked.text,
|
|
2673
|
+
``
|
|
2674
|
+
].join("\n")
|
|
2675
|
+
}]
|
|
2676
|
+
};
|
|
2677
|
+
},
|
|
2678
|
+
async applyAction({ actionId, target, source, body, credential }) {
|
|
2679
|
+
if (actionId !== "feishu.reply") throw new Error(`Feishu cannot ${actionId}.`);
|
|
2680
|
+
const { credential: account } = await usable(credential);
|
|
2681
|
+
const to = destination(target, source);
|
|
2682
|
+
const text = body.trim();
|
|
2683
|
+
const posted = to.messageId && source.connectorId === "feishu" ? await replyMessage(account, to.messageId, text) : await sendMessage$1(account, to.chatId ?? "", text);
|
|
2684
|
+
return { url: permalink(account.host, posted.chatId || to.chatId || "") };
|
|
2685
|
+
},
|
|
2686
|
+
auth: {
|
|
2687
|
+
async connectWithFields(fields) {
|
|
2688
|
+
const appId = fields.appId?.trim() ?? "";
|
|
2689
|
+
const appSecret = fields.appSecret?.trim() ?? "";
|
|
2690
|
+
if (!appId) throw new Error("Paste the App ID from your Feishu or Lark app.");
|
|
2691
|
+
if (!appSecret) throw new Error("Paste the App Secret from your Feishu or Lark app.");
|
|
2692
|
+
const host = parseHost(fields.host);
|
|
2693
|
+
const minted = await tenantAccessToken(host, appId, appSecret);
|
|
2694
|
+
const bot = await botInfo({
|
|
2695
|
+
host,
|
|
2696
|
+
tenantAccessToken: minted.token
|
|
2697
|
+
});
|
|
2698
|
+
const stored = {
|
|
2699
|
+
appId,
|
|
2700
|
+
appSecret,
|
|
2701
|
+
host,
|
|
2702
|
+
botOpenId: bot.openId,
|
|
2703
|
+
botName: bot.name,
|
|
2704
|
+
...bot.avatarUrl ? { avatarUrl: bot.avatarUrl } : {},
|
|
2705
|
+
tenantAccessToken: minted.token,
|
|
2706
|
+
expiresAt: minted.expiresAt
|
|
2707
|
+
};
|
|
2708
|
+
return {
|
|
2709
|
+
credential: stored,
|
|
2710
|
+
account: accountOf$1(stored)
|
|
2711
|
+
};
|
|
2712
|
+
},
|
|
2713
|
+
async identity(credential) {
|
|
2714
|
+
const { credential: fresh, refreshed } = await usable(credential);
|
|
2715
|
+
const bot = await botInfo(fresh);
|
|
2716
|
+
const updated = {
|
|
2717
|
+
...fresh,
|
|
2718
|
+
botOpenId: bot.openId,
|
|
2719
|
+
botName: bot.name,
|
|
2720
|
+
...bot.avatarUrl ? { avatarUrl: bot.avatarUrl } : {}
|
|
2721
|
+
};
|
|
2722
|
+
return {
|
|
2723
|
+
account: accountOf$1(updated),
|
|
2724
|
+
...refreshed ? { renewedCredential: updated } : {}
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
};
|
|
2729
|
+
/**
|
|
1933
2730
|
* Tencent's iLink bot API, as documented for clients that are not OpenClaw:
|
|
1934
2731
|
* https://github.com/Tencent/openclaw-weixin/blob/main/docs/protocol.md
|
|
1935
2732
|
*
|
|
@@ -2264,6 +3061,8 @@ function accountOf(credential) {
|
|
|
2264
3061
|
/** Server-only registry. Never import this from a component. */
|
|
2265
3062
|
var RUNTIMES = {
|
|
2266
3063
|
github: githubRuntime,
|
|
3064
|
+
slack: slackRuntime,
|
|
3065
|
+
feishu: feishuRuntime,
|
|
2267
3066
|
gmail: gmailRuntime,
|
|
2268
3067
|
wechat: {
|
|
2269
3068
|
async readiness() {
|
|
@@ -2539,4 +3338,4 @@ async function checkConnection(id) {
|
|
|
2539
3338
|
return toView(await getConnection(id));
|
|
2540
3339
|
}
|
|
2541
3340
|
//#endregion
|
|
2542
|
-
export {
|
|
3341
|
+
export { connectorAction as a, connectorWorkflow as c, needsAccount as d, removeConnection as f, updateConnectionSettings as g, takeAuthAttempt as h, connectionNoun as i, credentialForConnector as l, startAuthAttempt as m, allWorkflows as n, connectorManifest as o, saveAuthorizedConnection as p, checkConnection as r, connectorRuntime as s, CONNECTOR_MANIFESTS as t, listConnections as u };
|