@coffer-org/plugin-webchat 7.0.1 → 7.1.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/dist/ChatWidget-BhGpxS9f.js +2179 -0
- package/dist/index.js +2 -0
- package/dist/runtime/actions.d.ts +1 -1
- package/dist/runtime/actions.js +40 -24
- package/dist/runtime/channel-registry.d.ts +11 -4
- package/dist/runtime/channel-registry.js +91 -13
- package/dist/runtime/connector.d.ts +3 -7
- package/dist/runtime/connector.js +14 -55
- package/dist/runtime/format.d.ts +0 -1
- package/dist/runtime/format.js +2 -11
- package/dist/runtime/index.d.ts +1 -2
- package/dist/runtime/index.js +6 -7
- package/dist/runtime/send.d.ts +2 -0
- package/dist/runtime/send.js +54 -29
- package/dist/schema.js +133 -84
- package/dist/web.js +1 -1
- package/package.json +5 -6
- package/dist/ChatWidget-DkEKl1-2.js +0 -24846
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ActionCaller } from '@coffer-org/server/plugin-hooks';
|
|
2
2
|
import { type AgentDescriptor, type ConnectorCapabilities } from '@coffer-org/server/orchestrator';
|
|
3
|
-
import { type ThreadSelection } from '
|
|
3
|
+
import { type Conversation as ThreadSelection } from '@coffer-org/server/conversation-store';
|
|
4
4
|
export interface StreamCtx {
|
|
5
5
|
caller: ActionCaller;
|
|
6
6
|
emit: (event: string, data: unknown) => void;
|
package/dist/runtime/actions.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { HttpError } from '@coffer-org/server/plugin-hooks';
|
|
2
|
-
import { SIDECAR_ROLES } from '@coffer-org/server/
|
|
2
|
+
import { SIDECAR_ROLES } from '@coffer-org/server/conversation-store';
|
|
3
3
|
import { listUsers } from '@coffer-org/server/auth-store';
|
|
4
4
|
import { abortTurn } from '@coffer-org/server/turn-gate';
|
|
5
5
|
import { listAgentCatalog, getDefaultAgentId, listRegisteredAgents, getConversationStarters, mayBePrivate, mayRead, } from '@coffer-org/server/orchestrator';
|
|
6
|
-
import {
|
|
6
|
+
import { getConversation, setConversation, listConversations, readConversation, } from '@coffer-org/server/conversation-store';
|
|
7
|
+
import { mayWrite } from '@coffer-org/server/orchestrator';
|
|
7
8
|
import { CAPABILITIES } from "./send.js";
|
|
8
9
|
import { subscribe, evictWhere } from "./channel-registry.js";
|
|
9
10
|
const TITLE_MAX = 60;
|
|
@@ -21,7 +22,7 @@ function title(firstUserText) {
|
|
|
21
22
|
return t.length <= TITLE_MAX ? t : `${t.slice(0, TITLE_MAX - 1)}…`;
|
|
22
23
|
}
|
|
23
24
|
export async function threadsAction(_body, caller) {
|
|
24
|
-
const chats = await
|
|
25
|
+
const chats = await listConversations(caller.id).then((rows) => rows.map((r) => ({ ...r, convId: r.id })));
|
|
25
26
|
const users = await listUsers();
|
|
26
27
|
const nameById = new Map(users.map((u) => [String(u.id), u.displayName]));
|
|
27
28
|
return {
|
|
@@ -42,13 +43,21 @@ function requireConvId(body) {
|
|
|
42
43
|
throw new HttpError(400, 'missing required field: convId');
|
|
43
44
|
return convId;
|
|
44
45
|
}
|
|
45
|
-
const EMPTY_SELECTION = {
|
|
46
|
+
const EMPTY_SELECTION = {
|
|
47
|
+
id: '',
|
|
48
|
+
agentId: null,
|
|
49
|
+
presetId: null,
|
|
50
|
+
title: null,
|
|
51
|
+
owner: null,
|
|
52
|
+
visibility: null,
|
|
53
|
+
updatedAt: '',
|
|
54
|
+
};
|
|
46
55
|
export async function historyAction(body, caller) {
|
|
47
56
|
const convId = requireConvId(body);
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
57
|
+
const conversationId = await openConversation(convId, caller.id, 'read');
|
|
58
|
+
if (conversationId === null)
|
|
50
59
|
return { convId, messages: [], headMsgId: null };
|
|
51
|
-
const rows = await
|
|
60
|
+
const rows = await readConversation(conversationId);
|
|
52
61
|
const reasoningByBot = new Map(rows.filter((m) => m.role === 'reasoning').map((m) => [m.msgId.slice(0, -2), m.text]));
|
|
53
62
|
const suggestionsByBot = new Map(rows
|
|
54
63
|
.filter((m) => m.role === 'suggestions')
|
|
@@ -71,18 +80,18 @@ export async function historyAction(body, caller) {
|
|
|
71
80
|
}
|
|
72
81
|
export async function agentsAction(body, caller) {
|
|
73
82
|
const convId = requireConvId(body);
|
|
74
|
-
const
|
|
83
|
+
const conversationId = await openConversation(convId, caller.id, 'read');
|
|
75
84
|
return {
|
|
76
85
|
agents: await listAgentCatalog(),
|
|
77
86
|
defaultAgentId: getDefaultAgentId() ?? null,
|
|
78
|
-
selection:
|
|
87
|
+
selection: conversationId === null ? EMPTY_SELECTION : await getConversation(conversationId),
|
|
79
88
|
starters: await getConversationStarters(),
|
|
80
89
|
};
|
|
81
90
|
}
|
|
82
91
|
export async function selectAgentAction(body, caller) {
|
|
83
92
|
const convId = requireConvId(body);
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
93
|
+
const conversationId = await openConversation(convId, caller.id, 'write');
|
|
94
|
+
if (conversationId === null)
|
|
86
95
|
return { selection: EMPTY_SELECTION };
|
|
87
96
|
const patch = {};
|
|
88
97
|
if ('agentId' in body) {
|
|
@@ -93,19 +102,19 @@ export async function selectAgentAction(body, caller) {
|
|
|
93
102
|
}
|
|
94
103
|
if ('presetId' in body)
|
|
95
104
|
patch.presetId = typeof body['presetId'] === 'string' && body['presetId'] ? body['presetId'] : null;
|
|
96
|
-
await
|
|
97
|
-
return { selection: await
|
|
105
|
+
await setConversation(conversationId, patch);
|
|
106
|
+
return { selection: await getConversation(conversationId) };
|
|
98
107
|
}
|
|
99
108
|
export async function channelAction(body, ctx) {
|
|
100
109
|
const convId = requireConvId(body);
|
|
101
|
-
const
|
|
102
|
-
if (
|
|
110
|
+
const conversationId = await openConversation(convId, ctx.caller.id, 'read');
|
|
111
|
+
if (conversationId === null)
|
|
103
112
|
return;
|
|
104
113
|
let resolveHeld;
|
|
105
114
|
const held = new Promise((resolve) => {
|
|
106
115
|
resolveHeld = resolve;
|
|
107
116
|
});
|
|
108
|
-
const off = subscribe(
|
|
117
|
+
const off = subscribe(conversationId, {
|
|
109
118
|
viewerId: ctx.caller.id,
|
|
110
119
|
emit: ctx.emit,
|
|
111
120
|
close: () => resolveHeld(),
|
|
@@ -123,23 +132,30 @@ export async function channelAction(body, ctx) {
|
|
|
123
132
|
export async function setVisibilityAction(body, caller, deps = {}) {
|
|
124
133
|
const convId = requireConvId(body);
|
|
125
134
|
const makePrivate = body['private'] === true;
|
|
126
|
-
const
|
|
127
|
-
if (
|
|
135
|
+
const conversationId = await openConversation(convId, caller.id, 'write');
|
|
136
|
+
if (conversationId === null)
|
|
128
137
|
return { visibility: null };
|
|
129
|
-
const selection = await
|
|
138
|
+
const selection = await getConversation(conversationId);
|
|
130
139
|
if (selection.owner !== caller.id)
|
|
131
140
|
return { visibility: selection.visibility };
|
|
132
141
|
if (makePrivate && !mayBePrivate(deps.capabilities ?? CAPABILITIES))
|
|
133
142
|
return { visibility: selection.visibility };
|
|
134
143
|
const visibility = makePrivate ? 'private' : null;
|
|
135
|
-
await
|
|
136
|
-
evictWhere(
|
|
144
|
+
await setConversation(conversationId, { visibility });
|
|
145
|
+
evictWhere(conversationId, (viewerId) => !mayRead({ owner: selection.owner, visibility }, viewerId));
|
|
137
146
|
return { visibility };
|
|
138
147
|
}
|
|
139
148
|
export async function stopAction(body, caller) {
|
|
140
149
|
const convId = requireConvId(body);
|
|
141
|
-
const
|
|
142
|
-
if (
|
|
150
|
+
const conversationId = await openConversation(convId, caller.id, 'write');
|
|
151
|
+
if (conversationId === null)
|
|
143
152
|
return;
|
|
144
|
-
abortTurn('webchat',
|
|
153
|
+
abortTurn('webchat', conversationId);
|
|
154
|
+
}
|
|
155
|
+
async function openConversation(convId, viewerId, need) {
|
|
156
|
+
if (!convId || convId.includes(':'))
|
|
157
|
+
return null;
|
|
158
|
+
const conv = await getConversation(convId);
|
|
159
|
+
const allowed = need === 'read' ? mayRead(conv, viewerId) : mayWrite(conv, viewerId);
|
|
160
|
+
return allowed ? convId : null;
|
|
145
161
|
}
|
|
@@ -3,7 +3,14 @@ export interface Subscriber {
|
|
|
3
3
|
emit(event: string, data: unknown): void;
|
|
4
4
|
close(): void;
|
|
5
5
|
}
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
export type ChannelCallback = (frame: {
|
|
7
|
+
event: string;
|
|
8
|
+
data: unknown;
|
|
9
|
+
}) => void;
|
|
10
|
+
export declare function subscribe(conversationId: string, sub: Subscriber | ChannelCallback): () => void;
|
|
11
|
+
export declare function subscribeAs(conversationId: string, viewerId: string, cb: ChannelCallback): Promise<() => void>;
|
|
12
|
+
export declare function broadcast(conversationId: string, event: string, data: unknown): void;
|
|
13
|
+
export declare function evictWhere(conversationId: string, drop: (viewerId: string) => boolean): void;
|
|
14
|
+
export declare function subscriberCount(conversationId: string): number;
|
|
15
|
+
export declare function initFanout(): () => void;
|
|
16
|
+
export declare function teardownFanout(): void;
|
|
@@ -1,24 +1,50 @@
|
|
|
1
1
|
import { getLogger } from '@coffer-org/sdk/logger';
|
|
2
|
+
import { getConversation, onMessage, conversationEmitter, HIDDEN_ROLES } from '@coffer-org/server/conversation-store';
|
|
3
|
+
import { mayRead } from '@coffer-org/server/orchestrator';
|
|
2
4
|
const log = getLogger('webchat');
|
|
3
5
|
const channels = new Map();
|
|
4
|
-
export function subscribe(
|
|
5
|
-
|
|
6
|
+
export function subscribe(conversationId, sub) {
|
|
7
|
+
if (typeof sub === 'function') {
|
|
8
|
+
return subscribe(conversationId, {
|
|
9
|
+
viewerId: '*',
|
|
10
|
+
emit: (event, data) => sub({ event, data }),
|
|
11
|
+
close: () => { },
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
let set = channels.get(conversationId);
|
|
6
15
|
if (!set) {
|
|
7
16
|
set = new Set();
|
|
8
|
-
channels.set(
|
|
17
|
+
channels.set(conversationId, set);
|
|
9
18
|
}
|
|
10
19
|
set.add(sub);
|
|
11
20
|
return () => {
|
|
12
|
-
const current = channels.get(
|
|
21
|
+
const current = channels.get(conversationId);
|
|
13
22
|
if (!current)
|
|
14
23
|
return;
|
|
15
24
|
current.delete(sub);
|
|
16
25
|
if (current.size === 0)
|
|
17
|
-
channels.delete(
|
|
26
|
+
channels.delete(conversationId);
|
|
18
27
|
};
|
|
19
28
|
}
|
|
20
|
-
export function
|
|
21
|
-
|
|
29
|
+
export async function subscribeAs(conversationId, viewerId, cb) {
|
|
30
|
+
if (viewerId !== '*') {
|
|
31
|
+
try {
|
|
32
|
+
const conv = await getConversation(conversationId);
|
|
33
|
+
if (conv && !mayRead(conv, viewerId)) {
|
|
34
|
+
return () => { };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return subscribe(conversationId, {
|
|
41
|
+
viewerId,
|
|
42
|
+
emit: (event, data) => cb({ event, data }),
|
|
43
|
+
close: () => { },
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export function broadcast(conversationId, event, data) {
|
|
47
|
+
const set = channels.get(conversationId);
|
|
22
48
|
if (!set)
|
|
23
49
|
return;
|
|
24
50
|
for (const sub of set) {
|
|
@@ -26,12 +52,12 @@ export function broadcast(chatId, event, data) {
|
|
|
26
52
|
sub.emit(event, data);
|
|
27
53
|
}
|
|
28
54
|
catch (err) {
|
|
29
|
-
log.warn(`channel emit failed for viewer ${sub.viewerId} on ${
|
|
55
|
+
log.warn(`channel emit failed for viewer ${sub.viewerId} on ${conversationId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
30
56
|
}
|
|
31
57
|
}
|
|
32
58
|
}
|
|
33
|
-
export function evictWhere(
|
|
34
|
-
const set = channels.get(
|
|
59
|
+
export function evictWhere(conversationId, drop) {
|
|
60
|
+
const set = channels.get(conversationId);
|
|
35
61
|
if (!set)
|
|
36
62
|
return;
|
|
37
63
|
for (const sub of set) {
|
|
@@ -41,8 +67,60 @@ export function evictWhere(chatId, drop) {
|
|
|
41
67
|
sub.close();
|
|
42
68
|
}
|
|
43
69
|
if (set.size === 0)
|
|
44
|
-
channels.delete(
|
|
70
|
+
channels.delete(conversationId);
|
|
71
|
+
}
|
|
72
|
+
export function subscriberCount(conversationId) {
|
|
73
|
+
return channels.get(conversationId)?.size ?? 0;
|
|
74
|
+
}
|
|
75
|
+
let unsubscribeMessage;
|
|
76
|
+
let unsubscribeConversation;
|
|
77
|
+
export function initFanout() {
|
|
78
|
+
if (!unsubscribeMessage) {
|
|
79
|
+
unsubscribeMessage = onMessage((m) => {
|
|
80
|
+
if (HIDDEN_ROLES.includes(m.role))
|
|
81
|
+
return;
|
|
82
|
+
if (m.role === 'assistant') {
|
|
83
|
+
broadcast(m.conversationId, 'message', { msgId: m.msgId, text: m.text });
|
|
84
|
+
}
|
|
85
|
+
else if (m.role === 'user') {
|
|
86
|
+
broadcast(m.conversationId, 'user', {
|
|
87
|
+
msgId: m.msgId,
|
|
88
|
+
text: m.text,
|
|
89
|
+
ts: m.ts,
|
|
90
|
+
...(m.attachments?.length ? { attachments: m.attachments } : {}),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (!unsubscribeConversation) {
|
|
96
|
+
const handler = (e) => {
|
|
97
|
+
if (e instanceof CustomEvent) {
|
|
98
|
+
const { id, patch } = e.detail;
|
|
99
|
+
if (patch && (patch.visibility !== undefined || patch.owner !== undefined)) {
|
|
100
|
+
void (async () => {
|
|
101
|
+
try {
|
|
102
|
+
const conv = await getConversation(id);
|
|
103
|
+
evictWhere(id, (viewerId) => !mayRead(conv, viewerId));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
}
|
|
107
|
+
})();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
conversationEmitter.addEventListener('conversation', handler);
|
|
112
|
+
unsubscribeConversation = () => {
|
|
113
|
+
conversationEmitter.removeEventListener('conversation', handler);
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return () => {
|
|
117
|
+
teardownFanout();
|
|
118
|
+
};
|
|
45
119
|
}
|
|
46
|
-
export function
|
|
47
|
-
|
|
120
|
+
export function teardownFanout() {
|
|
121
|
+
unsubscribeMessage?.();
|
|
122
|
+
unsubscribeMessage = undefined;
|
|
123
|
+
unsubscribeConversation?.();
|
|
124
|
+
unsubscribeConversation = undefined;
|
|
48
125
|
}
|
|
126
|
+
initFanout();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Connector } from '@coffer-org/server/orchestrator';
|
|
2
|
-
import {
|
|
2
|
+
import { setConversation as storeSelection } from '@coffer-org/server/conversation-store';
|
|
3
3
|
import type { ReasoningDisplay } from './settings.ts';
|
|
4
4
|
export interface StreamingConnector {
|
|
5
5
|
connector: Connector;
|
|
@@ -7,13 +7,9 @@ export interface StreamingConnector {
|
|
|
7
7
|
suggestions(): string[] | null;
|
|
8
8
|
}
|
|
9
9
|
export interface StreamingConnectorOpts {
|
|
10
|
-
|
|
11
|
-
botMsgId
|
|
10
|
+
conversationId?: string;
|
|
11
|
+
botMsgId?: string;
|
|
12
12
|
display: ReasoningDisplay;
|
|
13
|
-
record?: typeof storeAssistant;
|
|
14
|
-
recordReasoning?: typeof storeReasoning;
|
|
15
|
-
recordSuggestions?: typeof storeSuggestions;
|
|
16
|
-
recordContext?: typeof storeContext;
|
|
17
13
|
setSelection?: typeof storeSelection;
|
|
18
14
|
}
|
|
19
15
|
export declare function makeStreamingConnector(opts: StreamingConnectorOpts): StreamingConnector;
|
|
@@ -1,111 +1,70 @@
|
|
|
1
1
|
import { makeLiveSink, plainRender } from '@coffer-org/server/orchestrator';
|
|
2
|
-
import {
|
|
2
|
+
import { setConversation as storeSelection } from '@coffer-org/server/conversation-store';
|
|
3
3
|
import { broadcast } from "./channel-registry.js";
|
|
4
4
|
import { getLogger } from '@coffer-org/sdk/logger';
|
|
5
5
|
const log = getLogger('webchat');
|
|
6
6
|
const WEBCHAT_THROTTLE_MS = 250;
|
|
7
7
|
const WEBCHAT_MAX = 100_000;
|
|
8
8
|
export function makeStreamingConnector(opts) {
|
|
9
|
-
const record = opts.record ?? storeAssistant;
|
|
10
|
-
const recordReasoningFn = opts.recordReasoning ?? storeReasoning;
|
|
11
|
-
const recordSuggestionsFn = opts.recordSuggestions ?? storeSuggestions;
|
|
12
|
-
const recordContextFn = opts.recordContext ?? storeContext;
|
|
13
9
|
const setSelectionFn = opts.setSelection ?? storeSelection;
|
|
14
10
|
let recordedId = null;
|
|
15
11
|
let lastSuggestions = null;
|
|
12
|
+
let hasAnswer = false;
|
|
16
13
|
const connector = {
|
|
17
14
|
id: 'webchat',
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
open(envelope, msgId) {
|
|
16
|
+
const activeBotMsgId = msgId ?? opts.botMsgId ?? '';
|
|
17
|
+
const conversationId = envelope.conversationId ?? opts.conversationId ?? '';
|
|
21
18
|
const inner = makeLiveSink({
|
|
22
19
|
ops: {
|
|
23
20
|
send: async (text) => {
|
|
24
|
-
broadcast(
|
|
25
|
-
return
|
|
21
|
+
broadcast(conversationId, 'delta', { msgId: activeBotMsgId, text });
|
|
22
|
+
return activeBotMsgId;
|
|
26
23
|
},
|
|
27
|
-
edit: async (
|
|
28
|
-
broadcast(
|
|
24
|
+
edit: async (id, text) => {
|
|
25
|
+
broadcast(conversationId, 'delta', { msgId: id, text });
|
|
29
26
|
},
|
|
30
27
|
},
|
|
31
28
|
throttleMs: WEBCHAT_THROTTLE_MS,
|
|
32
29
|
maxLength: WEBCHAT_MAX,
|
|
33
30
|
render: plainRender(WEBCHAT_MAX),
|
|
34
31
|
});
|
|
35
|
-
let answerText = null;
|
|
36
|
-
let answerReasoning = null;
|
|
37
|
-
let hasAnswer = false;
|
|
38
|
-
let persisted = false;
|
|
39
32
|
let afterwordWork = Promise.resolve();
|
|
40
33
|
return {
|
|
41
34
|
emit(e) {
|
|
42
35
|
if (e.kind === 'reasoning') {
|
|
43
36
|
if (opts.display !== 'off')
|
|
44
|
-
broadcast(
|
|
37
|
+
broadcast(conversationId, 'reasoning', { msgId: activeBotMsgId, text: e.text });
|
|
45
38
|
return;
|
|
46
39
|
}
|
|
47
40
|
if (e.kind === 'suggestions') {
|
|
48
41
|
lastSuggestions = e.items;
|
|
49
|
-
if (recordedId && e.items.length) {
|
|
50
|
-
void recordSuggestionsFn({
|
|
51
|
-
chatId: opts.chatId,
|
|
52
|
-
botMsgId: opts.botMsgId,
|
|
53
|
-
parentMsgId: envelope.turnId,
|
|
54
|
-
suggestions: e.items,
|
|
55
|
-
now: Math.floor(Date.now() / 1000),
|
|
56
|
-
}).catch((err) => {
|
|
57
|
-
log.warn(`recordSuggestions failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
42
|
return;
|
|
61
43
|
}
|
|
62
44
|
if (e.kind === 'title') {
|
|
63
|
-
afterwordWork = afterwordWork.then(() => setSelectionFn(
|
|
45
|
+
afterwordWork = afterwordWork.then(() => setSelectionFn(conversationId, { title: e.text }).catch((err) => {
|
|
64
46
|
log.warn(`setSelection(title) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
65
47
|
}));
|
|
66
48
|
return;
|
|
67
49
|
}
|
|
68
50
|
if (e.kind === 'answer') {
|
|
69
|
-
answerText = e.text;
|
|
70
|
-
answerReasoning = e.reasoning;
|
|
71
51
|
hasAnswer = true;
|
|
52
|
+
recordedId = activeBotMsgId;
|
|
72
53
|
}
|
|
73
54
|
inner.emit(e);
|
|
74
55
|
},
|
|
75
56
|
async done() {
|
|
76
57
|
await inner.done();
|
|
77
|
-
if (hasAnswer && !persisted) {
|
|
78
|
-
persisted = true;
|
|
79
|
-
const now = Math.floor(Date.now() / 1000);
|
|
80
|
-
const wrote = await record({
|
|
81
|
-
chatId: opts.chatId,
|
|
82
|
-
parentMsgId: envelope.turnId,
|
|
83
|
-
botMsgId: opts.botMsgId,
|
|
84
|
-
text: answerText ?? '',
|
|
85
|
-
now,
|
|
86
|
-
});
|
|
87
|
-
recordedId = wrote ? opts.botMsgId : null;
|
|
88
|
-
if (wrote && opts.display !== 'off' && answerReasoning) {
|
|
89
|
-
await recordReasoningFn({
|
|
90
|
-
chatId: opts.chatId,
|
|
91
|
-
botMsgId: opts.botMsgId,
|
|
92
|
-
parentMsgId: envelope.turnId,
|
|
93
|
-
text: answerReasoning,
|
|
94
|
-
now,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
58
|
await afterwordWork;
|
|
99
59
|
},
|
|
100
60
|
};
|
|
101
61
|
},
|
|
102
|
-
async
|
|
103
|
-
await recordContextFn({ ...m, chatId: opts.chatId });
|
|
62
|
+
async bindMessage(_envelope, _msgId, _externalId) {
|
|
104
63
|
},
|
|
105
64
|
};
|
|
106
65
|
return {
|
|
107
66
|
connector,
|
|
108
|
-
recorded: () => recordedId,
|
|
67
|
+
recorded: () => (hasAnswer ? recordedId : null),
|
|
109
68
|
suggestions: () => lastSuggestions,
|
|
110
69
|
};
|
|
111
70
|
}
|
package/dist/runtime/format.d.ts
CHANGED
package/dist/runtime/format.js
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import { frontendInstructions } from '@coffer-org/server/frontend-agent';
|
|
2
|
-
|
|
3
|
-
'CRITICAL OUTPUT FORMAT — MARKDOWN ONLY. Format the reply strictly as Markdown (GFM). NEVER emit raw HTML tags — they are stripped before rendering and their content may be lost.',
|
|
4
|
-
'Inline: **bold**, *italic*, `inline code`, ~~strikethrough~~. Links: [text](https://…).',
|
|
5
|
-
'Blocks: blank-line-separated paragraphs, `#`–`###` headings, `>` blockquotes, `---` separators.',
|
|
6
|
-
'Lists: `- item` for bullets, `1. item` for numbered steps. Nest with two spaces.',
|
|
7
|
-
'Tables render as real tables — use GFM pipe syntax with a header separator row.',
|
|
8
|
-
'Code blocks: triple backticks with a language tag (```python) — highlighted on render.',
|
|
9
|
-
'The reply is read in a narrow chat panel (~360px) — keep it short and skimmable: short paragraphs, lists over prose, narrow tables.',
|
|
10
|
-
'Use Unicode symbols and emoji to structure replies: arrows (→), status marks (✅ ⚠️ ❌ ⏳), topic emoji (📅 💰 🏥 🛒 🌱 …).',
|
|
11
|
-
].join('\n');
|
|
2
|
+
import { NEUTRAL_FORMAT } from '@coffer-org/markdown';
|
|
12
3
|
export async function webChannelSystem() {
|
|
13
4
|
const site = await frontendInstructions('');
|
|
14
|
-
return [
|
|
5
|
+
return [NEUTRAL_FORMAT, site].filter(Boolean).join('\n\n');
|
|
15
6
|
}
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { PluginHooks } from '@coffer-org/server/plugin-hooks';
|
|
2
2
|
export type { ThreadSummary, HistoryMsg } from './actions.ts';
|
|
3
|
-
export { WEB_FORMAT } from './format.ts';
|
|
4
3
|
export { makeStreamingConnector } from './connector.ts';
|
|
5
|
-
export {
|
|
4
|
+
export { getConversation as getSelection, setConversation as setSelection, } from '@coffer-org/server/conversation-store';
|
|
6
5
|
export { sendAction, pageContext } from './send.ts';
|
|
7
6
|
export declare const serverHooks: PluginHooks;
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { pruneThreadState } from '@coffer-org/server/thread-state';
|
|
1
|
+
import { pruneConversations } from '@coffer-org/server/conversation-store';
|
|
3
2
|
import { threadsAction, historyAction, agentsAction, selectAgentAction, setVisibilityAction, channelAction, stopAction, } from "./actions.js";
|
|
4
3
|
import { sendAction } from "./send.js";
|
|
5
|
-
import { CONNECTOR } from "./chain-store.js";
|
|
6
4
|
import { registerConnector } from '@coffer-org/server/orchestrator';
|
|
5
|
+
import { initFanout, teardownFanout } from "./channel-registry.js";
|
|
7
6
|
const THREAD_TTL_MS = Number(process.env['WEBCHAT_THREAD_TTL_MS'] ?? 30 * 86_400_000);
|
|
8
7
|
let unregisterConnector;
|
|
9
|
-
export { WEB_FORMAT } from "./format.js";
|
|
10
8
|
export { makeStreamingConnector } from "./connector.js";
|
|
11
|
-
export {
|
|
9
|
+
export { getConversation as getSelection, setConversation as setSelection, } from '@coffer-org/server/conversation-store';
|
|
12
10
|
export { sendAction, pageContext } from "./send.js";
|
|
13
11
|
export const serverHooks = {
|
|
14
12
|
init: () => {
|
|
15
13
|
unregisterConnector = registerConnector({ id: 'webchat' });
|
|
14
|
+
initFanout();
|
|
16
15
|
},
|
|
17
16
|
teardown: () => {
|
|
18
17
|
unregisterConnector?.();
|
|
19
18
|
unregisterConnector = undefined;
|
|
19
|
+
teardownFanout();
|
|
20
20
|
},
|
|
21
21
|
userActions: {
|
|
22
22
|
threads: threadsAction,
|
|
@@ -36,8 +36,7 @@ export const serverHooks = {
|
|
|
36
36
|
intervalMs: 86_400_000,
|
|
37
37
|
run: async () => {
|
|
38
38
|
const cutoff = new Date(Date.now() - THREAD_TTL_MS);
|
|
39
|
-
await
|
|
40
|
-
await pruneThreadState(CONNECTOR, cutoff.toISOString());
|
|
39
|
+
await pruneConversations(Math.floor(cutoff.getTime() / 1000), cutoff.toISOString());
|
|
41
40
|
},
|
|
42
41
|
},
|
|
43
42
|
],
|
package/dist/runtime/send.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { handleIncoming } from '@coffer-org/server/orchestrator';
|
|
2
2
|
import { type ActionCaller } from '@coffer-org/server/plugin-hooks';
|
|
3
|
+
import { conversationEmitter } from '@coffer-org/server/conversation-store';
|
|
3
4
|
import type { ConnectorCapabilities, ContextFact } from '@coffer-org/server/orchestrator';
|
|
5
|
+
export { conversationEmitter };
|
|
4
6
|
export declare const CAPABILITIES: ConnectorCapabilities;
|
|
5
7
|
export interface SendDeps {
|
|
6
8
|
handleIncoming?: typeof handleIncoming;
|