@timurproko/a1 0.1.8-dev.136 → 0.1.8-dev.139
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/README.md +2 -4
- package/dist/cli/capabilities.d.ts +2 -11
- package/dist/cli/capabilities.js +1 -1
- package/dist/cli/dispatch.js +3 -6
- package/dist/composition/owned-ui.js +19 -1
- package/dist/contracts/owned-ui/model.d.ts +21 -0
- package/dist/contracts/owned-ui/validation.js +3 -0
- package/dist/features/launch/development-launch.js +11 -3
- package/dist/features/launch/index.d.ts +0 -1
- package/dist/features/launch/index.js +0 -1
- package/dist/features/launch/intent.d.ts +3 -2
- package/dist/features/launch/intent.js +1 -2
- package/dist/features/launch/prepare-launch.d.ts +0 -2
- package/dist/features/launch/prepare-launch.js +2 -9
- package/dist/features/launch/profile-paths.d.ts +1 -2
- package/dist/features/launch/profile-paths.js +2 -8
- package/dist/features/launch/runtime-selection.d.ts +2 -5
- package/dist/features/owned-ui/settings-app.js +36 -13
- package/dist/foundation/lifecycle/model.d.ts +1 -1
- package/dist/foundation/lifecycle/model.js +1 -1
- package/dist/foundation/release/bootstrap.d.ts +1 -3
- package/dist/foundation/release/bootstrap.js +12 -4
- package/dist/foundation/storage/control-store.js +37 -3
- package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
- package/dist/integrations/pi/components/shell-footer-status.js +14 -5
- package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -2
- package/dist/integrations/pi/components/shell-presenters-transcript.js +9 -8
- package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +13 -0
- package/dist/integrations/pi/components/submitted-prompt-adapter.js +27 -0
- package/dist/integrations/pi/engine/adapter.js +16 -2
- package/dist/integrations/pi/engine/session-integration.js +5 -2
- package/dist/integrations/pi/owned-ui/session-shell-root.d.ts +17 -2
- package/dist/integrations/pi/owned-ui/session-shell-root.js +451 -42
- package/dist/integrations/pi/owned-ui/session-shell.js +116 -13
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -1
- package/dist/integrations/pi/tui-runtime/adapter.js +51 -2
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +2 -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/product-identity.d.ts +2 -2
- package/dist/product-identity.js +3 -4
- package/dist/product-identity.json +3 -5
- package/dist/ui/components/index.d.ts +3 -0
- package/dist/ui/components/index.js +3 -0
- package/dist/ui/components/mouse.d.ts +10 -0
- package/dist/ui/components/mouse.js +23 -0
- package/dist/ui/components/prompt-row.d.ts +14 -0
- package/dist/ui/components/prompt-row.js +20 -0
- package/dist/ui/components/scrollbar.d.ts +26 -0
- package/dist/ui/components/scrollbar.js +35 -0
- package/dist/ui/components/spans.d.ts +6 -0
- package/dist/ui/components/spans.js +36 -0
- package/dist/ui/components/submitted-prompt.d.ts +23 -0
- package/dist/ui/components/submitted-prompt.js +50 -0
- package/dist/ui/components/text-selection.d.ts +53 -0
- package/dist/ui/components/text-selection.js +168 -0
- package/dist/ui/components/text.d.ts +14 -0
- package/dist/ui/components/text.js +40 -0
- package/dist/ui/components/transcript-viewport.d.ts +98 -0
- package/dist/ui/components/transcript-viewport.js +353 -0
- package/dist/ui/settings/declarations.d.ts +8 -1
- package/dist/ui/settings/declarations.js +33 -5
- package/dist/ui/settings/migrations.d.ts +1 -1
- package/dist/ui/settings/migrations.js +12 -2
- package/dist/ui/settings/sections.js +15 -8
- package/dist/ui/settings/session.d.ts +2 -0
- package/dist/ui/settings/session.js +7 -5
- package/docs/architecture/boundaries.md +2 -2
- package/docs/architecture/project-structure.md +1 -1
- package/docs/architecture/resource-and-data-policy.md +1 -1
- package/docs/architecture/toolchain.md +5 -5
- package/docs/architecture/ui-reference-provenance.md +11 -2
- package/docs/features/launch-profiles.md +7 -14
- package/docs/manual-launch-instance-acceptance.md +12 -35
- package/docs/manual-owned-ui-checkpoint.md +2 -3
- package/docs/manual-pi-boundary-candidate.md +4 -5
- package/package.json +1 -2
- package/dist/features/launch/profiles.d.ts +0 -12
- package/dist/features/launch/profiles.js +0 -26
|
@@ -15,12 +15,16 @@ export class OwnedUiSessionShell {
|
|
|
15
15
|
#stopped;
|
|
16
16
|
#resolveStopped;
|
|
17
17
|
#dialogId;
|
|
18
|
+
#dialogSource;
|
|
18
19
|
#routeHost;
|
|
19
20
|
#dialogHandle;
|
|
20
21
|
#sequence = 0;
|
|
21
22
|
#started = false;
|
|
22
23
|
#disposed = false;
|
|
23
24
|
#pointerReporting = false;
|
|
25
|
+
#customViewport;
|
|
26
|
+
#removeViewportPreInput;
|
|
27
|
+
#unsubscribeSettings;
|
|
24
28
|
#compactionQueue = [];
|
|
25
29
|
#lastClearTime = 0;
|
|
26
30
|
#activeLoginDialog;
|
|
@@ -30,6 +34,7 @@ export class OwnedUiSessionShell {
|
|
|
30
34
|
this.#sessionGeneration = this.backend.sessionGeneration;
|
|
31
35
|
this.#cwd = options.cwd;
|
|
32
36
|
this.#routeHost = options.routeHost ?? null;
|
|
37
|
+
this.#customViewport = options.sessionLayout === "custom-viewport";
|
|
33
38
|
this.#stopped = new Promise(resolve => {
|
|
34
39
|
this.#resolveStopped = resolve;
|
|
35
40
|
});
|
|
@@ -58,13 +63,50 @@ export class OwnedUiSessionShell {
|
|
|
58
63
|
}, this.backend.agentDir, {
|
|
59
64
|
getMessageRenderer: customType => this.backend.pinnedMessageRenderer(customType),
|
|
60
65
|
getToolDefinition: toolName => this.backend.pinnedToolDefinition(toolName),
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
}, options.sessionLayout);
|
|
67
|
+
// Bare A1 owns a bounded viewport and therefore always runs on the alternate
|
|
68
|
+
// fullscreen surface. The pinned comparison profiles still honor Pi's mode.
|
|
69
|
+
const tuiMode = this.#customViewport
|
|
70
|
+
? "fullscreen"
|
|
71
|
+
: this.backend.disposed ? "regular" : this.backend.pinnedSettingsSnapshot().tuiMode;
|
|
72
|
+
const runtimeOptions = {
|
|
73
|
+
root: this.root,
|
|
74
|
+
mode: tuiMode,
|
|
75
|
+
...(this.#customViewport ? {} : { layoutRoot: this.root.layoutRoot() }),
|
|
76
|
+
...(options.terminal === undefined ? {} : { terminal: options.terminal }),
|
|
77
|
+
hardwareCursor: this.backend.view().terminal.hardwareCursor,
|
|
78
|
+
};
|
|
66
79
|
runtime = new PiTuiRuntimeAdapter(runtimeOptions);
|
|
67
80
|
this.runtime = runtime;
|
|
81
|
+
this.#removeViewportPreInput = this.#customViewport
|
|
82
|
+
? this.runtime.addPreInputListener(data => {
|
|
83
|
+
// An overlay or editor-replacement screen owns its entire pointer
|
|
84
|
+
// surface. Letting the transcript pre-router inspect those reports
|
|
85
|
+
// steals settings value menus and numeric +/- controls before the
|
|
86
|
+
// settings app can receive them.
|
|
87
|
+
if (this.runtime.hasOverlay() || !this.root.usesDefaultInputSurface())
|
|
88
|
+
return undefined;
|
|
89
|
+
const routed = this.root.handleViewportPreInput(data, true);
|
|
90
|
+
if (routed.copyText !== undefined) {
|
|
91
|
+
this.runtime.writeControl(`\u001b]52;c;${Buffer.from(routed.copyText, "utf8").toString("base64")}\u0007`);
|
|
92
|
+
}
|
|
93
|
+
if (!routed.consumed)
|
|
94
|
+
return routed.data === data ? undefined : { data: routed.data };
|
|
95
|
+
return routed.data.length === 0 ? { consume: true } : { data: routed.data };
|
|
96
|
+
})
|
|
97
|
+
: () => { };
|
|
98
|
+
const applyViewportSettings = () => {
|
|
99
|
+
const snapshot = options.viewportSettings?.snapshot();
|
|
100
|
+
this.root.setViewportConfig(snapshot ?? {
|
|
101
|
+
scrollbarAppearance: "hover",
|
|
102
|
+
scrollbarStyle: "thin",
|
|
103
|
+
scrollbarSpeed: "normal",
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
applyViewportSettings();
|
|
107
|
+
this.#unsubscribeSettings = this.#customViewport && options.viewportSettings
|
|
108
|
+
? options.viewportSettings.onChange(settings => this.root.setViewportConfig(settings))
|
|
109
|
+
: () => { };
|
|
68
110
|
this.#extensionBridge = createPiExtensionUiBridge({
|
|
69
111
|
runtime: {
|
|
70
112
|
getColumns: () => this.runtime.viewport().columns,
|
|
@@ -102,9 +144,14 @@ export class OwnedUiSessionShell {
|
|
|
102
144
|
// A streamed chunk names one block, and touching only that block is what keeps the
|
|
103
145
|
// cost of a chunk the same in a long session as in a new one. Everything else
|
|
104
146
|
// resynchronizes the view, which is cheap next to re-reading the transcript.
|
|
147
|
+
if (event.type === "agent-run-started")
|
|
148
|
+
this.root.resumeViewportFollowing();
|
|
149
|
+
if (event.type === "assistant-message-completed")
|
|
150
|
+
this.root.noteCompletedAssistantMessage();
|
|
151
|
+
const semanticOnly = event.type === "agent-run-started" || event.type === "assistant-message-completed";
|
|
105
152
|
const view = event.type === "transcript-block" && this.#sessionGeneration === this.backend.sessionGeneration
|
|
106
153
|
? this.#syncBlock(event.block)
|
|
107
|
-
: this.#syncView();
|
|
154
|
+
: semanticOnly ? this.view() : this.#syncView();
|
|
108
155
|
if (view.lifecycle === "ready" && this.#compactionQueue.length > 0)
|
|
109
156
|
void this.#flushCompactionQueue();
|
|
110
157
|
if (event.type === "session-lifecycle" && event.lifecycle === "stopped")
|
|
@@ -121,6 +168,8 @@ export class OwnedUiSessionShell {
|
|
|
121
168
|
return;
|
|
122
169
|
this.#started = true;
|
|
123
170
|
this.runtime.start();
|
|
171
|
+
if (this.#customViewport)
|
|
172
|
+
this.#setPointerReporting(true);
|
|
124
173
|
void this.backend.bindExtensionUi(this.#extensionBridge.context, () => { void this.shutdown(); });
|
|
125
174
|
this.#syncView();
|
|
126
175
|
}
|
|
@@ -172,6 +221,7 @@ export class OwnedUiSessionShell {
|
|
|
172
221
|
}
|
|
173
222
|
const type = this.view().lifecycle === "busy" ? "steer" : "prompt";
|
|
174
223
|
this.root.editor.addToHistory(input);
|
|
224
|
+
this.root.resumeViewportFollowing();
|
|
175
225
|
return this.#execute({
|
|
176
226
|
type,
|
|
177
227
|
correlationId: this.#correlation(type),
|
|
@@ -251,6 +301,7 @@ export class OwnedUiSessionShell {
|
|
|
251
301
|
return rejected("nothing to queue");
|
|
252
302
|
this.root.editor.addToHistory(text);
|
|
253
303
|
this.root.editor.setText("");
|
|
304
|
+
this.root.resumeViewportFollowing();
|
|
254
305
|
if (this.view().status.workingMessage?.startsWith("Compacting") === true) {
|
|
255
306
|
this.#compactionQueue.push({ text, type: "follow-up" });
|
|
256
307
|
return { outcome: "completed", diagnostic: null };
|
|
@@ -272,6 +323,8 @@ export class OwnedUiSessionShell {
|
|
|
272
323
|
}
|
|
273
324
|
showSelector(title, options, onSelect, onCancel) {
|
|
274
325
|
this.#dialogHandle?.hide();
|
|
326
|
+
this.#dialogSource = "local";
|
|
327
|
+
this.#dialogId = undefined;
|
|
275
328
|
const component = createPiShellSelector({
|
|
276
329
|
title,
|
|
277
330
|
options,
|
|
@@ -279,10 +332,12 @@ export class OwnedUiSessionShell {
|
|
|
279
332
|
onSelect(id);
|
|
280
333
|
this.#dialogHandle?.hide();
|
|
281
334
|
this.#dialogHandle = undefined;
|
|
335
|
+
this.#dialogSource = undefined;
|
|
282
336
|
},
|
|
283
337
|
onCancel: () => {
|
|
284
338
|
this.#dialogHandle?.hide();
|
|
285
339
|
this.#dialogHandle = undefined;
|
|
340
|
+
this.#dialogSource = undefined;
|
|
286
341
|
onCancel?.();
|
|
287
342
|
},
|
|
288
343
|
});
|
|
@@ -486,6 +541,10 @@ export class OwnedUiSessionShell {
|
|
|
486
541
|
return;
|
|
487
542
|
}
|
|
488
543
|
if (callback === "onTuiModeChange") {
|
|
544
|
+
// The custom bare-A1 surface is permanently fullscreen; this callback
|
|
545
|
+
// remains available only to pinned comparison profiles.
|
|
546
|
+
if (this.#customViewport)
|
|
547
|
+
return;
|
|
489
548
|
if (value !== "regular" && value !== "fullscreen")
|
|
490
549
|
return;
|
|
491
550
|
if (!this.runtime.switchMode(value)) {
|
|
@@ -493,6 +552,10 @@ export class OwnedUiSessionShell {
|
|
|
493
552
|
this.runtime.requestRender();
|
|
494
553
|
return;
|
|
495
554
|
}
|
|
555
|
+
if (this.#customViewport) {
|
|
556
|
+
this.#pointerReporting = false;
|
|
557
|
+
this.#setPointerReporting(true);
|
|
558
|
+
}
|
|
496
559
|
}
|
|
497
560
|
const result = this.backend.applyPinnedSettingValue(callback, value);
|
|
498
561
|
if (result.outcome === "failed")
|
|
@@ -704,19 +767,23 @@ export class OwnedUiSessionShell {
|
|
|
704
767
|
* button instead of scrolling and selecting itself, so leaving it on outlives the
|
|
705
768
|
* screen that wanted it and takes the terminal's own scrolling and selection with it.
|
|
706
769
|
*/
|
|
707
|
-
#setPointerReporting(enabled) {
|
|
708
|
-
|
|
770
|
+
#setPointerReporting(enabled, forceOff = false) {
|
|
771
|
+
const effective = forceOff ? false : this.#customViewport || enabled;
|
|
772
|
+
if (this.#pointerReporting === effective)
|
|
709
773
|
return;
|
|
710
|
-
this.#pointerReporting =
|
|
774
|
+
this.#pointerReporting = effective;
|
|
711
775
|
if (!this.runtime.active)
|
|
712
776
|
return;
|
|
713
|
-
this.runtime.writeControl(
|
|
777
|
+
this.runtime.writeControl(effective ? MOUSE_TRACKING_ON : MOUSE_TRACKING_OFF);
|
|
714
778
|
}
|
|
715
779
|
async dispose() {
|
|
716
780
|
if (this.#disposed)
|
|
717
781
|
return;
|
|
718
782
|
this.#disposed = true;
|
|
719
|
-
this
|
|
783
|
+
this.root.clearViewportPointerState();
|
|
784
|
+
this.#setPointerReporting(false, true);
|
|
785
|
+
this.#removeViewportPreInput();
|
|
786
|
+
this.#unsubscribeSettings();
|
|
720
787
|
this.#unsubscribe();
|
|
721
788
|
this.#dialogHandle?.hide();
|
|
722
789
|
await this.backend.unbindExtensionUi();
|
|
@@ -741,7 +808,12 @@ export class OwnedUiSessionShell {
|
|
|
741
808
|
this.#sessionGeneration = this.backend.sessionGeneration;
|
|
742
809
|
this.#activeLoginDialog = undefined;
|
|
743
810
|
this.#extensionBridge.reset();
|
|
744
|
-
// A replaced session takes its
|
|
811
|
+
// A replaced session takes its transient viewport and owned-route state with it.
|
|
812
|
+
this.root.resetViewport();
|
|
813
|
+
this.#dialogHandle?.hide();
|
|
814
|
+
this.#dialogHandle = undefined;
|
|
815
|
+
this.#dialogId = undefined;
|
|
816
|
+
this.#dialogSource = undefined;
|
|
745
817
|
this.#setPointerReporting(false);
|
|
746
818
|
this.root.setInputSurface(null);
|
|
747
819
|
this.root.resetExtensionUi();
|
|
@@ -761,6 +833,7 @@ export class OwnedUiSessionShell {
|
|
|
761
833
|
if (!this.runtime.active)
|
|
762
834
|
return { outcome: "failed", diagnostic: "runtime is not active" };
|
|
763
835
|
this.#dialogHandle?.hide();
|
|
836
|
+
this.#dialogSource = "route";
|
|
764
837
|
// Any-event reporting: hover and drag are what the screen is driven by, and
|
|
765
838
|
// it also stops the terminal treating a drag as a text selection.
|
|
766
839
|
this.#setPointerReporting(true);
|
|
@@ -784,13 +857,32 @@ export class OwnedUiSessionShell {
|
|
|
784
857
|
// stray interrupt while it is up.
|
|
785
858
|
return { consume: true };
|
|
786
859
|
});
|
|
860
|
+
let removeSurfacePreInput = () => { };
|
|
787
861
|
const closeSurface = () => {
|
|
862
|
+
removeSurfacePreInput();
|
|
788
863
|
removeInterruptWatch();
|
|
789
864
|
this.#setPointerReporting(false);
|
|
790
865
|
this.#dialogHandle?.hide();
|
|
791
866
|
this.#dialogHandle = undefined;
|
|
792
867
|
this.#dialogId = undefined;
|
|
868
|
+
this.#dialogSource = undefined;
|
|
793
869
|
};
|
|
870
|
+
// Fullscreen Pi owns a fallback text-selection layer before focused overlay
|
|
871
|
+
// components see pointer input. Route every mouse report to the owned screen
|
|
872
|
+
// at the pre-input boundary so dropdowns, value hover, and numeric +/- work,
|
|
873
|
+
// and consume even unhandled reports so settings content is never selected.
|
|
874
|
+
removeSurfacePreInput = this.runtime.addPreInputListener(data => {
|
|
875
|
+
const { events, rest } = parseMouseInput(data);
|
|
876
|
+
if (events.length === 0)
|
|
877
|
+
return undefined;
|
|
878
|
+
for (const event of events)
|
|
879
|
+
surface.handleMouse(event);
|
|
880
|
+
if (surface.isClosed())
|
|
881
|
+
closeSurface();
|
|
882
|
+
else
|
|
883
|
+
this.runtime.requestRender();
|
|
884
|
+
return rest.length === 0 ? { consume: true } : { data: rest };
|
|
885
|
+
});
|
|
794
886
|
const rows = () => Math.max(1, this.runtime.viewport().rows);
|
|
795
887
|
const component = {
|
|
796
888
|
render: (width) => [...surface.render(Math.max(1, width), rows())],
|
|
@@ -821,22 +913,32 @@ export class OwnedUiSessionShell {
|
|
|
821
913
|
if (!this.runtime.active)
|
|
822
914
|
return;
|
|
823
915
|
if (dialog === null) {
|
|
916
|
+
// Locally owned routes (notably /settings) are independent of backend
|
|
917
|
+
// lifecycle/status events and remain open while an agent is working.
|
|
918
|
+
if (this.#dialogSource !== "backend")
|
|
919
|
+
return;
|
|
824
920
|
this.#dialogHandle?.hide();
|
|
825
921
|
this.#dialogHandle = undefined;
|
|
826
922
|
this.#dialogId = undefined;
|
|
923
|
+
this.#dialogSource = undefined;
|
|
827
924
|
return;
|
|
828
925
|
}
|
|
829
|
-
if (this.#dialogId === dialog.id)
|
|
926
|
+
if (this.#dialogSource === "backend" && this.#dialogId === dialog.id)
|
|
830
927
|
return;
|
|
831
928
|
this.#dialogHandle?.hide();
|
|
929
|
+
this.#dialogSource = "backend";
|
|
832
930
|
const component = createPiShellDialog(dialog, {
|
|
833
931
|
onSelect: () => {
|
|
834
932
|
this.#dialogHandle?.hide();
|
|
835
933
|
this.#dialogHandle = undefined;
|
|
934
|
+
this.#dialogId = undefined;
|
|
935
|
+
this.#dialogSource = undefined;
|
|
836
936
|
},
|
|
837
937
|
onCancel: () => {
|
|
838
938
|
this.#dialogHandle?.hide();
|
|
839
939
|
this.#dialogHandle = undefined;
|
|
940
|
+
this.#dialogId = undefined;
|
|
941
|
+
this.#dialogSource = undefined;
|
|
840
942
|
},
|
|
841
943
|
});
|
|
842
944
|
this.#dialogHandle = this.runtime.showOverlay(component, { width: "70%", maxHeight: "80%", anchor: "center" });
|
|
@@ -853,6 +955,7 @@ export class OwnedUiSessionShell {
|
|
|
853
955
|
return this.runWorkflow({ command: name, argument });
|
|
854
956
|
// Unknown slash input, prompt templates, skills, and extension commands remain Pi prompt input.
|
|
855
957
|
this.root.editor.addToHistory(text);
|
|
958
|
+
this.root.resumeViewportFollowing();
|
|
856
959
|
return this.#execute({
|
|
857
960
|
type: this.view().lifecycle === "busy" ? "steer" : "prompt",
|
|
858
961
|
correlationId: this.#correlation("prompt-command"),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PiTuiComponentPort, PiTuiInputListener, PiTuiOverlayHandle, PiTuiOverlayOptions, PiTuiRuntimeAdapterOptions, PiTuiRuntimeState, PiTuiScrollState, PiTuiStopOptions, PiTuiViewport } from "./contracts.js";
|
|
1
|
+
import type { PiTuiComponentPort, PiTuiInputListener, PiTuiOverlayHandle, PiTuiOverlayOptions, PiTuiPreInputListener, PiTuiRuntimeAdapterOptions, PiTuiRuntimeState, PiTuiScrollState, PiTuiStopOptions, PiTuiViewport } from "./contracts.js";
|
|
2
2
|
export type PiTuiRuntimeErrorStage = "construction" | "start" | "input-drain" | "restoration";
|
|
3
3
|
export declare class PiTuiRuntimeError extends Error {
|
|
4
4
|
readonly stage: PiTuiRuntimeErrorStage;
|
|
@@ -26,6 +26,7 @@ export declare class PiTuiRuntimeAdapter {
|
|
|
26
26
|
* the transparent and pinned paths never call it.
|
|
27
27
|
*/
|
|
28
28
|
writeControl(data: string): void;
|
|
29
|
+
addPreInputListener(listener: PiTuiPreInputListener): () => void;
|
|
29
30
|
addInputListener(listener: PiTuiInputListener): () => void;
|
|
30
31
|
switchMode(mode: "regular" | "fullscreen"): boolean;
|
|
31
32
|
setHardwareCursor(enabled: boolean): void;
|
|
@@ -81,6 +81,7 @@ class OverlayHandleBridge {
|
|
|
81
81
|
}
|
|
82
82
|
export class PiTuiRuntimeAdapter {
|
|
83
83
|
#terminal;
|
|
84
|
+
#tuiTerminal;
|
|
84
85
|
#tui;
|
|
85
86
|
#root;
|
|
86
87
|
#layoutRoot;
|
|
@@ -92,12 +93,14 @@ export class PiTuiRuntimeAdapter {
|
|
|
92
93
|
#scrollViews = new Map();
|
|
93
94
|
#overlayDisposers = new Set();
|
|
94
95
|
#inputListeners = new Map();
|
|
96
|
+
#preInputListeners = new Set();
|
|
95
97
|
#state = "idle";
|
|
96
98
|
#stopPromise;
|
|
97
99
|
#rootDisposed = false;
|
|
98
100
|
constructor(options) {
|
|
99
101
|
this.#root = options.root;
|
|
100
102
|
this.#terminal = options.terminal ?? new ProcessTerminal();
|
|
103
|
+
this.#tuiTerminal = preInputTerminal(this.#terminal, data => this.#routePreInput(data));
|
|
101
104
|
this.#layoutRoot = options.layoutRoot;
|
|
102
105
|
this.#logDirectory = options.logDirectory;
|
|
103
106
|
this.#rootBridge = new ComponentBridge(options.root);
|
|
@@ -239,6 +242,12 @@ export class PiTuiRuntimeAdapter {
|
|
|
239
242
|
this.#assertRunning("control sequence");
|
|
240
243
|
this.#terminal.write(data);
|
|
241
244
|
}
|
|
245
|
+
addPreInputListener(listener) {
|
|
246
|
+
if (this.#preInputListeners.has(listener))
|
|
247
|
+
throw new TypeError("Pi TUI pre-input listener is already registered");
|
|
248
|
+
this.#preInputListeners.add(listener);
|
|
249
|
+
return () => this.#preInputListeners.delete(listener);
|
|
250
|
+
}
|
|
242
251
|
addInputListener(listener) {
|
|
243
252
|
this.#assertRunning("input listener");
|
|
244
253
|
if (this.#inputListeners.has(listener))
|
|
@@ -313,6 +322,7 @@ export class PiTuiRuntimeAdapter {
|
|
|
313
322
|
return this.#stopPromise;
|
|
314
323
|
if (this.#state === "idle") {
|
|
315
324
|
this.#state = "stopped";
|
|
325
|
+
this.#preInputListeners.clear();
|
|
316
326
|
this.#disposeRoot();
|
|
317
327
|
return Promise.resolve();
|
|
318
328
|
}
|
|
@@ -352,8 +362,21 @@ export class PiTuiRuntimeAdapter {
|
|
|
352
362
|
}
|
|
353
363
|
#createTui(mode, hardwareCursor) {
|
|
354
364
|
return mode === "fullscreen"
|
|
355
|
-
? new TuiAltScreen(this.#
|
|
356
|
-
: new TuiMainScreen(this.#
|
|
365
|
+
? new TuiAltScreen(this.#tuiTerminal, hardwareCursor, this.#logDirectory, this.#tuiOptions)
|
|
366
|
+
: new TuiMainScreen(this.#tuiTerminal, hardwareCursor, this.#logDirectory);
|
|
367
|
+
}
|
|
368
|
+
#routePreInput(data) {
|
|
369
|
+
let current = data;
|
|
370
|
+
for (const listener of this.#preInputListeners) {
|
|
371
|
+
if (current.length === 0)
|
|
372
|
+
break;
|
|
373
|
+
const result = listener(current);
|
|
374
|
+
if (result?.data !== undefined)
|
|
375
|
+
current = result.data;
|
|
376
|
+
else if (result?.consume === true)
|
|
377
|
+
current = "";
|
|
378
|
+
}
|
|
379
|
+
return current;
|
|
357
380
|
}
|
|
358
381
|
#mountTui(tui) {
|
|
359
382
|
tui.addChild(this.#rootBridge);
|
|
@@ -458,11 +481,37 @@ export class PiTuiRuntimeAdapter {
|
|
|
458
481
|
for (const remove of this.#inputListeners.values())
|
|
459
482
|
remove();
|
|
460
483
|
this.#inputListeners.clear();
|
|
484
|
+
this.#preInputListeners.clear();
|
|
461
485
|
for (const dispose of [...this.#overlayDisposers])
|
|
462
486
|
dispose();
|
|
463
487
|
this.#disposeRoot();
|
|
464
488
|
}
|
|
465
489
|
}
|
|
490
|
+
function preInputTerminal(terminal, route) {
|
|
491
|
+
return {
|
|
492
|
+
get columns() { return terminal.columns; },
|
|
493
|
+
get rows() { return terminal.rows; },
|
|
494
|
+
get kittyProtocolActive() { return terminal.kittyProtocolActive; },
|
|
495
|
+
start(onInput, onResize) {
|
|
496
|
+
terminal.start(data => {
|
|
497
|
+
const routed = route(data);
|
|
498
|
+
if (routed.length > 0)
|
|
499
|
+
onInput(routed);
|
|
500
|
+
}, onResize);
|
|
501
|
+
},
|
|
502
|
+
stop: () => terminal.stop(),
|
|
503
|
+
drainInput: (maxMs, idleMs) => terminal.drainInput(maxMs, idleMs),
|
|
504
|
+
write: data => terminal.write(data),
|
|
505
|
+
moveBy: lines => terminal.moveBy(lines),
|
|
506
|
+
hideCursor: () => terminal.hideCursor(),
|
|
507
|
+
showCursor: () => terminal.showCursor(),
|
|
508
|
+
clearLine: () => terminal.clearLine(),
|
|
509
|
+
clearFromCursor: () => terminal.clearFromCursor(),
|
|
510
|
+
clearScreen: () => terminal.clearScreen(),
|
|
511
|
+
setTitle: title => terminal.setTitle(title),
|
|
512
|
+
setProgress: active => terminal.setProgress(active),
|
|
513
|
+
};
|
|
514
|
+
}
|
|
466
515
|
function toOverlayOptions(options) {
|
|
467
516
|
if (options === undefined)
|
|
468
517
|
return undefined;
|
|
@@ -71,6 +71,8 @@ export interface PiTuiInputListenerResult {
|
|
|
71
71
|
readonly data?: string;
|
|
72
72
|
}
|
|
73
73
|
export type PiTuiInputListener = (data: string) => PiTuiInputListenerResult | undefined;
|
|
74
|
+
/** Runs at the terminal bridge before Pi TUI receives physical input. */
|
|
75
|
+
export type PiTuiPreInputListener = PiTuiInputListener;
|
|
74
76
|
export interface PiTuiLayoutEntry {
|
|
75
77
|
readonly node: PiTuiLayoutNode;
|
|
76
78
|
readonly basis?: number | "auto";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "darwin",
|
|
6
6
|
"architecture": "arm64",
|
|
7
7
|
"capability": "unsupported",
|
|
8
|
-
"builtAt": "2026-08-
|
|
8
|
+
"builtAt": "2026-08-27T09:31:38.156Z",
|
|
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-
|
|
8
|
+
"builtAt": "2026-08-27T09:31:34.074Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "29e22fe29de2828982bc67ef418c4adcaab1490281477b7bea592ec6fe621bcd",
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"platform": "win32",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-08-
|
|
8
|
+
"builtAt": "2026-08-27T09:32:08.840Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "41b1b470549052c30109bdbd3187693ce4de9236295d729acc3a1204a2687025",
|
|
12
12
|
"size": 172544
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
declare const ENVIRONMENT_KEYS: readonly ["certificationTarball", "configDir", "dataDir", "databasePath", "developmentInstanceId", "developmentRoot", "endpoint", "fixture", "fixtureInput", "fixtureToken", "inputAcknowledgement", "internalPackaging", "
|
|
1
|
+
declare const ENVIRONMENT_KEYS: readonly ["certificationTarball", "configDir", "dataDir", "databasePath", "developmentInstanceId", "developmentRoot", "endpoint", "fixture", "fixtureInput", "fixtureToken", "inputAcknowledgement", "internalPackaging", "launchProfile", "nativePi", "paneId", "piParityIntentionalMutation", "piPortRoot", "piSourceLedgerPath", "piSourceScanRoot", "probeTrace", "processGuardianPath", "profileHome", "protocolVersion", "releaseDigest", "releaseId", "releaseRoot", "releaseRunnerLabel", "runtimeDir", "structuredFlowLimits", "terminalArgumentsJson", "terminalExecutable", "terminalSessionId"];
|
|
2
2
|
declare const FILESYSTEM_KEYS: readonly ["slug", "windowsDirectory", "unixDirectory", "temporaryPrefix"];
|
|
3
|
-
declare const STATE_KEYS: readonly ["windowsControlDirectory", "unixControlDirectory", "developmentDirectory", "piAgentProfile", "piVanillaProfile"
|
|
3
|
+
declare const STATE_KEYS: readonly ["windowsControlDirectory", "unixControlDirectory", "developmentDirectory", "piAgentProfile", "piVanillaProfile"];
|
|
4
4
|
declare const ENDPOINT_KEYS: readonly ["windowsPipeStem", "unixSocketFilename", "metadataFilename", "supervisorLogFilename", "databaseFilename"];
|
|
5
5
|
declare const MANIFEST_KEYS: readonly ["releaseFilename", "packageFilename"];
|
|
6
6
|
declare const PROTOCOL_KEYS: readonly ["namespace", "controlEnvelope", "supervisorSchema", "nativeHostSchema", "structuredAgentSchema", "controlStoreSchema", "releaseCohortSchema", "updateJournalSchema"];
|
package/dist/product-identity.js
CHANGED
|
@@ -12,7 +12,6 @@ const ENVIRONMENT_KEYS = [
|
|
|
12
12
|
"fixtureToken",
|
|
13
13
|
"inputAcknowledgement",
|
|
14
14
|
"internalPackaging",
|
|
15
|
-
"launchArgumentsJson",
|
|
16
15
|
"launchProfile",
|
|
17
16
|
"nativePi",
|
|
18
17
|
"paneId",
|
|
@@ -35,7 +34,7 @@ const ENVIRONMENT_KEYS = [
|
|
|
35
34
|
"terminalSessionId",
|
|
36
35
|
];
|
|
37
36
|
const FILESYSTEM_KEYS = ["slug", "windowsDirectory", "unixDirectory", "temporaryPrefix"];
|
|
38
|
-
const STATE_KEYS = ["windowsControlDirectory", "unixControlDirectory", "developmentDirectory", "piAgentProfile", "piVanillaProfile"
|
|
37
|
+
const STATE_KEYS = ["windowsControlDirectory", "unixControlDirectory", "developmentDirectory", "piAgentProfile", "piVanillaProfile"];
|
|
39
38
|
const ENDPOINT_KEYS = ["windowsPipeStem", "unixSocketFilename", "metadataFilename", "supervisorLogFilename", "databaseFilename"];
|
|
40
39
|
const MANIFEST_KEYS = ["releaseFilename", "packageFilename"];
|
|
41
40
|
const PROTOCOL_KEYS = ["namespace", "controlEnvelope", "supervisorSchema", "nativeHostSchema", "structuredAgentSchema", "controlStoreSchema", "releaseCohortSchema", "updateJournalSchema"];
|
|
@@ -64,8 +63,8 @@ export function validateProductIdentity(value) {
|
|
|
64
63
|
throw new TypeError("product display name must be non-empty without surrounding whitespace");
|
|
65
64
|
if (schema !== `${commandName}-product-identity-v1`)
|
|
66
65
|
throw new TypeError("product identity schema must derive from the command name");
|
|
67
|
-
if (filesystem.slug !== commandName || filesystem.unixDirectory !== filesystem.slug || filesystem.windowsDirectory !==
|
|
68
|
-
throw new TypeError("product filesystem identity must derive from
|
|
66
|
+
if (filesystem.slug !== commandName || filesystem.unixDirectory !== filesystem.slug || filesystem.windowsDirectory !== filesystem.slug) {
|
|
67
|
+
throw new TypeError("product filesystem identity must derive from the command name");
|
|
69
68
|
}
|
|
70
69
|
if (filesystem.temporaryPrefix !== `${filesystem.slug}-`)
|
|
71
70
|
throw new TypeError("product temporary prefix must derive from the filesystem slug");
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"packageName": "@timurproko/a1",
|
|
6
6
|
"filesystem": {
|
|
7
7
|
"slug": "a1",
|
|
8
|
-
"windowsDirectory": "
|
|
8
|
+
"windowsDirectory": "a1",
|
|
9
9
|
"unixDirectory": "a1",
|
|
10
10
|
"temporaryPrefix": "a1-"
|
|
11
11
|
},
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
"fixtureToken": "A1_FIXTURE_TOKEN",
|
|
23
23
|
"inputAcknowledgement": "A1_INPUT_ACK",
|
|
24
24
|
"internalPackaging": "A1_INTERNAL_PACKAGING",
|
|
25
|
-
"launchArgumentsJson": "A1_LAUNCH_ARGUMENTS_JSON",
|
|
26
25
|
"launchProfile": "A1_LAUNCH_PROFILE",
|
|
27
26
|
"nativePi": "A1_NATIVE_PI",
|
|
28
27
|
"paneId": "A1_PANE_ID",
|
|
@@ -45,12 +44,11 @@
|
|
|
45
44
|
"terminalSessionId": "A1_TERMINAL_SESSION_ID"
|
|
46
45
|
},
|
|
47
46
|
"state": {
|
|
48
|
-
"windowsControlDirectory": "
|
|
47
|
+
"windowsControlDirectory": "a1",
|
|
49
48
|
"unixControlDirectory": "a1",
|
|
50
49
|
"developmentDirectory": "a1-development",
|
|
51
50
|
"piAgentProfile": ".a1/agent",
|
|
52
|
-
"piVanillaProfile": ".pi/agent"
|
|
53
|
-
"piSandboxProfile": ".a1/sandbox"
|
|
51
|
+
"piVanillaProfile": ".pi/agent"
|
|
54
52
|
},
|
|
55
53
|
"endpoint": {
|
|
56
54
|
"windowsPipeStem": "a1",
|
|
@@ -11,8 +11,11 @@ export * from "./scrollbar.js";
|
|
|
11
11
|
export * from "./shortcuts.js";
|
|
12
12
|
export * from "./spans.js";
|
|
13
13
|
export * from "./status-line.js";
|
|
14
|
+
export * from "./submitted-prompt.js";
|
|
14
15
|
export * from "./stepper.js";
|
|
16
|
+
export * from "./transcript-viewport.js";
|
|
15
17
|
export * from "./surface.js";
|
|
16
18
|
export * from "./text.js";
|
|
19
|
+
export * from "./text-selection.js";
|
|
17
20
|
export * from "./theme.js";
|
|
18
21
|
export * from "./value-menu.js";
|
|
@@ -11,8 +11,11 @@ export * from "./scrollbar.js";
|
|
|
11
11
|
export * from "./shortcuts.js";
|
|
12
12
|
export * from "./spans.js";
|
|
13
13
|
export * from "./status-line.js";
|
|
14
|
+
export * from "./submitted-prompt.js";
|
|
14
15
|
export * from "./stepper.js";
|
|
16
|
+
export * from "./transcript-viewport.js";
|
|
15
17
|
export * from "./surface.js";
|
|
16
18
|
export * from "./text.js";
|
|
19
|
+
export * from "./text-selection.js";
|
|
17
20
|
export * from "./theme.js";
|
|
18
21
|
export * from "./value-menu.js";
|
|
@@ -13,3 +13,13 @@ export interface ParsedMouseInput {
|
|
|
13
13
|
}
|
|
14
14
|
/** Extracts SGR mouse reports, leaving any keyboard input untouched. */
|
|
15
15
|
export declare function parseMouseInput(data: string): ParsedMouseInput;
|
|
16
|
+
export interface RoutedMouseInput {
|
|
17
|
+
/** Input left for the TUI after only claimed reports were removed. */
|
|
18
|
+
readonly data: string;
|
|
19
|
+
readonly consumed: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Routes every SGR report independently, preserving keyboard bytes and mouse
|
|
23
|
+
* reports the caller does not claim even when they share one physical chunk.
|
|
24
|
+
*/
|
|
25
|
+
export declare function routeMouseInput(data: string, claim: (event: PaneMouseEvent) => boolean): RoutedMouseInput;
|
|
@@ -30,6 +30,29 @@ export function parseMouseInput(data) {
|
|
|
30
30
|
rest += data.slice(index);
|
|
31
31
|
return { events: Object.freeze(events), rest };
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Routes every SGR report independently, preserving keyboard bytes and mouse
|
|
35
|
+
* reports the caller does not claim even when they share one physical chunk.
|
|
36
|
+
*/
|
|
37
|
+
export function routeMouseInput(data, claim) {
|
|
38
|
+
if (!data.includes("\u001b[<"))
|
|
39
|
+
return { data, consumed: false };
|
|
40
|
+
let output = "";
|
|
41
|
+
let index = 0;
|
|
42
|
+
let consumed = false;
|
|
43
|
+
SGR_PATTERN.lastIndex = 0;
|
|
44
|
+
for (let match = SGR_PATTERN.exec(data); match !== null; match = SGR_PATTERN.exec(data)) {
|
|
45
|
+
output += data.slice(index, match.index);
|
|
46
|
+
index = match.index + match[0].length;
|
|
47
|
+
const event = toEvent(Number.parseInt(match[1] ?? "", 10), Number.parseInt(match[2] ?? "", 10), Number.parseInt(match[3] ?? "", 10), match[4] === "m");
|
|
48
|
+
if (event !== null && claim(event))
|
|
49
|
+
consumed = true;
|
|
50
|
+
else
|
|
51
|
+
output += match[0];
|
|
52
|
+
}
|
|
53
|
+
output += data.slice(index);
|
|
54
|
+
return { data: output, consumed };
|
|
55
|
+
}
|
|
33
56
|
function toEvent(code, column, row, released) {
|
|
34
57
|
if ((code & 64) !== 0) {
|
|
35
58
|
const kind = (code & 1) === 0 ? "wheel-up" : "wheel-down";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface TimestampedPromptRowsInput {
|
|
2
|
+
readonly width: number;
|
|
3
|
+
readonly sourceTimestamp: number | Date;
|
|
4
|
+
readonly render: (contentWidth: number) => readonly string[];
|
|
5
|
+
/** Styles the reserved suffix while keeping policy independent of a vendor theme. */
|
|
6
|
+
readonly decorateSuffix?: (text: string, firstRow: boolean) => string;
|
|
7
|
+
readonly minimumWidth?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function formatPromptTimestamp(source: number | Date): string | null;
|
|
10
|
+
/**
|
|
11
|
+
* Reserves timestamp columns before wrapping, so the clock never overwrites
|
|
12
|
+
* prompt content. At narrow widths the same renderer is used without a clock.
|
|
13
|
+
*/
|
|
14
|
+
export declare function composeTimestampedPromptRows(input: TimestampedPromptRowsInput): readonly string[];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { overlaySpan } from "./spans.js";
|
|
2
|
+
export function formatPromptTimestamp(source) {
|
|
3
|
+
const date = source instanceof Date ? source : new Date(source);
|
|
4
|
+
if (Number.isNaN(date.getTime()))
|
|
5
|
+
return null;
|
|
6
|
+
return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Reserves timestamp columns before wrapping, so the clock never overwrites
|
|
10
|
+
* prompt content. At narrow widths the same renderer is used without a clock.
|
|
11
|
+
*/
|
|
12
|
+
export function composeTimestampedPromptRows(input) {
|
|
13
|
+
const timestamp = formatPromptTimestamp(input.sourceTimestamp);
|
|
14
|
+
if (timestamp === null || input.width < (input.minimumWidth ?? 20))
|
|
15
|
+
return input.render(input.width);
|
|
16
|
+
const suffixWidth = timestamp.length + 2;
|
|
17
|
+
const contentWidth = Math.max(1, input.width - suffixWidth);
|
|
18
|
+
const decorate = input.decorateSuffix ?? ((text) => text);
|
|
19
|
+
return input.render(contentWidth).map((row, index) => overlaySpan(row, contentWidth, input.width, decorate(index === 0 ? ` ${timestamp}` : " ".repeat(suffixWidth), index === 0)));
|
|
20
|
+
}
|