@timurproko/a1 0.1.8-dev.157 → 0.1.8-dev.214

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 (171) hide show
  1. package/README.md +32 -0
  2. package/bin/cli.js +2 -2
  3. package/bin/sync-pi-tui-proxy.js +1 -1
  4. package/bin/ui.js +18 -3
  5. package/dist/cli/dispatch.js +3 -3
  6. package/dist/cli/packages.js +1 -1
  7. package/dist/cli/version-stats.js +3 -3
  8. package/dist/composition/owned-ui.d.ts +4 -1
  9. package/dist/composition/owned-ui.js +9 -6
  10. package/dist/composition/settings-route-host.js +6 -4
  11. package/dist/contracts/agent-engine/capability-ports.d.ts +2 -2
  12. package/dist/contracts/agent-engine/domain-validation.js +22 -4
  13. package/dist/contracts/agent-engine/domain.d.ts +17 -0
  14. package/dist/contracts/owned-ui/model.d.ts +8 -0
  15. package/dist/contracts/owned-ui/validation.js +11 -0
  16. package/dist/features/launch/development-launch.js +1 -1
  17. package/dist/features/owned-ui/customization.d.ts +2 -0
  18. package/dist/features/owned-ui/customization.js +2 -0
  19. package/dist/features/owned-ui/diagnostics.d.ts +1 -0
  20. package/dist/features/owned-ui/diagnostics.js +1 -0
  21. package/dist/features/owned-ui/index.d.ts +1 -0
  22. package/dist/features/owned-ui/index.js +1 -0
  23. package/dist/features/owned-ui/project-trust-prompt.d.ts +25 -0
  24. package/dist/features/owned-ui/project-trust-prompt.js +149 -0
  25. package/dist/features/owned-ui/settings-app.js +69 -44
  26. package/dist/features/workspace/reducer.d.ts +1 -0
  27. package/dist/features/workspace/reducer.js +1 -0
  28. package/dist/features/workspace/router.d.ts +1 -0
  29. package/dist/features/workspace/router.js +1 -0
  30. package/dist/features/workspace/store.d.ts +1 -0
  31. package/dist/features/workspace/store.js +1 -0
  32. package/dist/foundation/launch-guardian/main.js +1 -1
  33. package/dist/foundation/native-host-protocol/codec.d.ts +2 -0
  34. package/dist/foundation/native-host-protocol/codec.js +2 -0
  35. package/dist/foundation/process-containment/linux-process-inspector.d.ts +1 -0
  36. package/dist/foundation/process-containment/linux-process-inspector.js +1 -0
  37. package/dist/foundation/process-containment/native-guardian-containment.d.ts +1 -0
  38. package/dist/foundation/process-containment/native-guardian-containment.js +1 -0
  39. package/dist/foundation/process-containment/windows-process-inspector.d.ts +1 -0
  40. package/dist/foundation/process-containment/windows-process-inspector.js +1 -0
  41. package/dist/foundation/protocol/client.d.ts +1 -0
  42. package/dist/foundation/protocol/client.js +1 -0
  43. package/dist/foundation/protocol/messages.d.ts +1 -0
  44. package/dist/foundation/protocol/messages.js +1 -0
  45. package/dist/foundation/release/bootstrap.js +6 -6
  46. package/dist/foundation/release/cohort-state.d.ts +1 -0
  47. package/dist/foundation/release/cohort-state.js +1 -0
  48. package/dist/foundation/release/release-gc.js +1 -1
  49. package/dist/foundation/release/release.js +1 -1
  50. package/dist/foundation/release/update-transaction.d.ts +1 -0
  51. package/dist/foundation/release/update-transaction.js +1 -0
  52. package/dist/foundation/release/update.js +12 -12
  53. package/dist/foundation/storage/control-store.d.ts +1 -0
  54. package/dist/foundation/storage/control-store.js +1 -0
  55. package/dist/foundation/structured-agent-runtime/backpressure.d.ts +1 -0
  56. package/dist/foundation/structured-agent-runtime/backpressure.js +1 -0
  57. package/dist/foundation/structured-agent-runtime/commands.d.ts +1 -0
  58. package/dist/foundation/structured-agent-runtime/commands.js +1 -0
  59. package/dist/foundation/structured-agent-runtime/reconnection.d.ts +1 -0
  60. package/dist/foundation/structured-agent-runtime/reconnection.js +1 -0
  61. package/dist/foundation/structured-agent-runtime/state.d.ts +1 -0
  62. package/dist/foundation/structured-agent-runtime/state.js +1 -0
  63. package/dist/foundation/supervision/main.js +1 -1
  64. package/dist/foundation/supervision/server.d.ts +1 -0
  65. package/dist/foundation/supervision/server.js +4 -6
  66. package/dist/integrations/pi/components/conformance.d.ts +1 -0
  67. package/dist/integrations/pi/components/conformance.js +1 -0
  68. package/dist/integrations/pi/components/shell-components.d.ts +1 -0
  69. package/dist/integrations/pi/components/shell-components.js +1 -0
  70. package/dist/integrations/pi/components/shell-editor-autocomplete.js +10 -0
  71. package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
  72. package/dist/integrations/pi/components/shell-footer-status.js +19 -13
  73. package/dist/integrations/pi/components/shell-presenters-info.d.ts +36 -0
  74. package/dist/integrations/pi/components/shell-presenters-info.js +78 -0
  75. package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -36
  76. package/dist/integrations/pi/components/shell-presenters-transcript.js +88 -128
  77. package/dist/integrations/pi/components/shell-shared-facade.d.ts +11 -1
  78. package/dist/integrations/pi/components/shell-shared-facade.js +1 -1
  79. package/dist/integrations/pi/engine/adapter.d.ts +22 -4
  80. package/dist/integrations/pi/engine/adapter.js +220 -70
  81. package/dist/integrations/pi/engine/conformance.d.ts +2 -0
  82. package/dist/integrations/pi/engine/conformance.js +2 -0
  83. package/dist/integrations/pi/engine/http-dispatcher.d.ts +4 -0
  84. package/dist/integrations/pi/engine/http-dispatcher.js +25 -0
  85. package/dist/integrations/pi/engine/index.d.ts +3 -0
  86. package/dist/integrations/pi/engine/index.js +3 -0
  87. package/dist/integrations/pi/engine/model-auth-integration.d.ts +1 -0
  88. package/dist/integrations/pi/engine/model-auth-integration.js +1 -0
  89. package/dist/integrations/pi/engine/package-integration.js +1 -1
  90. package/dist/integrations/pi/engine/project-trust-preflight.d.ts +22 -0
  91. package/dist/integrations/pi/engine/project-trust-preflight.js +50 -0
  92. package/dist/integrations/pi/engine/resource-extension-integration.d.ts +1 -0
  93. package/dist/integrations/pi/engine/resource-extension-integration.js +1 -0
  94. package/dist/integrations/pi/engine/runtime-integration.d.ts +16 -1
  95. package/dist/integrations/pi/engine/runtime-integration.js +34 -4
  96. package/dist/integrations/pi/engine/session-integration.d.ts +1 -0
  97. package/dist/integrations/pi/engine/session-integration.js +2 -1
  98. package/dist/integrations/pi/engine/settings-effects.d.ts +66 -0
  99. package/dist/integrations/pi/engine/settings-effects.js +249 -0
  100. package/dist/integrations/pi/engine/settings-integration.d.ts +14 -26
  101. package/dist/integrations/pi/engine/settings-integration.js +102 -108
  102. package/dist/integrations/pi/engine/workflow-controllers.d.ts +2 -1
  103. package/dist/integrations/pi/engine/workflow-controllers.js +1 -0
  104. package/dist/integrations/pi/session-ui/clipboard-image.d.ts +11 -0
  105. package/dist/integrations/pi/session-ui/clipboard-image.js +29 -0
  106. package/dist/integrations/pi/session-ui/index.d.ts +1 -0
  107. package/dist/integrations/pi/session-ui/index.js +1 -0
  108. package/dist/integrations/pi/session-ui/prompt-chips.js +6 -2
  109. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +39 -4
  110. package/dist/integrations/pi/session-ui/session-shell-root.js +215 -80
  111. package/dist/integrations/pi/session-ui/session-shell.d.ts +10 -1
  112. package/dist/integrations/pi/session-ui/session-shell.js +256 -45
  113. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +5 -0
  114. package/dist/integrations/pi/session-ui/session-viewport-controller.js +76 -11
  115. package/dist/integrations/pi/session-ui/stream-presentation-coalescer.d.ts +29 -0
  116. package/dist/integrations/pi/session-ui/stream-presentation-coalescer.js +79 -0
  117. package/dist/integrations/pi/session-ui/system-clipboard.d.ts +9 -0
  118. package/dist/integrations/pi/session-ui/system-clipboard.js +38 -9
  119. package/dist/integrations/pi/tui-runtime/adapter.d.ts +6 -0
  120. package/dist/integrations/pi/tui-runtime/adapter.js +166 -8
  121. package/dist/integrations/pi/tui-runtime/contracts.d.ts +25 -0
  122. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +63 -0
  123. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +240 -0
  124. package/dist/integrations/pi/tui-runtime/index.d.ts +2 -0
  125. package/dist/integrations/pi/tui-runtime/index.js +2 -0
  126. package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.d.ts +39 -0
  127. package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.js +139 -0
  128. package/dist/native/darwin-arm64/manifest.json +1 -1
  129. package/dist/native/linux-x64/manifest.json +1 -1
  130. package/dist/native/win32-x64/manifest.json +2 -2
  131. package/dist/native/win32-x64/process-guardian.exe +0 -0
  132. package/dist/ui/apps/host.js +5 -9
  133. package/dist/ui/apps/registry.d.ts +1 -0
  134. package/dist/ui/apps/registry.js +1 -0
  135. package/dist/ui/components/dialog-panel.js +5 -3
  136. package/dist/ui/components/frame.d.ts +1 -0
  137. package/dist/ui/components/frame.js +1 -0
  138. package/dist/ui/components/index.d.ts +1 -0
  139. package/dist/ui/components/index.js +1 -0
  140. package/dist/ui/components/line-input.d.ts +1 -6
  141. package/dist/ui/components/line-input.js +4 -9
  142. package/dist/ui/components/list-view.d.ts +0 -6
  143. package/dist/ui/components/list-view.js +14 -7
  144. package/dist/ui/components/progress-status.d.ts +2 -0
  145. package/dist/ui/components/progress-status.js +4 -0
  146. package/dist/ui/components/scrollbar.js +1 -1
  147. package/dist/ui/components/shortcuts.d.ts +1 -0
  148. package/dist/ui/components/shortcuts.js +1 -0
  149. package/dist/ui/components/spans.d.ts +2 -5
  150. package/dist/ui/components/spans.js +28 -8
  151. package/dist/ui/components/surface.d.ts +0 -5
  152. package/dist/ui/components/surface.js +1 -5
  153. package/dist/ui/components/text-selection.d.ts +10 -1
  154. package/dist/ui/components/text-selection.js +96 -29
  155. package/dist/ui/components/text.js +3 -3
  156. package/dist/ui/components/transcript-viewport.d.ts +42 -0
  157. package/dist/ui/components/transcript-viewport.js +287 -39
  158. package/dist/ui/settings/sections.d.ts +6 -19
  159. package/dist/ui/settings/sections.js +17 -15
  160. package/dist/ui/settings/session.d.ts +8 -18
  161. package/dist/ui/settings/session.js +60 -52
  162. package/dist/ui/settings/store.d.ts +1 -0
  163. package/dist/ui/settings/store.js +2 -1
  164. package/docs/architecture/code-documentation.md +77 -0
  165. package/docs/architecture/project-structure.md +15 -1
  166. package/docs/architecture/ui-reference-provenance.md +23 -3
  167. package/docs/ci-release-runbook.md +98 -7
  168. package/docs/manual-owned-ui-checkpoint.md +28 -1
  169. package/docs/manual-terminal-colour-check.md +4 -3
  170. package/docs/repository-governance-live-acceptance.md +77 -0
  171. package/package.json +11 -3
