@timurproko/a1 0.2.7-dev.810 → 0.2.7-dev.828

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 (166) hide show
  1. package/README.md +7 -1
  2. package/bin/automatic-update.js +7 -0
  3. package/bin/tab-launch-context.d.ts +1 -0
  4. package/bin/tab-launch-context.js +3 -3
  5. package/bin/ui.js +96 -66
  6. package/dist/app/session-shell/resident-tab-coordinator.d.ts +6 -9
  7. package/dist/app/session-shell/resident-tab-coordinator.js +9 -52
  8. package/dist/app/session-shell/session-shell-root.d.ts +14 -8
  9. package/dist/app/session-shell/session-shell-root.js +9 -5
  10. package/dist/app/session-shell/session-shell.js +70 -20
  11. package/dist/app/session-shell/session-viewport-controller.js +6 -4
  12. package/dist/app/session-shell/shell-host.js +6 -3
  13. package/dist/app/session-shell/transcript-document.js +1 -1
  14. package/dist/cli/tabs.d.ts +1 -1
  15. package/dist/cli/tabs.js +10 -14
  16. package/dist/composition/owned-settings.d.ts +12 -0
  17. package/dist/composition/owned-settings.js +36 -0
  18. package/dist/composition/owned-ui.d.ts +5 -14
  19. package/dist/composition/owned-ui.js +93 -64
  20. package/dist/composition/resident-tab-bridge.d.ts +10 -1
  21. package/dist/composition/resident-tab-bridge.js +21 -0
  22. package/dist/composition/settings-route-host.d.ts +12 -2
  23. package/dist/composition/settings-route-host.js +27 -9
  24. package/dist/composition/settings-search-input.d.ts +9 -0
  25. package/dist/composition/settings-search-input.js +58 -0
  26. package/dist/composition/utility-tab.d.ts +28 -0
  27. package/dist/composition/utility-tab.js +242 -0
  28. package/dist/contracts/owned-ui/session-backend.d.ts +2 -0
  29. package/dist/contracts/owned-ui/session-workflows.d.ts +7 -3
  30. package/dist/contracts/presentation/index.d.ts +11 -0
  31. package/dist/contracts/presentation/index.js +12 -0
  32. package/dist/features/launch/development-launch.js +6 -0
  33. package/dist/features/launch/profile-paths.js +4 -0
  34. package/dist/features/owned-ui/plus-menu-dialog.d.ts +22 -0
  35. package/dist/features/owned-ui/plus-menu-dialog.js +76 -0
  36. package/dist/features/owned-ui/project-trust-dialog.d.ts +1 -1
  37. package/dist/features/owned-ui/project-trust-dialog.js +6 -8
  38. package/dist/features/owned-ui/project-trust-prompt.d.ts +1 -0
  39. package/dist/features/owned-ui/project-trust-prompt.js +7 -3
  40. package/dist/features/owned-ui/reference-screen-app.d.ts +2 -0
  41. package/dist/features/owned-ui/reference-screen-app.js +12 -2
  42. package/dist/features/owned-ui/settings-app.d.ts +10 -3
  43. package/dist/features/owned-ui/settings-app.js +98 -45
  44. package/dist/features/owned-ui/settings-search-input.d.ts +29 -0
  45. package/dist/features/owned-ui/settings-search-input.js +1 -0
  46. package/dist/features/prompt-history/index.d.ts +1 -1
  47. package/dist/features/prompt-history/paths.d.ts +2 -1
  48. package/dist/features/prompt-history/paths.js +10 -2
  49. package/dist/features/prompt-history/service.d.ts +5 -0
  50. package/dist/features/prompt-history/service.js +1 -1
  51. package/dist/features/resident-tab/shortcut-declarations.d.ts +1 -1
  52. package/dist/features/resident-tab/shortcut-declarations.js +2 -0
  53. package/dist/foundation/launch-context/index.d.ts +2 -0
  54. package/dist/foundation/launch-context/index.js +1 -0
  55. package/dist/foundation/lifecycle/session-repository-context.js +16 -2
  56. package/dist/foundation/release/automatic-update.d.ts +37 -0
  57. package/dist/foundation/release/automatic-update.js +265 -0
  58. package/dist/foundation/release/bootstrap.d.ts +1 -9
  59. package/dist/foundation/release/bootstrap.js +7 -3
  60. package/dist/foundation/release/early-accent.d.ts +12 -0
  61. package/dist/foundation/release/early-accent.js +59 -0
  62. package/dist/foundation/release/index.d.ts +1 -1
  63. package/dist/foundation/release/index.js +1 -1
  64. package/dist/foundation/release/latest-release.d.ts +3 -2
  65. package/dist/foundation/release/latest-release.js +1 -1
  66. package/dist/foundation/release/progress-palette.generated.d.ts +137 -3
  67. package/dist/foundation/release/progress-palette.generated.js +137 -3
  68. package/dist/foundation/release/release-gc.js +19 -4
  69. package/dist/foundation/release/update-execution-lease.d.ts +36 -0
  70. package/dist/foundation/release/update-execution-lease.js +145 -0
  71. package/dist/foundation/release/update.d.ts +9 -7
  72. package/dist/foundation/release/update.js +244 -175
  73. package/dist/foundation/resident-tabs/bridge-client.d.ts +10 -3
  74. package/dist/foundation/resident-tabs/bridge-client.js +20 -8
  75. package/dist/foundation/resident-tabs/maintenance.d.ts +2 -6
  76. package/dist/foundation/resident-tabs/maintenance.js +5 -23
  77. package/dist/foundation/resident-tabs/progress-observation-schema.d.ts +34 -0
  78. package/dist/foundation/resident-tabs/progress-observation-schema.js +1 -0
  79. package/dist/foundation/resident-tabs/progress-observation.d.ts +3 -6
  80. package/dist/foundation/resident-tabs/progress-observation.js +43 -12
  81. package/dist/foundation/resident-tabs/release-holds.js +3 -14
  82. package/dist/foundation/resident-tabs/routes.d.ts +5 -0
  83. package/dist/foundation/resident-tabs/routes.js +5 -0
  84. package/dist/foundation/resident-tabs/settings.d.ts +0 -1
  85. package/dist/foundation/resident-tabs/settings.js +0 -2
  86. package/dist/integrations/pi/components/editor-interaction.d.ts +2 -0
  87. package/dist/integrations/pi/components/modal-frame.d.ts +17 -1
  88. package/dist/integrations/pi/components/modal-frame.js +48 -9
  89. package/dist/integrations/pi/components/models-dialog.js +4 -52
  90. package/dist/integrations/pi/components/owned-editor-ux.d.ts +3 -0
  91. package/dist/integrations/pi/components/owned-editor-ux.js +75 -4
  92. package/dist/integrations/pi/components/shell-editor-autocomplete.js +57 -37
  93. package/dist/integrations/pi/components/shell-footer-status.js +19 -3
  94. package/dist/integrations/pi/components/shell-presenters-transcript.js +4 -1
  95. package/dist/integrations/pi/components/shell-selectors-dialogs.js +4 -1
  96. package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -2
  97. package/dist/integrations/pi/components/skills-dialog.js +2 -2
  98. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +7 -5
  99. package/dist/integrations/pi/components/upstream/components/owned-editor.js +15 -8
  100. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +3 -3
  101. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +2 -2
  102. package/dist/integrations/pi/components/upstream/components/session-footer.js +26 -23
  103. package/dist/integrations/pi/components/upstream/components/session-selector.js +7 -6
  104. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +7 -6
  105. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +9 -8
  106. package/dist/integrations/pi/components/upstream/components/tool-execution.js +6 -4
  107. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +8 -8
  108. package/dist/integrations/pi/components/upstream/components/tree-selector.js +10 -10
  109. package/dist/integrations/pi/components/upstream/history/editor-core.js +2 -0
  110. package/dist/integrations/pi/components/upstream/theme/theme.d.ts +2 -0
  111. package/dist/integrations/pi/components/upstream/theme/theme.js +5 -1
  112. package/dist/integrations/pi/engine/adapter.js +7 -2
  113. package/dist/integrations/pi/engine/offline-settings-port.d.ts +23 -0
  114. package/dist/integrations/pi/engine/offline-settings-port.js +53 -0
  115. package/dist/integrations/pi/engine/ports.d.ts +1 -1
  116. package/dist/integrations/pi/engine/ports.js +4 -0
  117. package/dist/integrations/pi/engine/settings-bridge.d.ts +5 -0
  118. package/dist/integrations/pi/engine/settings-bridge.js +13 -0
  119. package/dist/integrations/pi/engine/settings-port.js +1 -14
  120. package/dist/integrations/pi/engine/workflow-runner.js +9 -2
  121. package/dist/native/darwin-arm64/manifest.json +1 -1
  122. package/dist/native/darwin-arm64/terminal-host.manifest.json +3 -3
  123. package/dist/native/darwin-arm64/terminal-host.node +0 -0
  124. package/dist/native/darwin-x64/manifest.json +1 -1
  125. package/dist/native/darwin-x64/terminal-host.manifest.json +3 -3
  126. package/dist/native/darwin-x64/terminal-host.node +0 -0
  127. package/dist/native/linux-x64/manifest.json +1 -1
  128. package/dist/native/linux-x64/terminal-host.manifest.json +3 -3
  129. package/dist/native/linux-x64/terminal-host.node +0 -0
  130. package/dist/native/win32-x64/manifest.json +2 -2
  131. package/dist/native/win32-x64/process-guardian.node +0 -0
  132. package/dist/native/win32-x64/terminal-host.manifest.json +3 -3
  133. package/dist/native/win32-x64/terminal-host.node +0 -0
  134. package/dist/product-identity.d.ts +1 -1
  135. package/dist/product-identity.js +2 -2
  136. package/dist/product-identity.json +2 -0
  137. package/dist/ui/apps/host.d.ts +2 -0
  138. package/dist/ui/apps/host.js +7 -1
  139. package/dist/ui/components/dialog-panel.js +35 -30
  140. package/dist/ui/components/line-input.d.ts +3 -0
  141. package/dist/ui/components/line-input.js +3 -1
  142. package/dist/ui/components/prompt-input.d.ts +5 -4
  143. package/dist/ui/components/prompt-input.js +8 -6
  144. package/dist/ui/components/spans.js +2 -2
  145. package/dist/ui/components/submitted-prompt.d.ts +2 -2
  146. package/dist/ui/components/submitted-prompt.js +3 -3
  147. package/dist/ui/components/surface.js +4 -2
  148. package/dist/ui/components/text.d.ts +3 -6
  149. package/dist/ui/components/text.js +6 -8
  150. package/dist/ui/components/transcript-viewport.d.ts +1 -1
  151. package/dist/ui/components/transcript-viewport.js +141 -24
  152. package/dist/ui/settings/declarations.d.ts +67 -17
  153. package/dist/ui/settings/declarations.js +47 -15
  154. package/dist/ui/settings/early-accent.d.ts +81 -0
  155. package/dist/ui/settings/early-accent.js +50 -0
  156. package/dist/ui/settings/manager.js +7 -3
  157. package/dist/ui/settings/migrations.js +22 -0
  158. package/dist/ui/settings/progress-palette.generated.d.ts +139 -0
  159. package/dist/ui/settings/progress-palette.generated.js +139 -0
  160. package/dist/ui/settings/sections.d.ts +4 -0
  161. package/dist/ui/settings/sections.js +9 -3
  162. package/docs/architecture/resident-tabs.md +52 -21
  163. package/docs/architecture/tool-image-presentation.md +2 -1
  164. package/docs/features/prompt-history.md +41 -20
  165. package/docs/validation.md +25 -5
  166. package/package.json +1 -1
