@timurproko/a1 0.2.4-dev.663 → 0.2.4-dev.667

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 (90) hide show
  1. package/README.md +185 -35
  2. package/dist/app/session-shell/exit-notice.d.ts +14 -0
  3. package/dist/app/session-shell/exit-notice.js +59 -0
  4. package/dist/app/session-shell/session-shell.js +1 -1
  5. package/dist/composition/owned-ui.js +10 -0
  6. package/dist/foundation/launch-guardian/main.js +1 -1
  7. package/dist/foundation/process-containment/native-guardian-containment.d.ts +2 -1
  8. package/dist/foundation/process-containment/native-guardian-containment.js +14 -2
  9. package/dist/foundation/release/update-recovery.d.ts +2 -0
  10. package/dist/foundation/release/update-recovery.js +15 -2
  11. package/dist/foundation/release/update.d.ts +4 -1
  12. package/dist/foundation/release/update.js +20 -2
  13. package/dist/foundation/startup/startup-descriptor.js +2 -2
  14. package/dist/integrations/pi/components/resources/builtin-themes.d.ts +2 -2
  15. package/dist/integrations/pi/components/resources/builtin-themes.js +2 -2
  16. package/dist/integrations/pi/components/shell-footer-status.js +5 -2
  17. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
  18. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +1 -1
  19. package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
  20. package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
  21. package/dist/integrations/pi/components/upstream/components/daxnuts.d.ts +1 -1
  22. package/dist/integrations/pi/components/upstream/components/daxnuts.js +1 -1
  23. package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
  24. package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
  25. package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
  26. package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
  27. package/dist/integrations/pi/components/upstream/components/extension-selector.js +1 -1
  28. package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
  29. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
  30. package/dist/integrations/pi/components/upstream/components/owned-editor.js +1 -1
  31. package/dist/integrations/pi/components/upstream/components/pi-logo.d.ts +4 -0
  32. package/dist/integrations/pi/components/upstream/components/pi-logo.js +11 -2
  33. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
  34. package/dist/integrations/pi/components/upstream/components/session-footer.js +1 -1
  35. package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
  36. package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
  37. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +1 -1
  38. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +1 -1
  39. package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +1 -1
  40. package/dist/integrations/pi/components/upstream/components/status-indicator.js +1 -1
  41. package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
  42. package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
  43. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
  44. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +1 -1
  45. package/dist/integrations/pi/components/upstream/components/tool-execution.js +1 -1
  46. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
  47. package/dist/integrations/pi/components/upstream/components/tree-selector.js +1 -1
  48. package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
  49. package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
  50. package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +1 -1
  51. package/dist/integrations/pi/components/upstream/history/editor-core.js +1 -1
  52. package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
  53. package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
  54. package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
  55. package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
  56. package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
  57. package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
  58. package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
  59. package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
  60. package/dist/integrations/pi/components/upstream/theme/system-theme.d.ts +3 -2
  61. package/dist/integrations/pi/components/upstream/theme/system-theme.js +20 -6
  62. package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +1 -1
  63. package/dist/integrations/pi/components/upstream/theme/theme.js +1 -1
  64. package/dist/integrations/pi/engine/pi-settings-metadata.json +3 -2
  65. package/dist/integrations/pi/engine/resources/changelog.json +4 -4
  66. package/dist/integrations/pi/engine/settings-bridge.js +6 -1
  67. package/dist/integrations/pi/engine/workflows.d.ts +1 -1
  68. package/dist/integrations/pi/startup-public.js +485 -729
  69. package/dist/integrations/pi/startup-public.manifest.json +331 -779
  70. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
  71. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
  72. package/dist/native/darwin-arm64/manifest.json +3 -3
  73. package/dist/native/darwin-arm64/process-guardian +0 -0
  74. package/dist/native/linux-x64/manifest.json +3 -3
  75. package/dist/native/linux-x64/process-guardian +0 -0
  76. package/dist/native/win32-x64/manifest.json +3 -3
  77. package/dist/native/win32-x64/process-guardian.exe +0 -0
  78. package/dist/product-identity.d.ts +1 -1
  79. package/dist/product-identity.js +1 -1
  80. package/dist/product-identity.json +1 -0
  81. package/dist/runtime-payload-inventory.json +31471 -35770
  82. package/docs/architecture/internal-naming.md +1 -1
  83. package/docs/assets/readme/gears.svg +57 -0
  84. package/docs/assets/readme/mark.svg +8 -0
  85. package/docs/assets/readme/plugins.svg +77 -0
  86. package/docs/assets/readme/rocket.svg +67 -0
  87. package/docs/assets/readme/terminal.svg +77 -0
  88. package/docs/assets/readme/waves.svg +143 -0
  89. package/docs/ci-release-runbook.md +1 -1
  90. package/package.json +4 -3