@@ -1,4 +1,4 @@
1
- import { GLOBAL_SCOPE, LineInput, PLAIN_THEME, ShortcutRegistry, assertNoShortcutConflicts, PROMPT_GLYPH, blockJumpTarget, dialogValueColumn, numericValues, RAIL_COLUMNS, renderDialogPanel, renderEmptyState, renderGroupHeader, renderInputRow, renderStatusLine, renderListRow, renderNote, dialogRowAt, menuRowAt, regionAt, renderValueMenu, withScrollbarRail, stepperEnds, steppedValue, valueColumnFor, valueMenuFrame, promptRule, caretCell, blockRowSpan, displayWidth, faint, handleLineInputKey, humanizeLabel, humanizeTitle, indexOfKey, isThumbRow, layoutList, moveSelection, rowKey, scrollForSelection, scrollbarGeometry, selectableIndexes, } from "../../ui/components/index.js";
1
+ import { GLOBAL_SCOPE, LineInput, PLAIN_THEME, ShortcutRegistry, assertNoShortcutConflicts, blockJumpTarget, dialogValueColumn, numericValues, RAIL_COLUMNS, renderDialogPanel, renderEmptyState, renderGroupHeader, renderInputRow, renderListRow, renderNote, renderStatusLine, dialogRowAt, menuRowAt, regionAt, renderValueMenu, withScrollbarRail, stepperEnds, steppedValue, valueColumnFor, valueMenuFrame, blockRowSpan, displayWidth, handleLineInputKey, humanizeLabel, humanizeTitle, indexOfKey, isThumbRow, layoutList, moveSelection, rowKey, scrollForSelection, scrollbarGeometry, scrollbarWheelRows, selectableIndexes, } from "../../ui/components/index.js";
2
2
  export const SETTINGS_APP_ID = "settings";
