@coffer-org/server 7.3.0 → 7.5.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/auth-api.js +18 -19
- package/dist/compute-unit.js +0 -3
- package/dist/conversation-store.d.ts +61 -0
- package/dist/conversation-store.js +223 -0
- package/dist/entity-schema.d.ts +5 -4
- package/dist/entity-schema.js +32 -41
- package/dist/identity-providers.d.ts +17 -0
- package/dist/identity-providers.js +14 -0
- package/dist/index.js +2 -1
- package/dist/mcp-contract/schema.d.ts +0 -1
- package/dist/mcp-contract/schema.js +0 -2
- package/dist/mutate.js +0 -15
- package/dist/orchestrator/draft-message.d.ts +18 -0
- package/dist/orchestrator/draft-message.js +85 -0
- package/dist/orchestrator/index.d.ts +4 -5
- package/dist/orchestrator/index.js +2 -9
- package/dist/orchestrator/live-message.js +6 -3
- package/dist/orchestrator/pipeline.d.ts +7 -10
- package/dist/orchestrator/pipeline.js +177 -96
- package/dist/orchestrator/registry.d.ts +0 -2
- package/dist/orchestrator/registry.js +0 -9
- package/dist/orchestrator/types.d.ts +17 -25
- package/dist/plugin-hooks.d.ts +12 -1
- package/dist/plugin-hooks.js +4 -0
- package/dist/plugin-runtime.d.ts +4 -0
- package/dist/plugin-runtime.js +11 -7
- package/dist/plugins-api.d.ts +8 -2
- package/dist/plugins-api.js +4 -2
- package/dist/settings-write.d.ts +1 -2
- package/dist/settings-write.js +1 -2
- package/dist/temporal.js +8 -3
- package/package.json +2 -2
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { getLogger } from '@coffer-org/sdk/logger';
|
|
2
|
+
const log = getLogger('orchestrator');
|
|
3
|
+
async function swallow(fn, fallback, label) {
|
|
4
|
+
try {
|
|
5
|
+
return await fn();
|
|
6
|
+
}
|
|
7
|
+
catch (err) {
|
|
8
|
+
log.error(`draft-message ${label}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9
|
+
return fallback;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function makeDraftSink(o) {
|
|
13
|
+
let text = '';
|
|
14
|
+
let reasoning = '';
|
|
15
|
+
let lastDraft = '';
|
|
16
|
+
let lastPush = 0;
|
|
17
|
+
let busy = false;
|
|
18
|
+
let closed = false;
|
|
19
|
+
let inFlight = Promise.resolve();
|
|
20
|
+
const timer = setInterval(() => {
|
|
21
|
+
if (closed || busy)
|
|
22
|
+
return;
|
|
23
|
+
const t = o.preview({ text, reasoning: reasoning || null }).slice(0, o.maxLength);
|
|
24
|
+
if (!t)
|
|
25
|
+
return;
|
|
26
|
+
if (t === lastDraft && Date.now() - lastPush < o.keepAliveMs)
|
|
27
|
+
return;
|
|
28
|
+
busy = true;
|
|
29
|
+
inFlight = swallow(() => o.ops.draft(t), undefined, 'draft')
|
|
30
|
+
.then(() => {
|
|
31
|
+
lastDraft = t;
|
|
32
|
+
lastPush = Date.now();
|
|
33
|
+
})
|
|
34
|
+
.finally(() => {
|
|
35
|
+
busy = false;
|
|
36
|
+
});
|
|
37
|
+
}, o.throttleMs);
|
|
38
|
+
if (typeof timer.unref === 'function')
|
|
39
|
+
timer.unref();
|
|
40
|
+
function flushSegment() {
|
|
41
|
+
const body = text.slice(0, o.maxLength).trim();
|
|
42
|
+
text = '';
|
|
43
|
+
lastDraft = '';
|
|
44
|
+
lastPush = 0;
|
|
45
|
+
if (!body)
|
|
46
|
+
return;
|
|
47
|
+
inFlight = inFlight.then(() => swallow(() => o.ops.send(body), null, 'send(segment)'));
|
|
48
|
+
}
|
|
49
|
+
async function deliver(r) {
|
|
50
|
+
closed = true;
|
|
51
|
+
clearInterval(timer);
|
|
52
|
+
await inFlight;
|
|
53
|
+
for (const part of o.render(r)) {
|
|
54
|
+
await swallow(() => o.ops.send(part), null, 'send(final)');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
let finished = Promise.resolve();
|
|
58
|
+
let terminalDelivered = false;
|
|
59
|
+
function deliverTerminal(kind, r) {
|
|
60
|
+
if (terminalDelivered) {
|
|
61
|
+
log.warn(`draft-message: a second terminal event (${kind}) after the turn already finished — dropped`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
terminalDelivered = true;
|
|
65
|
+
finished = deliver(r);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
emit(e) {
|
|
69
|
+
if (e.kind === 'delta')
|
|
70
|
+
text = e.text;
|
|
71
|
+
else if (e.kind === 'reasoning')
|
|
72
|
+
reasoning = e.text;
|
|
73
|
+
else if (e.kind === 'segment')
|
|
74
|
+
flushSegment();
|
|
75
|
+
else if (e.kind === 'answer')
|
|
76
|
+
deliverTerminal('answer', { text: e.text, reasoning: e.reasoning });
|
|
77
|
+
else if (e.kind === 'error')
|
|
78
|
+
deliverTerminal('error', { text: null, reasoning: null });
|
|
79
|
+
},
|
|
80
|
+
done: () => {
|
|
81
|
+
clearInterval(timer);
|
|
82
|
+
return finished;
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -4,7 +4,7 @@ import { listDiagnostics } from './diagnostics.ts';
|
|
|
4
4
|
import { type PipelineOptions } from './pipeline.ts';
|
|
5
5
|
import type { Connector, TurnRequest } from './types.ts';
|
|
6
6
|
export declare const handleIncoming: (connector: Connector, turn: TurnRequest, options?: PipelineOptions) => Promise<void>;
|
|
7
|
-
export { registerAgent, resolveAgent, registerConnector, isConnectorRegistered, clearRuntimeRegistries, listRegisteredAgents, listRegisteredConnectors,
|
|
7
|
+
export { registerAgent, resolveAgent, registerConnector, isConnectorRegistered, clearRuntimeRegistries, listRegisteredAgents, listRegisteredConnectors, listAgentCatalog, getDefaultAgentId, liveAgentId, } from './registry.ts';
|
|
8
8
|
export { attachmentMaterializer } from './attachments.ts';
|
|
9
9
|
export { makeAttachmentCapabilities } from './agent-capabilities.ts';
|
|
10
10
|
export { makeSystemCapabilities } from './system-capabilities.ts';
|
|
@@ -15,19 +15,18 @@ export { inspectUpload, isAgentToolContentResult } from './file-inspection.ts';
|
|
|
15
15
|
export type { PipelineOptions, RunAgentFn } from './pipeline.ts';
|
|
16
16
|
export { buildPolicy, loadGatePolicy, loadAgentId } from './config.ts';
|
|
17
17
|
export { getConversationStarters, refreshSystemStarters } from './starters.ts';
|
|
18
|
-
export { resolveSpeaker } from './pipeline.ts';
|
|
19
18
|
export { mayRead, mayWrite, mayBePrivate } from './conversation-access.ts';
|
|
20
19
|
export type { ConversationAccess } from './conversation-access.ts';
|
|
21
20
|
export { makeLiveSink, plainRender } from './live-message.ts';
|
|
22
21
|
export type { LiveChannelOps, LiveSinkOpts, RenderFn } from './live-message.ts';
|
|
23
|
-
export {
|
|
22
|
+
export { makeDraftSink } from './draft-message.ts';
|
|
23
|
+
export type { DraftChannelOps, DraftSinkOpts } from './draft-message.ts';
|
|
24
24
|
export { DEFAULT_TASK_TIMEOUT_MS } from '../background-scheduler.ts';
|
|
25
|
-
export type { Connector, TurnEnvelope, ConnectorCapabilities, TurnBody, TurnRequest,
|
|
25
|
+
export type { Connector, TurnEnvelope, ConnectorCapabilities, TurnBody, TurnRequest, GatePolicy, TurnEvent, TurnSink, AttachmentRef, AttachmentMaterializer, AgentToolDefinition, AgentToolProvider, ConvMessage, AgentTurn, AgentRuntime, AgentCapabilities, AgentMediaKind, AgentMediaLimits, AgentPreset, AgentDescriptor, AgentCatalogEntry, ConnectorRegistration, AgentToolContentBlock, AgentToolContentResult, } from './types.ts';
|
|
26
26
|
export type { AuthRole } from '../plugin-hooks.ts';
|
|
27
27
|
export declare function startOrchestrator(): void;
|
|
28
28
|
export declare function stopOrchestrator(): void;
|
|
29
29
|
export declare const orchestratorStartersTask: BackgroundTask;
|
|
30
|
-
export declare const linkCodePruneTask: BackgroundTask;
|
|
31
30
|
export declare function orchestratorDiagnostics(): Promise<{
|
|
32
31
|
generatedAt: string;
|
|
33
32
|
agents: string[];
|
|
@@ -6,10 +6,9 @@ import { setLogDb } from "./pipeline.js";
|
|
|
6
6
|
import { refreshSystemStarters } from "./starters.js";
|
|
7
7
|
import { getLogger } from '@coffer-org/sdk/logger';
|
|
8
8
|
const log = getLogger('orchestrator');
|
|
9
|
-
import { pruneLinkCodes } from "../identity-link.js";
|
|
10
9
|
import { handleIncoming as handleIncomingImpl } from "./pipeline.js";
|
|
11
10
|
export const handleIncoming = handleIncomingImpl;
|
|
12
|
-
export { registerAgent, resolveAgent, registerConnector, isConnectorRegistered, clearRuntimeRegistries, listRegisteredAgents, listRegisteredConnectors,
|
|
11
|
+
export { registerAgent, resolveAgent, registerConnector, isConnectorRegistered, clearRuntimeRegistries, listRegisteredAgents, listRegisteredConnectors, listAgentCatalog, getDefaultAgentId, liveAgentId, } from "./registry.js";
|
|
13
12
|
export { attachmentMaterializer } from "./attachments.js";
|
|
14
13
|
export { makeAttachmentCapabilities } from "./agent-capabilities.js";
|
|
15
14
|
export { makeSystemCapabilities } from "./system-capabilities.js";
|
|
@@ -18,10 +17,9 @@ export { CONNECTOR_FACT_NAME } from "./context-facts.js";
|
|
|
18
17
|
export { inspectUpload, isAgentToolContentResult } from "./file-inspection.js";
|
|
19
18
|
export { buildPolicy, loadGatePolicy, loadAgentId } from "./config.js";
|
|
20
19
|
export { getConversationStarters, refreshSystemStarters } from "./starters.js";
|
|
21
|
-
export { resolveSpeaker } from "./pipeline.js";
|
|
22
20
|
export { mayRead, mayWrite, mayBePrivate } from "./conversation-access.js";
|
|
23
21
|
export { makeLiveSink, plainRender } from "./live-message.js";
|
|
24
|
-
export {
|
|
22
|
+
export { makeDraftSink } from "./draft-message.js";
|
|
25
23
|
export { DEFAULT_TASK_TIMEOUT_MS } from "../background-scheduler.js";
|
|
26
24
|
let db;
|
|
27
25
|
const STARTERS_CHECK_INTERVAL_MS = 10 * 60_000;
|
|
@@ -47,11 +45,6 @@ export const orchestratorStartersTask = {
|
|
|
47
45
|
intervalMs: STARTERS_CHECK_INTERVAL_MS,
|
|
48
46
|
run: runStartersRefresh,
|
|
49
47
|
};
|
|
50
|
-
export const linkCodePruneTask = {
|
|
51
|
-
name: 'core:link-code-prune',
|
|
52
|
-
intervalMs: 86_400_000,
|
|
53
|
-
run: pruneLinkCodes,
|
|
54
|
-
};
|
|
55
48
|
export async function orchestratorDiagnostics() {
|
|
56
49
|
return {
|
|
57
50
|
generatedAt: new Date().toISOString(),
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { chunk } from "./format.js";
|
|
2
1
|
import { getLogger } from '@coffer-org/sdk/logger';
|
|
3
2
|
const log = getLogger('orchestrator');
|
|
3
|
+
function chunk(text, max) {
|
|
4
|
+
const out = [];
|
|
5
|
+
for (let i = 0; i < text.length; i += max)
|
|
6
|
+
out.push(text.slice(i, i + max));
|
|
7
|
+
return out.length ? out : [''];
|
|
8
|
+
}
|
|
4
9
|
export function plainRender(max) {
|
|
5
10
|
return (r) => chunk((r.text ?? '').trim() || '⚠️ the agent returned no response', max);
|
|
6
11
|
}
|
|
@@ -104,8 +109,6 @@ export function makeLiveSink(o) {
|
|
|
104
109
|
}
|
|
105
110
|
else if (e.kind === 'answer')
|
|
106
111
|
deliverTerminal('answer', { text: e.text, reasoning: e.reasoning });
|
|
107
|
-
else if (e.kind === 'notice')
|
|
108
|
-
deliverTerminal('notice', { text: e.text, reasoning: null });
|
|
109
112
|
else if (e.kind === 'error')
|
|
110
113
|
deliverTerminal('error', { text: null, reasoning: null });
|
|
111
114
|
},
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import type { AgentMediaLimits, Connector, TurnRequest, GatePolicy, AgentRuntime } from './types.ts';
|
|
1
|
+
import type { AgentMediaLimits, Connector, ConvMessage, TurnRequest, GatePolicy, AgentRuntime } from './types.ts';
|
|
2
2
|
import type { LogDb } from './db.ts';
|
|
3
3
|
import { type AreaWorld, type SystemAreas } from './system-areas.ts';
|
|
4
4
|
import type { AuthUser } from '../auth-store.ts';
|
|
5
|
+
import { type ContextFact } from './context-facts.ts';
|
|
6
|
+
import { type StoredMsg } from '../conversation-store.ts';
|
|
5
7
|
export declare function setLogDb(db: LogDb | undefined): void;
|
|
6
|
-
export declare const NOTICE_THROTTLE: {
|
|
7
|
-
readonly max: 5;
|
|
8
|
-
readonly windowMs: 60000;
|
|
9
|
-
};
|
|
10
8
|
export type RunAgentFn = AgentRuntime['run'];
|
|
11
9
|
export declare function liveWorld(): Promise<AreaWorld>;
|
|
10
|
+
export declare function foldContextFacts(messages: readonly (StoredMsg | ConvMessage)[]): ContextFact[];
|
|
11
|
+
export declare function lastContextAt(messages: readonly (StoredMsg | ConvMessage)[]): Date | null;
|
|
12
|
+
export declare function toConvMessages(stored: readonly StoredMsg[], facts: ContextFact[], userMsgId: string, queryText: string, contextSource?: readonly StoredMsg[]): ConvMessage[];
|
|
12
13
|
export type UserLookup = (id: number) => Promise<AuthUser | null>;
|
|
13
|
-
export declare function defaultResolveUser(id: string, lookup?: UserLookup): Promise<AuthUser | null>;
|
|
14
|
-
export declare function defaultResolveLinkedUser(connectorId: string, externalId: string, lookup?: UserLookup): Promise<AuthUser | null>;
|
|
15
|
-
export declare function resolveSpeaker(connectorId: string, sender: TurnRequest['sender'], deps?: Pick<PipelineDeps, 'resolveUser' | 'resolveLinkedUser'>): Promise<AuthUser | null>;
|
|
16
14
|
export interface PipelineOptions {
|
|
17
15
|
policy?: GatePolicy;
|
|
18
16
|
}
|
|
@@ -22,8 +20,7 @@ export interface PipelineDeps extends PipelineOptions {
|
|
|
22
20
|
logDb?: LogDb | null;
|
|
23
21
|
agentBase?: () => Promise<string>;
|
|
24
22
|
media?: AgentMediaLimits;
|
|
25
|
-
|
|
26
|
-
resolveLinkedUser?: (connectorId: string, externalId: string) => Promise<AuthUser | null>;
|
|
23
|
+
lookupUser?: (id: number) => Promise<AuthUser | null>;
|
|
27
24
|
now?: () => Date;
|
|
28
25
|
timeZone?: string;
|
|
29
26
|
world?: AreaWorld;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { makeThrottle } from "./allow.js";
|
|
2
1
|
import { loadAgentId, loadGatePolicy } from "./config.js";
|
|
3
2
|
import { assembleSystem, mergeAreas } from "./system-areas.js";
|
|
4
3
|
import { attachmentMaterializer } from "./attachments.js";
|
|
@@ -7,7 +6,6 @@ import { makeSystemCapabilities } from "./system-capabilities.js";
|
|
|
7
6
|
import { resolveAgent } from "./registry.js";
|
|
8
7
|
import { recordDiagnostic } from "./diagnostics.js";
|
|
9
8
|
import { getLogger } from '@coffer-org/sdk/logger';
|
|
10
|
-
import { findLinkedUser } from "../identity-link.js";
|
|
11
9
|
import { buildContextFacts } from "./context-facts.js";
|
|
12
10
|
import { systemTimeZone } from "./environment.js";
|
|
13
11
|
import { discoverPlugins } from "../plugin-discovery.js";
|
|
@@ -16,21 +14,17 @@ import { LocalClient } from "../mcp-local.js";
|
|
|
16
14
|
import { loadComposedLocales } from "../locale-registry.js";
|
|
17
15
|
import { buildDomainAreas } from "../mcp-tools.js";
|
|
18
16
|
import { getActiveRegistry } from "../registry-context.js";
|
|
17
|
+
import { DEFAULT_MAX_DEPTH, countUserMessages, newMessageId, putMessage, readConversation, readAll, } from "../conversation-store.js";
|
|
19
18
|
const log = getLogger('orchestrator');
|
|
19
|
+
const DEFAULT_MEDIA = {
|
|
20
|
+
image: { maxBytes: 5 * 1024 * 1024, maxEdge: 1568, accepts: ['image/*'], encode: ['image/jpeg', 'image/png'] },
|
|
21
|
+
document: { maxBytes: 10 * 1024 * 1024, accepts: ['application/pdf'] },
|
|
22
|
+
text: { maxBytes: 512 * 1024, accepts: ['text/*'] },
|
|
23
|
+
};
|
|
20
24
|
let logDb;
|
|
21
25
|
export function setLogDb(db) {
|
|
22
26
|
logDb = db;
|
|
23
27
|
}
|
|
24
|
-
export const NOTICE_THROTTLE = { max: 5, windowMs: 60_000 };
|
|
25
|
-
const throttleByConnector = new Map();
|
|
26
|
-
function passThrottle(connectorId) {
|
|
27
|
-
let t = throttleByConnector.get(connectorId);
|
|
28
|
-
if (!t) {
|
|
29
|
-
t = makeThrottle(NOTICE_THROTTLE.max, NOTICE_THROTTLE.windowMs);
|
|
30
|
-
throttleByConnector.set(connectorId, t);
|
|
31
|
-
}
|
|
32
|
-
return t;
|
|
33
|
-
}
|
|
34
28
|
let cachedBase;
|
|
35
29
|
let cachedBaseAgentId;
|
|
36
30
|
function cachedAgentBase() {
|
|
@@ -71,40 +65,90 @@ function cachedDomainAreas() {
|
|
|
71
65
|
cachedDomain = buildDomainAreas();
|
|
72
66
|
return cachedDomain;
|
|
73
67
|
}
|
|
74
|
-
function foldContextFacts(messages) {
|
|
68
|
+
export function foldContextFacts(messages) {
|
|
75
69
|
const acc = new Map();
|
|
76
70
|
for (const m of messages) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
71
|
+
let facts;
|
|
72
|
+
if ('role' in m && m.role === 'context') {
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(m.text);
|
|
75
|
+
if (Array.isArray(parsed))
|
|
76
|
+
facts = parsed;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if ('context' in m && Array.isArray(m.context)) {
|
|
82
|
+
facts = m.context;
|
|
83
|
+
}
|
|
84
|
+
if (facts) {
|
|
85
|
+
for (const fact of facts) {
|
|
86
|
+
if (fact.name === 'cleared')
|
|
87
|
+
acc.delete(fact.value);
|
|
88
|
+
else
|
|
89
|
+
acc.set(fact.name, fact);
|
|
90
|
+
}
|
|
82
91
|
}
|
|
83
92
|
}
|
|
84
93
|
return [...acc.values()];
|
|
85
94
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
export function lastContextAt(messages) {
|
|
96
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
97
|
+
const m = messages[i];
|
|
98
|
+
if ('role' in m && m.role === 'context') {
|
|
99
|
+
try {
|
|
100
|
+
const parsed = JSON.parse(m.text);
|
|
101
|
+
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
102
|
+
return new Date(m.ts * 1000);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if ('context' in m && m.context && m.context.length > 0) {
|
|
109
|
+
return new Date(m.ts * 1000);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
99
113
|
}
|
|
100
|
-
export
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
114
|
+
export function toConvMessages(stored, facts, userMsgId, queryText, contextSource = stored) {
|
|
115
|
+
const contextMap = new Map();
|
|
116
|
+
for (const m of contextSource) {
|
|
117
|
+
if (m.role === 'context') {
|
|
118
|
+
try {
|
|
119
|
+
const parsed = JSON.parse(m.text);
|
|
120
|
+
if (Array.isArray(parsed)) {
|
|
121
|
+
contextMap.set(m.msgId, parsed);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const out = [];
|
|
129
|
+
for (const m of stored) {
|
|
130
|
+
if (m.role !== 'user' && m.role !== 'assistant')
|
|
131
|
+
continue;
|
|
132
|
+
const isCurrentUser = m.msgId === userMsgId;
|
|
133
|
+
const itemContext = isCurrentUser ? (facts.length ? facts : undefined) : contextMap.get(`${m.msgId}~c`);
|
|
134
|
+
out.push({
|
|
135
|
+
role: m.role,
|
|
136
|
+
content: isCurrentUser ? queryText : m.text,
|
|
137
|
+
sender: m.sender,
|
|
138
|
+
...(m.attachments ? { attachments: m.attachments } : {}),
|
|
139
|
+
msgId: m.msgId,
|
|
140
|
+
ts: m.ts,
|
|
141
|
+
...(itemContext?.length ? { context: itemContext } : {}),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
while (out.length && out[0]?.role !== 'user')
|
|
145
|
+
out.shift();
|
|
146
|
+
return out;
|
|
104
147
|
}
|
|
105
|
-
|
|
148
|
+
const storeLookup = async (id) => (await import("../auth-store.js")).findUserById(id);
|
|
149
|
+
function openSink(connector, envelope, msgId) {
|
|
106
150
|
try {
|
|
107
|
-
return connector.open(envelope);
|
|
151
|
+
return connector.open(envelope, msgId);
|
|
108
152
|
}
|
|
109
153
|
catch (err) {
|
|
110
154
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -129,82 +173,78 @@ export async function handleIncoming(connector, turn, deps) {
|
|
|
129
173
|
const selectedAgentId = turn.agentId ?? policy.agentId ?? (deps?.runAgent ? undefined : await loadAgentId());
|
|
130
174
|
const runtime = deps?.runAgent ? undefined : resolveAgent(selectedAgentId);
|
|
131
175
|
const agent = deps?.runAgent ?? runtime.run.bind(runtime);
|
|
132
|
-
const agentMedia = () => deps?.media ?? runtime
|
|
176
|
+
const agentMedia = () => deps?.media ?? runtime?.media ?? DEFAULT_MEDIA;
|
|
133
177
|
const db = deps && 'logDb' in deps ? deps.logDb : logDb;
|
|
134
178
|
const agentBase = deps?.agentBase ?? (() => (selectedAgentId ? runtime.systemBase() : cachedAgentBase()));
|
|
135
|
-
const { connectorId
|
|
179
|
+
const { connectorId } = turn.envelope;
|
|
180
|
+
const conversationId = turn.envelope.conversationId;
|
|
136
181
|
const { sender } = turn;
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
182
|
+
const text = turn.message?.text ?? '';
|
|
183
|
+
const replyToId = turn.message?.replyToMsgId ?? null;
|
|
184
|
+
const rawAttachments = turn.prepareAttachments
|
|
185
|
+
? await safeCall(() => turn.prepareAttachments(attachmentMaterializer), [], 'prepareAttachments')
|
|
186
|
+
: turn.message?.attachments;
|
|
187
|
+
const attachments = Array.isArray(rawAttachments) && rawAttachments.length > 0 && 'role' in rawAttachments[0]
|
|
188
|
+
? rawAttachments.at(-1)?.attachments
|
|
189
|
+
: rawAttachments;
|
|
190
|
+
const userMsgId = newMessageId();
|
|
191
|
+
const now = deps?.now ? deps.now() : new Date();
|
|
192
|
+
const ts = Math.floor(now.getTime() / 1000);
|
|
193
|
+
await putMessage({
|
|
194
|
+
conversationId,
|
|
195
|
+
msgId: userMsgId,
|
|
196
|
+
role: 'user',
|
|
197
|
+
sender: String(sender.userId),
|
|
198
|
+
text,
|
|
199
|
+
...(attachments ? { attachments } : {}),
|
|
200
|
+
ts,
|
|
201
|
+
replyToId,
|
|
202
|
+
});
|
|
203
|
+
if (policy.triggerPrefix && !text.toLowerCase().startsWith(policy.triggerPrefix.toLowerCase()))
|
|
143
204
|
return;
|
|
144
|
-
const speakerRow = await
|
|
145
|
-
if (speakerRow
|
|
146
|
-
if (passThrottle(connectorId)(sender.id)) {
|
|
147
|
-
const sink = openSink(connector, turn.envelope);
|
|
148
|
-
if (sink) {
|
|
149
|
-
sink.emit({ kind: 'notice', text: connector.enrolmentNotice });
|
|
150
|
-
await safeCall(() => sink.done(), undefined, 'done(unidentified)');
|
|
151
|
-
}
|
|
152
|
-
}
|
|
205
|
+
const speakerRow = await (deps?.lookupUser ?? storeLookup)(sender.userId);
|
|
206
|
+
if (!speakerRow || speakerRow.disabled)
|
|
153
207
|
return;
|
|
154
|
-
}
|
|
155
208
|
if (turn.signal?.aborted)
|
|
156
209
|
return;
|
|
157
|
-
const queryText = policy.triggerPrefix ?
|
|
158
|
-
if (!queryText)
|
|
159
|
-
if (turn.prepareAttachments && !policy.triggerPrefix) {
|
|
160
|
-
await safeCall(() => turn.prepareAttachments(attachmentMaterializer), messages, 'prepareAttachments');
|
|
161
|
-
}
|
|
210
|
+
const queryText = policy.triggerPrefix ? text.slice(policy.triggerPrefix.length).trim() : text.trim();
|
|
211
|
+
if (!queryText)
|
|
162
212
|
return;
|
|
163
|
-
}
|
|
164
|
-
const preparedMessages = turn.prepareAttachments
|
|
165
|
-
? await safeCall(() => turn.prepareAttachments(attachmentMaterializer), messages, 'prepareAttachments')
|
|
166
|
-
: messages;
|
|
167
213
|
const speaker = {
|
|
168
|
-
id: sender.
|
|
169
|
-
name: speakerRow.displayName ?? sender.displayName ?? sender.
|
|
214
|
+
id: String(sender.userId),
|
|
215
|
+
name: speakerRow.displayName ?? sender.displayName ?? String(sender.userId),
|
|
170
216
|
role: speakerRow.role,
|
|
171
217
|
};
|
|
172
|
-
const
|
|
173
|
-
const
|
|
174
|
-
const now = deps?.now ? deps.now() : new Date();
|
|
218
|
+
const stored = await readConversation(conversationId, { limit: DEFAULT_MAX_DEPTH });
|
|
219
|
+
const all = await readAll(conversationId);
|
|
175
220
|
const facts = buildContextFacts({
|
|
176
221
|
connectorFacts: turn.turnContext ?? [],
|
|
177
222
|
speaker,
|
|
178
223
|
now,
|
|
179
224
|
timeZone: deps?.timeZone ?? systemTimeZone(),
|
|
180
|
-
previous:
|
|
181
|
-
previousAt:
|
|
225
|
+
previous: foldContextFacts(all),
|
|
226
|
+
previousAt: lastContextAt(all),
|
|
182
227
|
});
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}), undefined, 'recordContext');
|
|
195
|
-
};
|
|
196
|
-
const authored = turn.body.systemPrompt;
|
|
228
|
+
if (facts.length) {
|
|
229
|
+
await putMessage({
|
|
230
|
+
conversationId,
|
|
231
|
+
msgId: `${userMsgId}~c`,
|
|
232
|
+
role: 'context',
|
|
233
|
+
text: JSON.stringify(facts),
|
|
234
|
+
ts: ts - 1,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
const agentMessages = toConvMessages(stored, facts, userMsgId, queryText, all);
|
|
238
|
+
const authored = turn.systemPrompt ?? {};
|
|
197
239
|
const domain = await (deps?.domainAreas ?? cachedDomainAreas)();
|
|
198
240
|
const areas = mergeAreas({ root: [await agentBase()] }, domain, authored);
|
|
199
241
|
const system = assembleSystem(areas, deps?.world ?? (await liveWorld()));
|
|
200
|
-
if (turn.signal?.aborted)
|
|
201
|
-
await persistContext();
|
|
242
|
+
if (turn.signal?.aborted)
|
|
202
243
|
return;
|
|
203
|
-
}
|
|
204
244
|
db?.logTurn({
|
|
205
245
|
connector: connectorId,
|
|
206
|
-
chatId,
|
|
207
|
-
userId: sender.
|
|
246
|
+
chatId: conversationId,
|
|
247
|
+
userId: String(sender.userId),
|
|
208
248
|
role: 'user',
|
|
209
249
|
text: queryText,
|
|
210
250
|
tokensIn: null,
|
|
@@ -214,11 +254,13 @@ export async function handleIncoming(connector, turn, deps) {
|
|
|
214
254
|
presetId: turn.presetId ?? null,
|
|
215
255
|
});
|
|
216
256
|
const startedAt = Date.now();
|
|
217
|
-
const
|
|
257
|
+
const botMsgId = newMessageId();
|
|
258
|
+
const sink = openSink(connector, turn.envelope, botMsgId);
|
|
218
259
|
if (!sink)
|
|
219
260
|
return;
|
|
220
261
|
let usage;
|
|
221
262
|
let answerText = null;
|
|
263
|
+
let reasoningText = null;
|
|
222
264
|
let runFailed = false;
|
|
223
265
|
let agentTurn;
|
|
224
266
|
try {
|
|
@@ -226,9 +268,10 @@ export async function handleIncoming(connector, turn, deps) {
|
|
|
226
268
|
const toolProvider = {
|
|
227
269
|
tools: [...attachmentTools.tools, ...makeSystemCapabilities().tools],
|
|
228
270
|
};
|
|
271
|
+
const userMessageCount = await countUserMessages(conversationId);
|
|
229
272
|
agentTurn = {
|
|
230
273
|
envelope: turn.envelope,
|
|
231
|
-
body: { system, messages: agentMessages,
|
|
274
|
+
body: { system, messages: agentMessages, userMessageCount },
|
|
232
275
|
toolProvider,
|
|
233
276
|
...(turn.presetId ? { presetId: turn.presetId } : {}),
|
|
234
277
|
senderRole: speaker.role,
|
|
@@ -239,8 +282,14 @@ export async function handleIncoming(connector, turn, deps) {
|
|
|
239
282
|
if (event.kind === 'usage') {
|
|
240
283
|
usage = { tokensIn: event.tokensIn, tokensOut: event.tokensOut, presetId: event.presetId };
|
|
241
284
|
}
|
|
285
|
+
else if (event.kind === 'reasoning') {
|
|
286
|
+
reasoningText = event.text;
|
|
287
|
+
}
|
|
242
288
|
else if (event.kind === 'answer') {
|
|
243
289
|
answerText = event.text;
|
|
290
|
+
if (event.reasoning !== null) {
|
|
291
|
+
reasoningText = event.reasoning;
|
|
292
|
+
}
|
|
244
293
|
}
|
|
245
294
|
sink.emit(event);
|
|
246
295
|
}
|
|
@@ -262,8 +311,31 @@ export async function handleIncoming(connector, turn, deps) {
|
|
|
262
311
|
}
|
|
263
312
|
}
|
|
264
313
|
await safeCall(() => sink.done(), undefined, 'done');
|
|
314
|
+
const capabilities = turn.capabilities ?? { events: [], privateChats: false };
|
|
315
|
+
const botTs = Math.max(ts + 2, Math.floor(Date.now() / 1000));
|
|
316
|
+
if (answerText !== null) {
|
|
317
|
+
await putMessage({
|
|
318
|
+
conversationId,
|
|
319
|
+
msgId: botMsgId,
|
|
320
|
+
role: 'assistant',
|
|
321
|
+
sender: null,
|
|
322
|
+
text: answerText,
|
|
323
|
+
ts: botTs,
|
|
324
|
+
replyToId: userMsgId,
|
|
325
|
+
});
|
|
326
|
+
if (reasoningText && capabilities.events.includes('reasoning')) {
|
|
327
|
+
await putMessage({
|
|
328
|
+
conversationId,
|
|
329
|
+
msgId: `${botMsgId}~r`,
|
|
330
|
+
role: 'reasoning',
|
|
331
|
+
text: reasoningText,
|
|
332
|
+
ts: botTs - 1,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
let extraSuggestions;
|
|
265
337
|
if (!runFailed && answerText && agentTurn) {
|
|
266
|
-
const events =
|
|
338
|
+
const events = capabilities.events;
|
|
267
339
|
const want = { suggestions: events.includes('suggestions'), title: events.includes('title') };
|
|
268
340
|
const afterwordFn = deps?.afterword ?? runtime?.afterword;
|
|
269
341
|
if ((want.suggestions || want.title) && afterwordFn) {
|
|
@@ -276,6 +348,7 @@ export async function handleIncoming(connector, turn, deps) {
|
|
|
276
348
|
recordDiagnostic('error', 'agent.afterword', message);
|
|
277
349
|
}
|
|
278
350
|
if (extra?.suggestions?.length) {
|
|
351
|
+
extraSuggestions = extra.suggestions;
|
|
279
352
|
try {
|
|
280
353
|
sink.emit({ kind: 'suggestions', items: extra.suggestions });
|
|
281
354
|
}
|
|
@@ -301,11 +374,20 @@ export async function handleIncoming(connector, turn, deps) {
|
|
|
301
374
|
}
|
|
302
375
|
}
|
|
303
376
|
await safeCall(() => sink.done(), undefined, 'done');
|
|
377
|
+
if (extraSuggestions?.length && capabilities.events.includes('suggestions')) {
|
|
378
|
+
await putMessage({
|
|
379
|
+
conversationId,
|
|
380
|
+
msgId: `${botMsgId}~s`,
|
|
381
|
+
role: 'suggestions',
|
|
382
|
+
text: JSON.stringify(extraSuggestions),
|
|
383
|
+
ts: botTs - 1,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
304
386
|
if (!runFailed && usage && answerText)
|
|
305
387
|
db?.logTurn({
|
|
306
388
|
connector: connectorId,
|
|
307
|
-
chatId,
|
|
308
|
-
userId: sender.
|
|
389
|
+
chatId: conversationId,
|
|
390
|
+
userId: String(sender.userId),
|
|
309
391
|
role: 'assistant',
|
|
310
392
|
text: answerText,
|
|
311
393
|
tokensIn: usage.tokensIn,
|
|
@@ -314,5 +396,4 @@ export async function handleIncoming(connector, turn, deps) {
|
|
|
314
396
|
agentId: selectedAgentId ?? null,
|
|
315
397
|
presetId: usage.presetId,
|
|
316
398
|
});
|
|
317
|
-
await persistContext();
|
|
318
399
|
}
|
|
@@ -8,8 +8,6 @@ export declare function registerConnector(registration: ConnectorRegistration):
|
|
|
8
8
|
export declare function isConnectorRegistered(id: string): boolean;
|
|
9
9
|
export declare function listRegisteredAgents(): string[];
|
|
10
10
|
export declare function listRegisteredConnectors(): string[];
|
|
11
|
-
export declare function listLinkableConnectors(): string[];
|
|
12
|
-
export declare function connectorLinkUrl(connectorId: string, code: string): string | undefined;
|
|
13
11
|
export declare function listAgentCatalog(): Promise<AgentCatalogEntry[]>;
|
|
14
12
|
export declare function getDefaultAgentId(): string | undefined;
|
|
15
13
|
export declare function clearRuntimeRegistries(): void;
|
|
@@ -48,15 +48,6 @@ export function listRegisteredAgents() {
|
|
|
48
48
|
export function listRegisteredConnectors() {
|
|
49
49
|
return [...connectors.keys()].sort();
|
|
50
50
|
}
|
|
51
|
-
export function listLinkableConnectors() {
|
|
52
|
-
return [...connectors.entries()]
|
|
53
|
-
.filter(([, registration]) => typeof registration.linkUrl === 'function')
|
|
54
|
-
.map(([id]) => id)
|
|
55
|
-
.sort();
|
|
56
|
-
}
|
|
57
|
-
export function connectorLinkUrl(connectorId, code) {
|
|
58
|
-
return connectors.get(connectorId)?.linkUrl?.(code);
|
|
59
|
-
}
|
|
60
51
|
export async function listAgentCatalog() {
|
|
61
52
|
const ids = [...agents.keys()].sort();
|
|
62
53
|
const out = [];
|