@timurproko/a1 0.1.8-dev.151 → 0.1.8-dev.182
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -9
- package/bin/cli.js +1 -0
- package/bin/ui.js +17 -2
- package/dist/cli/dispatch.d.ts +6 -0
- package/dist/cli/dispatch.js +107 -81
- package/dist/cli/version-stats.js +7 -1
- package/dist/composition/owned-ui.d.ts +4 -1
- package/dist/composition/owned-ui.js +8 -5
- package/dist/contracts/agent-engine/capability-ports.d.ts +2 -2
- package/dist/contracts/agent-engine/domain-validation.js +22 -4
- package/dist/contracts/agent-engine/domain.d.ts +17 -0
- package/dist/contracts/owned-ui/model.d.ts +8 -0
- package/dist/contracts/owned-ui/validation.js +11 -0
- package/dist/features/owned-ui/index.d.ts +1 -0
- package/dist/features/owned-ui/index.js +1 -0
- package/dist/features/owned-ui/project-trust-prompt.d.ts +20 -0
- package/dist/features/owned-ui/project-trust-prompt.js +32 -0
- package/dist/features/owned-ui/settings-app.js +37 -9
- package/dist/integrations/pi/components/shell-components.d.ts +1 -0
- package/dist/integrations/pi/components/shell-components.js +1 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +10 -0
- package/dist/integrations/pi/components/shell-footer-status.js +14 -9
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +36 -0
- package/dist/integrations/pi/components/shell-presenters-info.js +78 -0
- package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -36
- package/dist/integrations/pi/components/shell-presenters-transcript.js +88 -128
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +11 -1
- package/dist/integrations/pi/engine/adapter.d.ts +15 -4
- package/dist/integrations/pi/engine/adapter.js +185 -37
- package/dist/integrations/pi/engine/http-dispatcher.d.ts +4 -0
- package/dist/integrations/pi/engine/http-dispatcher.js +25 -0
- package/dist/integrations/pi/engine/index.d.ts +3 -0
- package/dist/integrations/pi/engine/index.js +3 -0
- package/dist/integrations/pi/engine/project-trust-preflight.d.ts +22 -0
- package/dist/integrations/pi/engine/project-trust-preflight.js +50 -0
- package/dist/integrations/pi/engine/runtime-integration.d.ts +16 -1
- package/dist/integrations/pi/engine/runtime-integration.js +34 -4
- package/dist/integrations/pi/engine/settings-effects.d.ts +55 -0
- package/dist/integrations/pi/engine/settings-effects.js +229 -0
- package/dist/integrations/pi/engine/settings-integration.d.ts +14 -26
- package/dist/integrations/pi/engine/settings-integration.js +102 -108
- package/dist/integrations/pi/engine/workflow-controllers.d.ts +1 -1
- package/dist/integrations/pi/session-ui/clipboard-image.d.ts +11 -0
- package/dist/integrations/pi/session-ui/clipboard-image.js +29 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +5 -1
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +9 -2
- package/dist/integrations/pi/session-ui/session-shell-root.js +83 -9
- package/dist/integrations/pi/session-ui/session-shell.d.ts +2 -0
- package/dist/integrations/pi/session-ui/session-shell.js +151 -15
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +34 -2
- package/dist/integrations/pi/session-ui/system-clipboard.d.ts +9 -0
- package/dist/integrations/pi/session-ui/system-clipboard.js +34 -5
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +4 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +28 -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/components/spans.d.ts +2 -0
- package/dist/ui/components/spans.js +25 -0
- package/dist/ui/components/text.js +24 -2
- package/dist/ui/settings/sections.d.ts +8 -19
- package/dist/ui/settings/sections.js +12 -4
- package/dist/ui/settings/session.d.ts +8 -18
- package/dist/ui/settings/session.js +59 -52
- package/docs/architecture/project-structure.md +14 -0
- package/docs/ci-release-runbook.md +75 -9
- package/docs/features/launch-profiles.md +3 -3
- package/docs/repository-governance-live-acceptance.md +77 -0
- package/package.json +5 -2
|
@@ -16,5 +16,5 @@ export declare class PiWorkflowControllerPort implements AgentWorkflowPort {
|
|
|
16
16
|
listWorkflows(): Promise<readonly AgentWorkflowDescriptor[]>;
|
|
17
17
|
executeWorkflow(workflowId: string, input: AgentJsonValue, signal?: AbortSignal): Promise<AgentJsonValue>;
|
|
18
18
|
}
|
|
19
|
-
export declare const PI_BUILTIN_WORKFLOW_ROUTES: readonly ("name" | "share" | "model" | "login" | "logout" | "settings" | "reload" | "compact" | "new" | "resume" | "scoped-models" | "export" | "import" | "copy" | "
|
|
19
|
+
export declare const PI_BUILTIN_WORKFLOW_ROUTES: readonly ("name" | "share" | "model" | "login" | "logout" | "settings" | "reload" | "compact" | "new" | "resume" | "session" | "scoped-models" | "export" | "import" | "copy" | "changelog" | "hotkeys" | "fork" | "clone" | "tree" | "trust" | "quit" | "debug" | "arminsayshi" | "dementedelves")[];
|
|
20
20
|
export declare const PI_WORKFLOW_CAPABILITIES: readonly WorkflowCapability[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ClipboardImageData {
|
|
2
|
+
readonly data: string;
|
|
3
|
+
readonly mimeType: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Converts valid padded or unpadded standard base64 into its unique padded form.
|
|
7
|
+
* Clipboard adapters are untrusted at this boundary, so reject representations
|
|
8
|
+
* that Node's permissive base64 decoder would otherwise partially accept.
|
|
9
|
+
*/
|
|
10
|
+
export declare function canonicalizeClipboardImage(image: ClipboardImageData): ClipboardImageData | null;
|
|
11
|
+
export declare function canonicalizeStandardBase64(value: string): string | null;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const STANDARD_BASE64 = /^[A-Za-z0-9+/]*={0,2}$/u;
|
|
2
|
+
/**
|
|
3
|
+
* Converts valid padded or unpadded standard base64 into its unique padded form.
|
|
4
|
+
* Clipboard adapters are untrusted at this boundary, so reject representations
|
|
5
|
+
* that Node's permissive base64 decoder would otherwise partially accept.
|
|
6
|
+
*/
|
|
7
|
+
export function canonicalizeClipboardImage(image) {
|
|
8
|
+
const data = canonicalizeStandardBase64(image.data);
|
|
9
|
+
return data === null ? null : { data, mimeType: image.mimeType };
|
|
10
|
+
}
|
|
11
|
+
export function canonicalizeStandardBase64(value) {
|
|
12
|
+
if (value.length === 0 || !STANDARD_BASE64.test(value))
|
|
13
|
+
return null;
|
|
14
|
+
const paddingStart = value.indexOf("=");
|
|
15
|
+
const core = paddingStart < 0 ? value : value.slice(0, paddingStart);
|
|
16
|
+
const suppliedPadding = value.length - core.length;
|
|
17
|
+
const remainder = core.length % 4;
|
|
18
|
+
if (remainder === 1)
|
|
19
|
+
return null;
|
|
20
|
+
const requiredPadding = remainder === 0 ? 0 : 4 - remainder;
|
|
21
|
+
if (suppliedPadding !== 0 && suppliedPadding !== requiredPadding)
|
|
22
|
+
return null;
|
|
23
|
+
const padded = `${core}${"=".repeat(requiredPadding)}`;
|
|
24
|
+
const decoded = Buffer.from(padded, "base64");
|
|
25
|
+
if (decoded.length === 0)
|
|
26
|
+
return null;
|
|
27
|
+
const canonical = decoded.toString("base64");
|
|
28
|
+
return canonical === padded ? canonical : null;
|
|
29
|
+
}
|
|
@@ -2,6 +2,7 @@ import { randomBytes } from "node:crypto";
|
|
|
2
2
|
import { existsSync, statSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { canonicalizeClipboardImage } from "./clipboard-image.js";
|
|
5
6
|
const CHIP_PATTERN = /\[(?:📷 [^\]]+|📁 [^\]]+|📄 [^\]]+|🖼 {1,2}[^\]]+|🔗 [^\]]+)\]/gu;
|
|
6
7
|
const IMAGE_EXTENSION = /\.(?:jpe?g|png|webp|gif|bmp|tiff?)$/iu;
|
|
7
8
|
const URL_PATTERN = /^https?:\/\/[^\s\u0000-\u001f\u007f]+$/iu;
|
|
@@ -11,12 +12,15 @@ export class PromptChipStore {
|
|
|
11
12
|
#chips = new Map();
|
|
12
13
|
transformPastedContent(content) {
|
|
13
14
|
if (content.kind === "image") {
|
|
15
|
+
const image = canonicalizeClipboardImage(content);
|
|
16
|
+
if (image === null)
|
|
17
|
+
return "";
|
|
14
18
|
const id = randomBytes(5).toString("hex");
|
|
15
19
|
const tag = `[📷 screenshot-${id}.png]`;
|
|
16
20
|
this.#chips.set(tag, {
|
|
17
21
|
kind: "image",
|
|
18
22
|
tag,
|
|
19
|
-
image: { type: "image", data:
|
|
23
|
+
image: { type: "image", data: image.data, mimeType: image.mimeType },
|
|
20
24
|
});
|
|
21
25
|
return tag;
|
|
22
26
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { OwnedUiSessionViewModel, OwnedUiViewportSettings, OwnedUiViewportSettingsPort } from "../../../contracts/owned-ui/index.js";
|
|
2
2
|
import type { UiRouteHost } from "../../../ui/apps/index.js";
|
|
3
3
|
import { type PiEngineAdapter, type PiWorkflowResult } from "../engine/index.js";
|
|
4
|
-
import { type PiShellClipboardContent, type PiShellComponentPort, type PiShellEditorPort, type PiShellExtensionRendererResolver, type PiShellHeaderOptions, type PiShellHeaderPort, type PiShellLoadedResourcesPort, type PiShellResourceEntry, type PiShellTranscriptComponentPort } from "../components/index.js";
|
|
4
|
+
import { type PiShellClipboardContent, type PiShellComponentPort, type PiShellEditorPort, type PiShellExtensionRendererResolver, type PiShellHeaderOptions, type PiShellHeaderPort, type PiShellImageAssetResolver, type PiShellLoadedResourcesPort, type PiShellResourceEntry, type PiShellTranscriptComponentPort } from "../components/index.js";
|
|
5
5
|
import { type PiTuiComponentPort, type PiTuiLayoutNode, type PiTuiTerminalPort } from "../tui-runtime/index.js";
|
|
6
6
|
import { type PreparedPrompt } from "./prompt-chips.js";
|
|
7
7
|
export type OwnedUiBackendPort = PiEngineAdapter;
|
|
@@ -54,7 +54,13 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
|
|
|
54
54
|
readonly onDequeue?: () => void;
|
|
55
55
|
readonly onCopyText?: (text: string) => void;
|
|
56
56
|
readonly readClipboardContent?: () => Promise<PiShellClipboardContent | null>;
|
|
57
|
-
}, startup?: PiShellHeaderOptions, agentDir?: string, extensionRenderers?: PiShellExtensionRendererResolver, sessionLayout?: "pinned" | "custom-viewport");
|
|
57
|
+
}, startup?: PiShellHeaderOptions, agentDir?: string, extensionRenderers?: PiShellExtensionRendererResolver, sessionLayout?: "pinned" | "custom-viewport", imageAssets?: PiShellImageAssetResolver);
|
|
58
|
+
setEditorPaddingX(padding: number): void;
|
|
59
|
+
setAutocompleteMaxVisible(maxVisible: number): void;
|
|
60
|
+
setOutputPad(padding: 0 | 1): void;
|
|
61
|
+
setHideThinkingBlock(hidden: boolean): void;
|
|
62
|
+
setMermaidRenderingMode(mode: "off" | "final" | "streaming"): void;
|
|
63
|
+
setImagePresentation(showImages: boolean, imageWidthCells: number): void;
|
|
58
64
|
preparePromptSubmission(text: string): PreparedPrompt;
|
|
59
65
|
update(view: OwnedUiSessionViewModel): void;
|
|
60
66
|
/**
|
|
@@ -76,6 +82,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
|
|
|
76
82
|
};
|
|
77
83
|
layoutRoot(): PiTuiLayoutNode;
|
|
78
84
|
transcriptComponent(id: string): PiShellTranscriptComponentPort | undefined;
|
|
85
|
+
exitTranscript(width: number): string;
|
|
79
86
|
appendWorkflowStatus(message: string): void;
|
|
80
87
|
appendWorkflowResult(result: PiWorkflowResult): void;
|
|
81
88
|
appendDaxnuts(): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { backgroundSgrSpan, composeSubmittedPromptRows, displayWidth, formatSubmittedPromptTime, heldNativeHyperlinkStyle, hyperlinkSgrSpan, nativeHyperlinkStyle, overlaySpan, submittedPromptLayout, stripAnsi, } from "../../../ui/components/index.js";
|
|
2
2
|
import { PINNED_PI_HIDDEN_COMMAND_NAMES, PINNED_PI_WORKFLOW_COMMAND_NAMES, } from "../engine/index.js";
|
|
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, piShellVisibleWidth, piTheme, renderPiShellPackageUpdateNotice, renderPiShellStartupDiagnostic, renderPiShellStatusText, renderPiShellTranscriptBlock, } from "../components/index.js";
|
|
3
|
+
import { createPiExtensionUiBridge, createPiQueuedInputStatus, createPiShellArmin, createPiShellAuthProviderSelector, createPiShellChangelog, createPiShellCollapsedChangelog, createPiShellDaxnuts, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellEditor, createPiShellExtensionSelector, createPiShellFooter, createPiShellHeader, createPiShellHotkeys, createPiShellLoadedResources, createPiShellLoginDialog, createPiShellModelSelector, createPiShellOperationLoader, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionInfo, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellStatus, createPiShellTranscriptComponent, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, onPiThemeChange, piShellVisibleWidth, piTheme, renderPiShellPackageUpdateNotice, renderPiShellStartupDiagnostic, renderPiShellStatusText, renderPiShellTranscriptBlock, } from "../components/index.js";
|
|
4
4
|
import { PiTuiRuntimeAdapter, } from "../tui-runtime/index.js";
|
|
5
5
|
import { PromptChipStore } from "./prompt-chips.js";
|
|
6
6
|
import { SessionViewportController } from "./session-viewport-controller.js";
|
|
@@ -21,6 +21,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
21
21
|
#footer;
|
|
22
22
|
#queued;
|
|
23
23
|
#extensionRenderers;
|
|
24
|
+
#imageAssets;
|
|
24
25
|
#promptChips = new PromptChipStore();
|
|
25
26
|
#customViewport;
|
|
26
27
|
#submittedPromptComposer;
|
|
@@ -28,6 +29,10 @@ export class OwnedUiSessionShellRoot {
|
|
|
28
29
|
#componentRuntime;
|
|
29
30
|
#toolsExpanded = false;
|
|
30
31
|
#thinkingVisible = true;
|
|
32
|
+
#mermaidRenderingMode = "off";
|
|
33
|
+
#showImages = true;
|
|
34
|
+
#imageWidthCells = 80;
|
|
35
|
+
#outputPad = 1;
|
|
31
36
|
#workflowTranscriptSequence = 0;
|
|
32
37
|
#workflowStatusAnchors = new Map();
|
|
33
38
|
#workflowStatusMessages = new Map();
|
|
@@ -42,7 +47,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
42
47
|
constructor(view, cwd, handlers, startup = {}, agentDir, extensionRenderers = {
|
|
43
48
|
getMessageRenderer: () => undefined,
|
|
44
49
|
getToolDefinition: () => undefined,
|
|
45
|
-
}, sessionLayout = "pinned") {
|
|
50
|
+
}, sessionLayout = "pinned", imageAssets) {
|
|
46
51
|
this.#view = view;
|
|
47
52
|
this.#customViewport = sessionLayout === "custom-viewport";
|
|
48
53
|
this.#submittedPromptComposer = this.#customViewport
|
|
@@ -54,6 +59,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
54
59
|
: undefined;
|
|
55
60
|
this.#cwd = cwd;
|
|
56
61
|
this.#extensionRenderers = extensionRenderers;
|
|
62
|
+
this.#imageAssets = imageAssets;
|
|
57
63
|
this.#componentRuntime = handlers;
|
|
58
64
|
this.header = createPiShellHeader(startup);
|
|
59
65
|
this.resources = createPiShellLoadedResources(startup.resources ?? [], startup.expanded ?? false);
|
|
@@ -109,6 +115,47 @@ export class OwnedUiSessionShellRoot {
|
|
|
109
115
|
this.#documentLayouts.clear();
|
|
110
116
|
});
|
|
111
117
|
}
|
|
118
|
+
setEditorPaddingX(padding) {
|
|
119
|
+
this.editor.setPaddingX(padding);
|
|
120
|
+
this.#documentLayouts.clear();
|
|
121
|
+
}
|
|
122
|
+
setAutocompleteMaxVisible(maxVisible) {
|
|
123
|
+
this.editor.setAutocompleteMaxVisible(maxVisible);
|
|
124
|
+
}
|
|
125
|
+
setOutputPad(padding) {
|
|
126
|
+
if (this.#outputPad === padding)
|
|
127
|
+
return;
|
|
128
|
+
this.#outputPad = padding;
|
|
129
|
+
this.#status.setOutputPad(padding);
|
|
130
|
+
for (const component of this.#transcript.values())
|
|
131
|
+
component.setOutputPad(padding);
|
|
132
|
+
this.#invalidateTranscriptPresentation();
|
|
133
|
+
}
|
|
134
|
+
setHideThinkingBlock(hidden) {
|
|
135
|
+
if (this.#thinkingVisible === !hidden)
|
|
136
|
+
return;
|
|
137
|
+
this.#thinkingVisible = !hidden;
|
|
138
|
+
for (const component of this.#transcript.values())
|
|
139
|
+
component.setHideThinkingBlock(hidden);
|
|
140
|
+
this.#invalidateTranscriptPresentation();
|
|
141
|
+
}
|
|
142
|
+
setMermaidRenderingMode(mode) {
|
|
143
|
+
if (this.#mermaidRenderingMode === mode)
|
|
144
|
+
return;
|
|
145
|
+
this.#mermaidRenderingMode = mode;
|
|
146
|
+
for (const component of this.#transcript.values())
|
|
147
|
+
component.setMermaidRenderingMode(mode);
|
|
148
|
+
this.#invalidateTranscriptPresentation();
|
|
149
|
+
}
|
|
150
|
+
setImagePresentation(showImages, imageWidthCells) {
|
|
151
|
+
if (this.#showImages === showImages && this.#imageWidthCells === imageWidthCells)
|
|
152
|
+
return;
|
|
153
|
+
this.#showImages = showImages;
|
|
154
|
+
this.#imageWidthCells = imageWidthCells;
|
|
155
|
+
for (const component of this.#transcript.values())
|
|
156
|
+
component.setImagePresentation(showImages, imageWidthCells);
|
|
157
|
+
this.#invalidateTranscriptPresentation();
|
|
158
|
+
}
|
|
112
159
|
preparePromptSubmission(text) {
|
|
113
160
|
return this.#promptChips.prepareSubmission(text);
|
|
114
161
|
}
|
|
@@ -135,7 +182,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
135
182
|
this.#blocksById.set(block.id, block);
|
|
136
183
|
const component = this.#transcript.get(block.id);
|
|
137
184
|
if (component === undefined) {
|
|
138
|
-
const created = createPiShellTranscriptComponent(block, this.#cwd, this.#extensionRenderers, this.#submittedPromptComposer);
|
|
185
|
+
const created = createPiShellTranscriptComponent(block, this.#cwd, this.#extensionRenderers, this.#submittedPromptComposer, this.#outputPad, !this.#thinkingVisible, this.#mermaidRenderingMode, this.#showImages, this.#imageWidthCells, this.#imageAssets);
|
|
139
186
|
created.setExpanded(this.#toolsExpanded);
|
|
140
187
|
this.#transcript.set(block.id, created);
|
|
141
188
|
this.#transcriptOrder.push(block.id);
|
|
@@ -363,7 +410,13 @@ export class OwnedUiSessionShellRoot {
|
|
|
363
410
|
.filter(diagnostic => diagnostic.code === "package-updates")
|
|
364
411
|
.flatMap(diagnostic => renderPiShellPackageUpdateNotice(diagnostic.message.split("\n").filter(line => line.startsWith("- ")).map(line => line.slice(2)), width)));
|
|
365
412
|
rows.push(...diagnostics
|
|
366
|
-
.filter(diagnostic => diagnostic.code
|
|
413
|
+
.filter(diagnostic => diagnostic.code === "changelog-collapsed" || diagnostic.code === "changelog-expanded")
|
|
414
|
+
.flatMap(diagnostic => diagnostic.code === "changelog-collapsed"
|
|
415
|
+
? createPiShellCollapsedChangelog().render(width)
|
|
416
|
+
: createPiShellChangelog(diagnostic.message).render(width)));
|
|
417
|
+
rows.push(...diagnostics
|
|
418
|
+
.filter(diagnostic => diagnostic.code !== "engine-startup" && diagnostic.code !== "package-updates"
|
|
419
|
+
&& diagnostic.code !== "changelog-collapsed" && diagnostic.code !== "changelog-expanded")
|
|
367
420
|
.slice(-3)
|
|
368
421
|
.flatMap(diagnostic => renderPiShellTranscriptBlock({
|
|
369
422
|
id: `diagnostic-${diagnostic.sequence}`,
|
|
@@ -420,6 +473,20 @@ export class OwnedUiSessionShellRoot {
|
|
|
420
473
|
transcriptComponent(id) {
|
|
421
474
|
return this.#transcript.get(id);
|
|
422
475
|
}
|
|
476
|
+
exitTranscript(width) {
|
|
477
|
+
const rows = [];
|
|
478
|
+
for (const id of this.#transcriptOrder) {
|
|
479
|
+
const block = this.#blocksById.get(id);
|
|
480
|
+
if (block === undefined || (!this.#thinkingVisible && block.kind === "thinking"))
|
|
481
|
+
continue;
|
|
482
|
+
if (rows.length > 0 && block.kind === "user")
|
|
483
|
+
rows.push("");
|
|
484
|
+
rows.push(...this.#blockRows(id, block, width).map(row => stripAnsi(row).trimEnd()));
|
|
485
|
+
}
|
|
486
|
+
while (rows.at(-1) === "")
|
|
487
|
+
rows.pop();
|
|
488
|
+
return rows.join("\n");
|
|
489
|
+
}
|
|
423
490
|
appendWorkflowStatus(message) {
|
|
424
491
|
const previousId = this.#lastWorkflowStatusId;
|
|
425
492
|
if (previousId !== undefined
|
|
@@ -432,7 +499,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
432
499
|
}
|
|
433
500
|
const id = this.#appendAnchoredWorkflowComponent(width => [
|
|
434
501
|
"",
|
|
435
|
-
...renderPiShellStatusText(this.#workflowStatusMessages.get(id) ?? message, width),
|
|
502
|
+
...renderPiShellStatusText(this.#workflowStatusMessages.get(id) ?? message, width, this.#outputPad),
|
|
436
503
|
]);
|
|
437
504
|
this.#workflowStatusMessages.set(id, message);
|
|
438
505
|
this.#lastWorkflowStatusId = id;
|
|
@@ -517,9 +584,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
517
584
|
this.#appendAnchoredWorkflowComponent(width => ["", ...daxnuts.render(width)], () => daxnuts.dispose?.());
|
|
518
585
|
}
|
|
519
586
|
toggleThinkingVisibility() {
|
|
520
|
-
this
|
|
521
|
-
this.#documentLayouts.clear();
|
|
522
|
-
this.invalidate();
|
|
587
|
+
this.setHideThinkingBlock(this.#thinkingVisible);
|
|
523
588
|
}
|
|
524
589
|
get toolsExpanded() {
|
|
525
590
|
return this.#toolsExpanded;
|
|
@@ -665,6 +730,11 @@ export class OwnedUiSessionShellRoot {
|
|
|
665
730
|
this.#footer.dispose?.();
|
|
666
731
|
this.#queued.dispose?.();
|
|
667
732
|
}
|
|
733
|
+
#invalidateTranscriptPresentation() {
|
|
734
|
+
this.#renderedRows.clear();
|
|
735
|
+
this.#documentLayouts.clear();
|
|
736
|
+
this.invalidate();
|
|
737
|
+
}
|
|
668
738
|
#syncTranscript(blocks) {
|
|
669
739
|
const previousIds = this.#transcriptOrder.filter(id => !id.startsWith("workflow-status-"));
|
|
670
740
|
const transcriptChanged = previousIds.length !== blocks.length || blocks.some((block, index) => {
|
|
@@ -687,7 +757,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
687
757
|
for (const block of blocks) {
|
|
688
758
|
const component = this.#transcript.get(block.id);
|
|
689
759
|
if (component === undefined) {
|
|
690
|
-
const created = createPiShellTranscriptComponent(block, this.#cwd, this.#extensionRenderers, this.#submittedPromptComposer);
|
|
760
|
+
const created = createPiShellTranscriptComponent(block, this.#cwd, this.#extensionRenderers, this.#submittedPromptComposer, this.#outputPad, !this.#thinkingVisible, this.#mermaidRenderingMode, this.#showImages, this.#imageWidthCells, this.#imageAssets);
|
|
691
761
|
created.setExpanded(this.#toolsExpanded);
|
|
692
762
|
this.#transcript.set(block.id, created);
|
|
693
763
|
}
|
|
@@ -724,6 +794,10 @@ export class OwnedUiSessionShellRoot {
|
|
|
724
794
|
invalidate() { },
|
|
725
795
|
update() { },
|
|
726
796
|
setExpanded() { },
|
|
797
|
+
setOutputPad() { },
|
|
798
|
+
setHideThinkingBlock() { },
|
|
799
|
+
setMermaidRenderingMode() { },
|
|
800
|
+
setImagePresentation() { },
|
|
727
801
|
...(dispose === undefined ? {} : { dispose }),
|
|
728
802
|
};
|
|
729
803
|
this.#transcript.set(id, component);
|
|
@@ -44,3 +44,5 @@ export declare class OwnedUiSessionShell {
|
|
|
44
44
|
showScopedModelsSelector(): void;
|
|
45
45
|
dispose(): Promise<void>;
|
|
46
46
|
}
|
|
47
|
+
export declare function formatSessionResumeCommand(sessionFile: string): string;
|
|
48
|
+
export declare function quoteCommandArgument(value: string): string;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { PRODUCT_TEXT } from "../../../product-identity.js";
|
|
1
2
|
import { MOUSE_TRACKING_OFF, MOUSE_TRACKING_ON, parseMouseInput } from "../../../ui/components/index.js";
|
|
2
3
|
import { PINNED_PI_HIDDEN_COMMAND_NAMES, PINNED_PI_WORKFLOW_COMMAND_NAMES, } from "../engine/index.js";
|
|
3
4
|
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
5
|
import { PiTuiRuntimeAdapter, } from "../tui-runtime/index.js";
|
|
6
|
+
import { canonicalizeClipboardImage } from "./clipboard-image.js";
|
|
5
7
|
import { preloadSystemClipboard, readSystemClipboardContent, writeSystemClipboardText, } from "./system-clipboard.js";
|
|
6
8
|
import { OwnedUiSessionShellRoot, shellResourceEntries, } from "./session-shell-root.js";
|
|
7
9
|
export { OwnedUiSessionShellRoot } from "./session-shell-root.js";
|
|
@@ -26,6 +28,13 @@ export class OwnedUiSessionShell {
|
|
|
26
28
|
#customViewport;
|
|
27
29
|
#removeViewportPreInput;
|
|
28
30
|
#unsubscribeSettings;
|
|
31
|
+
#unbindPiSettings;
|
|
32
|
+
#unbindTerminalSettings;
|
|
33
|
+
#unbindShutdownSettings;
|
|
34
|
+
#terminalProgressEnabled = false;
|
|
35
|
+
#showImages = true;
|
|
36
|
+
#imageWidthCells = 80;
|
|
37
|
+
#fullscreenExitOutput = "transcript";
|
|
29
38
|
#compactionQueue = [];
|
|
30
39
|
#lastClearTime = 0;
|
|
31
40
|
#lastEscapeTime = 0;
|
|
@@ -73,9 +82,17 @@ export class OwnedUiSessionShell {
|
|
|
73
82
|
await pendingClipboardWrite;
|
|
74
83
|
if (options.clipboard === undefined)
|
|
75
84
|
return readSystemClipboardContent();
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
try {
|
|
86
|
+
const image = await options.clipboard.readImage?.();
|
|
87
|
+
if (image !== null && image !== undefined) {
|
|
88
|
+
const canonical = canonicalizeClipboardImage(image);
|
|
89
|
+
if (canonical !== null)
|
|
90
|
+
return { kind: "image", ...canonical };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Treat an unavailable or malformed image as text-capable clipboard input.
|
|
95
|
+
}
|
|
79
96
|
const text = await options.clipboard.readText();
|
|
80
97
|
return text === null ? null : { kind: "text", text };
|
|
81
98
|
},
|
|
@@ -85,7 +102,9 @@ export class OwnedUiSessionShell {
|
|
|
85
102
|
}, this.backend.agentDir, {
|
|
86
103
|
getMessageRenderer: customType => this.backend.pinnedMessageRenderer(customType),
|
|
87
104
|
getToolDefinition: toolName => this.backend.pinnedToolDefinition(toolName),
|
|
88
|
-
}, options.sessionLayout
|
|
105
|
+
}, options.sessionLayout, {
|
|
106
|
+
resolve: assetId => this.backend.resolveTranscriptImage(assetId),
|
|
107
|
+
});
|
|
89
108
|
// Bare A1 owns a bounded viewport and therefore always runs on the alternate
|
|
90
109
|
// fullscreen surface. The pinned comparison profiles still honor Pi's mode.
|
|
91
110
|
const tuiMode = this.#customViewport
|
|
@@ -100,6 +119,32 @@ export class OwnedUiSessionShell {
|
|
|
100
119
|
};
|
|
101
120
|
runtime = new PiTuiRuntimeAdapter(runtimeOptions);
|
|
102
121
|
this.runtime = runtime;
|
|
122
|
+
const initialPiSettings = this.backend.pinnedSettingsSnapshot();
|
|
123
|
+
this.runtime.setHardwareCursor(initialPiSettings.showHardwareCursor);
|
|
124
|
+
this.runtime.setClearOnShrink(initialPiSettings.clearOnShrink);
|
|
125
|
+
this.#terminalProgressEnabled = initialPiSettings.showTerminalProgress;
|
|
126
|
+
this.#fullscreenExitOutput = initialPiSettings.fullscreenExitOutput;
|
|
127
|
+
this.#unbindShutdownSettings = this.backend.bindSettingsOwner("shutdown", {
|
|
128
|
+
fullscreenExitOutput: { apply() { } },
|
|
129
|
+
});
|
|
130
|
+
this.#unbindTerminalSettings = this.backend.bindSettingsOwner("terminal", {
|
|
131
|
+
showHardwareCursor: { apply: value => {
|
|
132
|
+
if (typeof value !== "boolean")
|
|
133
|
+
throw new TypeError("Hardware cursor setting is invalid");
|
|
134
|
+
this.runtime.setHardwareCursor(value);
|
|
135
|
+
} },
|
|
136
|
+
clearOnShrink: { apply: value => {
|
|
137
|
+
if (typeof value !== "boolean")
|
|
138
|
+
throw new TypeError("Clear-on-shrink setting is invalid");
|
|
139
|
+
this.runtime.setClearOnShrink(value);
|
|
140
|
+
} },
|
|
141
|
+
showTerminalProgress: { apply: value => {
|
|
142
|
+
if (typeof value !== "boolean")
|
|
143
|
+
throw new TypeError("Terminal progress setting is invalid");
|
|
144
|
+
this.#terminalProgressEnabled = value;
|
|
145
|
+
this.#syncTerminalProgress(this.view());
|
|
146
|
+
} },
|
|
147
|
+
});
|
|
103
148
|
this.#removeViewportPreInput = this.#customViewport
|
|
104
149
|
? this.runtime.addPreInputListener(data => {
|
|
105
150
|
// An overlay or editor-replacement screen owns its entire pointer
|
|
@@ -129,6 +174,53 @@ export class OwnedUiSessionShell {
|
|
|
129
174
|
this.#unsubscribeSettings = this.#customViewport && options.viewportSettings
|
|
130
175
|
? options.viewportSettings.onChange(settings => this.root.setViewportConfig(settings))
|
|
131
176
|
: () => { };
|
|
177
|
+
this.root.setEditorPaddingX(initialPiSettings.editorPaddingX);
|
|
178
|
+
this.root.setAutocompleteMaxVisible(initialPiSettings.autocompleteMaxVisible);
|
|
179
|
+
this.root.setOutputPad(initialPiSettings.outputPad);
|
|
180
|
+
this.root.setHideThinkingBlock(initialPiSettings.hideThinkingBlock);
|
|
181
|
+
this.root.setMermaidRenderingMode(initialPiSettings.mermaidRenderingMode);
|
|
182
|
+
this.#showImages = initialPiSettings.showImages;
|
|
183
|
+
this.#imageWidthCells = initialPiSettings.imageWidthCells;
|
|
184
|
+
this.root.setImagePresentation(this.#showImages, this.#imageWidthCells);
|
|
185
|
+
this.#unbindPiSettings = this.backend.bindSettingsOwner("shell", {
|
|
186
|
+
editorPaddingX: { apply: value => {
|
|
187
|
+
if (typeof value !== "number")
|
|
188
|
+
throw new TypeError("Editor padding is invalid");
|
|
189
|
+
this.root.setEditorPaddingX(value);
|
|
190
|
+
} },
|
|
191
|
+
autocompleteMaxVisible: { apply: value => {
|
|
192
|
+
if (typeof value !== "number")
|
|
193
|
+
throw new TypeError("Autocomplete maximum is invalid");
|
|
194
|
+
this.root.setAutocompleteMaxVisible(value);
|
|
195
|
+
} },
|
|
196
|
+
outputPad: { apply: value => {
|
|
197
|
+
if (value !== 0 && value !== 1)
|
|
198
|
+
throw new TypeError("Output padding is invalid");
|
|
199
|
+
this.root.setOutputPad(value);
|
|
200
|
+
} },
|
|
201
|
+
hideThinkingBlock: { apply: value => {
|
|
202
|
+
if (typeof value !== "boolean")
|
|
203
|
+
throw new TypeError("Thinking-block visibility is invalid");
|
|
204
|
+
this.root.setHideThinkingBlock(value);
|
|
205
|
+
} },
|
|
206
|
+
mermaidRenderingMode: { apply: value => {
|
|
207
|
+
if (value !== "off" && value !== "final" && value !== "streaming")
|
|
208
|
+
throw new TypeError("Mermaid mode is invalid");
|
|
209
|
+
this.root.setMermaidRenderingMode(value);
|
|
210
|
+
} },
|
|
211
|
+
showImages: { apply: value => {
|
|
212
|
+
if (typeof value !== "boolean")
|
|
213
|
+
throw new TypeError("Image visibility is invalid");
|
|
214
|
+
this.#showImages = value;
|
|
215
|
+
this.root.setImagePresentation(this.#showImages, this.#imageWidthCells);
|
|
216
|
+
} },
|
|
217
|
+
imageWidthCells: { apply: value => {
|
|
218
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)
|
|
219
|
+
throw new TypeError("Image width is invalid");
|
|
220
|
+
this.#imageWidthCells = value;
|
|
221
|
+
this.root.setImagePresentation(this.#showImages, this.#imageWidthCells);
|
|
222
|
+
} },
|
|
223
|
+
});
|
|
132
224
|
this.#extensionBridge = createPiExtensionUiBridge({
|
|
133
225
|
runtime: {
|
|
134
226
|
getColumns: () => this.runtime.viewport().columns,
|
|
@@ -174,6 +266,7 @@ export class OwnedUiSessionShell {
|
|
|
174
266
|
const view = event.type === "transcript-block" && this.#sessionGeneration === this.backend.sessionGeneration
|
|
175
267
|
? this.#syncBlock(event.block)
|
|
176
268
|
: semanticOnly ? this.view() : this.#syncView();
|
|
269
|
+
this.#syncTerminalProgress(view);
|
|
177
270
|
if (view.lifecycle === "ready" && this.#compactionQueue.length > 0)
|
|
178
271
|
void this.#flushCompactionQueue();
|
|
179
272
|
if (event.type === "session-lifecycle" && event.lifecycle === "stopped")
|
|
@@ -190,6 +283,7 @@ export class OwnedUiSessionShell {
|
|
|
190
283
|
return;
|
|
191
284
|
this.#started = true;
|
|
192
285
|
this.runtime.start();
|
|
286
|
+
this.#syncTerminalProgress(this.view());
|
|
193
287
|
if (this.#customViewport)
|
|
194
288
|
this.#setPointerReporting(true);
|
|
195
289
|
void this.backend.bindExtensionUi(this.#extensionBridge.context, () => { void this.shutdown(); });
|
|
@@ -238,7 +332,11 @@ export class OwnedUiSessionShell {
|
|
|
238
332
|
}
|
|
239
333
|
if (this.view().status.workingMessage?.startsWith("Compacting") === true) {
|
|
240
334
|
this.root.editor.addToHistory(displayInput);
|
|
241
|
-
this.#compactionQueue.push({
|
|
335
|
+
this.#compactionQueue.push({
|
|
336
|
+
text: input,
|
|
337
|
+
type: "steer",
|
|
338
|
+
...(prepared.images.length === 0 ? {} : { images: prepared.images }),
|
|
339
|
+
});
|
|
242
340
|
this.root.appendWorkflowResult({ command: "compact", outcome: "completed", message: `Queued during compaction: ${input}` });
|
|
243
341
|
this.runtime.requestRender();
|
|
244
342
|
return { outcome: "completed", diagnostic: null };
|
|
@@ -336,14 +434,20 @@ export class OwnedUiSessionShell {
|
|
|
336
434
|
return workflowAdapterResult(result);
|
|
337
435
|
}
|
|
338
436
|
async queueFollowUp() {
|
|
339
|
-
const
|
|
340
|
-
if (!
|
|
437
|
+
const displayInput = this.root.editor.getText().trim();
|
|
438
|
+
if (!displayInput)
|
|
341
439
|
return rejected("nothing to queue");
|
|
342
|
-
this.root.
|
|
440
|
+
const prepared = this.root.preparePromptSubmission(displayInput);
|
|
441
|
+
const text = prepared.text.trim();
|
|
442
|
+
this.root.editor.addToHistory(displayInput);
|
|
343
443
|
this.root.editor.setText("");
|
|
344
444
|
this.root.resumeViewportFollowing();
|
|
345
445
|
if (this.view().status.workingMessage?.startsWith("Compacting") === true) {
|
|
346
|
-
this.#compactionQueue.push({
|
|
446
|
+
this.#compactionQueue.push({
|
|
447
|
+
text,
|
|
448
|
+
type: "follow-up",
|
|
449
|
+
...(prepared.images.length === 0 ? {} : { images: prepared.images }),
|
|
450
|
+
});
|
|
347
451
|
return { outcome: "completed", diagnostic: null };
|
|
348
452
|
}
|
|
349
453
|
return this.#execute({
|
|
@@ -351,6 +455,7 @@ export class OwnedUiSessionShell {
|
|
|
351
455
|
correlationId: this.#correlation("follow-up"),
|
|
352
456
|
sessionId: this.backend.sessionId,
|
|
353
457
|
text,
|
|
458
|
+
...(prepared.images.length === 0 ? {} : { images: prepared.images }),
|
|
354
459
|
});
|
|
355
460
|
}
|
|
356
461
|
restoreQueuedInput() {
|
|
@@ -597,12 +702,13 @@ export class OwnedUiSessionShell {
|
|
|
597
702
|
this.#setPointerReporting(true);
|
|
598
703
|
}
|
|
599
704
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
705
|
+
void this.backend.applyPinnedSettingValue(callback, value).then(result => {
|
|
706
|
+
if (result.outcome === "failed")
|
|
707
|
+
this.root.appendWorkflowResult(result);
|
|
708
|
+
else if (callback === "onTuiModeChange")
|
|
709
|
+
this.root.appendWorkflowStatus(`TUI mode: ${value}`);
|
|
710
|
+
this.runtime.requestRender();
|
|
711
|
+
});
|
|
606
712
|
},
|
|
607
713
|
onCancel: close,
|
|
608
714
|
});
|
|
@@ -824,11 +930,27 @@ export class OwnedUiSessionShell {
|
|
|
824
930
|
this.#setPointerReporting(false, true);
|
|
825
931
|
this.#removeViewportPreInput();
|
|
826
932
|
this.#unsubscribeSettings();
|
|
933
|
+
const exitMode = this.backend.disposed
|
|
934
|
+
? this.#fullscreenExitOutput
|
|
935
|
+
: this.backend.pinnedSettingsSnapshot().fullscreenExitOutput;
|
|
936
|
+
const exitTranscript = this.root.exitTranscript(this.runtime.viewport().columns);
|
|
937
|
+
const sessionFile = this.backend.currentSessionFile();
|
|
938
|
+
const resumeHint = sessionFile === null ? "" : `Resume: ${formatSessionResumeCommand(sessionFile)}`;
|
|
939
|
+
const fullscreenExitText = this.runtime.mode !== "fullscreen"
|
|
940
|
+
? ""
|
|
941
|
+
: exitMode === "resume-hint"
|
|
942
|
+
? resumeHint
|
|
943
|
+
: [exitTranscript, resumeHint].filter(Boolean).join("\n\n");
|
|
944
|
+
this.#unbindPiSettings();
|
|
945
|
+
this.#unbindTerminalSettings();
|
|
946
|
+
this.#unbindShutdownSettings();
|
|
827
947
|
this.#unsubscribe();
|
|
828
948
|
this.#dialogHandle?.hide();
|
|
829
949
|
await this.backend.unbindExtensionUi();
|
|
830
950
|
this.#extensionBridge.dispose();
|
|
831
951
|
await this.runtime.dispose();
|
|
952
|
+
if (fullscreenExitText.length > 0)
|
|
953
|
+
this.runtime.writeAfterStop(`${fullscreenExitText}\n`);
|
|
832
954
|
}
|
|
833
955
|
/**
|
|
834
956
|
* Applies one transcript block without re-reading the session. The listeners still hear
|
|
@@ -842,6 +964,11 @@ export class OwnedUiSessionShell {
|
|
|
842
964
|
listener(view);
|
|
843
965
|
return view;
|
|
844
966
|
}
|
|
967
|
+
#syncTerminalProgress(view) {
|
|
968
|
+
if (!this.runtime.active)
|
|
969
|
+
return;
|
|
970
|
+
this.runtime.setTerminalProgress(this.#terminalProgressEnabled && view.lifecycle === "busy");
|
|
971
|
+
}
|
|
845
972
|
#syncView() {
|
|
846
973
|
const view = this.view();
|
|
847
974
|
if (this.backend.sessionGeneration !== this.#sessionGeneration) {
|
|
@@ -1117,6 +1244,7 @@ export class OwnedUiSessionShell {
|
|
|
1117
1244
|
correlationId: this.#correlation(`compaction-${item.type}`),
|
|
1118
1245
|
sessionId: this.backend.sessionId,
|
|
1119
1246
|
text: item.text,
|
|
1247
|
+
...(item.images === undefined ? {} : { images: item.images }),
|
|
1120
1248
|
});
|
|
1121
1249
|
}
|
|
1122
1250
|
}
|
|
@@ -1131,6 +1259,14 @@ export class OwnedUiSessionShell {
|
|
|
1131
1259
|
return `pi-shell-${prefix}-${this.#sequence}`;
|
|
1132
1260
|
}
|
|
1133
1261
|
}
|
|
1262
|
+
export function formatSessionResumeCommand(sessionFile) {
|
|
1263
|
+
return `${PRODUCT_TEXT.commandName} --session ${quoteCommandArgument(sessionFile)}`;
|
|
1264
|
+
}
|
|
1265
|
+
export function quoteCommandArgument(value) {
|
|
1266
|
+
if (process.platform === "win32")
|
|
1267
|
+
return `"${value.replaceAll("\"", "\"\"")}"`;
|
|
1268
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
1269
|
+
}
|
|
1134
1270
|
function modelReference(model) {
|
|
1135
1271
|
if (typeof model !== "object" || model === null)
|
|
1136
1272
|
return "";
|