@undefineds.co/linx 0.3.19 → 0.3.22
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/generated/version.js +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/auto-mode/pod-persistence.js +51 -3
- package/dist/lib/auto-mode/pod-persistence.js.map +1 -1
- package/dist/lib/auto-mode/secretary.js +2 -2
- package/dist/lib/auto-mode/secretary.js.map +1 -1
- package/dist/lib/chat-api.js +23 -61
- package/dist/lib/chat-api.js.map +1 -1
- package/dist/lib/codex-plugin/index.js +1 -0
- package/dist/lib/codex-plugin/index.js.map +1 -1
- package/dist/lib/codex-plugin/symphony-mcp.js +335 -0
- package/dist/lib/codex-plugin/symphony-mcp.js.map +1 -0
- package/dist/lib/models.js +3 -2
- package/dist/lib/models.js.map +1 -1
- package/dist/lib/pi-adapter/auth.js +68 -0
- package/dist/lib/pi-adapter/auth.js.map +1 -0
- package/dist/lib/pi-adapter/interactive.js +12 -17
- package/dist/lib/pi-adapter/interactive.js.map +1 -1
- package/dist/lib/pi-adapter/pod-mirror.js +34 -5
- package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
- package/dist/lib/pi-adapter/pod-tools.js +140 -0
- package/dist/lib/pi-adapter/pod-tools.js.map +1 -0
- package/dist/lib/pi-adapter/session.js +13 -17
- package/dist/lib/pi-adapter/session.js.map +1 -1
- package/dist/lib/pi-adapter/stream.js +2 -20
- package/dist/lib/pi-adapter/stream.js.map +1 -1
- package/dist/lib/pod-chat-store.js +53 -4
- package/dist/lib/pod-chat-store.js.map +1 -1
- package/dist/lib/resource-identity.js +2 -0
- package/dist/lib/resource-identity.js.map +1 -0
- package/dist/lib/symphony/archive.js +15 -37
- package/dist/lib/symphony/archive.js.map +1 -1
- package/dist/lib/symphony/pod-projection.js +188 -1347
- package/dist/lib/symphony/pod-projection.js.map +1 -1
- package/dist/lib/symphony-command.js +208 -108
- package/dist/lib/symphony-command.js.map +1 -1
- package/dist/plugins/linx-symphony-codex/.codex-plugin/plugin.json +38 -0
- package/dist/plugins/linx-symphony-codex/.mcp.json +10 -0
- package/dist/plugins/linx-symphony-codex/README.md +9 -0
- package/dist/plugins/linx-symphony-codex/hooks.json +60 -0
- package/dist/plugins/linx-symphony-codex/scripts/symphony-hook-events.mjs +119 -0
- package/dist/plugins/linx-symphony-codex/scripts/symphony-mcp.mjs +335 -0
- package/dist/plugins/linx-symphony-codex/skills/symphony/SKILL.md +791 -0
- package/dist/skills/symphony/SKILL.md +15 -4
- package/package.json +4 -4
- package/vendor/agent-runtime/dist/chat-reconciler.d.ts +33 -0
- package/vendor/agent-runtime/dist/chat-reconciler.js +108 -0
- package/vendor/agent-runtime/dist/index.d.ts +4 -0
- package/vendor/agent-runtime/dist/index.js +4 -0
- package/vendor/agent-runtime/dist/matrix-client.d.ts +149 -0
- package/vendor/agent-runtime/dist/matrix-client.js +220 -0
- package/vendor/agent-runtime/dist/pod-resource-identity.d.ts +17 -0
- package/vendor/agent-runtime/dist/pod-resource-identity.js +54 -0
- package/vendor/agent-runtime/dist/reconciler.js +2 -2
- package/vendor/agent-runtime/dist/symphony.d.ts +273 -28
- package/vendor/agent-runtime/dist/symphony.js +1267 -20
- package/vendor/agent-runtime/dist/workspace.d.ts +61 -0
- package/vendor/agent-runtime/dist/workspace.js +81 -0
- package/vendor/agent-runtime/package.json +5 -1
- package/vendor/stores/dist/current-pod-base.d.ts +2 -0
- package/vendor/stores/dist/current-pod-base.js +14 -0
- package/vendor/stores/dist/exact-records.d.ts +7 -0
- package/vendor/stores/dist/exact-records.js +87 -0
- package/vendor/stores/dist/index.d.ts +1 -0
- package/vendor/stores/dist/index.js +1 -0
- package/vendor/stores/dist/login.d.ts +51 -0
- package/vendor/stores/dist/login.js +195 -0
- package/vendor/stores/dist/pod-collection.d.ts +28 -0
- package/vendor/stores/dist/pod-collection.js +194 -0
- package/vendor/stores/dist/pod-write-guard.d.ts +5 -0
- package/vendor/stores/dist/pod-write-guard.js +133 -0
- package/vendor/stores/dist/symphony-control.d.ts +245 -0
- package/vendor/stores/dist/symphony-control.js +2175 -0
- package/vendor/stores/package.json +14 -0
|
@@ -82,9 +82,11 @@ LinX control plane, or for local mirrors materialized from Pod.
|
|
|
82
82
|
|
|
83
83
|
The portable Symphony runtime contract is storage-agnostic. It may produce
|
|
84
84
|
control records, work splits, prompts, and reports, but it does not own Pod IO.
|
|
85
|
-
LinX product runtime persists through shared models/repositories.
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
LinX product runtime persists through shared models/repositories. When the
|
|
86
|
+
writer is an AI operating through terminal/tools rather than in-process LinX
|
|
87
|
+
code, `xpod` CLI is the preferred direct Pod tool surface. Use model-backed
|
|
88
|
+
`xpod obj` operations for Symphony resources when available; do not invent a
|
|
89
|
+
parallel Symphony-specific AI tool API and do not hand-patch modeled TTL paths.
|
|
88
90
|
|
|
89
91
|
In LinX Agent Runtime, Pod authority belongs to the runtime session and should
|
|
90
92
|
be inherited by tools launched inside that session. If a Secretary or worker has
|
|
@@ -92,7 +94,9 @@ Pod access, `xpod` commands it runs should use the runtime-provided authority
|
|
|
92
94
|
bridge. Outside that bridge, all Solid apps share the same local auth source
|
|
93
95
|
`$SOLID_HOME/auth/credentials.json`; old `~/.xpod/config.json` and
|
|
94
96
|
`~/.xpod/secrets.json` files are not Solid auth sources, and their presence
|
|
95
|
-
alone must be treated as unauthenticated.
|
|
97
|
+
alone must be treated as unauthenticated. If `xpod auth status --json` reports a
|
|
98
|
+
different WebID or Pod root than the shared Solid auth file, treat it as an
|
|
99
|
+
auth-store mismatch and stop before writing. Never ask the model to handle raw
|
|
96
100
|
tokens, refresh tokens, client secrets, cookies, or DPoP material directly.
|
|
97
101
|
|
|
98
102
|
## Agent Config And Skill Resources
|
|
@@ -577,6 +581,13 @@ Worker-facing minimum contract:
|
|
|
577
581
|
should move to models/drizzle-solid/xpod, live-verification gaps, or
|
|
578
582
|
deferred cleanup. Workers may recommend follow-up, but they do not decide
|
|
579
583
|
whether it becomes a new Issue.
|
|
584
|
+
- If the LinX Symphony Codex plugin is installed, prefer its `linx-symphony`
|
|
585
|
+
MCP tools to check and write the delivery. The MCP helper only validates/writes
|
|
586
|
+
the portable Delivery file; it does not directly mutate Pod Issues, Tasks,
|
|
587
|
+
Deliveries, or acceptance state.
|
|
588
|
+
- Otherwise include the same terminal facts in the final report using the
|
|
589
|
+
`symphonyFinal: true` JSON envelope so LinX can archive the transcript through
|
|
590
|
+
shared control-plane use-cases; it is not a separate worker schema.
|
|
580
591
|
- Never use chat, Delivery, or repo docs to redefine scope, acceptance,
|
|
581
592
|
compatibility, lifecycle state, or release boundary. Write an Implementation
|
|
582
593
|
Change Request instead.
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@undefineds.co/linx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.22",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"linx": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"build": "
|
|
10
|
+
"build": "node ./scripts/build.mjs",
|
|
11
11
|
"dev": "node ./scripts/dev.mjs",
|
|
12
12
|
"test": "yarn build && NODE_OPTIONS=\"${NODE_OPTIONS:+$NODE_OPTIONS }--preserve-symlinks\" node --test --test-concurrency=1 test/**/*.test.mjs",
|
|
13
13
|
"test:live-acp": "node ./scripts/smoke-auto-mode-acp.mjs"
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"@earendil-works/pi-ai": "0.78.0",
|
|
19
19
|
"@earendil-works/pi-coding-agent": "0.78.0",
|
|
20
20
|
"@earendil-works/pi-tui": "0.78.0",
|
|
21
|
-
"@undefineds.co/drizzle-solid": "
|
|
22
|
-
"@undefineds.co/models": "0.2.
|
|
21
|
+
"@undefineds.co/drizzle-solid": "0.3.16",
|
|
22
|
+
"@undefineds.co/models": "0.2.42",
|
|
23
23
|
"@zed-industries/codex-acp": "0.14.0",
|
|
24
24
|
"n3": "^1.26.0",
|
|
25
25
|
"pi-web-access": "^0.10.7",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ReconcileDecisionSummary, type ReconcilerActorRef, type ReconcilerClientContext, type ThreadControlEvent, type ThreadPolicy, type ThreadPolicyKind } from './reconciler.js';
|
|
2
|
+
export type ChatAppendRole = 'user' | 'assistant' | 'system';
|
|
3
|
+
export type ChatAppendSource = 'web-chat' | 'cli-chat-store' | 'cli-pi-mirror' | 'cli-auto-mode' | 'service-runtime' | 'matrix' | 'secretary-runtime-intent' | 'primary-agent' | 'runtime' | 'worker' | (string & {});
|
|
4
|
+
export interface ChatAppendReconcilerInput {
|
|
5
|
+
chat?: string;
|
|
6
|
+
thread: string;
|
|
7
|
+
resource?: string;
|
|
8
|
+
role: ChatAppendRole;
|
|
9
|
+
content: string;
|
|
10
|
+
actor?: ReconcilerActorRef;
|
|
11
|
+
source?: ChatAppendSource;
|
|
12
|
+
policy?: ThreadPolicyKind | ThreadPolicy;
|
|
13
|
+
autoEnabled?: boolean;
|
|
14
|
+
secretaryAgent?: string;
|
|
15
|
+
defaultAssistantAgent?: string;
|
|
16
|
+
createdAt?: Date | string | number;
|
|
17
|
+
randomId?: string;
|
|
18
|
+
client?: ReconcilerClientContext;
|
|
19
|
+
data?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
export interface ChatAppendReconcilerResult {
|
|
22
|
+
event: ThreadControlEvent;
|
|
23
|
+
summary: ReconcileDecisionSummary;
|
|
24
|
+
}
|
|
25
|
+
export interface ChatReconcilerMetadata {
|
|
26
|
+
version: 1;
|
|
27
|
+
latest: ReconcileDecisionSummary;
|
|
28
|
+
decisions: ReconcileDecisionSummary[];
|
|
29
|
+
}
|
|
30
|
+
export declare function reconcileChatAppend(input: ChatAppendReconcilerInput): ChatAppendReconcilerResult;
|
|
31
|
+
export declare function createChatAppendEvent(input: ChatAppendReconcilerInput): ThreadControlEvent;
|
|
32
|
+
export declare function appendChatReconcilerMetadata(metadata: Record<string, unknown> | null | undefined, summary: ReconcileDecisionSummary): Record<string, unknown>;
|
|
33
|
+
export declare function readChatReconcilerMetadata(value: unknown): ChatReconcilerMetadata | null;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { decideThreadControlEvent } from './thread-reconciler-controller.js';
|
|
2
|
+
export function reconcileChatAppend(input) {
|
|
3
|
+
const event = createChatAppendEvent(input);
|
|
4
|
+
const { summary } = decideThreadControlEvent({
|
|
5
|
+
policy: resolveChatAppendPolicy(input),
|
|
6
|
+
event,
|
|
7
|
+
...(input.chat ? { chat: input.chat } : {}),
|
|
8
|
+
thread: input.thread,
|
|
9
|
+
...(input.createdAt !== undefined ? { now: toDate(input.createdAt) } : {}),
|
|
10
|
+
randomId: input.randomId ?? input.resource,
|
|
11
|
+
...(input.client ? { client: input.client } : {}),
|
|
12
|
+
});
|
|
13
|
+
return { event, summary };
|
|
14
|
+
}
|
|
15
|
+
export function createChatAppendEvent(input) {
|
|
16
|
+
const actor = input.actor ?? defaultActorForChatAppend(input);
|
|
17
|
+
return {
|
|
18
|
+
type: 'message.appended',
|
|
19
|
+
...(input.chat ? { chat: input.chat } : {}),
|
|
20
|
+
thread: input.thread,
|
|
21
|
+
...(input.resource ? { resource: input.resource } : {}),
|
|
22
|
+
actor,
|
|
23
|
+
content: input.content,
|
|
24
|
+
...(input.createdAt !== undefined ? { createdAt: toDate(input.createdAt).toISOString() } : {}),
|
|
25
|
+
data: {
|
|
26
|
+
...(input.data ?? {}),
|
|
27
|
+
role: input.role,
|
|
28
|
+
...(input.source ? { source: input.source } : {}),
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function appendChatReconcilerMetadata(metadata, summary) {
|
|
33
|
+
const base = isRecord(metadata) ? { ...metadata } : {};
|
|
34
|
+
const existing = readChatReconcilerMetadata(base.reconciler);
|
|
35
|
+
base.reconciler = {
|
|
36
|
+
version: 1,
|
|
37
|
+
latest: summary,
|
|
38
|
+
decisions: [...(existing?.decisions ?? []), summary],
|
|
39
|
+
};
|
|
40
|
+
return base;
|
|
41
|
+
}
|
|
42
|
+
export function readChatReconcilerMetadata(value) {
|
|
43
|
+
if (!isRecord(value)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const latest = isRecord(value.latest) ? value.latest : undefined;
|
|
47
|
+
const decisions = Array.isArray(value.decisions)
|
|
48
|
+
? value.decisions.filter(isRecord).map((item) => item)
|
|
49
|
+
: [];
|
|
50
|
+
if (!latest && decisions.length === 0) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
version: 1,
|
|
55
|
+
latest: latest ?? decisions[decisions.length - 1],
|
|
56
|
+
decisions: decisions.length > 0 ? decisions : [latest],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function resolveChatAppendPolicy(input) {
|
|
60
|
+
if (input.policy) {
|
|
61
|
+
return input.policy;
|
|
62
|
+
}
|
|
63
|
+
if (input.autoEnabled) {
|
|
64
|
+
return {
|
|
65
|
+
kind: 'auto',
|
|
66
|
+
secretaryAgent: input.secretaryAgent ?? '__secretary__',
|
|
67
|
+
defaultAssistantAgent: input.defaultAssistantAgent,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
kind: 'direct',
|
|
72
|
+
defaultAssistantAgent: input.defaultAssistantAgent ?? 'primary-agent',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function defaultActorForChatAppend(input) {
|
|
76
|
+
if (input.role === 'user') {
|
|
77
|
+
return { role: 'user' };
|
|
78
|
+
}
|
|
79
|
+
if (input.source === 'secretary-runtime-intent') {
|
|
80
|
+
return { id: input.secretaryAgent ?? '__secretary__', role: 'secretary' };
|
|
81
|
+
}
|
|
82
|
+
if (input.source === 'worker') {
|
|
83
|
+
return { role: 'worker' };
|
|
84
|
+
}
|
|
85
|
+
if (input.source === 'runtime') {
|
|
86
|
+
return { role: 'runtime' };
|
|
87
|
+
}
|
|
88
|
+
if (input.source === 'primary-agent') {
|
|
89
|
+
return { id: input.defaultAssistantAgent ?? 'primary-agent', role: 'primary-agent' };
|
|
90
|
+
}
|
|
91
|
+
if (input.role === 'assistant') {
|
|
92
|
+
return { role: 'assistant' };
|
|
93
|
+
}
|
|
94
|
+
return { role: 'runtime' };
|
|
95
|
+
}
|
|
96
|
+
function toDate(value) {
|
|
97
|
+
if (value instanceof Date) {
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
const date = new Date(value);
|
|
101
|
+
if (Number.isNaN(date.getTime())) {
|
|
102
|
+
throw new Error(`Invalid chat append timestamp: ${String(value)}`);
|
|
103
|
+
}
|
|
104
|
+
return date;
|
|
105
|
+
}
|
|
106
|
+
function isRecord(value) {
|
|
107
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
108
|
+
}
|
|
@@ -3,8 +3,11 @@ export * from './agent-runtime.js';
|
|
|
3
3
|
export * from './auto-mode.js';
|
|
4
4
|
export * from './companion-model.js';
|
|
5
5
|
export * from './client-inbox-subscription.js';
|
|
6
|
+
export * from './chat-reconciler.js';
|
|
6
7
|
export * from './control-plane.js';
|
|
7
8
|
export * from './file-sync.js';
|
|
9
|
+
export * from './matrix-client.js';
|
|
10
|
+
export * from './pod-resource-identity.js';
|
|
8
11
|
export * from './reconciler.js';
|
|
9
12
|
export * from './runtime.js';
|
|
10
13
|
export * from './symphony.js';
|
|
@@ -12,3 +15,4 @@ export * from './sync.js';
|
|
|
12
15
|
export * from './thread-reconciler-controller.js';
|
|
13
16
|
export * from './turn-controller.js';
|
|
14
17
|
export * from './wake-scheduler.js';
|
|
18
|
+
export * from './workspace.js';
|
|
@@ -3,8 +3,11 @@ export * from './agent-runtime.js';
|
|
|
3
3
|
export * from './auto-mode.js';
|
|
4
4
|
export * from './companion-model.js';
|
|
5
5
|
export * from './client-inbox-subscription.js';
|
|
6
|
+
export * from './chat-reconciler.js';
|
|
6
7
|
export * from './control-plane.js';
|
|
7
8
|
export * from './file-sync.js';
|
|
9
|
+
export * from './matrix-client.js';
|
|
10
|
+
export * from './pod-resource-identity.js';
|
|
8
11
|
export * from './reconciler.js';
|
|
9
12
|
export * from './runtime.js';
|
|
10
13
|
export * from './symphony.js';
|
|
@@ -12,3 +15,4 @@ export * from './sync.js';
|
|
|
12
15
|
export * from './thread-reconciler-controller.js';
|
|
13
16
|
export * from './turn-controller.js';
|
|
14
17
|
export * from './wake-scheduler.js';
|
|
18
|
+
export * from './workspace.js';
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
export interface MatrixVersionsResponse {
|
|
2
|
+
versions: string[];
|
|
3
|
+
unstable_features?: Record<string, boolean>;
|
|
4
|
+
}
|
|
5
|
+
export interface MatrixLoginFlowsResponse {
|
|
6
|
+
flows: Array<{
|
|
7
|
+
type: string;
|
|
8
|
+
}>;
|
|
9
|
+
}
|
|
10
|
+
export interface MatrixCreateRoomRequest {
|
|
11
|
+
visibility?: 'private' | 'public';
|
|
12
|
+
room_alias_name?: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
topic?: string;
|
|
15
|
+
invite?: string[];
|
|
16
|
+
creation_content?: Record<string, unknown>;
|
|
17
|
+
initial_state?: Array<{
|
|
18
|
+
type: string;
|
|
19
|
+
state_key?: string;
|
|
20
|
+
content?: Record<string, unknown>;
|
|
21
|
+
}>;
|
|
22
|
+
preset?: string;
|
|
23
|
+
is_direct?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface MatrixCreateRoomResponse {
|
|
26
|
+
room_id: string;
|
|
27
|
+
}
|
|
28
|
+
export interface MatrixWhoamiResponse {
|
|
29
|
+
user_id: string;
|
|
30
|
+
device_id?: string;
|
|
31
|
+
is_guest?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface MatrixJoinedRoomsResponse {
|
|
34
|
+
joined_rooms: string[];
|
|
35
|
+
}
|
|
36
|
+
export interface MatrixJoinRoomResponse {
|
|
37
|
+
room_id: string;
|
|
38
|
+
}
|
|
39
|
+
export interface MatrixInviteRequest {
|
|
40
|
+
user_id: string;
|
|
41
|
+
}
|
|
42
|
+
export interface MatrixSetStateResponse {
|
|
43
|
+
event_id: string;
|
|
44
|
+
}
|
|
45
|
+
export interface MatrixSendEventRequest {
|
|
46
|
+
body?: string;
|
|
47
|
+
msgtype?: string;
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
}
|
|
50
|
+
export interface MatrixSendEventResponse {
|
|
51
|
+
event_id: string;
|
|
52
|
+
}
|
|
53
|
+
export interface MatrixClientEvent {
|
|
54
|
+
event_id: string;
|
|
55
|
+
room_id: string;
|
|
56
|
+
type: string;
|
|
57
|
+
sender: string;
|
|
58
|
+
origin_server_ts: number;
|
|
59
|
+
content: Record<string, unknown>;
|
|
60
|
+
state_key?: string;
|
|
61
|
+
unsigned?: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
export interface MatrixSyncResponse {
|
|
64
|
+
next_batch: string;
|
|
65
|
+
rooms: {
|
|
66
|
+
join: Record<string, {
|
|
67
|
+
state: {
|
|
68
|
+
events: MatrixClientEvent[];
|
|
69
|
+
};
|
|
70
|
+
timeline: {
|
|
71
|
+
events: MatrixClientEvent[];
|
|
72
|
+
limited: boolean;
|
|
73
|
+
prev_batch?: string;
|
|
74
|
+
};
|
|
75
|
+
}>;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export interface MatrixMessagesResponse {
|
|
79
|
+
chunk: MatrixClientEvent[];
|
|
80
|
+
start?: string;
|
|
81
|
+
end: string;
|
|
82
|
+
}
|
|
83
|
+
export interface MatrixMembersResponse {
|
|
84
|
+
chunk: MatrixClientEvent[];
|
|
85
|
+
}
|
|
86
|
+
export interface MatrixClientOptions {
|
|
87
|
+
baseUrl: string;
|
|
88
|
+
accessToken?: string;
|
|
89
|
+
tokenType?: 'Bearer' | 'DPoP' | (string & {});
|
|
90
|
+
fetch?: typeof fetch;
|
|
91
|
+
randomId?: () => string;
|
|
92
|
+
}
|
|
93
|
+
export interface MatrixSyncOptions {
|
|
94
|
+
since?: string;
|
|
95
|
+
limit?: number;
|
|
96
|
+
timeout?: number;
|
|
97
|
+
}
|
|
98
|
+
export interface MatrixMessagesOptions {
|
|
99
|
+
from?: string;
|
|
100
|
+
dir?: 'b' | 'f';
|
|
101
|
+
limit?: number;
|
|
102
|
+
}
|
|
103
|
+
export declare class MatrixClientError extends Error {
|
|
104
|
+
readonly status: number;
|
|
105
|
+
readonly errcode?: string;
|
|
106
|
+
readonly body?: unknown;
|
|
107
|
+
constructor(message: string, input: {
|
|
108
|
+
status: number;
|
|
109
|
+
errcode?: string;
|
|
110
|
+
body?: unknown;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
export declare class MatrixClient {
|
|
114
|
+
private readonly baseUrl;
|
|
115
|
+
private readonly accessToken?;
|
|
116
|
+
private readonly tokenType;
|
|
117
|
+
private readonly fetchImpl;
|
|
118
|
+
private readonly randomId;
|
|
119
|
+
constructor(options: MatrixClientOptions);
|
|
120
|
+
versions(): Promise<MatrixVersionsResponse>;
|
|
121
|
+
loginFlows(): Promise<MatrixLoginFlowsResponse>;
|
|
122
|
+
whoami(): Promise<MatrixWhoamiResponse>;
|
|
123
|
+
createRoom(input?: MatrixCreateRoomRequest): Promise<MatrixCreateRoomResponse>;
|
|
124
|
+
joinedRooms(): Promise<MatrixJoinedRoomsResponse>;
|
|
125
|
+
joinRoom(roomIdOrAlias: string): Promise<MatrixJoinRoomResponse>;
|
|
126
|
+
joinRoomById(roomId: string): Promise<MatrixJoinRoomResponse>;
|
|
127
|
+
inviteUser(roomId: string, userId: string): Promise<Record<string, never>>;
|
|
128
|
+
leaveRoom(roomId: string): Promise<Record<string, never>>;
|
|
129
|
+
sendMessage(roomId: string, body: string, options?: {
|
|
130
|
+
msgtype?: string;
|
|
131
|
+
txnId?: string;
|
|
132
|
+
content?: Omit<MatrixSendEventRequest, 'body' | 'msgtype'>;
|
|
133
|
+
}): Promise<MatrixSendEventResponse>;
|
|
134
|
+
sendEvent(roomId: string, eventType: string, txnId: string, content?: MatrixSendEventRequest | Record<string, unknown>): Promise<MatrixSendEventResponse>;
|
|
135
|
+
sync(options?: MatrixSyncOptions): Promise<MatrixSyncResponse>;
|
|
136
|
+
messages(roomId: string, options?: MatrixMessagesOptions): Promise<MatrixMessagesResponse>;
|
|
137
|
+
getEvent(roomId: string, eventId: string): Promise<MatrixClientEvent>;
|
|
138
|
+
getState(roomId: string, eventType: string, stateKey?: string): Promise<Record<string, unknown>>;
|
|
139
|
+
setState(roomId: string, eventType: string, stateKey: string, content?: Record<string, unknown>): Promise<MatrixSetStateResponse>;
|
|
140
|
+
members(roomId: string): Promise<MatrixMembersResponse>;
|
|
141
|
+
private request;
|
|
142
|
+
}
|
|
143
|
+
export declare function createMatrixClient(options: MatrixClientOptions): MatrixClient;
|
|
144
|
+
export declare function matrixServerNameFromBaseUrl(baseUrl: string): string;
|
|
145
|
+
export declare function matrixUserIdFromWebId(webId: string, serverName: string): string;
|
|
146
|
+
export declare function matrixLocalpartFromUserId(userId: string): string;
|
|
147
|
+
export declare function matrixRoomSurfaceId(roomId: string): Promise<string>;
|
|
148
|
+
export declare function matrixChatResourceIdFromRoomId(roomId: string): Promise<string>;
|
|
149
|
+
export declare function matrixThreadResourceIdFromRoomId(roomId: string): Promise<string>;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
export class MatrixClientError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
errcode;
|
|
4
|
+
body;
|
|
5
|
+
constructor(message, input) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'MatrixClientError';
|
|
8
|
+
this.status = input.status;
|
|
9
|
+
this.errcode = input.errcode;
|
|
10
|
+
this.body = input.body;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class MatrixClient {
|
|
14
|
+
baseUrl;
|
|
15
|
+
accessToken;
|
|
16
|
+
tokenType;
|
|
17
|
+
fetchImpl;
|
|
18
|
+
randomId;
|
|
19
|
+
constructor(options) {
|
|
20
|
+
const baseUrl = options.baseUrl.trim();
|
|
21
|
+
if (!baseUrl) {
|
|
22
|
+
throw new Error('MatrixClient requires baseUrl');
|
|
23
|
+
}
|
|
24
|
+
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
|
25
|
+
this.accessToken = options.accessToken;
|
|
26
|
+
this.tokenType = options.tokenType ?? 'Bearer';
|
|
27
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
28
|
+
if (typeof this.fetchImpl !== 'function') {
|
|
29
|
+
throw new Error('MatrixClient requires a fetch implementation');
|
|
30
|
+
}
|
|
31
|
+
this.randomId = options.randomId ?? defaultRandomId;
|
|
32
|
+
}
|
|
33
|
+
async versions() {
|
|
34
|
+
return this.request('GET', '/_matrix/client/versions');
|
|
35
|
+
}
|
|
36
|
+
async loginFlows() {
|
|
37
|
+
return this.request('GET', '/_matrix/client/v3/login');
|
|
38
|
+
}
|
|
39
|
+
async whoami() {
|
|
40
|
+
return this.request('GET', '/_matrix/client/v3/account/whoami');
|
|
41
|
+
}
|
|
42
|
+
async createRoom(input = {}) {
|
|
43
|
+
return this.request('POST', '/_matrix/client/v3/createRoom', input);
|
|
44
|
+
}
|
|
45
|
+
async joinedRooms() {
|
|
46
|
+
return this.request('GET', '/_matrix/client/v3/joined_rooms');
|
|
47
|
+
}
|
|
48
|
+
async joinRoom(roomIdOrAlias) {
|
|
49
|
+
return this.request('POST', `/_matrix/client/v3/join/${encodePathSegment(roomIdOrAlias)}`, {});
|
|
50
|
+
}
|
|
51
|
+
async joinRoomById(roomId) {
|
|
52
|
+
return this.request('POST', `/_matrix/client/v3/rooms/${encodePathSegment(roomId)}/join`, {});
|
|
53
|
+
}
|
|
54
|
+
async inviteUser(roomId, userId) {
|
|
55
|
+
return this.request('POST', `/_matrix/client/v3/rooms/${encodePathSegment(roomId)}/invite`, {
|
|
56
|
+
user_id: userId,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async leaveRoom(roomId) {
|
|
60
|
+
return this.request('POST', `/_matrix/client/v3/rooms/${encodePathSegment(roomId)}/leave`, {});
|
|
61
|
+
}
|
|
62
|
+
async sendMessage(roomId, body, options = {}) {
|
|
63
|
+
return this.sendEvent(roomId, 'm.room.message', options.txnId ?? this.randomId(), {
|
|
64
|
+
...(options.content ?? {}),
|
|
65
|
+
msgtype: options.msgtype ?? 'm.text',
|
|
66
|
+
body,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async sendEvent(roomId, eventType, txnId, content = {}) {
|
|
70
|
+
return this.request('PUT', `/_matrix/client/v3/rooms/${encodePathSegment(roomId)}/send/${encodePathSegment(eventType)}/${encodePathSegment(txnId)}`, content);
|
|
71
|
+
}
|
|
72
|
+
async sync(options = {}) {
|
|
73
|
+
return this.request('GET', appendQuery('/_matrix/client/v3/sync', {
|
|
74
|
+
since: options.since,
|
|
75
|
+
limit: options.limit,
|
|
76
|
+
timeout: options.timeout,
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
async messages(roomId, options = {}) {
|
|
80
|
+
return this.request('GET', appendQuery(`/_matrix/client/v3/rooms/${encodePathSegment(roomId)}/messages`, {
|
|
81
|
+
from: options.from,
|
|
82
|
+
dir: options.dir,
|
|
83
|
+
limit: options.limit,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
async getEvent(roomId, eventId) {
|
|
87
|
+
return this.request('GET', `/_matrix/client/v3/rooms/${encodePathSegment(roomId)}/event/${encodePathSegment(eventId)}`);
|
|
88
|
+
}
|
|
89
|
+
async getState(roomId, eventType, stateKey = '') {
|
|
90
|
+
const statePath = stateKey
|
|
91
|
+
? `/_matrix/client/v3/rooms/${encodePathSegment(roomId)}/state/${encodePathSegment(eventType)}/${encodePathSegment(stateKey)}`
|
|
92
|
+
: `/_matrix/client/v3/rooms/${encodePathSegment(roomId)}/state/${encodePathSegment(eventType)}`;
|
|
93
|
+
return this.request('GET', statePath);
|
|
94
|
+
}
|
|
95
|
+
async setState(roomId, eventType, stateKey, content = {}) {
|
|
96
|
+
const statePath = stateKey
|
|
97
|
+
? `/_matrix/client/v3/rooms/${encodePathSegment(roomId)}/state/${encodePathSegment(eventType)}/${encodePathSegment(stateKey)}`
|
|
98
|
+
: `/_matrix/client/v3/rooms/${encodePathSegment(roomId)}/state/${encodePathSegment(eventType)}`;
|
|
99
|
+
return this.request('PUT', statePath, content);
|
|
100
|
+
}
|
|
101
|
+
async members(roomId) {
|
|
102
|
+
return this.request('GET', `/_matrix/client/v3/rooms/${encodePathSegment(roomId)}/members`);
|
|
103
|
+
}
|
|
104
|
+
async request(method, path, body) {
|
|
105
|
+
const headers = new Headers();
|
|
106
|
+
headers.set('Accept', 'application/json');
|
|
107
|
+
if (body !== undefined) {
|
|
108
|
+
headers.set('Content-Type', 'application/json');
|
|
109
|
+
}
|
|
110
|
+
if (this.accessToken) {
|
|
111
|
+
headers.set('Authorization', `${this.tokenType} ${this.accessToken}`);
|
|
112
|
+
}
|
|
113
|
+
const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
114
|
+
method,
|
|
115
|
+
headers,
|
|
116
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
117
|
+
});
|
|
118
|
+
const parsed = await parseMatrixJson(response);
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
const errorBody = isRecord(parsed) ? parsed : {};
|
|
121
|
+
const message = typeof errorBody.error === 'string'
|
|
122
|
+
? errorBody.error
|
|
123
|
+
: `Matrix request failed: ${response.status}`;
|
|
124
|
+
throw new MatrixClientError(message, {
|
|
125
|
+
status: response.status,
|
|
126
|
+
errcode: typeof errorBody.errcode === 'string' ? errorBody.errcode : undefined,
|
|
127
|
+
body: parsed,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return parsed;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
export function createMatrixClient(options) {
|
|
134
|
+
return new MatrixClient(options);
|
|
135
|
+
}
|
|
136
|
+
export function matrixServerNameFromBaseUrl(baseUrl) {
|
|
137
|
+
return new URL(baseUrl).host;
|
|
138
|
+
}
|
|
139
|
+
export function matrixUserIdFromWebId(webId, serverName) {
|
|
140
|
+
return `@${matrixLocalpartFromUserId(webId)}:${serverName}`;
|
|
141
|
+
}
|
|
142
|
+
export function matrixLocalpartFromUserId(userId) {
|
|
143
|
+
try {
|
|
144
|
+
const url = new URL(userId);
|
|
145
|
+
const withoutHash = `${url.pathname}${url.hash}`.replace(/^\/+/, '');
|
|
146
|
+
return matrixSlug(withoutHash || url.host);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return matrixSlug(userId);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export async function matrixRoomSurfaceId(roomId) {
|
|
153
|
+
const digest = await sha256Hex(roomId);
|
|
154
|
+
return `matrix-${digest.slice(0, 16)}`;
|
|
155
|
+
}
|
|
156
|
+
export async function matrixChatResourceIdFromRoomId(roomId) {
|
|
157
|
+
return `${await matrixRoomSurfaceId(roomId)}/index.ttl#this`;
|
|
158
|
+
}
|
|
159
|
+
export async function matrixThreadResourceIdFromRoomId(roomId) {
|
|
160
|
+
return `chat/${await matrixRoomSurfaceId(roomId)}/index.ttl#thread`;
|
|
161
|
+
}
|
|
162
|
+
function appendQuery(path, values) {
|
|
163
|
+
const params = new URLSearchParams();
|
|
164
|
+
for (const [key, value] of Object.entries(values)) {
|
|
165
|
+
if (value === undefined) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
params.set(key, String(value));
|
|
169
|
+
}
|
|
170
|
+
const query = params.toString();
|
|
171
|
+
return query ? `${path}?${query}` : path;
|
|
172
|
+
}
|
|
173
|
+
function encodePathSegment(value) {
|
|
174
|
+
return encodeURIComponent(value);
|
|
175
|
+
}
|
|
176
|
+
async function parseMatrixJson(response) {
|
|
177
|
+
const text = await response.text();
|
|
178
|
+
if (!text.trim()) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
return JSON.parse(text);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
throw new MatrixClientError(`Matrix request returned non-JSON error: ${response.status}`, {
|
|
187
|
+
status: response.status,
|
|
188
|
+
body: text,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function defaultRandomId() {
|
|
195
|
+
return globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2, 12);
|
|
196
|
+
}
|
|
197
|
+
function isRecord(value) {
|
|
198
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
199
|
+
}
|
|
200
|
+
function matrixSlug(value) {
|
|
201
|
+
return value.toLowerCase().replace(/[^a-z0-9._=-]+/g, '_').replace(/^_+|_+$/g, '') || 'user';
|
|
202
|
+
}
|
|
203
|
+
async function sha256Hex(value) {
|
|
204
|
+
const subtle = await resolveSubtleCrypto();
|
|
205
|
+
if (!subtle) {
|
|
206
|
+
throw new Error('Matrix room resource mapping requires Web Crypto SHA-256 support.');
|
|
207
|
+
}
|
|
208
|
+
const digest = await subtle.digest('SHA-256', new TextEncoder().encode(value));
|
|
209
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
210
|
+
}
|
|
211
|
+
async function resolveSubtleCrypto() {
|
|
212
|
+
if (globalThis.crypto?.subtle) {
|
|
213
|
+
return globalThis.crypto.subtle;
|
|
214
|
+
}
|
|
215
|
+
if (typeof process !== 'undefined' && process.versions?.node) {
|
|
216
|
+
const nodeCrypto = await import('node:crypto');
|
|
217
|
+
return nodeCrypto.webcrypto.subtle;
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare const baseRelativeResourceIdBrand: unique symbol;
|
|
2
|
+
declare const resourceIriBrand: unique symbol;
|
|
3
|
+
export type BaseRelativeResourceId = string & {
|
|
4
|
+
readonly [baseRelativeResourceIdBrand]: 'BaseRelativeResourceId';
|
|
5
|
+
};
|
|
6
|
+
export type ResourceIri = string & {
|
|
7
|
+
readonly [resourceIriBrand]: 'ResourceIri';
|
|
8
|
+
};
|
|
9
|
+
export declare function asBaseRelativeResourceId(value: string, label?: string): BaseRelativeResourceId;
|
|
10
|
+
export declare function asResourceIri(value: string, label?: string): ResourceIri;
|
|
11
|
+
export declare function requireRowResourceId(row: {
|
|
12
|
+
id?: string | null;
|
|
13
|
+
} | null | undefined, label?: string): BaseRelativeResourceId;
|
|
14
|
+
export declare function agentResourceId(key?: string | null): BaseRelativeResourceId;
|
|
15
|
+
export declare function agentKeyFromResourceId(resourceId: string): string;
|
|
16
|
+
export declare function agentHomeDirFromResourceId(resourceId: string): BaseRelativeResourceId;
|
|
17
|
+
export {};
|