@timurproko/a1 0.1.8-dev.b449b5c → 0.1.8-dev.c64be0e
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 +22 -17
- 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/src/cli/dispatch.js +53 -18
- package/dist/src/cli/packages.d.ts +9 -1
- package/dist/src/cli/packages.js +24 -20
- package/dist/src/foundation/pi-component-adapter/index.d.ts +1 -1
- package/dist/src/foundation/pi-component-adapter/index.js +1 -1
- package/dist/src/foundation/pi-component-adapter/shell-presenters-transcript.js +2 -1
- package/dist/src/foundation/pi-engine-adapter/adapter.d.ts +7 -0
- package/dist/src/foundation/pi-engine-adapter/adapter.js +193 -39
- package/dist/src/foundation/pi-owned-ui-integration/session-shell.d.ts +6 -0
- package/dist/src/foundation/pi-owned-ui-integration/session-shell.js +223 -13
- package/dist/src/foundation/release/index.d.ts +0 -1
- package/dist/src/foundation/release/index.js +0 -1
- package/dist/src/foundation/release/update.js +13 -3
- package/docs/ci-release-runbook.md +1 -1
- package/package.json +2 -1
- package/dist/src/foundation/release/development-preview-release.d.ts +0 -49
- package/dist/src/foundation/release/development-preview-release.js +0 -114
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MOUSE_TRACKING_OFF, MOUSE_TRACKING_ON, parseMouseInput } from "../ui-components/index.js";
|
|
2
2
|
import { PINNED_PI_HIDDEN_COMMAND_NAMES, PINNED_PI_WORKFLOW_COMMAND_NAMES, } from "../pi-engine-adapter/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, piTheme, renderPiShellPackageUpdateNotice, renderPiShellStartupDiagnostic, renderPiShellStatusText, renderPiShellTranscriptBlock, } from "../pi-component-adapter/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, piTheme, renderPiShellPackageUpdateNotice, renderPiShellStartupDiagnostic, renderPiShellStatusText, renderPiShellTranscriptBlock, } from "../pi-component-adapter/index.js";
|
|
4
4
|
import { PiTuiRuntimeAdapter, } from "../pi-tui-runtime-adapter/index.js";
|
|
5
5
|
export class OwnedUiSessionShellRoot {
|
|
6
6
|
editor;
|
|
@@ -8,6 +8,9 @@ export class OwnedUiSessionShellRoot {
|
|
|
8
8
|
resources;
|
|
9
9
|
#cwd;
|
|
10
10
|
#transcript = new Map();
|
|
11
|
+
#blocksById = new Map();
|
|
12
|
+
#renderedRows = new Map();
|
|
13
|
+
#themeUnsubscribe;
|
|
11
14
|
#transcriptOrder = [];
|
|
12
15
|
#view;
|
|
13
16
|
#status;
|
|
@@ -54,6 +57,9 @@ export class OwnedUiSessionShellRoot {
|
|
|
54
57
|
this.editor.addToHistory(block.text);
|
|
55
58
|
}
|
|
56
59
|
this.#syncTranscript(view.transcript);
|
|
60
|
+
// Colours come from the active theme, so rendered rows outlive their revision only
|
|
61
|
+
// until the theme under them changes.
|
|
62
|
+
this.#themeUnsubscribe = onPiThemeChange(() => this.#renderedRows.clear());
|
|
57
63
|
}
|
|
58
64
|
update(view) {
|
|
59
65
|
this.#view = view;
|
|
@@ -65,6 +71,25 @@ export class OwnedUiSessionShellRoot {
|
|
|
65
71
|
this.editor.setThinkingLevel(view.thinkingLevel);
|
|
66
72
|
this.invalidate();
|
|
67
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Applies one block: its component is created or updated in place and the order grows
|
|
76
|
+
* only when the block is new. A streamed chunk costs one component update rather than a
|
|
77
|
+
* walk of the whole transcript.
|
|
78
|
+
*/
|
|
79
|
+
applyTranscriptBlock(block) {
|
|
80
|
+
this.#blocksById.set(block.id, block);
|
|
81
|
+
const component = this.#transcript.get(block.id);
|
|
82
|
+
if (component === undefined) {
|
|
83
|
+
const created = createPiShellTranscriptComponent(block, this.#cwd, this.#extensionRenderers);
|
|
84
|
+
created.setExpanded(this.#toolsExpanded);
|
|
85
|
+
this.#transcript.set(block.id, created);
|
|
86
|
+
this.#transcriptOrder.push(block.id);
|
|
87
|
+
}
|
|
88
|
+
else if (component.revision !== block.revision) {
|
|
89
|
+
component.update(block);
|
|
90
|
+
}
|
|
91
|
+
this.invalidate();
|
|
92
|
+
}
|
|
68
93
|
render(width) {
|
|
69
94
|
const queued = this.#view.editor.queuedSubmissions.length === 0 ? [] : this.#queued.render(width);
|
|
70
95
|
return [
|
|
@@ -128,10 +153,10 @@ export class OwnedUiSessionShellRoot {
|
|
|
128
153
|
}
|
|
129
154
|
#renderDocument(width) {
|
|
130
155
|
const transcript = this.#transcriptOrder.flatMap((id, index) => {
|
|
131
|
-
const block = this.#
|
|
156
|
+
const block = this.#blocksById.get(id);
|
|
132
157
|
if (!this.#thinkingVisible && block?.kind === "thinking")
|
|
133
158
|
return [];
|
|
134
|
-
const rows = this.#
|
|
159
|
+
const rows = this.#blockRows(id, block, width);
|
|
135
160
|
if (index > 0 && block?.kind === "user")
|
|
136
161
|
return ["", ...rows];
|
|
137
162
|
return rows;
|
|
@@ -167,6 +192,25 @@ export class OwnedUiSessionShellRoot {
|
|
|
167
192
|
...diagnosticRows,
|
|
168
193
|
];
|
|
169
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Rows for one transcript block. A finalized block renders once for a given revision
|
|
197
|
+
* and width and is reused after that, so a frame costs what changed rather than what
|
|
198
|
+
* the session has accumulated. A live block, and anything the shell drives itself, is
|
|
199
|
+
* rendered every time because its content is still moving.
|
|
200
|
+
*/
|
|
201
|
+
#blockRows(id, block, width) {
|
|
202
|
+
const component = this.#transcript.get(id);
|
|
203
|
+
if (component === undefined)
|
|
204
|
+
return [];
|
|
205
|
+
if (block === undefined || block.status !== "finalized")
|
|
206
|
+
return component.render(width);
|
|
207
|
+
const cached = this.#renderedRows.get(id);
|
|
208
|
+
if (cached && cached.width === width && cached.revision === block.revision)
|
|
209
|
+
return cached.rows;
|
|
210
|
+
const rows = component.render(width);
|
|
211
|
+
this.#renderedRows.set(id, { width, revision: block.revision, rows });
|
|
212
|
+
return rows;
|
|
213
|
+
}
|
|
170
214
|
transcriptComponent(id) {
|
|
171
215
|
return this.#transcript.get(id);
|
|
172
216
|
}
|
|
@@ -334,6 +378,8 @@ export class OwnedUiSessionShellRoot {
|
|
|
334
378
|
this.#extensionWorkingMessage = undefined;
|
|
335
379
|
this.#status.setWorkingOverride(undefined);
|
|
336
380
|
this.#footer.update(this.#viewWithExtensionStatuses(this.#view));
|
|
381
|
+
// An extension renderer may have drawn transcript blocks that are now unrendered by it.
|
|
382
|
+
this.#renderedRows.clear();
|
|
337
383
|
this.invalidate();
|
|
338
384
|
}
|
|
339
385
|
addExtensionNotification(message, type) {
|
|
@@ -384,11 +430,13 @@ export class OwnedUiSessionShellRoot {
|
|
|
384
430
|
this.#inputSurface.setFocused?.(focused);
|
|
385
431
|
}
|
|
386
432
|
dispose() {
|
|
433
|
+
this.#themeUnsubscribe();
|
|
387
434
|
this.header.dispose?.();
|
|
388
435
|
this.resources.dispose?.();
|
|
389
436
|
for (const component of this.#transcript.values())
|
|
390
437
|
component.dispose?.();
|
|
391
438
|
this.#transcript.clear();
|
|
439
|
+
this.#renderedRows.clear();
|
|
392
440
|
if (this.#inputSurface !== this.editor)
|
|
393
441
|
this.#inputSurface.dispose?.();
|
|
394
442
|
this.#extensionHeader?.dispose?.();
|
|
@@ -402,12 +450,16 @@ export class OwnedUiSessionShellRoot {
|
|
|
402
450
|
this.#queued.dispose?.();
|
|
403
451
|
}
|
|
404
452
|
#syncTranscript(blocks) {
|
|
453
|
+
this.#blocksById.clear();
|
|
454
|
+
for (const block of blocks)
|
|
455
|
+
this.#blocksById.set(block.id, block);
|
|
405
456
|
const nextIds = new Set(blocks.map(block => block.id));
|
|
406
457
|
for (const [id, component] of this.#transcript) {
|
|
407
458
|
if (id.startsWith("workflow-status-") || nextIds.has(id))
|
|
408
459
|
continue;
|
|
409
460
|
component.dispose?.();
|
|
410
461
|
this.#transcript.delete(id);
|
|
462
|
+
this.#renderedRows.delete(id);
|
|
411
463
|
}
|
|
412
464
|
for (const block of blocks) {
|
|
413
465
|
const component = this.#transcript.get(block.id);
|
|
@@ -431,8 +483,9 @@ export class OwnedUiSessionShellRoot {
|
|
|
431
483
|
if (block !== undefined)
|
|
432
484
|
order.push(block.id);
|
|
433
485
|
}
|
|
486
|
+
const placed = new Set(order);
|
|
434
487
|
for (const statusId of statusIds) {
|
|
435
|
-
if (!
|
|
488
|
+
if (!placed.has(statusId))
|
|
436
489
|
order.push(statusId);
|
|
437
490
|
}
|
|
438
491
|
this.#transcriptOrder = order;
|
|
@@ -498,6 +551,8 @@ export class OwnedUiSessionShellRoot {
|
|
|
498
551
|
this.resources.setExpanded(expanded);
|
|
499
552
|
for (const component of this.#transcript.values())
|
|
500
553
|
component.setExpanded(expanded);
|
|
554
|
+
// Expansion changes what a block draws without changing its revision.
|
|
555
|
+
this.#renderedRows.clear();
|
|
501
556
|
this.invalidate();
|
|
502
557
|
}
|
|
503
558
|
}
|
|
@@ -525,12 +580,14 @@ function shellResourceEntries(backend) {
|
|
|
525
580
|
sourcePath: resource.sourcePath,
|
|
526
581
|
diagnostic: resource.diagnostic,
|
|
527
582
|
}));
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
583
|
+
const extensions = backend.extensionResources().filter(extension => !extension.hidden);
|
|
584
|
+
const loadedExtensions = extensions.filter(extension => extension.diagnostic === null);
|
|
585
|
+
const extensionLabels = compactExtensionLabels(loadedExtensions);
|
|
586
|
+
for (const extension of extensions) {
|
|
587
|
+
const labelIndex = loadedExtensions.indexOf(extension);
|
|
531
588
|
resources.push({
|
|
532
589
|
section: "Extensions",
|
|
533
|
-
label: compactResourceLabel(extension.sourcePath ?? extension.resolvedPath ?? "extension"),
|
|
590
|
+
label: extensionLabels[labelIndex] ?? compactResourceLabel(extension.sourcePath ?? extension.resolvedPath ?? "extension"),
|
|
534
591
|
sourcePath: extension.sourcePath ?? extension.resolvedPath,
|
|
535
592
|
diagnostic: extension.diagnostic,
|
|
536
593
|
});
|
|
@@ -538,12 +595,129 @@ function shellResourceEntries(backend) {
|
|
|
538
595
|
return resources;
|
|
539
596
|
}
|
|
540
597
|
function compactResourceLabel(path) {
|
|
541
|
-
const segments = path
|
|
598
|
+
const segments = compactPathSegments(path);
|
|
542
599
|
const leaf = segments.at(-1) ?? path;
|
|
543
600
|
if ((leaf === "index.ts" || leaf === "index.js") && segments.length > 1)
|
|
544
601
|
return segments.at(-2) ?? leaf;
|
|
545
602
|
return leaf;
|
|
546
603
|
}
|
|
604
|
+
/**
|
|
605
|
+
* Pinned from InteractiveMode's compact extension-label helpers at Pi commit
|
|
606
|
+
* 914cf1472e715297caa30db4b9535d534a9eb718. The source metadata crosses an
|
|
607
|
+
* A1-owned boundary first; the owned shell never inspects Pi's private root.
|
|
608
|
+
*/
|
|
609
|
+
function compactExtensionLabels(extensions) {
|
|
610
|
+
const localExtensions = extensions
|
|
611
|
+
.filter(extension => !isPackageExtensionSource(extension.sourceInfo))
|
|
612
|
+
.map(extension => {
|
|
613
|
+
const path = extension.sourcePath ?? extension.resolvedPath ?? "extension";
|
|
614
|
+
const segments = compactPathSegments(path);
|
|
615
|
+
if (segments.length > 1 && (segments.at(-1) === "index.ts" || segments.at(-1) === "index.js"))
|
|
616
|
+
segments.pop();
|
|
617
|
+
return { extension, segments };
|
|
618
|
+
});
|
|
619
|
+
return extensions.map(extension => {
|
|
620
|
+
const resourcePath = extension.sourcePath ?? extension.resolvedPath ?? "extension";
|
|
621
|
+
if (isPackageExtensionSource(extension.sourceInfo)) {
|
|
622
|
+
return compactPackageExtensionLabel(resourcePath, extension.sourceInfo);
|
|
623
|
+
}
|
|
624
|
+
const localIndex = localExtensions.findIndex(item => item.extension === extension);
|
|
625
|
+
const segments = localExtensions[localIndex]?.segments;
|
|
626
|
+
if (!segments || segments.length === 0)
|
|
627
|
+
return compactResourceLabel(resourcePath);
|
|
628
|
+
for (let count = 1; count <= segments.length; count += 1) {
|
|
629
|
+
const candidate = segments.slice(-count).join("/");
|
|
630
|
+
if (localExtensions.every((item, itemIndex) => itemIndex === localIndex || item.segments.slice(-count).join("/") !== candidate)) {
|
|
631
|
+
return candidate;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return segments.join("/");
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
function compactPackageExtensionLabel(resourcePath, sourceInfo) {
|
|
638
|
+
const sourceLabel = compactPackageSourceLabel(sourceInfo.source);
|
|
639
|
+
if (!sourceLabel)
|
|
640
|
+
return compactResourceLabel(resourcePath);
|
|
641
|
+
const shortPath = shortPackagePath(resourcePath, sourceInfo).replaceAll("\\", "/");
|
|
642
|
+
const packagePath = shortPath.startsWith("extensions/") ? shortPath.slice("extensions/".length) : shortPath;
|
|
643
|
+
const slash = packagePath.lastIndexOf("/");
|
|
644
|
+
const fileName = slash < 0 ? packagePath : packagePath.slice(slash + 1);
|
|
645
|
+
const directory = slash < 0 ? "" : packagePath.slice(0, slash);
|
|
646
|
+
const extension = fileName.lastIndexOf(".");
|
|
647
|
+
const name = extension <= 0 ? fileName : fileName.slice(0, extension);
|
|
648
|
+
if (name === "index")
|
|
649
|
+
return !directory || directory === "." ? sourceLabel : `${sourceLabel}:${directory}`;
|
|
650
|
+
return `${sourceLabel}:${packagePath}`;
|
|
651
|
+
}
|
|
652
|
+
function compactPackageSourceLabel(source) {
|
|
653
|
+
if (source.startsWith("npm:"))
|
|
654
|
+
return source.slice("npm:".length) || source;
|
|
655
|
+
if (!source.startsWith("git:"))
|
|
656
|
+
return source;
|
|
657
|
+
const gitSource = source.slice("git:".length).trim();
|
|
658
|
+
let repositoryPath;
|
|
659
|
+
const scpLike = gitSource.match(/^git@[^:]+:(.+)$/);
|
|
660
|
+
if (scpLike?.[1]) {
|
|
661
|
+
repositoryPath = scpLike[1];
|
|
662
|
+
}
|
|
663
|
+
else if (/^[a-z]+:\/\//i.test(gitSource)) {
|
|
664
|
+
try {
|
|
665
|
+
repositoryPath = new URL(gitSource).pathname.replace(/^\/+/, "");
|
|
666
|
+
}
|
|
667
|
+
catch {
|
|
668
|
+
return source;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
const slash = gitSource.indexOf("/");
|
|
673
|
+
if (slash >= 0)
|
|
674
|
+
repositoryPath = gitSource.slice(slash + 1);
|
|
675
|
+
}
|
|
676
|
+
if (!repositoryPath)
|
|
677
|
+
return source;
|
|
678
|
+
const ref = repositoryPath.indexOf("@");
|
|
679
|
+
const withoutRef = ref < 0 ? repositoryPath : repositoryPath.slice(0, ref);
|
|
680
|
+
return withoutRef.replace(/\.git$/, "") || source;
|
|
681
|
+
}
|
|
682
|
+
function shortPackagePath(resourcePath, sourceInfo) {
|
|
683
|
+
const fullPath = normalizeResourcePath(resourcePath);
|
|
684
|
+
const baseDir = sourceInfo.baseDir === null ? undefined : normalizeResourcePath(sourceInfo.baseDir).replace(/\/$/, "");
|
|
685
|
+
if (baseDir) {
|
|
686
|
+
const npmRoot = baseDir.match(/^(.*\/node_modules)\/(@?[^/]+(?:\/[^/]+)?)$/);
|
|
687
|
+
if (npmRoot?.[1] && fullPath.startsWith(`${npmRoot[1]}/`))
|
|
688
|
+
return relativeResourcePath(baseDir, fullPath);
|
|
689
|
+
if (fullPath === baseDir)
|
|
690
|
+
return ".";
|
|
691
|
+
if (fullPath.startsWith(`${baseDir}/`))
|
|
692
|
+
return fullPath.slice(baseDir.length + 1);
|
|
693
|
+
}
|
|
694
|
+
const npmMatch = fullPath.match(/node_modules\/(@?[^/]+(?:\/[^/]+)?)\/(.*)/);
|
|
695
|
+
if (npmMatch?.[2] && sourceInfo.source.startsWith("npm:"))
|
|
696
|
+
return npmMatch[2];
|
|
697
|
+
const gitMatch = fullPath.match(/git\/[^/]+\/[^/]+\/(.*)/);
|
|
698
|
+
if (gitMatch?.[1] && sourceInfo.source.startsWith("git:"))
|
|
699
|
+
return gitMatch[1];
|
|
700
|
+
return resourcePath;
|
|
701
|
+
}
|
|
702
|
+
function relativeResourcePath(from, to) {
|
|
703
|
+
const fromSegments = from.split("/").filter(Boolean);
|
|
704
|
+
const toSegments = to.split("/").filter(Boolean);
|
|
705
|
+
let common = 0;
|
|
706
|
+
while (common < fromSegments.length && common < toSegments.length
|
|
707
|
+
&& fromSegments[common]?.toLowerCase() === toSegments[common]?.toLowerCase())
|
|
708
|
+
common += 1;
|
|
709
|
+
return [...fromSegments.slice(common).map(() => ".."), ...toSegments.slice(common)].join("/") || ".";
|
|
710
|
+
}
|
|
711
|
+
function compactPathSegments(path) {
|
|
712
|
+
return normalizeResourcePath(path).split("/").filter(segment => segment.length > 0 && segment !== "~");
|
|
713
|
+
}
|
|
714
|
+
function normalizeResourcePath(path) {
|
|
715
|
+
return path.replaceAll("\\", "/");
|
|
716
|
+
}
|
|
717
|
+
function isPackageExtensionSource(sourceInfo) {
|
|
718
|
+
const source = sourceInfo?.source ?? "";
|
|
719
|
+
return source.startsWith("npm:") || source.startsWith("git:");
|
|
720
|
+
}
|
|
547
721
|
export class OwnedUiSessionShell {
|
|
548
722
|
backend;
|
|
549
723
|
root;
|
|
@@ -560,6 +734,7 @@ export class OwnedUiSessionShell {
|
|
|
560
734
|
#sequence = 0;
|
|
561
735
|
#started = false;
|
|
562
736
|
#disposed = false;
|
|
737
|
+
#pointerReporting = false;
|
|
563
738
|
#compactionQueue = [];
|
|
564
739
|
#lastClearTime = 0;
|
|
565
740
|
#activeLoginDialog;
|
|
@@ -638,8 +813,13 @@ export class OwnedUiSessionShell {
|
|
|
638
813
|
});
|
|
639
814
|
this.root.editor.setAutocompleteCommands(this.backend.workflowAutocompleteCommands());
|
|
640
815
|
this.#unsubscribe = this.backend.onEvent(event => {
|
|
641
|
-
|
|
642
|
-
|
|
816
|
+
// A streamed chunk names one block, and touching only that block is what keeps the
|
|
817
|
+
// cost of a chunk the same in a long session as in a new one. Everything else
|
|
818
|
+
// resynchronizes the view, which is cheap next to re-reading the transcript.
|
|
819
|
+
const view = event.type === "transcript-block" && this.#sessionGeneration === this.backend.sessionGeneration
|
|
820
|
+
? this.#syncBlock(event.block)
|
|
821
|
+
: this.#syncView();
|
|
822
|
+
if (view.lifecycle === "ready" && this.#compactionQueue.length > 0)
|
|
643
823
|
void this.#flushCompactionQueue();
|
|
644
824
|
if (event.type === "session-lifecycle" && event.lifecycle === "stopped")
|
|
645
825
|
this.#resolveStopped?.();
|
|
@@ -1232,22 +1412,51 @@ export class OwnedUiSessionShell {
|
|
|
1232
1412
|
this.root.appendDaxnuts();
|
|
1233
1413
|
}
|
|
1234
1414
|
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Turns terminal pointer reporting on for a screen that reads the pointer, and off for
|
|
1417
|
+
* every path that ends it. While it is on the terminal hands A1 the wheel and the
|
|
1418
|
+
* button instead of scrolling and selecting itself, so leaving it on outlives the
|
|
1419
|
+
* screen that wanted it and takes the terminal's own scrolling and selection with it.
|
|
1420
|
+
*/
|
|
1421
|
+
#setPointerReporting(enabled) {
|
|
1422
|
+
if (this.#pointerReporting === enabled)
|
|
1423
|
+
return;
|
|
1424
|
+
this.#pointerReporting = enabled;
|
|
1425
|
+
if (!this.runtime.active)
|
|
1426
|
+
return;
|
|
1427
|
+
this.runtime.writeControl(enabled ? MOUSE_TRACKING_ON : MOUSE_TRACKING_OFF);
|
|
1428
|
+
}
|
|
1235
1429
|
async dispose() {
|
|
1236
1430
|
if (this.#disposed)
|
|
1237
1431
|
return;
|
|
1238
1432
|
this.#disposed = true;
|
|
1433
|
+
this.#setPointerReporting(false);
|
|
1239
1434
|
this.#unsubscribe();
|
|
1240
1435
|
this.#dialogHandle?.hide();
|
|
1241
1436
|
await this.backend.unbindExtensionUi();
|
|
1242
1437
|
this.#extensionBridge.dispose();
|
|
1243
1438
|
await this.runtime.dispose();
|
|
1244
1439
|
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Applies one transcript block without re-reading the session. The listeners still hear
|
|
1442
|
+
* the view they would have heard, so nothing downstream can tell the difference.
|
|
1443
|
+
*/
|
|
1444
|
+
#syncBlock(block) {
|
|
1445
|
+
this.root.applyTranscriptBlock(block);
|
|
1446
|
+
this.runtime.requestRender();
|
|
1447
|
+
const view = this.view();
|
|
1448
|
+
for (const listener of this.#listeners)
|
|
1449
|
+
listener(view);
|
|
1450
|
+
return view;
|
|
1451
|
+
}
|
|
1245
1452
|
#syncView() {
|
|
1246
1453
|
const view = this.view();
|
|
1247
1454
|
if (this.backend.sessionGeneration !== this.#sessionGeneration) {
|
|
1248
1455
|
this.#sessionGeneration = this.backend.sessionGeneration;
|
|
1249
1456
|
this.#activeLoginDialog = undefined;
|
|
1250
1457
|
this.#extensionBridge.reset();
|
|
1458
|
+
// A replaced session takes its screens with it, pointer reporting included.
|
|
1459
|
+
this.#setPointerReporting(false);
|
|
1251
1460
|
this.root.setInputSurface(null);
|
|
1252
1461
|
this.root.resetExtensionUi();
|
|
1253
1462
|
this.root.resetWorkflowPresentation();
|
|
@@ -1257,6 +1466,7 @@ export class OwnedUiSessionShell {
|
|
|
1257
1466
|
this.runtime.requestRender();
|
|
1258
1467
|
for (const listener of this.#listeners)
|
|
1259
1468
|
listener(view);
|
|
1469
|
+
return view;
|
|
1260
1470
|
}
|
|
1261
1471
|
#openOwnedRoute(route) {
|
|
1262
1472
|
const surface = this.#routeHost?.open(route) ?? null;
|
|
@@ -1267,7 +1477,7 @@ export class OwnedUiSessionShell {
|
|
|
1267
1477
|
this.#dialogHandle?.hide();
|
|
1268
1478
|
// Any-event reporting: hover and drag are what the screen is driven by, and
|
|
1269
1479
|
// it also stops the terminal treating a drag as a text selection.
|
|
1270
|
-
this
|
|
1480
|
+
this.#setPointerReporting(true);
|
|
1271
1481
|
// The interrupt chord is global, so it is watched on raw input rather than
|
|
1272
1482
|
// through the overlay: the pinned shell handles that key before an overlay
|
|
1273
1483
|
// ever sees it, which is why an owned screen must not rely on being asked.
|
|
@@ -1290,7 +1500,7 @@ export class OwnedUiSessionShell {
|
|
|
1290
1500
|
});
|
|
1291
1501
|
const closeSurface = () => {
|
|
1292
1502
|
removeInterruptWatch();
|
|
1293
|
-
this
|
|
1503
|
+
this.#setPointerReporting(false);
|
|
1294
1504
|
this.#dialogHandle?.hide();
|
|
1295
1505
|
this.#dialogHandle = undefined;
|
|
1296
1506
|
this.#dialogId = undefined;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export * from "./bootstrap.js";
|
|
2
2
|
export * from "./cohort-selection.js";
|
|
3
3
|
export * from "./cohort-state.js";
|
|
4
|
-
export * from "./development-preview-release.js";
|
|
5
4
|
export * from "./process-cleanup.js";
|
|
6
5
|
export * from "./release.js";
|
|
7
6
|
export * from "./release-gc.js";
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export * from "./bootstrap.js";
|
|
2
2
|
export * from "./cohort-selection.js";
|
|
3
3
|
export * from "./cohort-state.js";
|
|
4
|
-
export * from "./development-preview-release.js";
|
|
5
4
|
export * from "./process-cleanup.js";
|
|
6
5
|
export * from "./release.js";
|
|
7
6
|
export * from "./release-gc.js";
|
|
@@ -259,8 +259,16 @@ async function resolveRequestedPreview(runner, requested, output) {
|
|
|
259
259
|
}
|
|
260
260
|
const versions = (Array.isArray(published) ? published : [published]).filter((value) => typeof value === "string");
|
|
261
261
|
const exact = versions.find(version => version === requested);
|
|
262
|
-
if (exact !== undefined)
|
|
262
|
+
if (exact !== undefined) {
|
|
263
|
+
// Naming a release here would install it through the preview path, which is a
|
|
264
|
+
// different command with a different meaning. The commit form cannot express
|
|
265
|
+
// one, so only the fuller spelling of a preview reaches this.
|
|
266
|
+
if (!exact.includes("-dev.")) {
|
|
267
|
+
output.stderr(`${PRODUCT_TEXT.diagnostic(`${exact} is a release, not a preview; run ${PRODUCT_TEXT.commandName} update to move to the current release.`)}\n`);
|
|
268
|
+
return { version: null, exitCode: 1 };
|
|
269
|
+
}
|
|
263
270
|
return { version: exact, exitCode: 0 };
|
|
271
|
+
}
|
|
264
272
|
const matches = versions.filter(version => version.endsWith(`-dev.${requested}`));
|
|
265
273
|
if (matches.length === 1)
|
|
266
274
|
return { version: matches[0], exitCode: 0 };
|
|
@@ -306,7 +314,9 @@ export async function runSelfUpdate(options) {
|
|
|
306
314
|
if (resolved.version === null)
|
|
307
315
|
return resolved.exitCode;
|
|
308
316
|
const targetVersion = resolved.version;
|
|
309
|
-
|
|
317
|
+
// No full stop after a version: it already ends in a dot-separated identifier,
|
|
318
|
+
// and a trailing one reads as part of the version rather than as punctuation.
|
|
319
|
+
output.stdout(`${PRODUCT_TEXT.commandName} update (${UPDATE_CHANNEL_LABELS[channel]}): ${runningVersion} → ${targetVersion}\n`);
|
|
310
320
|
const progress = createUpdateProgress(output, options.progress ?? (options.output === undefined && process.stdout.isTTY === true));
|
|
311
321
|
const rootLookup = await measure("global-root", async () => await runNpm(runner, ["root", "--global"], true, output, "resolve npm's global package root"));
|
|
312
322
|
if (rootLookup.result === null)
|
|
@@ -395,7 +405,7 @@ export async function runSelfUpdate(options) {
|
|
|
395
405
|
await transactionStore.clearCompleted();
|
|
396
406
|
options.onPhaseTiming?.({ phase: "transaction-complete", durationMs: Math.max(0, now() - transactionStartedAt) });
|
|
397
407
|
progress.finish();
|
|
398
|
-
output.stdout(`${PRODUCT_TEXT.commandName} updated successfully: ${targetVersion}
|
|
408
|
+
output.stdout(`${PRODUCT_TEXT.commandName} updated successfully: ${targetVersion}\n`);
|
|
399
409
|
return 0;
|
|
400
410
|
}
|
|
401
411
|
catch (error) {
|
|
@@ -54,7 +54,7 @@ npm run release -- patch # or minor, major, or an exact x.y.z
|
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
It lands `x.y.z` on `develop` through a pull request that merges itself, waits for
|
|
57
|
-
that publication to succeed, and then lands `x.y.(z+1)-dev
|
|
57
|
+
that publication to succeed, and then lands `x.y.(z+1)-dev` so previews resume
|
|
58
58
|
immediately. It publishes nothing itself and creates no tag.
|
|
59
59
|
|
|
60
60
|
Landing the stable version is what publishes. The same pipeline sees a commit
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timurproko/a1",
|
|
3
|
-
"version": "0.1.8-dev.
|
|
3
|
+
"version": "0.1.8-dev.c64be0e",
|
|
4
4
|
"description": "Standalone terminal workspace for supervised native and managed agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@11.13.0",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
59
59
|
"@earendil-works/pi-tui": "0.84.2",
|
|
60
|
+
"chalk": "5.6.2",
|
|
60
61
|
"cross-spawn": "7.0.6",
|
|
61
62
|
"grok-mermaid": "0.2.2",
|
|
62
63
|
"semver": "7.8.5"
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
export declare const PREVIEW_RELEASE_SCHEMA: string;
|
|
2
|
-
export interface DevelopmentPreviewCandidate {
|
|
3
|
-
readonly version: string;
|
|
4
|
-
readonly requiresVersionCommit: boolean;
|
|
5
|
-
}
|
|
6
|
-
export interface DevelopmentPreviewRegistryState {
|
|
7
|
-
readonly published: boolean;
|
|
8
|
-
readonly nextVersion: string | null;
|
|
9
|
-
}
|
|
10
|
-
export interface DevelopmentPreviewVerificationOptions {
|
|
11
|
-
readonly attempts?: number;
|
|
12
|
-
readonly delayMs?: number;
|
|
13
|
-
readonly delay?: (milliseconds: number) => Promise<void>;
|
|
14
|
-
}
|
|
15
|
-
export interface DevelopmentPreviewPublishResult {
|
|
16
|
-
readonly published: boolean;
|
|
17
|
-
readonly recoveredPublishError: unknown | null;
|
|
18
|
-
}
|
|
19
|
-
export interface UncertifiedDevelopmentPreviewEvidenceInput {
|
|
20
|
-
readonly packageName: string;
|
|
21
|
-
readonly version: string;
|
|
22
|
-
readonly commit: string;
|
|
23
|
-
readonly tarball: string;
|
|
24
|
-
readonly integrity: string;
|
|
25
|
-
readonly shasum: string;
|
|
26
|
-
readonly platform: NodeJS.Platform;
|
|
27
|
-
readonly architecture: string;
|
|
28
|
-
readonly recordedAt: string;
|
|
29
|
-
}
|
|
30
|
-
export interface UncertifiedDevelopmentPreviewEvidence extends UncertifiedDevelopmentPreviewEvidenceInput {
|
|
31
|
-
readonly schema: typeof PREVIEW_RELEASE_SCHEMA;
|
|
32
|
-
readonly channel: "next";
|
|
33
|
-
readonly certificationStatus: "uncertified-development-preview";
|
|
34
|
-
readonly terminalCapability: "owned-ui";
|
|
35
|
-
readonly manualAcceptance: "accepted";
|
|
36
|
-
readonly physicalHostCertification: "deferred";
|
|
37
|
-
readonly crossPlatformCertification: "deferred";
|
|
38
|
-
readonly stableReleaseEligible: false;
|
|
39
|
-
}
|
|
40
|
-
export declare function createUncertifiedDevelopmentPreviewEvidence(input: UncertifiedDevelopmentPreviewEvidenceInput): UncertifiedDevelopmentPreviewEvidence;
|
|
41
|
-
export declare function requireManuallyAcceptedDevelopmentPreview(version: string, acceptedVersion: string): void;
|
|
42
|
-
export declare function selectDevelopmentPreviewCandidate(currentVersion: string, publishedVersions: readonly string[]): DevelopmentPreviewCandidate;
|
|
43
|
-
/**
|
|
44
|
-
* Treats npm's process result as provisional: browser-auth completion can fail
|
|
45
|
-
* after the immutable upload succeeds. Registry identity remains authoritative.
|
|
46
|
-
*/
|
|
47
|
-
export declare function publishDevelopmentPreviewWithRecovery(publish: () => Promise<void>, verify: () => Promise<void>): Promise<DevelopmentPreviewPublishResult>;
|
|
48
|
-
export declare function verifyDevelopmentPreviewRegistry(version: string, observe: () => Promise<DevelopmentPreviewRegistryState>, repairNextTag: () => Promise<void>, options?: DevelopmentPreviewVerificationOptions): Promise<void>;
|
|
49
|
-
export declare function developmentPreviewTarballName(packageName: string, version: string): string;
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { compare, inc, prerelease, valid } from "semver";
|
|
2
|
-
import { PRODUCT_IDENTITY } from "../../product-identity.js";
|
|
3
|
-
export const PREVIEW_RELEASE_SCHEMA = PRODUCT_IDENTITY.evidence.previewReleaseSchema;
|
|
4
|
-
export function createUncertifiedDevelopmentPreviewEvidence(input) {
|
|
5
|
-
const prereleaseParts = prerelease(input.version);
|
|
6
|
-
if (valid(input.version) === null || prereleaseParts?.[0] !== "dev") {
|
|
7
|
-
throw new Error(`uncertified preview requires a development prerelease: ${input.version}`);
|
|
8
|
-
}
|
|
9
|
-
return {
|
|
10
|
-
schema: PREVIEW_RELEASE_SCHEMA,
|
|
11
|
-
channel: "next",
|
|
12
|
-
certificationStatus: "uncertified-development-preview",
|
|
13
|
-
terminalCapability: "owned-ui",
|
|
14
|
-
manualAcceptance: "accepted",
|
|
15
|
-
physicalHostCertification: "deferred",
|
|
16
|
-
crossPlatformCertification: "deferred",
|
|
17
|
-
stableReleaseEligible: false,
|
|
18
|
-
...input,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
export function requireManuallyAcceptedDevelopmentPreview(version, acceptedVersion) {
|
|
22
|
-
const acceptedPrerelease = prerelease(acceptedVersion);
|
|
23
|
-
if (valid(acceptedVersion) === null || acceptedPrerelease?.[0] !== "dev") {
|
|
24
|
-
throw new Error(`invalid manually accepted development preview: ${acceptedVersion}`);
|
|
25
|
-
}
|
|
26
|
-
if (version !== acceptedVersion) {
|
|
27
|
-
throw new Error(`development preview ${version} has no exact manual acceptance; accepted version is ${acceptedVersion}`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
export function selectDevelopmentPreviewCandidate(currentVersion, publishedVersions) {
|
|
31
|
-
if (valid(currentVersion) === null)
|
|
32
|
-
throw new Error(`invalid current package version: ${currentVersion}`);
|
|
33
|
-
const published = publishedVersions.map(version => {
|
|
34
|
-
if (valid(version) === null)
|
|
35
|
-
throw new Error(`invalid published package version: ${version}`);
|
|
36
|
-
return version;
|
|
37
|
-
});
|
|
38
|
-
const publishedSet = new Set(published);
|
|
39
|
-
const highestPublished = published.reduce((highest, version) => highest === null || compare(version, highest) > 0 ? version : highest, null);
|
|
40
|
-
const currentPrerelease = prerelease(currentVersion);
|
|
41
|
-
const currentIsUnpublishedLeadingDev = currentPrerelease?.[0] === "dev"
|
|
42
|
-
&& !publishedSet.has(currentVersion)
|
|
43
|
-
&& (highestPublished === null || compare(currentVersion, highestPublished) > 0);
|
|
44
|
-
if (currentIsUnpublishedLeadingDev)
|
|
45
|
-
return { version: currentVersion, requiresVersionCommit: false };
|
|
46
|
-
const base = highestPublished === null || compare(currentVersion, highestPublished) > 0
|
|
47
|
-
? currentVersion
|
|
48
|
-
: highestPublished;
|
|
49
|
-
const basePrerelease = prerelease(base);
|
|
50
|
-
let candidate = basePrerelease?.[0] === "dev"
|
|
51
|
-
? inc(base, "prerelease", "dev")
|
|
52
|
-
: inc(base, "prepatch", "dev");
|
|
53
|
-
if (candidate === null)
|
|
54
|
-
throw new Error(`could not increment development preview from ${base}`);
|
|
55
|
-
while (publishedSet.has(candidate)) {
|
|
56
|
-
candidate = inc(candidate, "prerelease", "dev");
|
|
57
|
-
if (candidate === null)
|
|
58
|
-
throw new Error(`could not increment development preview from ${base}`);
|
|
59
|
-
}
|
|
60
|
-
return { version: candidate, requiresVersionCommit: candidate !== currentVersion };
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Treats npm's process result as provisional: browser-auth completion can fail
|
|
64
|
-
* after the immutable upload succeeds. Registry identity remains authoritative.
|
|
65
|
-
*/
|
|
66
|
-
export async function publishDevelopmentPreviewWithRecovery(publish, verify) {
|
|
67
|
-
let publishError = null;
|
|
68
|
-
try {
|
|
69
|
-
await publish();
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
publishError = error;
|
|
73
|
-
}
|
|
74
|
-
try {
|
|
75
|
-
await verify();
|
|
76
|
-
return { published: true, recoveredPublishError: publishError };
|
|
77
|
-
}
|
|
78
|
-
catch (verificationError) {
|
|
79
|
-
if (publishError !== null)
|
|
80
|
-
throw new AggregateError([publishError, verificationError], "npm publish failed and the exact version could not be verified in the registry");
|
|
81
|
-
throw verificationError;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
export async function verifyDevelopmentPreviewRegistry(version, observe, repairNextTag, options = {}) {
|
|
85
|
-
const attempts = options.attempts ?? 12;
|
|
86
|
-
const delayMs = options.delayMs ?? 2_000;
|
|
87
|
-
const delay = options.delay ?? (async (milliseconds) => await new Promise(resolvePromise => setTimeout(resolvePromise, milliseconds)));
|
|
88
|
-
if (!Number.isInteger(attempts) || attempts < 1)
|
|
89
|
-
throw new Error(`invalid registry verification attempts: ${attempts}`);
|
|
90
|
-
let repaired = false;
|
|
91
|
-
let last = { published: false, nextVersion: null };
|
|
92
|
-
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
93
|
-
last = await observe();
|
|
94
|
-
if (last.published && last.nextVersion === version)
|
|
95
|
-
return;
|
|
96
|
-
if (last.published && !repaired) {
|
|
97
|
-
await repairNextTag();
|
|
98
|
-
repaired = true;
|
|
99
|
-
}
|
|
100
|
-
if (attempt < attempts)
|
|
101
|
-
await delay(delayMs);
|
|
102
|
-
}
|
|
103
|
-
if (!last.published)
|
|
104
|
-
throw new Error(`npm registry did not expose published version ${version} after ${attempts} attempts`);
|
|
105
|
-
throw new Error(`npm next resolved ${last.nextVersion ?? "nothing"}; expected ${version} after ${attempts} attempts`);
|
|
106
|
-
}
|
|
107
|
-
export function developmentPreviewTarballName(packageName, version) {
|
|
108
|
-
if (valid(version) === null)
|
|
109
|
-
throw new Error(`invalid development preview version: ${version}`);
|
|
110
|
-
const unscopedName = packageName.startsWith("@") ? packageName.slice(1).replace("/", "-") : packageName;
|
|
111
|
-
if (!/^[a-z0-9._-]+$/i.test(unscopedName))
|
|
112
|
-
throw new Error(`invalid package name: ${packageName}`);
|
|
113
|
-
return `${unscopedName}-${version}.tgz`;
|
|
114
|
-
}
|