induscode 0.1.0__py3-none-any.whl

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 (167) hide show
  1. induscode/__init__.py +56 -0
  2. induscode/addons/__init__.py +176 -0
  3. induscode/addons/contract.py +923 -0
  4. induscode/addons/dispatch/__init__.py +43 -0
  5. induscode/addons/dispatch/event_dispatcher.py +348 -0
  6. induscode/addons/dispatch/tool_interceptor.py +349 -0
  7. induscode/addons/host.py +469 -0
  8. induscode/addons/loader.py +314 -0
  9. induscode/addons/manifest.py +232 -0
  10. induscode/addons/surface.py +199 -0
  11. induscode/boot/__init__.py +108 -0
  12. induscode/boot/auth_vault.py +323 -0
  13. induscode/boot/boot.py +210 -0
  14. induscode/boot/contract.py +223 -0
  15. induscode/boot/invocation.py +117 -0
  16. induscode/boot/runners/__init__.py +42 -0
  17. induscode/boot/runners/link_runner.py +82 -0
  18. induscode/boot/runners/oneshot_runner.py +85 -0
  19. induscode/boot/runners/registry.py +46 -0
  20. induscode/boot/runners/repl_runner.py +340 -0
  21. induscode/boot/runners/session.py +549 -0
  22. induscode/boot/stages.py +198 -0
  23. induscode/boot/upgrade/__init__.py +36 -0
  24. induscode/boot/upgrade/apply.py +125 -0
  25. induscode/boot/upgrade/upgrades.py +136 -0
  26. induscode/briefing/__init__.py +115 -0
  27. induscode/briefing/compose.py +414 -0
  28. induscode/briefing/contract.py +528 -0
  29. induscode/briefing/macros.py +721 -0
  30. induscode/briefing/skills.py +417 -0
  31. induscode/capability_deck/__init__.py +233 -0
  32. induscode/capability_deck/bridge_ledger/__init__.py +66 -0
  33. induscode/capability_deck/bridge_ledger/key.py +181 -0
  34. induscode/capability_deck/bridge_ledger/ledger.py +276 -0
  35. induscode/capability_deck/bridge_ledger/network.py +336 -0
  36. induscode/capability_deck/builtin_bridge.py +358 -0
  37. induscode/capability_deck/cards/__init__.py +116 -0
  38. induscode/capability_deck/cards/bg_process.py +482 -0
  39. induscode/capability_deck/cards/memory.py +226 -0
  40. induscode/capability_deck/cards/saas.py +280 -0
  41. induscode/capability_deck/cards/task.py +256 -0
  42. induscode/capability_deck/cards/todo.py +312 -0
  43. induscode/capability_deck/contract.py +450 -0
  44. induscode/capability_deck/manifest.py +126 -0
  45. induscode/capability_deck/provision.py +217 -0
  46. induscode/channels/__init__.py +146 -0
  47. induscode/channels/contract.py +585 -0
  48. induscode/channels/framer.py +132 -0
  49. induscode/channels/link/__init__.py +50 -0
  50. induscode/channels/link/dialog.py +246 -0
  51. induscode/channels/link/driver.py +308 -0
  52. induscode/channels/link/server.py +217 -0
  53. induscode/channels/oneshot.py +178 -0
  54. induscode/channels/ops.py +140 -0
  55. induscode/channels/session_ops.py +172 -0
  56. induscode/conductor/__init__.py +240 -0
  57. induscode/conductor/catalog.py +309 -0
  58. induscode/conductor/conductor.py +1084 -0
  59. induscode/conductor/contract.py +1035 -0
  60. induscode/conductor/matcher.py +291 -0
  61. induscode/conductor/serialize.py +575 -0
  62. induscode/conductor/signal_hub.py +382 -0
  63. induscode/conductor/skill_parse.py +294 -0
  64. induscode/conductor/transcript_store.py +449 -0
  65. induscode/console/__init__.py +236 -0
  66. induscode/console/app.py +1677 -0
  67. induscode/console/components/__init__.py +62 -0
  68. induscode/console/components/banner.py +499 -0
  69. induscode/console/components/banner_sweep.py +188 -0
  70. induscode/console/components/emblem.py +181 -0
  71. induscode/console/components/status_bar.py +102 -0
  72. induscode/console/contract.py +836 -0
  73. induscode/console/input/__init__.py +107 -0
  74. induscode/console/input/chord.py +197 -0
  75. induscode/console/input/dir_reader.py +113 -0
  76. induscode/console/input/intents.py +258 -0
  77. induscode/console/input/providers.py +469 -0
  78. induscode/console/mount.py +137 -0
  79. induscode/console/overlays/__init__.py +94 -0
  80. induscode/console/overlays/auth.py +503 -0
  81. induscode/console/overlays/pickers.py +526 -0
  82. induscode/console/overlays/router.py +129 -0
  83. induscode/console/overlays/sessions.py +232 -0
  84. induscode/console/reducer.py +145 -0
  85. induscode/console/resume_picker.py +156 -0
  86. induscode/console/slash_commands/__init__.py +78 -0
  87. induscode/console/slash_commands/builtins.py +254 -0
  88. induscode/console/slash_commands/dynamic.py +217 -0
  89. induscode/console/slash_commands/integrations.py +949 -0
  90. induscode/console/slash_commands/transcript.py +404 -0
  91. induscode/console/slash_commands/workbench.py +430 -0
  92. induscode/console/startup.py +434 -0
  93. induscode/console/theme/__init__.py +44 -0
  94. induscode/console/theme/adapter.py +168 -0
  95. induscode/console/theme/palette.py +128 -0
  96. induscode/console/theme/resolve.py +123 -0
  97. induscode/console/theme/tokens.py +185 -0
  98. induscode/console_slash/__init__.py +111 -0
  99. induscode/console_slash/contract.py +185 -0
  100. induscode/console_slash/registry.py +140 -0
  101. induscode/console_slash/resolve.py +194 -0
  102. induscode/console_slash/shared.py +172 -0
  103. induscode/entry.py +108 -0
  104. induscode/insight/__init__.py +153 -0
  105. induscode/insight/collector.py +73 -0
  106. induscode/insight/replay.py +305 -0
  107. induscode/insight/wrapper.py +1115 -0
  108. induscode/kit/__init__.py +82 -0
  109. induscode/kit/clipboard_image.py +215 -0
  110. induscode/kit/external_editor.py +120 -0
  111. induscode/kit/image.py +188 -0
  112. induscode/kit/shell.py +89 -0
  113. induscode/kit/tool_fetch.py +288 -0
  114. induscode/launch/__init__.py +224 -0
  115. induscode/launch/catalog.py +310 -0
  116. induscode/launch/contract.py +569 -0
  117. induscode/launch/credentials.py +852 -0
  118. induscode/launch/invocation/__init__.py +39 -0
  119. induscode/launch/invocation/attachments.py +281 -0
  120. induscode/launch/invocation/flags.py +210 -0
  121. induscode/launch/invocation/read.py +369 -0
  122. induscode/launch/invocation/usage.py +110 -0
  123. induscode/launch/oauth.py +808 -0
  124. induscode/launch/packages.py +299 -0
  125. induscode/launch/pickers.py +291 -0
  126. induscode/py.typed +0 -0
  127. induscode/runtime_bridge/__init__.py +166 -0
  128. induscode/runtime_bridge/bridges/__init__.py +66 -0
  129. induscode/runtime_bridge/bridges/_drive.py +268 -0
  130. induscode/runtime_bridge/bridges/builtins.py +177 -0
  131. induscode/runtime_bridge/bridges/claude_cli.py +198 -0
  132. induscode/runtime_bridge/bridges/codex_cli.py +203 -0
  133. induscode/runtime_bridge/bridges/indusagi_cli.py +217 -0
  134. induscode/runtime_bridge/broker.py +397 -0
  135. induscode/runtime_bridge/contract.py +734 -0
  136. induscode/runtime_bridge/sink.py +351 -0
  137. induscode/sessions/__init__.py +25 -0
  138. induscode/sessions/contract.py +119 -0
  139. induscode/sessions/library.py +350 -0
  140. induscode/settings/__init__.py +47 -0
  141. induscode/settings/contract.py +313 -0
  142. induscode/settings/manager.py +268 -0
  143. induscode/transcript_export/__init__.py +109 -0
  144. induscode/transcript_export/contract.py +522 -0
  145. induscode/transcript_export/publish.py +455 -0
  146. induscode/transcript_export/sgr.py +566 -0
  147. induscode/transcript_export/template.py +319 -0
  148. induscode/transcript_export/theme_bridge.py +325 -0
  149. induscode/window_budget/__init__.py +76 -0
  150. induscode/window_budget/budget/__init__.py +26 -0
  151. induscode/window_budget/budget/estimate.py +273 -0
  152. induscode/window_budget/budget/gate.py +60 -0
  153. induscode/window_budget/budget/slice.py +145 -0
  154. induscode/window_budget/condenser.py +170 -0
  155. induscode/window_budget/contract.py +329 -0
  156. induscode/window_budget/summarize/__init__.py +33 -0
  157. induscode/window_budget/summarize/condense.py +212 -0
  158. induscode/window_budget/summarize/prompt.py +241 -0
  159. induscode/workspace/__init__.py +30 -0
  160. induscode/workspace/brand.py +96 -0
  161. induscode/workspace/locator.py +269 -0
  162. induscode-0.1.0.dist-info/METADATA +97 -0
  163. induscode-0.1.0.dist-info/RECORD +167 -0
  164. induscode-0.1.0.dist-info/WHEEL +4 -0
  165. induscode-0.1.0.dist-info/entry_points.txt +3 -0
  166. induscode-0.1.0.dist-info/licenses/CREDITS.md +22 -0
  167. induscode-0.1.0.dist-info/licenses/NOTICE +7 -0
