@undefineds.co/linx 0.3.20 → 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 +53 -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/linx-cloud-errors.js +0 -5
- package/dist/lib/linx-cloud-errors.js.map +1 -1
- package/dist/lib/linx-status-line.js +1 -8
- package/dist/lib/linx-status-line.js.map +1 -1
- 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/branding.js +34 -103
- package/dist/lib/pi-adapter/branding.js.map +1 -1
- package/dist/lib/pi-adapter/interactive.js +35 -49
- package/dist/lib/pi-adapter/interactive.js.map +1 -1
- package/dist/lib/pi-adapter/pod-mirror.js +38 -107
- package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
- package/dist/lib/pi-adapter/pod-native.js +2 -0
- package/dist/lib/pi-adapter/pod-native.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/runtime.js +2 -12
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- 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/status-line-command.js +2 -2
- package/dist/lib/status-line-command.js.map +1 -1
- package/dist/lib/symphony/archive.js +15 -37
- package/dist/lib/symphony/archive.js.map +1 -1
- package/dist/lib/symphony/pod-projection.js +189 -1346
- package/dist/lib/symphony/pod-projection.js.map +1 -1
- package/dist/lib/symphony-command.js +209 -109
- 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 +7 -0
- package/dist/skills/xpod-cli/SKILL.md +2 -13
- 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 -1
- package/vendor/agent-runtime/dist/index.js +4 -1
- 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.d.ts +0 -11
- package/vendor/agent-runtime/dist/reconciler.js +5 -43
- package/vendor/agent-runtime/dist/symphony.d.ts +272 -27
- package/vendor/agent-runtime/dist/symphony.js +1268 -21
- 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
- package/dist/lib/capture/persistence.js +0 -377
- package/dist/lib/capture/persistence.js.map +0 -1
- package/dist/lib/capture/tool.js +0 -242
- package/dist/lib/capture/tool.js.map +0 -1
- package/dist/skills/basic/SKILL.md +0 -46
- package/dist/skills/capture/SKILL.md +0 -165
- package/vendor/agent-runtime/dist/coordination.d.ts +0 -93
- package/vendor/agent-runtime/dist/coordination.js +0 -145
|
@@ -581,6 +581,13 @@ Worker-facing minimum contract:
|
|
|
581
581
|
should move to models/drizzle-solid/xpod, live-verification gaps, or
|
|
582
582
|
deferred cleanup. Workers may recommend follow-up, but they do not decide
|
|
583
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.
|
|
584
591
|
- Never use chat, Delivery, or repo docs to redefine scope, acceptance,
|
|
585
592
|
compatibility, lifecycle state, or release boundary. Write an Implementation
|
|
586
593
|
Change Request instead.
|
|
@@ -18,14 +18,6 @@ secrets.
|
|
|
18
18
|
- Read or write RDF resources when the user asks for Pod-level state.
|
|
19
19
|
- Manage AI/provider credentials through secret-safe flows.
|
|
20
20
|
- Verify that LinX, xpod, or another Solid app wrote the expected resource.
|
|
21
|
-
- In Symphony mode, inspect and mutate control-plane resources through `xpod`
|
|
22
|
-
when the AI is acting from the terminal/tool surface. This includes Idea,
|
|
23
|
-
Issue, Task, Delivery, Run, RunStep, Report, Evidence, ApprovalRequest,
|
|
24
|
-
InputRequest, and InboxNotification resources.
|
|
25
|
-
- In capture workflows, use `xpod obj` for model-backed `CaptureCandidate` and
|
|
26
|
-
`CaptureEvent` resources after the `capture` skill has decided what should
|
|
27
|
-
be written. xpod does not decide whether something is worth capturing or how
|
|
28
|
-
it should be classified.
|
|
29
21
|
|
|
30
22
|
Do not use this skill for xpod CLI repository maintenance. If the task is to
|
|
31
23
|
change xpod command behavior or release xpod itself, use the xpod project docs
|
|
@@ -57,12 +49,9 @@ Summarize human-facing output instead of pasting large raw payloads.
|
|
|
57
49
|
- Authentication has one Solid-app source: `$SOLID_HOME/auth/credentials.json`
|
|
58
50
|
(default `~/.solid/auth/credentials.json`). Old `~/.xpod/config.json` /
|
|
59
51
|
`~/.xpod/secrets.json` files are app-local stale files, not Solid auth; if
|
|
60
|
-
only those exist, report unauthenticated.
|
|
61
|
-
a different WebID or Pod root than the shared Solid auth file, treat that as an
|
|
62
|
-
auth-store mismatch and stop before writing.
|
|
52
|
+
only those exist, report unauthenticated.
|
|
63
53
|
- For product resources, do not guess paths. Use model-backed `xpod obj`
|
|
64
|
-
commands or inspect existing links first.
|
|
65
|
-
TTL just because a path appears obvious.
|
|
54
|
+
commands or inspect existing links first.
|
|
66
55
|
|
|
67
56
|
## Useful Patterns
|
|
68
57
|
|
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,9 +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
|
-
export * from './coordination.js';
|
|
8
8
|
export * from './file-sync.js';
|
|
9
|
+
export * from './matrix-client.js';
|
|
10
|
+
export * from './pod-resource-identity.js';
|
|
9
11
|
export * from './reconciler.js';
|
|
10
12
|
export * from './runtime.js';
|
|
11
13
|
export * from './symphony.js';
|
|
@@ -13,3 +15,4 @@ export * from './sync.js';
|
|
|
13
15
|
export * from './thread-reconciler-controller.js';
|
|
14
16
|
export * from './turn-controller.js';
|
|
15
17
|
export * from './wake-scheduler.js';
|
|
18
|
+
export * from './workspace.js';
|
|
@@ -3,9 +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
|
-
export * from './coordination.js';
|
|
8
8
|
export * from './file-sync.js';
|
|
9
|
+
export * from './matrix-client.js';
|
|
10
|
+
export * from './pod-resource-identity.js';
|
|
9
11
|
export * from './reconciler.js';
|
|
10
12
|
export * from './runtime.js';
|
|
11
13
|
export * from './symphony.js';
|
|
@@ -13,3 +15,4 @@ export * from './sync.js';
|
|
|
13
15
|
export * from './thread-reconciler-controller.js';
|
|
14
16
|
export * from './turn-controller.js';
|
|
15
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 {};
|