@timurproko/a1 0.1.8-dev.151 → 0.1.8-dev.182

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 (70) hide show
  1. package/README.md +53 -9
  2. package/bin/cli.js +1 -0
  3. package/bin/ui.js +17 -2
  4. package/dist/cli/dispatch.d.ts +6 -0
  5. package/dist/cli/dispatch.js +107 -81
  6. package/dist/cli/version-stats.js +7 -1
  7. package/dist/composition/owned-ui.d.ts +4 -1
  8. package/dist/composition/owned-ui.js +8 -5
  9. package/dist/contracts/agent-engine/capability-ports.d.ts +2 -2
  10. package/dist/contracts/agent-engine/domain-validation.js +22 -4
  11. package/dist/contracts/agent-engine/domain.d.ts +17 -0
  12. package/dist/contracts/owned-ui/model.d.ts +8 -0
  13. package/dist/contracts/owned-ui/validation.js +11 -0
  14. package/dist/features/owned-ui/index.d.ts +1 -0
  15. package/dist/features/owned-ui/index.js +1 -0
  16. package/dist/features/owned-ui/project-trust-prompt.d.ts +20 -0
  17. package/dist/features/owned-ui/project-trust-prompt.js +32 -0
  18. package/dist/features/owned-ui/settings-app.js +37 -9
  19. package/dist/integrations/pi/components/shell-components.d.ts +1 -0
  20. package/dist/integrations/pi/components/shell-components.js +1 -0
  21. package/dist/integrations/pi/components/shell-editor-autocomplete.js +10 -0
  22. package/dist/integrations/pi/components/shell-footer-status.js +14 -9
  23. package/dist/integrations/pi/components/shell-presenters-info.d.ts +36 -0
  24. package/dist/integrations/pi/components/shell-presenters-info.js +78 -0
  25. package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -36
  26. package/dist/integrations/pi/components/shell-presenters-transcript.js +88 -128
  27. package/dist/integrations/pi/components/shell-shared-facade.d.ts +11 -1
  28. package/dist/integrations/pi/engine/adapter.d.ts +15 -4
  29. package/dist/integrations/pi/engine/adapter.js +185 -37
  30. package/dist/integrations/pi/engine/http-dispatcher.d.ts +4 -0
  31. package/dist/integrations/pi/engine/http-dispatcher.js +25 -0
  32. package/dist/integrations/pi/engine/index.d.ts +3 -0
  33. package/dist/integrations/pi/engine/index.js +3 -0
  34. package/dist/integrations/pi/engine/project-trust-preflight.d.ts +22 -0
  35. package/dist/integrations/pi/engine/project-trust-preflight.js +50 -0
  36. package/dist/integrations/pi/engine/runtime-integration.d.ts +16 -1
  37. package/dist/integrations/pi/engine/runtime-integration.js +34 -4
  38. package/dist/integrations/pi/engine/settings-effects.d.ts +55 -0
  39. package/dist/integrations/pi/engine/settings-effects.js +229 -0
  40. package/dist/integrations/pi/engine/settings-integration.d.ts +14 -26
  41. package/dist/integrations/pi/engine/settings-integration.js +102 -108
  42. package/dist/integrations/pi/engine/workflow-controllers.d.ts +1 -1
  43. package/dist/integrations/pi/session-ui/clipboard-image.d.ts +11 -0
  44. package/dist/integrations/pi/session-ui/clipboard-image.js +29 -0
  45. package/dist/integrations/pi/session-ui/prompt-chips.js +5 -1
  46. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +9 -2
  47. package/dist/integrations/pi/session-ui/session-shell-root.js +83 -9
  48. package/dist/integrations/pi/session-ui/session-shell.d.ts +2 -0
  49. package/dist/integrations/pi/session-ui/session-shell.js +151 -15
  50. package/dist/integrations/pi/session-ui/session-viewport-controller.js +34 -2
  51. package/dist/integrations/pi/session-ui/system-clipboard.d.ts +9 -0
  52. package/dist/integrations/pi/session-ui/system-clipboard.js +34 -5
  53. package/dist/integrations/pi/tui-runtime/adapter.d.ts +4 -0
  54. package/dist/integrations/pi/tui-runtime/adapter.js +28 -0
  55. package/dist/native/darwin-arm64/manifest.json +1 -1
  56. package/dist/native/linux-x64/manifest.json +1 -1
  57. package/dist/native/win32-x64/manifest.json +2 -2
  58. package/dist/native/win32-x64/process-guardian.exe +0 -0
  59. package/dist/ui/components/spans.d.ts +2 -0
  60. package/dist/ui/components/spans.js +25 -0
  61. package/dist/ui/components/text.js +24 -2
  62. package/dist/ui/settings/sections.d.ts +8 -19
  63. package/dist/ui/settings/sections.js +12 -4
  64. package/dist/ui/settings/session.d.ts +8 -18
  65. package/dist/ui/settings/session.js +59 -52
  66. package/docs/architecture/project-structure.md +14 -0
  67. package/docs/ci-release-runbook.md +75 -9
  68. package/docs/features/launch-profiles.md +3 -3
  69. package/docs/repository-governance-live-acceptance.md +77 -0
  70. package/package.json +5 -2
