@timqi/pier 0.1.4 → 0.1.6
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/README.md +4 -4
- package/dist/agent/events.js +12 -3
- package/dist/agent/listing.js +10 -2
- package/dist/agent/packages.js +25 -14
- package/dist/agent/pi.js +13 -40
- package/dist/channels/attach.js +2 -2
- package/dist/channels/commands.js +10 -8
- package/dist/channels/config.js +4 -6
- package/dist/channels/control.js +60 -14
- package/dist/channels/conversations.js +60 -13
- package/dist/channels/handoff.js +90 -0
- package/dist/channels/lark-api.js +7 -0
- package/dist/channels/lark-outbound.js +19 -0
- package/dist/channels/lark-panel.js +41 -26
- package/dist/channels/lark-render.js +2 -1
- package/dist/channels/lark.js +35 -26
- package/dist/channels/lines.js +1 -1
- package/dist/channels/panel.js +324 -115
- package/dist/channels/receipts.js +25 -12
- package/dist/channels/routes.js +21 -4
- package/dist/channels/runtime.js +21 -7
- package/dist/channels/slack-outbound.js +10 -0
- package/dist/channels/slack-panel.js +81 -40
- package/dist/channels/slack-render.js +3 -1
- package/dist/channels/slack.js +29 -14
- package/dist/channels/types.js +1 -2
- package/dist/cli.js +13 -5
- package/dist/core/identity.js +47 -12
- package/dist/core/reply.js +4 -1
- package/dist/core/router.js +20 -3
- package/dist/db.js +6 -1
- package/dist/main.js +42 -9
- package/dist/service.js +4 -2
- package/dist/settings.js +3 -12
- package/dist/socket.js +10 -6
- package/dist/tasks/cli.js +17 -7
- package/dist/tasks/operations.js +24 -12
- package/dist/tasks/routes.js +1 -0
- package/dist/tools.js +1 -3
- package/dist/web/public/assets/activity-BWpiRicQ.js +5 -0
- package/dist/web/public/assets/activity-BWpiRicQ.js.br +0 -0
- package/dist/web/public/assets/activity-BWpiRicQ.js.gz +0 -0
- package/dist/web/public/assets/boards-BnRMjBln.js +1 -0
- package/dist/web/public/assets/boards-BnRMjBln.js.br +0 -0
- package/dist/web/public/assets/boards-BnRMjBln.js.gz +0 -0
- package/dist/web/public/assets/explorer-DLZ0ccgE.js +4 -0
- package/dist/web/public/assets/explorer-DLZ0ccgE.js.br +0 -0
- package/dist/web/public/assets/explorer-DLZ0ccgE.js.gz +0 -0
- package/dist/web/public/assets/index-DMq4awU8.css +2 -0
- package/dist/web/public/assets/index-DMq4awU8.css.br +0 -0
- package/dist/web/public/assets/index-DMq4awU8.css.gz +0 -0
- package/dist/web/public/assets/index-T-vLXPch.js +85 -0
- package/dist/web/public/assets/index-T-vLXPch.js.br +0 -0
- package/dist/web/public/assets/index-T-vLXPch.js.gz +0 -0
- package/dist/web/public/assets/runs-oFQ162um.js +1 -0
- package/dist/web/public/assets/runs-oFQ162um.js.br +0 -0
- package/dist/web/public/assets/runs-oFQ162um.js.gz +0 -0
- package/dist/web/public/assets/settings-_t0Js-DJ.js +5 -0
- package/dist/web/public/assets/settings-_t0Js-DJ.js.br +0 -0
- package/dist/web/public/assets/settings-_t0Js-DJ.js.gz +0 -0
- package/dist/web/public/assets/task-runs-m3ufvOQI.js +3 -0
- package/dist/web/public/assets/task-runs-m3ufvOQI.js.br +0 -0
- package/dist/web/public/assets/task-runs-m3ufvOQI.js.gz +0 -0
- package/dist/web/public/assets/tasks-C1ATvPUc.js +4 -0
- package/dist/web/public/assets/tasks-C1ATvPUc.js.br +0 -0
- package/dist/web/public/assets/tasks-C1ATvPUc.js.gz +0 -0
- package/dist/web/public/index.html +3 -3
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/push.js +4 -10
- package/dist/web/server.js +5 -4
- package/dist/{extensions/web → websearch}/artifacts.js +2 -2
- package/dist/websearch/cli.js +73 -0
- package/dist/websearch/run.js +273 -0
- package/docs/deploy.md +4 -4
- package/package.json +2 -3
- package/skills/pier-help/SKILL.md +22 -10
- package/skills/pier-tasks/SKILL.md +17 -8
- package/skills/pier-web/SKILL.md +50 -0
- package/dist/channels/telegram-api.js +0 -86
- package/dist/channels/telegram-panel.js +0 -97
- package/dist/channels/telegram-render.js +0 -69
- package/dist/channels/telegram.js +0 -421
- package/dist/extensions/index.js +0 -10
- package/dist/extensions/web/index.js +0 -9
- package/dist/extensions/web/tools.js +0 -265
- package/dist/web/public/assets/activity-NLS8W9yl.js +0 -5
- package/dist/web/public/assets/activity-NLS8W9yl.js.br +0 -0
- package/dist/web/public/assets/activity-NLS8W9yl.js.gz +0 -0
- package/dist/web/public/assets/boards-DleegoiC.js +0 -1
- package/dist/web/public/assets/boards-DleegoiC.js.br +0 -0
- package/dist/web/public/assets/boards-DleegoiC.js.gz +0 -0
- package/dist/web/public/assets/explorer-vJvV1Sx0.js +0 -4
- package/dist/web/public/assets/explorer-vJvV1Sx0.js.br +0 -0
- package/dist/web/public/assets/explorer-vJvV1Sx0.js.gz +0 -0
- package/dist/web/public/assets/index-Bs-gol9o.css +0 -2
- package/dist/web/public/assets/index-Bs-gol9o.css.br +0 -0
- package/dist/web/public/assets/index-Bs-gol9o.css.gz +0 -0
- package/dist/web/public/assets/index-Cyp2DKKF.js +0 -85
- package/dist/web/public/assets/index-Cyp2DKKF.js.br +0 -0
- package/dist/web/public/assets/index-Cyp2DKKF.js.gz +0 -0
- package/dist/web/public/assets/runs-C5FveZ75.js +0 -1
- package/dist/web/public/assets/runs-C5FveZ75.js.br +0 -0
- package/dist/web/public/assets/runs-C5FveZ75.js.gz +0 -0
- package/dist/web/public/assets/settings-nJHIBbAz.js +0 -5
- package/dist/web/public/assets/settings-nJHIBbAz.js.br +0 -0
- package/dist/web/public/assets/settings-nJHIBbAz.js.gz +0 -0
- package/dist/web/public/assets/task-runs-CJJ2j5Ks.js +0 -3
- package/dist/web/public/assets/task-runs-CJJ2j5Ks.js.br +0 -0
- package/dist/web/public/assets/task-runs-CJJ2j5Ks.js.gz +0 -0
- package/dist/web/public/assets/tasks-R2Pv0Abg.js +0 -4
- package/dist/web/public/assets/tasks-R2Pv0Abg.js.br +0 -0
- package/dist/web/public/assets/tasks-R2Pv0Abg.js.gz +0 -0
- /package/dist/{extensions/web → websearch}/anthropic.js +0 -0
- /package/dist/{extensions/web → websearch}/content.js +0 -0
- /package/dist/{extensions/web → websearch}/http.js +0 -0
- /package/dist/{extensions/web → websearch}/json.js +0 -0
- /package/dist/{extensions/web → websearch}/language.js +0 -0
- /package/dist/{extensions/web → websearch}/openai.js +0 -0
- /package/dist/{extensions/web → websearch}/provider.js +0 -0
|
@@ -50,6 +50,25 @@ export class LarkOutbound {
|
|
|
50
50
|
if (lost)
|
|
51
51
|
await this.api.replyCard(root, card([markdown(lost)]));
|
|
52
52
|
}
|
|
53
|
+
/** The root, then one card inside its topic: on the phone a topic has its
|
|
54
|
+
* own composer only once it has a reply. Answers the root's id. The handoff
|
|
55
|
+
* writes no row when this throws, so a root left behind would invite a reply
|
|
56
|
+
* into a thread nobody answers: it is taken back down. */
|
|
57
|
+
async open(chatId, note) {
|
|
58
|
+
const link = note.url ? `[Open on the web](${note.url})` : "(no public URL set — Settings → Instance)";
|
|
59
|
+
const { messageId: root } = await this.api.createCard(chatId, card([
|
|
60
|
+
markdown(`**Continued from web: ${note.title}**\n${link}\nReply in this thread to continue.`),
|
|
61
|
+
]));
|
|
62
|
+
try {
|
|
63
|
+
await this.api.replyCard(root, card([markdown("Reply here to continue.")]));
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
await this.api.deleteMessage(root)
|
|
67
|
+
.catch((e) => this.log(`orphan handoff root ${root} not deleted: ${String(e)}`));
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
return root;
|
|
71
|
+
}
|
|
53
72
|
/** Best-effort: a card sent before a restart keeps its row, logged. */
|
|
54
73
|
async retire(messageId) {
|
|
55
74
|
const known = this.sent.get(messageId);
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
// Lark's half of the settings panel (panel.ts has the rest).
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Lark's half of the settings panel (panel.ts has the rest). No modal a
|
|
2
|
+
// WebSocket app can open, so the typed answer is the panel patched into a form
|
|
3
|
+
// card whose submit button's `name` carries the thread root; every callback
|
|
4
|
+
// button's value carries the draft, so a tap after a reload lands on its card.
|
|
4
5
|
import { button as cardButton, buttonRow, card, footer, formInput, markdown, } from "./lark-render.js";
|
|
5
|
-
import { ChatPanel, CWD_PLACEHOLDER,
|
|
6
|
+
import { ChatPanel, CWD_DRAFT_TAIL, CWD_PLACEHOLDER, holdQuestion, PANEL_PREFIX, readDraft, } from "./panel.js";
|
|
6
7
|
/** A form-submit button name: `cwdgo:<thread root>`. */
|
|
7
8
|
export const CWD_SUBMIT_PREFIX = "cwdgo:";
|
|
8
9
|
const CWD_FIELD = "cwd";
|
|
10
|
+
const fresh = (root, messageId, draft) => ({ root, messageId, draft, dirs: [], sessions: [] });
|
|
9
11
|
export class LarkPanel extends ChatPanel {
|
|
10
12
|
deps;
|
|
11
|
-
platform = "lark";
|
|
12
13
|
fence = ["`", "`"];
|
|
13
14
|
constructor(deps) {
|
|
14
15
|
super(deps);
|
|
@@ -19,25 +20,38 @@ export class LarkPanel extends ChatPanel {
|
|
|
19
20
|
return text;
|
|
20
21
|
}
|
|
21
22
|
// --- rendering -------------------------------------------------------------
|
|
22
|
-
btn(b,
|
|
23
|
-
|
|
23
|
+
btn(b, state) {
|
|
24
|
+
const draft = Object.keys(state.draft).length ? { draft: state.draft } : {};
|
|
25
|
+
return cardButton(b.label, { key: `${PANEL_PREFIX}${b.action}`, root: state.root, ...draft });
|
|
24
26
|
}
|
|
25
|
-
render(view,
|
|
27
|
+
render(view, state, note) {
|
|
26
28
|
const elements = [
|
|
27
29
|
...view.groups.map((g) => markdown([`**${g.title}**${g.suffix ?? ""}`, ...g.lines].join("\n"))),
|
|
28
|
-
...(view.picks?.length ? [buttonRow(view.picks.map((p) => this.btn(p,
|
|
29
|
-
...view.rows.filter((r) => r.length).map((row) => buttonRow(row.map((b) => this.btn(b,
|
|
30
|
+
...(view.picks?.length ? [buttonRow(view.picks.map((p) => this.btn(p, state)))] : []),
|
|
31
|
+
...view.rows.filter((r) => r.length).map((row) => buttonRow(row.map((b) => this.btn(b, state)))),
|
|
30
32
|
];
|
|
31
33
|
if (note)
|
|
32
34
|
elements.push(footer(note));
|
|
33
35
|
return card(elements);
|
|
34
36
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
/** A card that cannot be posted must not look like nothing happening: the
|
|
38
|
+
* topic gets the reason as a plain card. */
|
|
39
|
+
async open(key, root, question) {
|
|
40
|
+
const state = fresh(root, "", holdQuestion(question));
|
|
41
|
+
let sent;
|
|
42
|
+
try {
|
|
43
|
+
sent = await this.deps.api.replyCard(root, this.render(await this.view(key, state), state));
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
this.deps.log(`panel open failed: ${String(err)}`);
|
|
47
|
+
await this.deps.api.replyCard(root, card([markdown(this.esc(`Could not open the panel: ${String(err)}`))]))
|
|
48
|
+
.catch((e) => this.deps.log(`panel open failure not posted: ${String(e)}`));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this.remember(key, { ...state, messageId: sent.messageId });
|
|
38
52
|
}
|
|
39
53
|
async draw(state, view, note) {
|
|
40
|
-
await this.deps.api.patchCard(state.messageId, this.render(view, state
|
|
54
|
+
await this.deps.api.patchCard(state.messageId, this.render(view, state, note))
|
|
41
55
|
.catch((err) => this.deps.log(`panel edit failed: ${String(err)}`));
|
|
42
56
|
}
|
|
43
57
|
async erase(state) {
|
|
@@ -45,35 +59,36 @@ export class LarkPanel extends ChatPanel {
|
|
|
45
59
|
.catch((err) => this.deps.log(`panel close failed: ${String(err)}`));
|
|
46
60
|
}
|
|
47
61
|
// --- actions ---------------------------------------------------------------
|
|
48
|
-
/** Returns false when the action is not ours.
|
|
49
|
-
|
|
50
|
-
|
|
62
|
+
/** Returns false when the action is not ours. `run` delivers Start's
|
|
63
|
+
* question as the tapper's message. */
|
|
64
|
+
async onAction(action, key, payload, root, run) {
|
|
65
|
+
return this.dispatch(key, payload, action, () => fresh(root, action.messageId, readDraft(action.value?.draft)), run);
|
|
51
66
|
}
|
|
52
67
|
// --- working directory (one typed answer, in a form card) -------------------
|
|
53
|
-
async promptCwd(
|
|
68
|
+
async promptCwd(_key, state, _action) {
|
|
54
69
|
const form = {
|
|
55
70
|
tag: "form",
|
|
56
71
|
name: "cwd_form",
|
|
57
72
|
elements: [
|
|
58
73
|
formInput(CWD_FIELD, "Working directory", CWD_PLACEHOLDER),
|
|
59
|
-
markdown(`An absolute path. ${
|
|
74
|
+
markdown(`An absolute path. ${CWD_DRAFT_TAIL}`),
|
|
60
75
|
{
|
|
61
76
|
tag: "button",
|
|
62
|
-
text: { tag: "plain_text", content: "
|
|
77
|
+
text: { tag: "plain_text", content: "Set" },
|
|
63
78
|
type: "primary",
|
|
64
79
|
action_type: "form_submit",
|
|
65
80
|
name: `${CWD_SUBMIT_PREFIX}${state.root}`,
|
|
66
81
|
},
|
|
67
82
|
],
|
|
68
83
|
};
|
|
69
|
-
await this.deps.api.patchCard(state.messageId, card([form, buttonRow([this.btn({ label: "Cancel", action: "panel" }, state
|
|
84
|
+
await this.deps.api.patchCard(state.messageId, card([form, buttonRow([this.btn({ label: "Cancel", action: "panel" }, state)])])).catch((err) => this.deps.log(`cwd form failed: ${String(err)}`));
|
|
70
85
|
}
|
|
71
86
|
/** A panel that outlived its process is re-remembered from the event, so
|
|
72
|
-
* the outcome lands on the card the user is looking at.
|
|
87
|
+
* the outcome lands on the card the user is looking at. The submit button
|
|
88
|
+
* carries no value, so a draft's earlier picks do not survive that restart. */
|
|
73
89
|
async onCwdSubmit(key, action, root) {
|
|
74
|
-
if (!this.state(key))
|
|
75
|
-
this.remember(key,
|
|
76
|
-
|
|
77
|
-
await this.startSessionIn(key, String(action.formValue?.[CWD_FIELD] ?? "").trim());
|
|
90
|
+
if (!this.state(key))
|
|
91
|
+
this.remember(key, fresh(root, action.messageId, {}));
|
|
92
|
+
await this.chooseDir(key, String(action.formValue?.[CWD_FIELD] ?? "").trim());
|
|
78
93
|
}
|
|
79
94
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// 「卡片」, and card interactions expire after 30 days. Lark's markdown dialect
|
|
5
5
|
// takes the agent's markdown near-unmodified and treats what it cannot parse as
|
|
6
6
|
// literal text, so there is no translation and no escaping.
|
|
7
|
+
import { cut } from "../core/reply.js";
|
|
7
8
|
import { balanceFences, chunkText } from "./chunk.js";
|
|
8
9
|
/** The binding limit is the card's 30KB request cap in bytes; a CJK character
|
|
9
10
|
* spends three, so 7000 chars keeps an all-CJK turn near 21KB plus scaffolding. */
|
|
@@ -29,7 +30,7 @@ export const withFooter = (body, note) => {
|
|
|
29
30
|
const brk = LAZY_LINE.test(last) ? "\n\n" : "\n";
|
|
30
31
|
return markdown(`${body}${brk}<font color='grey'>${note}</font>`);
|
|
31
32
|
};
|
|
32
|
-
const truncate = (label) => label
|
|
33
|
+
const truncate = (label) => cut(label, BUTTON_MAX);
|
|
33
34
|
export const button = (label, value) => ({
|
|
34
35
|
tag: "button",
|
|
35
36
|
text: { tag: "plain_text", content: truncate(label) },
|
package/dist/channels/lark.js
CHANGED
|
@@ -12,7 +12,7 @@ import { awaitsTurn } from "../core/reply.js";
|
|
|
12
12
|
import { bindHint, bindResult, picked, STALE_OPTION, STOPPED } from "./lines.js";
|
|
13
13
|
import { logger } from "../log.js";
|
|
14
14
|
import { Chains } from "./chains.js";
|
|
15
|
-
import { parseCommand } from "./commands.js";
|
|
15
|
+
import { parseCommand, settingsDraft } from "./commands.js";
|
|
16
16
|
import { Dedup } from "./dedup.js";
|
|
17
17
|
import { Gatekeeper } from "./gatekeeper.js";
|
|
18
18
|
import { LarkApi, } from "./lark-api.js";
|
|
@@ -24,7 +24,8 @@ import { ReceiptLedger, Receipts } from "./receipts.js";
|
|
|
24
24
|
const WORKING = "OnIt";
|
|
25
25
|
// The event is already acked, so this bounds concurrency, not the backlog.
|
|
26
26
|
const MAX_ACTIVE_CHATS = 16;
|
|
27
|
-
|
|
27
|
+
/** Only an idle conversation ages out, so this need not cover a long turn. */
|
|
28
|
+
const RECEIPT_STALE_MS = 10 * 60_000;
|
|
28
29
|
const DRAIN_TIMEOUT_MS = 5000;
|
|
29
30
|
const DEDUP_TTL_MS = 5 * 60_000;
|
|
30
31
|
const DEDUP_MAX = 2000;
|
|
@@ -41,6 +42,8 @@ const threadOf = (msg) => msg.rootId || msg.messageId;
|
|
|
41
42
|
export class LarkChannel {
|
|
42
43
|
deps;
|
|
43
44
|
id = "lark";
|
|
45
|
+
/** No `pier lark` CLI and no mention syntax out: its ids buy the prompt nothing. */
|
|
46
|
+
opaqueIds = true;
|
|
44
47
|
api;
|
|
45
48
|
log;
|
|
46
49
|
receipts;
|
|
@@ -71,12 +74,12 @@ export class LarkChannel {
|
|
|
71
74
|
setReaction: (_chatId, messageId, emoji) => emoji
|
|
72
75
|
? this.api.addReaction(messageId, emoji)
|
|
73
76
|
: this.api.removeReaction(messageId, WORKING),
|
|
74
|
-
}, deps.receipts ?? new ReceiptLedger("lark"), this.log, WORKING, RECEIPT_STALE_MS);
|
|
75
|
-
if (deps.control) {
|
|
77
|
+
}, deps.receipts ?? new ReceiptLedger("lark"), this.log, WORKING, RECEIPT_STALE_MS, (conversationId) => deps.control?.working({ channelId: this.id, conversationId }) ?? false);
|
|
78
|
+
if (deps.control && deps.handoff) {
|
|
76
79
|
this.panel = new LarkPanel({
|
|
77
80
|
api: this.api,
|
|
78
81
|
control: deps.control,
|
|
79
|
-
|
|
82
|
+
handoff: deps.handoff,
|
|
80
83
|
log: this.log,
|
|
81
84
|
});
|
|
82
85
|
}
|
|
@@ -154,10 +157,7 @@ export class LarkChannel {
|
|
|
154
157
|
});
|
|
155
158
|
}
|
|
156
159
|
const text = raw.trim();
|
|
157
|
-
|
|
158
|
-
// means "not us".
|
|
159
|
-
const parsed = parseCommand(text);
|
|
160
|
-
const command = parsed?.target ? undefined : parsed;
|
|
160
|
+
const command = parseCommand(text);
|
|
161
161
|
const root = threadOf(msg);
|
|
162
162
|
const here = { channelId: this.id, conversationId: conversationId(msg.chatId, root) };
|
|
163
163
|
const bindRequest = command?.name === "bind" && isDm;
|
|
@@ -178,13 +178,16 @@ export class LarkChannel {
|
|
|
178
178
|
return this.bind(senderId, msg.messageId, command?.args ?? "");
|
|
179
179
|
if (command?.name === "stop")
|
|
180
180
|
return this.abortTurn(here, msg.messageId);
|
|
181
|
-
// A bare `@bot` and `/settings` are the same request.
|
|
182
|
-
if (this.panel && (command?.name === "settings" || (!text && !attachments.length && mentioned))) {
|
|
183
|
-
return this.panel.open(here, msg.chatId, root);
|
|
184
|
-
}
|
|
185
181
|
// Downloading only past the gate: an unauthorized sender must not make the
|
|
186
182
|
// bot pull bytes on their behalf.
|
|
187
183
|
const markers = await this.saveAttachments(msg.messageId, attachments);
|
|
184
|
+
// A bare `@bot` and `/settings` are the same request; `s <text>` drafts a
|
|
185
|
+
// session, so only where this message would start one: outside any topic.
|
|
186
|
+
// The held question carries its markers, so Start sends what the user sent.
|
|
187
|
+
const question = msg.rootId ? undefined : settingsDraft(text);
|
|
188
|
+
if (this.panel && (question || command?.name === "settings" || (!text && !attachments.length && mentioned))) {
|
|
189
|
+
return this.panel.open(here, root, question && [question, ...markers].join("\n"));
|
|
190
|
+
}
|
|
188
191
|
// Resolved before the mark: any await between mark() and dispatch is a
|
|
189
192
|
// window in which a previous turn can settle and take this receipt with it.
|
|
190
193
|
const sender = { id: senderId, name: await this.userName(senderId) };
|
|
@@ -315,7 +318,9 @@ export class LarkChannel {
|
|
|
315
318
|
return;
|
|
316
319
|
}
|
|
317
320
|
if (payload.startsWith(PANEL_PREFIX)) {
|
|
318
|
-
|
|
321
|
+
// Start's question: the card is the message the tap was on, so it carries the 👀.
|
|
322
|
+
const run = (text) => this.deliver(key, action, action.messageId, text, onMessage);
|
|
323
|
+
if (!(await this.panel?.onAction(action, key, payload, root, run))) {
|
|
319
324
|
this.log(`panel action ${payload} with no panel wired, dropped`);
|
|
320
325
|
}
|
|
321
326
|
return;
|
|
@@ -334,23 +339,21 @@ export class LarkChannel {
|
|
|
334
339
|
}
|
|
335
340
|
// A bot cannot post as the user, so the pick is echoed: otherwise the
|
|
336
341
|
// topic shows an answer to a request nobody can see, with nothing to carry the eyes.
|
|
337
|
-
const sender = { id: action.operatorId, name: await this.userName(action.operatorId) };
|
|
338
342
|
await this.out.retire(action.messageId);
|
|
339
343
|
const echo = await this.api.replyCard(root, card([markdown(picked(label))]))
|
|
340
344
|
.catch((err) => {
|
|
341
345
|
this.log(`option echo failed: ${String(err)}`);
|
|
342
346
|
return undefined;
|
|
343
347
|
});
|
|
348
|
+
await this.deliver(key, action, echo?.messageId, label, onMessage);
|
|
349
|
+
}
|
|
350
|
+
/** A tap's text as the tapper's message; `messageId` is the message that carries the 👀. */
|
|
351
|
+
async deliver(key, action, messageId, text, onMessage) {
|
|
352
|
+
const sender = { id: action.operatorId, name: await this.userName(action.operatorId) };
|
|
344
353
|
// No await between mark and dispatch — see onMessage.
|
|
345
|
-
if (
|
|
346
|
-
this.receipts.mark(key.conversationId, action.chatId,
|
|
347
|
-
onMessage({
|
|
348
|
-
key,
|
|
349
|
-
senderId: action.operatorId,
|
|
350
|
-
sender,
|
|
351
|
-
text: label,
|
|
352
|
-
mode: "steer",
|
|
353
|
-
});
|
|
354
|
+
if (messageId)
|
|
355
|
+
this.receipts.mark(key.conversationId, action.chatId, messageId);
|
|
356
|
+
onMessage({ key, senderId: action.operatorId, sender, text, mode: "steer" });
|
|
354
357
|
}
|
|
355
358
|
/** The abort ends the turn, which reaches send() and clears the receipts. */
|
|
356
359
|
async abortTurn(key, messageId) {
|
|
@@ -405,6 +408,11 @@ export class LarkChannel {
|
|
|
405
408
|
// The turn ended either way; a 👀 left up by a failed send looks like work.
|
|
406
409
|
await this.receipts.settleAfter(conversation, () => this.out.reply(root, reply), reply.meta);
|
|
407
410
|
}
|
|
411
|
+
/** A web session's topic: the root is the one card Pier posts into a
|
|
412
|
+
* chat's main flow (channels/handoff.ts). */
|
|
413
|
+
async openThread(chatId, note) {
|
|
414
|
+
return conversationId(chatId, await this.out.open(chatId, note));
|
|
415
|
+
}
|
|
408
416
|
/** The 👀 goes on the note itself: the turn it triggers has no message of
|
|
409
417
|
* the user's to carry them. */
|
|
410
418
|
async notify(conversation, note) {
|
|
@@ -414,7 +422,8 @@ export class LarkChannel {
|
|
|
414
422
|
return;
|
|
415
423
|
}
|
|
416
424
|
const messageId = await this.out.note(root, note);
|
|
417
|
-
if (messageId && awaitsTurn(note.origin))
|
|
418
|
-
this.receipts.mark(conversation, chatId, messageId);
|
|
425
|
+
if (messageId && awaitsTurn(note.origin)) {
|
|
426
|
+
this.receipts.mark(conversation, chatId, messageId, note.at);
|
|
427
|
+
}
|
|
419
428
|
}
|
|
420
429
|
}
|
package/dist/channels/lines.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// What the shared control moments say — one spelling for
|
|
1
|
+
// What the shared control moments say — one spelling for both platforms. The
|
|
2
2
|
// only legitimate variation is the spelling of the bind command, so it is the parameter.
|
|
3
3
|
export const bindHint = (command) => `You are not bound yet. Ask the operator for a bind code, then send ${command}.`;
|
|
4
4
|
/** The name arrives escaped by the caller. */
|