package/README.md CHANGED
@@ -1,81 +1,231 @@
1
- # A1
1
+ <p align="center">
2
+ <a href="https://agentnumberone.dev"><img src="docs/assets/readme/mark.svg" alt="a1" width="112"></a>
3
+ </p>
4
+
5
+ <p align="center">
6
+ An open-source AI coding agent for the terminal, built on the pi engine.
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/@timurproko/a1"><img alt="npm" src="https://img.shields.io/npm/v/@timurproko/a1?style=flat-square&color=2638d2"></a>
11
+ <a href="LICENSE"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-2638d2?style=flat-square"></a>
12
+ <a href="https://agentnumberone.dev"><img alt="Website" src="https://img.shields.io/badge/web-agentnumberone.dev-2638d2?style=flat-square"></a>
13
+ </p>
2
14
 
3
15
  ## Install
4
16
 
17
+ Release:
18
+
19
+ ```sh
20
+ npm x -y -- @timurproko/a1-install
21
+ ```
22
+
23
+ Develop:
24
+
5
25
  ```sh
6
- npm x -y -- @timurproko/a1-install # release
7
- npm x -y -- @timurproko/a1-install --develop # develop
8
- npm x -y -- @timurproko/a1-install --develop 107 # preview number
9
- npm x -y -- @timurproko/a1-install --develop 0.1.8-dev.107 # exact preview
26
+ npm x -y -- @timurproko/a1-install --develop
10
27
  ```
11
28
 
29
+ Preview number:
30
+
31
+ ```sh
32
+ npm x -y -- @timurproko/a1-install --develop 107
33
+ ```
34
+
35
+ Exact preview:
36
+
37
+ ```sh
38
+ npm x -y -- @timurproko/a1-install --develop 0.1.8-dev.107
39
+ ```
40
+
41
+ <p align="center"><picture><img src="docs/assets/readme/terminal.svg" alt="" width="240"></picture></p>
42
+
12
43
  ## Use
13
44
 
45
+ Launch A1 (profile: `~/.a1/agent`):
46
+
47
+ ```sh
48
+ a1
49
+ ```
50
+
51
+ Show all commands:
52
+
53
+ ```sh
54
+ a1 help
55
+ ```
56
+
57
+ Show the current and latest versions:
58
+
59
+ ```sh
60
+ a1 version
61
+ ```
62
+
63
+ Vanilla Pi oracle (`~/.pi/agent`):
64
+
65
+ ```sh
66
+ a1 pi
67
+ ```
68
+
69
+ ### Update
70
+
71
+ Install the release:
72
+
14
73
  ```sh
15
- a1 # launch A1 (profile: ~/.a1/agent)
16
- a1 help # show all commands
17
- a1 version # show the current and latest versions
18
- a1 pi # vanilla Pi oracle: ~/.pi/agent
74
+ a1 update
19
75
  ```
20
76
 
21
- Update:
77
+ Install develop:
22
78
 
23
79
  ```sh
24
- a1 update # install the release
25
- a1 update --develop # install develop
26
- a1 update --develop 107 # install preview 107
27
- a1 update --develop 0.1.8-dev.107 # install that exact preview
28
- a1 update --models # refresh A1's model catalogs
80
+ a1 update --develop
29
81
  ```
30
82
 
31
- On interactive startup A1 checks, at most once a day, whether a newer release exists on its own channel and shows an `Update Available` notice with the command to run. It never installs anything. Turn it off with the `Update check` setting (Generic section), or set `A1_SKIP_VERSION_CHECK=1`; it is also skipped when `PI_OFFLINE` or `CI` is set, or when output is not a terminal. The check reads the public npm registry, or `npm_config_registry` when set.
83
+ Install preview 107:
84
+
85
+ ```sh
86
+ a1 update --develop 107
87
+ ```
88
+
89
+ Install that exact preview:
90
+
91
+ ```sh
92
+ a1 update --develop 0.1.8-dev.107
93
+ ```
94
+
95
+ Refresh A1's model catalogs:
96
+
97
+ ```sh
98
+ a1 update --models
99
+ ```
100
+
101
+ Stuck on `0.2.2` and `a1 update` fails? Run the installer once, then use `a1 update` as usual:
102
+
103
+ ```sh
104
+ npm x -y -- @timurproko/a1-install
105
+ ```
106
+
107
+ A1 shows an `Update Available` notice at most once a day and never installs anything by itself. Turn it off in Settings (`Update check`) or with `A1_SKIP_VERSION_CHECK=1`.
108
+
109
+ <p align="center"><picture><img src="docs/assets/readme/plugins.svg" alt="" width="240"></picture></p>
32
110
 
