@timurproko/a1 0.1.1-dev.7 → 0.1.1-dev.9
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 +11 -74
- package/bin/{a1-guardian.js → guardian.js} +1 -1
- package/bin/{a1-ui.js → ui.js} +1 -8
- package/dist/native/darwin-arm64/manifest.json +3 -3
- package/dist/native/darwin-arm64/{a1-process-guardian → process-guardian} +0 -0
- package/dist/native/linux-x64/manifest.json +3 -3
- package/dist/native/linux-x64/{a1-process-guardian → 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/src/composition/index.js +7 -1
- 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 -9
- package/dist/src/features/launch/runtime-selection.js +6 -6
- package/dist/src/features/owned-ui/settings-app.js +6 -4
- package/dist/src/foundation/pi-component-adapter/index.d.ts +1 -1
- package/dist/src/foundation/pi-component-adapter/index.js +1 -1
- 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 +5 -0
- package/dist/src/foundation/pi-engine-adapter/adapter.js +7 -0
- package/dist/src/foundation/pi-engine-adapter/settings-integration.d.ts +0 -2
- package/dist/src/foundation/pi-engine-adapter/settings-integration.js +24 -37
- package/dist/src/foundation/release/bootstrap.js +2 -2
- package/dist/src/foundation/ui-components/list-view.js +4 -4
- package/dist/src/foundation/ui-components/theme.d.ts +6 -0
- package/dist/src/foundation/ui-components/theme.js +1 -0
- package/dist/src/product-identity.json +7 -7
- package/docs/architecture/process-guardian-provenance.md +5 -5
- package/docs/architecture/toolchain.md +3 -3
- package/docs/ci-release-runbook.md +10 -2
- package/docs/manual-terminal-colour-check.md +55 -0
- package/package.json +9 -7
- package/dist/native/win32-x64/a1-process-guardian.exe +0 -0
- 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 -72
- package/dist/src/foundation/transparent-terminal/foreground-broker.d.ts +0 -24
- package/dist/src/foundation/transparent-terminal/foreground-broker.js +0 -30
- 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 -87
- 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
|
@@ -86,13 +86,13 @@
|
|
|
86
86
|
"piEventFrameParitySchema": "a1-pi-event-frame-parity-v1"
|
|
87
87
|
},
|
|
88
88
|
"artifacts": {
|
|
89
|
-
"cliEntry": "bin/
|
|
90
|
-
"supervisorEntry": "bin/
|
|
91
|
-
"guardianEntry": "bin/
|
|
92
|
-
"uiEntry": "bin/
|
|
93
|
-
"nativeExecutable": "
|
|
94
|
-
"nativeCrate": "
|
|
95
|
-
"processGuardianExecutable": "
|
|
89
|
+
"cliEntry": "bin/cli.js",
|
|
90
|
+
"supervisorEntry": "bin/supervisor.js",
|
|
91
|
+
"guardianEntry": "bin/guardian.js",
|
|
92
|
+
"uiEntry": "bin/ui.js",
|
|
93
|
+
"nativeExecutable": "terminal-host",
|
|
94
|
+
"nativeCrate": "terminal-host",
|
|
95
|
+
"processGuardianExecutable": "process-guardian",
|
|
96
96
|
"releaseTarballStem": "timurproko-a1",
|
|
97
97
|
"diagnosticStem": "a1"
|
|
98
98
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Process guardian provenance
|
|
2
2
|
|
|
3
|
-
A1 launch-instance containment uses a standalone native executable named `
|
|
3
|
+
A1 launch-instance containment uses a standalone native executable named `process-guardian`. The approved implementation is a separate Rust crate at `native/process-guardian`; it is not a Node-API addon and does not reuse or resume the held composed terminal-host implementation.
|
|
4
4
|
|
|
5
5
|
## Boundary
|
|
6
6
|
|
|
@@ -13,10 +13,10 @@ On Windows it uses `windows-sys 0.61.2` to create a Job Object, enables `JOB_OBJ
|
|
|
13
13
|
Release builds place the exact executable at:
|
|
14
14
|
|
|
15
15
|
```text
|
|
16
|
-
dist/native/win32-x64/
|
|
17
|
-
dist/native/linux-x64/
|
|
18
|
-
dist/native/darwin-x64/
|
|
19
|
-
dist/native/darwin-arm64/
|
|
16
|
+
dist/native/win32-x64/process-guardian.exe
|
|
17
|
+
dist/native/linux-x64/process-guardian
|
|
18
|
+
dist/native/darwin-x64/process-guardian
|
|
19
|
+
dist/native/darwin-arm64/process-guardian
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
Each supported artifact must be named in the release integrity manifest with target triple, crate version, guardian protocol version, SHA-256, byte size, build provenance, and signature/attestation status. A missing, altered, incompatible, or wrong-platform artifact is a concise unsupported-containment launch error; A1 never silently falls back to PID-only cleanup.
|
|
@@ -65,15 +65,15 @@ This mandatory candidate workflow checks documented package-root exports and the
|
|
|
65
65
|
npm run sync:pi-ui
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
This optional, mutating presentation-maintenance workflow regenerates component and event-frame parity evidence when maintainers deliberately adopt upstream UI changes. Review generated diffs, attribution, source-ledger records
|
|
68
|
+
This optional, mutating presentation-maintenance workflow regenerates component and event-frame parity evidence when maintainers deliberately adopt upstream UI changes. Review generated diffs, attribution, and source-ledger records before committing them. It is never an engine candidate acceptance gate and is not run by `check`, `prepack`, or release publication.
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
Presentation acceptance is the reader comparing `a1 pi` with pinned Pi. `node scripts/check-pinned-pi-source-ledger.mjs` validates accepted provenance; its `--engine-only` mode validates the ownership partition without comparing private upstream source.
|
|
71
71
|
|
|
72
72
|
## Preview publication
|
|
73
73
|
|
|
74
74
|
A preview candidate must use a unique `-dev.N` version and exact manually accepted bytes. Publication packs once, binds evidence to source commit/version/integrity, runs applicable non-desktop gates, publishes under npm `next`, and verifies registry identity. It must keep `latest` unchanged and record physical/cross-platform certification as deferred.
|
|
75
75
|
|
|
76
|
-
The GitHub trusted-publishing workflow is `.github/workflows/publish
|
|
76
|
+
The GitHub trusted-publishing workflow is `.github/workflows/npm-publish.yml`. Stable publication remains a separate release process from a clean tagged `master` commit after all mandatory platform gates pass.
|
|
77
77
|
|
|
78
78
|
## A1 state paths
|
|
79
79
|
|
|
@@ -22,10 +22,18 @@ Need more coverage for a risky preview? Dispatch the candidate with `full: true`
|
|
|
22
22
|
|
|
23
23
|
## Publish a preview to npm `next`
|
|
24
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
|
+
|
|
25
33
|
1. Make sure `Development validation required` is green on the `develop` tip.
|
|
26
34
|
2. Dispatch **Build npm next candidate** with the exact commit and `confirm_candidate=build-uncertified-next-candidate`.
|
|
27
35
|
3. Check the resulting `candidate-evidence.json`: gates passed, package integrity matches.
|
|
28
|
-
4. Approve the `npm-
|
|
36
|
+
4. Approve the `npm-publish` environment and dispatch **Publish npm next** with the candidate run id.
|
|
29
37
|
|
|
30
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.
|
|
31
39
|
|
|
@@ -35,7 +43,7 @@ The publisher uploads the exact validated tarball — it never rebuilds. Candida
|
|
|
35
43
|
2. Dispatch **Build stable candidate** — it packs once and validates the same bytes on all three platforms.
|
|
36
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.
|
|
37
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.
|
|
38
|
-
5. Merge that exact commit to `master`, tag it `v<version>`, and dispatch **Publish npm stable**, then approve `npm-
|
|
46
|
+
5. Merge that exact commit to `master`, tag it `v<version>`, and dispatch **Publish npm stable**, then approve `npm-publish`.
|
|
39
47
|
|
|
40
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.
|
|
41
49
|
|
|
@@ -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.9",
|
|
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",
|
|
@@ -21,32 +21,33 @@
|
|
|
21
21
|
"clean": "node scripts/clean.mjs",
|
|
22
22
|
"build": "npm run clean && tsc -p tsconfig.build.json && node scripts/build-process-guardian.mjs",
|
|
23
23
|
"build:process-guardian": "node scripts/build-process-guardian.mjs",
|
|
24
|
-
"typecheck": "
|
|
24
|
+
"typecheck": "tsgo -p tsconfig.json --noEmit",
|
|
25
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",
|
|
26
26
|
"check:customization-ready": "node scripts/check-owned-ui-customization-prerequisites.mjs",
|
|
27
27
|
"check:deprecated": "node scripts/check-deprecated-dependencies.mjs",
|
|
28
28
|
"branches:prune": "node scripts/prune-merged-branches.mjs",
|
|
29
29
|
"check": "npm run test:fast",
|
|
30
30
|
"publish:next": "tsx scripts/publish-next.ts",
|
|
31
|
+
"release:next": "node scripts/release-next.mjs",
|
|
31
32
|
"test": "npm run test:fast",
|
|
32
33
|
"test:fast": "node scripts/run-validation-tier.mjs typecheck fast",
|
|
33
34
|
"test:scope": "node scripts/run-validation-tier.mjs",
|
|
34
35
|
"test:full": "node scripts/run-validation-tier.mjs full-release",
|
|
35
36
|
"test:release": "node scripts/run-release-gates.mjs",
|
|
36
37
|
"test:terminal-host": "node scripts/run-terminal-host-probe.mjs",
|
|
37
|
-
"test:pi-terminal-parity": "npm run build --silent && node scripts/run-pi-terminal-parity.mjs",
|
|
38
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",
|
|
39
39
|
"report:pi-engine-conformance": "npm run build --silent && node scripts/run-pi-engine-conformance.mjs",
|
|
40
40
|
"sync:pi-ui": "npm run update:pi-component-parity && npm run update:pi-event-frame-parity && npm run update:pi-settings-metadata",
|
|
41
41
|
"update:pi-component-parity": "tsx scripts/update-pi-component-parity.ts",
|
|
42
42
|
"update:pi-event-frame-parity": "tsx scripts/update-pi-event-frame-parity.ts",
|
|
43
43
|
"proof:terminal-host": "node scripts/run-terminal-host.mjs",
|
|
44
|
-
"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",
|
|
45
47
|
"prepack": "node scripts/prepack-gate.mjs",
|
|
46
48
|
"prepublishOnly": "npm run test:release",
|
|
47
49
|
"prepare": "npm run build",
|
|
48
|
-
"update:pi-settings-metadata": "node scripts/update-pi-settings-metadata.mjs"
|
|
49
|
-
"clean:worktrees": "node scripts/clean-worktrees.mjs"
|
|
50
|
+
"update:pi-settings-metadata": "node scripts/update-pi-settings-metadata.mjs"
|
|
50
51
|
},
|
|
51
52
|
"dependencies": {
|
|
52
53
|
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
@@ -59,6 +60,7 @@
|
|
|
59
60
|
"@types/cross-spawn": "6.0.6",
|
|
60
61
|
"@types/node": "24.2.1",
|
|
61
62
|
"@types/semver": "7.8.0",
|
|
63
|
+
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
62
64
|
"@xterm/headless": "6.0.0",
|
|
63
65
|
"node-pty": "1.1.0",
|
|
64
66
|
"tsx": "4.20.3",
|
|
Binary file
|
|
@@ -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,72 +0,0 @@
|
|
|
1
|
-
import { access, readFile } from "node:fs/promises";
|
|
2
|
-
import { 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(";").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 [...new Set(source.split(";").filter(Boolean).flatMap(value => {
|
|
60
|
-
const extension = value.startsWith(".") ? value : `.${value}`;
|
|
61
|
-
return [extension, extension.toLowerCase()];
|
|
62
|
-
}))];
|
|
63
|
-
}
|
|
64
|
-
async function isFile(path) {
|
|
65
|
-
try {
|
|
66
|
-
await access(path);
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { NativeProcessIdentity, TransparentTerminalLaunchProfile, TransparentTerminalLifecycleOutcome } from "../lifecycle/index.js";
|
|
2
|
-
export type TransparentStopReason = "owner-disconnect" | "user-request" | "update";
|
|
3
|
-
export interface TransparentChildHandle {
|
|
4
|
-
readonly processIdentity: NativeProcessIdentity;
|
|
5
|
-
readonly outcome: Promise<TransparentTerminalLifecycleOutcome>;
|
|
6
|
-
stop(reason: TransparentStopReason): Promise<TransparentTerminalLifecycleOutcome>;
|
|
7
|
-
}
|
|
8
|
-
export interface TransparentNativeLauncher {
|
|
9
|
-
launch(profile: TransparentTerminalLaunchProfile): Promise<TransparentChildHandle>;
|
|
10
|
-
}
|
|
11
|
-
export interface ForegroundBrokerRequest {
|
|
12
|
-
readonly profile: TransparentTerminalLaunchProfile;
|
|
13
|
-
readonly stopRequested?: Promise<TransparentStopReason>;
|
|
14
|
-
}
|
|
15
|
-
export interface ForegroundBrokerResult {
|
|
16
|
-
readonly processIdentity: NativeProcessIdentity | null;
|
|
17
|
-
readonly outcome: TransparentTerminalLifecycleOutcome;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Waits for one directly attached child. Launch-instance registration and
|
|
21
|
-
* process-tree containment belong to the outer profile-neutral guardian.
|
|
22
|
-
* Ordinary terminal data never enters this object.
|
|
23
|
-
*/
|
|
24
|
-
export declare function runForegroundBroker(request: ForegroundBrokerRequest, launcher: TransparentNativeLauncher): Promise<ForegroundBrokerResult>;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { assertNativeProcessIdentity, assertTransparentTerminalLaunchProfile } from "../lifecycle/index.js";
|
|
2
|
-
/**
|
|
3
|
-
* Waits for one directly attached child. Launch-instance registration and
|
|
4
|
-
* process-tree containment belong to the outer profile-neutral guardian.
|
|
5
|
-
* Ordinary terminal data never enters this object.
|
|
6
|
-
*/
|
|
7
|
-
export async function runForegroundBroker(request, launcher) {
|
|
8
|
-
assertTransparentTerminalLaunchProfile(request.profile);
|
|
9
|
-
let handle;
|
|
10
|
-
try {
|
|
11
|
-
handle = await launcher.launch(request.profile);
|
|
12
|
-
assertNativeProcessIdentity(handle.processIdentity);
|
|
13
|
-
}
|
|
14
|
-
catch (error) {
|
|
15
|
-
return {
|
|
16
|
-
processIdentity: null,
|
|
17
|
-
outcome: { kind: "spawn-error", message: errorMessage(error), code: errorCode(error) },
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
const outcome = request.stopRequested
|
|
21
|
-
? await Promise.race([handle.outcome, request.stopRequested.then(async (reason) => await handle.stop(reason))])
|
|
22
|
-
: await handle.outcome;
|
|
23
|
-
return { processIdentity: handle.processIdentity, outcome };
|
|
24
|
-
}
|
|
25
|
-
function errorMessage(error) {
|
|
26
|
-
return error instanceof Error ? error.message : String(error);
|
|
27
|
-
}
|
|
28
|
-
function errorCode(error) {
|
|
29
|
-
return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : null;
|
|
30
|
-
}
|
|
@@ -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,87 +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 { runForegroundBroker } from "./foreground-broker.js";
|
|
6
|
-
import { createPlatformTransparentLauncher } from "./native-launcher.js";
|
|
7
|
-
export async function runTransparentForeground(options) {
|
|
8
|
-
const environment = { ...(options.environment ?? process.env) };
|
|
9
|
-
const profileId = options.profileId ?? environment.A1_LAUNCH_PROFILE ?? "a1";
|
|
10
|
-
assertLaunchProfileId(profileId);
|
|
11
|
-
const profile = await launchProfile(environment, options, profileId);
|
|
12
|
-
const stop = stopSignal();
|
|
13
|
-
try {
|
|
14
|
-
const result = await runForegroundBroker({ profile, stopRequested: stop.requested }, createPlatformTransparentLauncher());
|
|
15
|
-
if (result.outcome.kind === "exited")
|
|
16
|
-
return result.outcome.exitCode;
|
|
17
|
-
if (result.outcome.kind === "signaled")
|
|
18
|
-
return 1;
|
|
19
|
-
if (result.outcome.kind === "stopped" || result.outcome.kind === "detached")
|
|
20
|
-
return 0;
|
|
21
|
-
throw Object.assign(new Error(result.outcome.message), { code: result.outcome.code ?? undefined });
|
|
22
|
-
}
|
|
23
|
-
finally {
|
|
24
|
-
stop.dispose();
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
async function launchProfile(environment, options, profileId) {
|
|
28
|
-
const executable = options.executable;
|
|
29
|
-
const arguments_ = options.arguments;
|
|
30
|
-
const cwd = await realpath(options.cwd ?? process.cwd());
|
|
31
|
-
const terminalType = environment.TERM?.trim() || "xterm-256color";
|
|
32
|
-
return {
|
|
33
|
-
id: `${profileId}-transparent-${randomUUID()}`,
|
|
34
|
-
terminalCapability: "transparent",
|
|
35
|
-
executable,
|
|
36
|
-
arguments: arguments_,
|
|
37
|
-
cwd,
|
|
38
|
-
environment: selectedChildEnvironment(environment),
|
|
39
|
-
terminalType,
|
|
40
|
-
dimensions: terminalDimensions(environment),
|
|
41
|
-
ownerDisconnect: "stop",
|
|
42
|
-
recovery: "none",
|
|
43
|
-
surface: "none",
|
|
44
|
-
visualReconnection: "none",
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
function selectedChildEnvironment(environment) {
|
|
48
|
-
const selected = {};
|
|
49
|
-
for (const [name, value] of Object.entries(environment)) {
|
|
50
|
-
if (value !== undefined && !name.startsWith("A1_"))
|
|
51
|
-
selected[name] = value;
|
|
52
|
-
}
|
|
53
|
-
return selected;
|
|
54
|
-
}
|
|
55
|
-
function terminalDimensions(environment) {
|
|
56
|
-
const columns = positiveInteger(environment.COLUMNS) ?? 80;
|
|
57
|
-
const rows = positiveInteger(environment.LINES) ?? 24;
|
|
58
|
-
return { columns: Math.min(500, Math.max(2, columns)), rows: Math.min(300, Math.max(1, rows)) };
|
|
59
|
-
}
|
|
60
|
-
function positiveInteger(value) {
|
|
61
|
-
const parsed = Number(value);
|
|
62
|
-
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
|
|
63
|
-
}
|
|
64
|
-
function stopSignal() {
|
|
65
|
-
let resolveStop;
|
|
66
|
-
let settled = false;
|
|
67
|
-
const requested = new Promise(resolve => { resolveStop = resolve; });
|
|
68
|
-
const request = (reason) => {
|
|
69
|
-
if (settled)
|
|
70
|
-
return;
|
|
71
|
-
settled = true;
|
|
72
|
-
resolveStop(reason);
|
|
73
|
-
};
|
|
74
|
-
const onSigterm = () => request("user-request");
|
|
75
|
-
// The attached child shares foreground signal delivery. Keep the broker alive
|
|
76
|
-
// without translating or forwarding Ctrl+C.
|
|
77
|
-
const onSigint = () => undefined;
|
|
78
|
-
process.once("SIGTERM", onSigterm);
|
|
79
|
-
process.on("SIGINT", onSigint);
|
|
80
|
-
return {
|
|
81
|
-
requested,
|
|
82
|
-
dispose() {
|
|
83
|
-
process.off("SIGTERM", onSigterm);
|
|
84
|
-
process.off("SIGINT", onSigint);
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { type ChildProcess, type SpawnOptions } from "node:child_process";
|
|
2
|
-
import type { TransparentTerminalLaunchProfile } from "../lifecycle/index.js";
|
|
3
|
-
import type { TransparentChildHandle, TransparentNativeLauncher } from "./foreground-broker.js";
|
|
4
|
-
export interface NativeSpawnAdapter {
|
|
5
|
-
resolveCommand(profile: TransparentTerminalLaunchProfile): Promise<{
|
|
6
|
-
readonly executable: string;
|
|
7
|
-
readonly arguments: readonly string[];
|
|
8
|
-
}>;
|
|
9
|
-
spawn(executable: string, arguments_: readonly string[], options: SpawnOptions): ChildProcess;
|
|
10
|
-
observeStartIdentity(child: ChildProcess): Promise<string>;
|
|
11
|
-
identityMatches(child: ChildProcess, expectedStartIdentity: string): Promise<boolean>;
|
|
12
|
-
stop(child: ChildProcess, force: boolean): Promise<void>;
|
|
13
|
-
waitForExit(child: ChildProcess, timeoutMs: number): Promise<boolean>;
|
|
14
|
-
}
|
|
15
|
-
export declare function createPlatformTransparentLauncher(platform?: NodeJS.Platform, adapter?: NativeSpawnAdapter): TransparentNativeLauncher;
|
|
16
|
-
export declare class WindowsTransparentLauncher implements TransparentNativeLauncher {
|
|
17
|
-
private readonly adapter;
|
|
18
|
-
constructor(adapter?: NativeSpawnAdapter);
|
|
19
|
-
launch(profile: TransparentTerminalLaunchProfile): Promise<TransparentChildHandle>;
|
|
20
|
-
}
|
|
21
|
-
export declare class UnixTransparentLauncher implements TransparentNativeLauncher {
|
|
22
|
-
private readonly adapter;
|
|
23
|
-
constructor(adapter?: NativeSpawnAdapter);
|
|
24
|
-
launch(profile: TransparentTerminalLaunchProfile): Promise<TransparentChildHandle>;
|
|
25
|
-
}
|