@@ -0,0 +1,522 @@
1
+ """Transcript-export contract — the FROZEN type surface of the HTML publisher.
2
+
3
+ Port of the transcript-export half of TS ``src/briefing/contract.ts`` (the TS
4
+ build hosted these types on the briefing contract; the Python build relocates
5
+ them here, next to the modules that implement them — see the port note in
6
+ ``induscode.briefing.contract``). It declares *only* shapes plus inert
7
+ constants — no string assembly, no theme math — so every behavior module (the
8
+ SGR painter, the theme bridge, the page-shell template, and the publisher) is
9
+ written against the names declared here. The file is intentionally small,
10
+ append-mostly, and stable.
11
+
12
+ Design stance:
13
+
14
+ - ANSI styling is a **table-driven SGR machine**. :class:`SgrState` is the
15
+ running style; an :data:`SgrToken` is one parsed unit (a text run or an SGR
16
+ command); an :data:`SgrMutation` is the partial state one code imposes. The
17
+ SGR *semantics* (which code sets which attribute) are the published
18
+ standard; the state shape is ours.
19
+ - HTML-export color is computed behind a :class:`ThemeBridge` over an
20
+ :class:`ExportTheme` token bag, with luminance read from a precomputed
21
+ :data:`LuminanceLut` (the WCAG relative-luminance formula is a standard; the
22
+ LUT and the fallback colors are ours).
23
+ - Publishing consumes :class:`PublishEntry` nodes whose
24
+ :class:`PublishMessage` is a *narrow structural view* of a framework
25
+ message — the publisher never imports the framework message union; it reads
26
+ ``role`` / ``content`` / ``toolCallId`` / ``toolName`` and dispatches on
27
+ each part's ``type`` tag.
28
+
29
+ Faults stay briefing-owned: :class:`BriefingFault` (kinds ``publish`` /
30
+ ``theme`` included) is imported from ``induscode.briefing.contract`` and
31
+ re-exported here, so the closed fault set lives in exactly one place.
32
+
33
+ Framework anchors (from the ``indusagi`` package):
34
+
35
+ - :class:`TextContent`, :class:`ImageContent` ← ``indusagi.ai``
36
+
37
+ This contract never re-declares those; it composes them. Wire-facing field
38
+ names that mirror the framework's keep their camelCase (``toolCallId``,
39
+ ``toolName``, ``callId``, ``mimeType``), exactly as the framework's own
40
+ message dataclasses do; the camelCase :class:`ExportTheme` token names are
41
+ likewise wire-facing — the publisher hyphenates them into the ``--x-*`` CSS
42
+ custom properties the page shell reads.
43
+ """
44
+
45
+ from __future__ import annotations
46
+
47
+ from collections.abc import Mapping, Sequence
48
+ from dataclasses import dataclass
49
+ from types import MappingProxyType
50
+ from typing import ClassVar, Final, Literal, Protocol, TypeAlias
51
+
52
+ from indusagi.ai import ImageContent, TextContent
53
+
54
+ from induscode.briefing.contract import (
55
+ BriefingFault,
56
+ BriefingFaultKind,
57
+ briefing_fault,
58
+ )
59
+
60
+ __all__ = [
61
+ "BriefingFault",
62
+ "BriefingFaultKind",
63
+ "ExportTheme",
64
+ "FALLBACK_EXPORT_THEME",
65
+ "ImageContent",
66
+ "LuminanceLut",
67
+ "MessagePart",
68
+ "PublishEntry",
69
+ "PublishMessage",
70
+ "PublishOptions",
71
+ "PublishRole",
72
+ "Rgb",
73
+ "SGR_INITIAL_STATE",
74
+ "SHELL_SLOTS",
75
+ "SgrCommandToken",
76
+ "SgrMutation",
77
+ "SgrState",
78
+ "SgrTextToken",
79
+ "SgrToken",
80
+ "ShellSlot",
81
+ "TextContent",
82
+ "ThemeBridge",
83
+ "ThemeMode",
84
+ "ThinkingPart",
85
+ "ToolCallPart",
86
+ "TranscriptPart",
87
+ "WidgetRender",
88
+ "briefing_fault",
89
+ ]
90
+
91
+
92
+ # ---------------------------------------------------------------------------
93
+ # SGR machine (table-driven ANSI styling)
94
+ # ---------------------------------------------------------------------------
95
+
96
+
97
+ @dataclass(frozen=True, slots=True, kw_only=True)
98
+ class SgrState:
99
+ """The running display style of the table-driven SGR machine.
100
+
101
+ As the painter scans an ANSI stream it folds each SGR command into this
102
+ state via the mutation table, then emits a styled span whenever a text run
103
+ follows. Colors are stored as resolved CSS color strings (or ``None`` for
104
+ "inherit"); the boolean flags track the standard attribute set. The shape
105
+ is this rebuild's own; the SGR *semantics* (which code sets which
106
+ attribute) are the published standard.
107
+
108
+ - ``fg`` / ``bg`` — foreground / background color, or ``None`` for the
109
+ default.
110
+ - ``bold`` — increased weight (SGR 1).
111
+ - ``dim`` — decreased intensity (SGR 2).
112
+ - ``italic`` — italic (SGR 3).
113
+ - ``underline`` — underline (SGR 4).
114
+ - ``blink`` — blink (SGR 5/6), rendered as an attribute hook.
115
+ - ``inverse`` — swap fg/bg at render time (SGR 7).
116
+ - ``hidden`` — concealed text (SGR 8).
117
+ - ``strike`` — crossed-out (SGR 9).
118
+ """
119
+
120
+ # Foreground color as a CSS color string, or None for the default.
121
+ fg: str | None = None
122
+ # Background color as a CSS color string, or None for the default.
123
+ bg: str | None = None
124
+ # Increased weight (SGR 1).
125
+ bold: bool = False
126
+ # Decreased intensity (SGR 2).
127
+ dim: bool = False
128
+ # Italic (SGR 3).
129
+ italic: bool = False
130
+ # Underline (SGR 4).
131
+ underline: bool = False
132
+ # Blink (SGR 5/6).
133
+ blink: bool = False
134
+ # Swap foreground and background at render time (SGR 7).
135
+ inverse: bool = False
136
+ # Concealed text (SGR 8).
137
+ hidden: bool = False
138
+ # Crossed-out (SGR 9).
139
+ strike: bool = False
140
+
141
+
142
+ #: The neutral starting style of the SGR machine — every attribute off and
143
+ #: both colors at their default. A reset (SGR 0) returns the state to exactly
144
+ #: this.
145
+ SGR_INITIAL_STATE: Final[SgrState] = SgrState(
146
+ fg=None,
147
+ bg=None,
148
+ bold=False,
149
+ dim=False,
150
+ italic=False,
151
+ underline=False,
152
+ blink=False,
153
+ inverse=False,
154
+ hidden=False,
155
+ strike=False,
156
+ )
157
+
158
+
159
+ @dataclass(frozen=True, slots=True)
160
+ class SgrTextToken:
161
+ """A run of printable characters for the painter to wrap."""
162
+
163
+ kind: ClassVar[Literal["text"]] = "text"
164
+ text: str
165
+
166
+
167
+ @dataclass(frozen=True, slots=True)
168
+ class SgrCommandToken:
169
+ """The numeric parameter list of one ``ESC[...m`` sequence (an empty list
170
+ means a bare ``ESC[m``, equivalent to a reset)."""
171
+
172
+ kind: ClassVar[Literal["sgr"]] = "sgr"
173
+ params: tuple[int, ...]
174
+
175
+
176
+ #: One unit produced by the SGR tokenizer.
177
+ #:
178
+ #: Scanning an ANSI stream splits it into an alternation of plain text runs
179
+ #: and SGR command sequences. The painter walks these tokens, applying ``sgr``
180
+ #: parameter lists to the running :class:`SgrState` and wrapping ``text`` runs
181
+ #: in a span styled by the state in force.
182
+ SgrToken: TypeAlias = SgrTextToken | SgrCommandToken
183
+
184
+ #: A field-level mutation an SGR code applies to the running
185
+ #: :class:`SgrState` — a mapping of :class:`SgrState` field names to their
186
+ #: new values (the Python form of the TS ``Partial<SgrState>``).
187
+ #:
188
+ #: The painter's dispatch table maps each handled SGR parameter to one of
189
+ #: these partial states; folding it over the current state yields the next
190
+ #: state. This is the data form of the SGR semantics — a table, not a switch —
191
+ #: so the set of handled codes is extended by adding table rows, not by
192
+ #: editing control flow.
193
+ SgrMutation: TypeAlias = Mapping[str, str | bool | None]
194
+
195
+
196
+ # ---------------------------------------------------------------------------
197
+ # Export theme + ThemeBridge (HTML transcript publishing)
198
+ # ---------------------------------------------------------------------------
199
+
200
+
201
+ @dataclass(frozen=True, slots=True, kw_only=True)
202
+ class ExportTheme:
203
+ """The color token bag the HTML transcript is themed with.
204
+
205
+ These are this rebuild's *own* token names and default values —
206
+ deliberately not the legacy export's. The publisher emits them as CSS
207
+ custom properties and the page shell reads them. ``surface`` family tokens
208
+ are the large backgrounds; ``ink`` is the body text color; ``accent``
209
+ tints interactive affordances; the ``role`` tokens tint message
210
+ attribution. Values are any CSS color string. The camelCase token names
211
+ are wire-facing: the publisher hyphenates each into its ``--x-*`` CSS
212
+ custom property (``pageSurface`` → ``--x-page-surface``).
213
+
214
+ - ``pageSurface`` — the outermost page background.
215
+ - ``cardSurface`` — the surface of a message card.
216
+ - ``noteSurface`` — the surface of an informational/system note.
217
+ - ``ink`` — primary body text color.
218
+ - ``inkMuted`` — secondary/de-emphasized text color.
219
+ - ``accent`` — interactive affordance tint.
220
+ - ``border`` — hairline/separator color.
221
+ - ``userRole`` — attribution tint for user turns.
222
+ - ``agentRole`` — attribution tint for assistant turns.
223
+ - ``toolRole`` — attribution tint for tool turns.
224
+ """
225
+
226
+ # Outermost page background.
227
+ pageSurface: str
228
+ # Surface of a message card.
229
+ cardSurface: str
230
+ # Surface of an informational/system note.
231
+ noteSurface: str
232
+ # Primary body text color.
233
+ ink: str
234
+ # Secondary/de-emphasized text color.
235
+ inkMuted: str
236
+ # Interactive affordance tint.
237
+ accent: str
238
+ # Hairline/separator color.
239
+ border: str
240
+ # Attribution tint for user turns.
241
+ userRole: str
242
+ # Attribution tint for assistant turns.
243
+ agentRole: str
244
+ # Attribution tint for tool turns.
245
+ toolRole: str
246
+
247
+
248
+ #: This rebuild's own fallback :class:`ExportTheme` — a calm dark palette used
249
+ #: when the active UI theme supplies no export colors.
250
+ #:
251
+ #: Chosen fresh for this rebuild (a deep slate page over a muted blue accent);
252
+ #: none of these values are the legacy export's magic constants. The
253
+ #: :class:`ThemeBridge` derives surface variants from these when a richer
254
+ #: source is unavailable.
255
+ FALLBACK_EXPORT_THEME: Final[ExportTheme] = ExportTheme(
256
+ pageSurface="#13161c",
257
+ cardSurface="#1b1f27",
258
+ noteSurface="#232834",
259
+ ink="#e6e8ee",
260
+ inkMuted="#9aa3b2",
261
+ accent="#6f9ce8",
262
+ border="#2c323d",
263
+ userRole="#7fd1b9",
264
+ agentRole="#6f9ce8",
265
+ toolRole="#d6a36b",
266
+ )
267
+
268
+
269
+ @dataclass(frozen=True, slots=True, kw_only=True)
270
+ class Rgb:
271
+ """An RGB color decomposed into its 0–255 channels.
272
+
273
+ The neutral intermediate the :class:`ThemeBridge` parses a CSS color into
274
+ before computing luminance or deriving a brighter/darker variant.
275
+ ``parse_color`` produces one; ``format_color`` renders one back to a CSS
276
+ string.
277
+ """
278
+
279
+ # Red channel, 0–255.
280
+ r: int
281
+ # Green channel, 0–255.
282
+ g: int
283
+ # Blue channel, 0–255.
284
+ b: int
285
+
286
+
287
+ #: A precomputed lookup table for the per-channel sRGB→linear step of the
288
+ #: WCAG relative-luminance formula.
289
+ #:
290
+ #: The formula is a published standard; computing it per pixel is wasteful, so
291
+ #: the :class:`ThemeBridge` reads the linearized value of each 0–255 channel
292
+ #: from this table — one entry per channel value — and combines the three with
293
+ #: the standard weights. The table is ours (an implementation choice, expected
294
+ #: to be 256 entries long); the math it encodes is the standard.
295
+ LuminanceLut: TypeAlias = Sequence[float]
296
+
297
+ #: Whether a theme reads as light or dark, by the luminance of its page
298
+ #: surface.
299
+ #:
300
+ #: The :class:`ThemeBridge` branches surface derivation on this: a ``light``
301
+ #: base darkens its variants, a ``dark`` base lightens them. The threshold is
302
+ #: a fixed midpoint of the relative-luminance range.
303
+ ThemeMode: TypeAlias = Literal["light", "dark"]
304
+
305
+
306
+ class ThemeBridge(Protocol):
307
+ """The color service behind the HTML transcript export.
308
+
309
+ Replaces the legacy export's loose color functions and ``{{TOKEN}}``
310
+ string replacement with one small typed service: it resolves an
311
+ :class:`ExportTheme`, reports the theme's :data:`ThemeMode`, derives
312
+ surface variants via luminance, computes relative luminance through a
313
+ :data:`LuminanceLut`, and projects the theme to the CSS custom properties
314
+ the page shell consumes. Implementations are pure with respect to their
315
+ inputs.
316
+ """
317
+
318
+ # The resolved export theme this bridge serves.
319
+ theme: ExportTheme
320
+
321
+ def mode(self) -> ThemeMode:
322
+ """Whether the theme reads as light or dark by page-surface
323
+ luminance."""
324
+ ...
325
+
326
+ def luminance(self, color: str) -> float:
327
+ """Compute the WCAG relative luminance (0–1) of a CSS color, reading
328
+ the per-channel sRGB→linear step from the bridge's
329
+ :data:`LuminanceLut`.
330
+
331
+ :param color: a CSS color string to measure
332
+ """
333
+ ...
334
+
335
+ def derive_surface(self, base: str, amount: float) -> str:
336
+ """Derive a surface variant from a base color by stepping its
337
+ lightness toward or away from the theme's :data:`ThemeMode`.
338
+
339
+ :param base: the base CSS color to adjust
340
+ :param amount: signed lightness delta (positive lightens, negative
341
+ darkens)
342
+ """
343
+ ...
344
+
345
+ def to_css_vars(self) -> Mapping[str, str]:
346
+ """Project the theme to the CSS custom properties the page shell
347
+ reads, keyed by their token name (e.g. ``"pageSurface"``) with CSS
348
+ color string values."""
349
+ ...
350
+
351
+
352
+ # ---------------------------------------------------------------------------
353
+ # Transcript publishing
354
+ # ---------------------------------------------------------------------------
355
+
356
+ #: A message payload fragment as it appears in a transcript node — the union
357
+ #: the publisher renders into a message card.
358
+ #:
359
+ #: Aliases the framework content parts (:class:`TextContent` for prose,
360
+ #: :class:`ImageContent` for inline images) so a persisted transcript node
361
+ #: round-trips into the publisher without a parallel content type. The
362
+ #: publisher walks these and emits the corresponding HTML fragment.
363
+ TranscriptPart: TypeAlias = TextContent | ImageContent
364
+
365
+
366
+ @dataclass(frozen=True, slots=True)
367
+ class ThinkingPart:
368
+ """An internal-reasoning content part, read structurally by the
369
+ publisher and rendered as a muted note rather than prose."""
370
+
371
+ type: ClassVar[Literal["thinking"]] = "thinking"
372
+ thinking: str
373
+
374
+
375
+ @dataclass(frozen=True, slots=True, kw_only=True)
376
+ class ToolCallPart:
377
+ """A tool-call content part, read structurally so the assistant turn can
378
+ surface a call's name and arguments without importing the framework
379
+ message union. Structurally compatible with the framework's ``ToolCall``
380
+ (same ``type`` tag and field names), so either may appear in a
381
+ :class:`PublishMessage` content list."""
382
+
383
+ type: ClassVar[Literal["toolCall"]] = "toolCall"
384
+ id: str
385
+ name: str
386
+ arguments: Mapping[str, object]
387
+
388
+
389
+ #: The content-part union the publisher renders. Text and image parts alias
390
+ #: the framework's; thinking and tool-call parts are read structurally so the
391
+ #: assistant turn can surface a call's name and arguments.
392
+ MessagePart: TypeAlias = TextContent | ImageContent | ThinkingPart | ToolCallPart
393
+
394
+ #: The conversational role a published turn carries. Mirrors the conductor's
395
+ #: node roles but is declared locally so the publisher depends only on this
396
+ #: contract, not on the conductor subsystem.
397
+ PublishRole: TypeAlias = Literal["user", "assistant", "tool", "system", "condense", "note"]
398
+
399
+
400
+ @dataclass(frozen=True, slots=True, kw_only=True)
401
+ class PublishMessage:
402
+ """The narrow view of a framework message the publisher reads.
403
+
404
+ Covers the three concrete message roles' content shapes plus the
405
+ tool-call linkage a tool result carries, without redeclaring the whole
406
+ framework union. ``toolCallId`` / ``toolName`` keep the framework's
407
+ camelCase, exactly as ``indusagi.ai.ToolResultMessage`` spells them.
408
+ """
409
+
410
+ # The framework message role.
411
+ role: str
412
+ # The message content — a bare string or a list of content parts.
413
+ content: str | Sequence[MessagePart]
414
+ # Present on tool-result messages; links the node to its originating call.
415
+ toolCallId: str | None = None
416
+ # Present on tool-result messages; the invoked tool's display name.
417
+ toolName: str | None = None
418
+
419
+
420
+ @dataclass(frozen=True, slots=True, kw_only=True)
421
+ class PublishEntry:
422
+ """One transcript node the publisher consumes.
423
+
424
+ Structurally a subset of the conductor's persisted entry: a ``role`` for
425
+ attribution and a ``message`` carrying the framework content this node
426
+ renders. The ``message`` is read with narrow structural access so the
427
+ publisher never imports the framework message union.
428
+ """
429
+
430
+ # Attribution role for this node.
431
+ role: PublishRole
432
+ # The framework message payload to render.
433
+ message: PublishMessage
434
+
435
+
436
+ @dataclass(frozen=True, slots=True, kw_only=True)
437
+ class WidgetRender:
438
+ """The pre-rendered HTML a custom (non-builtin) tool contributes to the
439
+ transcript, keyed back to its tool-call.
440
+
441
+ Custom tools render their call and result through the widget rasterizer
442
+ ahead of page assembly; the publisher looks the result up by ``callId``
443
+ when it emits that tool's card. A single ``html`` string carries the
444
+ rendered block (the legacy collapsed/expanded pair is intentionally
445
+ flattened to one).
446
+ """
447
+
448
+ # The tool-call id this rendered block belongs to.
449
+ callId: str
450
+ # The rendered HTML block for the tool's call/result.
451
+ html: str
452
+
453
+
454
+ @dataclass(frozen=True, slots=True, kw_only=True)
455
+ class PublishOptions:
456
+ """Options that configure a single transcript publish.
457
+
458
+ ``theme`` overrides the export palette (otherwise the fallback is used);
459
+ ``title`` labels the document; ``out_dir`` chooses where the file is
460
+ written; and ``widgets`` supplies any pre-rendered custom-tool blocks to
461
+ splice in. All are optional — publishing a transcript with no options
462
+ produces a themed, self-contained file under the default location.
463
+ """
464
+
465
+ # Override export palette; defaults to FALLBACK_EXPORT_THEME.
466
+ theme: ExportTheme | None = None
467
+ # Document title for the published page.
468
+ title: str | None = None
469
+ # Directory to write the published file into.
470
+ out_dir: str | None = None
471
+ # Pre-rendered custom-tool blocks, keyed by their tool-call id.
472
+ widgets: Sequence[WidgetRender] | None = None
473
+
474
+
475
+ #: The placeholder token names the page shell template carries.
476
+ #:
477
+ #: The publisher interpolates each of these into the shell with a typed
478
+ #: substitution keyed off this mapping — replacing the legacy ad-hoc
479
+ #: ``{{TOKEN}}`` string replacement. The names are this rebuild's own; the
480
+ #: values are computed at publish time (theme vars, surface colors, inlined
481
+ #: CSS/JS, the base64 session payload, and the preserved library notices).
482
+ #:
483
+ #: Port note: the ``markedLib`` / ``highlightLib`` slot *keys* and token
484
+ #: strings are kept from the TS contract (the closed slot set is frozen), but
485
+ #: in the Python build they carry the markdown-it-py and Pygments license
486
+ #: notices — the libraries this port renders with — instead of marked /
487
+ #: highlight.js.
488
+ SHELL_SLOTS: Final[Mapping[str, str]] = MappingProxyType(
489
+ {
490
+ # CSS custom-property block derived from the ThemeBridge.
491
+ "themeVars": "THEME_VARS",
492
+ # Resolved page-surface color.
493
+ "pageSurface": "PAGE_SURFACE",
494
+ # Resolved card-surface color.
495
+ "cardSurface": "CARD_SURFACE",
496
+ # Resolved note-surface color.
497
+ "noteSurface": "NOTE_SURFACE",
498
+ # Inlined stylesheet body.
499
+ "styles": "STYLES",
500
+ # Inlined client renderer body.
501
+ "script": "SCRIPT",
502
+ # Base64-encoded session payload.
503
+ "payload": "PAYLOAD",
504
+ # Markdown library notice (markdown-it-py, MIT, preserved verbatim).
505
+ "markedLib": "MARKED_LIB",
506
+ # Highlighter library notice (Pygments, BSD-2-Clause, preserved verbatim).
507
+ "highlightLib": "HIGHLIGHT_LIB",
508
+ }
509
+ )
510
+
511
+ #: The literal slot-name type of :data:`SHELL_SLOTS`.
512
+ ShellSlot: TypeAlias = Literal[
513
+ "THEME_VARS",
514
+ "PAGE_SURFACE",
515
+ "CARD_SURFACE",
516
+ "NOTE_SURFACE",
517
+ "STYLES",
518
+ "SCRIPT",
519
+ "PAYLOAD",
520
+ "MARKED_LIB",
521
+ "HIGHLIGHT_LIB",
522
+ ]