@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.
- package/README.md +185 -35
- package/dist/app/session-shell/exit-notice.d.ts +14 -0
- package/dist/app/session-shell/exit-notice.js +59 -0
- package/dist/app/session-shell/session-shell.js +1 -1
- package/dist/composition/owned-ui.js +10 -0
- package/dist/foundation/launch-guardian/main.js +1 -1
- package/dist/foundation/process-containment/native-guardian-containment.d.ts +2 -1
- package/dist/foundation/process-containment/native-guardian-containment.js +14 -2
- package/dist/foundation/release/update-recovery.d.ts +2 -0
- package/dist/foundation/release/update-recovery.js +15 -2
- package/dist/foundation/release/update.d.ts +4 -1
- package/dist/foundation/release/update.js +20 -2
- package/dist/foundation/startup/startup-descriptor.js +2 -2
- package/dist/integrations/pi/components/resources/builtin-themes.d.ts +2 -2
- package/dist/integrations/pi/components/resources/builtin-themes.js +2 -2
- package/dist/integrations/pi/components/shell-footer-status.js +5 -2
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +1 -1
- package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
- package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/daxnuts.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/daxnuts.js +1 -1
- package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/components/pi-logo.d.ts +4 -0
- package/dist/integrations/pi/components/upstream/components/pi-logo.js +11 -2
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/session-footer.js +1 -1
- package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
- package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +1 -1
- package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/status-indicator.js +1 -1
- package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/tool-execution.js +1 -1
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/editor-core.js +1 -1
- package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
- package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
- package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
- package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
- package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/system-theme.d.ts +3 -2
- package/dist/integrations/pi/components/upstream/theme/system-theme.js +20 -6
- package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme.js +1 -1
- package/dist/integrations/pi/engine/pi-settings-metadata.json +3 -2
- package/dist/integrations/pi/engine/resources/changelog.json +4 -4
- package/dist/integrations/pi/engine/settings-bridge.js +6 -1
- package/dist/integrations/pi/engine/workflows.d.ts +1 -1
- package/dist/integrations/pi/startup-public.js +485 -729
- package/dist/integrations/pi/startup-public.manifest.json +331 -779
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
- package/dist/native/darwin-arm64/manifest.json +3 -3
- package/dist/native/darwin-arm64/process-guardian +0 -0
- package/dist/native/linux-x64/manifest.json +3 -3
- package/dist/native/linux-x64/process-guardian +0 -0
- package/dist/native/win32-x64/manifest.json +3 -3
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/product-identity.d.ts +1 -1
- package/dist/product-identity.js +1 -1
- package/dist/product-identity.json +1 -0
- package/dist/runtime-payload-inventory.json +31471 -35770
- package/docs/architecture/internal-naming.md +1 -1
- package/docs/assets/readme/gears.svg +57 -0
- package/docs/assets/readme/mark.svg +8 -0
- package/docs/assets/readme/plugins.svg +77 -0
- package/docs/assets/readme/rocket.svg +67 -0
- package/docs/assets/readme/terminal.svg +77 -0
- package/docs/assets/readme/waves.svg +143 -0
- package/docs/ci-release-runbook.md +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,81 +1,231 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
77
|
+
Install develop:
|
|
22
78
|
|
|
23
79
|
```sh
|
|
24
|
-
a1 update
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
119
|
+
Remove it (alias: `a1 uninstall`):
|
|
42
120
|
|
|
43
121
|
```sh
|
|
44
|
-
a1
|
|
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
|
|
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
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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: {
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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": "
|
|
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": "
|
|
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.
|
|
3
|
-
* commit
|
|
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.
|
|
3
|
-
* commit
|
|
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} ${
|
|
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.
|
|
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
|