@@ -1,4 +1,4 @@
1
- import { TranscriptViewport, routeMouseInput, scrollForTrackPage, scrollForThumbRow, scrollbarSelectionRows, scrollbarWheelRows, } from "../../../ui/components/index.js";
1
+ import { TranscriptViewport, hyperlinkTargetAtColumn, routeMouseInput, scrollForTrackPage, scrollForThumbRow, scrollbarSelectionRows, scrollbarWheelRows, } from "../../../ui/components/index.js";
2
2
  /**
3
3
  * Owns the stateful interaction policy of the bare-A1 session viewport. The
4
4
  * shell root supplies semantic rows and dock geometry; this controller owns
@@ -24,6 +24,8 @@ export class SessionViewportController {
24
24
  #editorPointerFrame;
25
25
  #selectionAutoScrollTimer;
26
26
  #selectionAutoScrollPointer;
27
+ #pointerPosition;
28
+ #hoveredHyperlinkKey;
27
29
  #activityTimer;
28
30
  constructor(options) {
29
31
  this.#enabled = options.enabled;
@@ -45,7 +47,17 @@ export class SessionViewportController {
45
47
  }
46
48
  compose(input) {
47
49
  this.#viewport.setConfig(this.#config);
48
- return this.#viewport.compose(input);
50
+ const frame = this.#viewport.compose(input);
51
+ if (this.#pointerPosition !== undefined && !this.#viewport.selectionActive && !this.#editorPointerSelecting) {
52
+ const next = this.#hyperlinkKeyAt(frame, this.#pointerPosition.column, this.#pointerPosition.row);
53
+ if (this.#hoveredHyperlinkKey !== undefined && next !== this.#hoveredHyperlinkKey) {
54
+ // Windows Terminal can leave its solid native-hover underline behind
55
+ // when a following viewport moves the link away from a stationary pointer.
56
+ this.#requestRender(true);
57
+ }
58
+ this.#hoveredHyperlinkKey = next;
59
+ }
60
+ return frame;
49
61
  }
50
62
  /** Returns true when document wrapping may have changed. */
