@timurproko/a1 0.1.1-dev.1 → 0.1.1-dev.11
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 +8 -43
- package/bin/guardian.js +22 -0
- package/bin/{a1-ui.js → ui.js} +4 -8
- package/dist/native/darwin-arm64/manifest.json +20 -0
- package/dist/native/darwin-arm64/process-guardian +0 -0
- package/dist/native/linux-x64/manifest.json +20 -0
- package/dist/native/linux-x64/process-guardian +0 -0
- package/dist/native/win32-x64/manifest.json +20 -0
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/src/composition/index.d.ts +26 -0
- package/dist/src/composition/index.js +94 -2
- package/dist/src/features/launch/initialize-profile.js +23 -2
- package/dist/src/features/launch/profiles.d.ts +1 -1
- package/dist/src/features/launch/profiles.js +2 -2
- package/dist/src/features/launch/runtime-selection.d.ts +13 -8
- package/dist/src/features/launch/runtime-selection.js +6 -6
- package/dist/src/features/owned-ui/index.d.ts +1 -0
- package/dist/src/features/owned-ui/index.js +1 -0
- package/dist/src/features/owned-ui/run.d.ts +6 -0
- package/dist/src/features/owned-ui/run.js +3 -1
- package/dist/src/features/owned-ui/settings-app.d.ts +22 -0
- package/dist/src/features/owned-ui/settings-app.js +633 -0
- package/dist/src/foundation/agent-engine-contracts/domain.d.ts +26 -0
- package/dist/src/foundation/launch-guardian/index.d.ts +1 -0
- package/dist/src/foundation/launch-guardian/index.js +1 -0
- package/dist/src/foundation/launch-guardian/main.d.ts +31 -0
- package/dist/src/foundation/launch-guardian/main.js +165 -0
- package/dist/src/foundation/lifecycle/commands.d.ts +40 -0
- package/dist/src/foundation/lifecycle/commands.js +1 -0
- package/dist/src/foundation/lifecycle/index.d.ts +2 -0
- package/dist/src/foundation/lifecycle/index.js +2 -0
- package/dist/src/foundation/lifecycle/launch-instance.d.ts +46 -0
- package/dist/src/foundation/lifecycle/launch-instance.js +134 -0
- package/dist/src/foundation/lifecycle/model.d.ts +6 -42
- package/dist/src/foundation/owned-ui-settings/declarations.d.ts +13 -0
- package/dist/src/foundation/owned-ui-settings/declarations.js +39 -0
- package/dist/src/foundation/owned-ui-settings/index.d.ts +6 -0
- package/dist/src/foundation/owned-ui-settings/index.js +6 -0
- package/dist/src/foundation/owned-ui-settings/migrations.d.ts +13 -0
- package/dist/src/foundation/owned-ui-settings/migrations.js +28 -0
- package/dist/src/foundation/owned-ui-settings/resolution.d.ts +45 -0
- package/dist/src/foundation/owned-ui-settings/resolution.js +147 -0
- package/dist/src/foundation/owned-ui-settings/sections.d.ts +70 -0
- package/dist/src/foundation/owned-ui-settings/sections.js +93 -0
- package/dist/src/foundation/owned-ui-settings/session.d.ts +44 -0
- package/dist/src/foundation/owned-ui-settings/session.js +145 -0
- package/dist/src/foundation/owned-ui-settings/store.d.ts +28 -0
- package/dist/src/foundation/owned-ui-settings/store.js +90 -0
- package/dist/src/foundation/pi-component-adapter/index.d.ts +1 -0
- package/dist/src/foundation/pi-component-adapter/index.js +1 -0
- package/dist/src/foundation/pi-component-adapter/shell-selectors-dialogs.d.ts +9 -1
- package/dist/src/foundation/pi-component-adapter/shell-selectors-dialogs.js +10 -8
- package/dist/src/foundation/pi-component-adapter/upstream/theme/theme.d.ts +8 -0
- package/dist/src/foundation/pi-component-adapter/upstream/theme/theme.js +12 -0
- package/dist/src/foundation/pi-engine-adapter/adapter.d.ts +17 -3
- package/dist/src/foundation/pi-engine-adapter/adapter.js +80 -7
- package/dist/src/foundation/pi-engine-adapter/pi-settings-metadata.json +374 -0
- package/dist/src/foundation/pi-engine-adapter/public-main-entry.js +10 -1
- package/dist/src/foundation/pi-engine-adapter/settings-integration.d.ts +40 -2
- package/dist/src/foundation/pi-engine-adapter/settings-integration.js +172 -24
- package/dist/src/foundation/pi-engine-adapter/workflows.d.ts +9 -0
- package/dist/src/foundation/pi-owned-ui-integration/index.d.ts +1 -0
- package/dist/src/foundation/pi-owned-ui-integration/index.js +1 -0
- package/dist/src/foundation/pi-owned-ui-integration/route-host.d.ts +29 -0
- package/dist/src/foundation/pi-owned-ui-integration/route-host.js +1 -0
- package/dist/src/foundation/pi-owned-ui-integration/session-shell.d.ts +6 -0
- package/dist/src/foundation/pi-owned-ui-integration/session-shell.js +71 -1
- package/dist/src/foundation/pi-tui-runtime-adapter/adapter.d.ts +6 -0
- package/dist/src/foundation/pi-tui-runtime-adapter/adapter.js +9 -0
- package/dist/src/foundation/process-containment/artifact.d.ts +1 -0
- package/dist/src/foundation/process-containment/artifact.js +34 -0
- package/dist/src/foundation/process-containment/contracts.d.ts +31 -0
- package/dist/src/foundation/process-containment/contracts.js +35 -0
- package/dist/src/foundation/process-containment/index.d.ts +5 -0
- package/dist/src/foundation/process-containment/index.js +5 -0
- package/dist/src/foundation/process-containment/linux-process-inspector.d.ts +6 -0
- package/dist/src/foundation/process-containment/linux-process-inspector.js +26 -0
- package/dist/src/foundation/process-containment/native-guardian-containment.d.ts +16 -0
- package/dist/src/foundation/process-containment/native-guardian-containment.js +139 -0
- package/dist/src/foundation/process-containment/windows-process-inspector.d.ts +17 -0
- package/dist/src/foundation/process-containment/windows-process-inspector.js +53 -0
- package/dist/src/foundation/protocol/client.d.ts +2 -1
- package/dist/src/foundation/protocol/client.js +3 -0
- package/dist/src/foundation/protocol/messages.d.ts +5 -5
- package/dist/src/foundation/protocol/messages.js +1 -1
- package/dist/src/foundation/release/bootstrap.d.ts +2 -2
- package/dist/src/foundation/release/bootstrap.js +69 -18
- package/dist/src/foundation/release/cohort-selection.js +3 -3
- package/dist/src/foundation/release/cohort-state.d.ts +3 -2
- package/dist/src/foundation/release/concurrency.d.ts +1 -0
- package/dist/src/foundation/release/concurrency.js +17 -0
- package/dist/src/foundation/release/process-cleanup.d.ts +1 -1
- package/dist/src/foundation/release/process-cleanup.js +2 -2
- package/dist/src/foundation/release/release-store.d.ts +35 -2
- package/dist/src/foundation/release/release-store.js +90 -31
- package/dist/src/foundation/release/release.d.ts +15 -0
- package/dist/src/foundation/release/release.js +65 -36
- package/dist/src/foundation/release/update.d.ts +19 -2
- package/dist/src/foundation/release/update.js +132 -16
- package/dist/src/foundation/storage/control-store.d.ts +8 -7
- package/dist/src/foundation/storage/control-store.js +105 -55
- package/dist/src/foundation/supervision/main.js +6 -5
- package/dist/src/foundation/supervision/server.d.ts +4 -2
- package/dist/src/foundation/supervision/server.js +254 -87
- package/dist/src/foundation/ui-apps/contracts.d.ts +42 -0
- package/dist/src/foundation/ui-apps/contracts.js +12 -0
- package/dist/src/foundation/ui-apps/host.d.ts +42 -0
- package/dist/src/foundation/ui-apps/host.js +152 -0
- package/dist/src/foundation/ui-apps/index.d.ts +3 -0
- package/dist/src/foundation/ui-apps/index.js +3 -0
- package/dist/src/foundation/ui-apps/registry.d.ts +11 -0
- package/dist/src/foundation/ui-apps/registry.js +32 -0
- package/dist/src/foundation/ui-components/dialog-panel.d.ts +36 -0
- package/dist/src/foundation/ui-components/dialog-panel.js +47 -0
- package/dist/src/foundation/ui-components/frame.d.ts +21 -0
- package/dist/src/foundation/ui-components/frame.js +54 -0
- package/dist/src/foundation/ui-components/index.d.ts +18 -0
- package/dist/src/foundation/ui-components/index.js +18 -0
- package/dist/src/foundation/ui-components/label.d.ts +8 -0
- package/dist/src/foundation/ui-components/label.js +36 -0
- package/dist/src/foundation/ui-components/line-input.d.ts +74 -0
- package/dist/src/foundation/ui-components/line-input.js +245 -0
- package/dist/src/foundation/ui-components/list-block.d.ts +66 -0
- package/dist/src/foundation/ui-components/list-block.js +176 -0
- package/dist/src/foundation/ui-components/list-view.d.ts +66 -0
- package/dist/src/foundation/ui-components/list-view.js +76 -0
- package/dist/src/foundation/ui-components/mouse.d.ts +15 -0
- package/dist/src/foundation/ui-components/mouse.js +46 -0
- package/dist/src/foundation/ui-components/pane.d.ts +33 -0
- package/dist/src/foundation/ui-components/pane.js +7 -0
- package/dist/src/foundation/ui-components/revision.d.ts +35 -0
- package/dist/src/foundation/ui-components/revision.js +79 -0
- package/dist/src/foundation/ui-components/scrollbar.d.ts +62 -0
- package/dist/src/foundation/ui-components/scrollbar.js +102 -0
- package/dist/src/foundation/ui-components/shortcuts.d.ts +57 -0
- package/dist/src/foundation/ui-components/shortcuts.js +81 -0
- package/dist/src/foundation/ui-components/spans.d.ts +7 -0
- package/dist/src/foundation/ui-components/spans.js +59 -0
- package/dist/src/foundation/ui-components/status-line.d.ts +16 -0
- package/dist/src/foundation/ui-components/status-line.js +14 -0
- package/dist/src/foundation/ui-components/stepper.d.ts +24 -0
- package/dist/src/foundation/ui-components/stepper.js +33 -0
- package/dist/src/foundation/ui-components/surface.d.ts +20 -0
- package/dist/src/foundation/ui-components/surface.js +38 -0
- package/dist/src/foundation/ui-components/text.d.ts +24 -0
- package/dist/src/foundation/ui-components/text.js +123 -0
- package/dist/src/foundation/ui-components/theme.d.ts +23 -0
- package/dist/src/foundation/ui-components/theme.js +13 -0
- package/dist/src/foundation/ui-components/value-menu.d.ts +41 -0
- package/dist/src/foundation/ui-components/value-menu.js +33 -0
- package/dist/src/product-identity.d.ts +2 -2
- package/dist/src/product-identity.js +2 -1
- package/dist/src/product-identity.json +8 -5
- package/docs/architecture/boundaries.md +31 -13
- package/docs/architecture/process-guardian-provenance.md +26 -0
- package/docs/architecture/toolchain.md +9 -3
- package/docs/architecture/ui-reference-provenance.md +50 -0
- package/docs/ci-release-runbook.md +67 -0
- package/docs/features/launch-profiles.md +7 -1
- package/docs/manual-launch-instance-acceptance.md +66 -0
- package/docs/manual-terminal-colour-check.md +55 -0
- package/package.json +20 -14
- package/dist/src/composition/transparent-runtime.d.ts +0 -6
- package/dist/src/composition/transparent-runtime.js +0 -19
- package/dist/src/foundation/transparent-terminal/command-resolution.d.ts +0 -13
- package/dist/src/foundation/transparent-terminal/command-resolution.js +0 -69
- package/dist/src/foundation/transparent-terminal/foreground-broker.d.ts +0 -32
- package/dist/src/foundation/transparent-terminal/foreground-broker.js +0 -87
- package/dist/src/foundation/transparent-terminal/index.d.ts +0 -4
- package/dist/src/foundation/transparent-terminal/index.js +0 -4
- package/dist/src/foundation/transparent-terminal/main.d.ts +0 -9
- package/dist/src/foundation/transparent-terminal/main.js +0 -94
- package/dist/src/foundation/transparent-terminal/native-launcher.d.ts +0 -25
- package/dist/src/foundation/transparent-terminal/native-launcher.js +0 -146
- /package/bin/{a1.js → cli.js} +0 -0
- /package/bin/{a1-supervisor.js → supervisor.js} +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# CI and release operations
|
|
2
|
+
|
|
3
|
+
GitHub Actions is the only automation platform. Two required checks protect the branches:
|
|
4
|
+
|
|
5
|
+
| Branch | Required check | Produced by |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| `develop` | `Development validation required` | `.github/workflows/ci.yml` |
|
|
8
|
+
| `master` | `Stable candidate required` | `.github/workflows/certify-stable.yml` |
|
|
9
|
+
|
|
10
|
+
These are job display names. If you rename one, update the ruleset definition, this runbook, and the live ruleset together.
|
|
11
|
+
|
|
12
|
+
## How much validation runs when
|
|
13
|
+
|
|
14
|
+
| Change | Validation |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| Docs or specs only | OpenSpec strict lint, nothing else |
|
|
17
|
+
| Any code PR into `develop` | Fast tier: typecheck, architecture checks, unit/contract tests |
|
|
18
|
+
| Preview candidate (`next`) | Fast tier + package gates: content, clean install, dependency policy |
|
|
19
|
+
| Stable candidate (`latest`) | Complete suite on Windows, Linux, and macOS + physical evidence |
|
|
20
|
+
|
|
21
|
+
Need more coverage for a risky preview? Dispatch the candidate with `full: true`, or run the **Full regression** workflow on demand.
|
|
22
|
+
|
|
23
|
+
## Publish a preview to npm `next`
|
|
24
|
+
|
|
25
|
+
One command from a clean `develop` checkout runs the whole chain — candidate build, publish, registry verification:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npm run release:next
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
It dispatches the same trusted workflows below and never publishes local bytes. The manual steps remain the reference path:
|
|
32
|
+
|
|
33
|
+
1. Make sure `Development validation required` is green on the `develop` tip.
|
|
34
|
+
2. Dispatch **Build npm next candidate** with the exact commit and `confirm_candidate=build-uncertified-next-candidate`.
|
|
35
|
+
3. Check the resulting `candidate-evidence.json`: gates passed, package integrity matches.
|
|
36
|
+
4. Approve the `npm-publish` environment and dispatch **Publish npm next** with the candidate run id.
|
|
37
|
+
|
|
38
|
+
The publisher uploads the exact validated tarball — it never rebuilds. Candidates expire after 14 days; an expired or mismatched artifact means building a new candidate, not patching the old one.
|
|
39
|
+
|
|
40
|
+
## Publish a stable release to npm `latest`
|
|
41
|
+
|
|
42
|
+
1. Commit the final version to `develop` (clean tree, version not yet on the registry).
|
|
43
|
+
2. Dispatch **Build stable candidate** — it packs once and validates the same bytes on all three platforms.
|
|
44
|
+
3. Dispatch **Certify stable physical platforms** on the dedicated isolated workers (they set `PHYSICAL_WORKER_ISOLATED=true` and run under the `stable-physical` environment). Never run physical host probes on a developer workstation.
|
|
45
|
+
4. Dispatch **Certify stable candidate** with both run ids. `Stable candidate required` passes only when every verdict binds the same commit, version, and digest.
|
|
46
|
+
5. Merge that exact commit to `master`, tag it `v<version>`, and dispatch **Publish npm stable**, then approve `npm-publish`.
|
|
47
|
+
|
|
48
|
+
Stable artifacts expire after 30 days. The same rule as previews applies: publication still requires exact certified bytes. Never upload locally rebuilt bytes, and never route around certification by rebuilding inside a publisher.
|
|
49
|
+
|
|
50
|
+
## When something fails
|
|
51
|
+
|
|
52
|
+
- **PR validation fails:** fix the code and push. Do not mark a failed tier optional.
|
|
53
|
+
- **Candidate validation fails:** discard the candidate and build a new one after the fix.
|
|
54
|
+
- **Physical worker fails:** fix or replace the worker, then rerun the full physical evidence for the candidate.
|
|
55
|
+
- **Publisher fails before npm accepted the bytes:** diagnose and retry with the same candidate while it is unexpired and the version is still unpublished.
|
|
56
|
+
- **Publisher is uncertain after npm accepted the bytes:** stop. Check the registry for the version, tag, and digest. Repair a dist-tag only as a separate reviewed operation — never republish.
|
|
57
|
+
|
|
58
|
+
## Why the rulesets look the way they do
|
|
59
|
+
|
|
60
|
+
One person maintains this repository, and GitHub does not let a PR author approve their own PR. Requiring even one approval would therefore deadlock the authorized solo-maintainer path — no PR could ever merge. So both rulesets require a pull request, a green required check, and resolved review threads, but set required approving reviews to zero.
|
|
61
|
+
|
|
62
|
+
- `develop` does not require the branch to be up to date: once a PR is approved and green, it merges even if unrelated work landed first — no re-validation loop.
|
|
63
|
+
- `master` does require it: a stable promotion always validates the exact final state.
|
|
64
|
+
|
|
65
|
+
If a second maintainer joins, raise the approval count. Do not add a direct-push bypass as a shortcut, and never weaken the force-push or deletion protection.
|
|
66
|
+
|
|
67
|
+
Ruleset mutation is a separate administrative operation: run `node scripts/check-github-rulesets.mjs` to see the diff, and apply only when a maintainer explicitly confirms with `--apply --confirm apply-a1-ci-rulesets`.
|
|
@@ -43,7 +43,13 @@ Run Pi’s normal `/login` independently in each profile that needs stored authe
|
|
|
43
43
|
|
|
44
44
|
Bare `a1` runs the A1-owned full-viewport TUI over public Pi engine, component, and terminal APIs. A1 owns composition, input routing, modal focus, selection behavior, scrolling, and restoration without inserting a PTY or terminal-byte relay. Its default presentation is pinned to vanilla Pi parity; structured tabs and A1-specific visual customization are not enabled.
|
|
45
45
|
|
|
46
|
-
`a1 pi` and `a1 sandbox` retain transparent direct attachment. In those profiles,
|
|
46
|
+
`a1 pi` and `a1 sandbox` retain transparent direct attachment. In those profiles, untouched Pi and the physical terminal own rendering, input, selection, clipboard, scrollback, and terminal modes; A1 owns only per-invocation process containment and lifecycle evidence.
|
|
47
|
+
|
|
48
|
+
## Concurrent instance lifecycle
|
|
49
|
+
|
|
50
|
+
Every `a1`, `a1 pi`, and `a1 sandbox` invocation receives a unique non-detachable launch instance. There is no product-wide foreground slot: the same profile or any mixture of profiles may run concurrently in separate terminals. Profile files remain shared according to the selected profile root, while process ownership, lifecycle outcome, and cleanup remain scoped to the command that launched them.
|
|
51
|
+
|
|
52
|
+
Normal root exit, terminal closure, guardian failure, and verified update shutdown all close the instance's remaining process tree within bounded graceful and forced deadlines. Closing one instance never closes another. A control supervisor may remain idle afterward, but closed instances leave no UI, Pi, extension daemon, agent worker, tool, or descendant process. Ordinary recovery never asks users to kill a PID or delete the control database.
|
|
47
53
|
|
|
48
54
|
## Recovery and comparison
|
|
49
55
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Launch-instance exact-artifact acceptance
|
|
2
|
+
|
|
3
|
+
Run this checklist only against a packaged candidate on a user-controlled terminal or isolated disposable Windows worker. Do not automate launching, focusing, driving, or closing terminal windows on an active workstation.
|
|
4
|
+
|
|
5
|
+
## Artifact identity
|
|
6
|
+
|
|
7
|
+
Record before testing:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
A1 release ID:
|
|
11
|
+
A1 package version:
|
|
12
|
+
Source commit:
|
|
13
|
+
Candidate tarball SHA-256/integrity:
|
|
14
|
+
Process guardian target: win32-x64
|
|
15
|
+
Process guardian crate version:
|
|
16
|
+
Process guardian protocol version:
|
|
17
|
+
Process guardian SHA-256:
|
|
18
|
+
Process guardian signature/attestation status:
|
|
19
|
+
Machine/Windows/terminal versions:
|
|
20
|
+
Tester or worker attestation:
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The process guardian values must match `dist/native/win32-x64/manifest.json` inside the exact installed candidate. Stop if the file is missing, mismatched, altered, or reports an unsupported capability.
|
|
24
|
+
|
|
25
|
+
## Concurrent launch matrix
|
|
26
|
+
|
|
27
|
+
Open four independent terminal tabs and launch:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
Terminal 1: a1
|
|
31
|
+
Terminal 2: a1
|
|
32
|
+
Terminal 3: a1 pi
|
|
33
|
+
Terminal 4: a1 sandbox
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Confirm all four become usable without a global foreground-lease error. Record each launch-instance ID from supervisor metadata and verify all four IDs are distinct.
|
|
37
|
+
|
|
38
|
+
## Isolated closure
|
|
39
|
+
|
|
40
|
+
1. Exit Terminal 3 normally. Confirm only its vanilla Pi tree exits; Terminals 1, 2, and 4 remain usable.
|
|
41
|
+
2. Close the Terminal 4 tab/window without exiting Pi first. Confirm its sandbox Pi, extension daemons, agent workers, tools, and descendants disappear within the bounded deadline while both bare-A1 terminals remain usable.
|
|
42
|
+
3. Exit Terminal 1 normally after causing a fixture extension to create a child and grandchild. Confirm the complete tree disappears and Terminal 2 remains usable.
|
|
43
|
+
4. Force the Terminal 2 launch guardian to fail on the isolated worker. Confirm Job Object kill-on-close removes its root and descendants and leaves unrelated fixture processes alive.
|
|
44
|
+
|
|
45
|
+
Record root, child, and grandchild native identities before each close and the observed terminal outcome afterward. Do not substitute manual PID termination for a failed cleanup.
|
|
46
|
+
|
|
47
|
+
## Stale-state recovery
|
|
48
|
+
|
|
49
|
+
Using isolated control directories, install a version-4 fixture database containing one requested or active legacy foreground lease whose former PID is dead. Run `a1 sandbox` once. Confirm that the same invocation migrates the row to historical interrupted state and launches normally without restarting the supervisor, deleting state, or discovering a PID.
|
|
50
|
+
|
|
51
|
+
Repeat with an ownership-uncertain fixture process. Confirm a new independent launch still starts and the uncertain unrelated process is not terminated.
|
|
52
|
+
|
|
53
|
+
## Update fan-out
|
|
54
|
+
|
|
55
|
+
With at least three mixed-profile instances active, run the candidate's update-shutdown fixture. Confirm every instance receives one stop intent, each complete process tree closes, and cohort replacement occurs only after all verified terminal outcomes. A delayed or uncertain instance must prevent unsafe replacement without causing another instance to be reported idle prematurely.
|
|
56
|
+
|
|
57
|
+
## Verdict
|
|
58
|
+
|
|
59
|
+
Record one of:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
ACCEPTED — exact package and guardian digests above passed every scenario
|
|
63
|
+
REJECTED — include failed step, observed process identities, outcomes, and diagnostics
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Acceptance applies only to the exact package and process-guardian bytes recorded above. It does not certify macOS, Linux, composed terminals, detached agents, or an operating-system security sandbox.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Terminal colour check
|
|
2
|
+
|
|
3
|
+
## What can go wrong
|
|
4
|
+
|
|
5
|
+
Every colour the interface draws is 24-bit: the pinned dark theme's accent is
|
|
6
|
+
`#8abeb7`, its border `#5f87ff`, its headings `#f0c674`. On Windows, Node decides
|
|
7
|
+
once at startup whether it may send those sequences to the terminal at all. It
|
|
8
|
+
asks the console to enable virtual terminal processing; when that fails it renders
|
|
9
|
+
ANSI itself, and its renderer knows only the sixteen SGR colours. Each 24-bit
|
|
10
|
+
colour is then replaced by the terminal's nearest palette entry, so the whole
|
|
11
|
+
interface shifts at once — accent to the terminal's cyan, headings to its yellow —
|
|
12
|
+
while the application still writes exactly the bytes it always wrote.
|
|
13
|
+
|
|
14
|
+
That decision is made per process, from the console handle the process was given,
|
|
15
|
+
and every process it launches inherits the result. It is a property of how the
|
|
16
|
+
session was started, not of what is drawn.
|
|
17
|
+
|
|
18
|
+
## Launching so colour survives
|
|
19
|
+
|
|
20
|
+
The installed command is an npm shim — a shell script that `exec`s Node — and that
|
|
21
|
+
shape keeps 24-bit colour. So does `pi`, for the same reason.
|
|
22
|
+
|
|
23
|
+
- Installed: `a1`, `a1 pi`
|
|
24
|
+
- From a checkout: `scripts/dev` and `scripts/dev pi`
|
|
25
|
+
|
|
26
|
+
`npm start` and `npm run start:pi` build first and then launch through the same
|
|
27
|
+
entry, but a package manager on Windows hands the script to `cmd.exe`, which can
|
|
28
|
+
leave the launch without a terminal on its input; the launcher says so and stops
|
|
29
|
+
rather than opening a session that closes as it starts. Running `scripts/dev`
|
|
30
|
+
from the shell is the direct path.
|
|
31
|
+
|
|
32
|
+
A directly launched `node scripts/start-local.mjs` under Git Bash does not, and
|
|
33
|
+
neither does a directly launched `node …/pi/dist/cli.js`: the collapse belongs to
|
|
34
|
+
the launch, not to either application.
|
|
35
|
+
|
|
36
|
+
## Checking a terminal
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
node scripts/check-terminal-colour.mjs
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
It prints the same colour as 24-bit, as a 256-colour index, and as the terminal's
|
|
43
|
+
own palette entry. When the first block is indistinguishable from the third, this
|
|
44
|
+
launch collapses colour and any comparison made in it is a comparison of two wrong
|
|
45
|
+
screens.
|
|
46
|
+
|
|
47
|
+
## What the gates hold
|
|
48
|
+
|
|
49
|
+
- `test/features/launch/terminal-colour-fidelity.test.ts` holds the emitted bytes:
|
|
50
|
+
the pinned palette resolves to 24-bit sequences, a 256-colour terminal stays on
|
|
51
|
+
indices, and the launch chain keeps inheriting the terminal it was started from
|
|
52
|
+
rather than opening a console of its own.
|
|
53
|
+
- The reader compares `a1 pi` with pinned Pi in the same terminal. Run the colour
|
|
54
|
+
check first: a launch that collapses colour makes both sessions equally wrong, so
|
|
55
|
+
they would agree with each other and disagree with what a user sees.
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timurproko/a1",
|
|
3
|
-
"version": "0.1.1-dev.
|
|
3
|
+
"version": "0.1.1-dev.11",
|
|
4
4
|
"description": "Standalone terminal workspace for supervised native and managed agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@11.13.0",
|
|
7
7
|
"bin": {
|
|
8
|
-
"a1": "bin/
|
|
8
|
+
"a1": "bin/cli.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"bin",
|
|
@@ -19,30 +19,35 @@
|
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"clean": "node scripts/clean.mjs",
|
|
22
|
-
"build": "npm run clean && tsc -p tsconfig.build.json",
|
|
23
|
-
"
|
|
22
|
+
"build": "npm run clean && tsc -p tsconfig.build.json && node scripts/build-process-guardian.mjs",
|
|
23
|
+
"build:process-guardian": "node scripts/build-process-guardian.mjs",
|
|
24
|
+
"typecheck": "tsgo -p tsconfig.json --noEmit",
|
|
24
25
|
"check:architecture": "node scripts/check-architecture.mjs && node scripts/product-identifier-policy.mjs --check && node scripts/check-product-identity-boundaries.mjs && node scripts/check-package-identity.mjs && node scripts/check-pinned-pi-source-ledger.mjs && node scripts/check-terminal-host-provenance.mjs",
|
|
25
26
|
"check:customization-ready": "node scripts/check-owned-ui-customization-prerequisites.mjs",
|
|
26
27
|
"check:deprecated": "node scripts/check-deprecated-dependencies.mjs",
|
|
27
|
-
"
|
|
28
|
-
"
|
|
28
|
+
"branches:prune": "node scripts/prune-merged-branches.mjs",
|
|
29
|
+
"check": "npm run test:fast",
|
|
29
30
|
"publish:next": "tsx scripts/publish-next.ts",
|
|
30
|
-
"
|
|
31
|
-
"test
|
|
32
|
-
"test:
|
|
31
|
+
"release:next": "node scripts/release-next.mjs",
|
|
32
|
+
"test": "npm run test:fast",
|
|
33
|
+
"test:fast": "node scripts/run-validation-tier.mjs typecheck fast",
|
|
34
|
+
"test:scope": "node scripts/run-validation-tier.mjs",
|
|
35
|
+
"test:full": "node scripts/run-validation-tier.mjs full-release",
|
|
33
36
|
"test:release": "node scripts/run-release-gates.mjs",
|
|
34
37
|
"test:terminal-host": "node scripts/run-terminal-host-probe.mjs",
|
|
35
|
-
"test:pi-terminal-parity": "npm run build --silent && node scripts/run-pi-terminal-parity.mjs",
|
|
36
38
|
"test:pi-engine-conformance": "vitest run test/foundation/pi-engine-adapter/conformance.test.ts test/foundation/pi-engine-adapter/runtime-integration.test.ts test/foundation/pi-engine-adapter/session-integration.test.ts",
|
|
37
39
|
"report:pi-engine-conformance": "npm run build --silent && node scripts/run-pi-engine-conformance.mjs",
|
|
38
|
-
"sync:pi-ui": "npm run update:pi-component-parity && npm run update:pi-event-frame-parity",
|
|
40
|
+
"sync:pi-ui": "npm run update:pi-component-parity && npm run update:pi-event-frame-parity && npm run update:pi-settings-metadata",
|
|
39
41
|
"update:pi-component-parity": "tsx scripts/update-pi-component-parity.ts",
|
|
40
42
|
"update:pi-event-frame-parity": "tsx scripts/update-pi-event-frame-parity.ts",
|
|
41
43
|
"proof:terminal-host": "node scripts/run-terminal-host.mjs",
|
|
42
|
-
"start": "npm run build && node scripts/
|
|
44
|
+
"start": "npm run build && node scripts/dev-launch.mjs",
|
|
45
|
+
"start:pi": "npm run build && node scripts/dev-launch.mjs pi",
|
|
46
|
+
"start:sandbox": "npm run build && node scripts/dev-launch.mjs sandbox",
|
|
43
47
|
"prepack": "node scripts/prepack-gate.mjs",
|
|
44
|
-
"prepublishOnly": "npm run
|
|
45
|
-
"prepare": "npm run build"
|
|
48
|
+
"prepublishOnly": "npm run test:release",
|
|
49
|
+
"prepare": "npm run build",
|
|
50
|
+
"update:pi-settings-metadata": "node scripts/update-pi-settings-metadata.mjs"
|
|
46
51
|
},
|
|
47
52
|
"dependencies": {
|
|
48
53
|
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
@@ -55,6 +60,7 @@
|
|
|
55
60
|
"@types/cross-spawn": "6.0.6",
|
|
56
61
|
"@types/node": "24.2.1",
|
|
57
62
|
"@types/semver": "7.8.0",
|
|
63
|
+
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
58
64
|
"@xterm/headless": "6.0.0",
|
|
59
65
|
"node-pty": "1.1.0",
|
|
60
66
|
"tsx": "4.20.3",
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { LaunchProfileId } from "../foundation/lifecycle/index.js";
|
|
2
|
-
export declare function selectTransparentChild(environment?: NodeJS.ProcessEnv, executable?: string, compositionUrl?: string): {
|
|
3
|
-
readonly executable: string;
|
|
4
|
-
readonly arguments: readonly string[];
|
|
5
|
-
};
|
|
6
|
-
export declare function runSelectedTransparentRuntime(profileId: Exclude<LaunchProfileId, "a1">, environment?: NodeJS.ProcessEnv): Promise<number>;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { fileURLToPath } from "node:url";
|
|
2
|
-
import { runTransparentForeground } from "../foundation/transparent-terminal/index.js";
|
|
3
|
-
export function selectTransparentChild(environment = process.env, executable = process.execPath, compositionUrl = import.meta.url) {
|
|
4
|
-
const entry = fileURLToPath(new URL("../foundation/pi-engine-adapter/public-main-entry.js", compositionUrl));
|
|
5
|
-
return { executable, arguments: [entry, ...parseSelectedArguments(environment.A1_LAUNCH_ARGUMENTS_JSON)] };
|
|
6
|
-
}
|
|
7
|
-
export async function runSelectedTransparentRuntime(profileId, environment = process.env) {
|
|
8
|
-
const child = selectTransparentChild(environment);
|
|
9
|
-
return await runTransparentForeground({ profileId, environment, ...child });
|
|
10
|
-
}
|
|
11
|
-
function parseSelectedArguments(source) {
|
|
12
|
-
if (!source)
|
|
13
|
-
return [];
|
|
14
|
-
const value = JSON.parse(source);
|
|
15
|
-
if (!Array.isArray(value) || value.some(item => typeof item !== "string")) {
|
|
16
|
-
throw new TypeError("A1_LAUNCH_ARGUMENTS_JSON must be a JSON array of strings");
|
|
17
|
-
}
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export interface ResolvedTransparentCommand {
|
|
2
|
-
readonly executable: string;
|
|
3
|
-
readonly arguments: readonly string[];
|
|
4
|
-
readonly source: "exact" | "path" | "npm-windows-shim";
|
|
5
|
-
}
|
|
6
|
-
export interface TransparentCommandResolutionOptions {
|
|
7
|
-
readonly platform?: NodeJS.Platform;
|
|
8
|
-
readonly cwd: string;
|
|
9
|
-
readonly environment: Readonly<Record<string, string>>;
|
|
10
|
-
readonly nodeExecutable?: string;
|
|
11
|
-
}
|
|
12
|
-
/** Resolve a command without invoking a command interpreter. */
|
|
13
|
-
export declare function resolveTransparentCommand(executable: string, arguments_: readonly string[], options: TransparentCommandResolutionOptions): Promise<ResolvedTransparentCommand>;
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { access, readFile } from "node:fs/promises";
|
|
2
|
-
import { delimiter, extname, isAbsolute, resolve } from "node:path";
|
|
3
|
-
/** Resolve a command without invoking a command interpreter. */
|
|
4
|
-
export async function resolveTransparentCommand(executable, arguments_, options) {
|
|
5
|
-
const platform = options.platform ?? process.platform;
|
|
6
|
-
return platform === "win32"
|
|
7
|
-
? await resolveWindowsCommand(executable, arguments_, options)
|
|
8
|
-
: { executable, arguments: [...arguments_], source: "exact" };
|
|
9
|
-
}
|
|
10
|
-
async function resolveWindowsCommand(command, commandArguments, options) {
|
|
11
|
-
const resolved = await resolveWindowsPathCommand(command, options.cwd, options.environment);
|
|
12
|
-
const selected = resolved ?? throwCommandNotFound(command);
|
|
13
|
-
if (extname(selected).toLowerCase() !== ".cmd") {
|
|
14
|
-
return { executable: selected, arguments: [...commandArguments], source: "path" };
|
|
15
|
-
}
|
|
16
|
-
return await unwrapNpmWindowsShim(selected, commandArguments, options.nodeExecutable ?? process.execPath);
|
|
17
|
-
}
|
|
18
|
-
async function resolveWindowsPathCommand(executable, cwd, environment) {
|
|
19
|
-
const hasPath = isAbsolute(executable) || executable.includes("/") || executable.includes("\\");
|
|
20
|
-
const roots = hasPath ? [cwd] : windowsPath(environment).split(delimiter).filter(Boolean);
|
|
21
|
-
const extension = extname(executable);
|
|
22
|
-
const extensions = extension ? [""] : windowsExecutableExtensions(environment);
|
|
23
|
-
for (const root of roots) {
|
|
24
|
-
const base = hasPath ? resolve(cwd, executable) : resolve(root.replace(/^"|"$/g, ""), executable);
|
|
25
|
-
for (const suffix of extensions) {
|
|
26
|
-
const candidate = `${base}${suffix}`;
|
|
27
|
-
if (await isFile(candidate))
|
|
28
|
-
return candidate;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
async function unwrapNpmWindowsShim(shimPath, arguments_, fallbackNodeExecutable) {
|
|
34
|
-
const source = (await readFile(shimPath, "utf8")).replace(/^\uFEFF/, "");
|
|
35
|
-
const targetMatch = source.match(/"%_prog%"\s+"%dp0%\\([^"\r\n]+)"\s+%\*/i);
|
|
36
|
-
if (!targetMatch?.[1]) {
|
|
37
|
-
throw Object.assign(new Error(`transparent launch refuses arbitrary Windows command script ${shimPath}; configure an executable binary instead`), { code: "UNSUPPORTED_WINDOWS_COMMAND_SHIM" });
|
|
38
|
-
}
|
|
39
|
-
const shimRoot = resolve(shimPath, "..");
|
|
40
|
-
const target = resolve(shimRoot, targetMatch[1].replaceAll("\\", "/"));
|
|
41
|
-
if (!await isFile(target))
|
|
42
|
-
throw Object.assign(new Error(`npm command shim target is missing: ${target}`), { code: "ENOENT" });
|
|
43
|
-
const localNode = resolve(shimRoot, "node.exe");
|
|
44
|
-
const nodeExecutable = await isFile(localNode) ? localNode : fallbackNodeExecutable;
|
|
45
|
-
return {
|
|
46
|
-
executable: nodeExecutable,
|
|
47
|
-
arguments: [target, ...arguments_],
|
|
48
|
-
source: "npm-windows-shim",
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function throwCommandNotFound(command) {
|
|
52
|
-
throw Object.assign(new Error(`spawn ${command} ENOENT`), { code: "ENOENT" });
|
|
53
|
-
}
|
|
54
|
-
function windowsPath(environment) {
|
|
55
|
-
return environment.Path ?? environment.PATH ?? "";
|
|
56
|
-
}
|
|
57
|
-
function windowsExecutableExtensions(environment) {
|
|
58
|
-
const source = environment.PATHEXT ?? ".COM;.EXE;.BAT;.CMD";
|
|
59
|
-
return source.split(";").filter(Boolean).map(value => value.startsWith(".") ? value : `.${value}`);
|
|
60
|
-
}
|
|
61
|
-
async function isFile(path) {
|
|
62
|
-
try {
|
|
63
|
-
await access(path);
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { CommandResult, ForegroundTerminalLeaseId, GenerationId, NativeProcessIdentity, RequestId, SupervisorCommand, TransparentTerminalLaunchProfile, TransparentTerminalLifecycleOutcome } from "../lifecycle/index.js";
|
|
2
|
-
export interface ForegroundLeaseControl {
|
|
3
|
-
command(command: SupervisorCommand): Promise<CommandResult>;
|
|
4
|
-
}
|
|
5
|
-
export type TransparentStopReason = "owner-disconnect" | "user-request" | "update";
|
|
6
|
-
export interface TransparentChildHandle {
|
|
7
|
-
readonly processIdentity: NativeProcessIdentity;
|
|
8
|
-
readonly outcome: Promise<TransparentTerminalLifecycleOutcome>;
|
|
9
|
-
stop(reason: TransparentStopReason): Promise<TransparentTerminalLifecycleOutcome>;
|
|
10
|
-
}
|
|
11
|
-
export interface TransparentNativeLauncher {
|
|
12
|
-
launch(profile: TransparentTerminalLaunchProfile): Promise<TransparentChildHandle>;
|
|
13
|
-
}
|
|
14
|
-
export interface ForegroundBrokerRequest {
|
|
15
|
-
readonly leaseId: ForegroundTerminalLeaseId;
|
|
16
|
-
readonly generationId: GenerationId;
|
|
17
|
-
readonly ownerId: string;
|
|
18
|
-
readonly profile: TransparentTerminalLaunchProfile;
|
|
19
|
-
readonly stopRequested?: Promise<TransparentStopReason>;
|
|
20
|
-
}
|
|
21
|
-
export interface ForegroundBrokerResult {
|
|
22
|
-
readonly leaseId: ForegroundTerminalLeaseId;
|
|
23
|
-
readonly generationId: GenerationId;
|
|
24
|
-
readonly processIdentity: NativeProcessIdentity | null;
|
|
25
|
-
readonly outcome: TransparentTerminalLifecycleOutcome;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Coordinates ownership around one natively attached child. Ordinary terminal
|
|
29
|
-
* data never enters this object: the launcher owns native attachment and this
|
|
30
|
-
* broker observes only process identity and the final lifecycle outcome.
|
|
31
|
-
*/
|
|
32
|
-
export declare function runForegroundBroker(request: ForegroundBrokerRequest, control: ForegroundLeaseControl, launcher: TransparentNativeLauncher, createRequestId: () => RequestId): Promise<ForegroundBrokerResult>;
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { assertNativeProcessIdentity, assertTransparentTerminalLaunchProfile } from "../lifecycle/index.js";
|
|
2
|
-
/**
|
|
3
|
-
* Coordinates ownership around one natively attached child. Ordinary terminal
|
|
4
|
-
* data never enters this object: the launcher owns native attachment and this
|
|
5
|
-
* broker observes only process identity and the final lifecycle outcome.
|
|
6
|
-
*/
|
|
7
|
-
export async function runForegroundBroker(request, control, launcher, createRequestId) {
|
|
8
|
-
assertRequest(request);
|
|
9
|
-
await requireAccepted(control, {
|
|
10
|
-
type: "acquire-foreground-terminal-lease",
|
|
11
|
-
requestId: createRequestId(),
|
|
12
|
-
leaseId: request.leaseId,
|
|
13
|
-
ownerId: request.ownerId,
|
|
14
|
-
profile: request.profile,
|
|
15
|
-
});
|
|
16
|
-
let handle;
|
|
17
|
-
try {
|
|
18
|
-
handle = await launcher.launch(request.profile);
|
|
19
|
-
assertNativeProcessIdentity(handle.processIdentity);
|
|
20
|
-
}
|
|
21
|
-
catch (error) {
|
|
22
|
-
const outcome = {
|
|
23
|
-
kind: "spawn-error",
|
|
24
|
-
message: errorMessage(error),
|
|
25
|
-
code: errorCode(error),
|
|
26
|
-
};
|
|
27
|
-
await requireAccepted(control, {
|
|
28
|
-
type: "release-foreground-terminal-lease",
|
|
29
|
-
requestId: createRequestId(),
|
|
30
|
-
leaseId: request.leaseId,
|
|
31
|
-
processIdentity: null,
|
|
32
|
-
outcome,
|
|
33
|
-
});
|
|
34
|
-
return { leaseId: request.leaseId, generationId: request.generationId, processIdentity: null, outcome };
|
|
35
|
-
}
|
|
36
|
-
try {
|
|
37
|
-
await requireAccepted(control, {
|
|
38
|
-
type: "activate-foreground-terminal-lease",
|
|
39
|
-
requestId: createRequestId(),
|
|
40
|
-
leaseId: request.leaseId,
|
|
41
|
-
generationId: request.generationId,
|
|
42
|
-
processIdentity: handle.processIdentity,
|
|
43
|
-
});
|
|
44
|
-
const outcome = request.stopRequested
|
|
45
|
-
? await Promise.race([handle.outcome, request.stopRequested.then(async (reason) => await handle.stop(reason))])
|
|
46
|
-
: await handle.outcome;
|
|
47
|
-
await requireAccepted(control, {
|
|
48
|
-
type: "release-foreground-terminal-lease",
|
|
49
|
-
requestId: createRequestId(),
|
|
50
|
-
leaseId: request.leaseId,
|
|
51
|
-
processIdentity: handle.processIdentity,
|
|
52
|
-
outcome,
|
|
53
|
-
});
|
|
54
|
-
return { leaseId: request.leaseId, generationId: request.generationId, processIdentity: handle.processIdentity, outcome };
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
const outcome = {
|
|
58
|
-
kind: "broker-error",
|
|
59
|
-
message: errorMessage(error),
|
|
60
|
-
code: errorCode(error),
|
|
61
|
-
};
|
|
62
|
-
await control.command({
|
|
63
|
-
type: "release-foreground-terminal-lease",
|
|
64
|
-
requestId: createRequestId(),
|
|
65
|
-
leaseId: request.leaseId,
|
|
66
|
-
processIdentity: handle.processIdentity,
|
|
67
|
-
outcome,
|
|
68
|
-
}).catch(() => undefined);
|
|
69
|
-
throw error;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
function assertRequest(request) {
|
|
73
|
-
if (!request.leaseId || !request.generationId || !request.ownerId)
|
|
74
|
-
throw new TypeError("foreground broker identities are required");
|
|
75
|
-
assertTransparentTerminalLaunchProfile(request.profile);
|
|
76
|
-
}
|
|
77
|
-
async function requireAccepted(control, command) {
|
|
78
|
-
const result = await control.command(command);
|
|
79
|
-
if (!result.ok)
|
|
80
|
-
throw new Error(result.error?.message ?? `supervisor rejected ${command.type}`);
|
|
81
|
-
}
|
|
82
|
-
function errorMessage(error) {
|
|
83
|
-
return error instanceof Error ? error.message : String(error);
|
|
84
|
-
}
|
|
85
|
-
function errorCode(error) {
|
|
86
|
-
return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : null;
|
|
87
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type LaunchProfileId } from "../lifecycle/index.js";
|
|
2
|
-
export interface TransparentForegroundOptions {
|
|
3
|
-
readonly environment?: NodeJS.ProcessEnv;
|
|
4
|
-
readonly profileId?: LaunchProfileId;
|
|
5
|
-
readonly cwd?: string;
|
|
6
|
-
readonly executable: string;
|
|
7
|
-
readonly arguments: readonly string[];
|
|
8
|
-
}
|
|
9
|
-
export declare function runTransparentForeground(options: TransparentForegroundOptions): Promise<number>;
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { realpath } from "node:fs/promises";
|
|
3
|
-
import { resolve } from "node:path";
|
|
4
|
-
import { assertLaunchProfileId } from "../lifecycle/index.js";
|
|
5
|
-
import { SupervisorClient } from "../protocol/index.js";
|
|
6
|
-
import { resolveProductPaths } from "../supervision/index.js";
|
|
7
|
-
import { runForegroundBroker } from "./foreground-broker.js";
|
|
8
|
-
import { createPlatformTransparentLauncher } from "./native-launcher.js";
|
|
9
|
-
export async function runTransparentForeground(options) {
|
|
10
|
-
const environment = { ...(options.environment ?? process.env) };
|
|
11
|
-
const profileId = options.profileId ?? environment.A1_LAUNCH_PROFILE ?? "a1";
|
|
12
|
-
assertLaunchProfileId(profileId);
|
|
13
|
-
const paths = resolveProductPaths(environment);
|
|
14
|
-
const client = new SupervisorClient(environment.A1_RELEASE_ID);
|
|
15
|
-
await client.connect(paths.endpoint);
|
|
16
|
-
const ownerId = `foreground-broker-${randomUUID()}`;
|
|
17
|
-
const profile = await launchProfile(environment, options, profileId);
|
|
18
|
-
const stop = stopSignal();
|
|
19
|
-
try {
|
|
20
|
-
const result = await runForegroundBroker({ leaseId: randomUUID(), generationId: randomUUID(), ownerId, profile, stopRequested: stop.requested }, client, createPlatformTransparentLauncher(), randomUUID);
|
|
21
|
-
if (result.outcome.kind === "exited")
|
|
22
|
-
return result.outcome.exitCode;
|
|
23
|
-
if (result.outcome.kind === "signaled")
|
|
24
|
-
return 1;
|
|
25
|
-
if (result.outcome.kind === "stopped" || result.outcome.kind === "detached")
|
|
26
|
-
return 0;
|
|
27
|
-
throw Object.assign(new Error(result.outcome.message), { code: result.outcome.code ?? undefined });
|
|
28
|
-
}
|
|
29
|
-
finally {
|
|
30
|
-
stop.dispose();
|
|
31
|
-
client.close();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
async function launchProfile(environment, options, profileId) {
|
|
35
|
-
const executable = options.executable;
|
|
36
|
-
const arguments_ = options.arguments;
|
|
37
|
-
const cwd = await realpath(options.cwd ?? process.cwd());
|
|
38
|
-
const terminalType = environment.TERM?.trim() || "xterm-256color";
|
|
39
|
-
return {
|
|
40
|
-
id: `${profileId}-transparent-${randomUUID()}`,
|
|
41
|
-
terminalCapability: "transparent",
|
|
42
|
-
executable,
|
|
43
|
-
arguments: arguments_,
|
|
44
|
-
cwd,
|
|
45
|
-
environment: selectedChildEnvironment(environment),
|
|
46
|
-
terminalType,
|
|
47
|
-
dimensions: terminalDimensions(environment),
|
|
48
|
-
ownerDisconnect: "stop",
|
|
49
|
-
recovery: "none",
|
|
50
|
-
surface: "none",
|
|
51
|
-
visualReconnection: "none",
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
function selectedChildEnvironment(environment) {
|
|
55
|
-
const selected = {};
|
|
56
|
-
for (const [name, value] of Object.entries(environment)) {
|
|
57
|
-
if (value !== undefined && !name.startsWith("A1_"))
|
|
58
|
-
selected[name] = value;
|
|
59
|
-
}
|
|
60
|
-
return selected;
|
|
61
|
-
}
|
|
62
|
-
function terminalDimensions(environment) {
|
|
63
|
-
const columns = positiveInteger(environment.COLUMNS) ?? 80;
|
|
64
|
-
const rows = positiveInteger(environment.LINES) ?? 24;
|
|
65
|
-
return { columns: Math.min(500, Math.max(2, columns)), rows: Math.min(300, Math.max(1, rows)) };
|
|
66
|
-
}
|
|
67
|
-
function positiveInteger(value) {
|
|
68
|
-
const parsed = Number(value);
|
|
69
|
-
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
|
|
70
|
-
}
|
|
71
|
-
function stopSignal() {
|
|
72
|
-
let resolveStop;
|
|
73
|
-
let settled = false;
|
|
74
|
-
const requested = new Promise(resolve => { resolveStop = resolve; });
|
|
75
|
-
const request = (reason) => {
|
|
76
|
-
if (settled)
|
|
77
|
-
return;
|
|
78
|
-
settled = true;
|
|
79
|
-
resolveStop(reason);
|
|
80
|
-
};
|
|
81
|
-
const onSigterm = () => request("user-request");
|
|
82
|
-
// The attached child shares foreground signal delivery. Keep the broker alive
|
|
83
|
-
// without translating or forwarding Ctrl+C.
|
|
84
|
-
const onSigint = () => undefined;
|
|
85
|
-
process.once("SIGTERM", onSigterm);
|
|
86
|
-
process.on("SIGINT", onSigint);
|
|
87
|
-
return {
|
|
88
|
-
requested,
|
|
89
|
-
dispose() {
|
|
90
|
-
process.off("SIGTERM", onSigterm);
|
|
91
|
-
process.off("SIGINT", onSigint);
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
}
|