33
111
  ## Extensions
34
112
 
113
+ Install a package:
114
+
35
115
  ```sh
36
- a1 install npm:pi-mcp-adapter # install a package
37
- a1 remove npm:pi-mcp-adapter # remove it (alias: a1 uninstall)
38
- a1 list # list installed packages
116
+ a1 install npm:pi-mcp-adapter
39
117
  ```
40
118
 
41
- Update:
119
+ Remove it (alias: `a1 uninstall`):
42
120
 
43
121
  ```sh
44
- a1 update --extensions # update every installed package
45
- a1 update npm:pi-mcp-adapter # update one
122
+ a1 remove npm:pi-mcp-adapter
46
123
  ```
47
124
 
125
+ List installed packages:
126
+
127
+ ```sh
128
+ a1 list
129
+ ```
130
+
131
+ Update every installed package:
132
+
133
+ ```sh
134
+ a1 update --extensions
135
+ ```
136
+
137
+ Update one:
138
+
139
+ ```sh
140
+ a1 update npm:pi-mcp-adapter
141
+ ```
142
+
143
+ <p align="center"><picture><img src="docs/assets/readme/gears.svg" alt="" width="240"></picture></p>
144
+
48
145
  ## Develop
49
146
 
147
+ Install exact locked dependencies:
148
+
50
149
  ```sh
51
- npm ci # install exact locked dependencies
52
- npm run build # compile TypeScript and the process guardian into dist
53
- npm start # build and launch a source `a1`
54
- npm run start:pi # build and launch a source `a1 pi`
55
- npm run test:fast # typecheck + fast suite (alias: npm test)
56
- npm run test:full # complete non-physical suite
57
- npm run doctor # report Node, npm, git, Rust, and dependency readiness
150
+ npm ci
58
151
  ```
59
152
 
153
+ Compile TypeScript and the process guardian into `dist`:
154
+
155
+ ```sh
156
+ npm run build
157
+ ```
158
+
159
+ Build and launch a source `a1`:
160
+
161
+ ```sh
162
+ npm start
163
+ ```
164
+
165
+ Build and launch a source `a1 pi`:
166
+
167
+ ```sh
168
+ npm run start:pi
169
+ ```
170
+
171
+ Typecheck + fast suite (alias: `npm test`):
172
+
173
+ ```sh
174
+ npm run test:fast
175
+ ```
176
+
177
+ Complete non-physical suite:
178
+
179
+ ```sh
180
+ npm run test:full
181
+ ```
182
+
183
+ Report Node, npm, git, Rust, and dependency readiness:
184
+
185
+ ```sh
186
+ npm run doctor
187
+ ```
188
+
189
+ Preview the README as GitHub renders it, in your browser (needs `gh`):
190
+
191
+ ```sh
192
+ npm run preview:readme
193
+ ```
194
+
195
+ <p align="center"><picture><img src="docs/assets/readme/rocket.svg" alt="" width="240"></picture></p>
196
+
60
197
  ## Publish
61
198
 
62
199
  ### Develop
63
200
 
201
+ Request the preview publish:
202
+
64
203
  ```sh
65
- npm run develop # request the preview publish
204
+ npm run develop
66
205
  ```
67
206
 
68
207
  ### Release
69
208
 
70
209
  ```sh
71
210
  npm run release -- patch # 0.1.8-dev -> 0.1.8
211
+ ```
212
+
213
+ ```sh
72
214
  npm run release -- minor # 0.1.8-dev -> 0.2.0
215
+ ```
216
+
217
+ ```sh
73
218
  npm run release -- major # 0.1.8-dev -> 1.0.0
219
+ ```
220
+
221
+ ```sh
74
222
  npm run release -- 0.4.0 # prepare an exact stable version
75
223
  ```
76
224
 
