@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
@@ -137,6 +137,24 @@ export const OWNED_UI_SETTINGS_MIGRATIONS = Object.freeze([
137
137
  return { ...values };
138
138
  },
139
139
  }),
140
+ Object.freeze({
141
+ to: 18,
142
+ description: "Introduce the top resident tab-bar position default.",
143
+ migrate(values) {
144
+ return { ...values };
145
+ },
146
+ }),
147
+ Object.freeze({
148
+ to: 19,
149
+ description: "Expand the update check into auto, notify, and no modes.",
150
+ migrate(values) {
151
+ if (values.updateCheck === true)
152
+ return { ...values, updateCheck: "notify" };
153
+ if (values.updateCheck === false)
154
+ return { ...values, updateCheck: "no" };
155
+ return { ...values };
156
+ },
157
+ }),
140
158
  ]);
141
159
  /** Shape-selected compatibility for the metadata-free product object. */
142
160
  export function normalizeOwnedUiSettingsValues(source) {
@@ -146,6 +164,10 @@ export function normalizeOwnedUiSettingsValues(source) {
146
164
  values.scrollbarAppearance = "auto";
147
165
  if (values.accentColor === "default")
148
166
  values.accentColor = "purple";
167
+ if (values.updateCheck === true)
168
+ values.updateCheck = "notify";
169
+ else if (values.updateCheck === false)
170
+ values.updateCheck = "no";
149
171
  if (values.quitEffect === "off") {
150
172
  if (Object.hasOwn(values, "quitAnimation") && values.quitAnimation !== false) {
151
173
  return { values: null, failure: "legacy quitEffect conflicts with quitAnimation" };
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Generated by scripts/pi/sync-progress-palette.ts from A1's named accent projection
3
+ * over pinned Pi 1.4.0 (MIT), commit 4a080252f06caf63a525d7873c5d94d3c23887e0. Do not edit directly.
4
+ */
5
+ export declare const SHARED_PROGRESS_PALETTE: Readonly<{
6
+ blue: {
7
+ dark: {
8
+ truecolor: {
9
+ accent: string;
10
+ border: string;
11
+ };
12
+ "256color": {
13
+ accent: string;
14
+ border: string;
15
+ };
16
+ };
17
+ light: {
18
+ truecolor: {
19
+ accent: string;
20
+ border: string;
21
+ };
22
+ "256color": {
23
+ accent: string;
24
+ border: string;
25
+ };
26
+ };
27
+ };
28
+ purple: {
29
+ dark: {
30
+ truecolor: {
31
+ accent: string;
32
+ border: string;
33
+ };
34
+ "256color": {
35
+ accent: string;
36
+ border: string;
37
+ };
38
+ };
39
+ light: {
40
+ truecolor: {
41
+ accent: string;
42
+ border: string;
43
+ };
44
+ "256color": {
45
+ accent: string;
46
+ border: string;
47
+ };
48
+ };
49
+ };
50
+ cyan: {
51
+ dark: {
52
+ truecolor: {
53
+ accent: string;
54
+ border: string;
55
+ };
56
+ "256color": {
57
+ accent: string;
58
+ border: string;
59
+ };
60
+ };
61
+ light: {
62
+ truecolor: {
63
+ accent: string;
64
+ border: string;
65
+ };
66
+ "256color": {
67
+ accent: string;
68
+ border: string;
69
+ };
70
+ };
71
+ };
72
+ green: {
73
+ dark: {
74
+ truecolor: {
75
+ accent: string;
76
+ border: string;
77
+ };
78
+ "256color": {
79
+ accent: string;
80
+ border: string;
81
+ };
82
+ };
83
+ light: {
84
+ truecolor: {
85
+ accent: string;
86
+ border: string;
87
+ };
88
+ "256color": {
89
+ accent: string;
90
+ border: string;
91
+ };
92
+ };
93
+ };
94
+ orange: {
95
+ dark: {
96
+ truecolor: {
97
+ accent: string;
98
+ border: string;
99
+ };
100
+ "256color": {
101
+ accent: string;
102
+ border: string;
103
+ };
104
+ };
105
+ light: {
106
+ truecolor: {
107
+ accent: string;
108
+ border: string;
109
+ };
110
+ "256color": {
111
+ accent: string;
112
+ border: string;
113
+ };
114
+ };
115
+ };
116
+ pink: {
117
+ dark: {
118
+ truecolor: {
119
+ accent: string;
120
+ border: string;
121
+ };
122
+ "256color": {
123
+ accent: string;
124
+ border: string;
125
+ };
126
+ };
127
+ light: {
128
+ truecolor: {
129
+ accent: string;
130
+ border: string;
131
+ };
132
+ "256color": {
133
+ accent: string;
134
+ border: string;
135
+ };
136
+ };
137
+ };
138
+ }>;
139
+ export declare const SHARED_PROGRESS_ACCENT_ANSI: string;
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Generated by scripts/pi/sync-progress-palette.ts from A1's named accent projection
3
+ * over pinned Pi 1.4.0 (MIT), commit 4a080252f06caf63a525d7873c5d94d3c23887e0. Do not edit directly.
4
+ */
5
+ export const SHARED_PROGRESS_PALETTE = Object.freeze({
6
+ "blue": {
7
+ "dark": {
8
+ "truecolor": {
9
+ "accent": "\u001b[38;2;130;160;241m",
10
+ "border": "\u001b[38;2;116;102;197m"
11
+ },
12
+ "256color": {
13
+ "accent": "\u001b[38;5;111m",
14
+ "border": "\u001b[38;5;98m"
15
+ }
16
+ },
17
+ "light": {
18
+ "truecolor": {
19
+ "accent": "\u001b[38;2;63;96;226m",
20
+ "border": "\u001b[38;2;83;67;154m"
21
+ },
22
+ "256color": {
23
+ "accent": "\u001b[38;5;62m",
24
+ "border": "\u001b[38;5;60m"
25
+ }
26
+ }
27
+ },
28
+ "purple": {
29
+ "dark": {
30
+ "truecolor": {
31
+ "accent": "\u001b[38;2;167;152;215m",
32
+ "border": "\u001b[38;2;105;115;167m"
33
+ },
34
+ "256color": {
35
+ "accent": "\u001b[38;5;140m",
36
+ "border": "\u001b[38;5;61m"
37
+ }
38
+ },
39
+ "light": {
40
+ "truecolor": {
41
+ "accent": "\u001b[38;2;116;89;180m",
42
+ "border": "\u001b[38;2;68;77;130m"
43
+ },
44
+ "256color": {
45
+ "accent": "\u001b[38;5;97m",
46
+ "border": "\u001b[38;5;60m"
47
+ }
48
+ }
49
+ },
50
+ "cyan": {
51
+ "dark": {
52
+ "truecolor": {
53
+ "accent": "\u001b[38;2;93;179;186m",
54
+ "border": "\u001b[38;2;74;128;146m"
55
+ },
56
+ "256color": {
57
+ "accent": "\u001b[38;5;73m",
58
+ "border": "\u001b[38;5;66m"
59
+ }
60
+ },
61
+ "light": {
62
+ "truecolor": {
63
+ "accent": "\u001b[38;2;40;122;129m",
64
+ "border": "\u001b[38;2;40;89;106m"
65
+ },
66
+ "256color": {
67
+ "accent": "\u001b[38;5;30m",
68
+ "border": "\u001b[38;5;23m"
69
+ }
70
+ }
71
+ },
72
+ "green": {
73
+ "dark": {
74
+ "truecolor": {
75
+ "accent": "\u001b[38;2;104;183;141m",
76
+ "border": "\u001b[38;2;75;132;120m"
77
+ },
78
+ "256color": {
79
+ "accent": "\u001b[38;5;72m",
80
+ "border": "\u001b[38;5;66m"
81
+ }
82
+ },
83
+ "light": {
84
+ "truecolor": {
85
+ "accent": "\u001b[38;2;51;126;88m",
86
+ "border": "\u001b[38;2;41;93;82m"
87
+ },
88
+ "256color": {
89
+ "accent": "\u001b[38;5;65m",
90
+ "border": "\u001b[38;5;23m"
91
+ }
92
+ }
93
+ },
94
+ "orange": {
95
+ "dark": {
96
+ "truecolor": {
97
+ "accent": "\u001b[38;2;231;135;83m",
98
+ "border": "\u001b[38;2;178;89;79m"
99
+ },
100
+ "256color": {
101
+ "accent": "\u001b[38;5;173m",
102
+ "border": "\u001b[38;5;131m"
103
+ }
104
+ },
105
+ "light": {
106
+ "truecolor": {
107
+ "accent": "\u001b[38;2;174;80;16m",
108
+ "border": "\u001b[38;2;133;57;49m"
109
+ },
110
+ "256color": {
111
+ "accent": "\u001b[38;5;130m",
112
+ "border": "\u001b[38;5;95m"
113
+ }
114
+ }
115
+ },
116
+ "pink": {
117
+ "dark": {
118
+ "truecolor": {
119
+ "accent": "\u001b[38;2;223;122;199m",
120
+ "border": "\u001b[38;2;150;91;171m"
121
+ },
122
+ "256color": {
123
+ "accent": "\u001b[38;5;176m",
124
+ "border": "\u001b[38;5;97m"
125
+ }
126
+ },
127
+ "light": {
128
+ "truecolor": {
129
+ "accent": "\u001b[38;2;178;56;153m",
130
+ "border": "\u001b[38;2;114;61;131m"
131
+ },
132
+ "256color": {
133
+ "accent": "\u001b[38;5;132m",
134
+ "border": "\u001b[38;5;60m"
135
+ }
136
+ }
137
+ }
138
+ });
139
+ export const SHARED_PROGRESS_ACCENT_ANSI = SHARED_PROGRESS_PALETTE.blue.dark.truecolor.accent;
@@ -49,4 +49,8 @@ export interface BuildOwnedUiSettingsSectionsInput {
49
49
  }
50
50
  export declare const AGENT_SECTION_ID = "agent";
51
51
  export declare function buildOwnedUiSettingsSections(input: BuildOwnedUiSettingsSectionsInput): readonly OwnedUiSettingsSection[];
52
+ /** The enabled parts a flagged A1 setting's comma-separated value names, as the record its dialog edits. */
53
+ export declare function flagRecord(flags: readonly {
54
+ readonly key: string;
55
+ }[], value: unknown): Record<string, boolean>;
52
56
  export declare function findOwnedUiSettingsEntry(sections: readonly OwnedUiSettingsSection[], id: string, backend: OwnedUiSettingsBackend): OwnedUiSettingsEntry | null;
@@ -5,20 +5,21 @@ export function buildOwnedUiSettingsSections(input) {
5
5
  for (const setting of input.resolution.settings) {
6
6
  const section = setting.declaration.section ?? { id: "a1", title: "A1" };
7
7
  const group = grouped.get(section.id) ?? { title: section.title, entries: [] };
8
+ const flagged = setting.declaration.flags;
8
9
  group.entries.push({
9
10
  id: setting.declaration.id,
10
11
  label: setting.declaration.label ?? null,
11
12
  backend: "a1",
12
13
  description: setting.declaration.description,
13
14
  value: setting.value,
14
- rawValue: setting.value,
15
+ rawValue: flagged === undefined ? setting.value : flagRecord(flagged, setting.value),
15
16
  storedValue: setting.value,
16
17
  effectiveValue: setting.value,
17
18
  editable: true,
18
- structured: false,
19
+ structured: flagged !== undefined,
19
20
  minimum: null,
20
21
  maximum: null,
21
- flags: [],
22
+ flags: flagged?.map(flag => ({ ...flag, fallback: false })) ?? [],
22
23
  choices: setting.declaration.allowedValues,
23
24
  origin: setting.source,
24
25
  application: setting.declaration.application === "restart" ? "next-start" : "live",
@@ -44,6 +45,11 @@ export function buildOwnedUiSettingsSections(input) {
44
45
  };
45
46
  return Object.freeze([...ownedSections, combinedAgent]);
46
47
  }
48
+ /** The enabled parts a flagged A1 setting's comma-separated value names, as the record its dialog edits. */
49
+ export function flagRecord(flags, value) {
50
+ const enabled = new Set(typeof value === "string" ? value.split(",") : []);
51
+ return Object.fromEntries(flags.map(flag => [flag.key, enabled.has(flag.key)]));
52
+ }
47
53
  export function findOwnedUiSettingsEntry(sections, id, backend) {
48
54
  for (const section of sections) {
49
55
  const entry = section.entries.find(candidate => candidate.id === id && candidate.backend === backend);
@@ -113,7 +113,7 @@ PRs #729–#736 prepared the owned UI for several sessions in one process.
113
113
 
114
114
  ### 1. Tabs are terminal sessions running the complete A1 UI
115
115
 
116
- Each tab runs `node bin/ui.js --tab` under a pseudoterminal. That child is today's owned UI with its in-process Pi runtime, extensions, and custom extension components, so there is no engine split, no remote adapter, and no extension rendering bridge. Tab mode adds only resident integration concerns:
116
+ Each Agent tab runs `node bin/ui.js --tab` under a pseudoterminal. That child is today's owned UI with its in-process Pi runtime, extensions, and custom extension components, so there is no engine split, no remote adapter, and no extension rendering bridge. Settings and Changelog remain terminal sessions but use the slim utility composition recorded in Decision 11 because they host no Agent. Agent tab mode adds only resident integration concerns:
117
117
 
118
118
  - it connects the tab bridge (Decision 7);
119
119
  - quit routes request a client detach instead of exiting (Decision 11);
@@ -167,11 +167,21 @@ Every attach, server, and holder role is `process.execPath` running the release'
167
167
  Because ConPTY belongs to its creating process, each tab has its own creator. A server crash therefore does not destroy a tab's ConPTY; holder failure affects only that tab. Actual child/tree exit and writer-lease release still require verification, not inference from the pseudoconsole closing. The 2026-10-17 Windows x64 migration measurement put an idle Node-hosted holder at approximately 30–40 MiB of resident memory, versus a few MiB for the former native-executable holder; allocator and shared-page accounting make this a range rather than a per-tab budget. The separate holder process is retained for certified crash isolation, and latency budgets were not raised to absorb Node startup.
168
168
  - **Why the server relays surfaces instead of clients connecting to holders directly.** It gives one authenticated endpoint per client, one ordering domain for focus and topology, and simpler multi-client arbitration. Measure the extra local pipe hop in the native hot-path evidence rather than assuming its latency. The server forwards encoded holder surfaces and does not re-render them.
169
169
 
170
+ ### Windows completion-driven transport and wake diagnosis
171
+
172
+ Windows server and client endpoints use overlapped named-pipe I/O. Each operation owns a stable `OVERLAPPED` and non-inherited completion event; read and write workers share handle custody but never serialize a pending read against a write. A worker waits for completion, connection shutdown, or an absolute admission deadline, not `PeekNamedPipe` plus a short sleep. The four-second first-envelope deadline covers fragmented headers and bodies. Cancellation targets one operation and drains its kernel completion before releasing its buffer/event; split-worker guards wake the other direction when the connection owner exits. Owner ACLs, first-instance protection, peer checks, frame limits, control ordering, and latest-surface bounds are unchanged. Unix socket behavior is unchanged.
173
+
174
+ Transport correctness does not rely on `timeBeginPeriod`. The existing per-role timer request remains for non-I/O timers, including ConPTY render-pass settling and animation/maintenance deadlines; removing it requires separate scheduling evidence. A detected attach gap still requests one complete cached repaint, never a process restart or prompt replay.
175
+
176
+ The existing opt-in progress route (`A1_RESIDENT_PROGRESS_OBSERVATION_DIR` plus `A1_RESIDENT_PROGRESS_RUN_ID`) adds `pipe-frame-read`, `pipe-write-complete`, `outer-write-complete`, `wake-gap`, and `wake-repaint-requested` stages. Connection-level records may omit tab identity. `durationUs` is local monotonic completion/assembly time; `idleWaitUs` separately describes waiting for the next frame and is not delivery latency. Native records include `monotonicUs`, `clockEpoch`, and optional `clockDiscontinuity`. Consumers exclude negative, epoch-crossing, and clock-discontinuity-crossing derived samples, while retaining valid local completion durations. The queue remains bounded and asynchronous, with loss accounting and no terminal content or credentials. Outer-write completion is not proof of physical pixels.
177
+
178
+ For recurring post-wake stalls, compare an existing resident client, a new client on the same resident host, and direct `a1 solo` in the same outer terminal. Use matched holder/attach revisions and local I/O durations to distinguish child progress, transport delay, and outer writes. If solo and other terminal applications also stall, do not assign the failure to resident pipes without further evidence. Simulated-gap fixtures do not certify physical sleep/wake recovery; do not automate workstation suspend. Updates retain old resident cohorts, but a new launch selects its own immutable release namespace rather than reusing their host. Existing roles keep their original code and lifecycle; installing an update never recycles running tabs.
179
+
170
180
  ### 3. Host scope, endpoints, writer authority, and credentials
171
181
 
172
- - There is **one server per OS user × canonical A1 profile root**, independent of the release cohort. Tabs must outlive release changes, and the supervisor is forbidden from retaining runtime processes.
173
- - Windows endpoint: `\\.\pipe\a1-tabs-<sha256(canonicalProfileHome)[0:20]>`.
174
- - macOS and Linux endpoint: a Unix domain socket `a1-tabs-<same profile hash>.sock` inside an owner-only (`0700`) per-user runtime directory (`$XDG_RUNTIME_DIR` when set, otherwise a private directory under the profile's runtime dir), kept short enough for the platform's socket path limit.
182
+ - There is **one server per OS user, canonical A1 profile root and immutable release identity**. Different releases coexist without migration; the supervisor remains forbidden from retaining runtime processes. A domain-separated hash of canonical profile and fixed originating release/addon identity selects the flat `<data>/tabs/<scope>` directory, credential files, registry, epochs and `server-start-<scope>.lock`.
183
+ - Windows endpoint: `\\.\pipe\a1-tabs-<scope[0:20]>`.
184
+ - macOS and Linux endpoint: a Unix domain socket `a1-tabs-<same scope>.sock` inside an owner-only (`0700`) per-user runtime directory (`$XDG_RUNTIME_DIR` when set, otherwise a private directory under the profile's runtime dir), kept short enough for the platform's socket path limit.
175
185
  - Generation 1 admits holders over the authenticated owner endpoint. Dedicated `…-h-<tabId>` endpoints remain an optional later optimization.
176
186
  - Hermetic override: `A1_TABS_ENDPOINT`, together with the `A1_*_DIR` overrides.
177
187
  - **The endpoint is discovery; the writer lease is authority.** A starting server probes with a real handshake. If a live server answers, it exits as `already-running` and joins the winner. A failed probe does not authorize blind endpoint reclaim: the starter reads the owner marker, verifies pid and native start identity, requests termination, and waits for the owner-only OS-exclusive registry-writer lease. If the owner cannot be verified or the lease cannot be acquired, startup fails closed instead of creating split brain.
@@ -179,7 +189,7 @@ Every attach, server, and holder role is `process.execPath` running the release'
179
189
  - **Access and authentication:**
180
190
  - The endpoint, token files, secret file, registry, and writer lease are owner-only: Windows ACLs on the named pipe and files, `0700` directories and `0600` files on macOS and Linux, plus a peer-credential check (`SO_PEERCRED` / `getpeereid`) on every Unix socket connection.
181
191
  - Clients present a random 32-byte client token from an owner-only file.
182
- - One random owner-only profile secret persists outside the registry. Per-tab/per-incarnation holder and bridge credentials are derived from that secret and the non-secret tab/incarnation identity, so a replacement server can authenticate surviving processes without storing tab credentials.
192
+ - One random owner-only profile/release secret persists outside its registry. Per-tab/per-incarnation holder and bridge credentials are derived from that secret and the non-secret tab/incarnation identity, so a replacement server can authenticate surviving processes without storing tab credentials.
183
193
  - Holders and bridges also present pid and native start identity. Nothing unverifiable is adopted, signalled, terminated, or allowed to mutate state.
184
194
 
185
195
  ### 4. Detached start
@@ -229,7 +239,7 @@ The channels carry bounded generation-1 frames: a u32 little-endian length follo
229
239
 
230
240
  ### 6. Rendering, input, and outer-terminal fidelity
231
241
 
232
- - **Composition.** The attach client composes row 0 (the strip) and rows `1..N` (the viewed tab's surface). The tab PTY is sized `cols × (rows − 1)`. Ghostty remains the sole child-terminal parser. Its stream parser reports synchronized-output begin/end/reset boundaries to the holder, which keeps mutable parser state separate from an immutable committed surface and never publishes an open transaction. A non-renewable 250 ms deadline publishes one typed recovery commit for a malformed transaction. On Windows, ConPTY re-renders the child's cells on its own schedule and relays the DEC 2026 markers when it parses them, so a render pass can land between a frame's markers and the rest of its cells can arrive after the end marker; the holder therefore treats a relayed end marker as "frame parsed", keeps the state private until the following render pass has arrived and settled for 2 ms, and publishes anyway after a 40 ms bound. A begin marker that arrives while that pass is still pending does not publish the torn state. Unix PTYs relay bytes verbatim, so their markers still delimit frames exactly. The outer composer owns the only synchronized envelope and falls back to a full repaint only when identity, geometry generation, dimensions, or the structured base changes.
242
+ - **Composition.** The attach client composes row 0 (the strip) and rows `1..N` (the viewed tab's surface). The tab PTY is sized `cols × (rows − 1)`. Ghostty remains the sole child-terminal parser. Its stream parser reports synchronized-output begin/end/reset boundaries to the holder, which keeps mutable parser state separate from an immutable committed surface and never publishes an open transaction. A non-renewable 250 ms deadline publishes one typed recovery commit for a malformed transaction. On Windows, ConPTY re-renders the child's cells on its own schedule and relays the DEC 2026 markers when it parses them, so a render pass can land between a frame's markers and the rest of its cells can arrive after the end marker; the holder therefore treats a relayed end marker as "frame parsed", keeps the state private until the following render pass has arrived and settled for 2 ms, with a non-renewable 16 ms fallback for the oldest unpublished pass. New end markers and continuous settling fragments cannot postpone that bound; consuming a complete or recovery publication clears it for the next update. The fallback never authorizes ordinary publication inside an open transaction. A begin marker that arrives while that pass is still pending does not publish the torn state. Unix PTYs relay bytes verbatim, so their markers still delimit frames exactly. The outer composer owns the only synchronized envelope and falls back to a full repaint only when identity, geometry generation, dimensions, or the structured base changes. Decoded child grids move into shared immutable attach/outer-offer/completed-baseline custody, so chrome-only work neither deep-clones nor revalidates an unchanged body. Overlay restoration and new grids still take the damage/validation path, and resize fading uses copy-on-write. Each attach round admits one queued input before its bounded server-render round; neither input nor rendering can starve the other. Attach and holder wait on coalesced wake notifications only after a round receives no work, so an already-ready backlog never waits for another producer pulse; all per-lane round limits and quiescent idle waits remain intact.
233
243
  - **Outer modes.** The client negotiates the outer terminal's modes: raw mode, alternate screen, bracketed paste, focus events, SGR mouse, kitty keyboard protocol where available, and synchronized output.
234
244
  - Child-requested modes are tracked in the holder's model. Input is re-encoded per child mode by libghostty-vt, which the proof already does.
235
245
  - Mouse rows are offset by the strip. Row 0 belongs to the client.
@@ -250,7 +260,7 @@ Direct `a1 solo` is the behavioural oracle for every viewed resident child. Only
250
260
  - **Modes and cursor.** Each surface carries the child's cursor cell and modes. Attach mirrors a cursor shape the child chose and resets it on detach or failure; a default block cursor is left to the outer terminal, as direct A1 leaves it.
251
261
  - **Shortcut projection.** The owned child projects the effective A1 keybindings plus `keybindings.json` into one assignment per tab action: a key an A1 action holds never becomes a tab shortcut, and the earlier declaration keeps a duplicated key. It publishes the projection as `bridge.shortcuts`; the server relays it as `tabs.shortcuts`, and attach dispatches and labels exactly that projection. `/hotkeys` renders the same projection. A shared case file is replayed by both resolvers.
252
262
  - **Committed presentation.** A requested view commits only with a topology that lists it and a complete target surface. Retained rows, palette/default colors, modes, and cursor are captured atomically; equal captures do not invent a revision. Surfaces of the committed view apply in revision order, and revisions rise across holder incarnations. A retained re-offer is marked stale from the request start and parks for at most 120 ms while the fresh frame arrives. One compositor writes strip, body, overlay, modes, and the viewport-clipped final cursor inside a single synchronized update. It erases suffix cells when a complete replacement row becomes shorter and restores the newest underlying surface when an overlay leaves. Clock, hover, and title-animation damage repaints only the strip and restores the child's cursor.
253
- - **Evidence.** One interaction matrix drives three layers: an owned-shell oracle that records content-free observations, role fixtures that drive real server, holder, and attach processes with a certified parity child, and exact-artifact harnesses. `verify-interaction-parity.mjs` compares the bytes the same child observes directly and through the built host. `verify-resident-parallel-progress.mjs` configures the real engine against a bounded loopback streaming provider and crosses all six first-response orders with all three initial views while every turn remains unfinished behind a completion barrier. At matching 100×29 child geometry, the same real-A1 prompt also runs through isolated direct-solo and resident profiles; the native Ghostty oracle compares a content-free digest of the intermediate fixture cells and their resolved styles together with normalized cursor and mode state. It advances only at parser-reported synchronized-output boundaries, rejects unbalanced transactions or disappearing intermediate cells, and excludes only the resident strip row rather than normalizing terminal semantics. Opt-in per-process JSONL observations identify engine delivery, completed child writes, PTY ingestion, distinct holder commits, attach receipt, and completed outer paint by run/tab/incarnation/process-instance/sequence without recording terminal or conversation content; queue loss, clock faults, duplicates, stale order, and missing stages fail the report. Named pressure variants keep all turns unfinished while exercising hidden progress, repeated switching, navigation input, rename overlays, tab-local resize, a bounded 35 ms attach-receipt delay, and a 900 ms paused outer sink; every tab must retain partial animation/progress before settlement and then present its final output. A detached/reconnect variant releases all three unfinished turns after terminating the authenticated attach and exact self-reported server identity; child writes, PTY ingestion, and holder commits must continue before a replacement server and attach present every retained final output. Separate isolated exact-artifact control markers stop one target tab at engine delivery, physical child rendering, holder commit, or outer paint; each control requires its upstream stages and the other tabs to advance, then removes the stop and requires final output from all three tabs. A fifth control fragments a real owned-shell synchronized write into at most 64 physical writes without changing its bytes, records the DEC private mode 2026 transaction classification, and requires ordinary complete holder commits without deadline recovery before final output. This characterizes the built PTY/ConPTY coherence path; a passing fragmented run does not by itself assign the historical stall to that path without a failing live reproduction. The marker path is absent in ordinary sessions and is scrubbed before extensions or tools inherit the child environment, while observation callbacks remain passive and cannot select a control. CI runs the vendored Ghostty VT tests and both exact-artifact harnesses on Windows x64, Linux x64, and macOS arm64. Each platform's verdict stands alone.
263
+ - **Evidence.** One interaction matrix drives three layers: an owned-shell oracle that records content-free observations, role fixtures that drive real server, holder, and attach processes with a certified parity child, and exact-artifact harnesses. `verify-interaction-parity.mjs` compares the bytes the same child observes directly and through the built host. `verify-resident-parallel-progress.mjs` configures the real engine against a bounded loopback streaming provider and crosses all six first-response orders with all three initial views while every turn remains unfinished behind a completion barrier. At matching 100×29 child geometry, the same real-A1 prompt also runs through isolated direct-solo and resident profiles; the native Ghostty oracle compares a content-free digest of the intermediate fixture cells and their resolved styles together with normalized cursor and mode state. It advances only at parser-reported synchronized-output boundaries, rejects unbalanced transactions or disappearing intermediate cells, and excludes only the resident strip row rather than normalizing terminal semantics. Opt-in per-process JSONL observations identify engine delivery, completed child writes, PTY ingestion, distinct holder commits, attach receipt, and completed outer paint by run/tab/incarnation/process-instance/sequence without recording terminal or conversation content; queue loss, clock faults, duplicates, stale order, and missing stages fail the report. The child observer batches check-phase appends with one directory initialization while retaining its pending-byte and loss limits. Auxiliary PTY-input completion and child input/semantic/composition/write phases narrow input-side delays without recording key or terminal content. Healthy viewed windows require at least forty matched live commit-to-first-paint samples per tab and enforce p95 at most 33 ms; activation/stale re-offers, later chrome paints of the same revision, and deliberately stopped or paused-sink windows cannot masquerade as live delivery samples. An additional real-A1 fixture compares forty synthetic typing, Models navigation, and transcript wheel responses per path at equal child geometry. Timing requires the shipping Rust optimization profile with all fixture controls retained; debug unit/role/probe coverage and feature-free shipping-artifact checks remain separate. Both paths retain matching complete 60-marker transcript baselines before sampling. Untimed provider setup acknowledges a complete literal editor echo before sending a separate Enter; the measured inputs keep their ordinary 30 ms cadence. Its incremental native Ghostty oracle matches completed semantic states, permits coalesced intermediate states, and rejects missing responses, one-second stalls, or resident p95 beyond direct solo plus the existing 16+33 ms input/presentation envelope. Direct target hashes come from the final semantic state in each ordinary inter-input interval, while timing uses its first observed occurrence, not a later settling delay or an early torn ConPTY hash. A fixed fixture role entry pins the exact addon for all detached roles, including Windows WMI launches that do not inherit the invoking shell's development override; production launch behavior is unchanged. Named pressure variants keep all turns unfinished while exercising hidden progress, repeated switching, navigation input, rename overlays, tab-local resize, a bounded 35 ms attach-receipt delay, and a 900 ms paused outer sink; every tab must retain partial animation/progress before settlement and then present its final output. A detached/reconnect variant releases all three unfinished turns after terminating the authenticated attach and exact self-reported server identity; child writes, PTY ingestion, and holder commits must continue before a replacement server and attach present every retained final output. Separate isolated exact-artifact control markers stop one target tab at engine delivery, physical child rendering, holder commit, or outer paint; each control requires its upstream stages and the other tabs to advance, then removes the stop and requires final output from all three tabs. The outer-paint control waits for a payload-free `fixture-stop-ack` from the sole writer before starting its scenario; that acknowledgement follows all earlier writes, and same-writer sequence ordering excludes only pre-acknowledgement paints from the zero-progress check. Any later paint still fails. A fifth control fragments a real owned-shell synchronized write into at most 64 physical writes without changing its bytes, records the DEC private mode 2026 transaction classification, and requires ordinary complete holder commits without deadline recovery before final output. This characterizes the built PTY/ConPTY coherence path; a passing fragmented run does not by itself assign the historical stall to that path without a failing live reproduction. The marker path is absent in ordinary sessions and is scrubbed before extensions or tools inherit the child environment, while observation callbacks remain passive and cannot select a control. CI runs the vendored Ghostty VT tests and both exact-artifact harnesses on Windows x64, Linux x64, and macOS arm64. Each platform's verdict stands alone.
254
264
 
255
265
  ### 7. Status and attention come from the tab bridge
256
266
 
@@ -303,7 +313,7 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
303
313
  | Tab child exits or crashes | holder sees the child exit | If the exit was not requested: `crashed`. The holder respawns `ui.js --tab --session <file>` with backoff 1 s, 5 s, 30 s. After 3 restarts in 10 min: `failed`, with a banner `✗ <reason> — [r] retry · [f] start fresh · [alt+w] close`. An interrupted prompt (reported via the bridge, or detected as a trailing user entry) is offered back into the editor and never resent. Child stderr, if separately available, is a private content-bearing recovery artifact, never an ordinary diagnostic log. |
304
314
  | Holder crash | pipe EOF plus verified process identity/exit | Pseudoterminal loss is not proof that every descendant exited. Verify the old writer/tree is gone and acquire its writer lease before resuming in a new holder; otherwise keep the tab blocked without duplicate writers. |
305
315
  | Holder hang | holder control heartbeat every 5 s, 3 missed over a healthy supervision path, then one heartbeat of grace for the holder-local watchdog record | Terminate only the verified holder/tree within bounded deadlines, then follow the lease-gated recovery path. |
306
- | A1 child hang | missing child event-loop heartbeat over a healthy bridge path | Warn at 30 s; restart at `tabsUnresponsiveRestartSeconds` (default 120), preserving journal/screen. A server/bridge transport outage alone is not proof of a child hang. |
316
+ | A1 child hang | missing child event-loop heartbeat over a healthy bridge path | Record `unresponsive` at 30 s without changing the chip or painting a notice; restart at `tabsUnresponsiveRestartSeconds` (default 120), preserving journal/screen. A server/bridge transport outage alone is not proof of a child hang. |
307
317
  | Server crash | clients and holders see the pipe EOF | Holders keep running. A client or holder starts a replacement, which acquires the released writer lease, increments the epoch, derives expected credentials, and re-admits identity-verified holders. A live but unresponsive recorded owner is terminated only after native identity verification; an unverifiable owner blocks replacement. After 3 starts in 60 s, clients show `✗ tab host stopped — [r] restart · [q] quit`. |
308
318
  | Attach client crash or terminal close | the server sees the pipe EOF | Nothing happens to tabs. Pending requests stay `needs-input`. |
309
319
  | Reboot or logout | the platform boot identity differs, or no verified holders exist for a new OS session | Tabs are not restored in this version. The server moves the previous tab set to registry history, and bare `a1` starts with one new tab. Every previous session stays resumable through the normal session picker or `a1 --session`. A pending journaled prompt is offered when its session is next opened in a tab, for up to seven days. Restoring tabs after reboot is a follow-up change. |
@@ -316,16 +326,16 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
316
326
  ### 10. Updates and version skew
317
327
 
318
328
  - **Artifacts and process images.** The server and holders are Node runtimes that load the addon and fixed entry from immutable release directories, never from the mutable npm prefix. Activation therefore never overwrites a loaded addon or entry, and release retention keeps every release used by a verified resident process.
319
- - **Same generation, newer client.** The client attaches normally, and features the server lacks are disabled per method.
320
- - **No automatic handoff in the first version.** A compatible resident cohort remains on its immutable release until all of its tabs stop. A newer same-generation client may attach with unsupported optional operations disabled.
321
- - **Retention.** Release collection keeps the server's, every holder's, and every tab child's release while native identities verify. Package activation does not terminate or overwrite them.
322
- - **Incompatibility.** A generation mismatch never kills resident processes. The client reports that the resident host must be stopped explicitly before the preview can restart on the newer generation. Automatic server handoff and idle tab recycling require a follow-up change.
329
+ - **Exact release admission.** Clients, holders and bridges present a release identity before capability negotiation. Matching protocol generation or display version does not permit cross-release attachment. Native clients also check the welcome identity; bridge identity is consumed from holder-private launch context before extensions load.
330
+ - **Independent activation.** A newer release starts or reuses its own host. Existing resident and solo processes remain untouched. Server recovery and child launches retain the originating fixed Node, entry and addon; the active-release pointer never retargets them. No automatic migration, handoff or idle recycling is performed.
331
+ - **Retention.** Read-only bounded collection scans every flat release namespace and legacy profile namespace. It keeps server, holder and child releases while identities verify and retains all candidates on unreadable, linked, oversized or unverifiable state. Package activation never overwrites loaded artifacts.
332
+ - **Maintenance and legacy state.** Maintenance resolves the invoking package's immutable materialization without activation or host startup, uses only that release's verified entry, and never retries another recorded cohort. List, stop, status and doctor remain release-local, including unresponsive-host termination. Legacy unversioned state is neither migrated nor adopted; it is read only for conservative retention. Shared configuration and session file locks remain profile-neutral.
323
333
 
324
334
  ### 11. Foreground UX
325
335
 
326
336
  - **Strip.** Row 0, drawn by the attach client, reproduces the approved v2 default appearance:
327
337
  - normal chips use RGB background `38,38,38` with foreground `170,170,170`, hover uses `48,48,48`, and the viewed chip uses `55,55,55` with white text, plus v2's palette fallback;
328
- - the empty state contains only the hoverable `+` control, with no clock, wallpaper, hint, or child cursor;
338
+ - the empty state keeps the hoverable `+` control and the clock beside the landing, with no cursor;
329
339
  - chips are at most 20 columns, clipped with `…` by grapheme width, with no separators or close glyph;
330
340
  - a `…` overflow menu holds tabs that do not fit, and `+` is always reserved;
331
341
  - these presentation choices are fixed, not settings, while each working spinner uses that tab's reported A1 accent.
@@ -337,6 +347,8 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
337
347
  | `Alt+W` | close |
338
348
  | `F2` | rename |
339
349
  | `Alt+,` | open the global Settings tab, or close it while it is viewed |
350
+ | `Alt+/` | open the command palette, or close it while it is open |
351
+ | unbound (`tabs.changelog`) | open the Changelog tab |
340
352
  | `Alt+1`…`Alt+9`, `Alt+0` | jump to a tab |
341
353
  | unbound (`tabs.next` / `tabs.previous`) | next / previous, wrapping |
342
354
  | unbound (`tabs.moveRight` / `tabs.moveLeft`) | move the tab right / left |
@@ -347,24 +359,35 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
347
359
  - **`Ctrl+C` twice leaves, in every tab (user decision).** The client forwards the first `Ctrl+C` to the tab unchanged, so a single press still clears, copies, or interrupts. It consumes the second press within the existing clear/exit interval as the detach request and does not forward it. The tab therefore never receives a second `Ctrl+C` from the chord, which removes v2's bug where `Ctrl+C+C` killed the agent. The trade-off: a future CLI tab cannot receive two quick `Ctrl+C`s. Pressing them more slowly than the interval still reaches the program.
348
360
  - **Mouse on row 0:**
349
361
  - click activates a tab;
350
- - right-click opens `Rename`/`Close` (`Close` only for Settings);
362
+ - right-click opens `Rename`/`Close` (`Close` only for Settings and Changelog);
363
+ - right-click on `+` opens a one-row `Configure` menu that opens the `Plus Menu` dialog over the dimmed active tab, without switching tabs;
351
364
  - dragging reorders, with a `│` drop marker, after 250 ms or once the pointer moves;
352
- - `+` opens a persistent dark v2 menu with `+ Agent`, a separator, and `Settings`; hover and click remain active through the opening release.
365
+ - `+` follows the plus menu: with only Agent enabled it adds an Agent at once; with other kinds it opens a persistent dark v2 menu of the enabled kinds, `+ Agent` first and a separator before the `Settings` and `Changelog` toggles, each `✓` while its tab is open; with none the strip hides `+`. Hover and click remain active through the opening release.
353
366
  - **Settings.** The strip owns one profile-global `Settings` tab. The grouped launcher creates or activates it; `Alt+,` does the same from another tab and closes it while it is viewed. Esc does not close the Settings tab; closing the tab removes it. Resident Agent autocomplete omits `/settings`, and manually entering it does not open a per-Agent screen. `a1 solo` and `a1 pi` retain their existing Settings routes.
354
- - **Commands inside A1 tabs:** existing `/name <name>` also renames the tab. Milestone 5 adds no tab-management slash commands: `/new-tab`, `/close`, `/tabs`, `/quit-all`, and `/restart-tab` are not exposed.
367
+ - **Commands inside A1 tabs:** a tab's name and its session's name stay in sync. a strip rename or an automatic name sets the session name through the bridge, and a session name set otherwise publishes the session view at once, so the tab takes it as a user name; and a resumed session's name becomes the tab name. An automatic name never replaces a user name. Resident Agents also omit and refuse `/changelog`, which opens as its own tab, and `/name`, since renaming the tab (`F2`) renames the session; direct A1 keeps both. Milestone 5 adds no tab-management slash commands: `/new-tab`, `/close`, `/tabs`, `/quit-all`, and `/restart-tab` are not exposed.
355
368
  - **Naming:**
356
369
  - Default names are `agent` or `agent N`.
357
370
  - When `tabsAutoName` is on (the default, by user decision), the tab's model proposes a hyphenated lowercase name of at most 16 characters after the first settled turn. It uses a small budget and one retry, with a fallback derived from the first prompt, and it runs inside the child.
358
371
  - A user rename sets `nameSource=user`, and auto-naming never overrides it.
359
372
  - An automatic rename uses v2's title transition: 600 ms moving rainbow, typing at 45 ms per character, then a 300 ms settle. Manual renames do not animate.
360
373
  - Rename is inline in the chip: `Enter` commits, `Esc` or a click elsewhere cancels, and `Ctrl+Left` / `Ctrl+Right` move by word. The name is trimmed, must not be empty, is at most 64 characters, and has control characters stripped.
361
- - **Close.** `Alt+W` and the right-click menu close a tab directly, including while it is working. Closing removes the chip without a confirmation, stopping/read-only frame, or close glyph, then gracefully stops the child under the existing bounded termination policy. Its session stays resumable. Closing the last tab leaves only the hoverable `+` control.
374
+ - **Plus menu.** The A1 setting `tabBarPlusMenu`, shown as `Plus menu` in the `Tab Bar` Settings section, holds the kinds `+` offers, Agent and every utility kind, as comma-joined keys in that order and defaults to `agent`. The `Plus Menu` dialog lists every kind with `●` when enabled and `○` when not under `Choose what the + button in the tab bar can add.`, with `Space` to toggle, Up and Down to move, typing to filter, and `Esc` to close. `+`'s `Configure` opens it in the attach client as a modal over the dimmed active tab or landing, built on the command palette's list and renderer, with a reversed white search caret like owned inputs. The Settings `configure` entry opens the same modal: the Settings child sends a `configure-plus` bridge request as the tab's controlling client, which the server forwards to that client as `plus.configure`; only without a bridge does Settings open its own equivalent dialog. Each toggle is written at once: the attach client takes the `settings.json.lock` directory lock the TypeScript writer takes through `proper-lockfile`, rereads the document, replaces only `a1.tabBarPlusMenu` (removed at the default), and renames a temporary file over it. A refused write restores the stored menu. The attach client reads the setting live, and with no kind enabled the strip hides `+`. Because a modal's overlay saves and restores the cursor while faint is active, the strip starts every frame from reset attributes, so no chip stays faint after a modal closes.
375
+ - **Command palette.** `Alt+/` (`tabs.commands`) opens a centered box over the whole frame in the owned list style: an accent `Commands` title, a `> ` search row, `Agent`, `Settings`, and `Changelog` with their effective shortcuts right-aligned, and a key hint. Typing or pasting filters it, Up and Down move, `Enter` runs the selected entry through its shortcut action, and `Esc` or `Alt+/` closes it; another tab shortcut closes it and runs. While it is open no key, paste, or pointer event reaches the strip or a child, the cursor is hidden, and the composer redraws the strip and body with faint re-asserted after every SGR beneath the bright overlay.
376
+ - **Changelog.** The changelog opens as a singleton `changelog-` utility tab titled `Changelog`, from the palette or the unbound `tabs.changelog` action. The tab registry accepts the `agent`, `settings`, and `changelog` kinds. Like Settings it is never renamed, offers only `Close`, is placed last, does not count as an Agent, and is prewarmed and hidden: the attach client starts it with `tab.prewarm-changelog` once Settings is ready, and again after a Changelog tab closes, so opening it shows the running tab at once. Like a hidden Settings tab, a hidden Changelog never takes the view when the viewed tab closes, so closing the last Agent shows the landing. Its screen is the tab: it shows no `Esc close` hint, and neither `Esc` nor one `Ctrl+C` closes it.
377
+ - **Utility tab rules.** Settings and Changelog are kinds in one table, `UtilityKind` in the attach core and `UTILITY_KINDS` on the server, and every rule follows the kind rather than a per-kind path: a kind's toggle shows a tab already in the strip in its slot, reveals a hidden or absent one through `tab.open-<kind>`, which the server answers by starting it or placing the running one last, and hides the viewed one while its process keeps running; the client prewarms each kind with `tab.prewarm-<kind>` once the kind before it runs; and a hidden one never takes the view when another tab closes. In the child, the route matching a resident tab's id is that tab's pinned screen. Adding a kind is a table entry, its prewarm state, and its route.
378
+ - `bin/ui.js` resolves the utility route from the consumed tab id before loading the declared Agent startup graph. A utility id imports the slim composition instead, so no Pi engine host, Agent session, extensions, prompt history, suggestions, or draft recovery are loaded or started; Agent tabs, the standby, `a1 solo`, and `a1 pi` keep the full graph.
379
+ - The slim process still owns one fullscreen `OwnedUiTerminalHost`, the background canvas, theme and accent singletons, Pi keybindings, controller-boundary input, and the tab bridge. A minimal presenter opens only the pinned route. It publishes effective tab shortcuts, requests the attach client's Plus Menu through `configure-plus`, requests tab close when the screen closes, detaches the controlling client on the interrupt exit, and ends on `bridge.stop`.
380
+ - Settings builds its Agent section from Pi's persisted `SettingsManager` after the ordinary project-trust decision, with persist-only bindings because each Agent process owns live effects. Its per-model thinking choices come from an offline `ModelRuntime` over the profile's `auth.json` and `models.json`. Changelog reads the packaged release-note catalog directly and never takes or acknowledges an Agent startup "What's New" claim.
381
+ - **Reveal.** Opening a tab draws it at once: a created Agent and a revealed Settings or Changelog tab both keep the current content, another tab or the landing, until their first drawn frame commits, a retained blank surface included, and a tab that never draws shows after the bounded first-draw wait. Only a resize fades (`roles/reveal.rs`): once the curtain lifts, the whole frame draws faint for one fade interval with no cursor, then at full intensity. A starting chip shows no spinner. One tab opens at a time: new-Agent input while a tab the client asked for is starting or not yet shown starts nothing.
382
+ - **Rename selection.** The inline rename draws a selection on the prompt editor's selection background (RGB `38,79,120`, palette `24`) and keeps the chip's text color, restoring the chip's background after it; only the caret is a reversed cell.
383
+ - **Close.** `Alt+W` and the right-click menu close a tab directly, including while it is working. Closing removes the chip without a confirmation, stopping/read-only frame, or close glyph, then gracefully stops the child under the existing bounded termination policy. Its session stays resumable. Closing the last tab leaves `+` and the clock beside the landing.
384
+ - **Landing.** While the attach client has no tab to show, the attach role renders the a1 landing as the frame's body. The launch cwd sits where an Agent tab's footer shows it, flush left on the second-to-last body row with the same `~` formatting and `dim` color, so the folder does not move when a tab opens or closes; `v<version>`, read once from the release root's `package.json`, sits below it and the three-row half-block `a1` wordmark in the profile's accent above it (omitted in Apple Terminal). The accent colors are a native table pinned by `landing_accents.json`, which a TypeScript test checks against `derivePiAccentProjection`. The body is painted with the canvas `backgroundStyle` gives Agent tabs. The landing holds no input: tab shortcuts, the launcher, strip mouse, and double-`Ctrl+C` behave as with tabs, and no cursor shows. A hidden Settings tab is prewarmed and kept running beside the landing, as beside Agents, so Settings opens without a start; it closes when the client detaches, and an attach treats a registry holding only Settings as empty, so it starts and views its first Agent. Adding a tab from the landing keeps it until the tab's first drawn frame replaces it at once; the landing has no animation of its own.
362
385
  - **Detach.**
363
386
  - `Ctrl+C` twice detaches from any tab and is handled by the attach client, so it works even when the bridge is down. Inside an A1 tab, `/quit` and empty-editor `Ctrl+D` send a detach request carrying the immutable command-origin controller identity and revision; stale, ambiguous or unavailable attribution keeps the tab running and directs the user to `Ctrl+C` twice.
364
387
  - The client restores the terminal. When agents are still running, it prints `1 agent still running run a1 to return.` or `N agents still running run a1 to return.`; otherwise it prints the resume hint.
365
388
  - **Launch and reattach:**
366
389
  - Bare `a1` ensures the server and attaches.
367
- - With zero tabs, it creates one A1 tab in the launch cwd.
390
+ - With zero tabs, it creates one A1 tab in the launch cwd; a registry holding only the hidden Settings tab counts as zero.
368
391
  - Otherwise it views the last active tab from its retained surface without byte-history replay. Every holder keeps draining its PTY and updating its retained terminal independently while hidden; selecting a working tab requests its newest complete partial-response surface immediately and then continues live delivery. Measure retained first paint and first input separately from bootstrap; status for the remaining tabs arrives without subscribing to their surfaces.
369
392
  - `a1 --session X` opens X in a new tab, or focuses the tab holding it.
370
393
  - A new tab's cwd is the client's launch cwd.
@@ -377,7 +400,7 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
377
400
  Internal resources remain bounded without imposing a user-visible tab-count quota.
378
401
 
379
402
  - The only user-facing Tabs setting during resident launches is `tabsAutoName` (default on). Legacy `tabsMax` data is preserved but ignored. Tab enablement is not a setting. Direct `a1 solo` and `a1 pi` launches omit the Tabs section entirely.
380
- - Unresponsive restart and stall-notice timing are internal fixed behavior at 120 and 300 seconds; concurrent starts use internal default 2/hard cap 8 and prewarm uses internal count/hard cap 1.
403
+ - Unresponsive restart timing is internal fixed behavior at 120 seconds; there is no progress-stall notice; concurrent starts use internal default 2/hard cap 8 and prewarm uses internal count/hard cap 1.
381
404
  - Every open tab keeps its holder and A1 process until explicit close. There is no idle-suspension setting, timer, lifecycle, or glyph.
382
405
  - Scrollback is capped at 10 MiB per holder, and the replay after a server restart is the retained model itself.
383
406
  - The server exits after 10 minutes with no attached client and no live user tab; the prewarmed standby does not keep it alive and is stopped first.
@@ -479,8 +502,8 @@ The v2 daemon was unreliable for structural reasons, and a forensic pass over it
479
502
  **Agents that stop making progress.** Liveness and progress are separate signals:
480
503
 
481
504
  - Holder heartbeat: a Rust main-loop tick every 5 s seen by the server; three missed ticks over an otherwise healthy supervision path plus one heartbeat of grace trigger verified holder recovery. The grace keeps external termination behind the holder's 15-second process-local watchdog so its own crash evidence wins despite scheduler jitter.
482
- - A1 heartbeat: the child's Node event loop, seen through the bridge. After 30 s it shows `unresponsive`; after `tabsUnresponsiveRestartSeconds` (default 120) the tab restarts from its session with its journal and last screen preserved. A missing server or bridge transport degrades status, not proof of a child event-loop stall; reacquire liveness evidence before destructive recovery.
483
- - Agent progress: model or tool events. After `tabsStallNoticeSeconds` (default 300) it shows `stalled` with interrupt and restart actions. It is never auto-killed, because a legitimate tool can run for a long time.
505
+ - A1 heartbeat: the child's Node event loop, seen through the bridge. After 30 s the server records `unresponsive` in the registry and counters, without changing the presented status, chip glyph, or colour and without a notice row or restart key; after `tabsUnresponsiveRestartSeconds` (default 120) the tab restarts from its session with its journal and last screen preserved. A missing server or bridge transport degrades status, not proof of a child event-loop stall; reacquire liveness evidence before destructive recovery.
506
+ - Agent progress: not supervised. A working tab without model or tool events keeps its working status indefinitely, gains no transcript notice, and is never auto-killed, because a legitimate tool can run for a long time.
484
507
 
485
508
  **Data-loss envelope.** Distinguish process termination, power loss, and loss of both holder and child:
486
509
 
@@ -589,6 +612,14 @@ The user made these decisions on 2026-09-24:
589
612
  - `Ctrl+C` twice leaves `a1` from any tab while tabs keep running. There is no separate detach key.
590
613
  - Reliability is a first-class, release-gating requirement (Decision 16).
591
614
 
615
+ On 2026-10-24 the user refined the tab bar:
616
+
617
+ - `+` adds an Agent by default; what it offers, Agent, Settings, and Changelog, is configurable from a right-click `Configure` modal over the active tab and from `Tab Bar` › `Plus menu` in Settings, where the `Tabs` section is renamed `Tab Bar`. With nothing enabled, `+` is hidden.
618
+ - `Alt+/` opens a keyboard-only command palette over a dimmed frame: `Agent`, `Settings`, and `Changelog`.
619
+ - The changelog opens at once as its own prewarmed singleton tab that `Esc` does not close, and leaves resident Agents' commands.
620
+ - Opening a tab, a new Agent or a revealed Settings or Changelog tab, draws it at once when its first frame is drawn, with no fade and no starting spinner; the current content, landing included, stays until then. Only a resize fades. The landing's wipe animation is removed.
621
+ - The tab rename selection matches the editor's selection style, and a tab's name and its session's name stay in sync.
622
+
592
623
  On 2026-10-16 the `ci-critical-path` change replaced the Intel runner for the release builds:
593
624
 
594
625
  - The macOS x64 process guardian and terminal host are cross-compiled on the `macos-15` arm64 runner (`npm run build:terminal-host -- --target x86_64-apple-darwin`, same for the guardian) and uploaded beside the arm64 artifacts; the Intel runner took 6 to 11 minutes for builds the arm64 runner finishes in about 4, and it gated packing in every publication. The x64 executable's `--version` identity is read by executing it through Rosetta 2 when the runner can; otherwise the manifest records a Mach-O architecture check as `provenance.identityCheck: "mach-o-header"` instead of a report nobody observed. Intel macOS stays certified separately and its physical acceptance record is unchanged; no publication lane ran the x64 bytes beyond that identity check before either, because the published-pair smoke matrix has no Intel lane.
@@ -16,7 +16,7 @@ The source unit is from MIT-licensed repository <https://github.com/earendil-wor
16
16
 
17
17
  One conversion is active per mounted tool. The waiting set is the newest current source set, not an unbounded queue of superseded snapshots. Hidden images and non-Kitty protocols do not start conversions. Work removed before codec entry is skipped. A public codec call already in progress cannot be cancelled; its eventual result is discarded if the source or mount is obsolete. This limitation is explicit, not claimed as cancellable worker execution.
18
18
 
19
- Pending Kitty conversion has a visible `[Image converting: ...]` marker. Failure, rejection, or oversized converted bytes produces `[Image unavailable: ...]`, without exposing codec diagnostics or changing the operation outcome. Converted pixels never replace the original payload passed to extension renderers. PNG and iTerm presentation use the actual public `Image` component. Hidden and no-image-capability text presentation matches pinned Pi.
19
+ Pending Kitty conversion has a visible `[Image converting: ...]` marker. Failure, rejection, or oversized converted bytes produces `[Image unavailable: ...]`, without exposing codec diagnostics or changing the operation outcome. Converted pixels never replace the original payload passed to extension renderers. PNG and iTerm presentation use the actual public `Image` component. Explicitly hidden images retain pinned textual indicators. When image display is enabled but the terminal advertises no image protocol, bare A1 omits the non-actionable attachment fallback from user and tool transcript content; attachment bytes, metadata, model delivery, the explicit `a1 pi` route, and extension-owned image components remain unchanged.
20
20
 
21
21
  Rebuild/disposal releases image ownership and invalidates the previous mount. Reentrant renderer invalidation is suppressed during synchronous reconstruction; valid asynchronous callbacks still request the existing scheduler and update presentation revision, not semantic revision.
22
22
 
@@ -31,6 +31,7 @@ This correction does not introduce an ANSI parser, change link detection, relax
31
31
  - `transcript-image-conversion.test.ts`: independent pinned stale-cache baseline, JPEG replacement, PNG replacement, hidden/non-Kitty parity, failed-conversion fallback and real scheduled Kitty publication at unchanged semantic revision.
32
32
  - `tool-image-presentation.test.ts`: current-source sharing, one active conversion, superseded bursts, removed/disposed callbacks, hidden/iTerm work avoidance, rejection and size fallback without retry loops.
33
33
  - `tool-shell-parity.test.ts`: independent actual pinned read/bash/edit/write/grep/find/ls and generic rendering at widths 40/80/192, partial/final/error states, expansion, binary text, and extension default/self shells with original inputs and stable renderer state.
34
+ - `shell-components.test.ts`: unsupported user and tool transcript images produce no fallback row or spacer, while explicit hiding and capable-terminal unavailable diagnostics remain visible.
34
35
  - Existing attachment-ownership, payload-limit, content-retention and shell tests remain non-regression gates. The wrapped-URL counterexample remains explicitly unresolved under issue #353.
35
36
 
36
37
  These are automated content and ownership checks, not Windows Terminal visual acceptance. The broader change stays open until scheduled-producer evidence, required CI, and exact-candidate user review satisfy its remaining tasks.