@segmentstream/ontology 0.92.1 → 0.93.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/README.md +38 -1
- package/dist/analyze.d.ts +16 -1
- package/dist/analyze.js +52 -33
- package/dist/manifest.d.ts +87 -0
- package/dist/manifest.js +9 -0
- package/dist/migrate/model.d.ts +48 -0
- package/dist/runtime-source.d.ts +10 -2
- package/dist/runtime-source.js +18 -2
- package/dist/service/agent/attachments.js +12 -8
- package/dist/service/chat/delivery.d.ts +7 -0
- package/dist/service/chat/delivery.js +47 -25
- package/dist/service/chat/envelope.d.ts +26 -2
- package/dist/service/chat/providers/index.d.ts +22 -0
- package/dist/service/chat/providers/index.js +31 -0
- package/dist/service/chat/providers/provider.d.ts +61 -0
- package/dist/service/chat/providers/provider.js +28 -0
- package/dist/service/chat/providers/slack.d.ts +10 -0
- package/dist/service/chat/providers/slack.js +93 -0
- package/dist/service/chat/providers/telegram.d.ts +4 -12
- package/dist/service/chat/providers/telegram.js +29 -21
- package/dist/service/chat/providers/whatsapp.d.ts +14 -0
- package/dist/service/chat/providers/whatsapp.js +91 -0
- package/dist/service/chat/reads.d.ts +6 -1
- package/dist/service/chat/reads.js +9 -3
- package/dist/service/chat/resolve.d.ts +5 -1
- package/dist/service/chat/resolve.js +26 -19
- package/dist/service/chat/routes.js +6 -1
- package/dist/service/mcp/server.js +6 -1
- package/dist/service/mcp/tools.js +6 -1
- package/dist/service/schemas.d.ts +12 -0
- package/dist/slack.d.ts +15 -0
- package/dist/slack.js +21 -0
- package/dist/whatsapp.d.ts +14 -0
- package/dist/whatsapp.js +29 -0
- package/package.json +9 -1
|
@@ -5,7 +5,12 @@ import { CurrentPrincipal } from "../authentication.js";
|
|
|
5
5
|
import type { OntologyBackend } from "../runtime-contract.js";
|
|
6
6
|
import type { ConversationTransport } from "../agent/conversations.js";
|
|
7
7
|
import type { ChatList, ChatHistory } from "./schemas.js";
|
|
8
|
-
export declare function chatReads(backend: OntologyBackend["Service"],
|
|
8
|
+
export declare function chatReads(backend: OntologyBackend["Service"], options: {
|
|
9
|
+
/** The providers whose chats are listed, as `listedChatProviders` gives them. */
|
|
10
|
+
readonly providers: ReadonlyArray<string>;
|
|
11
|
+
readonly conversations?: ConversationTransport;
|
|
12
|
+
readonly attribution?: ChatAttributionSettings;
|
|
13
|
+
}): {
|
|
9
14
|
list: (input: typeof ChatList.Type) => Effect.Effect<{
|
|
10
15
|
items: {
|
|
11
16
|
id: string;
|
|
@@ -5,13 +5,16 @@ import { CurrentPrincipal } from "../authentication.js";
|
|
|
5
5
|
import { currentCaller } from "../caller-context.js";
|
|
6
6
|
import { chatAttribution } from "./attribution.js";
|
|
7
7
|
import { chatResolver, groupConversation, isChatMember } from "./resolve.js";
|
|
8
|
-
|
|
8
|
+
/** The providers whose archived conversations are keyed by the chat a destination names. */
|
|
9
|
+
const HISTORY_PROVIDERS = ["telegram"];
|
|
10
|
+
export function chatReads(backend, options) {
|
|
11
|
+
const { providers, conversations, attribution } = options;
|
|
9
12
|
return {
|
|
10
13
|
list: (input) => Effect.gen(function* () {
|
|
11
14
|
const principal = yield* CurrentPrincipal;
|
|
12
15
|
const caller = yield* currentCaller;
|
|
13
16
|
const query = input.query?.toLowerCase();
|
|
14
|
-
const items = (yield* chatResolver(backend, principal).list(input.chat))
|
|
17
|
+
const items = (yield* chatResolver(backend, principal, providers).list(input.chat))
|
|
15
18
|
.filter((item) => query === undefined || item.title.toLowerCase().includes(query))
|
|
16
19
|
.sort((a, b) => a.id.localeCompare(b.id));
|
|
17
20
|
const user = yield* backend.get({ entity: "user", id: principal.id });
|
|
@@ -39,7 +42,10 @@ export function chatReads(backend, conversations, attribution) {
|
|
|
39
42
|
if (principal.kind !== "member") {
|
|
40
43
|
return yield* new PermissionDenied({ message: "Only members may read conversations." });
|
|
41
44
|
}
|
|
42
|
-
|
|
45
|
+
// History reads Telegram chats only. Other channels archive a conversation under their
|
|
46
|
+
// session's address with private coordinates, not under the chat a destination names, so
|
|
47
|
+
// their history would be incomplete until the archive keys them the same way.
|
|
48
|
+
const target = yield* chatResolver(backend, principal, HISTORY_PROVIDERS, {
|
|
43
49
|
memberGroups: true,
|
|
44
50
|
}).resolve(input.chat);
|
|
45
51
|
// A group's members read its history, as they read its attachments; agent_chat:read reads
|
|
@@ -25,7 +25,11 @@ export declare function isChatMember(backend: OntologyBackend["Service"], princi
|
|
|
25
25
|
* reads those chats' attachments: being in a chat is enough, `agent_chat:read` reads them all.
|
|
26
26
|
*/
|
|
27
27
|
export declare function withChatMembership(transport: ConversationTransport, backend: OntologyBackend["Service"]): ConversationTransport;
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Resolves the chats of `providers`: their group chats in the registry, and each member's private
|
|
30
|
+
* chat on each provider they have a live identity on, `<provider>:private:<member id>`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function chatResolver(backend: OntologyBackend["Service"], principal: Principal, providers: ReadonlyArray<string>, options?: {
|
|
29
33
|
/** Also resolve the group chats a member is in without chat read or post permission. */
|
|
30
34
|
readonly memberGroups?: boolean;
|
|
31
35
|
}): {
|
|
@@ -65,7 +65,11 @@ export function withChatMembership(transport, backend) {
|
|
|
65
65
|
cause,
|
|
66
66
|
})), Effect.flatMap((memberGroups) => transport({ ...input, memberGroups })));
|
|
67
67
|
}
|
|
68
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Resolves the chats of `providers`: their group chats in the registry, and each member's private
|
|
70
|
+
* chat on each provider they have a live identity on, `<provider>:private:<member id>`.
|
|
71
|
+
*/
|
|
72
|
+
export function chatResolver(backend, principal, providers, options = {}) {
|
|
69
73
|
const canGroup = isAllowed("agent_chat:read", principal) || isAllowed("agent_chat:post_message", principal);
|
|
70
74
|
const memberGroups = options.memberGroups === true && principal.kind === "member";
|
|
71
75
|
const canDirectory = isAllowed("user:read", principal) || isAllowed("agent_chat:post_message", principal);
|
|
@@ -73,21 +77,22 @@ export function chatResolver(backend, principal, provider = "telegram", options
|
|
|
73
77
|
if (!canGroup && principal.kind !== "member") {
|
|
74
78
|
return yield* denied();
|
|
75
79
|
}
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
: undefined;
|
|
80
|
+
const named = providers.find((provider) => chat?.startsWith(`${provider}:private:`));
|
|
81
|
+
const privateId = named === undefined ? undefined : chat.slice(`${named}:private:`.length);
|
|
79
82
|
const groupId = chat !== undefined && UUID.test(chat);
|
|
80
83
|
const filters = [
|
|
84
|
+
{ provider: { in: [...providers] } },
|
|
81
85
|
...(chat === undefined ? [] : [groupId ? { id: { eq: chat } } : { title: { eq: chat } }]),
|
|
82
86
|
...(canGroup ? [] : [memberOf(principal)]),
|
|
83
87
|
];
|
|
84
|
-
const groups = (!canGroup && !memberGroups) || privateId !== undefined
|
|
88
|
+
const groups = (!canGroup && !memberGroups) || privateId !== undefined || providers.length === 0
|
|
85
89
|
? []
|
|
86
|
-
: yield* records(backend, "agent_chat",
|
|
87
|
-
? {}
|
|
88
|
-
|
|
90
|
+
: yield* records(backend, "agent_chat", {
|
|
91
|
+
where: filters.length === 1 ? filters[0] : { and: filters },
|
|
92
|
+
});
|
|
89
93
|
const result = groups
|
|
90
|
-
.filter((row) =>
|
|
94
|
+
.filter((row) => providers.includes(String(row.provider)) &&
|
|
95
|
+
(chat === undefined || row.id === chat || row.title === chat))
|
|
91
96
|
.map((row) => ({
|
|
92
97
|
id: String(row.id),
|
|
93
98
|
title: String(row.title),
|
|
@@ -119,16 +124,18 @@ export function chatResolver(backend, principal, provider = "telegram", options
|
|
|
119
124
|
continue;
|
|
120
125
|
}
|
|
121
126
|
const identities = yield* backend.agents.listIdentities(member.principal.id);
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
127
|
+
for (const provider of named === undefined ? providers : [named]) {
|
|
128
|
+
const identity = identities.find((item) => item.provider === provider && item.revokedAt === null);
|
|
129
|
+
if (identity !== undefined) {
|
|
130
|
+
result.push({
|
|
131
|
+
id: `${provider}:private:${member.principal.id}`,
|
|
132
|
+
title: member.name,
|
|
133
|
+
provider,
|
|
134
|
+
external_id: identity.externalId,
|
|
135
|
+
kind: "private",
|
|
136
|
+
participantIds: [member.principal.id],
|
|
137
|
+
});
|
|
138
|
+
}
|
|
132
139
|
}
|
|
133
140
|
}
|
|
134
141
|
return result;
|
|
@@ -8,6 +8,7 @@ import { RequestId, failureResponse } from "../errors.js";
|
|
|
8
8
|
import { OntologyBackend } from "../runtime-contract.js";
|
|
9
9
|
import { identityToken, makeOperations } from "../operations.js";
|
|
10
10
|
import { ConversationSearch } from "../agent/conversations.js";
|
|
11
|
+
import { listedChatProviders } from "./providers/index.js";
|
|
11
12
|
import { chatReads } from "./reads.js";
|
|
12
13
|
import { withChatMembership } from "./resolve.js";
|
|
13
14
|
import { ChatList, ChatHistory } from "./schemas.js";
|
|
@@ -16,7 +17,11 @@ export function chatRoutes(options) {
|
|
|
16
17
|
const router = yield* HttpRouter.HttpRouter;
|
|
17
18
|
const backend = yield* OntologyBackend;
|
|
18
19
|
const conversations = options.conversations && withChatMembership(options.conversations, backend);
|
|
19
|
-
const reads = chatReads(backend,
|
|
20
|
+
const reads = chatReads(backend, {
|
|
21
|
+
providers: listedChatProviders(options.built.plan.ontology.manifest.chat),
|
|
22
|
+
conversations,
|
|
23
|
+
attribution: options.built.plan.ontology.config.chat?.attribution,
|
|
24
|
+
});
|
|
20
25
|
const operations = makeOperations(backend, options.built.plan, options.hooks, options.connections, options.replicate, options.chatDelivery);
|
|
21
26
|
const planned = options.built.plan.entities.find((entity) => entity.name === "agent_chat");
|
|
22
27
|
const action = planned.actions.find((candidate) => candidate.name === "post_message");
|
|
@@ -14,6 +14,7 @@ import { CONFIRMATION_PREVIEW_METHOD, previewLocale, withoutConfirmation } from
|
|
|
14
14
|
import { consumeChatEnvelope, CurrentChatCall } from "../chat/envelope.js";
|
|
15
15
|
import { ChatList, ChatHistory, ChatPostWire, RuntimeChatPostWire } from "../chat/schemas.js";
|
|
16
16
|
import { ChatPostResult } from "../../chat.js";
|
|
17
|
+
import { listedChatProviders } from "../chat/providers/index.js";
|
|
17
18
|
import { chatReads } from "../chat/reads.js";
|
|
18
19
|
import { withChatMembership } from "../chat/resolve.js";
|
|
19
20
|
import { catalogFor } from "../catalog.js";
|
|
@@ -831,7 +832,11 @@ export function mcpLayer(options) {
|
|
|
831
832
|
const operations = makeOperations(backend, options.built.plan, options.hooks, options.connections, options.replicate, options.chatDelivery);
|
|
832
833
|
const conversations = options.conversations && withChatMembership(options.conversations, backend);
|
|
833
834
|
const server = {
|
|
834
|
-
chats: chatReads(backend,
|
|
835
|
+
chats: chatReads(backend, {
|
|
836
|
+
providers: listedChatProviders(options.built.plan.ontology.manifest.chat),
|
|
837
|
+
conversations,
|
|
838
|
+
attribution: options.built.plan.ontology.config.chat?.attribution,
|
|
839
|
+
}),
|
|
835
840
|
built: options.built,
|
|
836
841
|
tools,
|
|
837
842
|
discovery: createDiscovery(options.built, tools),
|
|
@@ -9,6 +9,7 @@ import { actionInputJsonSchema } from "../../runtime/generated-actions.js";
|
|
|
9
9
|
import { uploadFields } from "../../storage/attachments.js";
|
|
10
10
|
import { MEMBER_FILE_SCHEMAS } from "../member-files.js";
|
|
11
11
|
import { ConversationSearch } from "../agent/conversations.js";
|
|
12
|
+
import { declaredChatProviders } from "../chat/providers/index.js";
|
|
12
13
|
import { runtimeAllows } from "./runtime-policy.js";
|
|
13
14
|
import { confirmationFacts, needsConfirmation, withConfirmation, } from "./confirmation.js";
|
|
14
15
|
import { ChatList, ChatListResult, ChatHistory, ChatPostWire, RuntimeChatPostWire, } from "../chat/schemas.js";
|
|
@@ -735,7 +736,11 @@ export function mcpTools(built, conversations = false, storage = {}, navigation
|
|
|
735
736
|
...original,
|
|
736
737
|
inputSchema: encodedJsonSchema(ChatPostWire),
|
|
737
738
|
outputSchema: encodedJsonSchema(ChatPostResult),
|
|
738
|
-
|
|
739
|
+
// The same protocol for every provider: an agent that cannot post to one refuses its
|
|
740
|
+
// destinations when it describes the post.
|
|
741
|
+
...(declaredChatProviders(manifest.chat).length === 0
|
|
742
|
+
? {}
|
|
743
|
+
: { _meta: { [CHAT_POST_META]: 1 } }),
|
|
739
744
|
annotations: {
|
|
740
745
|
readOnlyHint: false,
|
|
741
746
|
destructiveHint: false,
|
|
@@ -452,6 +452,12 @@ export declare const Catalog: Schema.decodeTo<Schema.Struct<{
|
|
|
452
452
|
readonly telegram: Schema.optionalKey<Schema.Struct<{
|
|
453
453
|
readonly connection: Schema.String;
|
|
454
454
|
}>>;
|
|
455
|
+
readonly slack: Schema.optionalKey<Schema.Struct<{
|
|
456
|
+
readonly connection: Schema.String;
|
|
457
|
+
}>>;
|
|
458
|
+
readonly whatsapp: Schema.optionalKey<Schema.Struct<{
|
|
459
|
+
readonly connection: Schema.String;
|
|
460
|
+
}>>;
|
|
455
461
|
readonly attribution: Schema.optionalKey<Schema.Struct<{
|
|
456
462
|
readonly template: Schema.optionalKey<Schema.String>;
|
|
457
463
|
readonly separator: Schema.optionalKey<Schema.String>;
|
|
@@ -681,6 +687,12 @@ export declare const Catalog: Schema.decodeTo<Schema.Struct<{
|
|
|
681
687
|
readonly telegram: Schema.optionalKey<Schema.Struct<{
|
|
682
688
|
readonly connection: Schema.String;
|
|
683
689
|
}>>;
|
|
690
|
+
readonly slack: Schema.optionalKey<Schema.Struct<{
|
|
691
|
+
readonly connection: Schema.String;
|
|
692
|
+
}>>;
|
|
693
|
+
readonly whatsapp: Schema.optionalKey<Schema.Struct<{
|
|
694
|
+
readonly connection: Schema.String;
|
|
695
|
+
}>>;
|
|
684
696
|
readonly attribution: Schema.optionalKey<Schema.Struct<{
|
|
685
697
|
readonly template: Schema.optionalKey<Schema.String>;
|
|
686
698
|
readonly separator: Schema.optionalKey<Schema.String>;
|
package/dist/slack.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack's text for an exact chat post. The ontology service sends it and the agent's
|
|
3
|
+
* confirmation computes it from the same function, so the digest the member approves and the
|
|
4
|
+
* text delivered are the same.
|
|
5
|
+
*/
|
|
6
|
+
/** The most characters Slack accepts in `chat.postMessage`'s `markdown_text`. */
|
|
7
|
+
export declare const SLACK_MESSAGE_LIMIT = 12000;
|
|
8
|
+
/** Text that Slack's Markdown shows as it is: every character it could read as syntax escaped. */
|
|
9
|
+
export declare function escapeSlackMarkdown(text: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* The member's exact Markdown, which Slack renders, then the separator and the attribution as
|
|
12
|
+
* literal text, the attribution in italics. Neither can open or close a construct of the member's
|
|
13
|
+
* Markdown.
|
|
14
|
+
*/
|
|
15
|
+
export declare function renderSlack(markdown: string, attribution: string, separator?: string): string;
|
package/dist/slack.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack's text for an exact chat post. The ontology service sends it and the agent's
|
|
3
|
+
* confirmation computes it from the same function, so the digest the member approves and the
|
|
4
|
+
* text delivered are the same.
|
|
5
|
+
*/
|
|
6
|
+
/** The most characters Slack accepts in `chat.postMessage`'s `markdown_text`. */
|
|
7
|
+
export const SLACK_MESSAGE_LIMIT = 12_000;
|
|
8
|
+
/** The ASCII punctuation Markdown can read as syntax, which a backslash makes literal. */
|
|
9
|
+
const MARKDOWN_SYNTAX = /[\\`*_~[\](){}<>#+\-.!|&]/gu;
|
|
10
|
+
/** Text that Slack's Markdown shows as it is: every character it could read as syntax escaped. */
|
|
11
|
+
export function escapeSlackMarkdown(text) {
|
|
12
|
+
return text.replace(MARKDOWN_SYNTAX, "\\$&");
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The member's exact Markdown, which Slack renders, then the separator and the attribution as
|
|
16
|
+
* literal text, the attribution in italics. Neither can open or close a construct of the member's
|
|
17
|
+
* Markdown.
|
|
18
|
+
*/
|
|
19
|
+
export function renderSlack(markdown, attribution, separator = "\n\n") {
|
|
20
|
+
return `${markdown}${escapeSlackMarkdown(separator)}_${escapeSlackMarkdown(attribution)}_`;
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WhatsApp's text for an exact chat post. The ontology service sends it and the agent's
|
|
3
|
+
* confirmation computes it from the same function, so the digest the member approves and the
|
|
4
|
+
* text delivered are the same.
|
|
5
|
+
*/
|
|
6
|
+
/** The most characters WhatsApp accepts in a text message's `body`. */
|
|
7
|
+
export declare const WHATSAPP_MESSAGE_LIMIT = 4096;
|
|
8
|
+
/** Text that WhatsApp shows as it is: every formatting marker replaced by its look-alike. */
|
|
9
|
+
export declare function literalWhatsAppText(text: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* The member's text as written, then the separator and the attribution as literal text, the
|
|
12
|
+
* attribution in italics. Neither can open or close the member's formatting.
|
|
13
|
+
*/
|
|
14
|
+
export declare function renderWhatsApp(text: string, attribution: string, separator?: string): string;
|
package/dist/whatsapp.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WhatsApp's text for an exact chat post. The ontology service sends it and the agent's
|
|
3
|
+
* confirmation computes it from the same function, so the digest the member approves and the
|
|
4
|
+
* text delivered are the same.
|
|
5
|
+
*/
|
|
6
|
+
/** The most characters WhatsApp accepts in a text message's `body`. */
|
|
7
|
+
export const WHATSAPP_MESSAGE_LIMIT = 4096;
|
|
8
|
+
/**
|
|
9
|
+
* WhatsApp's formatting markers (`*bold*`, `_italic_`, `~strikethrough~`, `` ` `` code) and
|
|
10
|
+
* the look-alike each becomes. WhatsApp has no escape character, so a marker in text the
|
|
11
|
+
* platform adds is replaced by a character WhatsApp never reads as formatting.
|
|
12
|
+
*/
|
|
13
|
+
const MARKERS = {
|
|
14
|
+
"*": "∗",
|
|
15
|
+
_: "_",
|
|
16
|
+
"~": "∼",
|
|
17
|
+
"`": "ˋ",
|
|
18
|
+
};
|
|
19
|
+
/** Text that WhatsApp shows as it is: every formatting marker replaced by its look-alike. */
|
|
20
|
+
export function literalWhatsAppText(text) {
|
|
21
|
+
return text.replace(/[*_~`]/gu, (marker) => MARKERS[marker]);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The member's text as written, then the separator and the attribution as literal text, the
|
|
25
|
+
* attribution in italics. Neither can open or close the member's formatting.
|
|
26
|
+
*/
|
|
27
|
+
export function renderWhatsApp(text, attribution, separator = "\n\n") {
|
|
28
|
+
return `${text}${literalWhatsAppText(separator)}_${literalWhatsAppText(attribution)}_`;
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@segmentstream/ontology",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.93.0",
|
|
4
4
|
"description": "Define a SegmentStream workspace app's ontology: entities, relationships, roles and actions, written with Effect.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "SegmentStream",
|
|
@@ -29,6 +29,14 @@
|
|
|
29
29
|
"types": "./dist/telegram.d.ts",
|
|
30
30
|
"import": "./dist/telegram.js"
|
|
31
31
|
},
|
|
32
|
+
"./slack": {
|
|
33
|
+
"types": "./dist/slack.d.ts",
|
|
34
|
+
"import": "./dist/slack.js"
|
|
35
|
+
},
|
|
36
|
+
"./whatsapp": {
|
|
37
|
+
"types": "./dist/whatsapp.d.ts",
|
|
38
|
+
"import": "./dist/whatsapp.js"
|
|
39
|
+
},
|
|
32
40
|
"./channel-session": {
|
|
33
41
|
"types": "./dist/channel-session.d.ts",
|
|
34
42
|
"import": "./dist/channel-session.js"
|