77
- The command creates the draft, starts validation of its source, prints the progress
78
- link, and waits. When validation passes it prints the draft link: edit the Pi-style
79
- `## [version] - YYYY-MM-DD` changelog, then choose
80
- GitHub's native **Publish release** button: that publishes both packages to npm. If
81
- publication fails before npm, the Release returns to draft so you can publish it again.
225
+ Once validation passes, open the printed draft link, edit the `## [version] - YYYY-MM-DD` changelog, and press **Publish release** to publish to npm. Details: [release runbook](docs/ci-release-runbook.md).
226
+
227
+ <br>
228
+
229
+ <p align="center">
230
+ <picture><img src="docs/assets/readme/waves.svg" alt="" width="100%"></picture>
231
+ </p>
@@ -0,0 +1,14 @@
1
+ import { type SessionResumeCommandMetadata } from "./session-shell.js";
2
+ export interface ExitNoticeSource {
3
+ readonly sessionGeneration: number;
4
+ currentSessionResumeMetadata(): SessionResumeCommandMetadata | null;
5
+ onEvent(listener: (event: {
6
+ readonly type: string;
7
+ }) => void): () => void;
8
+ }
9
+ export interface ArmedExitNotice {
10
+ clear(): void;
11
+ }
12
+ export declare function uncleanExitNoticeLines(resume: SessionResumeCommandMetadata | null): string[];
13
+ /** Arms the notice and keeps its resume command following the bound session until cleared. */
14
+ export declare function armExitNotice(source: ExitNoticeSource, path: string): ArmedExitNotice;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The runtime's half of the unclean-exit protocol with the native process guardian. While the
3
+ * notice is armed, the guardian treats the runtime's end as unclean: it restores the terminal and
4
+ * prints these lines, so a killed session still leaves its resume command behind. A clean shutdown
5
+ * clears the notice after the terminal is restored.
6
+ */
7
+ import { renameSync, rmSync, writeFileSync } from "node:fs";
8
+ import { PRODUCT_IDENTITY } from "../../product-identity.js";
9
+ import { formatSessionResumeCommand } from "./session-shell.js";
10
+ export function uncleanExitNoticeLines(resume) {
11
+ return [
12
+ `${PRODUCT_IDENTITY.displayName} was stopped unexpectedly.`,
13
+ ...(resume === null ? [] : [`To resume this session: ${formatSessionResumeCommand(resume)}`]),
14
+ ];
15
+ }
16
+ /** Arms the notice and keeps its resume command following the bound session until cleared. */
17
+ export function armExitNotice(source, path) {
18
+ let current = null;
19
+ let cleared = false;
20
+ let generation = source.sessionGeneration;
21
+ const write = () => {
22
+ if (cleared)
23
+ return;
24
+ // Protocol: plain text only; the guardian strips control characters, so styling would be lost.
25
+ const text = `${uncleanExitNoticeLines(source.currentSessionResumeMetadata()).join("\n")}\n`;
26
+ if (text === current)
27
+ return;
28
+ try {
29
+ // Invariant: the guardian may read at any instant, so it sees the previous or the next notice, never half of one.
30
+ const temporary = `${path}.${process.pid}.tmp`;
31
+ writeFileSync(temporary, text, { mode: 0o600 });
32
+ renameSync(temporary, path);
33
+ current = text;
34
+ }
35
+ catch { }
36
+ };
37
+ write();
38
+ const unsubscribe = source.onEvent(event => {
39
+ // Rationale: a session is persisted by its first response and replaced by /new or /resume;
40
+ // these events cover both without touching the per-chunk stream.
41
+ const replaced = source.sessionGeneration !== generation;
42
+ if (!replaced && event.type !== "session-lifecycle" && event.type !== "assistant-message-completed")
43
+ return;
44
+ generation = source.sessionGeneration;
45
+ write();
46
+ });
47
+ return {
48
+ clear() {
49
+ if (cleared)
50
+ return;
51
+ cleared = true;
52
+ unsubscribe();
53
+ try {
54
+ rmSync(path, { force: true });
55
+ }
56
+ catch { }
57
+ },
58
+ };
59
+ }
@@ -1094,7 +1094,7 @@ export class OwnedUiSessionShell {
1094
1094
  showLoginProviderSelector(authType, initialSearchInput) {
1095
1095
  const options = this.backend.pinnedLoginOptions(authType);
1096
1096
  if (options.length === 0) {
1097
- this.root.appendWorkflowStatus(authType === "oauth" ? "No subscription providers available." : authType === "api_key" ? "No API key providers available." : "No login providers available.");
1097
+ this.root.appendWorkflowStatus(authType === "oauth" ? "No account providers available." : authType === "api_key" ? "No API key providers available." : "No login providers available.");
1098
1098
  this.runtime.requestRender();
1099
1099
  return;
1100
1100
  }
@@ -184,9 +184,17 @@ export async function composeOwnedUi(options = {}) {
184
184
  if (release !== null)
185
185
  adapter.announceReleaseUpdate(release);
186
186
  };
187
+ const exitNoticePath = process.env[PRODUCT_IDENTITY.environment.exitNoticePath];
188
+ // Security: tools the agent runs must never see, or rewrite, this instance's notice.
189
+ delete process.env[PRODUCT_IDENTITY.environment.exitNoticePath];
190
+ let exitNotice = Promise.resolve(null);
187
191
  const application = {
188
192
  get disposed() { return adapter.disposed; },
189
193
  start: () => {
194
+ // Performance: the guardian notice loads beside first paint, never on the startup path.
195
+ if (exitNoticePath)
196
+ exitNotice = import("../app/session-shell/exit-notice.js")
197
+ .then(module => module.armExitNotice(adapter, exitNoticePath), () => null);
190
198
  shell.start();
191
199
  void announceNewerRelease().catch(() => undefined);
192
200
  },
@@ -195,6 +203,8 @@ export async function composeOwnedUi(options = {}) {
195
203
  dispose: async () => {
196
204
  try {
197
205
  await shell.dispose();
206
+ // Invariant: cleared only once the terminal is restored; a failed dispose leaves it armed.
207
+ (await exitNotice)?.clear();
198
208
  await releaseNoteAcknowledgement?.catch(() => undefined);
199
209
  }
200
210
  finally {
@@ -30,7 +30,7 @@ export async function runLaunchGuardian(options) {
30
30
  : paths.endpoint;
31
31
  await client.connect(endpoint);
32
32
  await markStartupPhase(environment, "guardian-connected");
33
- const containment = options.containment ?? new NativeGuardianContainment(instanceId, helperPath, resolve(paths.runtimeDir, "launch-instances", `${instanceId}.ready.json`));
33
+ const containment = options.containment ?? new NativeGuardianContainment(instanceId, helperPath, resolve(paths.runtimeDir, "launch-instances", `${instanceId}.ready.json`), resolve(paths.runtimeDir, "launch-instances", `${instanceId}.exit-notice`));
34
34
  const stop = stopRequests(client, instanceId);
35
35
  let rootIdentity = null;
36
36
  try {
@@ -6,7 +6,8 @@ export declare class NativeGuardianContainment implements ProcessContainment {
6
6
  readonly instanceId: string;
7
7
  readonly helperPath: string;
8
8
  readonly statusPath: string;
9
- constructor(instanceId: string, helperPath: string, statusPath: string);
9
+ readonly exitNoticePath: string | null;
10
+ constructor(instanceId: string, helperPath: string, statusPath: string, exitNoticePath?: string | null);
10
11
  get identity(): ProcessContainmentIdentity;
11
12
  spawn(executable: string, arguments_: readonly string[], options: ContainedSpawnOptions): Promise<ContainedProcessHandle>;
12
13
  contains(identity: NativeProcessIdentity): Promise<boolean>;
@@ -11,10 +11,12 @@ export class NativeGuardianContainment {
11
11
  instanceId;
12
12
  helperPath;
13
13
  statusPath;
14
- constructor(instanceId, helperPath, statusPath) {
14
+ exitNoticePath;
15
+ constructor(instanceId, helperPath, statusPath, exitNoticePath = null) {
15
16
  this.instanceId = instanceId;
16
17
  this.helperPath = helperPath;
17
18
  this.statusPath = statusPath;
19
+ this.exitNoticePath = exitNoticePath;
18
20
  this.#identity = { provider: "native-guardian-pending", token: instanceId };
19
21
  }
20
22
  get identity() {
@@ -25,14 +27,22 @@ export class NativeGuardianContainment {
25
27
  throw new Error("process containment already has a root runtime");
26
28
  await mkdir(dirname(this.statusPath), { recursive: true, mode: 0o700 });
27
29
  await rm(this.statusPath, { force: true });
30
+ if (this.exitNoticePath)
31
+ await rm(this.exitNoticePath, { force: true });
28
32
  const helper = spawn(this.helperPath, [
29
33
  "--parent-pid", String(process.pid),
30
34
  "--instance", this.instanceId,
31
35
  "--status-file", this.statusPath,
36
+ ...(this.exitNoticePath ? ["--exit-notice", this.exitNoticePath] : []),
32
37
  "--", executable, ...arguments_,
33
38
  ], {
34
39
  cwd: options.cwd,
35
- env: { ...options.environment, ...(options.terminalType ? { TERM: options.terminalType } : {}) },
40
+ env: {
41
+ ...options.environment,
42
+ ...(options.terminalType ? { TERM: options.terminalType } : {}),
43
+ // Protocol: the runtime arms this notice and the guardian prints it if the runtime dies unclean.
44
+ ...(this.exitNoticePath ? { [PRODUCT_IDENTITY.environment.exitNoticePath]: this.exitNoticePath } : {}),
45
+ },
36
46
  shell: false,
37
47
  stdio: "inherit",
38
48
  windowsHide: false,
@@ -79,6 +89,8 @@ export class NativeGuardianContainment {
79
89
  helper.kill("SIGKILL");
80
90
  await this.waitForEmpty(1_500);
81
91
  await rm(this.statusPath, { force: true });
92
+ if (this.exitNoticePath)
93
+ await rm(this.exitNoticePath, { force: true });
82
94
  }
83
95
  }
84
96
  export function resolveProcessGuardianPath(releaseRoot, environment = process.env, platform = process.platform, architecture = process.arch) {
@@ -41,6 +41,8 @@ export interface ProtectedPackageReplacementOptions {
41
41
  readonly globalRoot: string;
42
42
  /** Root used only to locate the active npm implementation; installation remains pinned to globalRoot. */
43
43
  readonly npmCliRoot?: string;
44
+ /** Canonical entry established before update lifecycle mutation and revalidated when the capsule is committed. */
45
+ readonly npmCli?: string;
44
46
  readonly packageRoot: string;
45
47
  readonly transaction: UpdateTransaction;
46
48
  readonly priorRelease: {
@@ -119,7 +119,9 @@ export async function prepareUpdateRecoveryCapsule(options) {
119
119
  const prefix = npmPrefixForGlobalRoot(canonicalGlobal, platform);
120
120
  const launcherRoot = platform === "win32" ? prefix : resolve(prefix, "bin");
121
121
  const npmCliRoot = await realpath(options.npmCliRoot ?? canonicalGlobal);
122
- const npmCli = await resolveNpmCli(npmCliRoot, options.environment ?? process.env, platform);
122
+ const npmCli = options.npmCli === undefined
123
+ ? await resolveNpmCli(npmCliRoot, options.environment ?? process.env, platform)
124
+ : await requireCanonicalNpmCli(options.npmCli);
123
125
  const capsule = {
124
126
  schema: UPDATE_RECOVERY_SCHEMA,
125
127
  launchContract: PRIVATE_LAUNCH_CONTRACT,
@@ -300,7 +302,10 @@ async function spawnRecoveryWorker(entry, manifestPath, environment) {
300
302
  child.unref();
301
303
  }
302
304
  export async function resolveNpmCli(globalRoot, environment, platform = process.platform, nodeExecutable = process.execPath) {
303
- const candidates = [environment.npm_execpath, resolve(globalRoot, "npm", "bin", "npm-cli.js")]
305
+ const declaredNpmEntry = platform === "win32"
306
+ ? windowsEnvironmentValue(environment, "npm_execpath")
307
+ : environment.npm_execpath;
308
+ const candidates = [declaredNpmEntry, resolve(globalRoot, "npm", "bin", "npm-cli.js")]
304
309
  .filter((value) => typeof value === "string" && value.length > 0);
305
310
  for (const candidate of candidates) {
306
311
  const canonical = await canonicalRegularFile(candidate);
@@ -342,12 +347,20 @@ async function windowsNpmCliFromPath(environment) {
342
347
  return null;
343
348
  }
344
349
  function windowsEnvironmentValue(environment, name) {
350
+ if (Object.hasOwn(environment, name))
351
+ return environment[name];
345
352
  const key = Object.keys(environment).find(candidate => candidate.toLowerCase() === name.toLowerCase());
346
353
  return key ? environment[key] : undefined;
347
354
  }
348
355
  function unquoteWindowsPathEntry(value) {
349
356
  return value.length >= 2 && value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
350
357
  }
358
+ async function requireCanonicalNpmCli(path) {
359
+ const canonical = await canonicalRegularFile(path);
360
+ if (!canonical)
361
+ throw new Error("preflight npm JavaScript entry is no longer a regular file");
362
+ return canonical;
363
+ }
351
364
  async function canonicalRegularFile(path) {
352
365
  const canonical = await realpath(path).catch(() => null);
353
366
  if (!canonical)
@@ -1,7 +1,7 @@
1
1
  import type { UpdateChannel } from "./types.js";
2
2
  import { type UpdateActivationPhase, type UpdateMaterializationProgress } from "./update-activation.js";
3
3
  import { type UpdateRecoveryState, type UpdateTransaction, type UpdateTransactionPhase } from "./update-transaction.js";
4
- import { type ProtectedPackageReplacementResult } from "./update-recovery.js";
4
+ import { resolveNpmCli, type ProtectedPackageReplacementResult } from "./update-recovery.js";
5
5
  export declare const PRODUCT_PACKAGE: string;
6
6
  export type { UpdateChannel } from "./types.js";
7
7
  export interface ProcessRequest {
@@ -69,6 +69,8 @@ export interface SelfUpdateOptions {
69
69
  transactionStore?: UpdateTransactionJournal;
70
70
  /** Test seam for protected global package replacement. */
71
71
  packageReplacement?: (input: UpdatePackageReplacementInput) => Promise<ProtectedPackageReplacementResult>;
72
+ /** Test seam for proving replacement-authority preflight ordering. */
73
+ npmCliResolver?: typeof resolveNpmCli;
72
74
  /** Test or embedding seam for post-activation release maintenance. */
73
75
  maintenance?: () => Promise<void>;
74
76
  progress?: boolean;
@@ -102,6 +104,7 @@ export interface UpdatePackageReplacementInput {
102
104
  readonly dataDir: string;
103
105
  readonly globalRoot: string;
104
106
  readonly npmCliRoot: string;
107
+ readonly npmCli?: string;
105
108
  readonly packageRoot: string;
106
109
  readonly transaction: UpdateTransaction;
107
110
  readonly priorRelease: {
@@ -16,7 +16,7 @@ import { materializeRelease, readMaterializedRelease } from "./release-store.js"
16
16
  import { scheduleReleaseCleanup } from "./release-gc.js";
17
17
  import { UPDATE_ACTIVATION_CONTRACT, activateInstalledRelease, delegateActivation, readActivationContracts, } from "./update-activation.js";
18
18
  import { UpdateTransactionStore } from "./update-transaction.js";
19
- import { npmPrefixForGlobalRoot, removeUpdateRecoveryCapsule, runProtectedPackageReplacement, updateLauncherPaths, updateNpmInstallArguments, } from "./update-recovery.js";
19
+ import { npmPrefixForGlobalRoot, removeUpdateRecoveryCapsule, resolveNpmCli, runProtectedPackageReplacement, updateLauncherPaths, updateNpmInstallArguments, } from "./update-recovery.js";
20
20
  export const PRODUCT_PACKAGE = PRODUCT_TEXT.packageName;
21
21
  const UPDATE_DIST_TAGS = { stable: "latest", next: "next" };
22
22
  const defaultFileSystem = {
@@ -520,6 +520,7 @@ export async function runSelfUpdate(options) {
520
520
  ? async () => { }
521
521
  : async () => await scheduleReleaseCleanup(paths.dataDir, paths));
522
522
  let transaction = await transactionStore.read();
523
+ let npmCli;
523
524
  try {
524
525
  if ((!transaction || transaction.status === "completed") && await lifecycle.targetIsActive(targetVersion)) {
525
526
  await maintenance();
@@ -527,7 +528,17 @@ export async function runSelfUpdate(options) {
527
528
  output.stdout(`${PRODUCT_TEXT.commandName} is up to date — no update needed.\n`);
528
529
  return 0;
529
530
  }
530
- // Rationale: the bar first appears here so a no-change run never flashes it.
531
+ const preflightRequired = options.npmCliResolver !== undefined
532
+ || options.packageReplacement === undefined && options.runner === undefined;
533
+ if (preflightRequired) {
534
+ try {
535
+ npmCli = await (options.npmCliResolver ?? resolveNpmCli)(npmCliRoot, environment);
536
+ }
537
+ catch (error) {
538
+ throw new UpdatePreflightFailure(errorMessage(error));
539
+ }
540
+ }
541
+ // Rationale: the bar first appears only after every non-mutating replacement prerequisite passes.
531
542
  progress.set(3, 15);
532
543
  const cohortStore = new CohortStateStore(paths.dataDir);
533
544
  let cohortState = await cohortStore.read();
@@ -573,6 +584,7 @@ export async function runSelfUpdate(options) {
573
584
  dataDir: paths.dataDir,
574
585
  globalRoot,
575
586
  npmCliRoot,
587
+ ...(npmCli === undefined ? {} : { npmCli }),
576
588
  packageRoot,
577
589
  transaction: replacementTransaction,
578
590
  priorRelease,
@@ -655,6 +667,10 @@ export async function runSelfUpdate(options) {
655
667
  catch (error) {
656
668
  progress.clear();
657
669
  const message = errorMessage(error);
670
+ if (error instanceof UpdatePreflightFailure) {
671
+ output.stderr(`${PRODUCT_TEXT.diagnostic(`update could not start safely: ${message}. Nothing was changed. Retry with npm x -y -- ${PRODUCT_PACKAGE}-install`)}\n`);
672
+ return 1;
673
+ }
658
674
  const rollback = options.lifecycle
659
675
  ? "previous test lifecycle retained"
660
676
  : await rollbackPriorCohort(paths.dataDir, environment, transaction?.priorActiveReleaseId ?? null).catch(rollbackError => `rollback failed: ${errorMessage(rollbackError)}`);
@@ -760,6 +776,8 @@ class UpdateFailure extends Error {
760
776
  exitCode;
761
777
  constructor(exitCode, message) { super(message); this.exitCode = exitCode; }
762
778
  }
779
+ class UpdatePreflightFailure extends Error {
780
+ }
763
781
  async function canonicalImmutableRoot(dataDir, releaseRoot) {
764
782
  try {
765
783
  const [store, selected] = await Promise.all([realpath(resolve(dataDir, "releases")), realpath(releaseRoot)]);
@@ -10,7 +10,7 @@ export const STARTUP_DESCRIPTOR = Object.freeze({
10
10
  ],
11
11
  "generatedArtifact": {
12
12
  "path": "dist/integrations/pi/startup-public.js",
13
- "sha256": "10be6744beda393db760b38cb367f047881b6c5e72819c2b0f07d196088e7813"
13
+ "sha256": "cb2331c550009704bce2614f2f7fb6e1b45ed00403be804da3b83c1ea90a17c4"
14
14
  },
15
15
  "dependencyLayerIdentity": {
16
16
  "source": "A1_RELEASE_LAYERS",
@@ -19,7 +19,7 @@ export const STARTUP_DESCRIPTOR = Object.freeze({
19
19
  "compileCacheNamespace": {
20
20
  "strategy": "node-abi-version-and-ordered-layer-sha256-v1"
21
21
  },
22
- "identity": "14172049798e27f0442cad5289bbabb876c9c9490fd750b77af0ce0f8bd5adbb"
22
+ "identity": "54cea64be46c7a0d36ab6a1889206f79813dba6b9884387b391440fe30a1fdee"
23
23
  });
24
24
 
25
25
  export function loadDeclaredStartupGraph() {
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Attributed owned theme resources adapted from @earendil-works/pi-coding-agent 0.99.2 (MIT),
3
- * commit 005af57d88ee23b33778f343a9595b32e67ff788.
2
+ * Attributed owned theme resources adapted from @earendil-works/pi-coding-agent 1.0.0 (MIT),
3
+ * commit a13d35a742c6ef8462812a28fbe1d8c8b7431c32.
4
4
  * Upstream dark.json SHA-256: c11a588b714d35300293079b425fb09a3693d4b2d453585d211b08163c648b75
5
5
  * Upstream light.json SHA-256: f590c51c2bc8b238891efd0e983473ed2e2a6487b6692cc1d4b2845165e703f7
6
6
  * Modifications: removed upstream schema/export metadata; runtime color data is unchanged.
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Attributed owned theme resources adapted from @earendil-works/pi-coding-agent 0.99.2 (MIT),
3
- * commit 005af57d88ee23b33778f343a9595b32e67ff788.
2
+ * Attributed owned theme resources adapted from @earendil-works/pi-coding-agent 1.0.0 (MIT),
3
+ * commit a13d35a742c6ef8462812a28fbe1d8c8b7431c32.
4
4
  * Upstream dark.json SHA-256: c11a588b714d35300293079b425fb09a3693d4b2d453585d211b08163c648b75
5
5
  * Upstream light.json SHA-256: f590c51c2bc8b238891efd0e983473ed2e2a6487b6692cc1d4b2845165e703f7
6
6
  * Modifications: removed upstream schema/export metadata; runtime color data is unchanged.
@@ -1,7 +1,7 @@
1
1
  import { rawKeyHint, VERSION } from "../startup-public.js";
2
2
  import { Spacer, Text, } from "@earendil-works/pi-tui";
3
3
  import { SessionFooter } from "./upstream/components/session-footer.js";
4
- import { piLogoLines } from "./upstream/components/pi-logo.js";
4
+ import { piLogoLines, piWordmark, supportsPiLogo, } from "./upstream/components/pi-logo.js";
5
5
  import { ThemedText } from "./upstream/components/themed-text.js";
6
6
  import { KeybindingsManager } from "./upstream/adjacent/core/keybindings.js";
7
7
  import { StatusIndicator, WorkingStatusIndicator } from "./upstream/components/status-indicator.js";
@@ -300,8 +300,11 @@ function expandedHeaderText(bindings) {
300
300
  return `${headerWithLogo(instructions)}\n\n${onboarding}`;
301
301
  }
302
302
  function headerWithLogo(hints) {
303
+ const version = piTheme().fg("dim", `v${VERSION}`);
304
+ if (!supportsPiLogo())
305
+ return `${piWordmark()} ${version}\n${hints}`;
303
306
  const [top, bottom] = piLogoLines();
304
- return `${top} ${piTheme().fg("dim", `v${VERSION}`)}\n${bottom} ${hints}`;
307
+ return `${top} ${version}\n${bottom} ${hints}`;
305
308
  }
306
309
  function noticeText(notice) {
307
310
  const theme = piTheme();
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Provenance: @earendil-works/pi-coding-agent 0.99.2 (MIT), commit 005af57d88ee23b33778f343a9595b32e67ff788,
2
+ * Provenance: @earendil-works/pi-coding-agent 1.0.0 (MIT), commit a13d35a742c6ef8462812a28fbe1d8c8b7431c32,
3
3
  * packages/coding-agent/src/core/keybindings.ts.
4
4
  * Modifications: Mechanical source port with Node import prefixes, public package-root agent-directory
5
5
  * resolution, and an opt-in bare-A1 input profile including Ctrl+L level cycling, unbound model