@soimy/dingtalk 3.1.4 → 3.3.0
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/LICENSE +21 -0
- package/README.md +808 -40
- package/index.ts +62 -0
- package/package.json +4 -2
- package/src/access-control.ts +18 -0
- package/src/ack-reaction-classifier.ts +62 -0
- package/src/ack-reaction-service.ts +135 -0
- package/src/attachment-text-extractor.ts +147 -0
- package/src/card-callback-service.ts +119 -0
- package/src/card-draft-controller.ts +114 -0
- package/src/card-service.ts +794 -62
- package/src/channel.ts +675 -179
- package/src/config-schema.ts +49 -5
- package/src/config.ts +136 -4
- package/src/connection-manager.ts +356 -36
- package/src/dedup.ts +1 -0
- package/src/docs-service.ts +198 -0
- package/src/draft-stream-loop.ts +119 -0
- package/src/feedback-learning-service.ts +643 -0
- package/src/feedback-learning-store.ts +543 -0
- package/src/group-members-store.ts +48 -14
- package/src/inbound-handler.ts +1191 -206
- package/src/learning-command-service.ts +339 -0
- package/src/media-utils.ts +566 -8
- package/src/message-utils.ts +301 -39
- package/src/onboarding.ts +85 -3
- package/src/peer-id-registry.ts +102 -0
- package/src/persistence-store.ts +131 -0
- package/src/quote-journal.ts +242 -0
- package/src/quoted-file-service.ts +385 -0
- package/src/quoted-msg-cache.ts +226 -0
- package/src/send-service.ts +239 -59
- package/src/session-command-service.ts +147 -0
- package/src/session-lock.ts +34 -0
- package/src/session-peer-store.ts +77 -0
- package/src/session-routing.ts +33 -0
- package/src/types.ts +165 -22
- package/src/utils.ts +231 -12
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Card draft controller for throttled AI Card streaming updates.
|
|
3
|
+
*
|
|
4
|
+
* Wraps {@link createDraftStreamLoop} with a phase-based state machine
|
|
5
|
+
* (idle → reasoning → answer) that manages what content is sent to the
|
|
6
|
+
* DingTalk AI Card during the reply lifecycle.
|
|
7
|
+
*
|
|
8
|
+
* Responsibilities (and non-responsibilities):
|
|
9
|
+
* - DOES manage throttled card preview updates via streamAICard
|
|
10
|
+
* - DOES enforce single-flight, latest-wins, phase-gated semantics
|
|
11
|
+
* - Does NOT handle tool append, finalize, or markdown fallback —
|
|
12
|
+
* those stay in inbound-handler's deliver callback.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { formatContentForCard, streamAICard } from "./card-service";
|
|
16
|
+
import { createDraftStreamLoop } from "./draft-stream-loop";
|
|
17
|
+
import type { AICardInstance, Logger } from "./types";
|
|
18
|
+
|
|
19
|
+
export type CardDraftPhase = "idle" | "reasoning" | "answer";
|
|
20
|
+
|
|
21
|
+
export interface CardDraftController {
|
|
22
|
+
updateAnswer: (text: string) => void;
|
|
23
|
+
updateReasoning: (text: string) => void;
|
|
24
|
+
/** Signal that a new assistant turn has started (e.g. after a tool call). */
|
|
25
|
+
notifyNewAssistantTurn: () => void;
|
|
26
|
+
flush: () => Promise<void>;
|
|
27
|
+
waitForInFlight: () => Promise<void>;
|
|
28
|
+
stop: () => void;
|
|
29
|
+
isFailed: () => boolean;
|
|
30
|
+
/** Last content sent to card (reasoning or answer). */
|
|
31
|
+
getLastContent: () => string;
|
|
32
|
+
/** Last content sent to card during answer phase only. */
|
|
33
|
+
getLastAnswerContent: () => string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createCardDraftController(params: {
|
|
37
|
+
card: AICardInstance;
|
|
38
|
+
throttleMs?: number;
|
|
39
|
+
log?: Logger;
|
|
40
|
+
}): CardDraftController {
|
|
41
|
+
let phase: CardDraftPhase = "idle";
|
|
42
|
+
let failed = false;
|
|
43
|
+
let stopped = false;
|
|
44
|
+
let lastSentContent = "";
|
|
45
|
+
let lastAnswerContent = "";
|
|
46
|
+
let answerPrefix = "";
|
|
47
|
+
let turnBoundaryPending = false;
|
|
48
|
+
|
|
49
|
+
const loop = createDraftStreamLoop({
|
|
50
|
+
throttleMs: params.throttleMs ?? 300,
|
|
51
|
+
isStopped: () => stopped || failed,
|
|
52
|
+
sendOrEditStreamMessage: async (content: string) => {
|
|
53
|
+
try {
|
|
54
|
+
await streamAICard(params.card, content, false, params.log);
|
|
55
|
+
lastSentContent = content;
|
|
56
|
+
if (phase === "answer") {
|
|
57
|
+
lastAnswerContent = content;
|
|
58
|
+
}
|
|
59
|
+
} catch (err: unknown) {
|
|
60
|
+
failed = true;
|
|
61
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
62
|
+
params.log?.warn?.(`[DingTalk][AICard] Stream failed: ${message}`);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
updateReasoning: (text: string) => {
|
|
69
|
+
if (stopped || failed || phase === "answer") { return; }
|
|
70
|
+
phase = "reasoning";
|
|
71
|
+
const formatted = formatContentForCard(text, "thinking");
|
|
72
|
+
if (formatted) {
|
|
73
|
+
loop.update(formatted);
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
updateAnswer: (text: string) => {
|
|
78
|
+
if (stopped || failed) { return; }
|
|
79
|
+
if (phase !== "answer") {
|
|
80
|
+
params.log?.debug?.(`[DingTalk][Draft] phase ${phase} → answer`);
|
|
81
|
+
phase = "answer";
|
|
82
|
+
if (turnBoundaryPending && lastAnswerContent) {
|
|
83
|
+
answerPrefix = lastAnswerContent + "\n\n";
|
|
84
|
+
}
|
|
85
|
+
turnBoundaryPending = false;
|
|
86
|
+
}
|
|
87
|
+
const trimmed = text?.trimStart();
|
|
88
|
+
if (trimmed) {
|
|
89
|
+
loop.update(answerPrefix + trimmed);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
notifyNewAssistantTurn: () => {
|
|
94
|
+
if (stopped || failed) { return; }
|
|
95
|
+
turnBoundaryPending = true;
|
|
96
|
+
if (phase === "reasoning") {
|
|
97
|
+
loop.resetPending();
|
|
98
|
+
}
|
|
99
|
+
phase = "idle";
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
flush: () => loop.flush(),
|
|
103
|
+
waitForInFlight: () => loop.waitForInFlight(),
|
|
104
|
+
|
|
105
|
+
stop: () => {
|
|
106
|
+
stopped = true;
|
|
107
|
+
loop.stop();
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
isFailed: () => failed,
|
|
111
|
+
getLastContent: () => lastSentContent,
|
|
112
|
+
getLastAnswerContent: () => lastAnswerContent,
|
|
113
|
+
};
|
|
114
|
+
}
|