3
3
  export const SETTINGS_ROUTE = "settings";
4
4
  const SCOPE = SETTINGS_APP_ID;
@@ -57,7 +57,7 @@ export class SettingsApp {
57
57
  #session;
58
58
  #selectedKey;
59
59
  #scroll = 0;
60
- /** Keyboard navigation requests visibility once; pointer scrolling then stays free. */
60
+ // Invariant: keyboard navigation requests visibility once; pointer scrolling then stays free.
61
61
  #selectionNeedsReveal = true;
62
62
  #reveal;
63
63
  #notice = null;
@@ -66,19 +66,15 @@ export class SettingsApp {
66
66
  #structured = null;
67
67
  #loading = true;
68
68
  #interruptArmed = false;
69
- /** Values requested but not yet reflected by the source, keyed by entry. */
69
+ // Invariant: pending values remain visible until the source reflects them.
70
70
  #pending = new Map();
71
71
  #footerHeight = 1;
72
- /** Column the dialog's values start at, so a label stays a label. */
73
72
  #dialogValueColumn = 0;
74
- /** Screen row the dialog panel starts on, for pointer hits. */
75
73
  #panelTop = 0;
76
74
  #panelTopForFrame = 0;
77
- /** Row key under the pointer, and where each row was drawn last frame. */
78
75
  #hoverKey = null;
79
76
  #hoverRegion = "label";
80
77
  #frameRows = [];
81
- /** Where the value menu was drawn last frame, for hit testing. */
82
78
  #menuFrame = null;
83
79
  constructor(session) {
84
80
  this.#session = session;
@@ -93,12 +89,12 @@ export class SettingsApp {
93
89
  const theme = host.theme ?? PLAIN_THEME;
94
90
  this.#interruptArmed = host.interruptArmed;
95
91
  const rows = this.#rows();
92
+ const selected = indexOfKey(rows, this.#selectedKey);
96
93
  const footer = this.#footerLines(rect.width, theme);
97
94
  this.#footerHeight = footer.length;
98
95
  this.#panelTopForFrame = Math.max(0, rect.height - footer.length);
99
96
  this.#panelTop = this.#panelTopForFrame;
100
97
  const bodyHeight = Math.max(0, rect.height - footer.length);
101
- const selected = indexOfKey(rows, this.#selectedKey);
102
98
  if (this.#selectionNeedsReveal) {
103
99
  this.#scroll = scrollForSelection(rows, bodyHeight, this.#scroll, selected, this.#reveal);
104
100
  this.#selectionNeedsReveal = false;
@@ -154,7 +150,8 @@ export class SettingsApp {
154
150
  return this.#menuKey(data);
155
151
  if (this.#filter !== null)
156
152
  return this.#filterKey(data);
157
- const action = SETTINGS_SHORTCUTS.resolve(KEYS[data] ?? data, SCOPE);
153
+ const key = KEYS[data] ?? data;
154
+ const action = SETTINGS_SHORTCUTS.resolve(key, SCOPE);
158
155
  if (action === null)
159
156
  return { consumed: false };
160
157
  const rows = this.#rows();
@@ -192,7 +189,7 @@ export class SettingsApp {
192
189
  }
193
190
  case "first":
194
191
  case "last": {
195
- // End lands on the very last setting, not on the head of its section.
192
+ // Invariant: End lands on the very last setting, not on the head of its section.
196
193
  const selectable = selectableIndexes(rows);
197
194
  const target = action === "last" ? selectable.at(-1) : selectable[0];
198
195
  if (target !== undefined) {
@@ -217,7 +214,7 @@ export class SettingsApp {
217
214
  const overRow = menuRowAt(frame, event.row - 1, event.column);
218
215
  const overMenu = overRow !== null;
219
216
  if (event.kind === "motion") {
220
- // The menu owns the pointer, but the row it came from is still the thing
217
+ // Invariant: the menu owns the pointer, but the row it came from is still the thing
221
218
  // being changed, so its value keeps reading as the one under the pointer.
222
219
  // Only a dialog that takes the screen puts that out.
223
220
  const hadHover = this.#hoverKey !== menu.anchorKey || this.#hoverRegion !== "value";
@@ -236,7 +233,7 @@ export class SettingsApp {
236
233
  if (event.kind !== "press")
237
234
  return { consumed: true, render: false };
238
235
  if (overRow === null) {
239
- // A press anywhere else dismisses the menu rather than acting through it.
236
+ // Invariant: a press anywhere else dismisses the menu rather than acting through it.
240
237
  this.#menu = null;
241
238
  return { consumed: true };
242
239
  }
@@ -248,7 +245,7 @@ export class SettingsApp {
248
245
  }
249
246
  const open = this.#structured;
250
247
  if (open !== null) {
251
- // The panel owns the pointer while it is open; its flag rows are the targets.
248
+ // Invariant: the panel owns the pointer while it is open; its flag rows are the targets.
252
249
  // The panel's rows begin one line below its rule.
253
250
  const panel = { firstRow: this.#panelTop + 1, rows: open.flags.length, valueColumn: this.#dialogValueColumn };
254
251
  const row = event.row - 1 - panel.firstRow;
@@ -262,7 +259,7 @@ export class SettingsApp {
262
259
  }
263
260
  if (event.kind === "press") {
264
261
  open.index = row;
265
- // Pointing at the label picks the row; the value is what changes it,
262
+ // Rationale: pointing at the label picks the row; the value is what changes it,
266
263
  // exactly as in the list behind the dialog.
267
264
  const key = open.flags[row] ?? "";
268
265
  const width = displayWidth((open.record[key] ?? false) ? "true" : "false");
@@ -273,11 +270,12 @@ export class SettingsApp {
273
270
  return { consumed: true, render: false };
274
271
  }
275
272
  if (event.kind === "wheel-up" || event.kind === "wheel-down") {
276
- // The whole list pane owns wheel scrolling, including blank space beside
273
+ // Invariant: the whole list pane owns wheel scrolling, including blank space beside
277
274
  // short labels. It must not depend on finding an item under the pointer.
278
275
  if (event.row < 1 || event.row > this.#panelTopForFrame)
279
276
  return { consumed: false };
280
- this.#scroll = Math.max(0, this.#scroll + (event.kind === "wheel-down" ? 3 : -3));
277
+ const distance = scrollbarWheelRows(this.#scrollbarSpeed());
278
+ this.#scroll = Math.max(0, this.#scroll + (event.kind === "wheel-down" ? distance : -distance));
281
279
  return { consumed: true };
282
280
  }
283
281
  const row = this.#frameRows.find(candidate => candidate.screenRow === event.row - 1);
@@ -294,7 +292,7 @@ export class SettingsApp {
294
292
  const rows = this.#rows();
295
293
  const index = rows.findIndex(candidate => rowKey(candidate) === row.key);
296
294
  if (index >= 0) {
297
- // The pointer acts where it points; the arrow belongs to the keyboard.
295
+ // Rationale: the pointer acts where it points; the arrow belongs to the keyboard.
298
296
  this.#notice = null;
299
297
  if (this.#hoverRegion === "minus")
300
298
  this.#cycle(rows, index, -1);
@@ -318,7 +316,7 @@ export class SettingsApp {
318
316
  return;
319
317
  }
320
318
  const shown = this.#shownValue(entry);
321
- // A number is stepped, not picked from a list: it has its own two controls,
319
+ // Rationale: a number is stepped, not picked from a list: it has its own two controls,
322
320
  // and pointing at it is not a request for anything else.
323
321
  if (typeof shown === "number")
324
322
  return;
@@ -327,16 +325,12 @@ export class SettingsApp {
327
325
  return;
328
326
  }
329
327
  const current = shown === null ? 0 : Math.max(0, entry.choices.indexOf(shown));
330
- // Nothing is highlighted until the pointer or a key picks a row, so opening
331
- // the menu does not flash a highlight the reader did not ask for.
332
- this.#menu = { entry, current, anchorKey: `${entry.backend}:${entry.id}`, choices: entry.choices, index: -1 };
328
+ // Compatibility: pinned SelectList opens on the value currently in effect.
329
+ this.#menu = { entry, current, anchorKey: `${entry.backend}:${entry.id}`, choices: entry.choices, index: current };
333
330
  }
334
- /**
335
- * A structured setting is a set of flags, so it opens as its own list rather
336
- * than as a value menu: each row names one flag and toggles it in place.
337
- */
331
+ // Rationale: a structured setting opens as its own flag list rather than a value menu.
338
332
  #openStructured(entry) {
339
- // The flags come from the declaration, not from the stored value: an unset
333
+ // Invariant: the flags come from the declaration, not from the stored value: an unset
340
334
  // flag still has a row, showing the default the source would apply.
341
335
  if (entry.flags.length === 0) {
342
336
  this.#notice = `${labelOf(entry)} has nothing to configure`;
@@ -350,7 +344,7 @@ export class SettingsApp {
350
344
  const value = stored[flag.key];
351
345
  record[flag.key] = typeof value === "boolean" ? value : flag.fallback;
352
346
  }
353
- // The dialog takes the screen: the row it was opened from stops being the
347
+ // Invariant: the dialog takes the screen: the row it was opened from stops being the
354
348
  // thing under the pointer, so it stops looking like it.
355
349
  this.#hoverKey = null;
356
350
  this.#hoverRegion = "label";
@@ -398,7 +392,7 @@ export class SettingsApp {
398
392
  return { consumed: true };
399
393
  }
400
394
  if (key === "up" || key === "down") {
401
- // The keyboard starts from the value in effect rather than from the top.
395
+ // Compatibility: the keyboard starts from the value in effect rather than from the top.
402
396
  menu.index = menu.index < 0
403
397
  ? menu.current
404
398
  : Math.min(menu.choices.length - 1, Math.max(0, menu.index + (key === "down" ? 1 : -1)));
@@ -429,7 +423,7 @@ export class SettingsApp {
429
423
  }
430
424
  if (key === "up" || key === "down" || key === "shift+up" || key === "shift+down") {
431
425
  const rows = this.#rows();
432
- // Nothing found means nothing to move through; the key is still swallowed
426
+ // Rationale: nothing found means nothing to move through; the key is still swallowed
433
427
  // rather than typed into the search.
434
428
  if (selectableIndexes(rows).length === 0)
435
429
  return { consumed: true, render: false };
@@ -461,7 +455,7 @@ export class SettingsApp {
461
455
  }
462
456
  const shown = this.#shownValue(entry);
463
457
  if (typeof shown === "number") {
464
- // At the end of the range there is nothing to say: the arrow already reads
458
+ // Rationale: at the end of the range there is nothing to say: the arrow already reads
465
459
  // as unavailable, so a message would only repeat it.
466
460
  const next = steppedValue(rangeOf(entry), shown, delta);
467
461
  if (next !== null)
@@ -482,25 +476,38 @@ export class SettingsApp {
482
476
  }
483
477
  #apply(entry, value) {
484
478
  const key = `${entry.backend}:${entry.id}`;
485
- // Shown immediately so the row never lags a keypress, and so the next press
479
+ // Invariant: shown immediately so the row never lags a keypress, and so the next press
486
480
  // steps from here rather than from a value the source has not caught up to.
487
481
  this.#pending.set(key, value);
488
482
  void this.#session.change(entry.backend, entry.id, value).then(outcome => {
489
- if (outcome.failure !== null) {
483
+ if (outcome.failure !== null || outcome.status === "failed") {
484
+ this.#pending.delete(key);
485
+ this.#notice = `Could not save ${labelOf(entry)}: ${outcome.failure ?? "the effect failed"}`;
486
+ return;
487
+ }
488
+ if (outcome.status === "unavailable" || outcome.limitationReason !== null) {
490
489
  this.#pending.delete(key);
491
- this.#notice = `Could not save ${labelOf(entry)}: ${outcome.failure}`;
490
+ this.#notice = outcome.limitationReason ?? `${labelOf(entry)} is unavailable`;
492
491
  return;
493
492
  }
494
- // A later press may have moved on; only the last request clears itself.
493
+ // Concurrency: a later press may have moved on; only the last request clears itself.
495
494
  if (this.#pending.get(key) === value)
496
495
  this.#pending.delete(key);
497
- this.#notice = outcome.pendingRestart ? `${labelOf(entry)} applies on the next start` : null;
496
+ this.#notice = outcome.status === "deferred" && outcome.application !== null
497
+ ? `${labelOf(entry)} is stored and applies ${applicationLabel(outcome.application)}`
498
+ : null;
498
499
  });
499
500
  }
500
- /** What the row shows: the value asked for if one is outstanding, else the source's. */
501
501
  #shownValue(entry) {
502
502
  return this.#pending.get(`${entry.backend}:${entry.id}`) ?? entry.value;
503
503
  }
504
+ #scrollbarSpeed() {
505
+ const entry = this.#session.sections()
506
+ .flatMap(section => section.entries)
507
+ .find(candidate => candidate.backend === "a1" && candidate.id === "scrollbarSpeed");
508
+ const value = entry === undefined ? this.#session.value("scrollbarSpeed") : this.#shownValue(entry);
509
+ return isScrollbarSpeed(value) ? value : "normal";
510
+ }
504
511
  #jump(rows, target) {
505
512
  this.#select(rows, target);
506
513
  this.#reveal = blockRowSpan(rows, target);
@@ -519,7 +526,7 @@ export class SettingsApp {
519
526
  || labelOf(entry).toLowerCase().includes(needle);
520
527
  const rows = [];
521
528
  for (const section of this.#session.sections()) {
522
- // A section named by the search is what the reader asked for, so it arrives
529
+ // Rationale: a section named by the search is what the reader asked for, so it arrives
523
530
  // whole rather than narrowed to the entries that happen to repeat its name.
524
531
  const named = needle.length > 0 && section.title.toLowerCase().includes(needle);
525
532
  const entries = named ? section.entries : section.entries.filter(matches);
@@ -535,7 +542,7 @@ export class SettingsApp {
535
542
  if (section.readOnlyReason !== null && (needle.length === 0 || named)) {
536
543
  rows.push({ kind: "note", group: section.id, text: section.readOnlyReason });
537
544
  }
538
- // Presented in the order the source reports, which is the order the
545
+ // Invariant: presented in the order the source reports, which is the order the
539
546
  // pinned engine shows and is neither declaration order nor alphabetical.
540
547
  for (const entry of entries) {
541
548
  rows.push({
@@ -568,12 +575,13 @@ export class SettingsApp {
568
575
  const hovered = this.#hoverKey === key;
569
576
  return renderListRow(this.#viewRow(entry), { selected, hovered, region: this.#hoverRegion }, valueColumn, width, theme);
570
577
  }
571
- /** What the list view needs to draw a setting: its words, and where it can go. */
572
578
  #viewRow(entry) {
573
579
  const shown = this.#shownValue(entry);
574
580
  const value = entry.structured
575
581
  ? CONFIGURE
576
- : shown === null ? describeRaw(entry.rawValue) : displayValue(shown);
582
+ : shown === null
583
+ ? describeRaw(entry.rawValue)
584
+ : effectiveDisplay(entry, shown);
577
585
  const range = rangeOf(entry);
578
586
  return {
579
587
  key: `${entry.backend}:${entry.id}`,
@@ -602,13 +610,12 @@ export class SettingsApp {
602
610
  this.#menuFrame = frame;
603
611
  return renderValueMenu(lines, state, frame, theme);
604
612
  }
605
- /** What the panel needs to present a structured setting's parts. */
606
613
  #dialogLines(open, width, theme) {
607
614
  const rows = open.flags.map(key => {
608
615
  const declared = open.entry.flags.find(flag => flag.key === key);
609
616
  return {
610
617
  label: declared?.label ?? humanizeLabel(key),
611
- // The engine writes these as the booleans they are rather than as yes/no.
618
+ // Protocol: the engine writes these as the booleans they are rather than as yes/no.
612
619
  value: (open.record[key] ?? false) ? "true" : "false",
613
620
  ...(declared?.description === undefined ? {} : { description: declared.description }),
614
621
  };
@@ -621,8 +628,6 @@ export class SettingsApp {
621
628
  const open = this.#structured;
622
629
  if (open !== null)
623
630
  return this.#dialogLines(open, width, theme);
624
- // The hint is the declarations, so a key cannot be described here and bound
625
- // to something else.
626
631
  const hint = this.#interruptArmed ? "press ctrl+c again to exit A1" : SETTINGS_SHORTCUTS.hint(SCOPE);
627
632
  const status = renderStatusLine({ hint, report: this.#notice }, width, theme);
628
633
  const input = this.#filter;
@@ -648,6 +653,23 @@ function displayValue(value) {
648
653
  return value ? "yes" : "no";
649
654
  return String(value);
650
655
  }
656
+ function effectiveDisplay(entry, stored) {
657
+ const effective = entry.effectiveValue;
658
+ if (effective === stored)
659
+ return displayValue(stored);
660
+ const shownEffective = typeof effective === "string" || typeof effective === "number" || typeof effective === "boolean"
661
+ ? displayValue(effective)
662
+ : describeRaw(effective);
663
+ return `${displayValue(stored)} (effective ${shownEffective}; ${applicationLabel(entry.application)})`;
664
+ }
665
+ function applicationLabel(application) {
666
+ switch (application) {
667
+ case "live": return "live";
668
+ case "next-session": return "in the next session";
669
+ case "next-start": return "on the next start";
670
+ case "current-exit": return "when the current session exits";
671
+ }
672
+ }
651
673
  function describeRaw(value) {
652
674
  if (value === null || value === undefined)
653
675
  return "unset";
@@ -655,3 +677,6 @@ function describeRaw(value) {
655
677
  return Array.isArray(value) ? `${value.length} items` : "structured value";
656
678
  return String(value);
657
679
  }
680
+ function isScrollbarSpeed(value) {
681
+ return value === "normal" || value === "fast" || value === "high";
682
+ }
@@ -23,6 +23,7 @@ export type WorkspaceReducerResult<T = undefined> = {
23
23
  readonly code: string;
24
24
  readonly diagnostic: string;
25
25
  };
26
+ /** Applies validated workspace mutations and advances the revision only when observable state changes. */
26
27
  export declare class WorkspaceReducer {
27
28
  #private;
28
29
  readonly workspaceId: string;
@@ -1,5 +1,6 @@
1
1
  import { assertManagedAgentDescriptor, } from "../../contracts/workspace/index.js";
2
2
  const REMOVABLE_STATES = new Set(["stopped", "failed", "discontinuous"]);
3
+ /** Applies validated workspace mutations and advances the revision only when observable state changes. */
3
4
  export class WorkspaceReducer {
4
5
  workspaceId;
5
6
  #agents = new Map();
@@ -16,6 +16,7 @@ export type WorkspaceRouterResult<T = undefined> = {
16
16
  readonly code: string;
17
17
  readonly diagnostic: string;
18
18
  };
19
+ /** Serializes workspace actions and capability-gates structured commands at the selected-agent boundary. */
19
20
  export declare class WorkspaceRouter {
20
21
  #private;
21
22
  readonly reducer: WorkspaceReducer;
@@ -1,6 +1,7 @@
1
1
  import { StructuredCommandTracker, } from "../../foundation/structured-agent-runtime/index.js";
2
2
  import { gateWorkspaceAction } from "./capabilities.js";
3
3
  import { WorkspaceReducer } from "./reducer.js";
4
+ /** Serializes workspace actions and capability-gates structured commands at the selected-agent boundary. */
4
5
  export class WorkspaceRouter {
5
6
  reducer;
6
7
  #trackers = new Map();
@@ -1,5 +1,6 @@
1
1
  import type { AgentRecoveryAuthority, RecoveryReferenceId } from "../../contracts/workspace/index.js";
2
2
  import { WorkspaceReducer, type WorkspaceView } from "./reducer.js";
3
+ /** Persists complete workspace views and recovery references through the control-store boundary. */
3
4
  export declare class WorkspaceStore {
4
5
  #private;
5
6
  constructor(path: string);
@@ -1,5 +1,6 @@
1
1
  import { ControlStore, DEFAULT_WORKSPACE_ID, } from "../../foundation/storage/index.js";
2
2
  import { WorkspaceReducer } from "./reducer.js";
3
+ /** Persists complete workspace views and recovery references through the control-store boundary. */
3
4
  export class WorkspaceStore {
4
5
  #store;
5
6
  constructor(path) {
@@ -17,7 +17,7 @@ export async function runLaunchGuardian(options) {
17
17
  if (!guardianIdentity)
18
18
  throw diagnosticError("launch guardian cannot verify its own native process identity", "PROCESS_IDENTITY_UNAVAILABLE");
19
19
  const client = options.control ?? new SupervisorClient(environment[PRODUCT_IDENTITY.environment.releaseId]);
20
- // An instance belongs to the cohort that launched it and talks to that cohort's endpoint
20
+ // Invariant: an instance belongs to the cohort that launched it and talks to that cohort's endpoint
21
21
  // for its whole life, whatever release becomes the active one meanwhile.
22
22
  const releaseId = environment[PRODUCT_IDENTITY.environment.releaseId];
23
23
  const endpoint = releaseId
@@ -1,8 +1,10 @@
1
1
  import { type NativeHostProofMessage } from "./messages.js";
2
+ /** Identifies a bounded native-host framing or message-contract failure. */
2
3
  export declare class NativeHostProtocolError extends Error {
3
4
  readonly code: "frame-too-large" | "malformed-frame" | "invalid-message";
4
5
  constructor(code: "frame-too-large" | "malformed-frame" | "invalid-message", message: string);
5
6
  }
7
+ /** Encodes and incrementally decodes size-bounded newline-delimited native-host proof messages. */
6
8
  export declare class NativeHostFrameCodec {
7
9
  #private;
8
10
  encode(message: NativeHostProofMessage): string;
@@ -1,4 +1,5 @@
1
1
  import { MAX_NATIVE_HOST_MESSAGE_BYTES, assertNativeHostProofMessage, } from "./messages.js";
2
+ /** Identifies a bounded native-host framing or message-contract failure. */
2
3
  export class NativeHostProtocolError extends Error {
3
4
  code;
4
5
  constructor(code, message) {
@@ -7,6 +8,7 @@ export class NativeHostProtocolError extends Error {
7
8
  this.name = "NativeHostProtocolError";
8
9
  }
9
10
  }
11
+ /** Encodes and incrementally decodes size-bounded newline-delimited native-host proof messages. */
10
12
  export class NativeHostFrameCodec {
11
13
  #buffer = "";
12
14
  encode(message) {
@@ -1,5 +1,6 @@
1
1
  import type { NativeProcessIdentity } from "../lifecycle/index.js";
2
2
  import type { NativeProcessInspector } from "./contracts.js";
3
+ /** Verifies Linux process identity from the PID and kernel start-time field in procfs. */
3
4
  export declare class LinuxNativeProcessInspector implements NativeProcessInspector {
4
5
  observe(pid: number): Promise<NativeProcessIdentity | null>;
5
6
  matches(identity: NativeProcessIdentity): Promise<boolean>;
@@ -1,4 +1,5 @@
1
1
  import { readFile } from "node:fs/promises";
2
+ /** Verifies Linux process identity from the PID and kernel start-time field in procfs. */
2
3
  export class LinuxNativeProcessInspector {
3
4
  async observe(pid) {
4
5
  if (!Number.isSafeInteger(pid) || pid <= 0)
@@ -1,5 +1,6 @@
1
1
  import type { NativeProcessIdentity, ProcessContainmentIdentity } from "../lifecycle/index.js";
2
2
  import type { ContainedProcessHandle, ContainedSpawnOptions, ProcessContainment } from "./contracts.js";
3
+ /** Delegates one root runtime to the native guardian and exposes its verified containment identity. */
3
4
  export declare class NativeGuardianContainment implements ProcessContainment {
4
5
  #private;
5
6
  readonly instanceId: string;
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
2
2
  import { mkdir, readFile, rm } from "node:fs/promises";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import { PRODUCT_IDENTITY } from "../../product-identity.js";
5
+ /** Delegates one root runtime to the native guardian and exposes its verified containment identity. */
5
6
  export class NativeGuardianContainment {
6
7
  instanceId;
7
8
  helperPath;
@@ -8,6 +8,7 @@ export interface InspectorCommandResult {
8
8
  export interface InspectorCommandRunner {
9
9
  run(executable: string, arguments_: readonly string[]): Promise<InspectorCommandResult>;
10
10
  }
11
+ /** Verifies Windows process identity through the bounded process-guardian inspection command. */
11
12
  export declare class WindowsNativeProcessInspector implements NativeProcessInspector {
12
13
  readonly helperPath: string;
13
14
  private readonly runner;
@@ -1,4 +1,5 @@
1
1
  import { execFile } from "node:child_process";
2
+ /** Verifies Windows process identity through the bounded process-guardian inspection command. */
2
3
  export class WindowsNativeProcessInspector {
3
4
  helperPath;
4
5
  runner;
@@ -5,6 +5,7 @@ interface ClientEvents {
5
5
  stopIntent: [LaunchInstanceStopIntent];
6
6
  disconnect: [];
7
7
  }
8
+ /** Maintains one negotiated supervisor connection and rejects all pending commands on disconnect. */
8
9
  export declare class SupervisorClient extends EventEmitter<ClientEvents> {
9
10
  #private;
10
11
  readonly clientId: `${string}-${string}-${string}-${string}-${string}`;
@@ -2,6 +2,7 @@ import { EventEmitter } from "node:events";
2
2
  import { connect } from "node:net";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { CONTROL_ENVELOPE, CONTROL_ENVELOPE_REVISION, encodeFrame, LineFrameDecoder, localControlHello, negotiateControlFeatures } from "./messages.js";
5
+ /** Maintains one negotiated supervisor connection and rejects all pending commands on disconnect. */
5
6
  export class SupervisorClient extends EventEmitter {
6
7
  clientId = randomUUID();
7
8
  releaseId;
@@ -76,6 +76,7 @@ export interface ControlNegotiation {
76
76
  export declare function localControlHello(releaseId?: string): ControlHello;
77
77
  export declare function negotiateControlFeatures(client: ControlHello, server?: ControlHello): ControlNegotiation;
78
78
  export declare function encodeFrame(message: ClientMessage | ServerMessage | Readonly<Record<string, unknown>>): string;
79
+ /** Incrementally decodes size-bounded newline-delimited control frames from arbitrary chunks. */
79
80
  export declare class LineFrameDecoder {
80
81
  #private;
81
82
  push(chunk: Buffer | string): unknown[];
@@ -58,6 +58,7 @@ export function negotiateControlFeatures(client, server = localControlHello()) {
58
58
  export function encodeFrame(message) {
59
59
  return `${JSON.stringify(message)}\n`;
60
60
  }
61
+ /** Incrementally decodes size-bounded newline-delimited control frames from arbitrary chunks. */
61
62
  export class LineFrameDecoder {
62
63
  #buffer = "";
63
64
  push(chunk) {
@@ -21,11 +21,11 @@ export async function runBootstrap(options) {
21
21
  await mkdir(paths.runtimeDir, { recursive: true, mode: 0o700 });
22
22
  const stateStore = new CohortStateStore(paths.dataDir);
23
23
  let state = await stateStore.read();
24
- // Records left by cohorts whose processes are gone say nothing about ownership, and there
24
+ // Invariant: records left by cohorts whose processes are gone say nothing about ownership, and there
25
25
  // can now be several of them. Clearing them first keeps the decision below about what is
26
26
  // actually running.
27
27
  await sweepDeadEndpoints(paths).catch(() => []);
28
- // Each cohort keeps its own endpoint, so a launch looks for the endpoint of the release it
28
+ // Protocol: each cohort keeps its own endpoint, so a launch looks for the endpoint of the release it
29
29
  // is about to run rather than for the one endpoint the runtime directory used to have.
30
30
  const activeReleaseId = state.references.active;
31
31
  let endpointPaths = activeReleaseId === null
@@ -33,7 +33,7 @@ export async function runBootstrap(options) {
33
33
  : resolveCohortEndpoint(paths, activeReleaseId, environment);
34
34
  let endpoint = await readEndpointMetadata(endpointPaths.endpointMetadataPath);
35
35
  if (endpoint === null) {
36
- // A release that predates cohort-scoped endpoints published one endpoint for the whole
36
+ // Compatibility: a release that predates cohort-scoped endpoints published one endpoint for the whole
37
37
  // runtime directory. Recognizing it is what lets a session started by that release keep
38
38
  // working through the first launch that knows about cohorts.
39
39
  const legacy = await readEndpointMetadata(paths.endpointMetadataPath);
@@ -76,7 +76,7 @@ export async function runBootstrap(options) {
76
76
  }
77
77
  else if (!activeIsLaunchable && candidate.releaseId !== activeId
78
78
  && state.releases[candidate.releaseId]?.approval !== "approved") {
79
- // The active reference points at a copy that cannot launch, so reusing it
79
+ // Invariant: the active reference points at a copy that cannot launch, so reusing it
80
80
  // is off the table — but an unapproved candidate would lose the selection
81
81
  // below to that same broken active (`start-active`). Approving the healed
82
82
  // candidate here lets ordinary cohort selection activate it, while a live
@@ -85,7 +85,7 @@ export async function runBootstrap(options) {
85
85
  await stateStore.approve(candidate.releaseId, diagnosticsPath);
86
86
  state = await stateStore.read();
87
87
  }
88
- // The candidate's own endpoint decides whether this launch attaches or starts a supervisor.
88
+ // Protocol: the candidate's own endpoint decides whether this launch attaches or starts a supervisor.
89
89
  // A cohort other than this one is not in the way: it listens somewhere else.
90
90
  endpointPaths = resolveCohortEndpoint(paths, candidate.releaseId, environment);
91
91
  endpoint = await readEndpointMetadata(endpointPaths.endpointMetadataPath);
@@ -326,7 +326,7 @@ export async function releaseVerifiedIdleOwner(metadata, dataDir, operations = {
326
326
  return true;
327
327
  }
328
328
  catch {
329
- // The authenticated idle-release handshake authorizes bounded cleanup of
329
+ // Security: the authenticated idle-release handshake authorizes bounded cleanup of
330
330
  // this exact boot when a native handle outlives graceful shutdown.
331
331
  const diagnostics = await (operations.cleanup ?? cleanupProvenIdleOwner)(metadata);
332
332
  await writeFile(resolve(dataDir, `cleanup-${Date.now()}.json`), JSON.stringify(diagnostics, null, 2));
@@ -54,6 +54,7 @@ export interface SupervisorEndpointMetadata {
54
54
  readonly optionalFeatures: readonly string[];
55
55
  readonly contractDigest: string;
56
56
  }
57
+ /** Serializes validated cohort-state revisions under a lock and commits each update atomically. */
57
58
  export declare class CohortStateStore {
58
59
  readonly path: string;
59
60
  constructor(dataDir: string);
@@ -3,6 +3,7 @@ import { mkdir, open, readFile, rename, rm } from "node:fs/promises";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import { PRODUCT_IDENTITY } from "../../product-identity.js";
5
5
  export const RELEASE_COHORT_SCHEMA = PRODUCT_IDENTITY.protocol.releaseCohortSchema;
6
+ /** Serializes validated cohort-state revisions under a lock and commits each update atomically. */
6
7
  export class CohortStateStore {
7
8
  path;
8
9
  constructor(dataDir) {
@@ -13,7 +13,7 @@ paths) {
13
13
  const release = state.releases[releaseId];
14
14
  if (!release)
15
15
  throw new Error(`unknown release ${releaseId}`);
16
- // A superseded cohort keeps working until its last session leaves, and it runs from this
16
+ // Invariant: a superseded cohort keeps working until its last session leaves, and it runs from this
17
17
  // content while it does. Collecting it would pull the release out from under live work.
18
18
  if (paths && (await liveReleaseIds(paths)).includes(releaseId)) {
19
19
  throw new Error(`release ${releaseId} is still running a live cohort`);
@@ -143,7 +143,7 @@ async function findInstalledDependency(root, requesterRoot, name) {
143
143
  const metadata = await lstat(candidate).catch(() => null);
144
144
  if (metadata?.isDirectory() && !metadata.isSymbolicLink())
145
145
  return candidate;
146
- // A linked dependency directory is how this installation unifies a module
146
+ // Invariant: a linked dependency directory is how this installation unifies a module
147
147
  // that would otherwise be present twice, and the link is made by A1 itself
148
148
  // at install and at every launch. Follow it to the directory it names and
149
149
  // collect from there, so the payload still reads only real files — but
@@ -14,6 +14,7 @@ export interface UpdateTransaction {
14
14
  readonly startedAt: string;
15
15
  readonly updatedAt: string;
16
16
  }
17
+ /** Persists the durable update journal and permits only validated forward transaction phases. */
17
18
  export declare class UpdateTransactionStore {
18
19
  #private;
19
20
  readonly path: string;
@@ -3,6 +3,7 @@ import { mkdir, open, readFile, rename, rm } from "node:fs/promises";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
5
5
  export const UPDATE_JOURNAL_SCHEMA = PRODUCT_IDENTITY.protocol.updateJournalSchema;
6
+ /** Persists the durable update journal and permits only validated forward transaction phases. */
6
7
  export class UpdateTransactionStore {
7
8
  path;
8
9
  constructor(dataDir) { this.path = resolve(dataDir, "update-transaction.json"); }