@@ -81,7 +81,7 @@ class PromptSelectionInterceptor {
81
81
  installAtomicSegmentation(editor, options.atomicRanges, () => this.#wordDirection);
82
82
  }
83
83
  handleInput(data, next) {
84
- if (this.#handleTerminalPaste(data, next))
84
+ if (this.options.pasteEnabled === false ? discardTerminalPaste(this, data, value => this.handleInput(value, next)) : this.#handleTerminalPaste(data, next))
85
85
  return;
86
86
  if (this.keybindings.matches(data, "owned.editor.selectAll")) {
87
87
  this.#selectAll();
@@ -107,7 +107,8 @@ class PromptSelectionInterceptor {
107
107
  return;
108
108
  }
109
109
  if (this.keybindings.matches(data, "owned.editor.paste")) {
110
- this.pasteClipboard();
110
+ if (this.options.pasteEnabled !== false)
111
+ this.pasteClipboard();
111
112
  return;
112
113
  }
113
114
  if (this.keybindings.matches(data, "owned.editor.redo")) {
@@ -132,7 +133,7 @@ class PromptSelectionInterceptor {
132
133
  this.#deleteSelection();
133
134
  return;
134
135
  }
135
- const inserted = insertedText(data);
136
+ const inserted = editorInsertedText(data);
136
137
  if (inserted !== undefined) {
137
138
  this.#replaceSelection(inserted);
138
139
  return;
@@ -280,11 +281,13 @@ class PromptSelectionInterceptor {
280
281
  this.#pointerSelecting = false;
281
282
  this.#lastClick = undefined;
282
283
  this.#redoStack = [];
284
+ DISABLED_TERMINAL_PASTES.delete(this);
283
285
  this.#selectionRevision += 1;
284
286
  }
285
287
  cancelPendingPastes() {
286
288
  this.#pasteGeneration += 1;
287
289
  this.#terminalPaste = undefined;
290
+ DISABLED_TERMINAL_PASTES.delete(this);
288
291
  }
289
292
  hasSelection() {
290
293
  return this.#activeRange() !== undefined;
@@ -295,6 +298,8 @@ class PromptSelectionInterceptor {
295
298
  : this.options.expandCopiedText(this.#selectedText());
296
299
  }
297
300
  pasteClipboard() {
301
+ if (this.options.pasteEnabled === false)
302
+ return false;
298
303
  this.#pasteFromClipboard();
299
304
  return true;
300
305
  }
@@ -933,7 +938,73 @@ function isEditorSegment(value) {
933
938
  const input = Reflect.get(value, "input");
934
939
  return typeof segment === "string" && typeof index === "number" && typeof input === "string";
935
940
  }
936
- function insertedText(data) {
941
+ const DISABLED_TERMINAL_PASTES = new WeakMap();
942
+ function discardTerminalPaste(owner, data, forward) {
943
+ const startMarker = "\x1b[200~";
944
+ const endMarker = "\x1b[201~";
945
+ const state = DISABLED_TERMINAL_PASTES.get(owner) ?? { startTail: "", bodyTail: null };
946
+ if (state.bodyTail !== null) {
947
+ const framed = state.bodyTail + data;
948
+ const end = framed.indexOf(endMarker);
949
+ if (end < 0) {
950
+ state.bodyTail = delimiterTail(framed, endMarker);
951
+ DISABLED_TERMINAL_PASTES.set(owner, state);
952
+ return true;
953
+ }
954
+ DISABLED_TERMINAL_PASTES.delete(owner);
955
+ const remaining = framed.slice(end + endMarker.length);
956
+ if (remaining.length > 0)
957
+ forward(remaining);
958
+ return true;
959
+ }
960
+ const hadStartTail = state.startTail.length > 0;
961
+ const candidate = state.startTail + data;
962
+ state.startTail = "";
963
+ const start = candidate.indexOf(startMarker);
964
+ if (start >= 0) {
965
+ if (start > 0)
966
+ forward(candidate.slice(0, start));
967
+ const framed = candidate.slice(start + startMarker.length);
968
+ const end = framed.indexOf(endMarker);
969
+ if (end < 0) {
970
+ state.bodyTail = delimiterTail(framed, endMarker);
971
+ DISABLED_TERMINAL_PASTES.set(owner, state);
972
+ }
973
+ else {
974
+ DISABLED_TERMINAL_PASTES.delete(owner);
975
+ const remaining = framed.slice(end + endMarker.length);
976
+ if (remaining.length > 0)
977
+ forward(remaining);
978
+ }
979
+ return true;
980
+ }
981
+ const partial = delimiterTail(candidate, startMarker);
982
+ // Compatibility: standalone Escape still cancels; marker fragments stay pending.
983
+ if (partial.length > 1) {
984
+ const before = candidate.slice(0, candidate.length - partial.length);
985
+ if (before.length > 0)
986
+ forward(before);
987
+ state.startTail = partial;
988
+ DISABLED_TERMINAL_PASTES.set(owner, state);
989
+ return true;
990
+ }
991
+ DISABLED_TERMINAL_PASTES.delete(owner);
992
+ if (hadStartTail) {
993
+ forward(candidate);
994
+ return true;
995
+ }
996
+ return false;
997
+ }
998
+ function delimiterTail(text, delimiter) {
999
+ const limit = Math.min(text.length, delimiter.length - 1);
1000
+ for (let length = limit; length > 0; length -= 1) {
1001
+ const suffix = text.slice(text.length - length);
1002
+ if (delimiter.startsWith(suffix))
1003
+ return suffix;
1004
+ }
1005
+ return "";
1006
+ }
1007
+ export function editorInsertedText(data) {
937
1008
  if (!data || data.includes("\u001b[200~"))
938
1009
  return undefined;
939
1010
  const kitty = decodeKittyPrintable(data);
@@ -5,6 +5,8 @@ import { OwnedEditorUxInterception, createPromptSelectionInterceptor, editorVisu
5
5
  import { PINNED_PI_LAYOUT, getPiSelectListTheme, piTheme, renderPiListSelection, } from "./theme.js";
6
6
  import { createPiKeybindingsHost, createTuiFacade, ensureTheme, isAutocompleteProvider, } from "./shell-shared-facade.js";
7
7
  import { SKILLS_COMMAND_NAME, collapseSkillCommands, createSkillsTunnelProvider, } from "./skills-command.js";
8
+ /** The built-in commands resident A1 serves from its tab bar instead: utility tabs, and the tab rename for `/name`. */
9
+ const RESIDENT_UTILITY_TAB_COMMANDS = new Set(["settings", "changelog", "name"]);
8
10
  /** A selected autocomplete row: its arrow/primary column, then the aligned description. */
9
11
  const SELECTED_DESCRIBED_ROW = /^(→ .*?\S)(\s{2,}.*)$/u;
10
12
  const INPUT_COUNTER_INSET = 3;
@@ -62,6 +64,8 @@ export function createPiShellEditor(options) {
62
64
  ensureTheme();
63
65
  const tui = createTuiFacade(options);
64
66
  const inputPresentation = options.keybindingProfile === "a1" ? options.promptPresentation?.input : undefined;
67
+ const inputCapabilities = options.inputCapabilities ?? {};
68
+ const autocompleteEnabled = inputCapabilities.autocomplete !== false;
65
69
  // Invariant: the process shares one manager; a standalone editor gets its own host for its profile.
66
70
  const host = options.keybindings ?? createPiKeybindingsHost({
67
71
  profile: options.keybindingProfile ?? "pi",
@@ -74,7 +78,7 @@ export function createPiShellEditor(options) {
74
78
  }
75
79
  const scrollInfo = { emitted: false, counter: undefined };
76
80
  const selectListTheme = getPiSelectListTheme();
77
- let tunnelSkills = [];
81
+ const autocomplete = { provider: emptyAutocompleteProvider(), skills: [] };
78
82
  const EditorClass = options.keybindingProfile === "a1" && options.persistentHistory === true ? options.historyEditor : OwnedEditor;
79
83
  const editor = new EditorClass(tui, {
80
84
  borderColor: (value) => inputPresentation === undefined ? piTheme().fg("borderMuted", value) : inputPresentation.styleRule(value),
@@ -100,13 +104,14 @@ export function createPiShellEditor(options) {
100
104
  ...(options.keybindingProfile === "a1" ? {
101
105
  terminalRows: options.getRows,
102
106
  getVisualLineCount: (width) => editorVisualLineCount(editor, width),
103
- clearCommandSearchOnEscape: true,
104
- commandTunnels: () => tunnelSkills.length === 0 ? [] : [SKILLS_COMMAND_NAME],
107
+ clearCommandSearchOnEscape: autocompleteEnabled,
108
+ commandTunnels: () => !autocompleteEnabled || autocomplete.skills.length === 0 ? [] : [SKILLS_COMMAND_NAME],
105
109
  } : {}),
106
110
  ...(options.keybindingProfile === "a1" && options.promptPresentation !== undefined ? {
107
111
  ...(inputPresentation === undefined ? {} : { inputPresentation }),
108
112
  styleSuggestion: options.promptPresentation.styleSuggestion,
109
113
  styleSuggestionCaret: options.promptPresentation.styleSuggestionCaret,
114
+ ...(inputCapabilities.placeholder === undefined ? {} : { placeholder: inputCapabilities.placeholder }),
110
115
  } : {}),
111
116
  });
112
117
  const editorUx = options.keybindingProfile === "a1"
@@ -117,6 +122,7 @@ export function createPiShellEditor(options) {
117
122
  ...(options.beginClipboardPaste === undefined ? {} : { beginClipboardPaste: options.beginClipboardPaste }),
118
123
  ...(options.beginTextPaste === undefined ? {} : { beginTextPaste: options.beginTextPaste }),
119
124
  ...(options.deferTextPaste === undefined ? {} : { deferTextPaste: options.deferTextPaste }),
125
+ pasteEnabled: inputCapabilities.paste !== false,
120
126
  ...(options.onPasteRejected === undefined ? {} : { onPasteRejected: options.onPasteRejected }),
121
127
  ...(options.onPasteInput === undefined ? {} : { onPasteInput: options.onPasteInput }),
122
128
  transformPastedContent: options.transformPastedContent ?? (content => content.kind === "text" ? content.text : ""),
@@ -142,37 +148,7 @@ export function createPiShellEditor(options) {
142
148
  : isBashMode ? piTheme().getBashModeBorderColor() : piTheme().getThinkingBorderColor(thinkingLevel);
143
149
  tui.requestRender();
144
150
  };
145
- let autocompleteProvider;
146
- const builtInCatalog = (options.keybindingProfile === "a1" ? OWNED_BUILTIN_SLASH_COMMANDS : PINNED_PI_BUILTIN_SLASH_COMMANDS)
147
- .filter(command => !options.hideSettingsCommand || command.name !== "settings");
148
- const setAutocompleteCommands = (installed) => {
149
- // Invariant: collapse is a bare-A1 presentation; the comparison profile installs the pinned list.
150
- const collapsed = options.keybindingProfile === "a1" && options.skillsPresentation?.() === "collapse"
151
- ? collapseSkillCommands(installed)
152
- : { commands: installed, skills: [] };
153
- tunnelSkills = collapsed.skills;
154
- const commands = collapsed.commands;
155
- const additions = new Map(commands.map(command => [command.name, command]));
156
- const builtInNames = new Set(builtInCatalog.map(command => command.name));
157
- const builtIns = builtInCatalog.flatMap(command => [
158
- autocompleteCommand(command, additions.get(command.name)),
159
- // Rationale: the collapsed browser is a primary bare-A1 command, not a resource after quit.
160
- ...(command.name === "settings" && collapsed.collapsedCommand !== undefined
161
- ? [autocompleteCommand(collapsed.collapsedCommand)]
162
- : []),
163
- ]);
164
- // Invariant: built-in additions and the promoted collapsed browser never reappear as resources.
165
- const resources = commands
166
- .filter(command => command.name !== collapsed.collapsedCommand?.name && command.source !== "builtin" && !builtInNames.has(command.name))
167
- .map(command => autocompleteCommand(command));
168
- const catalog = [...builtIns, ...resources];
169
- const combined = new CombinedAutocompleteProvider(catalog, options.cwd ?? process.cwd());
170
- const tunnelAware = tunnelSkills.length === 0 ? combined : createSkillsTunnelProvider(combined, tunnelSkills);
171
- autocompleteProvider = options.keybindingProfile === "a1"
172
- ? createDelimiterReadyCommandProvider(tunnelAware, catalog)
173
- : tunnelAware;
174
- editor.setAutocompleteProvider(autocompleteProvider);
175
- };
151
+ const setAutocompleteCommands = autocompleteCommandSetter(options, editor, autocomplete, autocompleteEnabled);
176
152
  setAutocompleteCommands(options.autocompleteCommands ?? []);
177
153
  let submitHandler = options.onSubmit;
178
154
  let interruptHandler = options.onInterrupt ?? (() => { });
@@ -259,6 +235,9 @@ export function createPiShellEditor(options) {
259
235
  reloadKeybindings: () => host.reload(),
260
236
  matchesTerminalKey: (data, key) => matchesKey(data, key),
261
237
  handleInput: data => {
238
+ if (inputCapabilities.singleLine === true
239
+ && (keybindings.matches(data, "tui.input.newLine") || keybindings.matches(data, "tui.input.submit")))
240
+ return;
262
241
  // Compatibility: an unassigned reverse Tab must not clear selection or reach autocomplete fallback.
263
242
  if (editorUx !== undefined && matchesKey(data, "shift+tab")
264
243
  && !Object.values(keybindings.getEffectiveConfig()).some(keys => (Array.isArray(keys) ? keys : [keys]).some(key => key !== undefined && matchesKey(data, key))))
@@ -316,13 +295,15 @@ export function createPiShellEditor(options) {
316
295
  tui.requestRender();
317
296
  },
318
297
  addAutocompleteProvider(factory) {
298
+ if (!autocompleteEnabled)
299
+ return;
319
300
  if (typeof factory !== "function")
320
301
  throw new TypeError("extension autocomplete factory must be a function");
321
- const candidate = Reflect.apply(factory, undefined, [autocompleteProvider]);
302
+ const candidate = Reflect.apply(factory, undefined, [autocomplete.provider]);
322
303
  if (!isAutocompleteProvider(candidate))
323
304
  throw new TypeError("extension autocomplete factory returned a malformed provider");
324
- autocompleteProvider = candidate;
325
- editor.setAutocompleteProvider(autocompleteProvider);
305
+ autocomplete.provider = candidate;
306
+ editor.setAutocompleteProvider(autocomplete.provider);
326
307
  },
327
308
  setThinkingLevel(level) {
328
309
  if (thinkingLevel === level)
@@ -354,6 +335,45 @@ export function createPiShellEditor(options) {
354
335
  cancelPendingPastes: () => editorUx?.cancelPendingPastes(),
355
336
  };
356
337
  }
338
+ function autocompleteCommandSetter(options, editor, state, enabled) {
339
+ const builtInCatalog = (options.keybindingProfile === "a1" ? OWNED_BUILTIN_SLASH_COMMANDS : PINNED_PI_BUILTIN_SLASH_COMMANDS)
340
+ .filter(command => !options.hideUtilityTabCommands || !RESIDENT_UTILITY_TAB_COMMANDS.has(command.name));
341
+ return installed => {
342
+ if (!enabled) {
343
+ state.provider = emptyAutocompleteProvider();
344
+ editor.setAutocompleteProvider(state.provider);
345
+ return;
346
+ }
347
+ // Invariant: collapse is a bare-A1 presentation; the comparison profile installs the pinned list.
348
+ const collapsed = options.keybindingProfile === "a1" && options.skillsPresentation?.() === "collapse"
349
+ ? collapseSkillCommands(installed)
350
+ : { commands: installed, skills: [] };
351
+ state.skills = collapsed.skills;
352
+ const additions = new Map(collapsed.commands.map(command => [command.name, command]));
353
+ const builtInNames = new Set(builtInCatalog.map(command => command.name));
354
+ const builtIns = builtInCatalog.flatMap(command => [
355
+ autocompleteCommand(command, additions.get(command.name)),
356
+ // Rationale: the collapsed browser is a primary bare-A1 command, not a resource after quit.
357
+ ...(command.name === "settings" && collapsed.collapsedCommand !== undefined
358
+ ? [autocompleteCommand(collapsed.collapsedCommand)] : []),
359
+ ]);
360
+ const resources = collapsed.commands
361
+ .filter(command => command.name !== collapsed.collapsedCommand?.name && command.source !== "builtin" && !builtInNames.has(command.name))
362
+ .map(command => autocompleteCommand(command));
363
+ const catalog = [...builtIns, ...resources];
364
+ const combined = new CombinedAutocompleteProvider(catalog, options.cwd ?? process.cwd());
365
+ const tunnelAware = state.skills.length === 0 ? combined : createSkillsTunnelProvider(combined, state.skills);
366
+ state.provider = options.keybindingProfile === "a1" ? createDelimiterReadyCommandProvider(tunnelAware, catalog) : tunnelAware;
367
+ editor.setAutocompleteProvider(state.provider);
368
+ };
369
+ }
370
+ function emptyAutocompleteProvider() {
371
+ return {
372
+ getSuggestions: async () => null,
373
+ applyCompletion: (lines, cursorLine, cursorCol) => ({ lines, cursorLine, cursorCol }),
374
+ shouldTriggerFileCompletion: () => false,
375
+ };
376
+ }
357
377
  /** Bare A1 leaves an applied catalog command ready for `:`, a user-entered space, or submission. */
358
378
  function createDelimiterReadyCommandProvider(base, commands) {
359
379
  const commandNames = new Set(commands.map(command => command.name));
@@ -7,7 +7,7 @@ import { KeybindingsManager } from "./upstream/adjacent/core/keybindings.js";
7
7
  import { StatusIndicator, WorkingStatusIndicator } from "./upstream/components/status-indicator.js";
8
8
  import { protectPiPromptChipPresentation } from "./prompt-chip-presentation.js";
9
9
  import { PINNED_PI_LAYOUT, piTheme, } from "./theme.js";
10
- import { createTuiFacade, ensureTheme, } from "./shell-shared-facade.js";
10
+ import { createTuiFacade, ensureTheme, piShellTruncateToWidth, piShellVisibleWidth, } from "./shell-shared-facade.js";
11
11
  export function createPiShellHeader(options = {}) {
12
12
  ensureTheme();
13
13
  let expanded = options.expanded ?? false;
@@ -107,9 +107,9 @@ export function createPiShellStatus(view, progressStatus, runtime) {
107
107
  component = statusComponent(view, statusUi, outputPad, liveStatusText, placement, progressPresentation, progressStatus);
108
108
  };
109
109
  return {
110
- render: width => component?.render(width) ?? [],
110
+ render: width => appendInstalledUpdateStatus(component?.render(width) ?? [], width, view),
111
111
  renderDock: width => placement === "dock" ? component?.render(width) ?? [] : [],
112
- renderLive: width => placement === "live" ? component?.render(width) ?? [] : [],
112
+ renderLive: width => appendInstalledUpdateStatus(placement === "live" ? component?.render(width) ?? [] : [], width, view),
113
113
  placement: () => placement,
114
114
  invalidate: () => component?.invalidate(),
115
115
  update(next) {
@@ -228,6 +228,22 @@ function statusSignature(view, workingOverride, outputPad, placement, progressPr
228
228
  const message = withMessage ? `${workingOverride ?? ""}\u0000${view.status.workingMessage ?? ""}\u0000${view.status.workingProgress ?? ""}` : "";
229
229
  return `${placement}\u0000${outputPad}\u0000${view.lifecycle}\u0000${progressPresentation}\u0000${message}\u0000${view.status.diagnostics.at(-1) ?? ""}`;
230
230
  }
231
+ function appendInstalledUpdateStatus(rows, width, view) {
232
+ if (width <= 0 || !view.diagnostics.some(diagnostic => diagnostic.code === "release-update-installed"))
233
+ return rows;
234
+ const theme = piTheme();
235
+ const full = `${theme.fg("success", "Update installed")} ${theme.fg("muted", "·")} ${theme.fg("accent", "Restart to update")}`;
236
+ const status = piShellTruncateToWidth(full, width);
237
+ const statusWidth = piShellVisibleWidth(status);
238
+ const target = rows.findLastIndex(row => piShellVisibleWidth(row.trimEnd()) > 0);
239
+ if (target < 0)
240
+ return [`${" ".repeat(Math.max(0, width - statusWidth))}${status}`];
241
+ const base = (rows[target] ?? "").trimEnd();
242
+ const gap = width - piShellVisibleWidth(base) - statusWidth;
243
+ if (gap >= 2)
244
+ return rows.map((row, index) => index === target ? `${base}${" ".repeat(gap)}${status}` : row);
245
+ return [...rows.slice(0, target + 1), `${" ".repeat(Math.max(0, width - statusWidth))}${status}`, ...rows.slice(target + 1)];
246
+ }
231
247
  function queuedInputChipWrapping(text, presentation, width) {
232
248
  return presentation === "custom-viewport"
233
249
  ? protectPiPromptChipPresentation(text, width)
@@ -4,7 +4,7 @@ import { SkillInvocationMessageComponent } from "./upstream/components/skill-inv
4
4
  import { piToolArguments as toolArguments, updatePiToolResult as updateToolResult } from "./tool-result-adapter.js";
5
5
  import { createTranscriptImageResolver } from "./transcript-image-resolver.js";
6
6
  import { createMermaidMarkdownTransformer } from "./upstream/components/mermaid.js";
7
- import { Container, Image, Spacer, Text, } from "@earendil-works/pi-tui";
7
+ import { Container, getCapabilities, Image, Spacer, Text, } from "@earendil-works/pi-tui";
8
8
  import { createPiSubmittedPromptComponent, isPiPromptStyleCompaction, } from "./submitted-prompt-adapter.js";
9
9
  export { isPiPromptStyleCompaction, paintPiSubmittedPromptTimestamp } from "./submitted-prompt-adapter.js";
10
10
  import { getPiMarkdownTheme, PINNED_PI_LAYOUT, piTheme, } from "./theme.js";
@@ -127,7 +127,10 @@ function withTranscriptImages(component, block, assets, showImages, imageWidthCe
127
127
  return component;
128
128
  const container = new Container();
129
129
  container.addChild(component);
130
+ const imageProtocolUnavailable = getCapabilities().images === null;
130
131
  for (const reference of references) {
132
+ if (showImages && imageProtocolUnavailable)
133
+ continue;
131
134
  container.addChild(new Spacer(1));
132
135
  const asset = assets?.resolve(reference.assetId) ?? null;
133
136
  if (!showImages) {
@@ -3,7 +3,7 @@ import { EarendilAnnouncementComponent } from "./upstream/components/earendil-an
3
3
  import { ExtensionSelectorComponent } from "./upstream/components/extension-selector.js";
4
4
  import { Box, Container, SelectList, Spacer, Text, } from "@earendil-works/pi-tui";
5
5
  import { ScopedModelsSelectorComponent } from "./upstream/components/scoped-models-selector.js";
6
- import { addPiModalSelectFooter, adoptPiModalFrame, adoptPiModalHeader } from "./modal-frame.js";
6
+ import { addPiModalSelectFooter, adoptPiModalFrame, adoptPiModalHeader, adoptPiModalSearchInput } from "./modal-frame.js";
7
7
  import { ModelsDialogComponent } from "./models-dialog.js";
8
8
  import { TrustSelectorComponent } from "./upstream/components/trust-selector.js";
9
9
  import { canonicalizePiSettingsHint, DIALOG_CLOSE_SHORTCUT_HINT, PINNED_PI_LAYOUT, getPiSelectListTheme, paintPiBorder, piTheme, renderPiModalShortcutHints, } from "./theme.js";
@@ -96,6 +96,7 @@ export function createPiShellSettingsSelector(options) {
96
96
  export function createPiShellModelSelector(options) {
97
97
  ensureTheme();
98
98
  const selector = new ModelSelectorComponent(createTuiFacade(options.runtime), options.currentModel, options.modelRuntime, options.scopedModels, options.onSelect, options.onCancel, options.initialSearchInput, options.onSelectAsDefault, options.defaultModel);
99
+ adoptPiModalSearchInput(selector, selector.getSearchInput());
99
100
  adoptPiModalFrame(selector, { topIndex: 0, bottomIndex: selector.children.length - 1 });
100
101
  return componentPort(selector);
101
102
  }
@@ -235,6 +236,8 @@ export function createPiShellAuthProviderSelector(mode, providers, onSelect, onC
235
236
  if (selected)
236
237
  onSelect(selected.id);
237
238
  }, onCancel, initialSearchInput);
239
+ // Provenance: OAuth selector slot is top rule, spacer, title, spacer, then its search Input.
240
+ adoptPiModalSearchInput(selector, selector.children[4]);
238
241
  addPiModalSelectFooter(selector);
239
242
  const preInsetContent = [selector.children[2], selector.children[6]];
240
243
  const header = adoptPiModalHeader(selector, 0, 2);
@@ -161,11 +161,18 @@ export interface PiShellPasteReservation {
161
161
  }
162
162
  export interface PiShellEditorOptions {
163
163
  readonly persistentHistory?: boolean;
164
+ /** Configures the shared interactive input for a caller narrower than the agent prompt. */
165
+ readonly inputCapabilities?: {
166
+ readonly singleLine?: boolean;
167
+ readonly paste?: boolean;
168
+ readonly autocomplete?: boolean;
169
+ readonly placeholder?: string;
170
+ };
164
171
  readonly historyEditor?: HistoryEditorConstructor;
165
172
  /** Bare A1 adds ergonomic aliases while comparison profiles retain Pi defaults. */
166
173
  readonly keybindingProfile?: "pi" | "a1";
167
- /** Resident Agents expose Settings through the global tab instead of a slash command. */
168
- readonly hideSettingsCommand?: boolean;
174
+ /** Resident Agents expose Settings and the changelog as utility tabs instead of slash commands. */
175
+ readonly hideUtilityTabCommands?: boolean;
169
176
  /** The process's shared keybindings; its profile must match `keybindingProfile`. */
170
177
  readonly keybindings?: PiKeybindingsHost;
171
178
  readonly getColumns: () => number;
@@ -1,6 +1,6 @@
1
1
  import { DynamicBorder } from "../startup-public.js";
2
2
  import { Container, getKeybindings, Input, Spacer, Text } from "@earendil-works/pi-tui";
3
- import { addPiModalHeader, adoptPiModalFrame } from "./modal-frame.js";
3
+ import { addPiModalHeader, addPiModalSearchInput, adoptPiModalFrame } from "./modal-frame.js";
4
4
  import { DIALOG_CLOSE_SHORTCUT_HINT, paintPiBorder, PINNED_PI_LAYOUT, piTheme, renderPiModalListRow, renderPiModalShortcutHints, } from "./theme.js";
5
5
  import { componentPort, ensureTheme, piShellTruncateToWidth, piShellVisibleWidth } from "./shell-shared-facade.js";
6
6
  import { skillMatchesQuery } from "./skills-command.js";
@@ -50,7 +50,7 @@ class SkillsSelectorComponent extends Container {
50
50
  this.#searchInput.onSubmit = () => this.#selectCurrent();
51
51
  const header = addPiModalHeader(this, new DynamicBorder(paintPiBorder), new Text(piTheme().fg("accent", piTheme().bold("Skills")), 0, 0));
52
52
  this.addChild(new Spacer(1));
53
- this.addChild(this.#searchInput);
53
+ addPiModalSearchInput(this, this.#searchInput);
54
54
  this.addChild(new Spacer(1));
55
55
  this.addChild(this.#listContainer);
56
56
  this.addChild(new Spacer(1));
@@ -3,11 +3,11 @@
3
3
  * packages/coding-agent/src/modes/interactive/components/custom-editor.ts.
4
4
  * Modifications: A1-owned class name and synchronized A1 keybinding contract replace the nominal
5
5
  * private upstream keybinding constructor dependency; bare A1 injects the shared Settings/agent input
6
- * frame, transient contextual-suggestion branch, and explicit body geometry for selection and
7
- * above-prompt autocomplete with a one-cell outer gutter. Bare A1 also clears a sole top-level
8
- * slash-command search on Escape and completes a selected or exact completed tunnel command with `:`
9
- * and reopens the menu on its tunnel rows, and synchronously retains then refreshes an exact top-level
10
- * command menu after Tab applies its row.
6
+ * frame, configurable empty-input placeholder branch, transient contextual-suggestion branch, and
7
+ * explicit body geometry for selection and above-prompt autocomplete with a one-cell outer gutter.
8
+ * Bare A1 also clears a sole top-level slash-command search on Escape and completes a selected or
9
+ * exact completed tunnel command with `:` and reopens the menu on its tunnel rows, and synchronously
10
+ * retains then refreshes an exact top-level command menu after Tab applies its row.
11
11
  * Deviations: owned-shared-input-frame, above-prompt-autocomplete-placement,
12
12
  * clear-command-search-on-escape, command-tunnel-colon-completion, keep-command-menu-open-after-tab.
13
13
  */
@@ -21,6 +21,8 @@ export interface OwnedEditorOptions extends EditorOptions {
21
21
  readonly inputPresentation?: PiShellPromptInputPresentation;
22
22
  readonly styleSuggestion?: (text: string) => string;
23
23
  readonly styleSuggestionCaret?: (text: string) => string;
24
+ /** Non-accepting empty-input guidance rendered through the shared suggestion presentation. */
25
+ readonly placeholder?: string;
24
26
  readonly terminalRows?: () => number;
25
27
  /** Reuses the editor's established atomic-aware visual layout when available. */
26
28
  readonly getVisualLineCount?: (width: number) => number | undefined;
@@ -3,11 +3,11 @@
3
3
  * packages/coding-agent/src/modes/interactive/components/custom-editor.ts.
4
4
  * Modifications: A1-owned class name and synchronized A1 keybinding contract replace the nominal
5
5
  * private upstream keybinding constructor dependency; bare A1 injects the shared Settings/agent input
6
- * frame, transient contextual-suggestion branch, and explicit body geometry for selection and
7
- * above-prompt autocomplete with a one-cell outer gutter. Bare A1 also clears a sole top-level
8
- * slash-command search on Escape and completes a selected or exact completed tunnel command with `:`
9
- * and reopens the menu on its tunnel rows, and synchronously retains then refreshes an exact top-level
10
- * command menu after Tab applies its row.
6
+ * frame, configurable empty-input placeholder branch, transient contextual-suggestion branch, and
7
+ * explicit body geometry for selection and above-prompt autocomplete with a one-cell outer gutter.
8
+ * Bare A1 also clears a sole top-level slash-command search on Escape and completes a selected or
9
+ * exact completed tunnel command with `:` and reopens the menu on its tunnel rows, and synchronously
10
+ * retains then refreshes an exact top-level command menu after Tab applies its row.
11
11
  * Deviations: owned-shared-input-frame, above-prompt-autocomplete-placement,
12
12
  * clear-command-search-on-escape, command-tunnel-colon-completion, keep-command-menu-open-after-tab.
13
13
  */
@@ -25,6 +25,7 @@ export function createOwnedEditorClass(Base) {
25
25
  #inputPresentation;
26
26
  #styleSuggestion;
27
27
  #styleSuggestionCaret;
28
+ #placeholder;
28
29
  #terminalRows;
29
30
  #getVisualLineCount;
30
31
  #clearCommandSearchOnEscape;
@@ -37,6 +38,7 @@ export function createOwnedEditorClass(Base) {
37
38
  this.#inputPresentation = options.inputPresentation;
38
39
  this.#styleSuggestion = options.styleSuggestion ?? (text => text);
39
40
  this.#styleSuggestionCaret = options.styleSuggestionCaret ?? (text => `\u001b[7m${text}\u001b[27m`);
41
+ this.#placeholder = options.placeholder ?? null;
40
42
  this.#terminalRows = options.terminalRows ?? (() => 24);
41
43
  this.#getVisualLineCount = options.getVisualLineCount;
42
44
  this.#clearCommandSearchOnEscape = options.clearCommandSearchOnEscape === true;
@@ -70,7 +72,12 @@ export function createOwnedEditorClass(Base) {
70
72
  return rows;
71
73
  }
72
74
  if (this.#promptSuggestion !== null && this.canPresentPromptSuggestion()) {
73
- const rows = this.#renderSuggestion(width);
75
+ const rows = this.#renderSuggestion(width, this.#promptSuggestion);
76
+ this.#renderedBodyRowCount = rows.length;
77
+ return rows;
78
+ }
79
+ if (this.#placeholder !== null && this.focused && this.getText().length === 0 && !this.isShowingAutocomplete()) {
80
+ const rows = this.#renderSuggestion(width, this.#placeholder);
74
81
  this.#renderedBodyRowCount = rows.length;
75
82
  return rows;
76
83
  }
@@ -176,10 +183,10 @@ export function createOwnedEditorClass(Base) {
176
183
  triggerAutocomplete(this);
177
184
  return true;
178
185
  }
179
- #renderSuggestion(width) {
186
+ #renderSuggestion(width, suggestion) {
180
187
  const presentation = this.#inputPresentation;
181
188
  const { paddingX, contentWidth, layoutWidth } = presentation.geometry(width, this.getPaddingX());
182
- const chunks = wrapTextWithAnsi(this.#promptSuggestion ?? "", layoutWidth).map(text => ({ text }));
189
+ const chunks = wrapTextWithAnsi(suggestion, layoutWidth).map(text => ({ text }));
183
190
  const maxVisible = Math.max(5, Math.floor(this.#terminalRows() * 0.3));
184
191
  const visible = chunks.slice(0, maxVisible);
185
192
  const leftPadding = " ".repeat(paddingX);
@@ -5,9 +5,9 @@
5
5
  * search, bulk/provider/reorder actions, dirty state, Ctrl+S persistence, refresh status,
6
6
  * cancellation, and focus while remapping theme and public helper imports; local key labels preserve
7
7
  * pinned platform formatting before layout without changing binding identities, and bare A1 uses the
8
- * shared semantic modal shortcut row and compact padded modal frame while immediately closing on the
9
- * implicit Ctrl+C selection-cancel alias without advertising it.
10
- * Deviations: owned-modal-shortcut-hints, owned-dialog-ctrl-c-cancel.
8
+ * shared semantic modal shortcut row, accent search marker, and compact padded modal frame while
9
+ * immediately closing on the implicit Ctrl+C selection-cancel alias without advertising it.
10
+ * Deviations: owned-modal-shortcut-hints, owned-dialog-ctrl-c-cancel, owned-dialog-search-marker.
11
11
  */
12
12
  interface ScopedModel {
13
13
  readonly provider: string;
@@ -1,7 +1,7 @@
1
1
  import { Container, fuzzyFilter, getKeybindings, Input, Spacer, Text, } from "@earendil-works/pi-tui";
2
2
  import { DynamicBorder } from "../../../startup-public.js";
3
3
  import { getModelSearchText } from "../model-search.js";
4
- import { addPiModalHeader, adoptPiModalFrame } from "../../modal-frame.js";
4
+ import { addPiModalHeader, addPiModalSearchInput, adoptPiModalFrame } from "../../modal-frame.js";
5
5
  import { DIALOG_CLOSE_SHORTCUT_HINT, paintPiBorder, piTheme, renderPiModalShortcutHints } from "../../theme.js";
6
6
  // Rationale: upstream's capitalized hint formatter is private to the package; the same mapping lives here.
7
7
  function keyDisplayText(keybinding) {
@@ -118,7 +118,7 @@ export class ScopedModelsSelectorComponent extends Container {
118
118
  this.addChild(new Spacer(1));
119
119
  // Search input
120
120
  this.searchInput = new Input();
121
- this.addChild(this.searchInput);
121
+ addPiModalSearchInput(this, this.searchInput);
122
122
  this.addChild(new Spacer(1));
123
123
  // List container
124
124
  this.listContainer = new Container();