@timurproko/a1 0.2.5-dev.704 → 0.2.6-dev.717
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/dist/app/session-shell/session-shell-root.d.ts +3 -0
- package/dist/app/session-shell/session-shell.js +87 -48
- package/dist/composition/settings-route-host.js +2 -2
- package/dist/features/owned-ui/reference-routes.d.ts +1 -1
- package/dist/features/owned-ui/reference-routes.js +1 -1
- package/dist/features/owned-ui/resources/release-notes.json +4 -0
- package/dist/integrations/pi/components/lazy-selectors.d.ts +42 -0
- package/dist/integrations/pi/components/lazy-selectors.js +32 -0
- package/dist/integrations/pi/components/upstream/components/session-selector.js +5 -11
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +6 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +24 -1
- 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/docs/local-worktree-cleanup.md +12 -4
- package/docs/manual-owned-ui-checkpoint.md +1 -1
- package/docs/openspec-archive-automation.md +15 -15
- package/docs/releases/0.2.5.md +35 -0
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ import type { UiRouteHost, UiRouteInput } from "../../ui/apps/contracts.js";
|
|
|
5
5
|
import type { TranscriptViewportFrame, TranscriptViewportFrameDescriptor } from "../../ui/components/transcript-viewport.js";
|
|
6
6
|
import type { PiEngineAdapter } from "../../integrations/pi/engine/adapter.js";
|
|
7
7
|
import type { PiWorkflowMessage, PiWorkflowResult } from "../../integrations/pi/engine/workflows.js";
|
|
8
|
+
import type { PiShellLazySelectorLoader } from "../../integrations/pi/components/lazy-selectors.js";
|
|
8
9
|
import { type PiShellHotkeysPresentation } from "../../integrations/pi/components/shell-presenters-info.js";
|
|
9
10
|
import { type PiShellClipboardContent, type PiShellComponentPort, type PiShellEditorPort, type PiShellExtensionRendererResolver, type PiShellHeaderOptions, type PiShellHeaderPort, type PiShellImageAssetResolver, type PiShellLoadedResourcesPort, type PiShellResourceEntry, type PiShellTranscriptComponentPort } from "../../integrations/pi/components/shell-shared-facade.js";
|
|
10
11
|
import { type PiTuiInputCoordinationScheduler, type PiTuiInputSurfaceKind } from "../../integrations/pi/tui-runtime/input-presentation-coordinator.js";
|
|
@@ -116,6 +117,8 @@ export interface OwnedUiShellDiagnosticOptions {
|
|
|
116
117
|
readonly paste?: (event: PasteEvent) => void;
|
|
117
118
|
/** Test seams for isolated paste preparation: an in-process starter, or the forked helper's entry. */
|
|
118
119
|
readonly pastePreparation?: Omit<PastePreparationClientOptions, "onEvent" | "spareIdleMs">;
|
|
120
|
+
/** Test seam for pending and failed optional selector-module preparation. */
|
|
121
|
+
readonly lazySelectors?: PiShellLazySelectorLoader;
|
|
119
122
|
}
|
|
120
123
|
/** Live prompt-image admission policy supplied only by the bare-A1 profile. */
|
|
121
124
|
export interface OwnedUiShellPromptImagesOptions {
|
|
@@ -14,7 +14,7 @@ import { createPiExtensionUiBridge } from "../../integrations/pi/components/shel
|
|
|
14
14
|
import { createPiShellEditor } from "../../integrations/pi/components/shell-editor-autocomplete.js";
|
|
15
15
|
import { SKILLS_COMMAND_NAME, findSkillByArgument, rewriteSkillsTunnelSubmission, skillPrompt, skillsFromCommands, } from "../../integrations/pi/components/skills-command.js";
|
|
16
16
|
import { createPiQueuedInputStatus, createPiShellFooter, createPiShellHeader, createPiShellLoadedResources, createPiShellStatus, } from "../../integrations/pi/components/shell-footer-status.js";
|
|
17
|
-
import { createPiShellArmin, createPiShellAuthProviderSelector, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellExtensionSelector, createPiShellLoginDialog, createPiShellModelSelector, createPiShellModelsDialog, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellSkillsSelector,
|
|
17
|
+
import { createPiShellArmin, createPiShellAuthProviderSelector, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellExtensionSelector, createPiShellLoginDialog, createPiShellModelSelector, createPiShellModelsDialog, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellSkillsSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, } from "../../integrations/pi/components/shell-selectors-dialogs.js";
|
|
18
18
|
import { createPiShellChangelog, createPiShellHotkeys, createPiShellSessionInfo, renderPiShellPackageUpdateNotice, renderPiShellStatusText, } from "../../integrations/pi/components/shell-presenters-info.js";
|
|
19
19
|
import { createPiShellTranscriptComponent, renderPiShellStartupDiagnostic, renderPiShellTranscriptBlock, } from "../../integrations/pi/components/shell-presenters-transcript.js";
|
|
20
20
|
import { onPiThemeChange, piTheme } from "../../integrations/pi/components/upstream/theme/theme.js";
|
|
@@ -62,6 +62,8 @@ export class OwnedUiSessionShell {
|
|
|
62
62
|
// Invariant: translated startup diagnostics create at most one dock notice per kind per shell.
|
|
63
63
|
#startupTrustHandled = false;
|
|
64
64
|
#customViewport;
|
|
65
|
+
#lazySelectors;
|
|
66
|
+
#lazySelectorsPromise;
|
|
65
67
|
#responseCopy;
|
|
66
68
|
// Rationale: only an executor this shell created owns a spare copy helper worth warming and disposing.
|
|
67
69
|
#copyExecutor;
|
|
@@ -100,6 +102,7 @@ export class OwnedUiSessionShell {
|
|
|
100
102
|
this.#routeHost = routeHost ?? null;
|
|
101
103
|
this.#startupRoute = options.engine.startupRoute;
|
|
102
104
|
this.#customViewport = sessionLayout === "custom-viewport";
|
|
105
|
+
this.#lazySelectors = options.diagnostics?.lazySelectors;
|
|
103
106
|
this.#stopped = new Promise(resolve => {
|
|
104
107
|
this.#resolveStopped = resolve;
|
|
105
108
|
});
|
|
@@ -622,6 +625,7 @@ export class OwnedUiSessionShell {
|
|
|
622
625
|
if (this.#disposed || !this.#customViewport)
|
|
623
626
|
return;
|
|
624
627
|
this.#presentStartupRoute();
|
|
628
|
+
void this.#loadLazySelectors().then(selectors => selectors.prepare()).catch(() => { });
|
|
625
629
|
this.root.warmPastePreparation();
|
|
626
630
|
this.#copyExecutor?.warm();
|
|
627
631
|
});
|
|
@@ -900,6 +904,14 @@ export class OwnedUiSessionShell {
|
|
|
900
904
|
this.#dialogHandle = handle;
|
|
901
905
|
return handle;
|
|
902
906
|
}
|
|
907
|
+
#loadLazySelectors() {
|
|
908
|
+
if (this.#lazySelectors !== undefined)
|
|
909
|
+
return Promise.resolve(this.#lazySelectors);
|
|
910
|
+
return this.#lazySelectorsPromise ??= import("../../integrations/pi/components/lazy-selectors.js").then(module => {
|
|
911
|
+
this.#lazySelectors = module.piShellLazySelectors;
|
|
912
|
+
return module.piShellLazySelectors;
|
|
913
|
+
});
|
|
914
|
+
}
|
|
903
915
|
async showThinkingSelector() {
|
|
904
916
|
const snapshot = this.backend.pinnedSettingsSnapshot();
|
|
905
917
|
const close = () => {
|
|
@@ -911,14 +923,32 @@ export class OwnedUiSessionShell {
|
|
|
911
923
|
close();
|
|
912
924
|
void this.runWorkflow({ command: "thinking", argument: "", selection: level });
|
|
913
925
|
};
|
|
914
|
-
const
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
926
|
+
const releasePresentation = this.#customViewport ? this.runtime.beginPresentationHold() : undefined;
|
|
927
|
+
try {
|
|
928
|
+
const selectors = await this.#loadLazySelectors();
|
|
929
|
+
const component = await selectors.createThinking({
|
|
930
|
+
currentLevel: snapshot.thinkingLevel,
|
|
931
|
+
availableLevels: snapshot.availableThinkingLevels,
|
|
932
|
+
onSelect: select,
|
|
933
|
+
onCancel: close,
|
|
934
|
+
onSelectAsDefault: level => this.backend.setDefaultThinkingLevel(level),
|
|
935
|
+
defaultLevel: snapshot.defaultThinkingLevel,
|
|
936
|
+
...(this.#customViewport ? {
|
|
937
|
+
presentation: {
|
|
938
|
+
profile: "bare",
|
|
939
|
+
cycleBinding: this.root.editor.keybindingConfig()["app.thinking.cycle"] ?? [],
|
|
940
|
+
},
|
|
941
|
+
} : {}),
|
|
942
|
+
});
|
|
943
|
+
if (this.#disposed)
|
|
944
|
+
return;
|
|
945
|
+
this.root.setFooterLevel(false);
|
|
946
|
+
this.root.setInputSurface(component);
|
|
947
|
+
this.runtime.requestRender();
|
|
948
|
+
}
|
|
949
|
+
finally {
|
|
950
|
+
releasePresentation?.();
|
|
951
|
+
}
|
|
922
952
|
}
|
|
923
953
|
/** The Skills dialog: a replacement input like the model selector, applying the chosen skill through the prompt path. */
|
|
924
954
|
showSkillsSelector(skills = this.#skillSummaries()) {
|
|
@@ -1050,47 +1080,56 @@ export class OwnedUiSessionShell {
|
|
|
1050
1080
|
this.root.setInputSurface(null);
|
|
1051
1081
|
this.runtime.requestRender();
|
|
1052
1082
|
};
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
void this.backend.copyWorkflowText(text).then(acknowledged => {
|
|
1068
|
-
if (this.#disposed || generation !== this.backend.sessionBindingGeneration)
|
|
1083
|
+
const releasePresentation = this.#customViewport ? this.runtime.beginPresentationHold() : undefined;
|
|
1084
|
+
try {
|
|
1085
|
+
const selectors = await this.#loadLazySelectors();
|
|
1086
|
+
const component = await selectors.createTree({
|
|
1087
|
+
tree: context.tree,
|
|
1088
|
+
currentLeafId: context.currentLeafId,
|
|
1089
|
+
terminalHeight: this.runtime.viewport().rows,
|
|
1090
|
+
initialFilterMode: context.filterMode,
|
|
1091
|
+
...(initialSelectedId === undefined ? {} : { initialSelectedId }),
|
|
1092
|
+
onLabelChange: context.appendLabelChange,
|
|
1093
|
+
onCopy: text => {
|
|
1094
|
+
if (!text) {
|
|
1095
|
+
this.root.appendWorkflowResult({ command: "tree", outcome: "failed", message: "Selected entry has no text to copy" });
|
|
1096
|
+
this.runtime.requestRender();
|
|
1069
1097
|
return;
|
|
1070
|
-
|
|
1071
|
-
this.
|
|
1072
|
-
|
|
1073
|
-
|
|
1098
|
+
}
|
|
1099
|
+
const generation = this.backend.sessionBindingGeneration;
|
|
1100
|
+
void this.backend.copyWorkflowText(text).then(acknowledged => {
|
|
1101
|
+
if (this.#disposed || generation !== this.backend.sessionBindingGeneration)
|
|
1102
|
+
return;
|
|
1103
|
+
this.root.appendWorkflowStatus(acknowledged ? "Copied selected message to clipboard" : "Submitted selected message to clipboard");
|
|
1104
|
+
this.runtime.requestRender();
|
|
1105
|
+
}).catch(error => {
|
|
1106
|
+
if (this.#disposed || generation !== this.backend.sessionBindingGeneration)
|
|
1107
|
+
return;
|
|
1108
|
+
this.root.appendWorkflowResult({ command: "tree", outcome: "failed", message: error instanceof Error ? error.message : String(error) });
|
|
1109
|
+
this.runtime.requestRender();
|
|
1110
|
+
});
|
|
1111
|
+
},
|
|
1112
|
+
onCancel: close,
|
|
1113
|
+
onSelect: entryId => {
|
|
1114
|
+
if (entryId === context.currentLeafId) {
|
|
1115
|
+
close();
|
|
1116
|
+
this.root.appendWorkflowStatus("Already at this point");
|
|
1117
|
+
this.runtime.requestRender();
|
|
1074
1118
|
return;
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
void this.#completeTreeSelection(entryId, context.skipSummaryPrompt);
|
|
1090
|
-
},
|
|
1091
|
-
});
|
|
1092
|
-
this.root.setInputSurface(component);
|
|
1093
|
-
this.runtime.requestRender();
|
|
1119
|
+
}
|
|
1120
|
+
if (context.skipSummaryPrompt)
|
|
1121
|
+
close();
|
|
1122
|
+
void this.#completeTreeSelection(entryId, context.skipSummaryPrompt);
|
|
1123
|
+
},
|
|
1124
|
+
});
|
|
1125
|
+
if (this.#disposed)
|
|
1126
|
+
return;
|
|
1127
|
+
this.root.setInputSurface(component);
|
|
1128
|
+
this.runtime.requestRender();
|
|
1129
|
+
}
|
|
1130
|
+
finally {
|
|
1131
|
+
releasePresentation?.();
|
|
1132
|
+
}
|
|
1094
1133
|
}
|
|
1095
1134
|
showLoginAuthTypeSelector() {
|
|
1096
1135
|
const close = () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CHANGELOG_ROUTE,
|
|
1
|
+
import { CHANGELOG_ROUTE, HOTKEYS_ROUTE, HOTKEYS_TITLE } from "../features/owned-ui/reference-routes.js";
|
|
2
2
|
import { SETTINGS_APP_ID, SETTINGS_ROUTE } from "../features/owned-ui/settings-route.js";
|
|
3
3
|
import { piTheme } from "../integrations/pi/components/upstream/theme/theme.js";
|
|
4
4
|
import { faint } from "../ui/components/text.js";
|
|
@@ -15,7 +15,7 @@ export function createOwnedRouteHost(settings, references) {
|
|
|
15
15
|
}
|
|
16
16
|
const changelog = route === CHANGELOG_ROUTE;
|
|
17
17
|
const session = route === "session";
|
|
18
|
-
const title = session ? "Session Info" : changelog ?
|
|
18
|
+
const title = session ? "Session Info" : changelog ? input ? "What's New" : "Changelog" : HOTKEYS_TITLE;
|
|
19
19
|
const document = session ? references.session() : changelog ? references.changelog(input) : references.hotkeys();
|
|
20
20
|
return deferredSurface(route, title, () => loadReferenceSurface(settings, route, route, title, document));
|
|
21
21
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const CHANGELOG_APP_ID = "changelog";
|
|
2
2
|
export declare const CHANGELOG_ROUTE = "changelog";
|
|
3
|
-
export declare const CHANGELOG_TITLE = "
|
|
3
|
+
export declare const CHANGELOG_TITLE = "Changelog";
|
|
4
4
|
export declare const HOTKEYS_APP_ID = "hotkeys";
|
|
5
5
|
export declare const HOTKEYS_ROUTE = "hotkeys";
|
|
6
6
|
export declare const HOTKEYS_TITLE = "Keyboard Shortcuts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const CHANGELOG_APP_ID = "changelog";
|
|
2
2
|
export const CHANGELOG_ROUTE = "changelog";
|
|
3
|
-
export const CHANGELOG_TITLE = "
|
|
3
|
+
export const CHANGELOG_TITLE = "Changelog";
|
|
4
4
|
export const HOTKEYS_APP_ID = "hotkeys";
|
|
5
5
|
export const HOTKEYS_ROUTE = "hotkeys";
|
|
6
6
|
export const HOTKEYS_TITLE = "Keyboard Shortcuts";
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "a1-release-notes-v1",
|
|
3
3
|
"releases": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.2.5",
|
|
6
|
+
"markdown": "## [0.2.5] - 2026-10-07\n\n### New Features\n\n- show session info full screen ([#680](https://github.com/timurproko/a1/pull/680))\n- redesign session tree dialog ([#682](https://github.com/timurproko/a1/pull/682))\n- add name command input dialog ([#694](https://github.com/timurproko/a1/pull/694))\n\n### Changed\n\n- refine animated separator ([#681](https://github.com/timurproko/a1/pull/681))\n- upgrade pinned Pi to 1.0.4 ([#683](https://github.com/timurproko/a1/pull/683))\n- unify selection treatment ([#686](https://github.com/timurproko/a1/pull/686))\n- align project trust dialogs ([#687](https://github.com/timurproko/a1/pull/687))\n- standardize share dialog ([#689](https://github.com/timurproko/a1/pull/689))\n- align Resume Session dialog ([#693](https://github.com/timurproko/a1/pull/693))\n- unify dialog close hints ([#695](https://github.com/timurproko/a1/pull/695))\n- accent model scope markers ([#696](https://github.com/timurproko/a1/pull/696))\n\n### Fixed\n\n- wrap session file after label ([#684](https://github.com/timurproko/a1/pull/684))\n- align thinking markers with models ([#685](https://github.com/timurproko/a1/pull/685))\n- place new-session notice above prompt ([#688](https://github.com/timurproko/a1/pull/688))\n- close dialogs with Ctrl+C ([#690](https://github.com/timurproko/a1/pull/690))\n- restore suggestion after Backspace ([#691](https://github.com/timurproko/a1/pull/691))\n- cycle dialog filters backward ([#692](https://github.com/timurproko/a1/pull/692))\n- avoid initial value-menu highlight ([#697](https://github.com/timurproko/a1/pull/697))\n- keep session root expanded ([#698](https://github.com/timurproko/a1/pull/698))\n- hide skill prefixes in collapsed menu ([#699](https://github.com/timurproko/a1/pull/699))\n- omit space after command completion ([#700](https://github.com/timurproko/a1/pull/700))\n- stabilize values and add undo ([#701](https://github.com/timurproko/a1/pull/701))\n- standardize typing search hints ([#702](https://github.com/timurproko/a1/pull/702))\n- show tree time toggle state ([#703](https://github.com/timurproko/a1/pull/703))\n- refine resume search hints ([#704](https://github.com/timurproko/a1/pull/704))\n"
|
|
7
|
+
},
|
|
4
8
|
{
|
|
5
9
|
"version": "0.2.4",
|
|
6
10
|
"markdown": "## [0.2.4] - 2026-10-05\n\n### Changed\n\n- upgrade pinned Pi to 1.0.0 ([#658](https://github.com/timurproko/a1/pull/658))\n- upgrade pinned Pi to 1.0.2 ([#669](https://github.com/timurproko/a1/pull/669))\n- add animated mark, section art, and browser preview ([#665](https://github.com/timurproko/a1/pull/665))\n- keep the hero mark still and link only it to the site ([#666](https://github.com/timurproko/a1/pull/666))\n- pick dark-mode art via picture sources ([#668](https://github.com/timurproko/a1/pull/668))\n\n### Fixed\n\n- make self-update upgrades reliable ([#664](https://github.com/timurproko/a1/pull/664))\n- restore the terminal and print a resume hint when a1 is killed ([#667](https://github.com/timurproko/a1/pull/667))\n- align progress with Pi accent ([#671](https://github.com/timurproko/a1/pull/671))\n- support Git URL rewrites ([#673](https://github.com/timurproko/a1/pull/673))\n- support repository relocation ([#675](https://github.com/timurproko/a1/pull/675))\n- prevent bottom control gutter bleed ([#677](https://github.com/timurproko/a1/pull/677))\n"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { PiShellComponentPort } from "./shell-shared-facade.js";
|
|
2
|
+
import type { ThinkingSelectorLevel } from "./upstream/components/thinking-selector.js";
|
|
3
|
+
import type { FilterMode } from "./upstream/components/tree-selector.js";
|
|
4
|
+
type ThinkingSelectorModule = typeof import("./thinking-selector-dialog.js");
|
|
5
|
+
type TreeSelectorModule = typeof import("./upstream/components/tree-selector.js");
|
|
6
|
+
export interface PiShellLazyThinkingSelectorOptions {
|
|
7
|
+
readonly currentLevel: ThinkingSelectorLevel;
|
|
8
|
+
readonly availableLevels: readonly ThinkingSelectorLevel[];
|
|
9
|
+
readonly onSelect: (level: ThinkingSelectorLevel) => void;
|
|
10
|
+
readonly onCancel: () => void;
|
|
11
|
+
readonly onSelectAsDefault?: (level: ThinkingSelectorLevel) => void;
|
|
12
|
+
readonly defaultLevel?: ThinkingSelectorLevel;
|
|
13
|
+
readonly presentation?: {
|
|
14
|
+
readonly profile: "bare";
|
|
15
|
+
readonly cycleBinding: string | readonly string[];
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export interface PiShellLazyTreeSelectorOptions {
|
|
19
|
+
readonly tree: readonly unknown[];
|
|
20
|
+
readonly currentLeafId: string | null;
|
|
21
|
+
readonly terminalHeight: number;
|
|
22
|
+
readonly onSelect: (id: string) => void;
|
|
23
|
+
readonly onCancel: () => void;
|
|
24
|
+
readonly onLabelChange: (entryId: string, label: string | undefined) => void;
|
|
25
|
+
readonly onCopy?: (text: string | undefined) => void;
|
|
26
|
+
readonly initialSelectedId?: string;
|
|
27
|
+
readonly initialFilterMode?: FilterMode;
|
|
28
|
+
}
|
|
29
|
+
export interface PiShellLazySelectorLoader {
|
|
30
|
+
prepare(): Promise<void>;
|
|
31
|
+
createThinking(options: PiShellLazyThinkingSelectorOptions): Promise<PiShellComponentPort>;
|
|
32
|
+
createTree(options: PiShellLazyTreeSelectorOptions): Promise<PiShellComponentPort>;
|
|
33
|
+
}
|
|
34
|
+
export interface PiShellLazySelectorImports {
|
|
35
|
+
readonly thinking: () => Promise<ThinkingSelectorModule>;
|
|
36
|
+
readonly tree: () => Promise<TreeSelectorModule>;
|
|
37
|
+
}
|
|
38
|
+
/** Builds one idempotent selector loader; fulfilled, in-flight, and rejected imports remain shared. */
|
|
39
|
+
export declare function createPiShellLazySelectorLoader(imports?: PiShellLazySelectorImports): PiShellLazySelectorLoader;
|
|
40
|
+
/** Production selector loader, dynamically reached only after the first input-ready frame. */
|
|
41
|
+
export declare const piShellLazySelectors: PiShellLazySelectorLoader;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { componentPort, ensureTheme } from "./shell-shared-facade.js";
|
|
2
|
+
const DEFAULT_IMPORTS = {
|
|
3
|
+
thinking: () => import("./thinking-selector-dialog.js"),
|
|
4
|
+
tree: () => import("./upstream/components/tree-selector.js"),
|
|
5
|
+
};
|
|
6
|
+
/** Builds one idempotent selector loader; fulfilled, in-flight, and rejected imports remain shared. */
|
|
7
|
+
export function createPiShellLazySelectorLoader(imports = DEFAULT_IMPORTS) {
|
|
8
|
+
let thinkingSelectorModule;
|
|
9
|
+
let treeSelectorModule;
|
|
10
|
+
const loadThinkingSelector = () => thinkingSelectorModule ??= imports.thinking();
|
|
11
|
+
const loadTreeSelector = () => treeSelectorModule ??= imports.tree();
|
|
12
|
+
return {
|
|
13
|
+
async prepare() {
|
|
14
|
+
// Performance: allSettled observes background failures while retaining each rejected module for explicit route recovery.
|
|
15
|
+
await Promise.allSettled([loadThinkingSelector(), loadTreeSelector()]);
|
|
16
|
+
},
|
|
17
|
+
async createThinking(options) {
|
|
18
|
+
const { createPiShellThinkingSelector } = await loadThinkingSelector();
|
|
19
|
+
return createPiShellThinkingSelector(options.currentLevel, options.availableLevels, options.onSelect, options.onCancel, options.onSelectAsDefault, options.defaultLevel, options.presentation);
|
|
20
|
+
},
|
|
21
|
+
async createTree(options) {
|
|
22
|
+
ensureTheme();
|
|
23
|
+
const { TreeSelectorComponent } = await loadTreeSelector();
|
|
24
|
+
const selector = new TreeSelectorComponent([...options.tree], options.currentLeafId, options.terminalHeight, options.onSelect, options.onCancel, options.onLabelChange, options.initialSelectedId, options.initialFilterMode);
|
|
25
|
+
if (options.onCopy)
|
|
26
|
+
selector.onCopy = options.onCopy;
|
|
27
|
+
return componentPort(selector);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** Production selector loader, dynamically reached only after the first input-ready frame. */
|
|
32
|
+
export const piShellLazySelectors = createPiShellLazySelectorLoader();
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* frame with standalone title, standard outer rules, stable filter/status row, progressive result
|
|
9
9
|
* paging, aligned result columns, Session Tree selection roles, quiet in-field query syntax guidance,
|
|
10
10
|
* and one-line bottom dynamic feedback and searchable-dialog shortcut footer. Selected session rows
|
|
11
|
-
* use the blue selection surface
|
|
12
|
-
* weight.
|
|
11
|
+
* use the blue selection surface while preserving title and metadata foregrounds; the active session
|
|
12
|
+
* title uses success green independently of keyboard selection, with ordinary text weight.
|
|
13
13
|
* Deviations: owned-modal-shortcut-hints, owned-resume-session-dialog,
|
|
14
14
|
* owned-standard-dialog-selection.
|
|
15
15
|
*/
|
|
@@ -473,19 +473,13 @@ class SessionList {
|
|
|
473
473
|
messageColor = "error";
|
|
474
474
|
}
|
|
475
475
|
else if (isCurrent) {
|
|
476
|
-
messageColor = "
|
|
476
|
+
messageColor = "success";
|
|
477
477
|
}
|
|
478
478
|
else if (hasName) {
|
|
479
479
|
messageColor = "warning";
|
|
480
480
|
}
|
|
481
|
-
const styledMsg =
|
|
482
|
-
|
|
483
|
-
: isSelected
|
|
484
|
-
? theme.fg("success", truncatedMsg)
|
|
485
|
-
: messageColor
|
|
486
|
-
? theme.fg(messageColor, truncatedMsg)
|
|
487
|
-
: truncatedMsg;
|
|
488
|
-
const metadataColor = isConfirmingDelete ? "error" : isSelected ? "muted" : "dim";
|
|
481
|
+
const styledMsg = messageColor ? theme.fg(messageColor, truncatedMsg) : truncatedMsg;
|
|
482
|
+
const metadataColor = isConfirmingDelete ? "error" : "dim";
|
|
489
483
|
const titleColumn = fitToWidth(cursor + theme.fg("dim", prefix) + styledMsg, titleColumnWidth, "…");
|
|
490
484
|
const pathColumn = pathColumnWidth > 0
|
|
491
485
|
? `${theme.fg(metadataColor, fitPathToWidth(pathText, pathColumnWidth, this.showPath))}${" ".repeat(columnGapWidth)}`
|
|
@@ -39,6 +39,12 @@ export declare class PiTuiRuntimeAdapter {
|
|
|
39
39
|
*/
|
|
40
40
|
freezePresentation(): void;
|
|
41
41
|
get presentationFrozen(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Keeps asynchronous replacement-surface construction atomic at the terminal. Renderers may
|
|
44
|
+
* compose while held, but their writes are dropped until the final forced repaint on release.
|
|
45
|
+
*/
|
|
46
|
+
beginPresentationHold(): () => void;
|
|
47
|
+
get presentationHeld(): boolean;
|
|
42
48
|
addPreInputListener(listener: PiTuiPreInputListener): () => void;
|
|
43
49
|
addInputListener(listener: PiTuiInputListener): () => void;
|
|
44
50
|
switchMode(mode: "regular" | "fullscreen"): boolean;
|
|
@@ -118,6 +118,7 @@ export class PiTuiRuntimeAdapter {
|
|
|
118
118
|
#rootDisposed = false;
|
|
119
119
|
#terminalProgress = false;
|
|
120
120
|
#presentationFrozen = false;
|
|
121
|
+
#presentationHolds = new Set();
|
|
121
122
|
constructor(options) {
|
|
122
123
|
this.#root = options.root;
|
|
123
124
|
this.#overlayGeometry = options.onOverlayGeometry === undefined ? undefined : new OverlayGeometryTracker(options.onOverlayGeometry);
|
|
@@ -128,7 +129,7 @@ export class PiTuiRuntimeAdapter {
|
|
|
128
129
|
// Invariant: the pinned renderer's frames pass through this gate, while writeControl and
|
|
129
130
|
// the stop sequence reach the terminal directly. Freezing drops frames without touching
|
|
130
131
|
// the renderer, so a scheduled repaint cannot land on top of the quit outro.
|
|
131
|
-
const gatedTerminal = frozenGateTerminal(this.#terminal, () => this.#presentationFrozen);
|
|
132
|
+
const gatedTerminal = frozenGateTerminal(this.#terminal, () => this.#presentationFrozen || this.#presentationHolds.size > 0);
|
|
132
133
|
const tracedTerminal = options.inputDiagnostics === undefined
|
|
133
134
|
? gatedTerminal
|
|
134
135
|
: diagnosticTerminal(gatedTerminal, phase => this.#traceRuntimePhase(phase));
|
|
@@ -320,6 +321,24 @@ export class PiTuiRuntimeAdapter {
|
|
|
320
321
|
get presentationFrozen() {
|
|
321
322
|
return this.#presentationFrozen;
|
|
322
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* Keeps asynchronous replacement-surface construction atomic at the terminal. Renderers may
|
|
326
|
+
* compose while held, but their writes are dropped until the final forced repaint on release.
|
|
327
|
+
*/
|
|
328
|
+
beginPresentationHold() {
|
|
329
|
+
this.#assertRunning("presentation hold");
|
|
330
|
+
const token = Symbol("presentation-hold");
|
|
331
|
+
this.#presentationHolds.add(token);
|
|
332
|
+
return () => {
|
|
333
|
+
if (!this.#presentationHolds.delete(token) || this.#presentationHolds.size > 0)
|
|
334
|
+
return;
|
|
335
|
+
if (this.active && !this.#presentationFrozen)
|
|
336
|
+
this.#tui.requestRender(true);
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
get presentationHeld() {
|
|
340
|
+
return this.#presentationHolds.size > 0;
|
|
341
|
+
}
|
|
323
342
|
addPreInputListener(listener) {
|
|
324
343
|
if (this.#preInputListeners.has(listener))
|
|
325
344
|
throw new TypeError("Pi TUI pre-input listener is already registered");
|
|
@@ -427,6 +446,7 @@ export class PiTuiRuntimeAdapter {
|
|
|
427
446
|
return this.#stopPromise;
|
|
428
447
|
if (this.#state === "idle") {
|
|
429
448
|
this.#state = "stopped";
|
|
449
|
+
this.#presentationHolds.clear();
|
|
430
450
|
this.#clearTerminalProgress();
|
|
431
451
|
this.#preInputListeners.clear();
|
|
432
452
|
this.#disposeRoot();
|
|
@@ -462,6 +482,7 @@ export class PiTuiRuntimeAdapter {
|
|
|
462
482
|
// Invariant: the gate opens only for the synchronous stop sequence, so no render
|
|
463
483
|
// scheduled during the outro can slip in before the alternate screen is left.
|
|
464
484
|
this.#presentationFrozen = false;
|
|
485
|
+
this.#presentationHolds.clear();
|
|
465
486
|
this.#tui.stop(stopOptions);
|
|
466
487
|
}
|
|
467
488
|
catch (error) {
|
|
@@ -599,6 +620,7 @@ export class PiTuiRuntimeAdapter {
|
|
|
599
620
|
}
|
|
600
621
|
#restoreAfterFailedStart() {
|
|
601
622
|
this.#presentationFrozen = false;
|
|
623
|
+
this.#presentationHolds.clear();
|
|
602
624
|
try {
|
|
603
625
|
this.#tui.stop();
|
|
604
626
|
}
|
|
@@ -615,6 +637,7 @@ export class PiTuiRuntimeAdapter {
|
|
|
615
637
|
}
|
|
616
638
|
#bestEffortTerminalRestore() {
|
|
617
639
|
this.#presentationFrozen = false;
|
|
640
|
+
this.#presentationHolds.clear();
|
|
618
641
|
this.#clearTerminalProgress();
|
|
619
642
|
try {
|
|
620
643
|
if (this.mode === "fullscreen")
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "darwin",
|
|
6
6
|
"architecture": "arm64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-10-
|
|
8
|
+
"builtAt": "2026-10-08T10:43:26.855Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "51b0b2dc1c9ae577f2586c4217066929278726c2c15308d9144eb6f538e118ff",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "linux",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-10-
|
|
8
|
+
"builtAt": "2026-10-08T10:43:15.921Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "ef62679e83bc1fad3d38f9f21675d9256f08d20e67fd4791571032115ca1b0e9",
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"platform": "win32",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-10-
|
|
8
|
+
"builtAt": "2026-10-08T10:43:54.524Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "4c84729bd86ecee969b71895ccf86021af1f4b4e86e0906ff9ef78dbad03e6d7",
|
|
12
12
|
"size": 288256
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|
|
@@ -24,9 +24,17 @@ node scripts/governance/local-worktree-cleanup.mjs sweep --repo D:/Git/a1
|
|
|
24
24
|
|
|
25
25
|
`sweep` evaluates every released registration in one pass under the queue limits (100 registrations, 500 remote requests, a durable round-robin cursor) with a 180-second elapsed budget, since each merged candidate costs three evidence loads, and completes each candidate whose PR is verified merged using exactly the `complete` safeguards. It needs no `enable` and starts no process: its authority is each candidate's release. A candidate whose PR is open, draft, or not yet finalized reports `pending`; a candidate whose PR closed without merge reports `awaiting-discard` and is never touched; blockers report their exact reason. An old stop sentinel does not prevent a sweep, but `disable` run while a sweep is executing stops it before its next destructive step. If another session holds the mutation lock the sweep reports `mutation-busy` and the agent relays it as deferred rather than waiting. The JSON report carries a `lines` array with one relayable line per candidate and pruned branch, for example `#458 close-absent-and-skewed-cleanup: removed [worktree-removed, local-ref-removed]`. Pending or blocked results never delay the new delivery.
|
|
26
26
|
|
|
27
|
+
### Standalone documentation evidence
|
|
28
|
+
|
|
29
|
+
A PR without an `openspec-implementation` fence does not require corrective implementation association when its complete changed-file list is standalone documentation under the repository's existing documentation lifecycle policy. This includes ordinary documentation, the root `README.md`, canonical specification revisions, and revisions to existing active-change or archived-change documentation. Cleanup reuses the same path allowlist and immutable lifecycle checks as documentation auto-merge rather than maintaining a broader cleanup-only definition.
|
|
30
|
+
|
|
31
|
+
The PR must be non-draft, same-repository, merged into `develop`, and bound to the registered PR, head, and branch. Cleanup additionally requires successful required CI for that exact head, the merge commit to remain an ancestor of current `develop`, and the remote topic ref to be absent. Introduced active changes, implementation/code or mixed paths, release notes, acceptance records, malformed lifecycle metadata, incomplete changed-file evidence, and invalid rename shapes do not qualify. Those cases remain on their existing implementation-association or fail-closed paths.
|
|
32
|
+
|
|
33
|
+
Qualifying documentation uses the ordinary cleanup path without weakening it: exact worktree and local-ref identity, ownership, clean content, generated-content inspection, journaling, non-force Git removal, residue handling, and compare-and-delete local-ref behavior remain required. A blocker at any gate retains the worktree; agents never substitute manual deletion.
|
|
34
|
+
|
|
27
35
|
### Nothing left to remove
|
|
28
36
|
|
|
29
|
-
A released entry whose evidence can never verify (for example `delivery-content-drift` after a stale-base merge, or `source-association` for
|
|
37
|
+
A released entry whose evidence can never verify (for example `delivery-content-drift` after a stale-base merge, or `source-association` for an unassociated implementation PR) would otherwise be re-verified and reported `blocked` on every sweep. One narrow exception repairs an integrated unassociated delivery without rewriting its merged body: current `develop` must contain exactly one archived `association-repair.json` for the registered change, naming the exact original PR/head/merge and successful validation run, and its named corrective version-3 PR must itself have authorized manual merge, current exact-head CI, synchronized specs, a valid archive, and absent original/corrective remote refs. Only then does cleanup continue through all ordinary worktree identity, ownership, cleanliness, disposable-content, non-force removal, journaling, and local-ref safeguards. A mismatch remains blocked and generic merge/archive evidence never substitutes.
|
|
30
38
|
|
|
31
39
|
When no valid corrective association exists and such an entry's worktree path is gone, Git holds no live row for it (its own dangling row is retired), and its local topic ref is absent, the sweep and `complete` ask GitHub only whether the pull request merged into `develop` in this repository and then mark the journal `done` with `retired-nothing-left` and the original evidence reason. Nothing is deleted; a present path, row, or ref keeps the entry blocked, an unmerged PR keeps it `pending` or `awaiting-discard`, a deferred failure such as `remote-budget` is never treated as unverifiable, and preview retires nothing. For an all-absent entry whose PR is not merged, the maintainer runs the explicit form, which reads nothing from GitHub and deletes nothing:
|
|
32
40
|
|
|
@@ -64,7 +72,7 @@ node scripts/governance/local-worktree-cleanup.mjs complete \
|
|
|
64
72
|
|
|
65
73
|
The central disposable policy is `node_modules`, `dist`, `.builds`, `.artifacts`, `native/process-guardian/target`, `native/terminal-host/target`, and the exact generated file `src/integrations/pi/engine/pi-settings-metadata.json`. The `.artifacts` root is the repository's ignored generated-artifact root (finalization and validation reports, packed candidates, agent-written logs and diffs); the two native roots contain repository-generated Cargo output; Vitest writes the metadata file for source-side settings tests and leaves it shared for overlapping test processes until worktree cleanup. Agents select the owning worktree's `.artifacts/` root for their own transient delivery files rather than OS temp or another checkout, but its disposable status never makes scratch authoritative or permits secrets to be stored there. Registrations that still name `.artifacts/openspec-archive` or `.artifacts/validation` stay valid and are widened to the current policy on the next `complete`.
|
|
66
74
|
|
|
67
|
-
Each root
|
|
75
|
+
Each approved root must be an ignored regular file, directory, symbolic link, or Windows junction at that exact path inside the exact worktree. An exact-root link may target an external generated-content directory; cleanup records its link classification, filesystem identity, and canonical target, revalidates all three immediately before deletion, and removes only the link entry through a non-recursive primitive. It never traverses, removes, or mutates the external target. A descendant link remains stricter: its canonical target must stay inside that same exact disposable root. Escapes by descendant links, drift, replacements, unresolved/cyclic targets, special files, and nested repository boundaries remain blocking. Authority is component-exact: near matches such as `.artifacts-user`, `artifacts`, or `pi-settings-metadata-user.json`, arbitrary `target` directories, and sibling native projects remain blocking. Tracked/staged/unstaged/untracked content and every unknown ignored path still block. A tracked regular `.gitmodules` file alone is ordinary content; actual nested `.git` metadata, gitlinks, configured submodules, and submodule changes block. Ordinary content and these approved generated paths are traversed under separate finite entry allowances, so a normal dependency installation does not consume the ordinary source-tree allowance; both allowances retain the same deadline and content-boundary checks. Once those checks pass, cleanup deletes the declared disposable roots or files itself with a bounded retry for transient Windows sharing violations before handing the worktree to Git, so non-force Git removal only has to delete tracked content. A disposable path that stays locked after the retry budget reports `blocked` with `disposable-path-locked` (or `disposable-link-locked` for a link entry) and the path; the worktree, its `.git` pointer, and its journal are untouched, so stop the process holding the handle and rerun the same command.
|
|
68
76
|
|
|
69
77
|
Agents do not manually remove generated content, call `git worktree remove`, or delete the local branch after delivery. The JSON result is authoritative: report success only for `removed` or verified `already-absent`; otherwise retain the worktree and report the exact blocker. A handed-off entry whose worktree was deleted by hand completes through its journal, and its branch is deleted under the accepted-ancestry rule. Legacy roles can supply separate `--source-pr`, `--candidate-pr`, and `--role` values.
|
|
70
78
|
|
|
@@ -150,13 +158,13 @@ node scripts/governance/local-worktree-cleanup.mjs register --repo D:/Git/a1 --p
|
|
|
150
158
|
|
|
151
159
|
Registration binds the repository, exact directory/filesystem identity, candidate PR, HEAD/ref, role, and generation. It returns an ID and generation, not the owner token. The worktree stays `owned`: registration alone never authorizes removal. Supported roles:
|
|
152
160
|
|
|
153
|
-
- `implementation`: exact merged implementation PR head; candidate PR equals source PR. This is the only version-3 role because the same PR contains implementation, acceptance manifest, synchronized specs, and archive.
|
|
161
|
+
- `implementation`: exact merged implementation PR head, or exact merged standalone-documentation PR head under the policy above; candidate PR equals source PR. This is the only version-3 role because the same PR contains implementation, acceptance manifest, synchronized specs, and archive.
|
|
154
162
|
- `acceptance`: legacy exact verified implementation merge commit; candidate PR equals source PR.
|
|
155
163
|
- `archive`: legacy exact merged generated archive PR head; candidate PR is the archive PR and source PR is the implementation PR.
|
|
156
164
|
|
|
157
165
|
A CI/base/older-ancestor checkout does not qualify automatically. Version 3 has no local acceptance/archive checkout to register. No local archive checkout is required when a legacy archive was generated entirely on GitHub. Legacy version-1/version-2 linkage remains supported, but individually reviewing and registering a legacy checkout is explicit adoption, not a bulk sweep.
|
|
158
166
|
|
|
159
|
-
Low-level `register` keeps an explicit disposable allowlist for legacy and diagnostic use. The standard `complete` command supplies the central repository policy automatically; agents do not choose flags for normal completed-delivery cleanup. Neither route permits discarding tracked, staged, unstaged, untracked, unknown ignored, linked, special, or actual nested repository/submodule content. Declaring or centrally recognizing a generated directory does not bypass ignored-path, identity, type, or traversal checks.
|
|
167
|
+
Low-level `register` keeps an explicit disposable allowlist for legacy and diagnostic use. The standard `complete` command supplies the central repository policy automatically; agents do not choose flags for normal completed-delivery cleanup. Neither route permits discarding tracked, staged, unstaged, untracked, unknown ignored, unapproved linked, special, or actual nested repository/submodule content. The sole link exception is the guarded exact-root or contained descendant behavior above. Declaring or centrally recognizing a generated directory does not bypass ignored-path, identity, type, or traversal checks.
|
|
160
168
|
|
|
161
169
|
Before handing a completed checkout to cleanup, stop its development processes, leave its directory, and release it using the returned ID and current generation:
|
|
162
170
|
|
|
@@ -55,7 +55,7 @@ Use equivalent values in bare A1 and the Pi comparison profile. Exercise both a
|
|
|
55
55
|
- [ ] `/settings`: selected cursor, label and value accent; unselected values muted; descriptions and hints dim; search uses `> ` with inverse cursor; scalar menus, numeric controls, warning-parts dialog, deferred notice, failed write, section spacing, clipping, and hidden-entry absence match their pinned semantic roles.
|
|
56
56
|
- [ ] Transcript: toggle images and image width, thinking level/visibility, Mermaid mode, output padding, cache notices, warnings, auto-compaction, queue modes, command autocomplete, provider timeout/retry/error, and changelog collapse. Existing and streaming blocks must re-render without focus, selection, scroll, or queued-work loss.
|
|
57
57
|
- [ ] Trust startup: from an undecided project, compare selected Trust/Do not trust rows, arrow navigation, Enter, Escape/Ctrl+C, clearing, cursor state, and restoration. No project extension/theme/prompt/skill may run before selection, and a fail-closed diagnostic must appear once on the restored parent rather than above a blank fullscreen frame.
|
|
58
|
-
- [ ] Stable release note: with a built matching stable fixture from an explicitly approved draft GitHub Release snapshot and a fresh A1 profile, verify that exact note opens once after trust/other startup modals, fills the existing `What's New` screen, renders the Pi-style `## [version] - YYYY-MM-DD` heading and categorized `###` sections, scrolls by keys/wheel/rail, resizes, and closes with Escape. Relaunch to verify it stays closed, then use `/changelog` to verify version-labeled packaged history puts the latest A1 release at the top and the oldest at the bottom. A preview fixture must not auto-open or write acknowledgement state; `a1 pi` must retain Pi's oldest-first in-feed changelog so its latest release remains nearest the bottom.
|
|
58
|
+
- [ ] Stable release note: with a built matching stable fixture from an explicitly approved draft GitHub Release snapshot and a fresh A1 profile, verify that exact note opens once after trust/other startup modals, fills the existing `What's New` screen, renders the Pi-style `## [version] - YYYY-MM-DD` heading and categorized `###` sections, scrolls by keys/wheel/rail, resizes, and closes with Escape. Relaunch to verify it stays closed, then use `/changelog` to verify the distinct `Changelog` screen's version-labeled packaged history puts the latest A1 release at the top and the oldest at the bottom. A preview fixture must not auto-open or write acknowledgement state; `a1 pi` must retain Pi's oldest-first in-feed changelog so its latest release remains nearest the bottom.
|
|
59
59
|
- [ ] Terminal lifecycle: toggle hardware cursor, clear-on-shrink, and terminal progress; resize smaller/larger; open/close selectors; select and copy transcript text; verify no duplicate rows, stale OSC progress, leaked mouse mode, misplaced cursor, or broken parent input.
|
|
60
60
|
- [ ] Images: in Kitty or iTerm2 verify inline width and clipping; in Windows Terminal verify the textual fallback and absence of image protocol bytes without hiding `showImages`.
|
|
61
61
|
- [ ] Bare A1 quit (`/quit` and the second `Ctrl+C`): the configured quit effect plays over the last frame on the alternate screen, the terminal is restored once, and the parent shows only its earlier scrollback plus the dim `To resume this session:` hint. No frame rows, transcript rows, editor box, or footer may remain. With `/settings` → Quit → Effect set to `off`, the leave is immediate and the parent output is identical.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Atomic OpenSpec delivery and legacy archival
|
|
2
2
|
|
|
3
|
-
New OpenSpec deliveries use one ordinary development pull request from draft planning through implementation, validation, acceptance, specification synchronization, and archival.
|
|
3
|
+
New OpenSpec deliveries use one ordinary development pull request from draft planning through implementation, validation, acceptance, specification synchronization, and archival. An authorized maintainer chooses integration by manually merging the exact validated head or personally enabling GitHub native auto-merge for that unchanged head. Agents, Apps, bots, merge queue, and repository automation never arm or invoke implementation integration and never create a version-3 acceptance or archive follow-up PR.
|
|
4
4
|
|
|
5
|
-
Unrelated standalone documentation remains separate: a non-draft unassociated PR changing only `openspec/**`, `docs/**`, and/or root `README.md` retains the existing CI-gated automatic route. A new active change, implementation association, malformed lifecycle data, or mixed path
|
|
5
|
+
Unrelated standalone documentation remains separate: a non-draft unassociated PR changing only `openspec/**`, `docs/**`, and/or root `README.md` retains the existing CI-gated automatic route. A new active change, implementation association, malformed lifecycle data, or mixed path remains outside documentation-owned integration. Before finalization or without valid human provenance, any arm is disabled; a finalized version-3 human arm remains a separate maintainer-owned route.
|
|
6
6
|
|
|
7
7
|
Version-1 and version-2 deliveries and their existing comments, acceptance PRs, archive PRs, receipts, retries, and bounded catch-up remain supported under [Legacy delivery](#legacy-delivery). Do not silently convert an in-flight legacy delivery.
|
|
8
8
|
|
|
@@ -14,7 +14,7 @@ Version-1 and version-2 deliveries and their existing comments, acceptance PRs,
|
|
|
14
14
|
4. **Plain acceptance list:** keep the body phase-free and add final `## Acceptance` with one to ten concise implementation-specific behavior-and-result bullets. Do not use checkboxes, generic review/CI/approval/archive statements, URLs, mentions, or automated-test inventory.
|
|
15
15
|
5. **Ready and automated finalization:** mark the PR ready. The trusted `OpenSpec finalization` workflow reconciles current `develop`, conservatively synchronizes all deltas, moves the active change into its dated archive, stages the conditional acceptance manifest, commits that to the same branch with the archive App identity, and writes the emitted paths into the body's implementation fence. Running the [finalization command](#finalization-command) locally first is optional and yields the same bytes.
|
|
16
16
|
6. **Validate:** one normal exact-head workflow validates the finalized head: implementation, synchronized specs, archive, manifest, tasks/evidence, exact PR-body list, and every selected product/governance scope before emitting the stable protected aggregate. A new commit or acceptance-list change re-finalizes automatically when needed and requires full renewed validation; no lifecycle body edit or second workflow run is required.
|
|
17
|
-
7. **
|
|
17
|
+
7. **Maintainer integration accepts:** after reviewing the finalized candidate, an authorized human either manually merges the exact validated head after the stable protected aggregate succeeds or personally enables GitHub native auto-merge for that unchanged head so protected integration waits for the aggregate. That human action means the listed scenarios are accepted. Agents, Apps, bots, merge queue, automation-owned implementation arms, and documentation integration remain forbidden. A changed head, acceptance list, or finalization invalidates the earlier arm and requires the maintainer to choose again.
|
|
18
18
|
8. **Verify and clean:** trusted post-merge policy derives `Archived` and reports `accepted-and-archived` from committed bytes and immutable GitHub provenance without editing the accepted PR body. It publishes no lifecycle branch or PR. Shared exact-head remote cleanup may delete the unchanged topic ref; local cleanup remains separately ownership-controlled: the agent parks the worktree with `handoff` at step 7, and the next session's `sweep` removes it once the merge, archive, and remote-ref evidence verify (see [local cleanup](local-worktree-cleanup.md)).
|
|
19
19
|
|
|
20
20
|
### Complete-regression evidence ordering
|
|
@@ -38,7 +38,7 @@ Replace the multi-PR OpenSpec handoff with one manually merged development PR wh
|
|
|
38
38
|
|
|
39
39
|
- Keep planning and implementation in the same draft PR.
|
|
40
40
|
- Integrate implementation, synchronized specs, acceptance, and archive atomically.
|
|
41
|
-
- Preserve standalone documentation auto-merge while forbidding implementation auto-merge.
|
|
41
|
+
- Preserve standalone documentation auto-merge while forbidding automation-owned implementation auto-merge.
|
|
42
42
|
|
|
43
43
|
## Automation
|
|
44
44
|
|
|
@@ -75,7 +75,7 @@ Do not add a quoted phase line or a routine `Validation` section listing command
|
|
|
75
75
|
</details>
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
Keep acceptance absent during proposal review so unfinished intent is not mistaken for final acceptance criteria. The finalization workflow rewrites only the implementation fence; keep the rest of the phase-free body unchanged through exact-head validation, maintainer review, and authorized
|
|
78
|
+
Keep acceptance absent during proposal review so unfinished intent is not mistaken for final acceptance criteria. The finalization workflow rewrites only the implementation fence; keep the rest of the phase-free body unchanged through exact-head validation, maintainer review, and authorized integration. The same workflow run validates its finalized delivery record and applicable product/governance scopes before the stable protected aggregate succeeds. Do not add a lifecycle body edit or start a second validation run. After integration, trusted verification derives `Archived`; do not rewrite the accepted body.
|
|
79
79
|
|
|
80
80
|
## Agent scratch files
|
|
81
81
|
|
|
@@ -139,7 +139,7 @@ Use plain bullets:
|
|
|
139
139
|
- Finalized specs and archive match the implementation.
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
The committed conditional manifest contains the same ordered text. Trusted policy validates membership but never checks or edits it and never claims that the scenarios passed.
|
|
142
|
+
The committed conditional manifest contains the same ordered text. Trusted policy validates membership but never checks or edits it and never claims that the scenarios passed. The authorized maintainer's manual merge or personal native auto-merge arm for the unchanged exact head is the acceptance decision; no acceptance comment, checkbox edit, review-approval requirement, JSON edit, acceptance PR, archive PR, or later command is needed.
|
|
143
143
|
|
|
144
144
|
If the body list changes, candidate validation reruns and compares it with the committed manifest. If the head changes, all prior exact-head CI is stale. If only the body changes to disagree with the manifest, integration remains blocked until the list and committed candidate agree again. No lifecycle body edit is needed after green CI.
|
|
145
145
|
|
|
@@ -158,7 +158,7 @@ Because the branch gains commits from the archive App, pull before pushing. A re
|
|
|
158
158
|
|
|
159
159
|
A missing fence is not an ordinary ready PR when immutable base/head evidence shows that the candidate introduces or restores an active change, or mixes active-change edits with code or operational paths. Base-controlled readiness and finalization both fail with `missing-implementation-association`; deleting editable body metadata cannot bypass finalization. Existing active-change documentation-only revisions and ordinary code PRs without active delivery paths retain their established routes.
|
|
160
160
|
|
|
161
|
-
If an unassociated implementation has already integrated, do not edit its merged body or publish an archive-only follow-up. A corrective implementation-bound PR for the same still-active change must add the prevention fix and an exact `association-repair.json` record naming the original PR/head/merge/validation and the corrective PR. Normal version-3 finalization then synchronizes and archives the change; the corrective PR's authorized
|
|
161
|
+
If an unassociated implementation has already integrated, do not edit its merged body or publish an archive-only follow-up. A corrective implementation-bound PR for the same still-active change must add the prevention fix and an exact `association-repair.json` record naming the original PR/head/merge/validation and the corrective PR. Normal version-3 finalization then synchronizes and archives the change; the corrective PR's authorized maintainer integration accepts that repair without pretending the original PR was finalized retroactively.
|
|
162
162
|
|
|
163
163
|
## Finalization command
|
|
164
164
|
|
|
@@ -200,7 +200,7 @@ Before a first finalization, canonical specs must still equal the selected targe
|
|
|
200
200
|
|
|
201
201
|
## Conditional acceptance and derived receipt
|
|
202
202
|
|
|
203
|
-
`acceptance.md` truthfully says its verdict activates only when the containing exact head
|
|
203
|
+
`acceptance.md` truthfully says its verdict activates only when an authorized human manually merges the containing exact head after required current-head checks or personally arms native auto-merge for that unchanged head and GitHub integrates it after those checks. Its `openspec-delivery` block records stable pre-merge facts:
|
|
204
204
|
|
|
205
205
|
- schema version, repository, change, and source PR;
|
|
206
206
|
- archive and manifest paths and finalization date;
|
|
@@ -209,7 +209,7 @@ Before a first finalization, canonical specs must still equal the selected targe
|
|
|
209
209
|
- archive, canonical-spec, task, and evidence digests; and
|
|
210
210
|
- explicit known-gap dispositions.
|
|
211
211
|
|
|
212
|
-
It does not predict source head, merge commit, merger, or merge time. The PR head contains the manifest, so required CI binds those bytes without a recursive self-hash. After merge, the shared reader combines the manifest with immutable GitHub source head, successful required run, authorized human actor
|
|
212
|
+
It does not predict source head, merge commit, merger, or merge time. The PR head contains the manifest, so required CI binds those bytes without a recursive self-hash. After merge, the shared reader combines the manifest with immutable GitHub source head, successful required run, authorized human actor and permission, integration route, merge commit/time, and `develop` ancestry. Direct manual integration requires no active automatic authority. Human-enabled native auto-merge additionally requires a matching final-head enable event and `auto_merge.enabled_by`, with no later candidate commit or disable event. Queue, App/Bot, automation-owned, stale, malformed, or differently authored authority remains invalid. A human enable followed by disable before a valid manual merge is an abandoned attempt, not contradictory provenance. That shared result is the durable version-3 receipt used by audit and cleanup.
|
|
213
213
|
|
|
214
214
|
Unavailable, stale, automatic, unauthorized, conflicting, or contradictory provenance produces `invalid-provenance`/`blocked`, not acceptance. Post-merge code does not repair it by pushing `develop` or publishing another PR.
|
|
215
215
|
|
|
@@ -217,7 +217,7 @@ Unavailable, stale, automatic, unauthorized, conflicting, or contradictory prove
|
|
|
217
217
|
|
|
218
218
|
Normal Development CI remains complete for the implementation once its reviewable candidate exists. An archive-shaped final diff does not select documentation-only validation because the authoritative version-3 association remains implementation-bound. Base-controlled readiness defers a ready head that still holds the active change without running test suites or emitting `Development validation required`. The finalization workflow pushes the finalized head and updates its implementation fence; that update starts one complete selected validation, while PR-level concurrency cancels any superseded eligible run.
|
|
219
219
|
|
|
220
|
-
`pull_request` body edits rerun required CI. The ordinary finalized phase-free run exposes the stable protected aggregate directly; it does not wait for a lifecycle body edit. Documentation auto-merge's trusted owner also reevaluates lifecycle association and disables
|
|
220
|
+
`pull_request` body edits rerun required CI. The ordinary finalized phase-free run exposes the stable protected aggregate directly; it does not wait for a lifecycle body edit. Documentation auto-merge's trusted owner also reevaluates lifecycle association. It preserves only an authorized human arm for the exact finalized version-3 head, never creates or exercises that arm, and disables stale, automated, malformed, draft, edited, or otherwise ineligible arms. Every publication entry point explicitly refuses version 3.
|
|
221
221
|
|
|
222
222
|
The OpenSpec archive workflow remains default-branch trusted. For version 3 it uses read-only contents, PR, and Actions access to report the integrated result; App credentials are unnecessary and are not minted for post-merge verification. For legacy candidates it retains its existing scoped App publication behavior. The OpenSpec finalization workflow is the one version-3 user of the archive App: it mints a short-lived installation token with `contents: write` and `pull_requests: write`, pushes only to the candidate's own branch under a lease, edits only the body fence, and revokes the token when the run ends.
|
|
223
223
|
|
|
@@ -229,7 +229,7 @@ Read-only inspection remains:
|
|
|
229
229
|
GH_TOKEN="$(gh auth token)" node scripts/governance/reconcile-openspec-archive.mjs --dry-run --pr <implementation-pr>
|
|
230
230
|
```
|
|
231
231
|
|
|
232
|
-
For version 3, status distinguishes `needs-finalization`, `ready-for-
|
|
232
|
+
For version 3, status distinguishes `needs-finalization`, `ready-for-maintainer-integration`, `accepted-and-archived`, `closed`, `blocked`, and `invalid-provenance`. A successful workflow invocation does not imply acceptance while the PR is open. Dry run never mutates refs, PRs, body text, tasks, evidence, specs, or acceptance.
|
|
233
233
|
|
|
234
234
|
Scheduled scans continue to serve legacy catch-up. Version-3 candidates are verification-only and do not consume publication slots or block the legacy queue.
|
|
235
235
|
|
|
@@ -237,7 +237,7 @@ Scheduled scans continue to serve legacy catch-up. Version-3 candidates are veri
|
|
|
237
237
|
|
|
238
238
|
After version-3 merge, verify:
|
|
239
239
|
|
|
240
|
-
- the source PR is merged with the exact reviewed head
|
|
240
|
+
- the source PR is merged with the exact reviewed head through a verified authorized-human integration route;
|
|
241
241
|
- required exact-head CI succeeded;
|
|
242
242
|
- current `develop` descends from the merge;
|
|
243
243
|
- the active change is absent;
|
|
@@ -263,16 +263,16 @@ This bootstrap policy itself finishes under deployed version-2 authority. The fi
|
|
|
263
263
|
- draft planning remaining unmerged;
|
|
264
264
|
- explicit plan approval and same-PR implementation;
|
|
265
265
|
- automated current-target finalization and exact-head CI;
|
|
266
|
-
- authorized human manual merge with plain acceptance scenarios;
|
|
266
|
+
- authorized human integration, including manual merge and personally enabled exact-head native auto-merge, with plain acceptance scenarios;
|
|
267
267
|
- integrated canonical specs/archive and no generated acceptance/archive PR;
|
|
268
268
|
- read-only `accepted-and-archived` verification and exact-head branch cleanup;
|
|
269
|
-
- stale head/body/base, incomplete tasks/evidence, malformed manifest, and
|
|
269
|
+
- stale head/body/base/arm, incomplete tasks/evidence, malformed manifest, and automated or unauthorized auto-merge refusal fixtures; and
|
|
270
270
|
- an unrelated standalone spec/docs PR retaining auto-merge.
|
|
271
271
|
|
|
272
272
|
Unit tests or API success alone are not live acceptance. Until this evidence exists, version 3 is deployable but documented as awaiting its operational canary.
|
|
273
273
|
|
|
274
274
|
## Disable or roll back
|
|
275
275
|
|
|
276
|
-
Before a version-3 merge, disable new version-3 authoring and continue using legacy version 2. After a version-3 merge, preserve its integrated archive and provenance and use an ordinary explicit corrective PR. Never direct-push a rewritten receipt, delete historical evidence, relax branch protection, or reinterpret
|
|
276
|
+
Before a version-3 merge, disable new version-3 authoring and continue using legacy version 2. After a version-3 merge, preserve its integrated archive and provenance and use an ordinary explicit corrective PR. Never direct-push a rewritten receipt, delete historical evidence, relax branch protection, or reinterpret automation-owned, stale, App/Bot, or queue-backed integration as human acceptance.
|
|
277
277
|
|
|
278
278
|
Disabling the OpenSpec archive workflow stops legacy publication and version-3 post-merge reporting, but does not change existing acceptance records, documentation auto-merge, remote branch cleanup, or release authority.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
## [0.2.5] - 2026-10-07
|
|
2
|
+
|
|
3
|
+
### New Features
|
|
4
|
+
|
|
5
|
+
- show session info full screen ([#680](https://github.com/timurproko/a1/pull/680))
|
|
6
|
+
- redesign session tree dialog ([#682](https://github.com/timurproko/a1/pull/682))
|
|
7
|
+
- add name command input dialog ([#694](https://github.com/timurproko/a1/pull/694))
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- refine animated separator ([#681](https://github.com/timurproko/a1/pull/681))
|
|
12
|
+
- upgrade pinned Pi to 1.0.4 ([#683](https://github.com/timurproko/a1/pull/683))
|
|
13
|
+
- unify selection treatment ([#686](https://github.com/timurproko/a1/pull/686))
|
|
14
|
+
- align project trust dialogs ([#687](https://github.com/timurproko/a1/pull/687))
|
|
15
|
+
- standardize share dialog ([#689](https://github.com/timurproko/a1/pull/689))
|
|
16
|
+
- align Resume Session dialog ([#693](https://github.com/timurproko/a1/pull/693))
|
|
17
|
+
- unify dialog close hints ([#695](https://github.com/timurproko/a1/pull/695))
|
|
18
|
+
- accent model scope markers ([#696](https://github.com/timurproko/a1/pull/696))
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- wrap session file after label ([#684](https://github.com/timurproko/a1/pull/684))
|
|
23
|
+
- align thinking markers with models ([#685](https://github.com/timurproko/a1/pull/685))
|
|
24
|
+
- place new-session notice above prompt ([#688](https://github.com/timurproko/a1/pull/688))
|
|
25
|
+
- close dialogs with Ctrl+C ([#690](https://github.com/timurproko/a1/pull/690))
|
|
26
|
+
- restore suggestion after Backspace ([#691](https://github.com/timurproko/a1/pull/691))
|
|
27
|
+
- cycle dialog filters backward ([#692](https://github.com/timurproko/a1/pull/692))
|
|
28
|
+
- avoid initial value-menu highlight ([#697](https://github.com/timurproko/a1/pull/697))
|
|
29
|
+
- keep session root expanded ([#698](https://github.com/timurproko/a1/pull/698))
|
|
30
|
+
- hide skill prefixes in collapsed menu ([#699](https://github.com/timurproko/a1/pull/699))
|
|
31
|
+
- omit space after command completion ([#700](https://github.com/timurproko/a1/pull/700))
|
|
32
|
+
- stabilize values and add undo ([#701](https://github.com/timurproko/a1/pull/701))
|
|
33
|
+
- standardize typing search hints ([#702](https://github.com/timurproko/a1/pull/702))
|
|
34
|
+
- show tree time toggle state ([#703](https://github.com/timurproko/a1/pull/703))
|
|
35
|
+
- refine resume search hints ([#704](https://github.com/timurproko/a1/pull/704))
|
package/package.json
CHANGED