51
63
  setConfig(config) {
@@ -76,6 +88,8 @@ export class SessionViewportController {
76
88
  this.#stopSelectionAutoScroll();
77
89
  this.#editorPointerSelecting = false;
78
90
  this.#editorPointerFrame = undefined;
91
+ this.#pointerPosition = undefined;
92
+ this.#hoveredHyperlinkKey = undefined;
79
93
  this.#viewport.reset();
80
94
  }
81
95
  clearPointerState() {
@@ -84,6 +98,8 @@ export class SessionViewportController {
84
98
  this.#dockPointerSuppressed = false;
85
99
  this.#editorPointerSelecting = false;
86
100
  this.#editorPointerFrame = undefined;
101
+ this.#pointerPosition = undefined;
102
+ this.#hoveredHyperlinkKey = undefined;
87
103
  this.#stopSelectionAutoScroll();
88
104
  this.#viewport.clearSelection();
89
105
  this.#viewport.clearTransient();
@@ -160,6 +176,16 @@ export class SessionViewportController {
160
176
  const overBottom = hits.bottom !== null && event.row === hits.bottom.row
161
177
  && event.column >= hits.bottom.columnStart && event.column <= hits.bottom.columnEnd;
162
178
  if (event.kind === "motion") {
179
+ this.#pointerPosition = { column: event.column, row: event.row };
180
+ if (!this.#viewport.selectionActive && !this.#editorPointerSelecting) {
181
+ const nextHyperlink = this.#hyperlinkKeyAt(frame, event.column, event.row);
182
+ if (this.#hoveredHyperlinkKey !== undefined && nextHyperlink !== this.#hoveredHyperlinkKey) {
183
+ // A forced redraw overwrites Windows Terminal's cached solid hover
184
+ // underline exactly once when the pointer leaves or changes links.
185
+ forceRepaint = true;
186
+ }
187
+ this.#hoveredHyperlinkKey = nextHyperlink;
188
+ }
163
189
  this.#viewport.setRailHovered(overRail);
164
190
  this.#viewport.setStickyHovered(overSticky);
165
191
  this.#viewport.setBottomHovered(overBottom);
@@ -307,6 +333,12 @@ export class SessionViewportController {
307
333
  this.#requestRender(forceRepaint);
308
334
  return routed;
309
335
  }
336
+ #hyperlinkKeyAt(frame, column, row) {
337
+ if (row < 1 || row > frame.rows.length || column < 1)
338
+ return undefined;
339
+ const target = hyperlinkTargetAtColumn(frame.rows[row - 1] ?? "", column - 1);
340
+ return target === undefined ? undefined : `${row}\u0000${target}`;
341
+ }
310
342
  #updateSelectionAutoScroll(column, row, viewportHeight) {
311
343
  const beyondEdge = row <= 1 || row > viewportHeight;
312
344
  if (!beyondEdge) {
@@ -1,4 +1,9 @@
1
1
  import type { PiShellClipboardContent } from "../components/index.js";
2
+ export interface SystemClipboardImageReader {
3
+ hasImage(): boolean;
4
+ getImageBinary?(): Promise<readonly number[]>;
5
+ getImageBase64?(): Promise<string>;
6
+ }
2
7
  /** Load the native adapter during shell startup so the first paste is warm. */
3
8
  export declare function preloadSystemClipboard(): void;
4
9
  /**
@@ -7,5 +12,9 @@ export declare function preloadSystemClipboard(): void;
7
12
  * unavailable clipboard simply makes the paste action a no-op.
8
13
  */
9
14
  export declare function readSystemClipboardContent(): Promise<PiShellClipboardContent | null>;
15
+ export declare function readSystemClipboardImage(reader: SystemClipboardImageReader): Promise<{
16
+ readonly data: string;
17
+ readonly mimeType: string;
18
+ } | null>;
10
19
  export declare function readSystemClipboardText(): Promise<string | null>;
11
20
  export declare function writeSystemClipboardText(text: string): Promise<void>;
@@ -1,4 +1,5 @@
1
1
  import { execFile } from "node:child_process";
2
+ import { canonicalizeClipboardImage } from "./clipboard-image.js";
2
3
  const MAX_CLIPBOARD_BYTES = 16 * 1024 * 1024;
3
4
  let nativeClipboardPromise;
4
5
  let pendingClipboardWrite = Promise.resolve();
@@ -22,11 +23,9 @@ export async function readSystemClipboardContent() {
22
23
  if (files !== null)
23
24
  return { kind: "text", text: files };
24
25
  }
25
- if (native.hasImage()) {
26
- const data = await native.getImageBase64();
27
- if (data.length > 0)
28
- return { kind: "image", data, mimeType: "image/png" };
29
- }
26
+ const image = await readSystemClipboardImage(native);
27
+ if (image !== null)
28
+ return { kind: "image", ...image };
30
29
  }
31
30
  catch {
32
31
  // Fall through to text and platform readers.
@@ -35,6 +34,36 @@ export async function readSystemClipboardContent() {
35
34
  const text = await readSystemClipboardText();
36
35
  return text === null ? null : { kind: "text", text };
37
36
  }
37
+ export async function readSystemClipboardImage(reader) {
38
+ if (!reader.hasImage())
39
+ return null;
40
+ if (reader.getImageBinary !== undefined) {
41
+ try {
42
+ const bytes = await reader.getImageBinary();
43
+ if (bytes.length > 0 && bytes.every(byte => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
44
+ return canonicalizeClipboardImage({
45
+ data: Buffer.from(bytes).toString("base64"),
46
+ mimeType: "image/png",
47
+ });
48
+ }
49
+ }
50
+ catch {
51
+ // Older or partially available native bindings may still expose base64.
52
+ }
53
+ }
54
+ if (reader.getImageBase64 !== undefined) {
55
+ try {
56
+ return canonicalizeClipboardImage({
57
+ data: await reader.getImageBase64(),
58
+ mimeType: "image/png",
59
+ });
60
+ }
61
+ catch {
62
+ // Fall through to the caller's text path.
63
+ }
64
+ }
65
+ return null;
66
+ }
38
67
  export async function readSystemClipboardText() {
39
68
  // Ctrl+C/Ctrl+X and Ctrl+V can arrive in adjacent input turns. Serialize the
40
69
  // read behind our native write so paste never observes the previous value.
@@ -31,7 +31,11 @@ export declare class PiTuiRuntimeAdapter {
31
31
  switchMode(mode: "regular" | "fullscreen"): boolean;
32
32
  setHardwareCursor(enabled: boolean): void;
33
33
  getHardwareCursor(): boolean;
34
+ setClearOnShrink(enabled: boolean): void;
35
+ getClearOnShrink(): boolean;
36
+ setTerminalProgress(active: boolean): void;
34
37
  setTitle(title: string): void;
38
+ writeAfterStop(text: string): void;
35
39
  invalidate(): void;
36
40
  requestRender(force?: boolean): void;
37
41
  renderNow(force?: boolean): void;
@@ -97,6 +97,7 @@ export class PiTuiRuntimeAdapter {
97
97
  #state = "idle";
98
98
  #stopPromise;
99
99
  #rootDisposed = false;
100
+ #terminalProgress = false;
100
101
  constructor(options) {
101
102
  this.#root = options.root;
102
103
  this.#terminal = options.terminal ?? new ProcessTerminal();
@@ -301,9 +302,26 @@ export class PiTuiRuntimeAdapter {
301
302
  getHardwareCursor() {
302
303
  return this.#tui.getShowHardwareCursor();
303
304
  }
305
+ setClearOnShrink(enabled) {
306
+ this.#tui.setClearOnShrink(enabled);
307
+ }
308
+ getClearOnShrink() {
309
+ return this.#tui.getClearOnShrink();
310
+ }
311
+ setTerminalProgress(active) {
312
+ if (this.#terminalProgress === active)
313
+ return;
314
+ this.#terminal.setProgress(active);
315
+ this.#terminalProgress = active;
316
+ }
304
317
  setTitle(title) {
305
318
  this.#terminal.setTitle(title);
306
319
  }
320
+ writeAfterStop(text) {
321
+ if (this.#state !== "stopped")
322
+ throw new Error("terminal output requires a stopped Pi TUI runtime");
323
+ this.#terminal.write(text);
324
+ }
307
325
  invalidate() {
308
326
  this.#tui.invalidate();
309
327
  if (this.active)
@@ -322,6 +340,7 @@ export class PiTuiRuntimeAdapter {
322
340
  return this.#stopPromise;
323
341
  if (this.#state === "idle") {
324
342
  this.#state = "stopped";
343
+ this.#clearTerminalProgress();
325
344
  this.#preInputListeners.clear();
326
345
  this.#disposeRoot();
327
346
  return Promise.resolve();
@@ -354,6 +373,7 @@ export class PiTuiRuntimeAdapter {
354
373
  this.#bestEffortTerminalRestore();
355
374
  }
356
375
  finally {
376
+ this.#clearTerminalProgress();
357
377
  this.#disposeComponents();
358
378
  }
359
379
  this.#state = failure === undefined ? "stopped" : "failed";
@@ -459,7 +479,15 @@ export class PiTuiRuntimeAdapter {
459
479
  this.#bestEffortTerminalRestore();
460
480
  }
461
481
  }
482
+ #clearTerminalProgress() {
483
+ try {
484
+ this.#terminal.setProgress(false);
485
+ }
486
+ catch { }
487
+ this.#terminalProgress = false;
488
+ }
462
489
  #bestEffortTerminalRestore() {
490
+ this.#clearTerminalProgress();
463
491
  try {
464
492
  this.#terminal.showCursor();
465
493
  }
@@ -5,7 +5,7 @@
5
5
  "platform": "darwin",
6
6
  "architecture": "arm64",
7
7
  "capability": "unsupported",
8
- "builtAt": "2026-08-28T10:16:08.776Z",
8
+ "builtAt": "2026-08-28T18:22:44.284Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "7524bf568992517f50ed53196c861c5edeba0d7275633bb4735ab45bd5963a28",
@@ -5,7 +5,7 @@
5
5
  "platform": "linux",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-08-28T10:16:08.593Z",
8
+ "builtAt": "2026-08-28T18:22:55.678Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "ee8a00eaaf79c707459bbbfb52518e9739314967049fbe5fa625f36ce33db9ee",
@@ -5,10 +5,10 @@
5
5
  "platform": "win32",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-08-28T10:16:41.229Z",
8
+ "builtAt": "2026-08-28T18:23:19.899Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "8daa77c737ea8f96eb022fce63a25a4fce3e5d15c409201712a7f36853c8e8e4",
11
+ "sha256": "20d36267095f117000abb65e7e2edf9c2a6bd300ee2bd6368b544d823017e5c9",
12
12
  "size": 177664
13
13
  },
14
14
  "provenance": {
@@ -12,6 +12,8 @@ export declare function overlaySpan(line: string, from: number, to: number, span
12
12
  */
13
13
  /** Wraps visible columns `[from,to)` in an explicit OSC 8 hyperlink. */
14
14
  export declare function hyperlinkSgrSpan(line: string, from: number, to: number, target: string, widthOf?: (text: string) => number): string;
15
+ /** Returns the OSC 8 target painted at a zero-based visible terminal column. */
16
+ export declare function hyperlinkTargetAtColumn(line: string, targetColumn: number, widthOf?: (text: string) => number): string | undefined;
15
17
  /**
16
18
  * Gives existing and bare web links explicit, tightly bounded OSC 8 regions so
17
19
  * the terminal owns dotted-idle/solid-hover decoration without leaking hover
@@ -93,6 +93,31 @@ export function hyperlinkSgrSpan(line, from, to, target, widthOf = displayWidth)
93
93
  output += off;
94
94
  return output;
95
95
  }
96
+ /** Returns the OSC 8 target painted at a zero-based visible terminal column. */
97
+ export function hyperlinkTargetAtColumn(line, targetColumn, widthOf = displayWidth) {
98
+ if (targetColumn < 0)
99
+ return undefined;
100
+ let hyperlinkTarget;
101
+ let column = 0;
102
+ for (const token of line.split(ANSI_SPLIT)) {
103
+ if (!token)
104
+ continue;
105
+ const link = HYPERLINK.exec(token);
106
+ if (link) {
107
+ hyperlinkTarget = (link[1] ?? "") || undefined;
108
+ continue;
109
+ }
110
+ if (token.startsWith("\u001b"))
111
+ continue;
112
+ for (const { segment } of GRAPHEMES.segment(token)) {
113
+ const width = widthOf(segment);
114
+ if (targetColumn >= column && targetColumn < column + width)
115
+ return hyperlinkTarget;
116
+ column += width;
117
+ }
118
+ }
119
+ return undefined;
120
+ }
96
121
  /**
97
122
  * Gives existing and bare web links explicit, tightly bounded OSC 8 regions so
98
123
  * the terminal owns dotted-idle/solid-hover decoration without leaking hover
@@ -7,6 +7,15 @@
7
7
  const ANSI_PATTERN = /\[[0-9;:?]*[ -/]*[@-~]|\][^]*(?:|\\)|[@-Z\\-_]/g;
8
8
  const SEGMENTER = new Intl.Segmenter(undefined, { granularity: "grapheme" });
9
9
  const WORD_SEGMENTER = new Intl.Segmenter(undefined, { granularity: "word" });
10
+ /**
11
+ * Recommended-for-general-interchange emoji. Terminals give every one of these
12
+ * two columns wherever its code points sit, so the wide blocks in
13
+ * `codePointWidth` cannot carry the measurement alone: a lone U+26A1 or any
14
+ * U+FE0F presentation sequence escapes them and would measure one column short.
15
+ */
16
+ const RGI_EMOJI_PATTERN = /^\p{RGI_Emoji}$/v;
17
+ /** Columns a terminal advances for a tab, matching the host runtime. */
18
+ const TAB_WIDTH = 3;
10
19
  /** Removes styling sequences so only visible characters remain. */
11
20
  export function stripAnsi(text) {
12
21
  return text.replace(ANSI_PATTERN, "");
@@ -50,13 +59,26 @@ function codePointWidth(codePoint) {
50
59
  return 2;
51
60
  if (codePoint >= 0x20000 && codePoint <= 0x3fffd)
52
61
  return 2;
53
- // Control characters occupy nothing.
62
+ // A tab advances to the host runtime's stop; other control characters occupy nothing.
63
+ if (codePoint === 0x09)
64
+ return TAB_WIDTH;
54
65
  if (codePoint < 0x20 || (codePoint >= 0x7f && codePoint < 0xa0))
55
66
  return 0;
56
67
  return 1;
57
68
  }
58
- /** Width of one grapheme cluster: the widest code point it contains, marks excluded. */
69
+ /** Cheap pre-filter: only clusters that could be emoji pay for the RGI test. */
70
+ function couldBeEmoji(grapheme) {
71
+ const codePoint = grapheme.codePointAt(0) ?? 0;
72
+ return ((codePoint >= 0x1f000 && codePoint <= 0x1fbff) ||
73
+ (codePoint >= 0x2300 && codePoint <= 0x23ff) ||
74
+ (codePoint >= 0x2600 && codePoint <= 0x27bf) ||
75
+ (codePoint >= 0x2b00 && codePoint <= 0x2bff) ||
76
+ grapheme.length > 1);
77
+ }
78
+ /** Width of one grapheme cluster: two for emoji, else the widest code point it contains. */
59
79
  function graphemeWidth(grapheme) {
80
+ if (couldBeEmoji(grapheme) && RGI_EMOJI_PATTERN.test(grapheme))
81
+ return 2;
60
82
  let width = 0;
61
83
  for (const character of grapheme) {
62
84
  width = Math.max(width, codePointWidth(character.codePointAt(0) ?? 0));
@@ -1,3 +1,4 @@
1
+ import type { AgentSettingApplicationBoundary, AgentSettingDescriptor } from "../../contracts/agent-engine/index.js";
1
2
  import type { OwnedUiSettingValue } from "./declarations.js";
2
3
  import type { OwnedUiSettingsResolution } from "./resolution.js";
3
4
  export type OwnedUiSettingsBackend = "a1" | "agent";
@@ -9,9 +10,13 @@ export interface OwnedUiSettingsEntry {
9
10
  readonly description: string | null;
10
11
  /** Present when the value is a scalar the surface can offer as a choice. */
11
12
  readonly value: OwnedUiSettingValue | null;
12
- /** Raw value as reported by its backend, for entries the surface can only display. */
13
+ /** Raw stored value as reported by its backend, for entries the surface can only display. */
13
14
  readonly rawValue: unknown;
15
+ readonly storedValue: unknown;
16
+ readonly effectiveValue: unknown;
14
17
  readonly editable: boolean;
18
+ readonly available: boolean;
19
+ readonly limitationReason: string | null;
15
20
  readonly choices: readonly OwnedUiSettingValue[] | null;
16
21
  /** True when the value is a structured object edited through its own surface. */
17
22
  readonly structured: boolean;
@@ -26,7 +31,7 @@ export interface OwnedUiSettingsEntry {
26
31
  readonly fallback: boolean;
27
32
  }[];
28
33
  readonly origin: "default" | "stored" | "engine";
29
- readonly application: "live" | "restart" | "engine";
34
+ readonly application: AgentSettingApplicationBoundary;
30
35
  }
31
36
  export interface OwnedUiSettingsSection {
32
37
  readonly id: string;
@@ -38,23 +43,7 @@ export interface OwnedUiSettingsSection {
38
43
  readonly readOnlyReason: string | null;
39
44
  }
40
45
  export interface AgentSettingsSnapshot {
41
- readonly descriptors: readonly {
42
- readonly key: string;
43
- readonly valueType: "boolean" | "number" | "string" | "enum" | "json";
44
- readonly writable: boolean;
45
- readonly choices?: readonly unknown[];
46
- readonly label?: string;
47
- readonly description?: string;
48
- readonly minimum?: number;
49
- readonly maximum?: number;
50
- readonly flags?: readonly {
51
- readonly key: string;
52
- readonly label: string;
53
- readonly description: string;
54
- readonly fallback: boolean;
55
- }[];
56
- }[];
57
- readonly values: Readonly<Record<string, unknown>>;
46
+ readonly descriptors: readonly AgentSettingDescriptor[];
58
47
  /** Whether the engine advertises settings write capability at all. */
59
48
  readonly writeAdvertised: boolean;
60
49
  /** Set when the engine could not report its settings. */
@@ -12,14 +12,18 @@ export function buildOwnedUiSettingsSections(input) {
12
12
  description: setting.declaration.description,
13
13
  value: setting.value,
14
14
  rawValue: setting.value,
15
+ storedValue: setting.value,
16
+ effectiveValue: setting.value,
15
17
  editable: true,
18
+ available: true,
19
+ limitationReason: null,
16
20
  structured: false,
17
21
  minimum: null,
18
22
  maximum: null,
19
23
  flags: [],
20
24
  choices: setting.declaration.allowedValues,
21
25
  origin: setting.source,
22
- application: setting.declaration.application,
26
+ application: setting.declaration.application === "restart" ? "next-start" : "live",
23
27
  });
24
28
  grouped.set(section.id, group);
25
29
  }
@@ -51,7 +55,7 @@ function agentSection(snapshot) {
51
55
  return frozenSection("The agent engine reported no settings", null, []);
52
56
  }
53
57
  const entries = snapshot.descriptors.map(descriptor => {
54
- const raw = Object.hasOwn(snapshot.values, descriptor.key) ? snapshot.values[descriptor.key] : null;
58
+ const raw = descriptor.storedValue;
55
59
  return {
56
60
  id: descriptor.key,
57
61
  label: descriptor.label ?? null,
@@ -59,9 +63,13 @@ function agentSection(snapshot) {
59
63
  description: descriptor.description ?? null,
60
64
  value: scalar(raw),
61
65
  rawValue: raw,
66
+ storedValue: descriptor.storedValue,
67
+ effectiveValue: descriptor.effectiveValue,
68
+ available: descriptor.available,
69
+ limitationReason: descriptor.limitationReason,
62
70
  // A structured value is editable through its own surface rather than a
63
71
  // value menu, so it stays reachable instead of being reported as fixed.
64
- editable: snapshot.writeAdvertised && descriptor.writable,
72
+ editable: snapshot.writeAdvertised && descriptor.writable && descriptor.available,
65
73
  structured: descriptor.valueType === "json",
66
74
  minimum: descriptor.minimum ?? null,
67
75
  maximum: descriptor.maximum ?? null,
@@ -70,7 +78,7 @@ function agentSection(snapshot) {
70
78
  // so it opens the same menu as any other enumerated setting.
71
79
  choices: descriptor.valueType === "boolean" ? BOOLEAN_CHOICES : choicesOf(descriptor.choices),
72
80
  origin: "engine",
73
- application: "engine",
81
+ application: descriptor.application,
74
82
  };
75
83
  });
76
84
  const readOnlyReason = snapshot.writeAdvertised
@@ -1,46 +1,36 @@
1
- import type { AgentSettingsPort } from "../../contracts/agent-engine/index.js";
1
+ import type { AgentJsonValue, AgentSettingApplicationBoundary, AgentSettingsPort } from "../../contracts/agent-engine/index.js";
2
2
  import type { OwnedUiSettingValue } from "./declarations.js";
3
3
  import type { OwnedUiSettingsResolution } from "./resolution.js";
4
4
  import { type OwnedUiSettingsBackend, type OwnedUiSettingsSection } from "./sections.js";
5
5
  import type { OwnedUiSettingsStore } from "./store.js";
6
6
  export interface OwnedUiSettingsChangeOutcome {
7
+ readonly status: "applied" | "deferred" | "unavailable" | "failed";
7
8
  readonly applied: boolean;
8
- /** True when the value is stored but only takes effect on the next start. */
9
+ /** Compatibility projection for existing A1 restart-bound declarations. */
9
10
  readonly pendingRestart: boolean;
10
- /** Present when the change could not be persisted; the surface must not show it as saved. */
11
+ readonly application: AgentSettingApplicationBoundary | null;
12
+ readonly storedValue: AgentJsonValue | null;
13
+ readonly effectiveValue: AgentJsonValue | null;
14
+ readonly limitationReason: string | null;
11
15
  readonly failure: string | null;
12
16
  }
13
17
  export interface OwnedUiSettingsSessionOptions {
14
18
  readonly store: OwnedUiSettingsStore;
15
- /** Null when no engine is attached; the Agent section then reports itself unavailable. */
16
19
  readonly agent?: AgentSettingsPort | null;
17
- /**
18
- * Resolves the port at load time instead of construction time, for an engine
19
- * whose runtime is not up yet. Takes precedence over `agent`.
20
- */
21
20
  readonly agentProvider?: () => AgentSettingsPort | null;
22
- /** Agent-owned controls fixed by this product surface and therefore omitted. */
23
21
  readonly hiddenAgentSettingIds?: readonly string[];
24
22
  }
25
23
  export type OwnedUiSettingsListener = (session: OwnedUiSettingsSession) => void;
26
- /**
27
- * Holds the resolved settings for one owned UI session and routes every accepted
28
- * change to the backend that owns it: A1 settings to the A1 document, agent
29
- * settings to the engine settings port. Neither backend ever sees the other's value.
30
- */
24
+ /** Routes every accepted setting change only to its owning backend. */
31
25
  export declare class OwnedUiSettingsSession {
32
26
  #private;
33
27
  constructor(options: OwnedUiSettingsSessionOptions);
34
28
  get resolution(): OwnedUiSettingsResolution;
35
- /** Reads the engine's settings so the Agent section can be built. Never throws. */
36
29
  load(): Promise<void>;
37
30
  sections(): readonly OwnedUiSettingsSection[];
38
- /** Resolved value of an A1 setting, or null when it is not declared. */
39
31
  value(id: string): OwnedUiSettingValue | null;
40
- /** Value stored this session but not yet in effect, for a restart-required setting. */
41
32
  pendingValue(id: string): OwnedUiSettingValue | null;
42
33
  onChange(listener: OwnedUiSettingsListener): () => void;
43
- /** Writes a structured value, which only an engine-backed setting can hold. */
44
34
  changeStructured(backend: OwnedUiSettingsBackend, id: string, value: Readonly<Record<string, unknown>>): Promise<OwnedUiSettingsChangeOutcome>;
45
35
  change(backend: OwnedUiSettingsBackend, id: string, value: OwnedUiSettingValue): Promise<OwnedUiSettingsChangeOutcome>;
46
36
  }