@timurproko/a1 0.2.3-dev.651 → 0.2.3-dev.656

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 (83) hide show
  1. package/dist/app/session-shell/session-shell.js +9 -2
  2. package/dist/contracts/owned-ui/model.d.ts +5 -0
  3. package/dist/foundation/startup/startup-descriptor.js +2 -2
  4. package/dist/integrations/pi/components/resources/builtin-themes.d.ts +114 -126
  5. package/dist/integrations/pi/components/resources/builtin-themes.js +145 -157
  6. package/dist/integrations/pi/components/shell-footer-status.js +12 -8
  7. package/dist/integrations/pi/components/shell-presenters-info.js +1 -1
  8. package/dist/integrations/pi/components/shell-selectors-dialogs.js +1 -0
  9. package/dist/integrations/pi/components/shell-shared-facade.js +1 -2
  10. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +2 -2
  11. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +2 -2
  12. package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
  13. package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
  14. package/dist/integrations/pi/components/upstream/components/daxnuts.d.ts +1 -1
  15. package/dist/integrations/pi/components/upstream/components/daxnuts.js +1 -1
  16. package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
  17. package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
  18. package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
  19. package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
  20. package/dist/integrations/pi/components/upstream/components/extension-selector.js +1 -1
  21. package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
  22. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
  23. package/dist/integrations/pi/components/upstream/components/owned-editor.js +1 -1
  24. package/dist/integrations/pi/components/upstream/components/pi-logo.d.ts +2 -0
  25. package/dist/integrations/pi/components/upstream/components/pi-logo.js +21 -0
  26. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
  27. package/dist/integrations/pi/components/upstream/components/session-footer.js +13 -6
  28. package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
  29. package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
  30. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +1 -1
  31. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +1 -1
  32. package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +1 -1
  33. package/dist/integrations/pi/components/upstream/components/status-indicator.js +1 -1
  34. package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +16 -0
  35. package/dist/integrations/pi/components/upstream/components/themed-text.js +28 -0
  36. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
  37. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +1 -1
  38. package/dist/integrations/pi/components/upstream/components/tool-execution.js +25 -3
  39. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
  40. package/dist/integrations/pi/components/upstream/components/tree-selector.js +1 -1
  41. package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
  42. package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
  43. package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +1 -1
  44. package/dist/integrations/pi/components/upstream/history/editor-core.js +5 -3
  45. package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
  46. package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
  47. package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
  48. package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
  49. package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
  50. package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
  51. package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
  52. package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
  53. package/dist/integrations/pi/components/upstream/theme/system-theme.d.ts +60 -0
  54. package/dist/integrations/pi/components/upstream/theme/system-theme.js +695 -0
  55. package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +1 -1
  56. package/dist/integrations/pi/components/upstream/theme/theme.d.ts +6 -2
  57. package/dist/integrations/pi/components/upstream/theme/theme.js +22 -7
  58. package/dist/integrations/pi/engine/adapter.js +6 -0
  59. package/dist/integrations/pi/engine/pi-settings-metadata.json +10 -0
  60. package/dist/integrations/pi/engine/resources/changelog.json +4 -4
  61. package/dist/integrations/pi/engine/session-integration.d.ts +2 -2
  62. package/dist/integrations/pi/engine/session-integration.js +1 -1
  63. package/dist/integrations/pi/engine/settings-bridge.d.ts +1 -1
  64. package/dist/integrations/pi/engine/settings-bridge.js +9 -0
  65. package/dist/integrations/pi/engine/settings-port.js +4 -1
  66. package/dist/integrations/pi/engine/workflows.d.ts +2 -1
  67. package/dist/integrations/pi/engine/workflows.js +1 -0
  68. package/dist/integrations/pi/startup-public.js +1925 -1309
  69. package/dist/integrations/pi/startup-public.manifest.json +1611 -929
  70. package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -0
  71. package/dist/integrations/pi/tui-runtime/adapter.js +5 -0
  72. package/dist/integrations/pi/tui-runtime/contracts.d.ts +2 -1
  73. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
  74. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
  75. package/dist/native/darwin-arm64/manifest.json +3 -3
  76. package/dist/native/darwin-arm64/process-guardian +0 -0
  77. package/dist/native/linux-x64/manifest.json +3 -3
  78. package/dist/native/linux-x64/process-guardian +0 -0
  79. package/dist/native/win32-x64/manifest.json +3 -3
  80. package/dist/native/win32-x64/process-guardian.exe +0 -0
  81. package/dist/runtime-payload-inventory.json +40622 -27400
  82. package/docs/ci-release-runbook.md +10 -7
  83. package/package.json +3 -3
