@timurproko/a1 0.1.8-dev.137 → 0.1.8-dev.151
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 +26 -5
- package/dist/composition/settings-route-host.d.ts +1 -1
- package/dist/composition/settings-route-host.js +1 -1
- package/dist/contracts/owned-ui/model.d.ts +28 -0
- package/dist/contracts/owned-ui/validation.js +14 -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 +38 -14
- 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 +1 -1
- package/dist/foundation/storage/control-store.js +37 -3
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +55 -0
- package/dist/integrations/pi/components/owned-editor-ux.js +778 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +44 -6
- 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/shell-shared-facade.d.ts +41 -7
- package/dist/integrations/pi/components/shell-shared-facade.js +3 -1
- 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/components/upstream/adjacent/core/keybindings.d.ts +10 -2
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +36 -3
- package/dist/integrations/pi/engine/adapter.js +21 -3
- package/dist/integrations/pi/engine/session-integration.d.ts +2 -1
- package/dist/integrations/pi/engine/session-integration.js +17 -3
- package/dist/integrations/pi/session-ui/prompt-chips.d.ts +23 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +242 -0
- package/dist/integrations/pi/session-ui/route-host.d.ts +5 -0
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell-root.d.ts +34 -5
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell-root.js +294 -45
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell.d.ts +1 -1
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell.js +167 -24
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +39 -0
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +369 -0
- package/dist/integrations/pi/session-ui/system-clipboard.d.ts +11 -0
- package/dist/integrations/pi/session-ui/system-clipboard.js +101 -0
- 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 +4 -6
- 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 +3 -3
- 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/apps/contracts.d.ts +23 -1
- package/dist/ui/apps/host.js +9 -1
- 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 +21 -0
- package/dist/ui/components/spans.js +178 -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 +104 -0
- package/dist/ui/components/transcript-viewport.js +381 -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 +21 -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 +3 -3
- package/docs/architecture/project-structure.md +2 -2
- 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 +4 -2
- package/dist/features/launch/profiles.d.ts +0 -12
- package/dist/features/launch/profiles.js +0 -26
- package/dist/integrations/pi/owned-ui/route-host.d.ts +0 -29
- /package/dist/integrations/pi/{owned-ui → session-ui}/index.d.ts +0 -0
- /package/dist/integrations/pi/{owned-ui → session-ui}/index.js +0 -0
- /package/dist/integrations/pi/{owned-ui → session-ui}/route-host.js +0 -0
|
@@ -2,6 +2,7 @@ import { MOUSE_TRACKING_OFF, MOUSE_TRACKING_ON, parseMouseInput } from "../../..
|
|
|
2
2
|
import { PINNED_PI_HIDDEN_COMMAND_NAMES, PINNED_PI_WORKFLOW_COMMAND_NAMES, } from "../engine/index.js";
|
|
3
3
|
import { createPiExtensionUiBridge, createPiQueuedInputStatus, createPiShellArmin, createPiShellAuthProviderSelector, createPiShellChangelog, createPiShellDaxnuts, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellEditor, createPiShellExtensionSelector, createPiShellFooter, createPiShellHeader, createPiShellHotkeys, createPiShellLoadedResources, createPiShellLoginDialog, createPiShellModelSelector, createPiShellOperationLoader, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionInfo, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellStatus, createPiShellTranscriptComponent, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, onPiThemeChange, piTheme, renderPiShellPackageUpdateNotice, renderPiShellStartupDiagnostic, renderPiShellStatusText, renderPiShellTranscriptBlock, } from "../components/index.js";
|
|
4
4
|
import { PiTuiRuntimeAdapter, } from "../tui-runtime/index.js";
|
|
5
|
+
import { preloadSystemClipboard, readSystemClipboardContent, writeSystemClipboardText, } from "./system-clipboard.js";
|
|
5
6
|
import { OwnedUiSessionShellRoot, shellResourceEntries, } from "./session-shell-root.js";
|
|
6
7
|
export { OwnedUiSessionShellRoot } from "./session-shell-root.js";
|
|
7
8
|
export class OwnedUiSessionShell {
|
|
@@ -15,14 +16,19 @@ export class OwnedUiSessionShell {
|
|
|
15
16
|
#stopped;
|
|
16
17
|
#resolveStopped;
|
|
17
18
|
#dialogId;
|
|
19
|
+
#dialogSource;
|
|
18
20
|
#routeHost;
|
|
19
21
|
#dialogHandle;
|
|
20
22
|
#sequence = 0;
|
|
21
23
|
#started = false;
|
|
22
24
|
#disposed = false;
|
|
23
25
|
#pointerReporting = false;
|
|
26
|
+
#customViewport;
|
|
27
|
+
#removeViewportPreInput;
|
|
28
|
+
#unsubscribeSettings;
|
|
24
29
|
#compactionQueue = [];
|
|
25
30
|
#lastClearTime = 0;
|
|
31
|
+
#lastEscapeTime = 0;
|
|
26
32
|
#activeLoginDialog;
|
|
27
33
|
#sessionGeneration;
|
|
28
34
|
constructor(options) {
|
|
@@ -30,14 +36,18 @@ export class OwnedUiSessionShell {
|
|
|
30
36
|
this.#sessionGeneration = this.backend.sessionGeneration;
|
|
31
37
|
this.#cwd = options.cwd;
|
|
32
38
|
this.#routeHost = options.routeHost ?? null;
|
|
39
|
+
this.#customViewport = options.sessionLayout === "custom-viewport";
|
|
40
|
+
if (this.#customViewport && options.clipboard === undefined)
|
|
41
|
+
preloadSystemClipboard();
|
|
33
42
|
this.#stopped = new Promise(resolve => {
|
|
34
43
|
this.#resolveStopped = resolve;
|
|
35
44
|
});
|
|
36
45
|
let runtime;
|
|
46
|
+
let pendingClipboardWrite = Promise.resolve();
|
|
37
47
|
this.root = new OwnedUiSessionShellRoot(this.backend.view(), options.cwd, {
|
|
38
48
|
getColumns: () => runtime?.viewport().columns ?? options.terminal?.columns ?? 80,
|
|
39
49
|
getRows: () => runtime?.viewport().rows ?? options.terminal?.rows ?? 24,
|
|
40
|
-
requestRender:
|
|
50
|
+
requestRender: force => runtime?.requestRender(force),
|
|
41
51
|
onSubmit: text => { void this.submit(text); },
|
|
42
52
|
onInterrupt: () => { void this.interrupt(); },
|
|
43
53
|
onClear: () => { void this.clearOrExit(); },
|
|
@@ -52,19 +62,73 @@ export class OwnedUiSessionShell {
|
|
|
52
62
|
onMessageCopy: () => { void this.runWorkflow({ command: "copy", argument: "" }); },
|
|
53
63
|
onFollowUp: () => { void this.queueFollowUp(); },
|
|
54
64
|
onDequeue: () => this.restoreQueuedInput(),
|
|
65
|
+
onCopyText: text => {
|
|
66
|
+
runtime?.writeControl(`\u001b]52;c;${Buffer.from(text, "utf8").toString("base64")}\u0007`);
|
|
67
|
+
const write = options.clipboard === undefined
|
|
68
|
+
? writeSystemClipboardText(text)
|
|
69
|
+
: options.clipboard.writeText?.(text) ?? Promise.resolve();
|
|
70
|
+
pendingClipboardWrite = write.catch(() => { });
|
|
71
|
+
},
|
|
72
|
+
readClipboardContent: async () => {
|
|
73
|
+
await pendingClipboardWrite;
|
|
74
|
+
if (options.clipboard === undefined)
|
|
75
|
+
return readSystemClipboardContent();
|
|
76
|
+
const image = await options.clipboard.readImage?.();
|
|
77
|
+
if (image !== null && image !== undefined)
|
|
78
|
+
return { kind: "image", ...image };
|
|
79
|
+
const text = await options.clipboard.readText();
|
|
80
|
+
return text === null ? null : { kind: "text", text };
|
|
81
|
+
},
|
|
55
82
|
}, {
|
|
56
83
|
...options.startup,
|
|
57
84
|
resources: options.startup?.resources ?? shellResourceEntries(this.backend),
|
|
58
85
|
}, this.backend.agentDir, {
|
|
59
86
|
getMessageRenderer: customType => this.backend.pinnedMessageRenderer(customType),
|
|
60
87
|
getToolDefinition: toolName => this.backend.pinnedToolDefinition(toolName),
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
88
|
+
}, options.sessionLayout);
|
|
89
|
+
// Bare A1 owns a bounded viewport and therefore always runs on the alternate
|
|
90
|
+
// fullscreen surface. The pinned comparison profiles still honor Pi's mode.
|
|
91
|
+
const tuiMode = this.#customViewport
|
|
92
|
+
? "fullscreen"
|
|
93
|
+
: this.backend.disposed ? "regular" : this.backend.pinnedSettingsSnapshot().tuiMode;
|
|
94
|
+
const runtimeOptions = {
|
|
95
|
+
root: this.root,
|
|
96
|
+
mode: tuiMode,
|
|
97
|
+
...(this.#customViewport ? {} : { layoutRoot: this.root.layoutRoot() }),
|
|
98
|
+
...(options.terminal === undefined ? {} : { terminal: options.terminal }),
|
|
99
|
+
hardwareCursor: this.backend.view().terminal.hardwareCursor,
|
|
100
|
+
};
|
|
66
101
|
runtime = new PiTuiRuntimeAdapter(runtimeOptions);
|
|
67
102
|
this.runtime = runtime;
|
|
103
|
+
this.#removeViewportPreInput = this.#customViewport
|
|
104
|
+
? this.runtime.addPreInputListener(data => {
|
|
105
|
+
// An overlay or editor-replacement screen owns its entire pointer
|
|
106
|
+
// surface. Letting the transcript pre-router inspect those reports
|
|
107
|
+
// steals settings value menus and numeric +/- controls before the
|
|
108
|
+
// settings app can receive them.
|
|
109
|
+
if (this.runtime.hasOverlay() || !this.root.usesDefaultInputSurface())
|
|
110
|
+
return undefined;
|
|
111
|
+
const routed = this.root.handleViewportPreInput(data, true);
|
|
112
|
+
if (routed.copyText !== undefined) {
|
|
113
|
+
this.runtime.writeControl(`\u001b]52;c;${Buffer.from(routed.copyText, "utf8").toString("base64")}\u0007`);
|
|
114
|
+
}
|
|
115
|
+
if (!routed.consumed)
|
|
116
|
+
return routed.data === data ? undefined : { data: routed.data };
|
|
117
|
+
return routed.data.length === 0 ? { consume: true } : { data: routed.data };
|
|
118
|
+
})
|
|
119
|
+
: () => { };
|
|
120
|
+
const applyViewportSettings = () => {
|
|
121
|
+
const snapshot = options.viewportSettings?.snapshot();
|
|
122
|
+
this.root.setViewportConfig(snapshot ?? {
|
|
123
|
+
scrollbarAppearance: "auto",
|
|
124
|
+
scrollbarStyle: "thin",
|
|
125
|
+
scrollbarSpeed: "normal",
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
applyViewportSettings();
|
|
129
|
+
this.#unsubscribeSettings = this.#customViewport && options.viewportSettings
|
|
130
|
+
? options.viewportSettings.onChange(settings => this.root.setViewportConfig(settings))
|
|
131
|
+
: () => { };
|
|
68
132
|
this.#extensionBridge = createPiExtensionUiBridge({
|
|
69
133
|
runtime: {
|
|
70
134
|
getColumns: () => this.runtime.viewport().columns,
|
|
@@ -102,9 +166,14 @@ export class OwnedUiSessionShell {
|
|
|
102
166
|
// A streamed chunk names one block, and touching only that block is what keeps the
|
|
103
167
|
// cost of a chunk the same in a long session as in a new one. Everything else
|
|
104
168
|
// resynchronizes the view, which is cheap next to re-reading the transcript.
|
|
169
|
+
if (event.type === "agent-run-started")
|
|
170
|
+
this.root.resumeViewportFollowing();
|
|
171
|
+
if (event.type === "assistant-message-completed")
|
|
172
|
+
this.root.noteCompletedAssistantMessage();
|
|
173
|
+
const semanticOnly = event.type === "agent-run-started" || event.type === "assistant-message-completed";
|
|
105
174
|
const view = event.type === "transcript-block" && this.#sessionGeneration === this.backend.sessionGeneration
|
|
106
175
|
? this.#syncBlock(event.block)
|
|
107
|
-
: this.#syncView();
|
|
176
|
+
: semanticOnly ? this.view() : this.#syncView();
|
|
108
177
|
if (view.lifecycle === "ready" && this.#compactionQueue.length > 0)
|
|
109
178
|
void this.#flushCompactionQueue();
|
|
110
179
|
if (event.type === "session-lifecycle" && event.lifecycle === "stopped")
|
|
@@ -121,6 +190,8 @@ export class OwnedUiSessionShell {
|
|
|
121
190
|
return;
|
|
122
191
|
this.#started = true;
|
|
123
192
|
this.runtime.start();
|
|
193
|
+
if (this.#customViewport)
|
|
194
|
+
this.#setPointerReporting(true);
|
|
124
195
|
void this.backend.bindExtensionUi(this.#extensionBridge.context, () => { void this.shutdown(); });
|
|
125
196
|
this.#syncView();
|
|
126
197
|
}
|
|
@@ -133,16 +204,18 @@ export class OwnedUiSessionShell {
|
|
|
133
204
|
return this.#stopped;
|
|
134
205
|
}
|
|
135
206
|
async submit(text) {
|
|
136
|
-
const
|
|
137
|
-
if (!
|
|
207
|
+
const displayInput = text.trim();
|
|
208
|
+
if (!displayInput)
|
|
138
209
|
return { outcome: "completed", diagnostic: null };
|
|
139
|
-
if (
|
|
140
|
-
return this.#slashCommand(
|
|
210
|
+
if (displayInput.startsWith("/"))
|
|
211
|
+
return this.#slashCommand(displayInput);
|
|
212
|
+
const prepared = this.root.preparePromptSubmission(displayInput);
|
|
213
|
+
const input = prepared.text.trim();
|
|
141
214
|
if (input.startsWith("!")) {
|
|
142
215
|
const excludeFromContext = input.startsWith("!!");
|
|
143
216
|
const command = input.slice(excludeFromContext ? 2 : 1).trim();
|
|
144
217
|
if (command) {
|
|
145
|
-
this.root.editor.addToHistory(
|
|
218
|
+
this.root.editor.addToHistory(displayInput);
|
|
146
219
|
try {
|
|
147
220
|
const result = await this.backend.executeBashWorkflow(command, excludeFromContext);
|
|
148
221
|
const workflow = {
|
|
@@ -164,19 +237,21 @@ export class OwnedUiSessionShell {
|
|
|
164
237
|
}
|
|
165
238
|
}
|
|
166
239
|
if (this.view().status.workingMessage?.startsWith("Compacting") === true) {
|
|
167
|
-
this.root.editor.addToHistory(
|
|
240
|
+
this.root.editor.addToHistory(displayInput);
|
|
168
241
|
this.#compactionQueue.push({ text: input, type: "steer" });
|
|
169
242
|
this.root.appendWorkflowResult({ command: "compact", outcome: "completed", message: `Queued during compaction: ${input}` });
|
|
170
243
|
this.runtime.requestRender();
|
|
171
244
|
return { outcome: "completed", diagnostic: null };
|
|
172
245
|
}
|
|
173
246
|
const type = this.view().lifecycle === "busy" ? "steer" : "prompt";
|
|
174
|
-
this.root.editor.addToHistory(
|
|
247
|
+
this.root.editor.addToHistory(displayInput);
|
|
248
|
+
this.root.resumeViewportFollowing();
|
|
175
249
|
return this.#execute({
|
|
176
250
|
type,
|
|
177
251
|
correlationId: this.#correlation(type),
|
|
178
252
|
sessionId: this.backend.sessionId,
|
|
179
253
|
text: input,
|
|
254
|
+
...(prepared.images.length === 0 ? {} : { images: prepared.images }),
|
|
180
255
|
});
|
|
181
256
|
}
|
|
182
257
|
async clearOrExit(now = Date.now()) {
|
|
@@ -187,14 +262,29 @@ export class OwnedUiSessionShell {
|
|
|
187
262
|
this.runtime.requestRender();
|
|
188
263
|
return { outcome: "completed", diagnostic: null };
|
|
189
264
|
}
|
|
190
|
-
async interrupt() {
|
|
265
|
+
async interrupt(now = Date.now()) {
|
|
191
266
|
if (this.view().lifecycle === "busy")
|
|
192
267
|
return this.abort();
|
|
193
|
-
if (this.root.editor.getText().length > 0) {
|
|
268
|
+
if (this.root.editor.getText().trim().length > 0) {
|
|
194
269
|
this.root.editor.setText("");
|
|
195
270
|
return { outcome: "completed", diagnostic: null };
|
|
196
271
|
}
|
|
197
|
-
|
|
272
|
+
// Match Pi: with an empty editor, two escapes inside 500 ms open the
|
|
273
|
+
// configured session navigator. The first escape intentionally does not
|
|
274
|
+
// interrupt or mutate the prompt.
|
|
275
|
+
const action = this.backend.pinnedSettingsSnapshot().doubleEscapeAction;
|
|
276
|
+
if (action === "none")
|
|
277
|
+
return { outcome: "rejected", diagnostic: "nothing to interrupt" };
|
|
278
|
+
if (now - this.#lastEscapeTime < 500) {
|
|
279
|
+
this.#lastEscapeTime = 0;
|
|
280
|
+
if (action === "tree")
|
|
281
|
+
this.showTreeSelector();
|
|
282
|
+
else
|
|
283
|
+
this.showForkSelector();
|
|
284
|
+
return { outcome: "completed", diagnostic: null };
|
|
285
|
+
}
|
|
286
|
+
this.#lastEscapeTime = now;
|
|
287
|
+
return { outcome: "completed", diagnostic: null };
|
|
198
288
|
}
|
|
199
289
|
async abort() {
|
|
200
290
|
return this.#execute(this.#simple("abort"));
|
|
@@ -251,6 +341,7 @@ export class OwnedUiSessionShell {
|
|
|
251
341
|
return rejected("nothing to queue");
|
|
252
342
|
this.root.editor.addToHistory(text);
|
|
253
343
|
this.root.editor.setText("");
|
|
344
|
+
this.root.resumeViewportFollowing();
|
|
254
345
|
if (this.view().status.workingMessage?.startsWith("Compacting") === true) {
|
|
255
346
|
this.#compactionQueue.push({ text, type: "follow-up" });
|
|
256
347
|
return { outcome: "completed", diagnostic: null };
|
|
@@ -272,6 +363,8 @@ export class OwnedUiSessionShell {
|
|
|
272
363
|
}
|
|
273
364
|
showSelector(title, options, onSelect, onCancel) {
|
|
274
365
|
this.#dialogHandle?.hide();
|
|
366
|
+
this.#dialogSource = "local";
|
|
367
|
+
this.#dialogId = undefined;
|
|
275
368
|
const component = createPiShellSelector({
|
|
276
369
|
title,
|
|
277
370
|
options,
|
|
@@ -279,10 +372,12 @@ export class OwnedUiSessionShell {
|
|
|
279
372
|
onSelect(id);
|
|
280
373
|
this.#dialogHandle?.hide();
|
|
281
374
|
this.#dialogHandle = undefined;
|
|
375
|
+
this.#dialogSource = undefined;
|
|
282
376
|
},
|
|
283
377
|
onCancel: () => {
|
|
284
378
|
this.#dialogHandle?.hide();
|
|
285
379
|
this.#dialogHandle = undefined;
|
|
380
|
+
this.#dialogSource = undefined;
|
|
286
381
|
onCancel?.();
|
|
287
382
|
},
|
|
288
383
|
});
|
|
@@ -486,6 +581,10 @@ export class OwnedUiSessionShell {
|
|
|
486
581
|
return;
|
|
487
582
|
}
|
|
488
583
|
if (callback === "onTuiModeChange") {
|
|
584
|
+
// The custom bare-A1 surface is permanently fullscreen; this callback
|
|
585
|
+
// remains available only to pinned comparison profiles.
|
|
586
|
+
if (this.#customViewport)
|
|
587
|
+
return;
|
|
489
588
|
if (value !== "regular" && value !== "fullscreen")
|
|
490
589
|
return;
|
|
491
590
|
if (!this.runtime.switchMode(value)) {
|
|
@@ -493,6 +592,10 @@ export class OwnedUiSessionShell {
|
|
|
493
592
|
this.runtime.requestRender();
|
|
494
593
|
return;
|
|
495
594
|
}
|
|
595
|
+
if (this.#customViewport) {
|
|
596
|
+
this.#pointerReporting = false;
|
|
597
|
+
this.#setPointerReporting(true);
|
|
598
|
+
}
|
|
496
599
|
}
|
|
497
600
|
const result = this.backend.applyPinnedSettingValue(callback, value);
|
|
498
601
|
if (result.outcome === "failed")
|
|
@@ -704,19 +807,23 @@ export class OwnedUiSessionShell {
|
|
|
704
807
|
* button instead of scrolling and selecting itself, so leaving it on outlives the
|
|
705
808
|
* screen that wanted it and takes the terminal's own scrolling and selection with it.
|
|
706
809
|
*/
|
|
707
|
-
#setPointerReporting(enabled) {
|
|
708
|
-
|
|
810
|
+
#setPointerReporting(enabled, forceOff = false) {
|
|
811
|
+
const effective = forceOff ? false : this.#customViewport || enabled;
|
|
812
|
+
if (this.#pointerReporting === effective)
|
|
709
813
|
return;
|
|
710
|
-
this.#pointerReporting =
|
|
814
|
+
this.#pointerReporting = effective;
|
|
711
815
|
if (!this.runtime.active)
|
|
712
816
|
return;
|
|
713
|
-
this.runtime.writeControl(
|
|
817
|
+
this.runtime.writeControl(effective ? MOUSE_TRACKING_ON : MOUSE_TRACKING_OFF);
|
|
714
818
|
}
|
|
715
819
|
async dispose() {
|
|
716
820
|
if (this.#disposed)
|
|
717
821
|
return;
|
|
718
822
|
this.#disposed = true;
|
|
719
|
-
this
|
|
823
|
+
this.root.clearViewportPointerState();
|
|
824
|
+
this.#setPointerReporting(false, true);
|
|
825
|
+
this.#removeViewportPreInput();
|
|
826
|
+
this.#unsubscribeSettings();
|
|
720
827
|
this.#unsubscribe();
|
|
721
828
|
this.#dialogHandle?.hide();
|
|
722
829
|
await this.backend.unbindExtensionUi();
|
|
@@ -741,7 +848,12 @@ export class OwnedUiSessionShell {
|
|
|
741
848
|
this.#sessionGeneration = this.backend.sessionGeneration;
|
|
742
849
|
this.#activeLoginDialog = undefined;
|
|
743
850
|
this.#extensionBridge.reset();
|
|
744
|
-
// A replaced session takes its
|
|
851
|
+
// A replaced session takes its transient viewport and owned-route state with it.
|
|
852
|
+
this.root.resetViewport();
|
|
853
|
+
this.#dialogHandle?.hide();
|
|
854
|
+
this.#dialogHandle = undefined;
|
|
855
|
+
this.#dialogId = undefined;
|
|
856
|
+
this.#dialogSource = undefined;
|
|
745
857
|
this.#setPointerReporting(false);
|
|
746
858
|
this.root.setInputSurface(null);
|
|
747
859
|
this.root.resetExtensionUi();
|
|
@@ -761,6 +873,7 @@ export class OwnedUiSessionShell {
|
|
|
761
873
|
if (!this.runtime.active)
|
|
762
874
|
return { outcome: "failed", diagnostic: "runtime is not active" };
|
|
763
875
|
this.#dialogHandle?.hide();
|
|
876
|
+
this.#dialogSource = "route";
|
|
764
877
|
// Any-event reporting: hover and drag are what the screen is driven by, and
|
|
765
878
|
// it also stops the terminal treating a drag as a text selection.
|
|
766
879
|
this.#setPointerReporting(true);
|
|
@@ -784,13 +897,32 @@ export class OwnedUiSessionShell {
|
|
|
784
897
|
// stray interrupt while it is up.
|
|
785
898
|
return { consume: true };
|
|
786
899
|
});
|
|
900
|
+
let removeSurfacePreInput = () => { };
|
|
787
901
|
const closeSurface = () => {
|
|
902
|
+
removeSurfacePreInput();
|
|
788
903
|
removeInterruptWatch();
|
|
789
904
|
this.#setPointerReporting(false);
|
|
790
905
|
this.#dialogHandle?.hide();
|
|
791
906
|
this.#dialogHandle = undefined;
|
|
792
907
|
this.#dialogId = undefined;
|
|
908
|
+
this.#dialogSource = undefined;
|
|
793
909
|
};
|
|
910
|
+
// Fullscreen Pi owns a fallback text-selection layer before focused overlay
|
|
911
|
+
// components see pointer input. Route every mouse report to the owned screen
|
|
912
|
+
// at the pre-input boundary so dropdowns, value hover, and numeric +/- work,
|
|
913
|
+
// and consume even unhandled reports so settings content is never selected.
|
|
914
|
+
removeSurfacePreInput = this.runtime.addPreInputListener(data => {
|
|
915
|
+
const { events, rest } = parseMouseInput(data);
|
|
916
|
+
if (events.length === 0)
|
|
917
|
+
return undefined;
|
|
918
|
+
for (const event of events)
|
|
919
|
+
surface.handleMouse(event);
|
|
920
|
+
if (surface.isClosed())
|
|
921
|
+
closeSurface();
|
|
922
|
+
else
|
|
923
|
+
this.runtime.requestRender();
|
|
924
|
+
return rest.length === 0 ? { consume: true } : { data: rest };
|
|
925
|
+
});
|
|
794
926
|
const rows = () => Math.max(1, this.runtime.viewport().rows);
|
|
795
927
|
const component = {
|
|
796
928
|
render: (width) => [...surface.render(Math.max(1, width), rows())],
|
|
@@ -821,22 +953,32 @@ export class OwnedUiSessionShell {
|
|
|
821
953
|
if (!this.runtime.active)
|
|
822
954
|
return;
|
|
823
955
|
if (dialog === null) {
|
|
956
|
+
// Locally owned routes (notably /settings) are independent of backend
|
|
957
|
+
// lifecycle/status events and remain open while an agent is working.
|
|
958
|
+
if (this.#dialogSource !== "backend")
|
|
959
|
+
return;
|
|
824
960
|
this.#dialogHandle?.hide();
|
|
825
961
|
this.#dialogHandle = undefined;
|
|
826
962
|
this.#dialogId = undefined;
|
|
963
|
+
this.#dialogSource = undefined;
|
|
827
964
|
return;
|
|
828
965
|
}
|
|
829
|
-
if (this.#dialogId === dialog.id)
|
|
966
|
+
if (this.#dialogSource === "backend" && this.#dialogId === dialog.id)
|
|
830
967
|
return;
|
|
831
968
|
this.#dialogHandle?.hide();
|
|
969
|
+
this.#dialogSource = "backend";
|
|
832
970
|
const component = createPiShellDialog(dialog, {
|
|
833
971
|
onSelect: () => {
|
|
834
972
|
this.#dialogHandle?.hide();
|
|
835
973
|
this.#dialogHandle = undefined;
|
|
974
|
+
this.#dialogId = undefined;
|
|
975
|
+
this.#dialogSource = undefined;
|
|
836
976
|
},
|
|
837
977
|
onCancel: () => {
|
|
838
978
|
this.#dialogHandle?.hide();
|
|
839
979
|
this.#dialogHandle = undefined;
|
|
980
|
+
this.#dialogId = undefined;
|
|
981
|
+
this.#dialogSource = undefined;
|
|
840
982
|
},
|
|
841
983
|
});
|
|
842
984
|
this.#dialogHandle = this.runtime.showOverlay(component, { width: "70%", maxHeight: "80%", anchor: "center" });
|
|
@@ -853,6 +995,7 @@ export class OwnedUiSessionShell {
|
|
|
853
995
|
return this.runWorkflow({ command: name, argument });
|
|
854
996
|
// Unknown slash input, prompt templates, skills, and extension commands remain Pi prompt input.
|
|
855
997
|
this.root.editor.addToHistory(text);
|
|
998
|
+
this.root.resumeViewportFollowing();
|
|
856
999
|
return this.#execute({
|
|
857
1000
|
type: this.view().lifecycle === "busy" ? "steer" : "prompt",
|
|
858
1001
|
correlationId: this.#correlation("prompt-command"),
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { OwnedUiViewportSettings } from "../../../contracts/owned-ui/index.js";
|
|
2
|
+
import { type TranscriptViewportFrame, type TranscriptViewportFrameInput } from "../../../ui/components/index.js";
|
|
3
|
+
import type { PiShellEditorPort } from "../components/index.js";
|
|
4
|
+
export interface SessionViewportControllerOptions {
|
|
5
|
+
readonly enabled: boolean;
|
|
6
|
+
readonly editor: PiShellEditorPort;
|
|
7
|
+
readonly requestRender: (force?: boolean) => void;
|
|
8
|
+
/** Whether mutable editor URL chips need an immediate forced repaint on deletion. */
|
|
9
|
+
readonly hasEditorLinks?: () => boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface SessionViewportInputResult {
|
|
12
|
+
readonly data: string;
|
|
13
|
+
readonly consumed: boolean;
|
|
14
|
+
readonly copyText?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Owns the stateful interaction policy of the bare-A1 session viewport. The
|
|
18
|
+
* shell root supplies semantic rows and dock geometry; this controller owns
|
|
19
|
+
* follow state, selection, pointer latches, timers, and input routing.
|
|
20
|
+
*/
|
|
21
|
+
export declare class SessionViewportController {
|
|
22
|
+
#private;
|
|
23
|
+
constructor(options: SessionViewportControllerOptions);
|
|
24
|
+
get config(): OwnedUiViewportSettings;
|
|
25
|
+
get editorPointerSelecting(): boolean;
|
|
26
|
+
get transcriptPointerSelecting(): boolean;
|
|
27
|
+
setEditorPointerFrame(frame: {
|
|
28
|
+
readonly rowStart: number;
|
|
29
|
+
readonly rowEnd: number;
|
|
30
|
+
} | undefined): void;
|
|
31
|
+
compose(input: TranscriptViewportFrameInput): TranscriptViewportFrame;
|
|
32
|
+
/** Returns true when document wrapping may have changed. */
|
|
33
|
+
setConfig(config: OwnedUiViewportSettings): boolean;
|
|
34
|
+
resumeFollowing(): void;
|
|
35
|
+
noteCompletedAssistantMessage(): void;
|
|
36
|
+
reset(): void;
|
|
37
|
+
clearPointerState(): void;
|
|
38
|
+
handlePreInput(data: string, allowWheel?: boolean, now?: number): SessionViewportInputResult;
|
|
39
|
+
}
|