dsh-team-rooms 1.0.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/ARCHITECTURE.md +127 -0
- package/LICENSE +201 -0
- package/README-es.md +248 -0
- package/README-hi.md +248 -0
- package/README-pt.md +248 -0
- package/README-zh.md +248 -0
- package/README.md +248 -0
- package/THIRD_PARTY_NOTICES.md +41 -0
- package/cordis.patch.yml +34 -0
- package/lib/client.js +4713 -0
- package/lib/index.js +6580 -0
- package/lib/types/audit.d.ts +80 -0
- package/lib/types/client/TeamRoomsSection.d.ts +33 -0
- package/lib/types/client/index.d.ts +41 -0
- package/lib/types/client/room-locales.d.ts +61 -0
- package/lib/types/client/room-presenter.d.ts +67 -0
- package/lib/types/facts.d.ts +66 -0
- package/lib/types/inbound.d.ts +282 -0
- package/lib/types/index.d.ts +125 -0
- package/lib/types/room/commands.d.ts +23 -0
- package/lib/types/room/domain.d.ts +72 -0
- package/lib/types/room/events.d.ts +105 -0
- package/lib/types/room/hub.d.ts +243 -0
- package/lib/types/room/projection.d.ts +388 -0
- package/lib/types/room/schema.d.ts +550 -0
- package/lib/types/room/tools.d.ts +34 -0
- package/lib/types/vocabulary.d.ts +20 -0
- package/package.json +170 -0
- package/src/audit.ts +113 -0
- package/src/client/TeamRoomsSection.module.css +272 -0
- package/src/client/TeamRoomsSection.tsx +369 -0
- package/src/client/css-modules.d.ts +5 -0
- package/src/client/index.ts +215 -0
- package/src/client/room-locales.ts +115 -0
- package/src/client/room-presenter.ts +126 -0
- package/src/facts.ts +127 -0
- package/src/inbound.ts +440 -0
- package/src/index.ts +309 -0
- package/src/room/commands.ts +278 -0
- package/src/room/domain.ts +38 -0
- package/src/room/events.ts +116 -0
- package/src/room/hub.ts +1022 -0
- package/src/room/projection.ts +267 -0
- package/src/room/schema.ts +153 -0
- package/src/room/tools.ts +603 -0
- package/src/vocabulary.ts +20 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-team-rooms: persistent, cross-session team rooms for DeepSeek Harness.
|
|
3
|
+
*
|
|
4
|
+
* A room is `{members (each an independent session), message bus
|
|
5
|
+
* (directed/broadcast), task board, shared timeline}` persisted in the
|
|
6
|
+
* harness's own storage layer — the `team_rooms` storage domain (SQLite or
|
|
7
|
+
* JSONL backend; the deployment chooses, the plugin adds no service of its
|
|
8
|
+
* own) — and it recovers across DSH restarts. The `/room` command family
|
|
9
|
+
* drives rooms from the user; the model gets eight `room_*` tools;
|
|
10
|
+
* cross-member task handoffs route through the official approval seam; and
|
|
11
|
+
* member sessions receive a one-line role-statement brief (Minimal persona
|
|
12
|
+
* style) on join and resume.
|
|
13
|
+
*
|
|
14
|
+
* Model-visible ⟺ recorded: every delivered room message is a durable
|
|
15
|
+
* `user/message` in the member's own session log, and the shared timeline
|
|
16
|
+
* mirrors as log-only `team-room/fact` events folded by the `teamRoom`
|
|
17
|
+
* projection the settings panel renders. The storage domain stays the
|
|
18
|
+
* cross-session authority; the projection is each member's own reconstructed
|
|
19
|
+
* copy.
|
|
20
|
+
*
|
|
21
|
+
* Extracted verbatim from `dsh-background-agents` 0.9.6, whose background-agent
|
|
22
|
+
* half is superseded by native DSH continuable subagents. Three identity
|
|
23
|
+
* strings must stay byte-identical to that version so existing profiles keep
|
|
24
|
+
* working: the storage domain `team_rooms`, the projection key `teamRoom`,
|
|
25
|
+
* and the session event type `team-room/fact` (plus the client slot id
|
|
26
|
+
* `team-rooms`).
|
|
27
|
+
*
|
|
28
|
+
* This half never touches `ctx.subagents`: no room operation spawns or steers
|
|
29
|
+
* a child agent, so the package carries no subagent dependency at all.
|
|
30
|
+
*
|
|
31
|
+
* @module dsh-team-rooms
|
|
32
|
+
*/
|
|
33
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
34
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
35
|
+
/** The cordis row name; the mounted graph id and the logger channel share it. */
|
|
36
|
+
export declare const name = "team-rooms";
|
|
37
|
+
/** Hard service dependencies: the tool registry, the agent registry, and the session store. */
|
|
38
|
+
export declare const inject: string[];
|
|
39
|
+
/**
|
|
40
|
+
* Lifecycle policy. Every tunable is a validated Config field: thresholds and
|
|
41
|
+
* retentions belong in cordis.yml, never in code. Nothing is required (rooms
|
|
42
|
+
* mount wherever the storage domain is composed); the Schemastery schema
|
|
43
|
+
* materializes the documented defaults from {@link DEFAULTS}, and direct
|
|
44
|
+
* apply() callers keep the same defaults.
|
|
45
|
+
*/
|
|
46
|
+
export interface Config {
|
|
47
|
+
/** Hard cap on team rooms across the profile. */
|
|
48
|
+
maxRooms?: number;
|
|
49
|
+
/** Hard cap on members per room. */
|
|
50
|
+
maxMembersPerRoom?: number;
|
|
51
|
+
/** Hard cap on rooms one member session may join. */
|
|
52
|
+
maxRoomsPerMember?: number;
|
|
53
|
+
/** Bus messages kept per room (the message retention window). */
|
|
54
|
+
busRetention?: number;
|
|
55
|
+
/** Timeline events kept per room. */
|
|
56
|
+
timelineRetention?: number;
|
|
57
|
+
/** Completed tasks kept per room; older `done` rows are pruned. */
|
|
58
|
+
taskRetention?: number;
|
|
59
|
+
/** Hard cap on one room message's text (rejected above, never truncated). */
|
|
60
|
+
maxMessageChars?: number;
|
|
61
|
+
/** Inject the short room brief into member sessions (join + resume). */
|
|
62
|
+
injectRoomBrief?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* How long the `team_rooms` storage-domain open may take before every
|
|
65
|
+
* room operation fails loud (`store-unavailable`) instead of hanging.
|
|
66
|
+
*/
|
|
67
|
+
roomOpenTimeoutMs?: number;
|
|
68
|
+
/**
|
|
69
|
+
* Force the log-only `team-room/fact` events even on hosts that drop the
|
|
70
|
+
* `ignorable` envelope marker (every released rc line through
|
|
71
|
+
* `0.1.0-rc.8`). Deliberately dangerous: unmarked fact events make
|
|
72
|
+
* sessions unresumable on stricter harness builds. Default `false` — the
|
|
73
|
+
* runtime detects such hosts and skips fact appends (the `teamRoom`
|
|
74
|
+
* projection degrades to an empty fold; the durable store and the
|
|
75
|
+
* model-visible deliveries keep working).
|
|
76
|
+
*/
|
|
77
|
+
allowUnmarkedFacts?: boolean;
|
|
78
|
+
/** Cross-ecosystem inbound bridge (P2) policy; absent = disabled. */
|
|
79
|
+
inbound?: InboundConfig;
|
|
80
|
+
}
|
|
81
|
+
/** Cross-ecosystem inbound bridge (P2) policy. */
|
|
82
|
+
export interface InboundConfig {
|
|
83
|
+
/**
|
|
84
|
+
* Enable the newline-delimited JSON-RPC 2.0 stdio inbound bridge that lets
|
|
85
|
+
* external agent runtimes (OpenAI Agents SDK / CrewAI) publish into team
|
|
86
|
+
* rooms. Default `false` (fail-closed — the bridge never spawns unless
|
|
87
|
+
* explicitly enabled).
|
|
88
|
+
*/
|
|
89
|
+
enabled?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* External runtime launch command. When `enabled` and present, the plugin
|
|
92
|
+
* spawns it and listens for notifications on its stdout. Absent or
|
|
93
|
+
* unspawnable = the bridge stays dormant with a logged warning.
|
|
94
|
+
*/
|
|
95
|
+
command?: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The single source of truth for every optional policy default: the schema
|
|
99
|
+
* materializes from it and apply() falls back to it, so the two can never
|
|
100
|
+
* drift apart.
|
|
101
|
+
*/
|
|
102
|
+
export declare const DEFAULTS: {
|
|
103
|
+
readonly maxRooms: 16;
|
|
104
|
+
readonly maxMembersPerRoom: 8;
|
|
105
|
+
readonly maxRoomsPerMember: 4;
|
|
106
|
+
readonly busRetention: 200;
|
|
107
|
+
readonly timelineRetention: 500;
|
|
108
|
+
readonly taskRetention: 50;
|
|
109
|
+
readonly maxMessageChars: 4000;
|
|
110
|
+
readonly injectRoomBrief: true;
|
|
111
|
+
readonly roomOpenTimeoutMs: 15000;
|
|
112
|
+
readonly allowUnmarkedFacts: false;
|
|
113
|
+
readonly inbound: {
|
|
114
|
+
readonly enabled: false;
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
export declare const Config: Schema<Config>;
|
|
118
|
+
/**
|
|
119
|
+
* Mount the room hub, the eight `room_*` tools, the `/room` command family,
|
|
120
|
+
* the `teamRoom` projection unit, and the room prompt section.
|
|
121
|
+
* @param ctx - context carrying the tool registry, the agent registry, and the session store.
|
|
122
|
+
* @param config - room policy (Schemastery-validated).
|
|
123
|
+
*/
|
|
124
|
+
export declare function apply(ctx: Context, config: Config): void;
|
|
125
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The human-facing `/room` command family: create, join, leave, list, send,
|
|
3
|
+
* tasks, and the task board subcommands (add / assign / claim / done). The
|
|
4
|
+
* command executes directly against the receiving session's agent — it never
|
|
5
|
+
* goes through the model — and every write lands on the shared durable room
|
|
6
|
+
* store. Because the human typed the command, command-side handoffs are
|
|
7
|
+
* already user-authorized (the model-facing `room_transfer_task` tool is
|
|
8
|
+
* what routes through the approval seam).
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-team-rooms/room/commands
|
|
11
|
+
*/
|
|
12
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
13
|
+
import { type RoomHub } from './hub.js';
|
|
14
|
+
/**
|
|
15
|
+
* Register the `/room` command when the harness composes the command
|
|
16
|
+
* registry. The handler runs outside any model turn, so it performs the
|
|
17
|
+
* mutations directly (the typing user is the authorizer). The command surface
|
|
18
|
+
* is optional: without it the room_* tools still work from the model side.
|
|
19
|
+
* @returns the exact command disposer, or undefined when no command registry
|
|
20
|
+
* is composed.
|
|
21
|
+
*/
|
|
22
|
+
export declare function registerRoomCommand(ctx: Context, hub: RoomHub): (() => void) | undefined;
|
|
23
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `team_rooms` storage-domain declaration: rooms, the message bus, the
|
|
3
|
+
* task board, and the shared timeline as four KV tables over the harness's
|
|
4
|
+
* own storage layer (SQLite or JSONL backend — the deployment chooses; the
|
|
5
|
+
* plugin adds no service of its own). Records are validated at the durable
|
|
6
|
+
* boundary by the same zod schemas the projection and the client share.
|
|
7
|
+
*
|
|
8
|
+
* The domain's single write chain is the ordering authority: every bus
|
|
9
|
+
* append and cursor bump queues on it, so concurrent posters can never
|
|
10
|
+
* interleave a read-modify-write.
|
|
11
|
+
*
|
|
12
|
+
* @module dsh-team-rooms/room/domain
|
|
13
|
+
*/
|
|
14
|
+
/** Branded-ish key aliases: append-table keys are plain `${roomId}/${…}` strings. */
|
|
15
|
+
export type RoomKey = string & {
|
|
16
|
+
readonly __roomKey?: never;
|
|
17
|
+
};
|
|
18
|
+
export type AppendKey = string & {
|
|
19
|
+
readonly __appendKey?: never;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The domain spec: identity, format version, and the four declared tables.
|
|
23
|
+
* The same schemas validate every record at the durable read boundary.
|
|
24
|
+
*/
|
|
25
|
+
export declare const teamRoomsDomainSpec: {
|
|
26
|
+
name: string;
|
|
27
|
+
version: number;
|
|
28
|
+
tables: {
|
|
29
|
+
rooms: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<RoomKey, {
|
|
30
|
+
name: string;
|
|
31
|
+
roomId: string;
|
|
32
|
+
createdAt: number;
|
|
33
|
+
members: {
|
|
34
|
+
sessionId: string;
|
|
35
|
+
role: "owner" | "member";
|
|
36
|
+
joinedAt: number;
|
|
37
|
+
lastDeliveredSeq: number;
|
|
38
|
+
lastFactSeq: number;
|
|
39
|
+
}[];
|
|
40
|
+
busNext: number;
|
|
41
|
+
timelineNext: number;
|
|
42
|
+
}>;
|
|
43
|
+
bus: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<AppendKey, {
|
|
44
|
+
text: string;
|
|
45
|
+
roomId: string;
|
|
46
|
+
createdAt: number;
|
|
47
|
+
seq: number;
|
|
48
|
+
senderSessionId: string;
|
|
49
|
+
toSessionId?: string | undefined;
|
|
50
|
+
}>;
|
|
51
|
+
tasks: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<AppendKey, {
|
|
52
|
+
status: "todo" | "in-progress" | "done";
|
|
53
|
+
title: string;
|
|
54
|
+
roomId: string;
|
|
55
|
+
createdAt: number;
|
|
56
|
+
taskId: string;
|
|
57
|
+
description: string;
|
|
58
|
+
assigneeSessionId: string | null;
|
|
59
|
+
createdBy: string;
|
|
60
|
+
updatedAt: number;
|
|
61
|
+
completedAt?: number | undefined;
|
|
62
|
+
}>;
|
|
63
|
+
timeline: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<AppendKey, {
|
|
64
|
+
at: number;
|
|
65
|
+
data: Record<string, unknown>;
|
|
66
|
+
roomId: string;
|
|
67
|
+
seq: number;
|
|
68
|
+
kind: "room-created" | "member-joined" | "member-left" | "message-posted" | "message-directed" | "task-created" | "task-claimed" | "task-assigned" | "task-completed";
|
|
69
|
+
}>;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=domain.d.ts.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The room-owned durable fact event: `team-room/fact`, appended to every
|
|
3
|
+
* MEMBER session log as a log-only record stamped with the envelope's
|
|
4
|
+
* `ignorable: true` marker (the same discipline as the background-agents
|
|
5
|
+
* fact channel). Two invariants hold:
|
|
6
|
+
*
|
|
7
|
+
* - model-visible ⟺ recorded: the messages the member's model sees are
|
|
8
|
+
* delivered through the official inbox (`agent.followup`/`agent.inject`,
|
|
9
|
+
* durable `user/message` events), while the shared timeline rides these
|
|
10
|
+
* log-only facts — the room store (`team_rooms` domain) stays the
|
|
11
|
+
* cross-session authority;
|
|
12
|
+
* - catch-up completeness: members offline while a fact landed receive every
|
|
13
|
+
* missed fact (and bus message) in store order on their next activation, so
|
|
14
|
+
* the `teamRoom` projection always reconstructs the shared view from the
|
|
15
|
+
* member's own log.
|
|
16
|
+
*
|
|
17
|
+
* Every timeline-bearing fact carries the canonical store `timelineSeq`, so
|
|
18
|
+
* the folded view orders exactly like the room store.
|
|
19
|
+
*
|
|
20
|
+
* @module dsh-team-rooms/room/events
|
|
21
|
+
*/
|
|
22
|
+
import type { RoomMember, TaskRecord, TimelineEvent } from './schema.js';
|
|
23
|
+
/** The log-only fact event type this plugin appends to member sessions. */
|
|
24
|
+
export declare const TEAM_ROOM_FACT: "team-room/fact";
|
|
25
|
+
/** One structured room fact, discriminated on `kind`. */
|
|
26
|
+
export type TeamRoomFact = {
|
|
27
|
+
/**
|
|
28
|
+
* This session became a member (create or join). Carries the full
|
|
29
|
+
* snapshot — room identity, member list, open tasks, and the retained
|
|
30
|
+
* timeline — so a cold member log reconstructs the shared view without
|
|
31
|
+
* reading the room store.
|
|
32
|
+
*/
|
|
33
|
+
readonly kind: 'room-joined';
|
|
34
|
+
readonly roomId: string;
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly createdAt: number;
|
|
37
|
+
readonly members: RoomMember[];
|
|
38
|
+
readonly tasks: TaskRecord[];
|
|
39
|
+
readonly timeline: TimelineEvent[];
|
|
40
|
+
} | {
|
|
41
|
+
/** Another session joined the room. */
|
|
42
|
+
readonly kind: 'member-joined';
|
|
43
|
+
readonly roomId: string;
|
|
44
|
+
readonly sessionId: string;
|
|
45
|
+
readonly role: RoomMember['role'];
|
|
46
|
+
readonly joinedAt: number;
|
|
47
|
+
readonly timelineSeq: number;
|
|
48
|
+
} | {
|
|
49
|
+
/** A member left (or the room was deleted). */
|
|
50
|
+
readonly kind: 'member-left';
|
|
51
|
+
readonly roomId: string;
|
|
52
|
+
readonly sessionId: string;
|
|
53
|
+
readonly timelineSeq: number;
|
|
54
|
+
} | {
|
|
55
|
+
/** One bus message; `toSessionId` marks a directed delivery. */
|
|
56
|
+
readonly kind: 'message-posted';
|
|
57
|
+
readonly roomId: string;
|
|
58
|
+
readonly seq: number;
|
|
59
|
+
readonly timelineSeq: number;
|
|
60
|
+
readonly senderSessionId: string;
|
|
61
|
+
readonly toSessionId?: string;
|
|
62
|
+
readonly text: string;
|
|
63
|
+
readonly createdAt: number;
|
|
64
|
+
} | {
|
|
65
|
+
/** A new task entered the board. */
|
|
66
|
+
readonly kind: 'task-created';
|
|
67
|
+
readonly roomId: string;
|
|
68
|
+
readonly taskId: string;
|
|
69
|
+
readonly title: string;
|
|
70
|
+
readonly description: string;
|
|
71
|
+
readonly assigneeSessionId: string | null;
|
|
72
|
+
readonly createdBy: string;
|
|
73
|
+
readonly createdAt: number;
|
|
74
|
+
readonly timelineSeq: number;
|
|
75
|
+
} | {
|
|
76
|
+
/** A member claimed a task (assignee + in-progress). */
|
|
77
|
+
readonly kind: 'task-claimed';
|
|
78
|
+
readonly roomId: string;
|
|
79
|
+
readonly taskId: string;
|
|
80
|
+
readonly assigneeSessionId: string;
|
|
81
|
+
readonly at: number;
|
|
82
|
+
readonly timelineSeq: number;
|
|
83
|
+
} | {
|
|
84
|
+
/** The owner handed a task to another member. */
|
|
85
|
+
readonly kind: 'task-assigned';
|
|
86
|
+
readonly roomId: string;
|
|
87
|
+
readonly taskId: string;
|
|
88
|
+
readonly assigneeSessionId: string;
|
|
89
|
+
readonly bySessionId: string;
|
|
90
|
+
readonly at: number;
|
|
91
|
+
readonly timelineSeq: number;
|
|
92
|
+
} | {
|
|
93
|
+
/** A task reached `done`. */
|
|
94
|
+
readonly kind: 'task-completed';
|
|
95
|
+
readonly roomId: string;
|
|
96
|
+
readonly taskId: string;
|
|
97
|
+
readonly at: number;
|
|
98
|
+
readonly timelineSeq: number;
|
|
99
|
+
};
|
|
100
|
+
declare module '@deepseek-ai/dsh-session/types' {
|
|
101
|
+
interface SessionEventMap {
|
|
102
|
+
[TEAM_ROOM_FACT]: TeamRoomFact;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RoomHub: the host-side service behind team rooms. It opens the
|
|
3
|
+
* `team_rooms` storage domain, owns every room mutation (membership, the
|
|
4
|
+
* message bus, the task board, the timeline), and drives delivery:
|
|
5
|
+
*
|
|
6
|
+
* - every write queues on ONE hub chain (the domain's single write chain is
|
|
7
|
+
* the ordering authority — concurrent posters cannot interleave a
|
|
8
|
+
* read-modify-write, and bus seqs mint strictly in commit order);
|
|
9
|
+
* - model-visible delivery goes through the official inbox
|
|
10
|
+
* (`agent.followup` wakes live members; offline members receive their
|
|
11
|
+
* backlog through `agent.inject` when their session next starts), so every
|
|
12
|
+
* model-visible room message is a durable `user/message` event in the
|
|
13
|
+
* member's own session log — model-visible ⟺ recorded;
|
|
14
|
+
* - the shared timeline mirrors into every member's log as log-only
|
|
15
|
+
* `team-room/fact` events (ignorable), so the `teamRoom` projection
|
|
16
|
+
* reconstructs the room view from each member's own durable log;
|
|
17
|
+
* - per-member delivery is at-least-once and ordered: a crash between bus
|
|
18
|
+
* commit and delivery re-delivers on catch-up (the cursor only advances
|
|
19
|
+
* after delivery), and per-member chains serialize delivery order.
|
|
20
|
+
*
|
|
21
|
+
* @module dsh-team-rooms/room/hub
|
|
22
|
+
*/
|
|
23
|
+
import { Context, Service } from '@deepseek-ai/cordis';
|
|
24
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
25
|
+
import { SessionId, type Session } from '@deepseek-ai/dsh-session';
|
|
26
|
+
import { type BusMessage, type RoomMember, type RoomRecord, type TaskRecord, type TimelineEvent } from './schema.js';
|
|
27
|
+
import type { FactAppender } from '../facts.js';
|
|
28
|
+
/** Tunables the room feature honors; every threshold is a validated Config field. */
|
|
29
|
+
export interface RoomConfig {
|
|
30
|
+
/** Hard cap on rooms across the profile. */
|
|
31
|
+
readonly maxRooms: number;
|
|
32
|
+
/** Hard cap on members per room. */
|
|
33
|
+
readonly maxMembersPerRoom: number;
|
|
34
|
+
/** Hard cap on rooms one member session may join. */
|
|
35
|
+
readonly maxRoomsPerMember: number;
|
|
36
|
+
/** Bus entries kept per room (the message retention window). */
|
|
37
|
+
readonly busRetention: number;
|
|
38
|
+
/** Timeline entries kept per room. */
|
|
39
|
+
readonly timelineRetention: number;
|
|
40
|
+
/** Completed tasks kept per room (older `done` rows are pruned). */
|
|
41
|
+
readonly taskRetention: number;
|
|
42
|
+
/** Hard cap on one bus message's text (rejected above, never truncated). */
|
|
43
|
+
readonly maxMessageChars: number;
|
|
44
|
+
/** Inject the short room brief into member sessions (join + resume). */
|
|
45
|
+
readonly injectRoomBrief: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* How long the `team_rooms` storage-domain open may take before every
|
|
48
|
+
* room operation fails loud (`store-unavailable`) instead of hanging
|
|
49
|
+
* forever (a stuck storage provider used to leave `/room` commands
|
|
50
|
+
* without a `command/done`).
|
|
51
|
+
*/
|
|
52
|
+
readonly roomOpenTimeoutMs: number;
|
|
53
|
+
}
|
|
54
|
+
/** A domain-level rejection with a stable code; tools and commands render it. */
|
|
55
|
+
export declare class RoomError extends Error {
|
|
56
|
+
readonly code: string;
|
|
57
|
+
constructor(code: string, message: string);
|
|
58
|
+
}
|
|
59
|
+
/** Read face of the live agent registry (kept narrow for tests). */
|
|
60
|
+
export interface LiveAgents {
|
|
61
|
+
get(id: SessionId): Agent | undefined;
|
|
62
|
+
}
|
|
63
|
+
/** Read face of the live session store (kept narrow for tests). */
|
|
64
|
+
export interface LiveSessions {
|
|
65
|
+
get(id: SessionId): Session | undefined;
|
|
66
|
+
}
|
|
67
|
+
/** Bus record the hub mints from a committed post. */
|
|
68
|
+
export interface PostedMessage {
|
|
69
|
+
readonly roomId: string;
|
|
70
|
+
readonly seq: number;
|
|
71
|
+
readonly senderSessionId: string;
|
|
72
|
+
readonly toSessionId?: string;
|
|
73
|
+
readonly text: string;
|
|
74
|
+
readonly createdAt: number;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The team-room service. Constructed in apply() with the validated room
|
|
78
|
+
* policy once the storage domain is available; {@link open} opens the
|
|
79
|
+
* `team_rooms` storage domain and the owning fiber closes it.
|
|
80
|
+
*/
|
|
81
|
+
export declare class RoomHub extends Service {
|
|
82
|
+
private readonly config;
|
|
83
|
+
private readonly agents;
|
|
84
|
+
private readonly sessions;
|
|
85
|
+
private readonly facts;
|
|
86
|
+
private rooms?;
|
|
87
|
+
private bus?;
|
|
88
|
+
private tasks?;
|
|
89
|
+
private timeline?;
|
|
90
|
+
/** One write chain: every mutation (create/join/post/task) queues here. */
|
|
91
|
+
private tail;
|
|
92
|
+
/** Per-room delivery chains: posts to one room serialize delivery order. */
|
|
93
|
+
private readonly roomChains;
|
|
94
|
+
/** Per-member delivery chains: live delivery and catch-up never interleave. */
|
|
95
|
+
private readonly memberChains;
|
|
96
|
+
/** Resolves once the storage domain is open (or failed); gates every operation. */
|
|
97
|
+
private readonly ready;
|
|
98
|
+
private readyResolve;
|
|
99
|
+
private initError;
|
|
100
|
+
constructor(ctx: Context, config: RoomConfig, agents: LiveAgents, sessions: LiveSessions, facts: FactAppender);
|
|
101
|
+
/**
|
|
102
|
+
* Open the `team_rooms` storage domain and load its four tables. Called
|
|
103
|
+
* once by the mount site after the storage domain becomes available; every
|
|
104
|
+
* hub operation gates on this resolution. A failed open fails every
|
|
105
|
+
* operation loud through {@link requireRooms} instead of hanging — and a
|
|
106
|
+
* STUCK open (a storage provider whose open promise never settles) is cut
|
|
107
|
+
* off by the `roomOpenTimeoutMs` timer so `/room` commands still settle
|
|
108
|
+
* with a `store-unavailable` error instead of never emitting
|
|
109
|
+
* `command/done`.
|
|
110
|
+
*/
|
|
111
|
+
open(): Promise<void>;
|
|
112
|
+
/** One room record, or undefined. */
|
|
113
|
+
room(roomId: string): Promise<RoomRecord | undefined>;
|
|
114
|
+
/** Every room one session is a member of, in creation order. */
|
|
115
|
+
roomsOfMember(sessionId: SessionId): Promise<RoomRecord[]>;
|
|
116
|
+
/** All rooms (used by the command surface for the roster). */
|
|
117
|
+
allRooms(): Promise<RoomRecord[]>;
|
|
118
|
+
/** Bus messages of one room with seq > since, in seq order. */
|
|
119
|
+
busMessages(roomId: string, since?: number): Promise<BusMessage[]>;
|
|
120
|
+
/** The task board of one room, creation order. */
|
|
121
|
+
tasksOf(roomId: string): Promise<TaskRecord[]>;
|
|
122
|
+
/** The timeline of one room with seq > since, in seq order. */
|
|
123
|
+
timelineOf(roomId: string, since?: number): Promise<TimelineEvent[]>;
|
|
124
|
+
/** The member slot of one session in one room, or undefined. */
|
|
125
|
+
memberOf(room: RoomRecord, sessionId: SessionId): RoomMember | undefined;
|
|
126
|
+
/**
|
|
127
|
+
* Create one room; the creator becomes its owner member. Enforces the
|
|
128
|
+
* profile-wide `maxRooms` cap inside the write chain.
|
|
129
|
+
*/
|
|
130
|
+
createRoom(sessionId: SessionId, name: string, now?: number): Promise<RoomRecord>;
|
|
131
|
+
/**
|
|
132
|
+
* Register one session as a member of an existing room (cross-session
|
|
133
|
+
* membership). Enforces the per-room and per-member caps inside the chain.
|
|
134
|
+
*/
|
|
135
|
+
joinRoom(sessionId: SessionId, roomId: string, now?: number): Promise<RoomRecord>;
|
|
136
|
+
/** Remove one member. The owner leaving deletes the room. */
|
|
137
|
+
leaveRoom(sessionId: SessionId, roomId: string, now?: number): Promise<RoomRecord | undefined>;
|
|
138
|
+
/** Owner-only room deletion. */
|
|
139
|
+
deleteRoom(sessionId: SessionId, roomId: string): Promise<void>;
|
|
140
|
+
/**
|
|
141
|
+
* Post one message onto the bus: broadcast, or directed when `toSessionId`
|
|
142
|
+
* names a member. Runs on the per-room delivery chain so per-member
|
|
143
|
+
* delivery order always equals bus seq order, and commits the store write
|
|
144
|
+
* before any delivery (cursors advance only after delivery — at-least-once).
|
|
145
|
+
*/
|
|
146
|
+
postMessage(input: {
|
|
147
|
+
readonly roomId: string;
|
|
148
|
+
readonly senderSessionId: SessionId;
|
|
149
|
+
readonly text: string;
|
|
150
|
+
readonly toSessionId?: SessionId;
|
|
151
|
+
}, now?: number): Promise<PostedMessage>;
|
|
152
|
+
/** Create a task on the board (assignee optional; default unassigned). */
|
|
153
|
+
createTask(input: {
|
|
154
|
+
readonly roomId: string;
|
|
155
|
+
readonly bySessionId: SessionId;
|
|
156
|
+
readonly title: string;
|
|
157
|
+
readonly description?: string;
|
|
158
|
+
readonly assigneeSessionId?: SessionId;
|
|
159
|
+
}, now?: number): Promise<TaskRecord>;
|
|
160
|
+
/** Claim a task for the calling member (in-progress + assignee). */
|
|
161
|
+
claimTask(input: {
|
|
162
|
+
readonly roomId: string;
|
|
163
|
+
readonly bySessionId: SessionId;
|
|
164
|
+
readonly taskId: string;
|
|
165
|
+
}, now?: number): Promise<TaskRecord>;
|
|
166
|
+
/**
|
|
167
|
+
* Hand a task to another member. Callers outside a tool (the /room command)
|
|
168
|
+
* are the user themselves; the room_transfer_task TOOL gates this same
|
|
169
|
+
* mutation behind the approval service.
|
|
170
|
+
*/
|
|
171
|
+
assignTask(input: {
|
|
172
|
+
readonly roomId: string;
|
|
173
|
+
readonly bySessionId: SessionId;
|
|
174
|
+
readonly taskId: string;
|
|
175
|
+
readonly toSessionId: SessionId;
|
|
176
|
+
}, now?: number): Promise<TaskRecord>;
|
|
177
|
+
/** Complete a task (done). Only the assignee or the owner may complete it. */
|
|
178
|
+
completeTask(input: {
|
|
179
|
+
readonly roomId: string;
|
|
180
|
+
readonly bySessionId: SessionId;
|
|
181
|
+
readonly taskId: string;
|
|
182
|
+
}, now?: number): Promise<TaskRecord>;
|
|
183
|
+
/**
|
|
184
|
+
* Deliver everything a member missed while offline: the log-only facts
|
|
185
|
+
* (shared timeline) and the model-visible bus backlog, both in store order.
|
|
186
|
+
* Runs on the member's delivery chain so a live post cannot interleave.
|
|
187
|
+
* Idempotent: cursors make a second call a no-op.
|
|
188
|
+
*/
|
|
189
|
+
catchUp(sessionId: SessionId): Promise<void>;
|
|
190
|
+
/**
|
|
191
|
+
* The member brief: a SHORT injected paragraph that starts with the
|
|
192
|
+
* one-line role statement (Minimal-persona style) and names the room id,
|
|
193
|
+
* the member count, and the collaboration tools. Injected on join and on
|
|
194
|
+
* every session start (resume included), as a durable user message — the
|
|
195
|
+
* member's model sees exactly what the member's log records.
|
|
196
|
+
*/
|
|
197
|
+
injectBrief(sessionId: SessionId, room: RoomRecord): void;
|
|
198
|
+
/** Build the minimal brief paragraph for one room. */
|
|
199
|
+
briefText(room: RoomRecord): string;
|
|
200
|
+
/**
|
|
201
|
+
* Queue one mutation on the single write chain; rejections are contained.
|
|
202
|
+
* The previous tail is captured SYNCHRONOUSLY: reading `this.tail` after
|
|
203
|
+
* `ready` resolves would see the just-assigned tail (a promise that settles
|
|
204
|
+
* with this very result) and deadlock the whole write chain — the exact
|
|
205
|
+
* hang that left `/room create` without a `command/done`.
|
|
206
|
+
*/
|
|
207
|
+
private enqueue;
|
|
208
|
+
/** Serialize work per room (delivery order = bus order). */
|
|
209
|
+
private onRoomChain;
|
|
210
|
+
/** Serialize delivery per member (live delivery vs catch-up). */
|
|
211
|
+
private onMemberChain;
|
|
212
|
+
/** One task-board mutation shared by claim/assign/complete. */
|
|
213
|
+
private mutateTask;
|
|
214
|
+
/** Append the room-joined snapshot fact to one session's live log. */
|
|
215
|
+
private appendFactTo;
|
|
216
|
+
/** Append one fact to every LIVE member session (offline members catch up). */
|
|
217
|
+
private broadcastFact;
|
|
218
|
+
/** Deliver one posted message's fact + model-visible copy, per member. */
|
|
219
|
+
private deliverPosted;
|
|
220
|
+
/** Advance delivery cursors for the members that just received the post. */
|
|
221
|
+
private advanceCursors;
|
|
222
|
+
/** Advance the fact cursor for every member whose session is live now. */
|
|
223
|
+
private advanceFactsForLive;
|
|
224
|
+
/** One model-visible delivery: a durable user message with a room header. */
|
|
225
|
+
private roomUserMessage;
|
|
226
|
+
/** The room-joined snapshot fact. */
|
|
227
|
+
private joinFact;
|
|
228
|
+
/** Rebuild the fact one timeline event corresponds to (catch-up replay). */
|
|
229
|
+
private factFromTimeline;
|
|
230
|
+
private taskCreatedFact;
|
|
231
|
+
private taskMutationFact;
|
|
232
|
+
private timelineEvent;
|
|
233
|
+
/** Retention pruning: bus, timeline, and completed tasks. */
|
|
234
|
+
private pruneRoom;
|
|
235
|
+
/** Delete every bus/task/timeline row of a deleted room. */
|
|
236
|
+
private purgeRoom;
|
|
237
|
+
private requireRoom;
|
|
238
|
+
private requireRooms;
|
|
239
|
+
private requireBus;
|
|
240
|
+
private requireTasks;
|
|
241
|
+
private requireTimeline;
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=hub.d.ts.map
|