@dxos/plugin-thread 0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264 → 0.13.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/PLUGIN.mdl +122 -1
- package/dist/lib/ChannelBackend.mjs +46 -3
- package/dist/lib/ChannelBackend.mjs.map +1 -1
- package/dist/lib/ThreadCapabilities.mjs.map +1 -1
- package/dist/lib/ThreadOperation.mjs +101 -4
- package/dist/lib/ThreadOperation.mjs.map +1 -1
- package/dist/lib/ThreadOperationHandlerSet.mjs +19 -0
- package/dist/lib/ThreadOperationHandlerSet.mjs.map +1 -0
- package/dist/lib/capabilities.browser.mjs +36 -0
- package/dist/lib/capabilities.browser.mjs.map +1 -0
- package/dist/lib/capabilities.mjs +8 -5
- package/dist/lib/capabilities.mjs.map +1 -1
- package/dist/lib/capabilities.node.mjs +6 -1
- package/dist/lib/capabilities.node.mjs.map +1 -1
- package/dist/lib/capabilities.tauri.mjs +36 -0
- package/dist/lib/capabilities.tauri.mjs.map +1 -0
- package/dist/lib/capabilities.workerd.mjs +6 -1
- package/dist/lib/capabilities.workerd.mjs.map +1 -1
- package/dist/lib/chunk-ChannelArticle.mjs +16 -19
- package/dist/lib/chunk-ChannelArticle.mjs.map +1 -1
- package/dist/lib/chunk-ChannelCreatePanel.mjs +5 -4
- package/dist/lib/chunk-ChannelCreatePanel.mjs.map +1 -1
- package/dist/lib/chunk-PLUGIN.mjs +6 -0
- package/dist/lib/chunk-PLUGIN.mjs.map +1 -0
- package/dist/lib/chunk-ThreadArticle.mjs +13 -8
- package/dist/lib/chunk-ThreadArticle.mjs.map +1 -1
- package/dist/lib/chunk-app-graph-builder.mjs +7 -4
- package/dist/lib/chunk-app-graph-builder.mjs.map +1 -1
- package/dist/lib/chunk-append-channel-message.mjs +3 -14
- package/dist/lib/chunk-append-channel-message.mjs.map +1 -1
- package/dist/lib/chunk-channel-backend-feed.mjs +8 -18
- package/dist/lib/chunk-channel-backend-feed.mjs.map +1 -1
- package/dist/lib/chunk-connect-channel.mjs +14 -0
- package/dist/lib/chunk-connect-channel.mjs.map +1 -0
- package/dist/lib/chunk-create-channel.mjs +1 -1
- package/dist/lib/chunk-create-object.mjs +1 -1
- package/dist/lib/chunk-disconnect-channel.mjs +14 -0
- package/dist/lib/chunk-disconnect-channel.mjs.map +1 -0
- package/dist/lib/chunk-get-channel-status.mjs +14 -0
- package/dist/lib/chunk-get-channel-status.mjs.map +1 -0
- package/dist/lib/chunk-open-direct.mjs +15 -0
- package/dist/lib/chunk-open-direct.mjs.map +1 -0
- package/dist/lib/chunk-operation-handler.mjs +2 -2
- package/dist/lib/chunk-operation-handler.mjs.map +1 -1
- package/dist/lib/chunk-react-surface.mjs +2 -2
- package/dist/lib/chunk-react-surface.mjs.map +1 -1
- package/dist/lib/chunk-send-to-channel.mjs +28 -0
- package/dist/lib/chunk-send-to-channel.mjs.map +1 -0
- package/dist/lib/components.mjs +9 -7
- package/dist/lib/components.mjs.map +1 -1
- package/dist/lib/containers.mjs.map +1 -1
- package/dist/lib/hooks.mjs +4 -3
- package/dist/lib/hooks.mjs.map +1 -1
- package/dist/lib/index.mjs +2 -4
- package/dist/lib/meta.mjs +1 -0
- package/dist/lib/meta.mjs.map +1 -1
- package/dist/lib/operations.mjs +2 -9
- package/dist/lib/translations.mjs +1 -1
- package/dist/lib/translations.mjs.map +1 -1
- package/dist/types/src/capabilities/channel-backend-feed.d.ts.map +1 -1
- package/dist/types/src/capabilities/gen/browser.d.ts +10 -0
- package/dist/types/src/capabilities/gen/browser.d.ts.map +1 -0
- package/dist/types/src/capabilities/gen/node.d.ts.map +1 -1
- package/dist/types/src/capabilities/gen/tauri.d.ts +10 -0
- package/dist/types/src/capabilities/gen/tauri.d.ts.map +1 -0
- package/dist/types/src/capabilities/gen/workerd.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/components/MessageThread/MessageThread.d.ts +2 -2
- package/dist/types/src/components/MessageThread/MessageThread.d.ts.map +1 -1
- package/dist/types/src/components/MessageThread/index.d.ts +1 -1
- package/dist/types/src/components/MessageThread/index.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/containers/ChannelArticle/ChannelArticle.d.ts +1 -1
- package/dist/types/src/containers/ChannelArticle/ChannelArticle.d.ts.map +1 -1
- package/dist/types/src/containers/ChannelArticle/ChannelArticle.stories.d.ts +1 -1
- package/dist/types/src/containers/ChannelArticle/ChannelArticle.stories.d.ts.map +1 -1
- package/dist/types/src/containers/ChannelArticle/index.d.ts +1 -1
- package/dist/types/src/containers/ChannelArticle/index.d.ts.map +1 -1
- package/dist/types/src/containers/ChannelCreatePanel/ChannelCreatePanel.d.ts +1 -1
- package/dist/types/src/containers/ChannelCreatePanel/ChannelCreatePanel.d.ts.map +1 -1
- package/dist/types/src/containers/ChannelCreatePanel/index.d.ts +1 -1
- package/dist/types/src/containers/ChannelCreatePanel/index.d.ts.map +1 -1
- package/dist/types/src/containers/ThreadArticle/ThreadArticle.d.ts +2 -2
- package/dist/types/src/containers/ThreadArticle/ThreadArticle.d.ts.map +1 -1
- package/dist/types/src/containers/ThreadArticle/index.d.ts +1 -1
- package/dist/types/src/containers/ThreadArticle/index.d.ts.map +1 -1
- package/dist/types/src/containers/index.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -2
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useStatus.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +2 -4
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/operations/ThreadOperationHandlerSet.d.ts +3 -0
- package/dist/types/src/operations/ThreadOperationHandlerSet.d.ts.map +1 -0
- package/dist/types/src/operations/append-channel-message.d.ts.map +1 -1
- package/dist/types/src/operations/channel-operations.test.d.ts +2 -0
- package/dist/types/src/operations/channel-operations.test.d.ts.map +1 -0
- package/dist/types/src/operations/connect-channel.d.ts +5 -0
- package/dist/types/src/operations/connect-channel.d.ts.map +1 -0
- package/dist/types/src/operations/disconnect-channel.d.ts +5 -0
- package/dist/types/src/operations/disconnect-channel.d.ts.map +1 -0
- package/dist/types/src/operations/get-channel-status.d.ts +5 -0
- package/dist/types/src/operations/get-channel-status.d.ts.map +1 -0
- package/dist/types/src/operations/index.d.ts +1 -2
- package/dist/types/src/operations/index.d.ts.map +1 -1
- package/dist/types/src/operations/open-direct.d.ts +5 -0
- package/dist/types/src/operations/open-direct.d.ts.map +1 -0
- package/dist/types/src/operations/send-to-channel.d.ts +5 -0
- package/dist/types/src/operations/send-to-channel.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/ChannelBackend.d.ts +79 -1
- package/dist/types/src/types/ChannelBackend.d.ts.map +1 -1
- package/dist/types/src/types/ThreadCapabilities.d.ts +30 -4
- package/dist/types/src/types/ThreadCapabilities.d.ts.map +1 -1
- package/dist/types/src/types/ThreadOperation.d.ts +78 -2
- package/dist/types/src/types/ThreadOperation.d.ts.map +1 -1
- package/dist/types/src/types/index.d.ts +4 -4
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dx.config.ts +3 -0
- package/package.json +53 -40
- package/src/capabilities/app-graph-builder.ts +3 -2
- package/src/capabilities/channel-backend-feed.ts +5 -10
- package/src/capabilities/gen/browser.ts +57 -0
- package/src/capabilities/gen/node.ts +5 -5
- package/src/capabilities/gen/tauri.ts +57 -0
- package/src/capabilities/gen/workerd.ts +5 -5
- package/src/capabilities/index.ts +8 -8
- package/src/capabilities/operation-handler.ts +1 -1
- package/src/capabilities/react-surface.ts +2 -2
- package/src/components/MessageThread/MessageThread.tsx +11 -14
- package/src/components/MessageThread/index.ts +1 -1
- package/src/components/index.ts +1 -1
- package/src/containers/ChannelArticle/ChannelArticle.stories.tsx +4 -4
- package/src/containers/ChannelArticle/ChannelArticle.tsx +17 -20
- package/src/containers/ChannelArticle/index.ts +1 -1
- package/src/containers/ChannelCreatePanel/ChannelCreatePanel.tsx +10 -5
- package/src/containers/ChannelCreatePanel/index.ts +1 -1
- package/src/containers/ThreadArticle/ThreadArticle.stories.tsx +7 -7
- package/src/containers/ThreadArticle/ThreadArticle.tsx +21 -12
- package/src/containers/ThreadArticle/index.ts +1 -1
- package/src/containers/index.ts +3 -3
- package/src/hooks/index.ts +2 -2
- package/src/hooks/useMessages.ts +2 -2
- package/src/hooks/useStatus.ts +4 -1
- package/src/index.ts +2 -4
- package/src/meta.ts +1 -1
- package/src/operations/ThreadOperationHandlerSet.ts +20 -0
- package/src/operations/append-channel-message.ts +2 -6
- package/src/operations/channel-operations.test.ts +185 -0
- package/src/operations/connect-channel.ts +23 -0
- package/src/operations/disconnect-channel.ts +23 -0
- package/src/operations/get-channel-status.ts +23 -0
- package/src/operations/index.ts +1 -9
- package/src/operations/open-direct.ts +32 -0
- package/src/operations/send-to-channel.ts +33 -0
- package/src/plugin.test.ts +3 -3
- package/src/translations.ts +2 -2
- package/src/types/ChannelBackend.test.ts +2 -2
- package/src/types/ChannelBackend.ts +66 -2
- package/src/types/ThreadCapabilities.ts +41 -4
- package/src/types/ThreadOperation.ts +114 -5
- package/src/types/index.ts +4 -4
- package/dist/lib/chunk-paths.mjs +0 -8
- package/dist/lib/chunk-paths.mjs.map +0 -1
- package/dist/lib/operations.mjs.map +0 -1
package/src/operations/index.ts
CHANGED
|
@@ -2,12 +2,4 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
import * as OperationHandlerSet from '@dxos/compute/OperationHandlerSet';
|
|
7
|
-
|
|
8
|
-
import { ThreadOperation } from '#types';
|
|
9
|
-
|
|
10
|
-
export const ThreadOperationHandlerSet = OperationHandlerSet.lazy([
|
|
11
|
-
ThreadOperation.AppendChannelMessage.pipe(Operation.lazyHandler(() => import('./append-channel-message'))),
|
|
12
|
-
ThreadOperation.CreateChannel.pipe(Operation.lazyHandler(() => import('./create-channel'))),
|
|
13
|
-
]);
|
|
5
|
+
export * as ThreadOperationHandlerSet from './ThreadOperationHandlerSet.ts';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
|
|
7
|
+
import * as Operation from '@dxos/compute/Operation';
|
|
8
|
+
import { Database } from '@dxos/echo';
|
|
9
|
+
|
|
10
|
+
import { ChannelBackend, ThreadOperation } from '#types';
|
|
11
|
+
|
|
12
|
+
const handler: Operation.WithHandler<typeof ThreadOperation.OpenDirect> = ThreadOperation.OpenDirect.pipe(
|
|
13
|
+
Operation.withHandler(
|
|
14
|
+
Effect.fnUntraced(function* ({ channel: channelRef, person: personRef }) {
|
|
15
|
+
const channel = yield* Database.load(channelRef);
|
|
16
|
+
const person = yield* Database.load(personRef);
|
|
17
|
+
const provider = yield* ChannelBackend.getProvider(channel);
|
|
18
|
+
const openDirect = yield* ChannelBackend.requireMember(provider, 'openDirect');
|
|
19
|
+
// As with sending, a backend refusal is an outcome the caller relays.
|
|
20
|
+
return yield* openDirect(channel, person).pipe(
|
|
21
|
+
Effect.map((thread) =>
|
|
22
|
+
thread === undefined
|
|
23
|
+
? { reason: `The person has no ${provider.label} handle the channel can reach.` }
|
|
24
|
+
: { thread },
|
|
25
|
+
),
|
|
26
|
+
Effect.catch((error) => Effect.succeed({ reason: error.message })),
|
|
27
|
+
);
|
|
28
|
+
}),
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export default handler;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
|
|
7
|
+
import * as Operation from '@dxos/compute/Operation';
|
|
8
|
+
import { Database } from '@dxos/echo';
|
|
9
|
+
import { Message } from '@dxos/types';
|
|
10
|
+
|
|
11
|
+
import { ChannelBackend, ThreadOperation } from '#types';
|
|
12
|
+
|
|
13
|
+
const handler: Operation.WithHandler<typeof ThreadOperation.SendToChannel> = ThreadOperation.SendToChannel.pipe(
|
|
14
|
+
Operation.withHandler(
|
|
15
|
+
Effect.fnUntraced(function* ({ channel: channelRef, thread, text }) {
|
|
16
|
+
const channel = yield* Database.load(channelRef);
|
|
17
|
+
const provider = yield* ChannelBackend.getProvider(channel);
|
|
18
|
+
const message = Message.make({ sender: { role: 'assistant' }, blocks: [{ _tag: 'text', text }] });
|
|
19
|
+
const send =
|
|
20
|
+
thread === undefined
|
|
21
|
+
? provider.send(channel, message)
|
|
22
|
+
: (yield* ChannelBackend.requireMember(provider, 'threads')).send(channel, thread, message);
|
|
23
|
+
|
|
24
|
+
// A backend refusal (closed DMs, missing permission) is an outcome the caller relays, not a failure.
|
|
25
|
+
return yield* send.pipe(
|
|
26
|
+
Effect.map((result) => ({ delivered: true, ...ChannelBackend.toReceipt(result) })),
|
|
27
|
+
Effect.catch((error) => Effect.succeed({ delivered: false, reason: error.message })),
|
|
28
|
+
);
|
|
29
|
+
}),
|
|
30
|
+
),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export default handler;
|
package/src/plugin.test.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { describe, test } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import * as ClientPlugin from '@dxos/plugin-client/ClientPlugin';
|
|
8
|
-
import
|
|
8
|
+
import * as Harness from '@dxos/plugin-testing/Harness';
|
|
9
9
|
|
|
10
10
|
import { meta } from '#meta';
|
|
11
11
|
import { ThreadPlugin } from '#plugin';
|
|
@@ -13,8 +13,8 @@ import { ThreadPlugin } from '#plugin';
|
|
|
13
13
|
const moduleId = (name: string) => `${meta.profile.key}.module.${name}`;
|
|
14
14
|
|
|
15
15
|
describe('ThreadPlugin', () => {
|
|
16
|
-
test('modules activate on the expected events', async ({ expect }) => {
|
|
17
|
-
await using harness = await createComposerTestApp({
|
|
16
|
+
test('modules activate on the expected events', { timeout: 60_000 }, async ({ expect }) => {
|
|
17
|
+
await using harness = await Harness.createComposerTestApp({
|
|
18
18
|
plugins: [ClientPlugin.make({}), ThreadPlugin()],
|
|
19
19
|
});
|
|
20
20
|
|
package/src/translations.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Type } from '@dxos/echo';
|
|
6
|
-
import
|
|
6
|
+
import type * as Theme from '@dxos/react-ui/Theme';
|
|
7
7
|
import { Channel } from '@dxos/types';
|
|
8
8
|
|
|
9
9
|
import { meta } from '#meta';
|
|
@@ -95,4 +95,4 @@ export const translations = [
|
|
|
95
95
|
},
|
|
96
96
|
},
|
|
97
97
|
},
|
|
98
|
-
] as const satisfies Resource[];
|
|
98
|
+
] as const satisfies Theme.Resource[];
|
|
@@ -8,8 +8,8 @@ import { describe, test } from 'vitest';
|
|
|
8
8
|
|
|
9
9
|
import { Feed } from '@dxos/echo';
|
|
10
10
|
|
|
11
|
-
import { buildChannelFormSchema, resolveProvider } from './ChannelBackend';
|
|
12
|
-
import * as ThreadCapabilities from './ThreadCapabilities';
|
|
11
|
+
import { buildChannelFormSchema, resolveProvider } from './ChannelBackend.ts';
|
|
12
|
+
import * as ThreadCapabilities from './ThreadCapabilities.ts';
|
|
13
13
|
|
|
14
14
|
describe('channel-backend helpers', () => {
|
|
15
15
|
test('resolveProvider finds by kind', ({ expect }) => {
|
|
@@ -2,11 +2,29 @@
|
|
|
2
2
|
// Copyright 2026 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
5
6
|
import * as Schema from 'effect/Schema';
|
|
6
7
|
|
|
7
|
-
import
|
|
8
|
+
import * as Capability from '@dxos/app-framework/Capability';
|
|
9
|
+
import { type Database, type Feed, Filter, Query } from '@dxos/echo';
|
|
10
|
+
import * as SchemaAST from '@dxos/effect/SchemaAST';
|
|
11
|
+
import { BaseError } from '@dxos/errors';
|
|
12
|
+
import { type Channel, Message } from '@dxos/types';
|
|
8
13
|
|
|
9
|
-
import * as ThreadCapabilities from './ThreadCapabilities';
|
|
14
|
+
import * as ThreadCapabilities from './ThreadCapabilities.ts';
|
|
15
|
+
import type * as ThreadOperation from './ThreadOperation.ts';
|
|
16
|
+
|
|
17
|
+
/** No contributed provider handles the channel's `backend.kind` (its plugin is disabled or absent). */
|
|
18
|
+
export class ChannelBackendNotFoundError extends BaseError.extend(
|
|
19
|
+
'ChannelBackendNotFoundError',
|
|
20
|
+
'No channel backend handles this channel.',
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
/** The channel's backend lacks an optional member the caller needs (`openDirect`, `threads`, `connection`). */
|
|
24
|
+
export class ChannelBackendUnsupportedError extends BaseError.extend(
|
|
25
|
+
'ChannelBackendUnsupportedError',
|
|
26
|
+
'The channel backend does not support this.',
|
|
27
|
+
) {}
|
|
10
28
|
|
|
11
29
|
/** Finds the provider matching a `Channel.backend.kind`. */
|
|
12
30
|
export const resolveProvider = (
|
|
@@ -14,6 +32,52 @@ export const resolveProvider = (
|
|
|
14
32
|
kind: string,
|
|
15
33
|
): ThreadCapabilities.ChannelBackendProvider | undefined => providers.find((provider) => provider.kind === kind);
|
|
16
34
|
|
|
35
|
+
/** The contributed provider for a channel, or a typed failure naming the missing kind. */
|
|
36
|
+
export const getProvider: (
|
|
37
|
+
channel: Channel.Channel,
|
|
38
|
+
) => Effect.Effect<ThreadCapabilities.ChannelBackendProvider, ChannelBackendNotFoundError, Capability.Service> =
|
|
39
|
+
Effect.fnUntraced(function* (channel) {
|
|
40
|
+
const providers = yield* Capability.getAll(ThreadCapabilities.ChannelBackend);
|
|
41
|
+
const provider = resolveProvider(providers, channel.backend.kind);
|
|
42
|
+
if (!provider) {
|
|
43
|
+
return yield* Effect.fail(new ChannelBackendNotFoundError({ context: { kind: channel.backend.kind } }));
|
|
44
|
+
}
|
|
45
|
+
return provider;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/** Narrows a provider to one that has the optional member `name`, or fails naming what is missing. */
|
|
49
|
+
export const requireMember = <K extends 'openDirect' | 'threads' | 'connection'>(
|
|
50
|
+
provider: ThreadCapabilities.ChannelBackendProvider,
|
|
51
|
+
name: K,
|
|
52
|
+
): Effect.Effect<NonNullable<ThreadCapabilities.ChannelBackendProvider[K]>, ChannelBackendUnsupportedError> => {
|
|
53
|
+
const member = provider[name];
|
|
54
|
+
return member === undefined
|
|
55
|
+
? Effect.fail(
|
|
56
|
+
new ChannelBackendUnsupportedError({
|
|
57
|
+
message: `The ${provider.label} channel backend does not support ${name}.`,
|
|
58
|
+
context: { kind: provider.kind, member: name },
|
|
59
|
+
}),
|
|
60
|
+
)
|
|
61
|
+
: Effect.succeed(member);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** A provider's send result as a receipt; a backend with nothing to report yields an empty one. */
|
|
65
|
+
export const toReceipt = (result: ThreadCapabilities.SendResult): ThreadOperation.SendReceipt =>
|
|
66
|
+
typeof result === 'object' ? result : {};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Subscribes to the messages appended to a feed, firing immediately; returns the unsubscribe.
|
|
70
|
+
* Shared by every backend whose messages live in (or are mirrored into) an ECHO feed.
|
|
71
|
+
*/
|
|
72
|
+
export const subscribeFeed = (
|
|
73
|
+
db: Database.Database,
|
|
74
|
+
feed: Feed.Feed,
|
|
75
|
+
onMessages: (messages: readonly Message.Message[]) => void,
|
|
76
|
+
): (() => void) => {
|
|
77
|
+
const result = db.query(Query.select(Filter.type(Message.Message)).from(feed));
|
|
78
|
+
return result.subscribe(() => onMessages(result.results), { fire: true });
|
|
79
|
+
};
|
|
80
|
+
|
|
17
81
|
/** Throws if two providers share a `kind` (which would make resolution order-dependent). */
|
|
18
82
|
export const assertUniqueKinds = (providers: readonly ThreadCapabilities.ChannelBackendProvider[]): void => {
|
|
19
83
|
const seen = new Set<string>();
|
|
@@ -4,15 +4,39 @@
|
|
|
4
4
|
|
|
5
5
|
// @import-as-namespace
|
|
6
6
|
|
|
7
|
-
import * as Effect from 'effect/Effect';
|
|
7
|
+
import type * as Effect from 'effect/Effect';
|
|
8
8
|
import type * as Schema from 'effect/Schema';
|
|
9
9
|
|
|
10
10
|
import * as Capability from '@dxos/app-framework/Capability';
|
|
11
11
|
import { type Obj } from '@dxos/echo';
|
|
12
|
-
import { type Channel, type Message } from '@dxos/types';
|
|
12
|
+
import { type Channel, type Message, type Person } from '@dxos/types';
|
|
13
13
|
|
|
14
14
|
import { meta } from '#meta';
|
|
15
15
|
|
|
16
|
+
import type * as ThreadOperation from './ThreadOperation.ts';
|
|
17
|
+
|
|
18
|
+
/** What a backend reports about a post; `void` when it has nothing to report. */
|
|
19
|
+
export type SendResult = ThreadOperation.SendReceipt | void;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Threads inside a channel, for backends that have them (Discord threads and DM channels).
|
|
23
|
+
* Thread ids are backend-scoped strings, always paired with the channel they belong to.
|
|
24
|
+
*/
|
|
25
|
+
export interface ChannelThreads {
|
|
26
|
+
send: (
|
|
27
|
+
channel: Channel.Channel,
|
|
28
|
+
thread: string,
|
|
29
|
+
message: Message.Message,
|
|
30
|
+
) => Effect.Effect<SendResult, Error, Capability.Service>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A connection that must be started (a bot gateway, an IRC socket). */
|
|
34
|
+
export interface ChannelConnection {
|
|
35
|
+
start: (channel: Channel.Channel) => Effect.Effect<ThreadOperation.ConnectionStatus, Error, Capability.Service>;
|
|
36
|
+
stop: (channel: Channel.Channel) => Effect.Effect<ThreadOperation.ConnectionStatus, Error, Capability.Service>;
|
|
37
|
+
status: (channel: Channel.Channel) => Effect.Effect<ThreadOperation.ConnectionStatus, Error, Capability.Service>;
|
|
38
|
+
}
|
|
39
|
+
|
|
16
40
|
/**
|
|
17
41
|
* A pluggable message backend for a `Channel`. Providers are contributed by
|
|
18
42
|
* plugins and resolved by `Channel.backend.kind`.
|
|
@@ -37,10 +61,23 @@ export interface ChannelBackendProvider {
|
|
|
37
61
|
* list immediately and on every change. Returns an unsubscribe function.
|
|
38
62
|
*/
|
|
39
63
|
subscribe: (channel: Channel.Channel, onMessages: (messages: readonly Message.Message[]) => void) => () => void;
|
|
40
|
-
/** Sends a message through the backend. */
|
|
41
|
-
send: (channel: Channel.Channel, message: Message.Message) => Effect.Effect<
|
|
64
|
+
/** Sends a message through the backend; fails with a reason a person can act on. */
|
|
65
|
+
send: (channel: Channel.Channel, message: Message.Message) => Effect.Effect<SendResult, Error, Capability.Service>;
|
|
42
66
|
/** Whether the channel is read-only. Defaults to "channel has foreign-key Obj.Meta". */
|
|
43
67
|
readOnly?: (channel: Channel.Channel) => boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Opens a direct conversation with a person through the channel's account (a Discord DM),
|
|
70
|
+
* returning its thread id; `undefined` when the person has no handle this backend knows.
|
|
71
|
+
* The person's handle is the backend's business, read from `Person.identities`.
|
|
72
|
+
*/
|
|
73
|
+
openDirect?: (
|
|
74
|
+
channel: Channel.Channel,
|
|
75
|
+
person: Person.Person,
|
|
76
|
+
) => Effect.Effect<string | undefined, Error, Capability.Service>;
|
|
77
|
+
/** Threads inside the channel (Discord threads and DM channels). */
|
|
78
|
+
threads?: ChannelThreads;
|
|
79
|
+
/** A connection that must be started; absent means the backend is always available. */
|
|
80
|
+
connection?: ChannelConnection;
|
|
44
81
|
}
|
|
45
82
|
|
|
46
83
|
/** Registry of channel-message backends. Collect with `Capability.getAll`. */
|
|
@@ -8,11 +8,8 @@ import * as Schema from 'effect/Schema';
|
|
|
8
8
|
|
|
9
9
|
import * as Capability from '@dxos/app-framework/Capability';
|
|
10
10
|
import * as Operation from '@dxos/compute/Operation';
|
|
11
|
-
import { DXN, Key, Type } from '@dxos/echo';
|
|
12
|
-
|
|
13
|
-
// it in the emitted .d.ts for AppendChannelMessage.
|
|
14
|
-
// eslint-disable-next-line unused-imports/no-unused-imports
|
|
15
|
-
import { Actor, Channel, type Person } from '@dxos/types';
|
|
11
|
+
import { Database, DXN, Key, Ref, Type } from '@dxos/echo';
|
|
12
|
+
import { Actor, Channel, Person } from '@dxos/types';
|
|
16
13
|
|
|
17
14
|
export const CreateChannel = Operation.make({
|
|
18
15
|
meta: {
|
|
@@ -50,3 +47,115 @@ export const AppendChannelMessage = Operation.make({
|
|
|
50
47
|
}),
|
|
51
48
|
output: Schema.Void,
|
|
52
49
|
});
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* What a backend reports about a message it posted. `properties` are stamped on the copy a caller
|
|
53
|
+
* records locally, so a mirror of that record (EDGE's Discord bot) can tell it was already posted.
|
|
54
|
+
*/
|
|
55
|
+
export const SendReceipt = Schema.Struct({
|
|
56
|
+
messageIds: Schema.optional(Schema.Array(Schema.String).annotate({ description: 'Backend ids of the posts.' })),
|
|
57
|
+
properties: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export interface SendReceipt extends Schema.Schema.Type<typeof SendReceipt> {}
|
|
61
|
+
|
|
62
|
+
/** Whether a backend's connection (a bot gateway, an IRC socket) is up. */
|
|
63
|
+
export const ConnectionStatus = Schema.Struct({
|
|
64
|
+
running: Schema.Boolean,
|
|
65
|
+
/** Backend-specific state name, e.g. a gateway's `connecting` or `ready`. */
|
|
66
|
+
state: Schema.optional(Schema.String),
|
|
67
|
+
detail: Schema.optional(Schema.String.annotate({ description: 'A short human-readable summary.' })),
|
|
68
|
+
error: Schema.optional(Schema.String.annotate({ description: 'Why the connection last failed.' })),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export interface ConnectionStatus extends Schema.Schema.Type<typeof ConnectionStatus> {}
|
|
72
|
+
|
|
73
|
+
/** Sends text to a channel, or to a thread inside it, through the channel's backend. */
|
|
74
|
+
export const SendToChannel = Operation.make({
|
|
75
|
+
meta: {
|
|
76
|
+
key: DXN.make('org.dxos.operation.thread.sendToChannel'),
|
|
77
|
+
name: 'Send to channel',
|
|
78
|
+
description: "Sends a message to a channel, or to a thread inside it, through the channel's backend.",
|
|
79
|
+
icon: 'ph--paper-plane-tilt--regular',
|
|
80
|
+
},
|
|
81
|
+
services: [Database.Service, Capability.Service],
|
|
82
|
+
input: Schema.Struct({
|
|
83
|
+
channel: Ref.Ref(Channel.Channel).annotate({ description: 'The channel to send to.' }),
|
|
84
|
+
thread: Schema.optional(
|
|
85
|
+
Schema.String.annotate({ description: 'A backend-scoped thread id (a Discord thread or DM channel id).' }),
|
|
86
|
+
),
|
|
87
|
+
text: Schema.String.annotate({ description: 'The message.' }),
|
|
88
|
+
}),
|
|
89
|
+
output: Schema.Struct({
|
|
90
|
+
delivered: Schema.Boolean,
|
|
91
|
+
...SendReceipt.fields,
|
|
92
|
+
reason: Schema.optional(Schema.String.annotate({ description: 'Why the message was not delivered.' })),
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
/** Opens (or finds) a direct conversation with a person through the channel's account. */
|
|
97
|
+
export const OpenDirect = Operation.make({
|
|
98
|
+
meta: {
|
|
99
|
+
key: DXN.make('org.dxos.operation.thread.openDirect'),
|
|
100
|
+
name: 'Open direct conversation',
|
|
101
|
+
description:
|
|
102
|
+
"Opens a direct conversation (a DM) with a person through the channel's account; returns its thread id.",
|
|
103
|
+
icon: 'ph--user-circle--regular',
|
|
104
|
+
},
|
|
105
|
+
services: [Database.Service, Capability.Service],
|
|
106
|
+
input: Schema.Struct({
|
|
107
|
+
channel: Ref.Ref(Channel.Channel).annotate({ description: 'The channel whose account sends the direct message.' }),
|
|
108
|
+
person: Ref.Ref(Person.Person).annotate({ description: 'The person to reach.' }),
|
|
109
|
+
}),
|
|
110
|
+
output: Schema.Struct({
|
|
111
|
+
thread: Schema.optional(
|
|
112
|
+
Schema.String.annotate({ description: 'The direct thread id; pass it to sendToChannel as `thread`.' }),
|
|
113
|
+
),
|
|
114
|
+
reason: Schema.optional(Schema.String.annotate({ description: 'Why the person cannot be reached directly.' })),
|
|
115
|
+
}),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
/** Starts the channel's connection (or reconfigures a running one). */
|
|
119
|
+
export const ConnectChannel = Operation.make({
|
|
120
|
+
meta: {
|
|
121
|
+
key: DXN.make('org.dxos.operation.thread.connectChannel'),
|
|
122
|
+
name: 'Connect channel',
|
|
123
|
+
description: "Starts the channel backend's connection, e.g. a bot gateway.",
|
|
124
|
+
icon: 'ph--play--regular',
|
|
125
|
+
},
|
|
126
|
+
services: [Database.Service, Capability.Service],
|
|
127
|
+
input: Schema.Struct({
|
|
128
|
+
channel: Ref.Ref(Channel.Channel).annotate({ description: 'The channel to connect.' }),
|
|
129
|
+
}),
|
|
130
|
+
output: Schema.Struct({ status: ConnectionStatus }),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
/** Stops the channel's connection. */
|
|
134
|
+
export const DisconnectChannel = Operation.make({
|
|
135
|
+
meta: {
|
|
136
|
+
key: DXN.make('org.dxos.operation.thread.disconnectChannel'),
|
|
137
|
+
name: 'Disconnect channel',
|
|
138
|
+
description: "Stops the channel backend's connection.",
|
|
139
|
+
icon: 'ph--stop--regular',
|
|
140
|
+
},
|
|
141
|
+
services: [Database.Service, Capability.Service],
|
|
142
|
+
input: Schema.Struct({
|
|
143
|
+
channel: Ref.Ref(Channel.Channel).annotate({ description: 'The channel to disconnect.' }),
|
|
144
|
+
}),
|
|
145
|
+
output: Schema.Struct({ status: ConnectionStatus }),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
/** Reports the channel's connection status. */
|
|
149
|
+
export const GetChannelStatus = Operation.make({
|
|
150
|
+
meta: {
|
|
151
|
+
key: DXN.make('org.dxos.operation.thread.getChannelStatus'),
|
|
152
|
+
name: 'Get channel status',
|
|
153
|
+
description: "Reports whether the channel backend's connection is up.",
|
|
154
|
+
icon: 'ph--pulse--regular',
|
|
155
|
+
},
|
|
156
|
+
services: [Database.Service, Capability.Service],
|
|
157
|
+
input: Schema.Struct({
|
|
158
|
+
channel: Ref.Ref(Channel.Channel).annotate({ description: 'The channel to report on.' }),
|
|
159
|
+
}),
|
|
160
|
+
output: Schema.Struct({ status: ConnectionStatus }),
|
|
161
|
+
});
|
package/src/types/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
export * as ChannelBackend from './ChannelBackend';
|
|
6
|
-
export * as ThreadCapabilities from './ThreadCapabilities';
|
|
7
|
-
export * as ThreadEvents from './ThreadEvents';
|
|
8
|
-
export * as ThreadOperation from './ThreadOperation';
|
|
5
|
+
export * as ChannelBackend from './ChannelBackend.ts';
|
|
6
|
+
export * as ThreadCapabilities from './ThreadCapabilities.ts';
|
|
7
|
+
export * as ThreadEvents from './ThreadEvents.ts';
|
|
8
|
+
export * as ThreadOperation from './ThreadOperation.ts';
|
package/dist/lib/chunk-paths.mjs
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import * as GraphPath from "@dxos/app-toolkit/GraphPath";
|
|
2
|
-
import { Channel } from "@dxos/types";
|
|
3
|
-
//#region src/paths.ts
|
|
4
|
-
var { getSectionPath: getChannelsPath, getObjectPath: getChannelPath } = GraphPath.createTypeSectionPaths(Channel.Channel, { groupId: GraphPath.GroupSegments.communications });
|
|
5
|
-
//#endregion
|
|
6
|
-
export { getChannelsPath as n, getChannelPath as t };
|
|
7
|
-
|
|
8
|
-
//# sourceMappingURL=chunk-paths.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-paths.mjs","names":[],"sources":["../../src/paths.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as GraphPath from '@dxos/app-toolkit/GraphPath';\nimport { Channel } from '@dxos/types';\n\nconst { getSectionPath: getChannelsPath, getObjectPath: getChannelPath } = GraphPath.createTypeSectionPaths(\n Channel.Channel,\n { groupId: GraphPath.GroupSegments.communications },\n);\n\nexport { getChannelPath, getChannelsPath };\n"],"mappings":";;;AAOA,IAAM,EAAE,gBAAgB,iBAAiB,eAAe,mBAAmB,UAAU,uBACnF,QAAQ,SACR,EAAE,SAAS,UAAU,cAAc,eAAe,CACpD"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"operations.mjs","names":[],"sources":["../../src/operations/index.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Operation from '@dxos/compute/Operation';\nimport * as OperationHandlerSet from '@dxos/compute/OperationHandlerSet';\n\nimport { ThreadOperation } from '#types';\n\nexport const ThreadOperationHandlerSet = OperationHandlerSet.lazy([\n ThreadOperation.AppendChannelMessage.pipe(Operation.lazyHandler(() => import('./append-channel-message'))),\n ThreadOperation.CreateChannel.pipe(Operation.lazyHandler(() => import('./create-channel'))),\n]);\n"],"mappings":";;;;AASA,IAAa,4BAA4B,oBAAoB,KAAK,CAChE,gBAAgB,qBAAqB,KAAK,UAAU,kBAAkB,OAAO,qCAA2B,CAAC,GACzG,gBAAgB,cAAc,KAAK,UAAU,kBAAkB,OAAO,6BAAmB,CAAC,CAC5F,CAAC"}
|