@sharpee/channel-service 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.
@@ -0,0 +1,147 @@
1
+ /**
2
+ * @sharpee/channel-service — `ChannelService` class.
3
+ *
4
+ * Owner context: platform package — runs in-process wherever the engine
5
+ * runs (Node CLI, multi-user server, browser zifmia, platform-browser).
6
+ *
7
+ * Public interface (per ADR-163 §6, §13, §14):
8
+ *
9
+ * - `ChannelService` — concrete runtime. Constructor takes an
10
+ * `IChannelRegistry` plus the client's `ClientCapabilities`.
11
+ * - `buildManifest()` — returns a `CmgtPacket` listing the
12
+ * capability-filtered channel definitions.
13
+ * - `build({ world, events, blocks, turn })` — walks the registry,
14
+ * calls each `IOChannel.produce` closure, applies mode + emit-policy
15
+ * semantics, and returns a `TurnPacket`.
16
+ *
17
+ * Lifecycle (ADR-163 §13): instances are cheap. A new session (engine
18
+ * restart, story switch, RESTART command) creates a fresh
19
+ * `ChannelService`. There is no global session state — `prevValues`
20
+ * lives on the instance.
21
+ *
22
+ * Bootstrap-order invariants (engine-enforced, not service-enforced):
23
+ * the engine emits `channel:manifest` before any `channel:packet`
24
+ * because it calls `buildManifest()` once during `start()` before
25
+ * entering the turn loop. This service does not throw on
26
+ * out-of-order calls; engineers reading the engine code can trace the
27
+ * order from the engine's startup sequence.
28
+ *
29
+ * @see ADR-163 — Channel-Service Platform — §6, §11, §13, §14
30
+ * @see ADR-165 — Renderer Architecture (consumer side)
31
+ */
32
+ import type { IChannelRegistry, ClientCapabilities, CmgtPacket, TurnPacket } from "../if-domain/index";
33
+ import type { ITextBlock } from "../text-blocks/index";
34
+ import type { ISemanticEvent } from "../core/index";
35
+ /**
36
+ * Wire-protocol version emitted in every CMGT manifest. Bumped on
37
+ * breaking shape changes to packet kinds or `ChannelDefinition` fields.
38
+ * Additive channels do not bump version.
39
+ */
40
+ export declare const PROTOCOL_VERSION = 1;
41
+ /**
42
+ * Input to {@link ChannelService.build}.
43
+ *
44
+ * `world` is typed `unknown` here for consistency with
45
+ * `ChannelProduceContext` (if-domain cannot import `IWorldModel`).
46
+ * Engine call sites pass an `IWorldModel`; closures cast at the
47
+ * boundary.
48
+ */
49
+ export interface BuildInput {
50
+ readonly world: unknown;
51
+ readonly events: readonly ISemanticEvent[];
52
+ readonly blocks: readonly ITextBlock[];
53
+ /** Monotonic turn count supplied by the engine. Used for `turn_id`. */
54
+ readonly turn: number;
55
+ }
56
+ /**
57
+ * The channel-service runtime.
58
+ *
59
+ * One instance per session. Composes a channel registry (typically
60
+ * `@sharpee/stdlib`'s `channelRegistry`) with the negotiated client
61
+ * capabilities. Holds per-channel previous-value state for sparse-emit
62
+ * change detection and `always`-mode replace re-emission.
63
+ */
64
+ export declare class ChannelService {
65
+ private readonly registry;
66
+ private readonly capabilities;
67
+ /**
68
+ * Per-channel previous emitted value. Replace-mode channels store the
69
+ * latest emitted value (used both for sparse change detection and for
70
+ * `always`-mode idle-turn re-emission). Append-mode channels store the
71
+ * total accumulated entry count (for diagnostics; the renderer owns
72
+ * accumulation per ADR-165 §5). Event-mode channels do not consult
73
+ * prevValue.
74
+ */
75
+ private readonly prevValues;
76
+ constructor(registry: IChannelRegistry, capabilities: ClientCapabilities);
77
+ /**
78
+ * Build the per-client CMGT manifest (ADR-163 §11).
79
+ *
80
+ * Walks every registered channel. Capability-gated channels
81
+ * (`IOChannel.gatedBy`) are filtered out when the named capability is
82
+ * not declared `true` for this client. Returns a fresh manifest each
83
+ * call; safe to invoke multiple times (the engine calls it once
84
+ * during `start()`).
85
+ */
86
+ buildManifest(): CmgtPacket;
87
+ /**
88
+ * Build the turn packet for the turn just executed (ADR-163 §1, §5).
89
+ *
90
+ * For each registered, non-gated channel:
91
+ * 1. Builds a {@link ChannelProduceContext} with the channel's
92
+ * `prevValue` from this instance's cache.
93
+ * 2. Calls `channel.produce(ctx)`.
94
+ * 3. Applies mode + emit-policy semantics to the return value to
95
+ * decide whether the channel appears in this turn's payload and
96
+ * what value it carries.
97
+ * 4. Updates the per-channel `prevValue` cache.
98
+ *
99
+ * Returns a `TurnPacket` whose `payload` contains only the channels
100
+ * that emitted this turn. Sparse channels stay quiet on no-change;
101
+ * always channels appear every turn.
102
+ */
103
+ build(input: BuildInput): TurnPacket;
104
+ /**
105
+ * True if the channel's `gatedBy` flag is set and the client did not
106
+ * declare that capability as `true`. Gated-out channels appear neither
107
+ * in the manifest nor in turn packets.
108
+ */
109
+ private isGatedOut;
110
+ /**
111
+ * Apply mode + emit-policy semantics to a closure's return value.
112
+ *
113
+ * Mutates `payload` (sets `payload[channel.id]` if the channel
114
+ * emits) and `this.prevValues` (records the new value for sparse
115
+ * compare and idle re-emission).
116
+ *
117
+ * Mode semantics (ADR-163 §4, §6):
118
+ *
119
+ * - `replace`:
120
+ * * `undefined` — `always` re-emits prevValue (idle turn);
121
+ * `sparse` skips.
122
+ * * `null` — emit `null` (hide / stop signal); cache `null`.
123
+ * * value — `always` emits unconditionally; `sparse` emits only
124
+ * on change. Cache the new value.
125
+ *
126
+ * - `append`:
127
+ * * `undefined` — skip (no new entries this turn).
128
+ * * `null` — treated as "no new entries"; skip.
129
+ * * scalar — wrapped into a single-element array (closure
130
+ * convenience: `produce: () => 'line'` works without `[]`).
131
+ * * array (possibly empty) — `always` emits the array as-is;
132
+ * `sparse` emits only when non-empty. The renderer owns
133
+ * accumulation per ADR-165 §5.
134
+ *
135
+ * - `event`:
136
+ * * `undefined` / `null` — skip (event channels emit only on
137
+ * fire). Emit policy is informative only — `event` mode is
138
+ * inherently sparse.
139
+ * * value — emit the value (transient signal).
140
+ *
141
+ * The closure return type `T | T[] | undefined | null` allows append
142
+ * mode to return entries directly while replace and event return a
143
+ * scalar. Append's scalar-as-shortcut is documented above.
144
+ */
145
+ private applyEmission;
146
+ }
147
+ //# sourceMappingURL=channel-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-service.d.ts","sourceRoot":"","sources":["../../../../../repos/sharpee/packages/channel-service/src/channel-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAEhB,kBAAkB,EAGlB,UAAU,EACV,UAAU,EACX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IACvC,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,qBAAa,cAAc;IAYvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAZ/B;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA8B;gBAGtC,QAAQ,EAAE,gBAAgB,EAC1B,YAAY,EAAE,kBAAkB;IAGnD;;;;;;;;OAQG;IACH,aAAa,IAAI,UAAU;IAa3B;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU;IA0BpC;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAKlB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCG;IACH,OAAO,CAAC,aAAa;CAmDtB"}
@@ -0,0 +1,253 @@
1
+ "use strict";
2
+ /**
3
+ * @sharpee/channel-service — `ChannelService` class.
4
+ *
5
+ * Owner context: platform package — runs in-process wherever the engine
6
+ * runs (Node CLI, multi-user server, browser zifmia, platform-browser).
7
+ *
8
+ * Public interface (per ADR-163 §6, §13, §14):
9
+ *
10
+ * - `ChannelService` — concrete runtime. Constructor takes an
11
+ * `IChannelRegistry` plus the client's `ClientCapabilities`.
12
+ * - `buildManifest()` — returns a `CmgtPacket` listing the
13
+ * capability-filtered channel definitions.
14
+ * - `build({ world, events, blocks, turn })` — walks the registry,
15
+ * calls each `IOChannel.produce` closure, applies mode + emit-policy
16
+ * semantics, and returns a `TurnPacket`.
17
+ *
18
+ * Lifecycle (ADR-163 §13): instances are cheap. A new session (engine
19
+ * restart, story switch, RESTART command) creates a fresh
20
+ * `ChannelService`. There is no global session state — `prevValues`
21
+ * lives on the instance.
22
+ *
23
+ * Bootstrap-order invariants (engine-enforced, not service-enforced):
24
+ * the engine emits `channel:manifest` before any `channel:packet`
25
+ * because it calls `buildManifest()` once during `start()` before
26
+ * entering the turn loop. This service does not throw on
27
+ * out-of-order calls; engineers reading the engine code can trace the
28
+ * order from the engine's startup sequence.
29
+ *
30
+ * @see ADR-163 — Channel-Service Platform — §6, §11, §13, §14
31
+ * @see ADR-165 — Renderer Architecture (consumer side)
32
+ */
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.ChannelService = exports.PROTOCOL_VERSION = void 0;
35
+ /**
36
+ * Wire-protocol version emitted in every CMGT manifest. Bumped on
37
+ * breaking shape changes to packet kinds or `ChannelDefinition` fields.
38
+ * Additive channels do not bump version.
39
+ */
40
+ exports.PROTOCOL_VERSION = 1;
41
+ /**
42
+ * The channel-service runtime.
43
+ *
44
+ * One instance per session. Composes a channel registry (typically
45
+ * `@sharpee/stdlib`'s `channelRegistry`) with the negotiated client
46
+ * capabilities. Holds per-channel previous-value state for sparse-emit
47
+ * change detection and `always`-mode replace re-emission.
48
+ */
49
+ class ChannelService {
50
+ registry;
51
+ capabilities;
52
+ /**
53
+ * Per-channel previous emitted value. Replace-mode channels store the
54
+ * latest emitted value (used both for sparse change detection and for
55
+ * `always`-mode idle-turn re-emission). Append-mode channels store the
56
+ * total accumulated entry count (for diagnostics; the renderer owns
57
+ * accumulation per ADR-165 §5). Event-mode channels do not consult
58
+ * prevValue.
59
+ */
60
+ prevValues = new Map();
61
+ constructor(registry, capabilities) {
62
+ this.registry = registry;
63
+ this.capabilities = capabilities;
64
+ }
65
+ /**
66
+ * Build the per-client CMGT manifest (ADR-163 §11).
67
+ *
68
+ * Walks every registered channel. Capability-gated channels
69
+ * (`IOChannel.gatedBy`) are filtered out when the named capability is
70
+ * not declared `true` for this client. Returns a fresh manifest each
71
+ * call; safe to invoke multiple times (the engine calls it once
72
+ * during `start()`).
73
+ */
74
+ buildManifest() {
75
+ const channels = [];
76
+ for (const channel of this.registry.all()) {
77
+ if (this.isGatedOut(channel))
78
+ continue;
79
+ channels.push(toDefinition(channel));
80
+ }
81
+ return {
82
+ kind: 'cmgt',
83
+ protocol_version: exports.PROTOCOL_VERSION,
84
+ channels,
85
+ };
86
+ }
87
+ /**
88
+ * Build the turn packet for the turn just executed (ADR-163 §1, §5).
89
+ *
90
+ * For each registered, non-gated channel:
91
+ * 1. Builds a {@link ChannelProduceContext} with the channel's
92
+ * `prevValue` from this instance's cache.
93
+ * 2. Calls `channel.produce(ctx)`.
94
+ * 3. Applies mode + emit-policy semantics to the return value to
95
+ * decide whether the channel appears in this turn's payload and
96
+ * what value it carries.
97
+ * 4. Updates the per-channel `prevValue` cache.
98
+ *
99
+ * Returns a `TurnPacket` whose `payload` contains only the channels
100
+ * that emitted this turn. Sparse channels stay quiet on no-change;
101
+ * always channels appear every turn.
102
+ */
103
+ build(input) {
104
+ const payload = {};
105
+ for (const channel of this.registry.all()) {
106
+ if (this.isGatedOut(channel))
107
+ continue;
108
+ const prevValue = this.prevValues.get(channel.id);
109
+ const ctx = {
110
+ world: input.world,
111
+ events: input.events,
112
+ blocks: input.blocks,
113
+ turn: input.turn,
114
+ prevValue,
115
+ };
116
+ const produced = channel.produce(ctx);
117
+ this.applyEmission(channel, produced, prevValue, payload);
118
+ }
119
+ return {
120
+ kind: 'turn',
121
+ turn_id: `turn-${input.turn}`,
122
+ payload,
123
+ };
124
+ }
125
+ /**
126
+ * True if the channel's `gatedBy` flag is set and the client did not
127
+ * declare that capability as `true`. Gated-out channels appear neither
128
+ * in the manifest nor in turn packets.
129
+ */
130
+ isGatedOut(channel) {
131
+ if (channel.gatedBy === undefined)
132
+ return false;
133
+ return this.capabilities[channel.gatedBy] !== true;
134
+ }
135
+ /**
136
+ * Apply mode + emit-policy semantics to a closure's return value.
137
+ *
138
+ * Mutates `payload` (sets `payload[channel.id]` if the channel
139
+ * emits) and `this.prevValues` (records the new value for sparse
140
+ * compare and idle re-emission).
141
+ *
142
+ * Mode semantics (ADR-163 §4, §6):
143
+ *
144
+ * - `replace`:
145
+ * * `undefined` — `always` re-emits prevValue (idle turn);
146
+ * `sparse` skips.
147
+ * * `null` — emit `null` (hide / stop signal); cache `null`.
148
+ * * value — `always` emits unconditionally; `sparse` emits only
149
+ * on change. Cache the new value.
150
+ *
151
+ * - `append`:
152
+ * * `undefined` — skip (no new entries this turn).
153
+ * * `null` — treated as "no new entries"; skip.
154
+ * * scalar — wrapped into a single-element array (closure
155
+ * convenience: `produce: () => 'line'` works without `[]`).
156
+ * * array (possibly empty) — `always` emits the array as-is;
157
+ * `sparse` emits only when non-empty. The renderer owns
158
+ * accumulation per ADR-165 §5.
159
+ *
160
+ * - `event`:
161
+ * * `undefined` / `null` — skip (event channels emit only on
162
+ * fire). Emit policy is informative only — `event` mode is
163
+ * inherently sparse.
164
+ * * value — emit the value (transient signal).
165
+ *
166
+ * The closure return type `T | T[] | undefined | null` allows append
167
+ * mode to return entries directly while replace and event return a
168
+ * scalar. Append's scalar-as-shortcut is documented above.
169
+ */
170
+ applyEmission(channel, produced, prevValue, payload) {
171
+ const emit = channel.emit;
172
+ switch (channel.mode) {
173
+ case 'replace': {
174
+ if (produced === undefined) {
175
+ if (emit === 'always' && prevValue !== undefined) {
176
+ payload[channel.id] = prevValue;
177
+ }
178
+ return;
179
+ }
180
+ // `produced` is either a real value or `null` (hide signal).
181
+ // Both go through the same change-detection path: sparse
182
+ // channels only emit on transition; always channels emit each
183
+ // turn. The cached prevValue tracks `null` so a subsequent
184
+ // null-return on a sparse channel is recognized as no-change.
185
+ if (emit === 'always' || !valueEquals(produced, prevValue)) {
186
+ payload[channel.id] = produced;
187
+ }
188
+ this.prevValues.set(channel.id, produced);
189
+ return;
190
+ }
191
+ case 'append': {
192
+ if (produced === undefined || produced === null)
193
+ return;
194
+ const entries = Array.isArray(produced) ? produced : [produced];
195
+ if (emit === 'always') {
196
+ payload[channel.id] = entries;
197
+ }
198
+ else if (entries.length > 0) {
199
+ payload[channel.id] = entries;
200
+ }
201
+ // Track entry count for diagnostics; renderer owns the buffer.
202
+ const prevCount = typeof prevValue === 'number' ? prevValue : 0;
203
+ this.prevValues.set(channel.id, prevCount + entries.length);
204
+ return;
205
+ }
206
+ case 'event': {
207
+ if (produced === undefined || produced === null)
208
+ return;
209
+ // Multi-firing in one turn collapses to the closure's last
210
+ // return value — the closure itself is the single fire site.
211
+ payload[channel.id] = produced;
212
+ return;
213
+ }
214
+ }
215
+ }
216
+ }
217
+ exports.ChannelService = ChannelService;
218
+ /**
219
+ * Project an `IOChannel` into a wire-shaped `ChannelDefinition`.
220
+ *
221
+ * The closure does not cross the wire; only identity and configuration.
222
+ */
223
+ function toDefinition(channel) {
224
+ return {
225
+ id: channel.id,
226
+ contentType: channel.contentType,
227
+ mode: channel.mode,
228
+ emit: channel.emit,
229
+ };
230
+ }
231
+ /**
232
+ * Deep value equality for sparse-emit change detection.
233
+ *
234
+ * Uses `JSON.stringify` for object/array compares — adequate for the
235
+ * value shapes carried over the wire (scalars, plain objects, arrays
236
+ * of scalars or text content nodes). Not safe for cyclic graphs, but
237
+ * those cannot cross the JSON wire anyway.
238
+ */
239
+ function valueEquals(a, b) {
240
+ if (a === b)
241
+ return true;
242
+ if (a === undefined || b === undefined)
243
+ return false;
244
+ if (a === null || b === null)
245
+ return a === b;
246
+ if (typeof a !== typeof b)
247
+ return false;
248
+ if (typeof a === 'object') {
249
+ return JSON.stringify(a) === JSON.stringify(b);
250
+ }
251
+ return false;
252
+ }
253
+ //# sourceMappingURL=channel-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-service.js","sourceRoot":"","sources":["../../../../../../repos/sharpee/packages/channel-service/src/channel-service.ts"],"names":[],"mappings":"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"}
package/index.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @sharpee/channel-service
3
+ *
4
+ * Channel-I/O wire producer for Sharpee surfaces (ADR-163,
5
+ * closure-per-channel model — 2026-05-02 rewrite + 2026-05-03 refinement).
6
+ *
7
+ * Owner context: platform package — runs in-process wherever the engine
8
+ * runs (Node CLI, multi-user server, browser zifmia, platform-browser).
9
+ *
10
+ * Public interface:
11
+ *
12
+ * - **`ChannelService`** — concrete runtime class. Constructor takes an
13
+ * `IChannelRegistry` plus the client's `ClientCapabilities`.
14
+ * - `buildManifest()` returns a `CmgtPacket` (capability-filtered).
15
+ * - `build({ world, events, blocks, turn })` walks the registry,
16
+ * calls each `IOChannel.produce`, and returns a `TurnPacket`.
17
+ * - **Channel + wire types** are re-exported from `@sharpee/if-domain`
18
+ * for caller convenience. `if-domain` is the single canonical home
19
+ * for these contracts (CLAUDE.md rule 7b).
20
+ * - **Wire decoder** ships here (`createDecoder`, `Decoder`,
21
+ * `DecoderState`) for clients that receive packets and need
22
+ * bootstrap-order enforcement.
23
+ * - **`flattenContent`** utility for projecting `TextContent` arrays
24
+ * to plain strings (used by closures and renderers).
25
+ *
26
+ * Standard channel definitions (`main`, `prompt`, `score`, `turn`,
27
+ * `location`, media channels, etc.) live in `@sharpee/stdlib` per
28
+ * ADR-163 §7, §14. This package is domain-agnostic — it does not know
29
+ * what `scoring` means or what `room.name` means.
30
+ *
31
+ * @see ADR-163 — Channel-Service Platform
32
+ * @see ADR-165 — Renderer Architecture (consumer side)
33
+ */
34
+ export { ChannelService, PROTOCOL_VERSION } from './channel-service';
35
+ export type { BuildInput } from './channel-service';
36
+ export type { IOChannel, IChannelRegistry, ChannelProduceContext, ChannelContentType, ChannelMode, ChannelEmitPolicy, ClientCapabilities, CapabilityFlag, ChannelDefinition, HelloPacket, CmgtPacket, TurnPacket, CommandPacket, WirePacket, } from "../if-domain/index";
37
+ export { createDecoder, type Decoder, type DecoderState } from './wire';
38
+ export { flattenContent } from './utils/flatten';
39
+ export { renderToString, renderStatusLine } from './render-to-string';
40
+ export type { CLIRenderOptions } from './render-to-string';
41
+ export { Renderer, createRenderer, createJsonTreeFallbackFactory, type RendererOptions, type FallbackOutputSink, type FallbackWarningSink, } from './renderer';
42
+ export type { ChannelRenderer, IRenderer, ChannelStateStore, SlotHandle, } from './renderer';
43
+ //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../repos/sharpee/packages/channel-service/src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,EAClB,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,UAAU,EACV,aAAa,EACb,UAAU,GACX,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,QAAQ,CAAC;AAExE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAQjD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtE,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG3D,OAAO,EACL,QAAQ,EACR,cAAc,EACd,6BAA6B,EAC7B,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,eAAe,EACf,SAAS,EACT,iBAAiB,EACjB,UAAU,GACX,MAAM,YAAY,CAAC"}
package/index.js ADDED
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ /**
3
+ * @sharpee/channel-service
4
+ *
5
+ * Channel-I/O wire producer for Sharpee surfaces (ADR-163,
6
+ * closure-per-channel model — 2026-05-02 rewrite + 2026-05-03 refinement).
7
+ *
8
+ * Owner context: platform package — runs in-process wherever the engine
9
+ * runs (Node CLI, multi-user server, browser zifmia, platform-browser).
10
+ *
11
+ * Public interface:
12
+ *
13
+ * - **`ChannelService`** — concrete runtime class. Constructor takes an
14
+ * `IChannelRegistry` plus the client's `ClientCapabilities`.
15
+ * - `buildManifest()` returns a `CmgtPacket` (capability-filtered).
16
+ * - `build({ world, events, blocks, turn })` walks the registry,
17
+ * calls each `IOChannel.produce`, and returns a `TurnPacket`.
18
+ * - **Channel + wire types** are re-exported from `@sharpee/if-domain`
19
+ * for caller convenience. `if-domain` is the single canonical home
20
+ * for these contracts (CLAUDE.md rule 7b).
21
+ * - **Wire decoder** ships here (`createDecoder`, `Decoder`,
22
+ * `DecoderState`) for clients that receive packets and need
23
+ * bootstrap-order enforcement.
24
+ * - **`flattenContent`** utility for projecting `TextContent` arrays
25
+ * to plain strings (used by closures and renderers).
26
+ *
27
+ * Standard channel definitions (`main`, `prompt`, `score`, `turn`,
28
+ * `location`, media channels, etc.) live in `@sharpee/stdlib` per
29
+ * ADR-163 §7, §14. This package is domain-agnostic — it does not know
30
+ * what `scoring` means or what `room.name` means.
31
+ *
32
+ * @see ADR-163 — Channel-Service Platform
33
+ * @see ADR-165 — Renderer Architecture (consumer side)
34
+ */
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createJsonTreeFallbackFactory = exports.createRenderer = exports.Renderer = exports.renderStatusLine = exports.renderToString = exports.flattenContent = exports.createDecoder = exports.PROTOCOL_VERSION = exports.ChannelService = void 0;
37
+ var channel_service_1 = require("./channel-service");
38
+ Object.defineProperty(exports, "ChannelService", { enumerable: true, get: function () { return channel_service_1.ChannelService; } });
39
+ Object.defineProperty(exports, "PROTOCOL_VERSION", { enumerable: true, get: function () { return channel_service_1.PROTOCOL_VERSION; } });
40
+ var wire_1 = require("./wire");
41
+ Object.defineProperty(exports, "createDecoder", { enumerable: true, get: function () { return wire_1.createDecoder; } });
42
+ var flatten_1 = require("./utils/flatten");
43
+ Object.defineProperty(exports, "flattenContent", { enumerable: true, get: function () { return flatten_1.flattenContent; } });
44
+ // Display flatteners (ADR-174 Phase 2 — `renderToString` / `renderStatusLine`
45
+ // migrated here from `@sharpee/text-service` per OQ-1 resolution).
46
+ // Used by transcript tooling, dev scripts, and chat overlays that need a
47
+ // single string projection of an `ITextBlock[]`. `flattenContent` (above)
48
+ // is the lower-level helper used inside producer closures; `renderToString`
49
+ // adds smart block-joining, ANSI translation, and status-block filtering.
50
+ var render_to_string_1 = require("./render-to-string");
51
+ Object.defineProperty(exports, "renderToString", { enumerable: true, get: function () { return render_to_string_1.renderToString; } });
52
+ Object.defineProperty(exports, "renderStatusLine", { enumerable: true, get: function () { return render_to_string_1.renderStatusLine; } });
53
+ // Consumer-side renderer (ADR-165) — see ./renderer.
54
+ var renderer_1 = require("./renderer");
55
+ Object.defineProperty(exports, "Renderer", { enumerable: true, get: function () { return renderer_1.Renderer; } });
56
+ Object.defineProperty(exports, "createRenderer", { enumerable: true, get: function () { return renderer_1.createRenderer; } });
57
+ Object.defineProperty(exports, "createJsonTreeFallbackFactory", { enumerable: true, get: function () { return renderer_1.createJsonTreeFallbackFactory; } });
58
+ //# sourceMappingURL=index.js.map
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../repos/sharpee/packages/channel-service/src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;;;AAEH,qDAAqE;AAA5D,iHAAA,cAAc,OAAA;AAAE,mHAAA,gBAAgB,OAAA;AAoBzC,+BAAwE;AAA/D,qGAAA,aAAa,OAAA;AAEtB,2CAAiD;AAAxC,yGAAA,cAAc,OAAA;AAEvB,8EAA8E;AAC9E,mEAAmE;AACnE,yEAAyE;AACzE,0EAA0E;AAC1E,4EAA4E;AAC5E,0EAA0E;AAC1E,uDAAsE;AAA7D,kHAAA,cAAc,OAAA;AAAE,oHAAA,gBAAgB,OAAA;AAGzC,qDAAqD;AACrD,uCAOoB;AANlB,oGAAA,QAAQ,OAAA;AACR,0GAAA,cAAc,OAAA;AACd,yHAAA,6BAA6B,OAAA"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@sharpee/channel-service",
3
+ "version": "1.0.0",
4
+ "description": "Channel-service platform — universal channel-I/O wire producer (ADR-163)",
5
+ "main": "./index.js",
6
+ "types": "./index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./index.d.ts",
10
+ "require": "./index.js",
11
+ "default": "./index.js"
12
+ }
13
+ },
14
+ "dependencies": {
15
+ "@sharpee/core": "^1.0.0",
16
+ "@sharpee/if-domain": "^1.0.0",
17
+ "@sharpee/text-blocks": "^1.0.0",
18
+ "@sharpee/world-model": "^1.0.0"
19
+ },
20
+ "keywords": [
21
+ "interactive-fiction",
22
+ "if",
23
+ "text-adventure",
24
+ "sharpee",
25
+ "channel-service",
26
+ "channel-io",
27
+ "wire-protocol"
28
+ ],
29
+ "author": "Sharpee Team",
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/ChicagoDave/sharpee.git",
34
+ "directory": "packages/channel-service"
35
+ },
36
+ "homepage": "https://github.com/ChicagoDave/sharpee#readme",
37
+ "bugs": {
38
+ "url": "https://github.com/ChicagoDave/sharpee/issues"
39
+ },
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ }
46
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * `renderToString` and `renderStatusLine` — flatten an `ITextBlock[]`
3
+ * to a single display string.
4
+ *
5
+ * Owner context: `@sharpee/channel-service`. Block-flattening helpers
6
+ * consumed by transcript tooling, chat overlays, and dev scripts.
7
+ * Channel-service is downstream of engine and upstream of clients —
8
+ * the right dependency position for a helper that walks ITextBlock[]
9
+ * for display.
10
+ *
11
+ * Public interface:
12
+ * - `renderToString(blocks, options?)` — flatten blocks to a joined
13
+ * string with smart separators between same-key vs different-key
14
+ * block transitions. Decorations translate to ANSI codes when
15
+ * `options.ansi === true`, or to bracket-stripped plain text
16
+ * otherwise.
17
+ * - `renderStatusLine(blocks, options?)` — render `status.*` blocks
18
+ * to a single pipe-separated line for status-bar display.
19
+ * - `CLIRenderOptions` — option shape (ansi, blockSeparator,
20
+ * colors, includeStatus). Name preserved from the prior
21
+ * `@sharpee/text-service` home; not CLI-specific in practice
22
+ * (zifmia uses `renderToString` for browser chat bubbles).
23
+ *
24
+ * Ported from `@sharpee/text-service/src/cli-renderer.ts` per ADR-174
25
+ * Phase 2 (OQ-1 resolution, 2026-05-10). The original file remains
26
+ * compilable in text-service through Phase 2 for zifmia's sake; Phase
27
+ * 3 deletes the package.
28
+ *
29
+ * @see ADR-174 — Decoration Architecture and Engine-Internal Prose Pipeline
30
+ * @see ADR-163 — Channel-Service Platform
31
+ */
32
+ import type { ITextBlock } from "../text-blocks/index";
33
+ /**
34
+ * CLI render options
35
+ */
36
+ export interface CLIRenderOptions {
37
+ /** Enable ANSI color codes (default: false) */
38
+ ansi?: boolean;
39
+ /** Separator between blocks (default: '\n\n') */
40
+ blockSeparator?: string;
41
+ /** Story-defined color mappings */
42
+ colors?: Record<string, string>;
43
+ /** Include status blocks in output (default: false) */
44
+ includeStatus?: boolean;
45
+ }
46
+ /**
47
+ * Render `ITextBlock[]` to string for display.
48
+ *
49
+ * Uses smart joining: single newline between consecutive blocks of the
50
+ * same key, double newline (or `options.blockSeparator`) between blocks
51
+ * of different keys. This keeps related output together (e.g., multiple
52
+ * "Taken." messages) while separating distinct sections.
53
+ *
54
+ * @example
55
+ * const output = renderToString(blocks, { ansi: true });
56
+ * console.log(output);
57
+ */
58
+ export declare function renderToString(blocks: ITextBlock[], options?: CLIRenderOptions): string;
59
+ /**
60
+ * Render status blocks to a single line (for status bar display).
61
+ *
62
+ * @example
63
+ * const status = renderStatusLine(blocks);
64
+ * // "West of House | Score: 0 | Turns: 1"
65
+ */
66
+ export declare function renderStatusLine(blocks: ITextBlock[], options?: CLIRenderOptions): string;
67
+ //# sourceMappingURL=render-to-string.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-to-string.d.ts","sourceRoot":"","sources":["../../../../../repos/sharpee/packages/channel-service/src/render-to-string.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,UAAU,EAA4B,MAAM,sBAAsB,CAAC;AAGjF;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf,iDAAiD;IACjD,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEhC,uDAAuD;IACvD,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAqJD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,UAAU,EAAE,EACpB,OAAO,GAAE,gBAAqB,GAC7B,MAAM,CA+BR;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,UAAU,EAAE,EACpB,OAAO,GAAE,gBAAqB,GAC7B,MAAM,CA6BR"}