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.
Files changed (46) hide show
  1. package/ARCHITECTURE.md +127 -0
  2. package/LICENSE +201 -0
  3. package/README-es.md +248 -0
  4. package/README-hi.md +248 -0
  5. package/README-pt.md +248 -0
  6. package/README-zh.md +248 -0
  7. package/README.md +248 -0
  8. package/THIRD_PARTY_NOTICES.md +41 -0
  9. package/cordis.patch.yml +34 -0
  10. package/lib/client.js +4713 -0
  11. package/lib/index.js +6580 -0
  12. package/lib/types/audit.d.ts +80 -0
  13. package/lib/types/client/TeamRoomsSection.d.ts +33 -0
  14. package/lib/types/client/index.d.ts +41 -0
  15. package/lib/types/client/room-locales.d.ts +61 -0
  16. package/lib/types/client/room-presenter.d.ts +67 -0
  17. package/lib/types/facts.d.ts +66 -0
  18. package/lib/types/inbound.d.ts +282 -0
  19. package/lib/types/index.d.ts +125 -0
  20. package/lib/types/room/commands.d.ts +23 -0
  21. package/lib/types/room/domain.d.ts +72 -0
  22. package/lib/types/room/events.d.ts +105 -0
  23. package/lib/types/room/hub.d.ts +243 -0
  24. package/lib/types/room/projection.d.ts +388 -0
  25. package/lib/types/room/schema.d.ts +550 -0
  26. package/lib/types/room/tools.d.ts +34 -0
  27. package/lib/types/vocabulary.d.ts +20 -0
  28. package/package.json +170 -0
  29. package/src/audit.ts +113 -0
  30. package/src/client/TeamRoomsSection.module.css +272 -0
  31. package/src/client/TeamRoomsSection.tsx +369 -0
  32. package/src/client/css-modules.d.ts +5 -0
  33. package/src/client/index.ts +215 -0
  34. package/src/client/room-locales.ts +115 -0
  35. package/src/client/room-presenter.ts +126 -0
  36. package/src/facts.ts +127 -0
  37. package/src/inbound.ts +440 -0
  38. package/src/index.ts +309 -0
  39. package/src/room/commands.ts +278 -0
  40. package/src/room/domain.ts +38 -0
  41. package/src/room/events.ts +116 -0
  42. package/src/room/hub.ts +1022 -0
  43. package/src/room/projection.ts +267 -0
  44. package/src/room/schema.ts +153 -0
  45. package/src/room/tools.ts +603 -0
  46. package/src/vocabulary.ts +20 -0
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Host-capability detection for the `ignorable` envelope-marker surface
3
+ * that every log-only plugin fact event depends on.
4
+ *
5
+ * `Session.append(type, data, { ignorable: true })` stamps the envelope
6
+ * marker on host builds that expose the surface (harness master
7
+ * `@deepseek-ai/dsh-session`); every released rc line through `0.1.0-rc.8`
8
+ * and the `0.1.1-rc` line silently drop the options bag, and the
9
+ * `0.1.2-alpha`/`0.1.2-rc` lines keep the envelope field for read
10
+ * compatibility but never accept an options bag either (the third append
11
+ * parameter is `SurfaceIntent` for surface event types only), so fact
12
+ * events land unmarked on all of them and stricter hosts refuse to resume
13
+ * those sessions (`SessionFormatUnsupportedError`).
14
+ * The fact appender detects the host before polluting a log: the installed
15
+ * peer version is checked against the known-unmarked lines first, and an
16
+ * unknown (unresolvable) version is verified by probing the FIRST
17
+ * appended event's returned envelope. The same discipline lives in
18
+ * `dsh-permission-rules` and `dsh-auto-review`.
19
+ * @module dsh-team-rooms/audit
20
+ */
21
+ /** Host envelope capability: unknown until the first append (or the peer-version pre-check). */
22
+ export type AuditSupport = 'unknown' | 'supported' | 'unsupported';
23
+ /**
24
+ * Whether an `append` call actually honored the `ignorable` marker: the
25
+ * logged event returned by the host carries `ignorable === true` on
26
+ * marker-aware builds and nothing on pre-marker builds. `false` (or any
27
+ * non-event return) means the host dropped the marker and the event landed
28
+ * unmarked — the appender then degrades instead of polluting further logs.
29
+ * @param result - the return value of the fact append.
30
+ * @returns true only when the marker is present on the returned envelope.
31
+ */
32
+ export declare function isMarkedAuditEvent(result: unknown): boolean;
33
+ /**
34
+ * Whether a `@deepseek-ai/dsh-session` version line predates the
35
+ * `ignorable` envelope-marker surface: every released rc line through
36
+ * `0.1.0-rc.8` silently drops the marker from `Session.append` options
37
+ * (the stamping fix exists on harness master only — no release carries it
38
+ * yet), and the `0.1.1-rc` line regressed the same way (verified on
39
+ * `0.1.1-rc.2`), so fact events written by those builds land unmarked and
40
+ * break resume on stricter hosts. The `0.1.2` line keeps the `ignorable`
41
+ * field on the event envelope but has no append option that writes it
42
+ * (the third parameter is `SurfaceIntent` for surface event types only),
43
+ * so every `0.1.2-rc` build is known-unmarked too (verified on
44
+ * `0.1.2-rc.1`). The gate therefore treats
45
+ * `0.1.1-rc.1`–`rc.8` as known-unmarked as well; over-refusal is harmless
46
+ * because `allowUnmarkedFacts: true` opts back in. Extend the bound when a
47
+ * new rc line ships that still drops the marker. Non-matching (later rc,
48
+ * stable, or unresolvable) versions are treated as possibly-marker-aware
49
+ * and verified by the append probe.
50
+ * @param version - the installed peer version string.
51
+ * @returns true for the known-unmarked rc.1–rc.8 lines of `0.1.0` and
52
+ * `0.1.1`, and every `0.1.2-rc` build.
53
+ */
54
+ export declare function isUnmarkedHostVersion(version: string): boolean;
55
+ /**
56
+ * The installed `@deepseek-ai/dsh-session` version, or `null` when
57
+ * unresolvable (falls back to the append probe).
58
+ * @returns the version string, or null when the peer cannot be resolved.
59
+ */
60
+ export declare function peerSessionVersion(): string | null;
61
+ /**
62
+ * Host policy for log-only fact events.
63
+ * `append` = the rc lines through `0.1.1-rc.2`: fact events may be
64
+ * appended when the `ignorable` marker is honored; `forbidden` = hosts at
65
+ * `0.1.2-alpha.1` and later (including every `0.1.2-rc` build), which fail
66
+ * closed on the session event
67
+ * vocabulary (`background-agents/fact` and `team-room/fact` are not in
68
+ * KNOWN_SESSION_EVENT_TYPES, so a written fact makes the session
69
+ * unreadable there) — never append fact events on those hosts.
70
+ */
71
+ export type FactEventPolicy = 'append' | 'forbidden' | 'unknown';
72
+ /**
73
+ * Classify an installed `@deepseek-ai/dsh-session` version for log-only
74
+ * fact events: `forbidden` at `0.1.2-alpha.1` and later, `append` on the
75
+ * earlier rc lines, `unknown` for unresolvable versions.
76
+ * @param version - the installed peer version string.
77
+ * @returns the fact-event policy for that host line.
78
+ */
79
+ export declare function factEventPolicyForVersion(version: string): FactEventPolicy;
80
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1,33 @@
1
+ import type { PropsLocale, PropsRuntime, InjectFace } from '@deepseek-ai/dsh-client-ui-slots';
2
+ /** Minimal structural snapshot contract (the owner package no longer re-exports the generic). */
3
+ interface ObservableSnapshot<T> {
4
+ getSnapshot(): T;
5
+ subscribe(listener: () => void): () => void;
6
+ }
7
+ import type { TeamRoomsState } from './room-presenter.js';
8
+ import { ROOM_NS } from './room-locales.js';
9
+ /** Business actions supplied by the slot registration (all via /room command execution). */
10
+ export interface TeamRoomsInjected {
11
+ hooks: {
12
+ /** The live controller state: current session id + derived room panels. */
13
+ teamRooms: ObservableSnapshot<TeamRoomsState>;
14
+ };
15
+ /** The current session id (commands execute against it), or undefined. */
16
+ getSessionId(): string | undefined;
17
+ create(name: string): Promise<string | undefined>;
18
+ join(roomId: string): Promise<string | undefined>;
19
+ leave(roomId: string): Promise<string | undefined>;
20
+ post(roomId: string, text: string): Promise<string | undefined>;
21
+ addTask(roomId: string, title: string): Promise<string | undefined>;
22
+ claimTask(roomId: string, taskId: string): Promise<string | undefined>;
23
+ completeTask(roomId: string, taskId: string): Promise<string | undefined>;
24
+ assignTask(roomId: string, taskId: string, member: string): Promise<string | undefined>;
25
+ }
26
+ /** The action share one room card needs (everything except the bound hooks). */
27
+ export type RoomActions = Omit<TeamRoomsInjected, 'hooks'>;
28
+ /** Full component props: the settings-section owner share, locale, and the bound inject face. */
29
+ export type TeamRoomsSectionProps = PropsRuntime<'settings.section'> & PropsLocale<typeof ROOM_NS> & InjectFace<TeamRoomsInjected>;
30
+ /** The settings section: room management over the live session's projection. */
31
+ export declare function TeamRoomsSection(props: TeamRoomsSectionProps): import("react").JSX.Element;
32
+ export {};
33
+ //# sourceMappingURL=TeamRoomsSection.d.ts.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * dsh-team-rooms, browser half: the Team Rooms `settings.section` page.
3
+ * Member status, the shared task board, and the timeline of every room the
4
+ * current session belongs to, read from the current session's `teamRoom`
5
+ * projection (live `faceOf` snapshots) and written back through the HOST
6
+ * `/room` command (`remote.commands.execute`) so every action keeps the
7
+ * durable command lifecycle. Zero custom RPC: the page rides the sanctioned
8
+ * plugin channels only.
9
+ *
10
+ * Extracted from the single client entry of `dsh-background-agents` 0.9.6,
11
+ * which served two slots from one bundle (`sidebar.footer.action`
12
+ * id `background-agents` for the background-agent panel, and this
13
+ * `settings.section` id `team-rooms`). Only the room half is shipped here;
14
+ * the slot id stays byte-identical so an installed settings page keeps
15
+ * resolving to this plugin.
16
+ *
17
+ * The client half reads no subagent surface at all: it needs `sessions`
18
+ * (list + bindings), `slots`, `locale`, and the remote command executor.
19
+ */
20
+ import type { Context } from '@deepseek-ai/cordis';
21
+ import { type TeamRoomsKey } from './room-locales.js';
22
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
23
+ interface LocaleNamespaceMap {
24
+ /** Team-room settings panel copy. */
25
+ teamRooms: TeamRoomsKey;
26
+ }
27
+ }
28
+ export type { TeamRoomsInjected, TeamRoomsSectionProps, } from './TeamRoomsSection.js';
29
+ /**
30
+ * Required services: sessions (list + bindings), slots, locale, the wire
31
+ * client, and the remote command executor (`remote.commands`, which carries
32
+ * every `/room` line this page issues). `connection` is deliberately absent —
33
+ * the room page never calls a remote namespace directly.
34
+ */
35
+ export declare const inject: string[];
36
+ /**
37
+ * Register the Team Rooms settings page.
38
+ * @param ctx - client root context.
39
+ */
40
+ export declare function apply(ctx: Context): void;
41
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,61 @@
1
+ /** `teamRooms` namespace dictionaries (the settings page panel copy). */
2
+ /** Dictionary namespace owned by the room panel. */
3
+ export declare const ROOM_NS = "teamRooms";
4
+ /** Simplified Chinese dictionary (the key-set source of truth). */
5
+ export declare const zh: {
6
+ readonly nav: "团队房间";
7
+ readonly title: "团队房间";
8
+ readonly intro: "跨会话的持久多 agent 协作:成员注册、消息总线、共享任务板与时间线。状态存在本地存储层(SQLite/JSONL),DSH 重启后自动恢复。";
9
+ readonly noSession: "暂无活动会话。打开一个会话后再管理团队房间。";
10
+ readonly empty: "本会话尚未加入任何团队房间。创建一个房间,或粘贴其它会话分享的房间 id 加入。";
11
+ readonly create: "创建房间";
12
+ readonly createPlaceholder: "房间名称…";
13
+ readonly createAction: "创建";
14
+ readonly join: "加入房间";
15
+ readonly joinPlaceholder: "房间 id…";
16
+ readonly joinAction: "加入";
17
+ readonly roomId: "房间 id";
18
+ readonly copy: "复制";
19
+ readonly copied: "已复制";
20
+ readonly members: "成员";
21
+ readonly owner: "房主";
22
+ readonly member: "成员";
23
+ readonly live: "在线";
24
+ readonly offline: "离线";
25
+ readonly tasks: "任务板";
26
+ readonly tasksEmpty: "暂无任务 —— 用 /room task add 或 room_create_task 添加。";
27
+ readonly taskAddPlaceholder: "新任务标题…";
28
+ readonly taskAdd: "添加";
29
+ readonly claim: "认领";
30
+ readonly assign: "交接";
31
+ readonly assignTo: "成员 id 或 me";
32
+ readonly assignAction: "确定";
33
+ readonly done: "完成";
34
+ readonly unassigned: "未分配";
35
+ readonly statusTodo: "待办";
36
+ readonly statusInProgress: "进行中";
37
+ readonly statusDone: "完成";
38
+ readonly timeline: "时间线";
39
+ readonly timelineEmpty: "暂无事件。";
40
+ readonly timelineMessage: "{sender} 广播:{text}";
41
+ readonly timelineDirected: "{sender} → {to}:{text}";
42
+ readonly timelineMemberJoined: "{member} 加入了房间";
43
+ readonly timelineMemberLeft: "{member} 离开了房间";
44
+ readonly timelineTaskCreated: "创建任务「{title}」";
45
+ readonly timelineTaskClaimed: "{member} 认领了任务";
46
+ readonly timelineTaskAssigned: "任务交接给 {member}";
47
+ readonly timelineTaskCompleted: "任务完成";
48
+ readonly timelineRoomCreated: "{member} 创建了房间";
49
+ readonly timelineUnknown: "事件 #{seq}";
50
+ readonly message: "发消息";
51
+ readonly messagePlaceholder: "广播给所有成员…";
52
+ readonly send: "发送";
53
+ readonly error: "操作失败";
54
+ readonly busy: "处理中…";
55
+ readonly leave: "离开房间";
56
+ };
57
+ /** English dictionary, key-identical to the Chinese source of truth. */
58
+ export declare const en: Record<TeamRoomsKey, string>;
59
+ /** The `teamRooms` namespace key union. */
60
+ export type TeamRoomsKey = keyof typeof zh;
61
+ //# sourceMappingURL=room-locales.d.ts.map
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Pure presentation of the team-room settings panel: derives the display
3
+ * state from the `teamRoom` projection value plus the session-list snapshot
4
+ * (the live overlay for member status). No I/O, clock, or randomness — the
5
+ * component stays a thin binder and the rows stay testable.
6
+ *
7
+ * @module dsh-team-rooms/room-presenter
8
+ */
9
+ import { type TimelineEvent } from '../room/schema.js';
10
+ /** The session-list face the presenter reads (host-sampled `running` per session). */
11
+ export interface SessionListLike {
12
+ byId: Record<string, {
13
+ id: string;
14
+ running?: boolean;
15
+ displayTitle?: string;
16
+ }>;
17
+ }
18
+ /** One member row with the live overlay applied. */
19
+ export interface RoomMemberRow {
20
+ readonly sessionId: string;
21
+ readonly role: 'owner' | 'member';
22
+ readonly joinedAt: number;
23
+ /** Live bit from the session list (this browser's own view of the process). */
24
+ readonly live: boolean;
25
+ readonly title?: string;
26
+ }
27
+ /** One task-board row. */
28
+ export interface RoomTaskRow {
29
+ readonly taskId: string;
30
+ readonly title: string;
31
+ readonly description: string;
32
+ readonly status: 'todo' | 'in-progress' | 'done';
33
+ readonly assigneeSessionId: string | null;
34
+ readonly createdBy: string;
35
+ }
36
+ /** One rendered timeline entry (the raw event plus its localized label inputs). */
37
+ export interface RoomTimelineRow {
38
+ readonly seq: number;
39
+ readonly kind: TimelineEvent['kind'];
40
+ readonly data: TimelineEvent['data'];
41
+ }
42
+ /** One rendered room panel. */
43
+ export interface RoomPanel {
44
+ readonly roomId: string;
45
+ readonly name: string;
46
+ readonly createdAt: number;
47
+ readonly members: RoomMemberRow[];
48
+ readonly tasks: RoomTaskRow[];
49
+ readonly timeline: RoomTimelineRow[];
50
+ }
51
+ /** The controller state the section component selects from. */
52
+ export interface TeamRoomsState {
53
+ readonly status: 'ready' | 'unavailable';
54
+ /** The current session id, when a conversation is open. */
55
+ readonly sessionId?: string;
56
+ readonly rooms: RoomPanel[];
57
+ }
58
+ /** Empty ready state (no session open). */
59
+ export declare function emptyTeamRoomsState(): TeamRoomsState;
60
+ /**
61
+ * Derive the room panels from one projection value and the session list.
62
+ * @param value - the opaque `teamRoom` projection cell.
63
+ * @param list - the session-list snapshot (live overlay source).
64
+ * @returns the panels, or undefined when the cell is absent or invalid.
65
+ */
66
+ export declare function buildRoomPanels(value: unknown, list: SessionListLike): RoomPanel[] | undefined;
67
+ //# sourceMappingURL=room-presenter.d.ts.map
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The single append seam for the log-only `team-room/fact` event that carries
3
+ * the shared room timeline into every member session.
4
+ *
5
+ * Two host lines gate the appender:
6
+ * - rc lines through `0.1.1-rc.2`: `Session.append` predates the
7
+ * `ignorable` envelope marker, so a fact event lands unmarked and
8
+ * stricter harness builds refuse to resume the session. The appender
9
+ * detects such hosts before the first append and disables fact appends
10
+ * with a one-time warning.
11
+ * - `0.1.2-alpha.1` and later: the host fails closed on the session event
12
+ * vocabulary - `team-room/fact` is not in KNOWN_SESSION_EVENT_TYPES, so
13
+ * writing one makes the session unreadable there. On those hosts fact
14
+ * events are never appended; each fact is routed to the injected fallback
15
+ * sink (logger/panel channel) instead.
16
+ * `allowUnmarkedFacts: true` opts back into unmarked appends - deliberately
17
+ * dangerous - and already-polluted logs can be repaired with
18
+ * `scripts/repair-session-logs.mjs` from `dsh-permission-rules`.
19
+ *
20
+ * Copied from `dsh-background-agents` 0.9.6 with exactly two changes: the
21
+ * header docstring names only the room fact channel, and {@link FactEventType}
22
+ * is narrowed to `'team-room/fact'`. The host gating itself is unchanged, so a
23
+ * profile holding `team_rooms` facts written by 0.9.6 replays them here
24
+ * identically. (The source carried five mojibake sequences - U+9205 where an
25
+ * em dash was intended - which are written as plain hyphens here.)
26
+ * @module dsh-team-rooms/facts
27
+ */
28
+ import type { Session } from '@deepseek-ai/dsh-session';
29
+ /** The log-only fact event type this appender owns. */
30
+ export type FactEventType = 'team-room/fact';
31
+ /**
32
+ * Host-gated fact appender. One instance per plugin mount, shared by the
33
+ * room hub and the room tools.
34
+ */
35
+ export declare class FactAppender {
36
+ private readonly allowUnmarked;
37
+ private readonly warn;
38
+ private readonly fallback?;
39
+ private support;
40
+ private policy;
41
+ private warned;
42
+ private warnedForbidden;
43
+ constructor(allowUnmarked: boolean, warn: (message: string) => void, fallback?: ((type: FactEventType, data: unknown) => void) | undefined);
44
+ /**
45
+ * Append one log-only fact, requesting the envelope's `ignorable: true`
46
+ * marker. On hosts whose event vocabulary forbids the fact events
47
+ * (`0.1.2-alpha.1+`) the record is routed to the fallback sink instead
48
+ * of the session log; on pre-marker rc hosts (and after a failed probe)
49
+ * the append is skipped so the session log stays loadable everywhere. On 0.1.2-alpha.3 the envelope field is retained for stored-log read compatibility only - its Session.append still cannot stamp the marker, so the gate behavior is unchanged.
50
+ * Append failures are contained: a fact hiccup never disturbs the
51
+ * caller's operation.
52
+ * @param session - the session whose log carries the fact.
53
+ * @param type - the fact event type.
54
+ * @param data - the fact payload.
55
+ */
56
+ append(session: Session, type: FactEventType, data: unknown): void;
57
+ /** Whether the host's event vocabulary still accepts the log-only fact events; the pre-check runs once. */
58
+ private factEventsAllowed;
59
+ /** Whether the host stamps the marker (or the dangerous opt-in is set); the pre-check runs once. */
60
+ private mayAppend;
61
+ /** After the first append on an unversioned host, probe the returned envelope for the marker. */
62
+ private probe;
63
+ /** One-time warning that fact appends were disabled to keep session logs loadable. */
64
+ private warnOnce;
65
+ }
66
+ //# sourceMappingURL=facts.d.ts.map
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Cross-ecosystem inbound (P2): a minimal newline-delimited JSON-RPC 2.0
3
+ * bridge over stdio that lets external agent runtimes — OpenAI Agents SDK,
4
+ * CrewAI, and similar — publish into a team room.
5
+ *
6
+ * This is a JSON-RPC direct-connect minimal set, not the official ACP wire
7
+ * protocol: full ACP compatibility waits for the upstream seam. The bridge
8
+ * exposes one seam ({@link InboundCoordinator.registerInboundAdapter}) that
9
+ * returns a disposer, and one concrete adapter ({@link StdioJsonRpcInbound})
10
+ * that spawns a runtime command and listens on its stdout.
11
+ *
12
+ * Wire shape (one JSON notification per line on the child's stdout):
13
+ *
14
+ * - `jsonrpc` is always `"2.0"`; `method` is the event name
15
+ * (`agent_started` | `agent_message` | `agent_finished`).
16
+ * - `params` carries the payload: `name` (the external agent's display
17
+ * name), `room` (the team-room id), `traceId` (the correlation id),
18
+ * `status` (`ok` | `error`, only meaningful on `agent_finished`),
19
+ * `message` (the text), and optional `usage` token accounting.
20
+ *
21
+ * Mapping onto the team room's existing surfaces (see {@link deliveriesFor}):
22
+ * `agent_started` opens a task-board card, `agent_message` posts to the
23
+ * message bus, and `agent_finished` closes the card and posts the outcome.
24
+ * Every invalid message fails closed: it is dropped and a JSON-RPC error
25
+ * response is written back to the child's stdin. The owning fiber disposes
26
+ * the adapter (kills the child, removes listeners) through the returned
27
+ * disposer; an unspawnable command degrades to a logged warning and a
28
+ * dormant bridge.
29
+ *
30
+ * @module dsh-team-rooms/inbound
31
+ */
32
+ import { type ChildProcess, type SpawnOptions } from 'node:child_process';
33
+ import { z } from 'zod';
34
+ /** A JSON-RPC 2.0 id: a string, a number, or null (notifications carry none). */
35
+ export type JsonRpcId = string | number | null;
36
+ /** The three recognized inbound event names (the JSON-RPC `method`). */
37
+ export declare const INBOUND_METHODS: readonly ["agent_started", "agent_message", "agent_finished"];
38
+ /** One recognized inbound event name. */
39
+ export type InboundMethod = (typeof INBOUND_METHODS)[number];
40
+ /** Optional token accounting the runtime may report on `agent_finished`. */
41
+ export declare const inboundUsageSchema: z.ZodObject<{
42
+ /** Un-cached input tokens of the finished run. */
43
+ inputTokens: z.ZodNumber;
44
+ /** Output tokens of the finished run. */
45
+ outputTokens: z.ZodNumber;
46
+ }, "strict", z.ZodTypeAny, {
47
+ inputTokens: number;
48
+ outputTokens: number;
49
+ }, {
50
+ inputTokens: number;
51
+ outputTokens: number;
52
+ }>;
53
+ /** Token accounting the runtime may report on `agent_finished`. */
54
+ export type InboundUsage = z.infer<typeof inboundUsageSchema>;
55
+ /**
56
+ * The `params` payload every inbound notification carries. `name` is the
57
+ * external agent's display name; `room` the team-room id; `traceId` the
58
+ * correlation id; `status` the ok/error outcome (only `agent_finished`);
59
+ * `message` the text (required for `agent_message`); `usage` optional token
60
+ * accounting. `.strict()` rejects unknown fields — fail-closed by default.
61
+ */
62
+ export declare const inboundParamsSchema: z.ZodObject<{
63
+ name: z.ZodString;
64
+ room: z.ZodString;
65
+ traceId: z.ZodString;
66
+ status: z.ZodOptional<z.ZodEnum<["ok", "error"]>>;
67
+ message: z.ZodOptional<z.ZodString>;
68
+ usage: z.ZodOptional<z.ZodObject<{
69
+ /** Un-cached input tokens of the finished run. */
70
+ inputTokens: z.ZodNumber;
71
+ /** Output tokens of the finished run. */
72
+ outputTokens: z.ZodNumber;
73
+ }, "strict", z.ZodTypeAny, {
74
+ inputTokens: number;
75
+ outputTokens: number;
76
+ }, {
77
+ inputTokens: number;
78
+ outputTokens: number;
79
+ }>>;
80
+ }, "strict", z.ZodTypeAny, {
81
+ name: string;
82
+ room: string;
83
+ traceId: string;
84
+ message?: string | undefined;
85
+ status?: "ok" | "error" | undefined;
86
+ usage?: {
87
+ inputTokens: number;
88
+ outputTokens: number;
89
+ } | undefined;
90
+ }, {
91
+ name: string;
92
+ room: string;
93
+ traceId: string;
94
+ message?: string | undefined;
95
+ status?: "ok" | "error" | undefined;
96
+ usage?: {
97
+ inputTokens: number;
98
+ outputTokens: number;
99
+ } | undefined;
100
+ }>;
101
+ /** One decoded JSON-RPC request line (params left unknown until mapped). */
102
+ export interface InboundRequest {
103
+ /** The JSON-RPC `method`, expected to be one of {@link INBOUND_METHODS}. */
104
+ readonly method: string;
105
+ /** The raw `params` value; validated by {@link mapInboundEvent}. */
106
+ readonly params: unknown;
107
+ /** The request id, or null when the line was a notification (no id). */
108
+ readonly id: JsonRpcId;
109
+ }
110
+ /** A parsed stdin line: either a valid request or a protocol error to report back. */
111
+ export type InboundParse = {
112
+ readonly ok: true;
113
+ readonly request: InboundRequest;
114
+ } | {
115
+ readonly ok: false;
116
+ readonly id: JsonRpcId;
117
+ readonly code: number;
118
+ readonly message: string;
119
+ };
120
+ /** A mapped event: either a normalized event or an invalid-params error. */
121
+ export type InboundDecode = {
122
+ readonly ok: true;
123
+ readonly event: InboundEvent;
124
+ } | {
125
+ readonly ok: false;
126
+ readonly code: number;
127
+ readonly message: string;
128
+ };
129
+ /**
130
+ * One normalized inbound event, discriminated on the JSON-RPC `method`.
131
+ * `name` is the external agent's display name; `roomId`/`traceId` come from
132
+ * `params.room`/`params.traceId`.
133
+ */
134
+ export type InboundEvent = {
135
+ readonly method: 'agent_started';
136
+ readonly name: string;
137
+ readonly roomId: string;
138
+ readonly traceId: string;
139
+ readonly message?: string;
140
+ } | {
141
+ readonly method: 'agent_message';
142
+ readonly name: string;
143
+ readonly roomId: string;
144
+ readonly traceId: string;
145
+ readonly message: string;
146
+ } | {
147
+ readonly method: 'agent_finished';
148
+ readonly name: string;
149
+ readonly roomId: string;
150
+ readonly traceId: string;
151
+ readonly status: 'ok' | 'error';
152
+ readonly message?: string;
153
+ readonly usage?: InboundUsage;
154
+ };
155
+ /**
156
+ * One room write the bridge performs for a normalized event: a task-board
157
+ * card open/close or a message-bus post. `traceId` correlates the card with
158
+ * the external run so `agent_finished` can close the card `agent_started`
159
+ * opened.
160
+ */
161
+ export type InboundDelivery = {
162
+ readonly kind: 'task-open';
163
+ readonly roomId: string;
164
+ readonly traceId: string;
165
+ readonly title: string;
166
+ } | {
167
+ readonly kind: 'bus-post';
168
+ readonly roomId: string;
169
+ readonly traceId: string;
170
+ readonly text: string;
171
+ } | {
172
+ readonly kind: 'task-close';
173
+ readonly roomId: string;
174
+ readonly traceId: string;
175
+ readonly status: 'ok' | 'error';
176
+ };
177
+ /** The host-side delivery target: receives normalized events, returns nothing (or a promise). */
178
+ export type InboundSink = (event: InboundEvent) => Promise<void> | void;
179
+ /** A minimal logger face; `console` and the Cordis logger both satisfy it. */
180
+ export interface InboundLogger {
181
+ info(message: string): void;
182
+ warn(message: string): void;
183
+ error(message: string): void;
184
+ debug?(message: string): void;
185
+ }
186
+ /** An adapter that listens for external events and forwards them to a sink. */
187
+ export interface InboundAdapter {
188
+ /**
189
+ * Begin listening; every mapped event goes to `sink`. Returns a disposer
190
+ * that stops the adapter and releases every side effect it owns.
191
+ * @param sink - the delivery target for normalized events.
192
+ * @returns the stop disposer.
193
+ */
194
+ start(sink: InboundSink): () => void;
195
+ }
196
+ /** Spawn signature the stdio adapter uses; injectable for protocol tests. */
197
+ export type SpawnFn = (command: string, options: SpawnOptions) => ChildProcess;
198
+ /**
199
+ * Parse one newline-delimited stdin line as a JSON-RPC 2.0 request or
200
+ * notification. Malformed JSON and non-conforming envelopes fail closed with
201
+ * a stable JSON-RPC error code; the caller reports the error back.
202
+ * @param line - one raw line (whitespace tolerated).
203
+ * @returns the parsed request, or a protocol error with its response id.
204
+ */
205
+ export declare function parseInboundLine(line: string): InboundParse;
206
+ /**
207
+ * Validate a parsed request's method and params and map them to a normalized
208
+ * {@link InboundEvent}. Unknown methods and invalid params fail closed with a
209
+ * stable JSON-RPC error code.
210
+ * @param request - the parsed request from {@link parseInboundLine}.
211
+ * @returns the normalized event, or an error to report back.
212
+ */
213
+ export declare function mapInboundEvent(request: InboundRequest): InboundDecode;
214
+ /**
215
+ * Map one normalized event to the team room's existing surfaces. The result
216
+ * is a list of room writes the host executes in order against the `RoomHub`.
217
+ * @param event - the normalized inbound event.
218
+ * @returns the ordered room deliveries for the event.
219
+ */
220
+ export declare function deliveriesFor(event: InboundEvent): InboundDelivery[];
221
+ /**
222
+ * Serialize one JSON-RPC 2.0 error response. `id` is echoed when the request
223
+ * carried one; null otherwise (best-effort observability for a notification,
224
+ * which the JSON-RPC spec would normally not answer).
225
+ * @param id - the request id to echo (or null).
226
+ * @param code - the JSON-RPC error code.
227
+ * @param message - the human-readable error text.
228
+ * @returns one serialized error response line (no trailing newline).
229
+ */
230
+ export declare function jsonRpcErrorResponse(id: JsonRpcId, code: number, message: string): string;
231
+ /**
232
+ * The stdio JSON-RPC inbound adapter: spawns a runtime command and listens
233
+ * for newline-delimited JSON-RPC notifications on its stdout. Invalid lines
234
+ * are dropped (fail-closed) and answered with a JSON-RPC error on the child's
235
+ * stdin. Start/stop are owned entirely by the returned disposer.
236
+ */
237
+ export declare class StdioJsonRpcInbound implements InboundAdapter {
238
+ private readonly command;
239
+ private readonly logger;
240
+ private readonly spawnFn;
241
+ private child;
242
+ private attempted;
243
+ private disposed;
244
+ /**
245
+ * @param command - the runtime launch command (spawned with a shell).
246
+ * @param logger - where lifecycle and rejection lines are logged.
247
+ * @param spawnFn - injectable spawn for tests; defaults to `node:child_process` spawn.
248
+ */
249
+ constructor(command: string, logger?: InboundLogger, spawnFn?: SpawnFn);
250
+ /**
251
+ * Spawn the runtime and begin listening. A spawn failure degrades to a
252
+ * logged warning and a no-op disposer (the bridge stays dormant).
253
+ * @param sink - the delivery target for mapped events.
254
+ * @returns the stop disposer (kills the child; further output is ignored).
255
+ */
256
+ start(sink: InboundSink): () => void;
257
+ /** Stop the adapter: kill the child if still running; further output is ignored. Idempotent. */
258
+ stop(): void;
259
+ /** Decode one line, emit a mapped event, or report the failure back (fail-closed). */
260
+ private handleLine;
261
+ /** Best-effort write of one JSON-RPC error response to the child's stdin. */
262
+ private writeResponse;
263
+ }
264
+ /**
265
+ * The inbound adapter provider seam: registers adapters and owns their
266
+ * disposers. Every registration returns a disposer that stops and unregisters
267
+ * exactly that adapter; {@link stopAll} stops every registered adapter.
268
+ */
269
+ export declare class InboundCoordinator {
270
+ private readonly stops;
271
+ /**
272
+ * Register one adapter for a sink and start it. Returns a disposer that
273
+ * stops the adapter and drops it from the registry (idempotent).
274
+ * @param adapter - the adapter to start.
275
+ * @param sink - the delivery target the adapter emits into.
276
+ * @returns the stop-and-unregister disposer.
277
+ */
278
+ registerInboundAdapter(adapter: InboundAdapter, sink: InboundSink): () => void;
279
+ /** Stop every registered adapter (idempotent). */
280
+ stopAll(): void;
281
+ }
282
+ //# sourceMappingURL=inbound.d.ts.map