@@ -0,0 +1,695 @@
1
+ /**
2
+ * Provenance: @earendil-works/pi-coding-agent 0.99.2 (MIT), commit 005af57d88ee23b33778f343a9595b32e67ff788,
3
+ * packages/coding-agent/src/modes/interactive/theme/system-theme.ts.
4
+ * Modifications: Copied the system palette generator with ESM import remapping and strict
5
+ * indexed-access assertions; A1 retains explicit owned theme selection.
6
+ * Deviations: none.
7
+ */
8
+ /**
9
+ * The `system` theme: pi's colors derived from the terminal's own theme.
10
+ *
11
+ * Every token belongs to a color family (its hue) and has contrast rules: it must reach a contrast level
12
+ * on the background and on the panels it is drawn on. Hue and saturation come from the terminal's palette
13
+ * color for the family's ANSI slot, or from the family's own hue when the terminal reports no palette.
14
+ * Lightness comes from the rules alone. Colors are built in OKHSL, whose saturation is relative to the
15
+ * sRGB gamut, and fade toward gray near black and white.
16
+ *
17
+ * A contrast level is a target-lightness curve: the OKLab lightness a token needs, given the lightness of
18
+ * the surface below it. The curves were fitted to the reference theme design from the "Pi themes: system
19
+ * and light/dark" review. On dark backgrounds they aim for nearly fixed lightness; on light backgrounds the
20
+ * required difference grows as the background darkens.
21
+ *
22
+ * Depending on what the terminal reports, the theme is generated in one of three tiers:
23
+ * - background and palette: hues from the palette, lightness from the background;
24
+ * - background only: the families' own hues, lightness from the background;
25
+ * - nothing: ANSI palette indices and the default colors, which the terminal renders itself.
26
+ */
27
+ import { colorToOkhsl, colorToOklch, okhslColor, oklabToOkhslLightness, rgbColor, } from "@earendil-works/pi-tui";
28
+ export const SYSTEM_THEME_NAME = "system";
29
+ const FAMILIES = {
30
+ neutral: { hue: 231.49, saturation: { min: 0.02, max: 0.08 }, slot: 8 },
31
+ blue: { hue: 231.49, saturation: { min: 0.1, max: 0.68 }, slot: 4 },
32
+ green: { hue: 158.68, saturation: { min: 0.1, max: 0.76 }, slot: 2 },
33
+ red: { hue: 20, saturation: { min: 0.1, max: 0.92 }, slot: 1 },
34
+ yellow: { hue: 82.36, saturation: { min: 0.5, max: 1 }, slot: 3 },
35
+ orange: { hue: 52, saturation: { min: 0.12, max: 0.85 }, slot: 3 },
36
+ violet: { hue: 295, saturation: { min: 0.2, max: 0.6 }, slot: 5 },
37
+ calamine: { hue: 202.43, saturation: { min: 0.1, max: 0.74 }, slot: 6 },
38
+ thinkingSlate: { hue: 231.49, saturation: { min: 0.08, max: 0.2 }, slot: 4 },
39
+ thinkingBlue: { hue: 231.49, saturation: { min: 0.2, max: 0.45 }, slot: 4 },
40
+ thinkingPeriwinkle: {
41
+ hue: 263.25,
42
+ saturation: { min: 0.3, max: 0.6 },
43
+ slot: 6,
44
+ },
45
+ thinkingViolet: { hue: 295, saturation: { min: 0.4, max: 0.75 }, slot: 5 },
46
+ thinkingMagenta: {
47
+ hue: 337.5,
48
+ saturation: { min: 0.5, max: 0.85 },
49
+ slot: 13,
50
+ },
51
+ thinkingRed: { hue: 20, saturation: { min: 0.95, max: 1 }, slot: 1 },
52
+ };
53
+ const TOKEN_FAMILIES = {
54
+ selectedBg: "blue",
55
+ searchMatchBg: "orange",
56
+ userMessageBg: "blue",
57
+ customMessageBg: "violet",
58
+ toolPendingBg: "neutral",
59
+ toolSuccessBg: "green",
60
+ toolErrorBg: "red",
61
+ text: "neutral",
62
+ userMessageText: "neutral",
63
+ customMessageText: "neutral",
64
+ toolTitle: "neutral",
65
+ syntaxOperator: "neutral",
66
+ syntaxPunctuation: "neutral",
67
+ muted: "neutral",
68
+ dim: "neutral",
69
+ thinkingText: "neutral",
70
+ toolOutput: "neutral",
71
+ mdLinkUrl: "neutral",
72
+ mdQuote: "neutral",
73
+ mdQuoteBorder: "neutral",
74
+ mdHr: "neutral",
75
+ mdCodeBlockBorder: "neutral",
76
+ toolDiffContext: "neutral",
77
+ syntaxComment: "neutral",
78
+ scrollbarTrack: "neutral",
79
+ scrollbarThumb: "neutral",
80
+ searchMatchText: "neutral",
81
+ borderMuted: "neutral",
82
+ accent: "violet",
83
+ borderAccent: "violet",
84
+ customMessageLabel: "violet",
85
+ mdCode: "violet",
86
+ mdListBullet: "violet",
87
+ syntaxType: "violet",
88
+ border: "blue",
89
+ mdLink: "blue",
90
+ syntaxKeyword: "blue",
91
+ syntaxVariable: "calamine",
92
+ success: "green",
93
+ mdCodeBlock: "green",
94
+ toolDiffAdded: "green",
95
+ bashMode: "green",
96
+ syntaxNumber: "green",
97
+ error: "red",
98
+ toolDiffRemoved: "red",
99
+ warning: "yellow",
100
+ mdHeading: "yellow",
101
+ syntaxFunction: "yellow",
102
+ syntaxString: "orange",
103
+ thinkingOff: "neutral",
104
+ thinkingMinimal: "thinkingSlate",
105
+ thinkingLow: "thinkingBlue",
106
+ thinkingMedium: "thinkingPeriwinkle",
107
+ thinkingHigh: "thinkingViolet",
108
+ thinkingXhigh: "thinkingMagenta",
109
+ thinkingMax: "thinkingRed",
110
+ };
111
+ /** Palette slots for tokens that would otherwise share a hue with a similar token. */
112
+ const TOKEN_SLOTS = {
113
+ syntaxString: 2,
114
+ syntaxNumber: 5,
115
+ searchMatchBg: 3,
116
+ };
117
+ const LEVELS = {
118
+ panel: {
119
+ dark: {
120
+ coefficients: [0.29131, -0.39746, 2.33185, -0.85524, -1.2076, 0.86276],
121
+ reachable: [0, 0.979],
122
+ },
123
+ light: {
124
+ coefficients: [
125
+ -3.74073, 27.94549, -78.44258, 112.6798, -79.60015, 22.11277,
126
+ ],
127
+ reachable: [0.348, 1],
128
+ },
129
+ },
130
+ track: {
131
+ dark: {
132
+ coefficients: [0.39028, -0.23015, 0.83573, 2.43829, -4.38292, 2.01582],
133
+ reachable: [0, 0.946],
134
+ },
135
+ light: {
136
+ coefficients: [
137
+ -5.24921, 38.37322, -107.28833, 152.10005, -106.17127, 29.18061,
138
+ ],
139
+ reachable: [0.368, 1],
140
+ },
141
+ },
142
+ thinking0: {
143
+ dark: {
144
+ coefficients: [0.52988, -0.05809, -0.30924, 4.63567, -6.52933, 2.89108],
145
+ reachable: [0, 0.873],
146
+ },
147
+ light: {
148
+ coefficients: [
149
+ -28.27749, 182.85284, -469.62416, 603.15916, -384.59976, 97.35147,
150
+ ],
151
+ reachable: [0.51, 1],
152
+ },
153
+ },
154
+ thinking1: {
155
+ dark: {
156
+ coefficients: [0.55278, -0.03667, -0.45659, 4.95347, -6.90265, 3.0706],
157
+ reachable: [0, 0.858],
158
+ },
159
+ light: {
160
+ coefficients: [
161
+ -37.10484, 235.86282, -596.62344, 754.3633, -474.00763, 118.3551,
162
+ ],
163
+ reachable: [0.535, 1],
164
+ },
165
+ },
166
+ thinking2: {
167
+ dark: {
168
+ coefficients: [0.57486, -0.01765, -0.58987, 5.25227, -7.27175, 3.25532],
169
+ reachable: [0, 0.842],
170
+ },
171
+ light: {
172
+ coefficients: [
173
+ -59.89653, 377.05024, -945.07843, 1182.03145, -734.96375, 181.68658,
174
+ ],
175
+ reachable: [0.556, 1],
176
+ },
177
+ },
178
+ thinking3: {
179
+ dark: {
180
+ coefficients: [0.59621, -0.00062, -0.71148, 5.53588, -7.6392, 3.44606],
181
+ reachable: [0, 0.827],
182
+ },
183
+ light: {
184
+ coefficients: [
185
+ -72.07122, 445.84082, -1099.57352, 1353.88793, -829.53392, 202.26164,
186
+ ],
187
+ reachable: [0.58, 1],
188
+ },
189
+ },
190
+ thinking4: {
191
+ dark: {
192
+ coefficients: [0.61691, 0.01462, -0.82288, 5.80651, -8.00641, 3.64333],
193
+ reachable: [0, 0.811],
194
+ },
195
+ light: {
196
+ coefficients: [
197
+ -110.14338, 674.21488, -1645.75941, 2004.32367, -1215.15899, 293.3183,
198
+ ],
199
+ reachable: [0.6, 1],
200
+ },
201
+ },
202
+ thinking5: {
203
+ dark: {
204
+ coefficients: [0.63702, 0.02826, -0.92498, 6.06465, -8.37246, 3.84651],
205
+ reachable: [0, 0.795],
206
+ },
207
+ light: {
208
+ coefficients: [
209
+ -175.47701, 1063.54495, -2570.70594, 3098.80776, -1860.15527, 444.76392,
210
+ ],
211
+ reachable: [0.62, 1],
212
+ },
213
+ },
214
+ thinking6: {
215
+ dark: {
216
+ coefficients: [0.65658, 0.04044, -1.01835, 6.30989, -8.73529, 4.05439],
217
+ reachable: [0, 0.779],
218
+ },
219
+ light: {
220
+ coefficients: [
221
+ -183.81712, 1094.70055, -2602.68539, 3088.71276, -1826.91131, 430.75931,
222
+ ],
223
+ reachable: [0.643, 1],
224
+ },
225
+ },
226
+ subtle: {
227
+ dark: {
228
+ coefficients: [0.56762, -0.02475, -0.5383, 5.12628, -7.10931, 3.17324],
229
+ reachable: [0, 0.848],
230
+ },
231
+ light: {
232
+ coefficients: [
233
+ -232.85459, 1376.54473, -3249.11801, 3827.91186, -2248.29472, 526.55751,
234
+ ],
235
+ reachable: [0.657, 1],
236
+ },
237
+ },
238
+ thumb: {
239
+ dark: {
240
+ coefficients: [0.60323, 0.00278, -0.73328, 5.57157, -7.68067, 3.46933],
241
+ reachable: [0, 0.823],
242
+ },
243
+ light: {
244
+ coefficients: [
245
+ -82.89897, 511.01355, -1255.98095, 1540.76821, -940.68087, 228.58523,
246
+ ],
247
+ reachable: [0.586, 1],
248
+ },
249
+ },
250
+ readable: {
251
+ dark: {
252
+ coefficients: [0.66937, 0.04704, -1.06871, 6.43941, -8.9332, 4.17229],
253
+ reachable: [0, 0.77],
254
+ },
255
+ light: {
256
+ coefficients: [
257
+ -1554.52576, 8733.56817, -19604.93507, 21977.72696, -12300.99599,
258
+ 2749.81288,
259
+ ],
260
+ reachable: [0.751, 1],
261
+ },
262
+ },
263
+ emphasis: {
264
+ dark: {
265
+ coefficients: [0.7303, 0.07695, -1.31626, 7.1681, -10.14436, 4.92846],
266
+ reachable: [0, 0.712],
267
+ },
268
+ light: {
269
+ coefficients: [
270
+ -4948.31942, 26870.91986, -58334.48399, 63280.17197, -34298.01053,
271
+ 7430.30146,
272
+ ],
273
+ reachable: [0.811, 1],
274
+ },
275
+ },
276
+ textOnPanel: {
277
+ dark: {
278
+ coefficients: [0.86713, 0.05232, -0.89428, 4.79014, -5.5432, 1.75023],
279
+ reachable: [0, 0.542],
280
+ },
281
+ light: {
282
+ coefficients: [
283
+ -8570.89457, 43954.60805, -90084.00702, 92220.6791, -47152.15802,
284
+ 9632.27113,
285
+ ],
286
+ reachable: [0.867, 1],
287
+ },
288
+ },
289
+ text: {
290
+ dark: {
291
+ coefficients: [0.89242, 0.02311, -0.44862, 2.34417, -0.06084, -2.63844],
292
+ reachable: [0, 0.5],
293
+ },
294
+ light: {
295
+ coefficients: [
296
+ -2004.67048, 6664.47299, -6060.70202, -1792.61209, 5133.82359,
297
+ -1939.85583,
298
+ ],
299
+ reachable: [0.894, 1],
300
+ },
301
+ },
302
+ };
303
+ const TOOL_PANELS = [
304
+ "toolPendingBg",
305
+ "toolSuccessBg",
306
+ "toolErrorBg",
307
+ ];
308
+ const MESSAGE_PANELS = ["userMessageBg", "customMessageBg"];
309
+ const PANELS = [
310
+ "userMessageBg",
311
+ "toolPendingBg",
312
+ "toolSuccessBg",
313
+ "toolErrorBg",
314
+ "selectedBg",
315
+ "searchMatchBg",
316
+ "customMessageBg",
317
+ ];
318
+ const THINKING = [
319
+ "thinkingOff",
320
+ "thinkingMinimal",
321
+ "thinkingLow",
322
+ "thinkingMedium",
323
+ "thinkingHigh",
324
+ "thinkingXhigh",
325
+ "thinkingMax",
326
+ ];
327
+ const THINKING_LEVELS = [
328
+ "thinking0",
329
+ "thinking1",
330
+ "thinking2",
331
+ "thinking3",
332
+ "thinking4",
333
+ "thinking5",
334
+ "thinking6",
335
+ ];
336
+ const each = (tokens, on, level) => tokens.map((token) => ({ token, on, level }));
337
+ const RULES = [
338
+ ...each(PANELS, ["background"], "panel"),
339
+ { token: "text", on: ["background"], level: "text" },
340
+ { token: "text", on: ["selectedBg"], level: "textOnPanel" },
341
+ { token: "userMessageText", on: ["userMessageBg"], level: "textOnPanel" },
342
+ { token: "toolTitle", on: TOOL_PANELS, level: "textOnPanel" },
343
+ ...each(["accent", "success", "error", "warning"], ["background", "selectedBg", ...TOOL_PANELS], "readable"),
344
+ {
345
+ token: "muted",
346
+ on: ["background", "selectedBg", "customMessageBg", ...TOOL_PANELS],
347
+ level: "readable",
348
+ },
349
+ {
350
+ token: "dim",
351
+ on: ["background", "selectedBg", "customMessageBg", ...TOOL_PANELS],
352
+ level: "subtle",
353
+ },
354
+ { token: "thinkingText", on: ["background"], level: "readable" },
355
+ {
356
+ token: "customMessageText",
357
+ on: ["customMessageBg", ...TOOL_PANELS],
358
+ level: "readable",
359
+ },
360
+ {
361
+ token: "customMessageLabel",
362
+ on: ["background", "customMessageBg", "selectedBg", ...TOOL_PANELS],
363
+ level: "readable",
364
+ },
365
+ {
366
+ token: "toolOutput",
367
+ on: ["background", ...TOOL_PANELS],
368
+ level: "readable",
369
+ },
370
+ ...each([
371
+ "mdHeading",
372
+ "mdLink",
373
+ "mdLinkUrl",
374
+ "mdCode",
375
+ "mdQuote",
376
+ "mdCodeBlockBorder",
377
+ "mdListBullet",
378
+ ], ["background", ...MESSAGE_PANELS], "readable"),
379
+ {
380
+ token: "mdCodeBlock",
381
+ on: ["background", ...MESSAGE_PANELS, ...TOOL_PANELS],
382
+ level: "readable",
383
+ },
384
+ ...each(["toolDiffAdded", "toolDiffRemoved", "toolDiffContext"], ["background", ...TOOL_PANELS], "readable"),
385
+ ...each([
386
+ "syntaxComment",
387
+ "syntaxKeyword",
388
+ "syntaxFunction",
389
+ "syntaxVariable",
390
+ "syntaxString",
391
+ "syntaxNumber",
392
+ "syntaxType",
393
+ "syntaxOperator",
394
+ "syntaxPunctuation",
395
+ ], ["background", ...MESSAGE_PANELS, ...TOOL_PANELS], "readable"),
396
+ { token: "searchMatchText", on: ["searchMatchBg"], level: "readable" },
397
+ ...each(["bashMode", "border", "borderAccent"], ["background"], "readable"),
398
+ { token: "borderMuted", on: ["background"], level: "subtle" },
399
+ ...each(["mdQuoteBorder", "mdHr"], ["background", ...MESSAGE_PANELS, ...TOOL_PANELS], "readable"),
400
+ { token: "scrollbarTrack", on: ["background"], level: "track" },
401
+ { token: "scrollbarThumb", on: ["scrollbarTrack"], level: "thumb" },
402
+ ...THINKING.map((token, index) => ({
403
+ token,
404
+ on: ["background"],
405
+ level: THINKING_LEVELS[index],
406
+ })),
407
+ ];
408
+ /** Relaxation compresses levels stronger than this one toward it before weakening all levels. */
409
+ const READABLE_FLOOR = {
410
+ dark: "readable",
411
+ light: "subtle",
412
+ };
413
+ /** Body text uses the terminal's foreground when it reaches this level, which is clearly stronger than muted. */
414
+ const FOREGROUND_LEVEL = "emphasis";
415
+ /** Text-level tokens that take the terminal's foreground. */
416
+ const FOREGROUND_TOKENS = [
417
+ "text",
418
+ "userMessageText",
419
+ "toolTitle",
420
+ ];
421
+ /** WCAG 2 contrast ratio that body text must reach on the surfaces it is drawn on. */
422
+ const TEXT_MINIMUM_WCAG_CONTRAST = 4.5;
423
+ /** Tokens in dependency order: every surface before the tokens drawn on it. */
424
+ const SOLVE_ORDER = (() => {
425
+ const order = [];
426
+ const visit = (token) => {
427
+ if (order.includes(token))
428
+ return;
429
+ for (const rule of RULES) {
430
+ if (rule.token !== token)
431
+ continue;
432
+ for (const surface of rule.on)
433
+ if (surface !== "background")
434
+ visit(surface);
435
+ }
436
+ order.push(token);
437
+ };
438
+ for (const rule of RULES)
439
+ visit(rule.token);
440
+ return order;
441
+ })();
442
+ /** OKLab lightness of an sRGB color, 0-1. */
443
+ function oklabLightness(color) {
444
+ return colorToOklch(rgbColor(color.r, color.g, color.b)).l;
445
+ }
446
+ /** WCAG 2 relative luminance. */
447
+ export function relativeLuminance({ r, g, b }) {
448
+ const linear = (channel) => {
449
+ const value = channel / 255;
450
+ return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
451
+ };
452
+ return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);
453
+ }
454
+ /** WCAG 2 contrast ratio, 1-21. */
455
+ export function wcagContrast(first, second) {
456
+ const a = relativeLuminance(first);
457
+ const b = relativeLuminance(second);
458
+ return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
459
+ }
460
+ /**
461
+ * Whether a terminal is dark or light, from its reported colors: the direction of its own foreground
462
+ * when text can be readable that way, otherwise dark when white text has more contrast on the
463
+ * background than black text.
464
+ */
465
+ export function terminalAppearance(background, foreground) {
466
+ const white = { r: 255, g: 255, b: 255 };
467
+ const black = { r: 0, g: 0, b: 0 };
468
+ const whiteContrast = wcagContrast(white, background);
469
+ const blackContrast = wcagContrast(black, background);
470
+ if (foreground) {
471
+ const foregroundL = oklabLightness(foreground);
472
+ const backgroundL = oklabLightness(background);
473
+ if (Math.abs(foregroundL - backgroundL) > 0.05) {
474
+ const appearance = foregroundL > backgroundL ? "dark" : "light";
475
+ const best = appearance === "dark" ? whiteContrast : blackContrast;
476
+ if (best >= TEXT_MINIMUM_WCAG_CONTRAST)
477
+ return appearance;
478
+ }
479
+ }
480
+ return whiteContrast >= blackContrast ? "dark" : "light";
481
+ }
482
+ // ============================================================================
483
+ // Generation
484
+ // ============================================================================
485
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
486
+ function hexOf({ r, g, b }) {
487
+ return `#${[r, g, b].map((channel) => Math.round(channel).toString(16).padStart(2, "0")).join("")}`;
488
+ }
489
+ /** Saturation weight at a lightness: a Gaussian (center 0.5, sigma 0.25), 0 at black and white, 1 in the middle. */
490
+ function bellWeight(lightness) {
491
+ const gaussian = (x) => Math.exp(-((x - 0.5) ** 2) / (2 * 0.25 ** 2));
492
+ return (gaussian(lightness) - gaussian(0)) / (1 - gaussian(0));
493
+ }
494
+ /** A family's saturation curve relative to its maximum: 1 at mid lightness, `min / max` at black and white. */
495
+ function saturationCurve({ saturation: { min, max } }, lightness) {
496
+ const floor = max > 0 ? min / max : 1;
497
+ return floor + (1 - floor) * bellWeight(lightness);
498
+ }
499
+ /** The target lightness for a level on a surface, or undefined where the level cannot be reached. */
500
+ function levelTarget(level, appearance, surfaceL) {
501
+ const curve = LEVELS[level][appearance];
502
+ if (surfaceL < curve.reachable[0] || surfaceL > curve.reachable[1])
503
+ return undefined;
504
+ return curve.coefficients.reduce((sum, coefficient, power) => sum + coefficient * surfaceL ** power, 0);
505
+ }
506
+ /**
507
+ * Generate the system theme's colors from the terminal's reported colors.
508
+ */
509
+ export function generateSystemThemeColors(input) {
510
+ const saturation = clamp(input.saturation ?? 1, 0, 1);
511
+ const { background, foreground } = input;
512
+ if (!background)
513
+ return indexedColors(saturation, input.appearanceHint);
514
+ const palette = input.palette?.length === 16 ? input.palette.map(okhslOf) : undefined;
515
+ const appearance = terminalAppearance(background, foreground);
516
+ const lighter = appearance === "dark";
517
+ const extreme = lighter ? 1 : 0;
518
+ const backgroundL = oklabLightness(background);
519
+ /**
520
+ * A token's color at an OKLab lightness. With a palette, the palette color's saturation applies at its
521
+ * own lightness and falls off toward black and white along the family's curve, never rising above it.
522
+ */
523
+ const paint = (token, oklabL) => {
524
+ const lightness = oklabToOkhslLightness(oklabL);
525
+ const family = FAMILIES[TOKEN_FAMILIES[token]];
526
+ if (!palette) {
527
+ const { min, max } = family.saturation;
528
+ return okhslColor(family.hue, (min + (max - min) * bellWeight(lightness)) * saturation, lightness);
529
+ }
530
+ return anchored(palette[TOKEN_SLOTS[token] ?? family.slot], family, lightness, saturation);
531
+ };
532
+ /**
533
+ * The lightness a rule needs on a surface, relaxed by `t`: from 0 to 1, levels stronger than the readable
534
+ * floor move toward it; from 1 to 2, all levels move toward the surface itself.
535
+ */
536
+ const target = (level, surfaceL, t) => {
537
+ const reached = levelTarget(level, appearance, surfaceL);
538
+ if (reached === undefined && t === 0)
539
+ return undefined;
540
+ const distance = (reached ?? extreme) - surfaceL;
541
+ const floor = (levelTarget(READABLE_FLOOR[appearance], appearance, surfaceL) ??
542
+ extreme) - surfaceL;
543
+ const compressed = Math.abs(distance) > Math.abs(floor)
544
+ ? distance - (distance - floor) * Math.min(t, 1)
545
+ : distance;
546
+ return surfaceL + compressed * (1 - Math.max(0, t - 1));
547
+ };
548
+ /**
549
+ * Keep a panel light enough (or dark enough) that white (or black) text still reaches the body text
550
+ * minimum on it. This only matters for backgrounds near mid-gray, where it barely does on the background.
551
+ */
552
+ const extremeText = lighter
553
+ ? { r: 255, g: 255, b: 255 }
554
+ : { r: 0, g: 0, b: 0 };
555
+ const readable = (color) => wcagContrast(extremeText, color) >= TEXT_MINIMUM_WCAG_CONTRAST;
556
+ const limitPanel = (token, l) => {
557
+ const color = paint(token, l);
558
+ if (readable(color))
559
+ return color;
560
+ let [low, high] = [backgroundL, l];
561
+ for (let index = 0; index < 20; index++) {
562
+ const middle = (low + high) / 2;
563
+ if (readable(paint(token, middle)))
564
+ low = middle;
565
+ else
566
+ high = middle;
567
+ }
568
+ return paint(token, low);
569
+ };
570
+ const solve = (t) => {
571
+ const colors = new Map([
572
+ ["background", background],
573
+ ]);
574
+ for (const token of SOLVE_ORDER) {
575
+ const targets = [];
576
+ for (const rule of RULES) {
577
+ if (rule.token !== token)
578
+ continue;
579
+ for (const surface of rule.on) {
580
+ const value = target(rule.level, oklabLightness(colors.get(surface) ?? background), t);
581
+ if (value === undefined || value < 0 || value > 1)
582
+ return undefined;
583
+ targets.push(value);
584
+ }
585
+ }
586
+ const l = lighter ? Math.max(...targets) : Math.min(...targets);
587
+ colors.set(token, PANELS.includes(token)
588
+ ? limitPanel(token, l)
589
+ : paint(token, l));
590
+ }
591
+ return colors;
592
+ };
593
+ let relaxation = 0;
594
+ let colors = solve(0);
595
+ if (!colors) {
596
+ // Mid-gray backgrounds cannot fit every level: relax as little as possible. Full relaxation always fits.
597
+ let [low, high] = [0, 2];
598
+ colors = solve(high);
599
+ for (let index = 0; index < 20; index++) {
600
+ const middle = (low + high) / 2;
601
+ const attempt = solve(middle);
602
+ if (attempt)
603
+ [high, colors] = [middle, attempt];
604
+ else
605
+ low = middle;
606
+ }
607
+ relaxation = high;
608
+ }
609
+ const solved = colors ?? new Map();
610
+ const surfacesOf = (token) => RULES.filter((rule) => rule.token === token).flatMap((rule) => rule.on.map((surface) => solved.get(surface) ?? background));
611
+ const result = {};
612
+ for (const token of Object.keys(TOKEN_FAMILIES)) {
613
+ const color = solved.get(token);
614
+ result[token] = color ? hexOf(color) : "";
615
+ }
616
+ for (const token of FOREGROUND_TOKENS) {
617
+ const surfaces = surfacesOf(token);
618
+ // Body text uses the terminal's own foreground where it is clearly stronger than muted text; otherwise
619
+ // the foreground's hue at just enough lightness.
620
+ let text = solved.get(token);
621
+ if (foreground) {
622
+ const targets = surfaces.map((surface) => target(FOREGROUND_LEVEL, oklabLightness(surface), relaxation));
623
+ if (targets.every((value) => value !== undefined && value >= 0 && value <= 1)) {
624
+ const needed = lighter
625
+ ? Math.max(...targets)
626
+ : Math.min(...targets);
627
+ const foregroundL = oklabLightness(foreground);
628
+ if (lighter ? foregroundL >= needed : foregroundL <= needed) {
629
+ result[token] = "";
630
+ continue;
631
+ }
632
+ text = anchored(okhslOf(foreground), FAMILIES.neutral, oklabToOkhslLightness(needed), saturation);
633
+ }
634
+ }
635
+ // Body text keeps at least 4.5:1 on the surfaces it is drawn on, even on relaxed mid-gray backgrounds.
636
+ if (text)
637
+ result[token] = hexOf(withTextContrast(text, surfaces, lighter));
638
+ }
639
+ return { colors: result, dim: [], appearance };
640
+ }
641
+ function okhslOf({ r, g, b }) {
642
+ return colorToOkhsl(rgbColor(r, g, b));
643
+ }
644
+ /**
645
+ * A source color's hue at another OKHSL lightness. Its saturation applies at its own lightness and falls off
646
+ * toward black and white along the family's saturation curve, never rising above it.
647
+ */
648
+ function anchored(source, family, lightness, saturation) {
649
+ const anchor = saturationCurve(family, source.l);
650
+ const falloff = anchor > 0 ? Math.min(1, saturationCurve(family, lightness) / anchor) : 1;
651
+ return okhslColor(source.h, source.s * falloff * saturation, lightness);
652
+ }
653
+ /** Move a text color toward white or black until it reaches the WCAG minimum on every surface. */
654
+ function withTextContrast(color, surfaces, lighter) {
655
+ const meets = (candidate) => surfaces.every((surface) => wcagContrast(candidate, surface) >= TEXT_MINIMUM_WCAG_CONTRAST);
656
+ if (meets(color))
657
+ return color;
658
+ const { h, s, l } = okhslOf(color);
659
+ const at = (lightness) => okhslColor(h, s, lightness);
660
+ const extreme = lighter ? 1 : 0;
661
+ if (!meets(at(extreme)))
662
+ return at(extreme);
663
+ let [low, high] = [l, extreme];
664
+ for (let index = 0; index < 20; index++) {
665
+ const middle = (low + high) / 2;
666
+ if (meets(at(middle)))
667
+ high = middle;
668
+ else
669
+ low = middle;
670
+ }
671
+ return at(high);
672
+ }
673
+ /**
674
+ * Colors for terminals that reported nothing: the terminal renders ANSI indices 0-15 and the default
675
+ * colors with its own theme, so they fit any background. Neutral tokens below body text are faint (SGR 2)
676
+ * instead of bright black, which some themes make nearly invisible. Panels have no background.
677
+ */
678
+ function indexedColors(saturation, appearance) {
679
+ const colors = {};
680
+ const dim = [];
681
+ for (const [token, familyName] of Object.entries(TOKEN_FAMILIES)) {
682
+ if (PANELS.includes(token)) {
683
+ colors[token] = "";
684
+ continue;
685
+ }
686
+ const neutral = familyName === "neutral";
687
+ colors[token] =
688
+ !neutral && saturation > 0
689
+ ? (TOKEN_SLOTS[token] ?? FAMILIES[familyName].slot)
690
+ : "";
691
+ if (neutral && !FOREGROUND_TOKENS.includes(token))
692
+ dim.push(token);
693
+ }
694
+ return { colors, dim, appearance };
695
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Provenance: @earendil-works/pi-coding-agent 0.87.1 (MIT), commit f07218c4d4bbc12bef056a7058c3dd49dfe41abe,
2
+ * Provenance: @earendil-works/pi-coding-agent 0.99.2 (MIT), commit 005af57d88ee23b33778f343a9595b32e67ff788,
3
3
  * packages/coding-agent/src/modes/interactive/theme/theme-controller.ts.
4
4
  * Modifications: Source-synchronized controller port: renamed owner class, injected dependency-free
5
5
  * settings/runtime ports, remapped private theme helpers to the public-backed A1 theme adapter, and