@timurproko/a1 0.1.8-dev.290 → 0.1.8-dev.322

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.
Files changed (112) hide show
  1. package/README.md +21 -0
  2. package/bin/guardian.js +4 -3
  3. package/bin/ui.js +4 -2
  4. package/bin/update-recovery.js +3 -1
  5. package/dist/composition/owned-ui.js +13 -1
  6. package/dist/contracts/owned-ui/index.d.ts +1 -0
  7. package/dist/contracts/owned-ui/index.js +1 -0
  8. package/dist/contracts/owned-ui/prompt-history.d.ts +37 -0
  9. package/dist/contracts/owned-ui/prompt-history.js +23 -0
  10. package/dist/features/launch/profile-paths.d.ts +2 -0
  11. package/dist/features/launch/profile-paths.js +11 -0
  12. package/dist/features/prompt-history/index.d.ts +2 -0
  13. package/dist/features/prompt-history/index.js +2 -0
  14. package/dist/features/prompt-history/paths.d.ts +4 -0
  15. package/dist/features/prompt-history/paths.js +16 -0
  16. package/dist/features/prompt-history/service.d.ts +24 -0
  17. package/dist/features/prompt-history/service.js +260 -0
  18. package/dist/features/prompt-history/store.d.ts +15 -0
  19. package/dist/features/prompt-history/store.js +166 -0
  20. package/dist/features/prompt-history/worker.d.ts +9 -0
  21. package/dist/features/prompt-history/worker.js +42 -0
  22. package/dist/foundation/launch-context/index.d.ts +29 -0
  23. package/dist/foundation/launch-context/index.js +83 -0
  24. package/dist/foundation/launch-guardian/main.js +4 -3
  25. package/dist/foundation/release/bootstrap.d.ts +1 -1
  26. package/dist/foundation/release/bootstrap.js +18 -12
  27. package/dist/foundation/release/cohort-state.d.ts +1 -0
  28. package/dist/foundation/release/cohort-state.js +4 -0
  29. package/dist/foundation/release/release-gc.js +2 -2
  30. package/dist/foundation/release/release-store.js +10 -3
  31. package/dist/foundation/release/release.d.ts +3 -1
  32. package/dist/foundation/release/release.js +7 -3
  33. package/dist/foundation/release/restart-certification.d.ts +1 -0
  34. package/dist/foundation/release/restart-certification.js +5 -0
  35. package/dist/foundation/release/update-recovery.d.ts +2 -0
  36. package/dist/foundation/release/update-recovery.js +4 -0
  37. package/dist/foundation/release/update.js +5 -0
  38. package/dist/foundation/release/warmup.d.ts +1 -1
  39. package/dist/foundation/release/warmup.js +5 -4
  40. package/dist/foundation/startup/startup-runtime.js +13 -11
  41. package/dist/foundation/supervision/main.js +8 -7
  42. package/dist/integrations/pi/components/editor-interaction.d.ts +38 -0
  43. package/dist/integrations/pi/components/editor-interaction.js +1 -0
  44. package/dist/integrations/pi/components/history-editor-loader.d.ts +5 -0
  45. package/dist/integrations/pi/components/history-editor-loader.js +5 -0
  46. package/dist/integrations/pi/components/index.d.ts +1 -0
  47. package/dist/integrations/pi/components/index.js +1 -0
  48. package/dist/integrations/pi/components/owned-editor-ux.d.ts +2 -1
  49. package/dist/integrations/pi/components/owned-editor-ux.js +95 -22
  50. package/dist/integrations/pi/components/shell-editor-autocomplete.js +12 -1
  51. package/dist/integrations/pi/components/shell-presenters-info.d.ts +9 -2
  52. package/dist/integrations/pi/components/shell-presenters-info.js +35 -4
  53. package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +3 -2
  54. package/dist/integrations/pi/components/shell-selectors-dialogs.js +8 -5
  55. package/dist/integrations/pi/components/shell-shared-facade.d.ts +15 -2
  56. package/dist/integrations/pi/components/upstream/assets/earendil-image.json +12 -0
  57. package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +25 -2
  58. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +13 -9
  59. package/dist/integrations/pi/components/upstream/components/owned-editor.js +137 -133
  60. package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +280 -0
  61. package/dist/integrations/pi/components/upstream/history/editor-core.js +2062 -0
  62. package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +32 -0
  63. package/dist/integrations/pi/components/upstream/history/kill-ring.js +48 -0
  64. package/dist/integrations/pi/components/upstream/history/printable-key.d.ts +1 -0
  65. package/dist/integrations/pi/components/upstream/history/printable-key.js +40 -0
  66. package/dist/integrations/pi/components/upstream/history/text-helpers.d.ts +14 -0
  67. package/dist/integrations/pi/components/upstream/history/text-helpers.js +28 -0
  68. package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +22 -0
  69. package/dist/integrations/pi/components/upstream/history/undo-stack.js +36 -0
  70. package/dist/integrations/pi/components/upstream/history/word-navigation.d.ts +24 -0
  71. package/dist/integrations/pi/components/upstream/history/word-navigation.js +100 -0
  72. package/dist/integrations/pi/engine/adapter.d.ts +13 -1
  73. package/dist/integrations/pi/engine/adapter.js +86 -25
  74. package/dist/integrations/pi/engine/changelog.d.ts +2 -0
  75. package/dist/integrations/pi/engine/changelog.js +63 -0
  76. package/dist/integrations/pi/engine/resources/changelog.json +12 -0
  77. package/dist/integrations/pi/engine/workflow-controllers.d.ts +1 -1
  78. package/dist/integrations/pi/engine/workflows.d.ts +1 -1
  79. package/dist/integrations/pi/session-ui/prompt-chips.d.ts +4 -1
  80. package/dist/integrations/pi/session-ui/prompt-chips.js +40 -13
  81. package/dist/integrations/pi/session-ui/prompt-history-controller.d.ts +30 -0
  82. package/dist/integrations/pi/session-ui/prompt-history-controller.js +147 -0
  83. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +33 -0
  84. package/dist/integrations/pi/session-ui/session-shell-root.js +68 -74
  85. package/dist/integrations/pi/session-ui/session-shell.d.ts +1 -1
  86. package/dist/integrations/pi/session-ui/session-shell.js +98 -12
  87. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +5 -0
  88. package/dist/integrations/pi/session-ui/session-viewport-controller.js +21 -15
  89. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +7 -0
  90. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +7 -6
  91. package/dist/native/darwin-arm64/manifest.json +1 -1
  92. package/dist/native/linux-x64/manifest.json +1 -1
  93. package/dist/native/win32-x64/manifest.json +2 -2
  94. package/dist/native/win32-x64/process-guardian.exe +0 -0
  95. package/dist/product-identity.d.ts +2 -2
  96. package/dist/product-identity.js +3 -36
  97. package/dist/product-identity.json +1 -22
  98. package/dist/ui/components/transcript-viewport.d.ts +8 -0
  99. package/dist/ui/components/transcript-viewport.js +23 -2
  100. package/dist/ui/components/visible-hyperlinks.d.ts +7 -0
  101. package/dist/ui/components/visible-hyperlinks.js +1 -0
  102. package/dist/ui/settings/declarations.js +19 -0
  103. package/dist/ui/settings/sections.js +11 -1
  104. package/dist/ui/settings/session.js +13 -4
  105. package/docs/architecture/boundaries.md +2 -0
  106. package/docs/architecture/history-editor-provenance.md +96 -0
  107. package/docs/architecture/internal-naming.md +89 -0
  108. package/docs/architecture/project-structure.md +1 -0
  109. package/docs/architecture/resource-and-data-policy.md +27 -1
  110. package/docs/architecture/toolchain.md +15 -1
  111. package/docs/features/prompt-history.md +149 -0
  112. package/package.json +8 -3
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Adapted from @earendil-works/pi-tui 0.84.2, packages/tui/src/kill-ring.ts (MIT).
3
+ * Source commit: 914cf1472e715297caa30db4b9535d534a9eb718.
4
+ * Modifications: public imports and owned editor-local typed seams; see docs/architecture/history-editor-provenance.md.
5
+ */
6
+ /**
7
+ * Ring buffer for Emacs-style kill/yank operations.
8
+ *
9
+ * Tracks killed (deleted) text entries. Consecutive kills can accumulate
10
+ * into a single entry. Supports yank (paste most recent) and yank-pop
11
+ * (cycle through older entries).
12
+ */
13
+ export declare class KillRing {
14
+ private ring;
15
+ /**
16
+ * Add text to the kill ring.
17
+ *
18
+ * @param text - The killed text to add
19
+ * @param opts - Push options
20
+ * @param opts.prepend - If accumulating, prepend (backward deletion) or append (forward deletion)
21
+ * @param opts.accumulate - Merge with the most recent entry instead of creating a new one
22
+ */
23
+ push(text: string, opts: {
24
+ prepend: boolean;
25
+ accumulate?: boolean;
26
+ }): void;
27
+ /** Get most recent entry without modifying the ring. */
28
+ peek(): string | undefined;
29
+ /** Move last entry to front (for yank-pop cycling). */
30
+ rotate(): void;
31
+ get length(): number;
32
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Adapted from @earendil-works/pi-tui 0.84.2, packages/tui/src/kill-ring.ts (MIT).
3
+ * Source commit: 914cf1472e715297caa30db4b9535d534a9eb718.
4
+ * Modifications: public imports and owned editor-local typed seams; see docs/architecture/history-editor-provenance.md.
5
+ */
6
+ /**
7
+ * Ring buffer for Emacs-style kill/yank operations.
8
+ *
9
+ * Tracks killed (deleted) text entries. Consecutive kills can accumulate
10
+ * into a single entry. Supports yank (paste most recent) and yank-pop
11
+ * (cycle through older entries).
12
+ */
13
+ export class KillRing {
14
+ ring = [];
15
+ /**
16
+ * Add text to the kill ring.
17
+ *
18
+ * @param text - The killed text to add
19
+ * @param opts - Push options
20
+ * @param opts.prepend - If accumulating, prepend (backward deletion) or append (forward deletion)
21
+ * @param opts.accumulate - Merge with the most recent entry instead of creating a new one
22
+ */
23
+ push(text, opts) {
24
+ if (!text)
25
+ return;
26
+ if (opts.accumulate && this.ring.length > 0) {
27
+ const last = this.ring.pop();
28
+ this.ring.push(opts.prepend ? text + last : last + text);
29
+ }
30
+ else {
31
+ this.ring.push(text);
32
+ }
33
+ }
34
+ /** Get most recent entry without modifying the ring. */
35
+ peek() {
36
+ return this.ring.length > 0 ? this.ring[this.ring.length - 1] : undefined;
37
+ }
38
+ /** Move last entry to front (for yank-pop cycling). */
39
+ rotate() {
40
+ if (this.ring.length > 1) {
41
+ const last = this.ring.pop();
42
+ this.ring.unshift(last);
43
+ }
44
+ }
45
+ get length() {
46
+ return this.ring.length;
47
+ }
48
+ }
@@ -0,0 +1 @@
1
+ export declare function decodePrintableKey(data: string): string | undefined;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Adapted from @earendil-works/pi-tui 0.84.2, packages/tui/src/keys.ts (MIT).
3
+ * Source commit: 914cf1472e715297caa30db4b9535d534a9eb718.
4
+ * Modifications: public imports and owned editor-local typed seams; see docs/architecture/history-editor-provenance.md.
5
+ */
6
+ import { decodeKittyPrintable } from "#pi-tui";
7
+ const MODIFIERS = {
8
+ shift: 1,
9
+ alt: 2,
10
+ ctrl: 4,
11
+ super: 8,
12
+ };
13
+ const LOCK_MASK = 64 + 128;
14
+ function parseModifyOtherKeysSequence(data) {
15
+ const match = data.match(/^\x1b\[27;(\d+);(\d+)~$/);
16
+ if (!match)
17
+ return null;
18
+ const modValue = parseInt(match[1], 10);
19
+ const codepoint = parseInt(match[2], 10);
20
+ return { codepoint, modifier: modValue - 1 };
21
+ }
22
+ function decodeModifyOtherKeysPrintable(data) {
23
+ const parsed = parseModifyOtherKeysSequence(data);
24
+ if (!parsed)
25
+ return undefined;
26
+ const modifier = parsed.modifier & ~LOCK_MASK;
27
+ if ((modifier & ~MODIFIERS.shift) !== 0)
28
+ return undefined;
29
+ if (!Number.isFinite(parsed.codepoint) || parsed.codepoint < 32)
30
+ return undefined;
31
+ try {
32
+ return String.fromCodePoint(parsed.codepoint);
33
+ }
34
+ catch {
35
+ return undefined;
36
+ }
37
+ }
38
+ export function decodePrintableKey(data) {
39
+ return decodeKittyPrintable(data) ?? decodeModifyOtherKeysPrintable(data);
40
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Get the shared grapheme segmenter instance.
3
+ */
4
+ export declare function getGraphemeSegmenter(): Intl.Segmenter;
5
+ /**
6
+ * Get the shared word segmenter instance.
7
+ */
8
+ export declare function getWordSegmenter(): Intl.Segmenter;
9
+ export declare const cjkBreakRegex: RegExp;
10
+ export declare const PUNCTUATION_REGEX: RegExp;
11
+ /**
12
+ * Check if a character is whitespace.
13
+ */
14
+ export declare function isWhitespaceChar(char: string): boolean;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Adapted from @earendil-works/pi-tui 0.84.2, packages/tui/src/utils.ts (MIT).
3
+ * Source commit: 914cf1472e715297caa30db4b9535d534a9eb718.
4
+ * Modifications: public imports and owned editor-local typed seams; see docs/architecture/history-editor-provenance.md.
5
+ */
6
+ // segmenters (shared instance)
7
+ const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
8
+ const wordSegmenter = new Intl.Segmenter(undefined, { granularity: "word" });
9
+ /**
10
+ * Get the shared grapheme segmenter instance.
11
+ */
12
+ export function getGraphemeSegmenter() {
13
+ return graphemeSegmenter;
14
+ }
15
+ /**
16
+ * Get the shared word segmenter instance.
17
+ */
18
+ export function getWordSegmenter() {
19
+ return wordSegmenter;
20
+ }
21
+ export const cjkBreakRegex = /[\p{Script_Extensions=Han}\p{Script_Extensions=Hiragana}\p{Script_Extensions=Katakana}\p{Script_Extensions=Hangul}\p{Script_Extensions=Bopomofo}]/u;
22
+ export const PUNCTUATION_REGEX = /[(){}[\]<>.,;:'"!?+\-=*/\\|&%^$#@~`]/;
23
+ /**
24
+ * Check if a character is whitespace.
25
+ */
26
+ export function isWhitespaceChar(char) {
27
+ return /\s/.test(char);
28
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Adapted from @earendil-works/pi-tui 0.84.2, packages/tui/src/undo-stack.ts (MIT).
3
+ * Source commit: 914cf1472e715297caa30db4b9535d534a9eb718.
4
+ * Modifications: public imports and owned editor-local typed seams; see docs/architecture/history-editor-provenance.md.
5
+ */
6
+ /**
7
+ * Generic undo stack with clone-on-push semantics.
8
+ *
9
+ * Stores deep clones of state snapshots. Popped snapshots are returned
10
+ * directly (no re-cloning) since they are already detached.
11
+ */
12
+ export declare class UndoStack<S> {
13
+ private stack;
14
+ /** Push a deep clone of the given state onto the stack. */
15
+ push(state: S): void;
16
+ /** Pop and return the most recent snapshot, or undefined if empty. */
17
+ pop(): S | undefined;
18
+ /** Remove all snapshots. */
19
+ clear(): void;
20
+ updateSnapshots(update: (snapshot: S) => void): void;
21
+ get length(): number;
22
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Adapted from @earendil-works/pi-tui 0.84.2, packages/tui/src/undo-stack.ts (MIT).
3
+ * Source commit: 914cf1472e715297caa30db4b9535d534a9eb718.
4
+ * Modifications: public imports and owned editor-local typed seams; see docs/architecture/history-editor-provenance.md.
5
+ */
6
+ /**
7
+ * Generic undo stack with clone-on-push semantics.
8
+ *
9
+ * Stores deep clones of state snapshots. Popped snapshots are returned
10
+ * directly (no re-cloning) since they are already detached.
11
+ */
12
+ export class UndoStack {
13
+ stack = [];
14
+ /** Push a deep clone of the given state onto the stack. */
15
+ push(state) {
16
+ this.stack.push(structuredClone(state));
17
+ }
18
+ /** Pop and return the most recent snapshot, or undefined if empty. */
19
+ pop() {
20
+ return this.stack.pop();
21
+ }
22
+ /** Remove all snapshots. */
23
+ clear() {
24
+ this.stack.length = 0;
25
+ }
26
+ updateSnapshots(update) {
27
+ this.stack = this.stack.map(snapshot => {
28
+ const copy = structuredClone(snapshot);
29
+ update(copy);
30
+ return copy;
31
+ });
32
+ }
33
+ get length() {
34
+ return this.stack.length;
35
+ }
36
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Options for word navigation functions.
3
+ * When omitted, uses the default Intl.Segmenter word segmentation.
4
+ */
5
+ export interface WordNavigationOptions {
6
+ /** Custom segmenter returning word segments for the given text. */
7
+ segment?: (text: string) => Iterable<Intl.SegmentData>;
8
+ /** Predicate identifying atomic segments that should be treated as single units (e.g. paste markers). */
9
+ isAtomicSegment?: (segment: string) => boolean;
10
+ }
11
+ /**
12
+ * Find the cursor position after moving one word backward from `cursor` in `text`.
13
+ * Skips trailing whitespace, then stops at the next word/punctuation boundary.
14
+ *
15
+ * Pure function - does not mutate any state.
16
+ */
17
+ export declare function findWordBackward(text: string, cursor: number, options?: WordNavigationOptions): number;
18
+ /**
19
+ * Find the cursor position after moving one word forward from `cursor` in `text`.
20
+ * Skips leading whitespace, then stops at the next word/punctuation boundary.
21
+ *
22
+ * Pure function - does not mutate any state.
23
+ */
24
+ export declare function findWordForward(text: string, cursor: number, options?: WordNavigationOptions): number;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Adapted from @earendil-works/pi-tui 0.84.2, packages/tui/src/word-navigation.ts (MIT).
3
+ * Source commit: 914cf1472e715297caa30db4b9535d534a9eb718.
4
+ * Modifications: public imports and owned editor-local typed seams; see docs/architecture/history-editor-provenance.md.
5
+ */
6
+ import { getWordSegmenter, isWhitespaceChar, PUNCTUATION_REGEX } from "./text-helpers.js";
7
+ const wordSegmenter = getWordSegmenter();
8
+ /**
9
+ * Find the cursor position after moving one word backward from `cursor` in `text`.
10
+ * Skips trailing whitespace, then stops at the next word/punctuation boundary.
11
+ *
12
+ * Pure function - does not mutate any state.
13
+ */
14
+ export function findWordBackward(text, cursor, options) {
15
+ if (cursor <= 0)
16
+ return 0;
17
+ const textBeforeCursor = text.slice(0, cursor);
18
+ const segmentFn = options?.segment;
19
+ const isAtomic = options?.isAtomicSegment;
20
+ const segments = segmentFn ? [...segmentFn(textBeforeCursor)] : [...wordSegmenter.segment(textBeforeCursor)];
21
+ let newCursor = cursor;
22
+ // Skip trailing whitespace
23
+ while (segments.length > 0 &&
24
+ !isAtomic?.(segments[segments.length - 1]?.segment || "") &&
25
+ isWhitespaceChar(segments[segments.length - 1]?.segment || "")) {
26
+ newCursor -= segments.pop()?.segment.length || 0;
27
+ }
28
+ if (segments.length === 0)
29
+ return newCursor;
30
+ const last = segments[segments.length - 1];
31
+ if (isAtomic?.(last.segment)) {
32
+ // Skip one atomic segment.
33
+ newCursor -= last.segment.length;
34
+ }
35
+ else if (last.isWordLike) {
36
+ // Skip inside one word-like segment, preserving ASCII punctuation boundaries.
37
+ const segment = last.segment;
38
+ const matches = [...segment.matchAll(new RegExp(PUNCTUATION_REGEX, "g"))];
39
+ if (matches.length <= 0) {
40
+ newCursor -= segment.length;
41
+ }
42
+ else {
43
+ const lastMatch = matches[matches.length - 1];
44
+ newCursor -= segment.length - (lastMatch.index + lastMatch[0].length);
45
+ }
46
+ }
47
+ else {
48
+ // Skip non-word non-whitespace run (punctuation)
49
+ while (segments.length > 0 &&
50
+ !isAtomic?.(segments[segments.length - 1]?.segment || "") &&
51
+ !segments[segments.length - 1]?.isWordLike &&
52
+ !isWhitespaceChar(segments[segments.length - 1]?.segment || "")) {
53
+ newCursor -= segments.pop()?.segment.length || 0;
54
+ }
55
+ }
56
+ return newCursor;
57
+ }
58
+ /**
59
+ * Find the cursor position after moving one word forward from `cursor` in `text`.
60
+ * Skips leading whitespace, then stops at the next word/punctuation boundary.
61
+ *
62
+ * Pure function - does not mutate any state.
63
+ */
64
+ export function findWordForward(text, cursor, options) {
65
+ if (cursor >= text.length)
66
+ return text.length;
67
+ const textAfterCursor = text.slice(cursor);
68
+ const segmentFn = options?.segment;
69
+ const isAtomic = options?.isAtomicSegment;
70
+ const segments = segmentFn ? segmentFn(textAfterCursor) : wordSegmenter.segment(textAfterCursor);
71
+ const iterator = segments[Symbol.iterator]();
72
+ let next = iterator.next();
73
+ let newCursor = cursor;
74
+ // Skip leading whitespace
75
+ while (!next.done && !isAtomic?.(next.value.segment) && isWhitespaceChar(next.value.segment)) {
76
+ newCursor += next.value.segment.length;
77
+ next = iterator.next();
78
+ }
79
+ if (next.done)
80
+ return newCursor;
81
+ if (isAtomic?.(next.value.segment)) {
82
+ // Skip one atomic segment.
83
+ newCursor += next.value.segment.length;
84
+ }
85
+ else if (next.value.isWordLike) {
86
+ // Skip inside one word-like segment, preserving ASCII punctuation boundaries.
87
+ newCursor += PUNCTUATION_REGEX.exec(next.value.segment)?.index ?? next.value.segment.length;
88
+ }
89
+ else {
90
+ // Skip non-word non-whitespace run (punctuation)
91
+ while (!next.done &&
92
+ !isAtomic?.(next.value.segment) &&
93
+ !next.value.isWordLike &&
94
+ !isWhitespaceChar(next.value.segment)) {
95
+ newCursor += next.value.segment.length;
96
+ next = iterator.next();
97
+ }
98
+ }
99
+ return newCursor;
100
+ }
@@ -1,5 +1,5 @@
1
1
  import type { PiSessionForkPrompt, PiSessionSelection } from "./session-selection.js";
2
- import { type AgentSessionRuntime, type AgentSessionServices, type SessionInfo } from "@earendil-works/pi-coding-agent";
2
+ import { type AgentSession, type AgentSessionRuntime, type AgentSessionServices, type SessionInfo } from "@earendil-works/pi-coding-agent";
3
3
  import { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, type OwnedUiCommand, type OwnedUiCommandOutcome, type OwnedUiImageAttachment, type OwnedUiPromptSuggestionGeneratorPort, type OwnedUiPromptSuggestionRequest, type OwnedUiPromptSuggestionResult, type OwnedUiEvent, type OwnedUiSessionViewModel, type OwnedUiSnapshot } from "../../../contracts/owned-ui/index.js";
4
4
  import { type PiAuthenticationProviderOption, type PiBashWorkflowResult, type PiPinnedSettingsCallback, type PiPinnedSettingsSnapshot, type PiWorkflowAutocompleteCommand, type PiWorkflowHost, type PiWorkflowInteractionHost, type PiWorkflowOption, type PiWorkflowRequest, type PiWorkflowResult } from "./workflows.js";
5
5
  import { PiSettingsIntegration } from "./settings-integration.js";
@@ -176,16 +176,28 @@ export declare class PiEngineAdapter implements OwnedUiPromptSuggestionGenerator
176
176
  readonly title: string;
177
177
  readonly options: readonly PiWorkflowOption[];
178
178
  };
179
+ pinnedAmbientAuthentication(selection: string): {
180
+ readonly providerId: string;
181
+ readonly providerName: string;
182
+ readonly title: string;
183
+ readonly message: string;
184
+ } | null;
179
185
  pinnedLogoutOptions(): Promise<readonly PiAuthenticationProviderOption[]>;
180
186
  pinnedForkOptions(): readonly PiWorkflowOption[];
181
187
  pinnedTreeSelectorContext(): PiTreeSelectorContext;
182
188
  pinnedSettingsSnapshot(): PiPinnedSettingsSnapshot;
183
189
  pinnedMessageRenderer(customType: string): unknown;
190
+ pinnedShortcutDescriptions(bindings: Parameters<AgentSession["extensionRunner"]["getShortcuts"]>[0]): readonly {
191
+ readonly key: string;
192
+ readonly description: string;
193
+ }[];
184
194
  pinnedToolDefinition(toolName: string): unknown;
195
+ copyWorkflowText(text: string): Promise<void>;
185
196
  clearQueuedWorkflows(): readonly string[];
186
197
  abortBashWorkflow(): void;
187
198
  executeBashWorkflow(command: string, excludeFromContext: boolean): Promise<PiBashWorkflowResult>;
188
199
  applyPinnedSettingValue(callback: PiPinnedSettingsCallback, value: unknown): Promise<PiWorkflowResult>;
200
+ reloadBlockedResult(): PiWorkflowResult | null;
189
201
  executeWorkflow(request: PiWorkflowRequest): Promise<PiWorkflowResult>;
190
202
  view(): OwnedUiSessionViewModel;
191
203
  snapshot(): OwnedUiSnapshot;
@@ -7,6 +7,7 @@ import { dirname, join, resolve } from "node:path";
7
7
  import { promisify } from "node:util";
8
8
  import { PRODUCT_IDENTITY } from "../../../product-identity.js";
9
9
  import { configureOwnedHttpDispatcher } from "./http-dispatcher.js";
10
+ import { readPinnedCommandChangelog } from "./changelog.js";
10
11
  import { copyToClipboard, CredentialSynchronizationError, DefaultPackageManager, getAgentDir, ProjectTrustStore, SessionManager, VERSION, } from "@earendil-works/pi-coding-agent";
11
12
  import { OWNED_UI_EXTENSION_CONTRACT_VERSION, OWNED_UI_EXTENSION_RENDER_CALLBACKS, OWNED_UI_EXTENSION_UI_CALLBACKS, OWNED_UI_EXTENSION_UI_PROPERTIES, CONTEXTUAL_PROMPT_SUGGESTION_INSTRUCTION, assertOwnedUiCommand, assertOwnedUiExtensionUiPort, assertOwnedUiPromptSuggestionRequest, assertOwnedUiPromptSuggestionResult, assertOwnedUiSnapshot, normalizePromptSuggestionCandidate, } from "../../../contracts/owned-ui/index.js";
12
13
  import { PINNED_PI_SETTINGS_CALLBACKS, PINNED_PI_WORKFLOW_COMMAND_NAMES, } from "./workflows.js";
@@ -308,14 +309,22 @@ export class PiEngineAdapter {
308
309
  if (!settingsManager || typeof settingsManager.getLastChangelogVersion !== "function"
309
310
  || typeof settingsManager.setLastChangelogVersion !== "function")
310
311
  return;
311
- if (settingsManager.getLastChangelogVersion() === VERSION)
312
+ if ((this.#session?.messages.length ?? 0) > 0)
312
313
  return;
313
- const markdown = await this.#workflowHost.readChangelog().catch(() => "");
314
+ const lastVersion = settingsManager.getLastChangelogVersion();
315
+ if (lastVersion === VERSION)
316
+ return;
317
+ if (!lastVersion) {
318
+ settingsManager.setLastChangelogVersion(VERSION);
319
+ await settingsManager.flush();
320
+ return;
321
+ }
322
+ const markdown = await this.#workflowHost.readChangelog(lastVersion).catch(() => "");
314
323
  if (markdown.trim().length > 0) {
315
324
  this.#addDiagnostic("info", settingsManager.getCollapseChangelog() ? "changelog-collapsed" : "changelog-expanded", markdown, true);
325
+ settingsManager.setLastChangelogVersion(VERSION);
326
+ await settingsManager.flush();
316
327
  }
317
- settingsManager.setLastChangelogVersion(VERSION);
318
- await settingsManager.flush();
319
328
  }
320
329
  async #announcePackageUpdates(settingsManager) {
321
330
  if (process.env.PI_OFFLINE)
@@ -877,6 +886,17 @@ export class PiEngineAdapter {
877
886
  }));
878
887
  return { title: `Select authentication method for ${providerName}:`, options };
879
888
  }
889
+ pinnedAmbientAuthentication(selection) {
890
+ if (!selection.startsWith("api_key:"))
891
+ return null;
892
+ const providerId = selection.slice("api_key:".length);
893
+ const provider = this.#runtime?.services.modelRuntime.getProvider?.(providerId);
894
+ const method = dynamicObject(dynamicObject(provider, "auth"), "apiKey");
895
+ if (!method || typeof method.login === "function")
896
+ return null;
897
+ const providerName = stringProperty(provider, "name") ?? providerId;
898
+ return { providerId, providerName, title: `${providerName} setup`, message: `${stringProperty(method, "name") ?? "Authentication"} is configured outside pi.` };
899
+ }
880
900
  pinnedLogoutOptions() {
881
901
  return this.#logoutOptions();
882
902
  }
@@ -957,9 +977,16 @@ export class PiEngineAdapter {
957
977
  pinnedMessageRenderer(customType) {
958
978
  return this.#requireWorkflowSession().extensionRunner?.getMessageRenderer?.(customType);
959
979
  }
980
+ pinnedShortcutDescriptions(bindings) {
981
+ const shortcuts = this.#requireWorkflowSession().extensionRunner?.getShortcuts?.(bindings);
982
+ return shortcuts === undefined ? [] : [...shortcuts].map(([key, shortcut]) => ({ key, description: shortcut.description ?? shortcut.extensionPath }));
983
+ }
960
984
  pinnedToolDefinition(toolName) {
961
985
  return this.#requireWorkflowSession().extensionRunner?.getToolDefinition?.(toolName);
962
986
  }
987
+ async copyWorkflowText(text) {
988
+ await this.#workflowHost.copyText(text);
989
+ }
963
990
  clearQueuedWorkflows() {
964
991
  const session = this.#requireWorkflowSession();
965
992
  const result = session.clearQueue?.();
@@ -992,6 +1019,14 @@ export class PiEngineAdapter {
992
1019
  return workflowResult("settings", "failed", error instanceof Error ? error.message : String(error));
993
1020
  }
994
1021
  }
1022
+ reloadBlockedResult() {
1023
+ const session = this.#requireWorkflowSession();
1024
+ if (session.isStreaming)
1025
+ return workflowResult("reload", "failed", "Wait for the current response to finish before reloading.", undefined, "warning");
1026
+ if (session.isCompacting)
1027
+ return workflowResult("reload", "failed", "Wait for compaction to finish before reloading.", undefined, "warning");
1028
+ return null;
1029
+ }
995
1030
  async executeWorkflow(request) {
996
1031
  try {
997
1032
  return await this.#performWorkflow(request);
@@ -999,7 +1034,7 @@ export class PiEngineAdapter {
999
1034
  catch (error) {
1000
1035
  const message = error instanceof Error ? error.message : String(error);
1001
1036
  const contextualMessage = request.command === "export"
1002
- ? `Failed to export session: ${message}`
1037
+ ? `Failed to export session: ${errorMessage(error, "Unknown error")}`
1003
1038
  : request.command === "import"
1004
1039
  ? `Failed to import session: ${message}`
1005
1040
  : request.command === "new"
@@ -1133,8 +1168,18 @@ export class PiEngineAdapter {
1133
1168
  : [...(runtime.services.modelRuntime.getAvailableSnapshot?.() ?? [])];
1134
1169
  let model = findExactWorkflowModel(reference, availableModels);
1135
1170
  const messages = [];
1171
+ const generation = this.#sessionGeneration;
1172
+ const current = () => !this.#disposed && generation === this.#sessionGeneration;
1173
+ const publish = (message) => {
1174
+ if (!current())
1175
+ return;
1176
+ if (this.#workflowInteraction.publish)
1177
+ this.#workflowInteraction.publish(message);
1178
+ else
1179
+ messages.push(message);
1180
+ };
1136
1181
  if (model === undefined && scopedModels.length === 0) {
1137
- messages.push({ kind: "status", message: "Refreshing model catalogs…" });
1182
+ publish({ kind: "status", message: "Refreshing model catalogs…" });
1138
1183
  const controller = new AbortController();
1139
1184
  let timedOut = false;
1140
1185
  const timeout = setTimeout(() => {
@@ -1144,14 +1189,14 @@ export class PiEngineAdapter {
1144
1189
  try {
1145
1190
  const refreshed = await runtime.services.modelRuntime.refresh?.({ signal: controller.signal });
1146
1191
  if (isRecord(refreshed) && refreshed.aborted === true && timedOut) {
1147
- messages.push({ kind: "warning", message: "Model refresh timed out; searching cached models." });
1192
+ publish({ kind: "warning", message: "Model refresh timed out; searching cached models." });
1148
1193
  }
1149
1194
  else if (isRecord(refreshed) && refreshed.errors instanceof Map && refreshed.errors.size > 0) {
1150
- messages.push({ kind: "warning", message: `Could not refresh ${[...refreshed.errors.keys()].join(", ")}; searching cached models.` });
1195
+ publish({ kind: "warning", message: `Could not refresh ${[...refreshed.errors.keys()].join(", ")}; searching cached models.` });
1151
1196
  }
1152
1197
  }
1153
1198
  catch (error) {
1154
- messages.push({
1199
+ publish({
1155
1200
  kind: "warning",
1156
1201
  message: timedOut
1157
1202
  ? "Model refresh timed out; searching cached models."
@@ -1164,13 +1209,25 @@ export class PiEngineAdapter {
1164
1209
  availableModels = [...(runtime.services.modelRuntime.getAvailableSnapshot?.() ?? [])];
1165
1210
  model = findExactWorkflowModel(reference, availableModels);
1166
1211
  }
1212
+ if (!current())
1213
+ return workflowResult(request.command, "cancelled", "Model selection cancelled", undefined, "silent");
1167
1214
  if (model === undefined) {
1168
1215
  return {
1169
1216
  ...workflowResult(request.command, "requires-selection", "Select a model", reference, "silent"),
1170
1217
  ...(messages.length === 0 ? {} : { messages: Object.freeze(messages) }),
1171
1218
  };
1172
1219
  }
1173
- await session.setModel(model);
1220
+ try {
1221
+ await session.setModel(model);
1222
+ }
1223
+ catch (error) {
1224
+ if (!current())
1225
+ return workflowResult(request.command, "cancelled", "Model selection cancelled", undefined, "silent");
1226
+ const failure = { kind: "error", message: error instanceof Error ? error.message : String(error) };
1227
+ return workflowResult(request.command, "failed", failure.message, undefined, "error", messages.length === 0 ? undefined : [...messages, failure]);
1228
+ }
1229
+ if (!current())
1230
+ return workflowResult(request.command, "cancelled", "Model selection cancelled", undefined, "silent");
1174
1231
  const providerId = stringProperty(model, "provider") ?? "unknown";
1175
1232
  const modelId = stringProperty(model, "id") ?? reference;
1176
1233
  this.#activeModel = { providerId, modelId, displayName: stringProperty(model, "name") ?? modelId };
@@ -1226,9 +1283,8 @@ export class PiEngineAdapter {
1226
1283
  const signal = request.signal;
1227
1284
  if (signal?.aborted)
1228
1285
  return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
1229
- let auth;
1230
1286
  try {
1231
- auth = await this.#workflowHost.runCommand("gh", ["auth", "status"], signal === undefined ? undefined : { signal });
1287
+ await this.#workflowHost.runCommand("gh", ["auth", "status"], signal === undefined ? undefined : { signal });
1232
1288
  }
1233
1289
  catch (error) {
1234
1290
  if (signal?.aborted || isAbortError(error))
@@ -1238,9 +1294,6 @@ export class PiEngineAdapter {
1238
1294
  : "GitHub CLI is not logged in. Run 'gh auth login' first.";
1239
1295
  return workflowResult(request.command, "failed", message);
1240
1296
  }
1241
- if (auth.stderr && !auth.stdout) {
1242
- return workflowResult(request.command, "failed", "GitHub CLI is not logged in. Run 'gh auth login' first.");
1243
- }
1244
1297
  const temporary = join(tmpdir(), `${PRODUCT_IDENTITY.filesystem.temporaryPrefix}pi-session-${process.pid}.html`);
1245
1298
  try {
1246
1299
  try {
@@ -1258,13 +1311,10 @@ export class PiEngineAdapter {
1258
1311
  catch (error) {
1259
1312
  if (signal?.aborted || isAbortError(error))
1260
1313
  return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
1261
- return workflowResult(request.command, "failed", `Failed to create gist: ${errorMessage(error, "Unknown error")}`);
1314
+ return workflowResult(request.command, "failed", `Failed to create gist: ${shareCommandFailureDetail(error)}`);
1262
1315
  }
1263
1316
  if (signal?.aborted)
1264
1317
  return workflowResult(request.command, "cancelled", "Share cancelled", undefined, "status");
1265
- if (gist.stderr && !gist.stdout) {
1266
- return workflowResult(request.command, "failed", `Failed to create gist: ${gist.stderr.trim() || "Unknown error"}`);
1267
- }
1268
1318
  const gistUrl = gist.stdout.trim();
1269
1319
  const gistId = gistUrl.split("/").at(-1);
1270
1320
  if (!gistId)
@@ -1497,13 +1547,17 @@ export class PiEngineAdapter {
1497
1547
  }
1498
1548
  }
1499
1549
  case "reload": {
1500
- if (session.isStreaming)
1501
- return workflowResult(request.command, "failed", "Wait for the current response to finish before reloading.", undefined, "warning");
1502
- if (session.isCompacting)
1503
- return workflowResult(request.command, "failed", "Wait for compaction to finish before reloading.", undefined, "warning");
1550
+ const blocked = this.reloadBlockedResult();
1551
+ if (blocked)
1552
+ return blocked;
1504
1553
  await requireCapability(session.reload, "reload").call(session);
1505
1554
  await this.#bindExtensionUiToSession();
1506
- return workflowResult(request.command, "completed", "Reloaded keybindings, extensions, skills, prompts, themes, and context files");
1555
+ const message = "Reloaded keybindings, extensions, skills, prompts, themes, and context files";
1556
+ const modelError = runtime.services.modelRuntime.getError?.();
1557
+ return workflowResult(request.command, "completed", message, undefined, "status", modelError ? [
1558
+ { kind: "error", message: `models.json error: ${modelError}` },
1559
+ { kind: "status", message },
1560
+ ] : undefined);
1507
1561
  }
1508
1562
  case "quit": {
1509
1563
  await this.dispose();
@@ -2698,7 +2752,7 @@ function defaultWorkflowHost() {
2698
2752
  const result = await execFileAsync(command, [...arguments_], { encoding: "utf8", signal: options?.signal });
2699
2753
  return { stdout: result.stdout, stderr: result.stderr };
2700
2754
  },
2701
- readChangelog: async () => "No changelog entries found.",
2755
+ readChangelog: readPinnedCommandChangelog,
2702
2756
  };
2703
2757
  }
2704
2758
  function workflowLoginNotification(event) {
@@ -2757,6 +2811,13 @@ function workflowConfirmation(command, message, detail) {
2757
2811
  function errorMessage(error, fallback) {
2758
2812
  return error instanceof Error ? error.message : fallback;
2759
2813
  }
2814
+ function shareCommandFailureDetail(error) {
2815
+ // Compatibility: Pi reports a failed child's stderr, not execFile's command wrapper.
2816
+ if (isRecord(error) && (typeof error.code === "number" || typeof error.signal === "string") && typeof error.stderr === "string") {
2817
+ return error.stderr.trim() || "Unknown error";
2818
+ }
2819
+ return errorMessage(error, "Unknown error");
2820
+ }
2760
2821
  function commandMissing(error) {
2761
2822
  return isRecord(error) && error.code === "ENOENT";
2762
2823
  }
@@ -0,0 +1,2 @@
1
+ export declare function readPinnedCommandChangelog(sinceVersion?: string): Promise<string>;
2
+ export declare function formatPinnedCommandChangelog(content: string, sinceVersion?: string): string;