@timurproko/a1 0.1.8-dev.182 → 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.
- package/bin/cli.js +2 -2
- package/bin/sync-pi-tui-proxy.js +1 -1
- package/bin/ui.js +1 -1
- package/dist/cli/dispatch.js +3 -3
- package/dist/cli/packages.js +1 -1
- package/dist/cli/version-stats.js +3 -3
- package/dist/composition/owned-ui.js +1 -1
- package/dist/composition/settings-route-host.js +6 -4
- package/dist/features/launch/development-launch.js +1 -1
- package/dist/features/owned-ui/customization.d.ts +2 -0
- package/dist/features/owned-ui/customization.js +2 -0
- package/dist/features/owned-ui/diagnostics.d.ts +1 -0
- package/dist/features/owned-ui/diagnostics.js +1 -0
- package/dist/features/owned-ui/project-trust-prompt.d.ts +11 -6
- package/dist/features/owned-ui/project-trust-prompt.js +134 -17
- package/dist/features/owned-ui/settings-app.js +47 -50
- package/dist/features/workspace/reducer.d.ts +1 -0
- package/dist/features/workspace/reducer.js +1 -0
- package/dist/features/workspace/router.d.ts +1 -0
- package/dist/features/workspace/router.js +1 -0
- package/dist/features/workspace/store.d.ts +1 -0
- package/dist/features/workspace/store.js +1 -0
- package/dist/foundation/launch-guardian/main.js +1 -1
- package/dist/foundation/native-host-protocol/codec.d.ts +2 -0
- package/dist/foundation/native-host-protocol/codec.js +2 -0
- package/dist/foundation/process-containment/linux-process-inspector.d.ts +1 -0
- package/dist/foundation/process-containment/linux-process-inspector.js +1 -0
- package/dist/foundation/process-containment/native-guardian-containment.d.ts +1 -0
- package/dist/foundation/process-containment/native-guardian-containment.js +1 -0
- package/dist/foundation/process-containment/windows-process-inspector.d.ts +1 -0
- package/dist/foundation/process-containment/windows-process-inspector.js +1 -0
- package/dist/foundation/protocol/client.d.ts +1 -0
- package/dist/foundation/protocol/client.js +1 -0
- package/dist/foundation/protocol/messages.d.ts +1 -0
- package/dist/foundation/protocol/messages.js +1 -0
- package/dist/foundation/release/bootstrap.js +6 -6
- package/dist/foundation/release/cohort-state.d.ts +1 -0
- package/dist/foundation/release/cohort-state.js +1 -0
- package/dist/foundation/release/release-gc.js +1 -1
- package/dist/foundation/release/release.js +1 -1
- package/dist/foundation/release/update-transaction.d.ts +1 -0
- package/dist/foundation/release/update-transaction.js +1 -0
- package/dist/foundation/release/update.js +12 -12
- package/dist/foundation/storage/control-store.d.ts +1 -0
- package/dist/foundation/storage/control-store.js +1 -0
- package/dist/foundation/structured-agent-runtime/backpressure.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/backpressure.js +1 -0
- package/dist/foundation/structured-agent-runtime/commands.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/commands.js +1 -0
- package/dist/foundation/structured-agent-runtime/reconnection.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/reconnection.js +1 -0
- package/dist/foundation/structured-agent-runtime/state.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/state.js +1 -0
- package/dist/foundation/supervision/main.js +1 -1
- package/dist/foundation/supervision/server.d.ts +1 -0
- package/dist/foundation/supervision/server.js +4 -6
- package/dist/integrations/pi/components/conformance.d.ts +1 -0
- package/dist/integrations/pi/components/conformance.js +1 -0
- package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
- package/dist/integrations/pi/components/shell-footer-status.js +8 -7
- package/dist/integrations/pi/components/shell-shared-facade.js +1 -1
- package/dist/integrations/pi/engine/adapter.d.ts +7 -0
- package/dist/integrations/pi/engine/adapter.js +37 -35
- package/dist/integrations/pi/engine/conformance.d.ts +2 -0
- package/dist/integrations/pi/engine/conformance.js +2 -0
- package/dist/integrations/pi/engine/http-dispatcher.js +1 -1
- package/dist/integrations/pi/engine/model-auth-integration.d.ts +1 -0
- package/dist/integrations/pi/engine/model-auth-integration.js +1 -0
- package/dist/integrations/pi/engine/package-integration.js +1 -1
- package/dist/integrations/pi/engine/project-trust-preflight.js +1 -1
- package/dist/integrations/pi/engine/resource-extension-integration.d.ts +1 -0
- package/dist/integrations/pi/engine/resource-extension-integration.js +1 -0
- package/dist/integrations/pi/engine/session-integration.d.ts +1 -0
- package/dist/integrations/pi/engine/session-integration.js +2 -1
- package/dist/integrations/pi/engine/settings-effects.d.ts +13 -2
- package/dist/integrations/pi/engine/settings-effects.js +62 -42
- package/dist/integrations/pi/engine/settings-integration.js +4 -4
- package/dist/integrations/pi/engine/workflow-controllers.d.ts +1 -0
- package/dist/integrations/pi/engine/workflow-controllers.js +1 -0
- package/dist/integrations/pi/session-ui/index.d.ts +1 -0
- package/dist/integrations/pi/session-ui/index.js +1 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +1 -1
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +30 -2
- package/dist/integrations/pi/session-ui/session-shell-root.js +133 -72
- package/dist/integrations/pi/session-ui/session-shell.d.ts +9 -2
- package/dist/integrations/pi/session-ui/session-shell.js +112 -37
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +5 -0
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +44 -11
- package/dist/integrations/pi/session-ui/stream-presentation-coalescer.d.ts +29 -0
- package/dist/integrations/pi/session-ui/stream-presentation-coalescer.js +79 -0
- package/dist/integrations/pi/session-ui/system-clipboard.js +6 -6
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +138 -8
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +25 -0
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +63 -0
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +240 -0
- package/dist/integrations/pi/tui-runtime/index.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/index.js +2 -0
- package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.d.ts +39 -0
- package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.js +139 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/apps/host.js +5 -9
- package/dist/ui/apps/registry.d.ts +1 -0
- package/dist/ui/apps/registry.js +1 -0
- package/dist/ui/components/dialog-panel.js +5 -3
- package/dist/ui/components/frame.d.ts +1 -0
- package/dist/ui/components/frame.js +1 -0
- package/dist/ui/components/index.d.ts +1 -0
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/line-input.d.ts +1 -6
- package/dist/ui/components/line-input.js +4 -9
- package/dist/ui/components/list-view.d.ts +0 -6
- package/dist/ui/components/list-view.js +14 -7
- package/dist/ui/components/progress-status.d.ts +2 -0
- package/dist/ui/components/progress-status.js +4 -0
- package/dist/ui/components/scrollbar.js +1 -1
- package/dist/ui/components/shortcuts.d.ts +1 -0
- package/dist/ui/components/shortcuts.js +1 -0
- package/dist/ui/components/spans.d.ts +0 -5
- package/dist/ui/components/spans.js +3 -8
- package/dist/ui/components/surface.d.ts +0 -5
- package/dist/ui/components/surface.js +1 -5
- package/dist/ui/components/text-selection.d.ts +10 -1
- package/dist/ui/components/text-selection.js +96 -29
- package/dist/ui/components/text.js +3 -3
- package/dist/ui/components/transcript-viewport.d.ts +42 -0
- package/dist/ui/components/transcript-viewport.js +287 -39
- package/dist/ui/settings/sections.d.ts +0 -2
- package/dist/ui/settings/sections.js +10 -16
- package/dist/ui/settings/session.js +5 -4
- package/dist/ui/settings/store.d.ts +1 -0
- package/dist/ui/settings/store.js +2 -1
- package/docs/architecture/code-documentation.md +77 -0
- package/docs/architecture/project-structure.md +1 -1
- package/docs/architecture/ui-reference-provenance.md +23 -3
- package/docs/ci-release-runbook.md +30 -4
- package/docs/manual-owned-ui-checkpoint.md +28 -1
- package/docs/manual-terminal-colour-check.md +4 -3
- package/package.json +7 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GLOBAL_SCOPE, LineInput, PLAIN_THEME, ShortcutRegistry, assertNoShortcutConflicts,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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,25 +316,21 @@ export class SettingsApp {
|
|
|
318
316
|
return;
|
|
319
317
|
}
|
|
320
318
|
const shown = this.#shownValue(entry);
|
|
321
|
-
//
|
|
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;
|
|
325
323
|
if (!entry.editable || entry.choices === null || entry.choices.length === 0) {
|
|
326
|
-
this.#notice =
|
|
324
|
+
this.#notice = `${labelOf(entry)} cannot be changed here`;
|
|
327
325
|
return;
|
|
328
326
|
}
|
|
329
327
|
const current = shown === null ? 0 : Math.max(0, entry.choices.indexOf(shown));
|
|
330
|
-
//
|
|
331
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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 };
|
|
@@ -456,12 +450,12 @@ export class SettingsApp {
|
|
|
456
450
|
return;
|
|
457
451
|
const entry = row.value;
|
|
458
452
|
if (!entry.editable) {
|
|
459
|
-
this.#notice =
|
|
453
|
+
this.#notice = `${labelOf(entry)} cannot be changed here`;
|
|
460
454
|
return;
|
|
461
455
|
}
|
|
462
456
|
const shown = this.#shownValue(entry);
|
|
463
457
|
if (typeof shown === "number") {
|
|
464
|
-
//
|
|
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)
|
|
@@ -470,7 +464,7 @@ export class SettingsApp {
|
|
|
470
464
|
}
|
|
471
465
|
const choices = entry.choices;
|
|
472
466
|
if (choices === null || choices.length === 0) {
|
|
473
|
-
this.#notice =
|
|
467
|
+
this.#notice = `${labelOf(entry)} cannot be changed here`;
|
|
474
468
|
return;
|
|
475
469
|
}
|
|
476
470
|
const current = shown === null ? -1 : choices.indexOf(shown);
|
|
@@ -482,7 +476,7 @@ export class SettingsApp {
|
|
|
482
476
|
}
|
|
483
477
|
#apply(entry, value) {
|
|
484
478
|
const key = `${entry.backend}:${entry.id}`;
|
|
485
|
-
//
|
|
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 => {
|
|
@@ -496,7 +490,7 @@ export class SettingsApp {
|
|
|
496
490
|
this.#notice = outcome.limitationReason ?? `${labelOf(entry)} is unavailable`;
|
|
497
491
|
return;
|
|
498
492
|
}
|
|
499
|
-
//
|
|
493
|
+
// Concurrency: a later press may have moved on; only the last request clears itself.
|
|
500
494
|
if (this.#pending.get(key) === value)
|
|
501
495
|
this.#pending.delete(key);
|
|
502
496
|
this.#notice = outcome.status === "deferred" && outcome.application !== null
|
|
@@ -504,10 +498,16 @@ export class SettingsApp {
|
|
|
504
498
|
: null;
|
|
505
499
|
});
|
|
506
500
|
}
|
|
507
|
-
/** What the row shows: the value asked for if one is outstanding, else the source's. */
|
|
508
501
|
#shownValue(entry) {
|
|
509
502
|
return this.#pending.get(`${entry.backend}:${entry.id}`) ?? entry.value;
|
|
510
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
|
+
}
|
|
511
511
|
#jump(rows, target) {
|
|
512
512
|
this.#select(rows, target);
|
|
513
513
|
this.#reveal = blockRowSpan(rows, target);
|
|
@@ -526,7 +526,7 @@ export class SettingsApp {
|
|
|
526
526
|
|| labelOf(entry).toLowerCase().includes(needle);
|
|
527
527
|
const rows = [];
|
|
528
528
|
for (const section of this.#session.sections()) {
|
|
529
|
-
//
|
|
529
|
+
// Rationale: a section named by the search is what the reader asked for, so it arrives
|
|
530
530
|
// whole rather than narrowed to the entries that happen to repeat its name.
|
|
531
531
|
const named = needle.length > 0 && section.title.toLowerCase().includes(needle);
|
|
532
532
|
const entries = named ? section.entries : section.entries.filter(matches);
|
|
@@ -542,7 +542,7 @@ export class SettingsApp {
|
|
|
542
542
|
if (section.readOnlyReason !== null && (needle.length === 0 || named)) {
|
|
543
543
|
rows.push({ kind: "note", group: section.id, text: section.readOnlyReason });
|
|
544
544
|
}
|
|
545
|
-
//
|
|
545
|
+
// Invariant: presented in the order the source reports, which is the order the
|
|
546
546
|
// pinned engine shows and is neither declaration order nor alphabetical.
|
|
547
547
|
for (const entry of entries) {
|
|
548
548
|
rows.push({
|
|
@@ -575,16 +575,13 @@ export class SettingsApp {
|
|
|
575
575
|
const hovered = this.#hoverKey === key;
|
|
576
576
|
return renderListRow(this.#viewRow(entry), { selected, hovered, region: this.#hoverRegion }, valueColumn, width, theme);
|
|
577
577
|
}
|
|
578
|
-
/** What the list view needs to draw a setting: its words, and where it can go. */
|
|
579
578
|
#viewRow(entry) {
|
|
580
579
|
const shown = this.#shownValue(entry);
|
|
581
|
-
const value =
|
|
582
|
-
?
|
|
583
|
-
:
|
|
584
|
-
?
|
|
585
|
-
: shown
|
|
586
|
-
? describeRaw(entry.rawValue)
|
|
587
|
-
: effectiveDisplay(entry, shown);
|
|
580
|
+
const value = entry.structured
|
|
581
|
+
? CONFIGURE
|
|
582
|
+
: shown === null
|
|
583
|
+
? describeRaw(entry.rawValue)
|
|
584
|
+
: effectiveDisplay(entry, shown);
|
|
588
585
|
const range = rangeOf(entry);
|
|
589
586
|
return {
|
|
590
587
|
key: `${entry.backend}:${entry.id}`,
|
|
@@ -613,13 +610,12 @@ export class SettingsApp {
|
|
|
613
610
|
this.#menuFrame = frame;
|
|
614
611
|
return renderValueMenu(lines, state, frame, theme);
|
|
615
612
|
}
|
|
616
|
-
/** What the panel needs to present a structured setting's parts. */
|
|
617
613
|
#dialogLines(open, width, theme) {
|
|
618
614
|
const rows = open.flags.map(key => {
|
|
619
615
|
const declared = open.entry.flags.find(flag => flag.key === key);
|
|
620
616
|
return {
|
|
621
617
|
label: declared?.label ?? humanizeLabel(key),
|
|
622
|
-
//
|
|
618
|
+
// Protocol: the engine writes these as the booleans they are rather than as yes/no.
|
|
623
619
|
value: (open.record[key] ?? false) ? "true" : "false",
|
|
624
620
|
...(declared?.description === undefined ? {} : { description: declared.description }),
|
|
625
621
|
};
|
|
@@ -632,8 +628,6 @@ export class SettingsApp {
|
|
|
632
628
|
const open = this.#structured;
|
|
633
629
|
if (open !== null)
|
|
634
630
|
return this.#dialogLines(open, width, theme);
|
|
635
|
-
// The hint is the declarations, so a key cannot be described here and bound
|
|
636
|
-
// to something else.
|
|
637
631
|
const hint = this.#interruptArmed ? "press ctrl+c again to exit A1" : SETTINGS_SHORTCUTS.hint(SCOPE);
|
|
638
632
|
const status = renderStatusLine({ hint, report: this.#notice }, width, theme);
|
|
639
633
|
const input = this.#filter;
|
|
@@ -683,3 +677,6 @@ function describeRaw(value) {
|
|
|
683
677
|
return Array.isArray(value) ? `${value.length} items` : "structured value";
|
|
684
678
|
return String(value);
|
|
685
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
|
-
//
|
|
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,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;
|
|
@@ -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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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"); }
|