@alipay/alipay-aipay 1.7.7 → 1.7.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.
Files changed (41) hide show
  1. package/README.md +2 -2
  2. package/package.json +3 -3
  3. package/skill/VERSION +1 -1
  4. package/skill/references/normal/json/policy-rules.json +1 -1
  5. package/skill/references/normal/scripts/open_official_url.sh +4 -2
  6. package/skill/references/normal/scripts/render_customer_message.mjs +4 -2
  7. package/skill/references/normal/scripts/self_update.mjs +6 -7
  8. package/skill/references/normal/scripts/windows/bootstrap-node.ps1 +0 -2
  9. package/skill-pre/VERSION +1 -1
  10. package/skill-pre/references/integration/flow.md +3 -3
  11. package/skill-pre/references/integration/modules/checklist.md +2 -2
  12. package/skill-pre/references/integration/modules/sandbox/a2m-sandbox-test.md +2 -2
  13. package/skill-pre/references/integration/modules/sandbox/sandbox-setup-guide.md +7 -7
  14. package/skill-pre/references/integration/modules/scripts/sandbox_config.sh +4 -10
  15. package/skill-pre/references/normal/conversation-telemetry.md +1 -1
  16. package/skill-pre/references/normal/full-process-routing.md +2 -1
  17. package/skill-pre/references/normal/scripts/runtime.mjs +6 -6
  18. package/skill-pre/references/normal/scripts/self_update.mjs +6 -7
  19. package/skill-pre/references/normal/scripts/telemetry.mjs +4 -4
  20. package/skill-pre/references/normal/scripts/windows/access-native.mjs +3 -0
  21. package/skill-pre/references/normal/scripts/windows/architecture.mjs +2 -30
  22. package/skill-pre/references/normal/scripts/windows/bootstrap-node.ps1 +1 -3
  23. package/skill-pre/references/normal/scripts/windows/git-discovery.mjs +7 -3
  24. package/skill-pre/references/normal/scripts/windows/git-install.mjs +3 -4
  25. package/skill-pre/references/normal/scripts/windows/host.mjs +31 -0
  26. package/skill-pre/references/normal/scripts/windows/preflight.mjs +27 -18
  27. package/skill-pre/references/normal/scripts/windows/prepare.mjs +3 -3
  28. package/skill-pre/references/normal/scripts/windows/python-execution.mjs +6 -6
  29. package/skill-pre/references/normal/scripts/windows/python-runtime.mjs +4 -4
  30. package/skill-pre/references/normal/scripts/windows/routing.mjs +11 -7
  31. package/skill-pre/references/normal/scripts/windows/shell_helpers.sh +2 -2
  32. package/skill-pre/references/normal/scripts/windows/winget.mjs +1 -1
  33. package/skill-pre/references/normal/windows-environment.md +15 -15
  34. package/skill-pre/references/onboarding/modules/authorization.md +1 -1
  35. package/skill-pre/references/onboarding/modules/scripts/app.sh +5 -5
  36. package/skill-pre/references/onboarding/modules/scripts/ar_sign_apply.sh +1 -1
  37. package/skill-pre/references/onboarding/modules/scripts/auth.sh +5 -5
  38. package/skill-pre/references/runtime-entry.md +1 -1
  39. package/skill-pre/references/normal/scripts/windows/acl-native.mjs +0 -3
  40. package/skill-pre/references/normal/scripts/windows/acl.mjs +0 -41
  41. package/skill-pre/references/normal/scripts/windows/architecture.ps1 +0 -55
@@ -1,14 +1,13 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import crypto from 'node:crypto';
4
- import {policy,run,fail} from './preflight.mjs';
4
+ import {loadPolicy,run,fail} from './preflight.mjs';
5
5
  import {findGitBash} from './git-discovery.mjs';
6
- import {powershell,psEnv} from './acl.mjs';
6
+ import {powershell,psEnv} from './host.mjs';
7
7
 
8
8
  export function installGitOfficial({env=process.env,execute=run,exists=fs.existsSync,
9
9
  read=fs.readFileSync,remove=fs.rmSync,discover=findGitBash,root,
10
- signatureCommand=powershell,beforeAttempt=()=>{}}={}) {
11
- const asset=policy.git;
10
+ signatureCommand=powershell,beforeAttempt=()=>{},asset=loadPolicy('Git Bash').git}={}) {
12
11
  if(!root || !env.LOCALAPPDATA || !path.win32.isAbsolute(env.LOCALAPPDATA) ||
13
12
  !/^https:\/\/github\.com\/git-for-windows\/git\/releases\/download\/v[0-9.]+\.windows\.[0-9]+\/Git-[0-9.]+-64-bit\.exe$/u.test(asset.url) ||
14
13
  !/^[a-f0-9]{64}$/u.test(asset.sha256)) fail('GIT_ASSET_INVALID','Git Bash');
@@ -0,0 +1,31 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import {spawnSync} from 'node:child_process';
5
+
6
+ export function fail(reason, dependency = '') { throw Object.assign(new Error(reason), {reason, dependency}); }
7
+ export function run(command,args,options={}) {
8
+ return spawnSync(command,args,{encoding:'utf8',windowsHide:true,shell:false,timeout:15000,maxBuffer:4*1024*1024,...options});
9
+ }
10
+ export function powershell() {
11
+ const root=process.env.SystemRoot || 'C:\\Windows';
12
+ const command=path.join(root,'System32/WindowsPowerShell/v1.0/powershell.exe');
13
+ if(!fs.existsSync(command)) fail('DEPENDENCY_MISSING','PowerShell');
14
+ return command;
15
+ }
16
+ export function psEnv(env=process.env) {
17
+ return Object.fromEntries(Object.entries(env).filter(([k])=>k.toLowerCase()!=='psmodulepath'));
18
+ }
19
+ export function checkWritable(target) {
20
+ let stat;
21
+ try { stat=fs.lstatSync(target); } catch { fail('STORAGE_FAILED','missing target'); }
22
+ if(stat.isSymbolicLink()||path.resolve(target)===path.parse(target).root)fail('STORAGE_FAILED','unsafe target');
23
+ if(stat.isDirectory()) {
24
+ const probe=path.join(target,'.aipay-probe-'+crypto.randomUUID());
25
+ try { fs.writeFileSync(probe,'',{flag:'wx'});fs.unlinkSync(probe); }
26
+ catch { try { fs.unlinkSync(probe); } catch {} fail('STORAGE_FAILED','directory not writable'); }
27
+ } else if(stat.isFile()) {
28
+ try { fs.accessSync(target,fs.constants.W_OK); }
29
+ catch { fail('STORAGE_FAILED','file not writable'); }
30
+ } else fail('STORAGE_FAILED','unsupported target');
31
+ }
@@ -5,19 +5,30 @@ import crypto from 'node:crypto';
5
5
  import {fileURLToPath} from 'node:url';
6
6
 
7
7
  export const directory = path.dirname(fileURLToPath(import.meta.url));
8
- export const policy = JSON.parse(fs.readFileSync(path.join(directory,'policy.json'),'utf8'));
8
+ export function loadPolicy(dependency, read = fs.readFileSync) {
9
+ try {
10
+ const policy = JSON.parse(read(path.join(directory,'policy.json'),'utf8'));
11
+ const asset = dependency === 'Git Bash' ? policy?.git : policy?.jq;
12
+ if (!asset || typeof asset !== 'object' || typeof asset.url !== 'string' ||
13
+ typeof asset.sha256 !== 'string' || (dependency === 'Git Bash' && typeof asset.version !== 'string')) {
14
+ throw new Error('incomplete policy');
15
+ }
16
+ return policy;
17
+ }
18
+ catch { fail('POLICY_UNAVAILABLE',dependency); }
19
+ }
9
20
  const slash = p => p.replaceAll('\\','/');
10
21
  let invocationContext;
11
- import {fail, run, powershell, psEnv, protect} from './acl.mjs';
12
- export {fail, run, protect} from './acl.mjs';
22
+ import {fail, run, checkWritable} from './host.mjs';
23
+ export {fail, run, checkWritable} from './host.mjs';
13
24
  import {telemetryStoragePaths} from '../telemetry.mjs';
14
25
  import {findGitBash} from './git-discovery.mjs';
15
- import {requireNativeX64} from './architecture.mjs';
26
+ import {requireWindowsX64Node} from './architecture.mjs';
16
27
 
17
28
  export function stateRoot() {
18
29
  const root=path.join(process.env.LOCALAPPDATA || path.join(os.homedir(),'AppData/Local'),'alipay-aipay-pre/windows');
19
30
  if(fs.existsSync(root)&&fs.lstatSync(root).isSymbolicLink()) fail('STORAGE_FAILED');
20
- fs.mkdirSync(root,{recursive:true}); protect(root); return root;
31
+ fs.mkdirSync(root,{recursive:true}); return root;
21
32
  }
22
33
  export function readState(root) {
23
34
  const file=path.join(root,'environment.json');
@@ -69,9 +80,8 @@ export function locateJq() {
69
80
  }
70
81
  export function locate() {
71
82
  if(process.platform!=='win32') fail('WINDOWS_ADAPTER_REQUIRES_WINDOWS');
72
- if(Number(process.versions.node.split('.')[0])<policy.nodeMinimumMajor) fail('DEPENDENCY_VERSION','Node');
73
- // Only a native x64 execution mapping has been implemented. No OS build allowlist.
74
- requireNativeX64();
83
+ if(Number(process.versions.node.split('.')[0])<18) fail('DEPENDENCY_VERSION','Node');
84
+ requireWindowsX64Node();
75
85
  const foundGit=findGitBash();
76
86
  if(!foundGit) fail('DEPENDENCY_MISSING','Git Bash');
77
87
  const {bash,git}=foundGit;
@@ -93,8 +103,7 @@ export function locate() {
93
103
  env.ALIPAY_WINDOWS_CREDENTIALS_DIR=slash(path.join(os.homedir(),'.alipay-cli/credentials'));
94
104
  env.ALIPAY_WINDOWS_LOGS_DIR=slash(path.join(process.env.APPDATA||path.join(os.homedir(),'AppData/Roaming'),'alipay-cli/data/logs'));
95
105
  env.ALIPAY_WINDOWS_CLI_ENTRY=slash(path.join(directory,'bin/alipay-cli'));
96
- env.ALIPAY_WINDOWS_ACL_SCRIPT=slash(path.join(directory,'acl-native.mjs'));
97
- env.ALIPAY_WINDOWS_POWERSHELL=slash(powershell());
106
+ env.ALIPAY_WINDOWS_ACCESS_SCRIPT=slash(path.join(directory,'access-native.mjs'));
98
107
  env.NODE_BINARY='node';
99
108
  return {bash,git,cli,jq,env};
100
109
  }
@@ -114,12 +123,8 @@ export function privateChildEnvironment(env, temporary) {
114
123
 
115
124
  export function inspectLocal(root) {
116
125
  const context=locate();
117
- const temporary=path.join(root,'tmp');fs.mkdirSync(temporary,{recursive:true});protect(temporary);
126
+ const temporary=path.join(root,'tmp');fs.mkdirSync(temporary,{recursive:true});
118
127
  context.env = privateChildEnvironment(context.env, temporary);
119
- const home=os.homedir();
120
- for(const p of [path.join(home,'.alipay-cli/credentials'),path.join(process.env.APPDATA||path.join(home,'AppData/Roaming'),'alipay-cli/data/logs')]) {
121
- fs.mkdirSync(p,{recursive:true});protect(p);writable(p);
122
- }
123
128
  for(const [exe,args,name] of [[context.cli,['version'],'alipay-cli'],[context.jq,['-n','-e','--arg','v','猫 $HOME "x"','$v == "猫 $HOME \\"x\\""'],'jq']]) {
124
129
  // jq round-trip is checked in the Bash probe below; this native check verifies launch/version.
125
130
  const r=run(exe,name==='jq'?['--version']:args,{env:context.env});
@@ -130,6 +135,11 @@ export function inspectLocal(root) {
130
135
  writable(temporary);
131
136
  return context;
132
137
  }
138
+ export function checkCredentialsWritable() {
139
+ const target=path.join(os.homedir(),'.alipay-cli/credentials');
140
+ fs.mkdirSync(target,{recursive:true});
141
+ checkWritable(target);
142
+ }
133
143
  export function ensureReady({retry=false}={}) {
134
144
  // Reuse only within this Node invocation. Each new process performs full checks;
135
145
  // persisted failures never prevent a read-only check after the user fixes a dependency.
@@ -172,14 +182,13 @@ export function resultError(error) {
172
182
  (error.reason === 'EXECUTION_FAILED' && error.dependency === 'alipay-cli');
173
183
  const reason=error.reason || error.code || 'CHECK_FAILED';
174
184
  const recovery=reason==='NO_COMPATIBLE_EXECUTION_MAPPING'?'USE_SUPPORTED_PLATFORM_OR_SITE':
175
- reason==='ARCHITECTURE_CHECK_FAILED'?'RESTORE_SYSTEM_ARCHITECTURE_PROBE_THEN_RECHECK':
176
185
  (reason==='DEPENDENCY_MISSING' && error.dependency==='curl')?'RESTORE_WINDOWS_CURL_THEN_RECHECK':
177
186
  needsSetup?'PREPARE_MISSING_DEPENDENCIES':
178
187
  reason==='DOWNLOAD_FAILED'?'RESTORE_NETWORK_THEN_REPAIR':
179
188
  ['INTEGRITY_FAILED','SIGNATURE_FAILED'].includes(reason)?'VERIFY_OFFICIAL_ASSET_BEFORE_REPAIR':
180
189
  reason==='RETRY_BUDGET_EXHAUSTED'?'MANUAL_REPAIR_THEN_RECHECK':
181
190
  reason==='USER_SCOPE_REQUIRED'?'USE_NON_ELEVATED_SESSION':
182
- ['ACL_FAILED','STORAGE_FAILED','ENVIRONMENT_BUSY','ENVIRONMENT_STATE_INVALID'].includes(reason)?'REPAIR_LOCAL_ENVIRONMENT_THEN_RECHECK':
191
+ ['STORAGE_FAILED','ENVIRONMENT_BUSY','ENVIRONMENT_STATE_INVALID'].includes(reason)?'REPAIR_LOCAL_ENVIRONMENT_THEN_RECHECK':
183
192
  'INSPECT_FAILURE_THEN_RECHECK';
184
193
  return {schemaVersion:1,status:needsSetup?'NEEDS_SETUP':'BLOCKED',reason,dependency:error.dependency||'',recovery,validationCoverage:'NOT_VERIFIED'};
185
194
  }
@@ -194,7 +203,7 @@ if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.ur
194
203
  if(process.argv[2]==='shell-env') {
195
204
  const quote=s=>"'"+s.replaceAll("'","'\\''")+"'";
196
205
  // Only explicit adapter-owned values. Business context and credentials never appear here.
197
- for(const k of ['PATH','TEMP','TMP','TMPDIR','ALIPAY_AIPAY_TELEMETRY_DIR','ALIPAY_WINDOWS_NODE','ALIPAY_WINDOWS_JQ','ALIPAY_WINDOWS_CLI','ALIPAY_WINDOWS_CREDENTIALS_DIR','ALIPAY_WINDOWS_LOGS_DIR','ALIPAY_WINDOWS_CLI_ENTRY','ALIPAY_WINDOWS_CLI_RUNNER','ALIPAY_WINDOWS_ACL_SCRIPT','ALIPAY_WINDOWS_POWERSHELL','NODE_BINARY','MSYS_NO_PATHCONV','MSYS2_ARG_CONV_EXCL']) {
206
+ for(const k of ['PATH','TEMP','TMP','TMPDIR','ALIPAY_AIPAY_TELEMETRY_DIR','ALIPAY_WINDOWS_NODE','ALIPAY_WINDOWS_JQ','ALIPAY_WINDOWS_CLI','ALIPAY_WINDOWS_CREDENTIALS_DIR','ALIPAY_WINDOWS_LOGS_DIR','ALIPAY_WINDOWS_CLI_ENTRY','ALIPAY_WINDOWS_CLI_RUNNER','ALIPAY_WINDOWS_ACCESS_SCRIPT','NODE_BINARY','MSYS_NO_PATHCONV','MSYS2_ARG_CONV_EXCL']) {
198
207
  const v=k==='PATH'?c.env[k].split(';').map(p=>slash(p).replace(/^([a-zA-Z]):/,(_,d)=>'/'+d.toLowerCase())).join(':'):c.env[k];
199
208
  console.log('export '+k+'='+quote(v));
200
209
  }
@@ -6,8 +6,8 @@ import {refreshWindowsCli} from './cli-install.mjs';
6
6
  import {findGitBash} from './git-discovery.mjs';
7
7
  import {findWinget} from './winget.mjs';
8
8
  import {installGitOfficial} from './git-install.mjs';
9
- import {powershell} from './acl.mjs';
10
- import {policy,run,stateRoot,readState,saveState,withLock,ensureReady,locateJq,fail,report} from './preflight.mjs';
9
+ import {powershell} from './host.mjs';
10
+ import {loadPolicy,run,stateRoot,readState,saveState,withLock,ensureReady,locateJq,fail,report} from './preflight.mjs';
11
11
 
12
12
  export function claimPreparation(state,stage,{source='official',repair=false,authorized=false}={}) {
13
13
  if(!authorized) fail('PREPARATION_AUTHORIZATION_REQUIRED');
@@ -65,7 +65,7 @@ export function prepareDependencies(state,{authorized=false,repair=false,save=()
65
65
  gitReady=()=>findGitBash(),jqReady=()=>locateJq(),
66
66
  cliReady=()=>{const cli=path.join(os.homedir(),'.local/bin/alipay-cli.exe');return fs.existsSync(cli)&&run(cli,['version']).status===0;},
67
67
  installWinget=installGitWithWinget,installOfficial=installGitOfficial,
68
- prepareJq=(beforeAttempt)=>{requireAssetDownloader();beforeAttempt();installAsset('jq',policy.jq);},
68
+ prepareJq=(beforeAttempt)=>{const asset=loadPolicy('jq').jq;requireAssetDownloader();beforeAttempt();installAsset('jq',asset);},
69
69
  prepareCli=(beforeAttempt)=>{powershell();beforeAttempt();if(refreshWindowsCli()!=='READY')fail('CLI_INSTALL_FAILED','alipay-cli');}
70
70
  }={}) {
71
71
  if(!authorized) fail('PREPARATION_AUTHORIZATION_REQUIRED');
@@ -2,10 +2,10 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import {spawnSync} from 'node:child_process';
5
- import {protect} from './acl.mjs';
5
+ import {checkWritable} from './host.mjs';
6
6
 
7
7
  // Pin tempfile itself: environment variables alone allow Python to fall back to
8
- // another directory if the protected directory becomes unavailable.
8
+ // another directory if the writable directory becomes unavailable.
9
9
  const bootstrap = [
10
10
  'import os, runpy, sys, tempfile',
11
11
  'tempfile.tempdir = sys.argv[1]',
@@ -16,14 +16,14 @@ const bootstrap = [
16
16
 
17
17
  // The caller has already validated the single registered A2M script path.
18
18
  export function runWindowsPython(python, script, args, {
19
- env = process.env, tempRoot = os.tmpdir(), protectDirectory = protect, spawn = spawnSync,
19
+ env = process.env, tempRoot = os.tmpdir(), checkDirectory = checkWritable, spawn = spawnSync,
20
20
  cleanup = directory => fs.rmSync(directory, {recursive: true, force: true})
21
21
  } = {}) {
22
22
  const temporary = fs.mkdtempSync(path.join(path.resolve(tempRoot), 'alipay-aipay-a2m-'));
23
23
  try {
24
- // Empty until NTFS DACL setup and readback have both succeeded. Children
25
- // inherit the restricted ACL, including files written directly by curl.
26
- protectDirectory(temporary);
24
+ // Confirm the fresh directory is writable before launching Python. Its
25
+ // inherited permissions remain intact for Python and curl output.
26
+ checkDirectory(temporary);
27
27
  const childEnv = Object.fromEntries(Object.entries(env)
28
28
  .filter(([key]) => !['TEMP', 'TMP', 'TMPDIR'].includes(key.toUpperCase())));
29
29
  Object.assign(childEnv, {TEMP: temporary, TMP: temporary, TMPDIR: temporary});
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import {fileURLToPath} from 'node:url';
4
- import {run, fail, protect} from './acl.mjs';
4
+ import {run, fail, checkWritable} from './host.mjs';
5
5
  import {findWinget} from './winget.mjs';
6
6
 
7
7
  const script = fileURLToPath(new URL('../../../integration/modules/scripts/local_402_sandbox_pay.py', import.meta.url));
@@ -34,11 +34,11 @@ export function findWindowsPython({env = process.env, execute = run, exists = fs
34
34
  // A separate budget: preparing Python for A2M never consumes or resets the
35
35
  // Git/jq/CLI environment repair state, or any business/telemetry state.
36
36
  export function prepareWindowsPython({authorized = false, repair = false, find = findWindowsPython,
37
- execute = run, env = process.env, root, protectRoot = protect, lock, exists = fs.existsSync} = {}) {
37
+ execute = run, env = process.env, root, checkRoot = checkWritable, lock, exists = fs.existsSync} = {}) {
38
38
  const existing = find({env});
39
39
  if (existing) return existing;
40
40
  if (!authorized) fail('DEPENDENCY_MISSING', 'Python 3');
41
- protectRoot(root);
41
+ checkRoot(root);
42
42
  return lock(root, () => {
43
43
  const file = path.join(root, 'python-environment.json');
44
44
  let state = {prepareAttempts: 0, repairAttempts: 0};
@@ -78,7 +78,7 @@ export async function runPythonEnvironment(args) {
78
78
  if (!found) {
79
79
  if (mode === 'check-python' || options['--authorized'] !== 'true') fail('DEPENDENCY_MISSING', 'Python 3');
80
80
  prepareWindowsPython({authorized: true, repair: options['--repair-confirmed'] === 'true',
81
- root: stateRoot(), protectRoot: protect, lock: withLock});
81
+ root: stateRoot(), checkRoot: checkWritable, lock: withLock});
82
82
  }
83
83
  process.stderr.write('ALIPAY_AIPAY_INTERNAL:PYTHON_ENV=READY\n');
84
84
  return 0;
@@ -1,18 +1,20 @@
1
- import {applyReady, report, resultError} from './preflight.mjs';
1
+ import {applyReady, checkCredentialsWritable, report, resultError} from './preflight.mjs';
2
2
  import {prepare} from './prepare.mjs';
3
3
 
4
4
  const intents = ['integration_only', 'onboarding_only', 'full_process'];
5
5
  const prefix = 'ALIPAY_AIPAY_INTERNAL:';
6
6
 
7
- // Only optional-tool failures can relax the code-development gate. Node,
8
- // storage/ACL failures, invalid state and unknown exceptions remain blocking.
7
+ // Optional tool failures do not prevent independent project code development.
9
8
  function codeOnlyFailure(error) {
9
+ if (error.reason === 'SHELL_TRANSPORT_FAILED' && error.dependency !== 'Node') return true;
10
+ if (error.reason === 'NOT_GIT_BASH' && error.dependency === 'ALIPAY_GIT_BASH') return true;
11
+ if (error.dependency === 'Node') return false;
10
12
  const tools = ['Git Bash', 'git', 'jq', 'alipay-cli', 'cli', 'curl', 'PowerShell'];
11
13
  if (!tools.includes(error.dependency)) return false;
12
14
  return ['DEPENDENCY_MISSING', 'DEPENDENCY_VERSION', 'EXECUTION_FAILED',
13
15
  'WINGET_UNAVAILABLE', 'GIT_INSTALL_FAILED', 'GIT_VERIFY_FAILED', 'CLI_INSTALL_FAILED',
14
16
  'DOWNLOAD_FAILED', 'INTEGRITY_FAILED', 'SIGNATURE_FAILED', 'RETRY_BUDGET_EXHAUSTED',
15
- 'USER_SCOPE_REQUIRED'].includes(error.reason);
17
+ 'USER_SCOPE_REQUIRED', 'POLICY_UNAVAILABLE'].includes(error.reason);
16
18
  }
17
19
 
18
20
  // This is a routing result, never proof of a successful payment or onboarding.
@@ -36,7 +38,7 @@ export function selectRoute({rootIntent = '', authorized = false, repair = false
36
38
  }
37
39
  report(error);
38
40
  const route = error.reason === 'NO_COMPATIBLE_EXECUTION_MAPPING' ? 'LEGACY' :
39
- rootIntent === 'integration_only' && codeOnlyFailure(error) ? 'CODE_ONLY' : 'BLOCKED';
41
+ rootIntent !== 'onboarding_only' && codeOnlyFailure(error) ? 'CODE_ONLY' : 'BLOCKED';
40
42
  return emit({rootIntent, route, ...resultError(error)});
41
43
  }
42
44
 
@@ -79,12 +81,14 @@ export function consumeRoute(args) {
79
81
 
80
82
  // Native Node messages, telemetry and project inspection do not depend on Bash.
81
83
  // READY is never trusted as proof; the actual business executor checks again.
82
- export function requireBusinessEnvironment() {
84
+ export function requireBusinessEnvironment({credentials = false} = {}) {
83
85
  if (['LEGACY','CODE_ONLY','BLOCKED'].includes(process.env.ALIPAY_AIPAY_WINDOWS_ROUTE)) {
84
86
  const error = Object.assign(new Error('WINDOWS_ENVIRONMENT_UNAVAILABLE'), {reason: 'WINDOWS_ENVIRONMENT_UNAVAILABLE'});
85
87
  throw error;
86
88
  }
87
- return applyReady();
89
+ const context=applyReady();
90
+ if (credentials) checkCredentialsWritable();
91
+ return context;
88
92
  }
89
93
 
90
94
  export function onboardingRoute() {
@@ -5,8 +5,8 @@ _win_exports=$(command node "$(cygpath -m "$_win_dir/preflight.mjs")" shell-env)
5
5
  eval "$_win_exports"
6
6
  unset _win_exports
7
7
 
8
- windows_protect() {
9
- "$ALIPAY_WINDOWS_NODE" "$ALIPAY_WINDOWS_ACL_SCRIPT" "$(cygpath -m "$1")" >/dev/null
8
+ windows_check_access() {
9
+ "$ALIPAY_WINDOWS_NODE" "$ALIPAY_WINDOWS_ACCESS_SCRIPT" "$(cygpath -m "$1")" >/dev/null
10
10
  }
11
11
 
12
12
  git() {
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import {run} from './acl.mjs';
2
+ import {run} from './host.mjs';
3
3
 
4
4
  // App execution aliases can be runnable even when fs.existsSync returns false.
5
5
  // Probe only; installing remains the caller's authorized, bounded operation.
@@ -1,10 +1,10 @@
1
1
  # Windows 兼容环境
2
2
 
3
- 仅真实 Windows 加载本层。目标为 Windows 10/11 原生 Intel/AMD x64,Node >=18(沿用仓库最低版本声明);不通过 OS build 白名单推定能力,不承诺 ARM64、x86 或 WSL。当前移植依据是 Windows 工程验证交付包,尚不能宣称 Windows 10、干净安装或全部真实业务分支已验收。
3
+ 仅真实 Windows 加载本层。当前适配要求实际运行的 Windows x64 Node >=18(沿用仓库最低版本声明);不通过 OS build 或系统原生架构探测推定能力,不承诺 ARM64、x86 或 WSL。当前移植依据是 Windows 工程验证交付包,尚不能宣称 Windows 10、干净安装或全部真实业务分支已验收。
4
4
 
5
- 架构检查同时要求当前 Node 为 x64,并由系统 `IsWow64Process2` 的原生架构事实确认系统为 x64;API 不可用时只读回退到 `Win32_Processor.Architecture`。Node 引导与运行时共用同一探测,不依赖 `PROCESSOR_ARCHITECTURE` 等继承环境变量,不把 ARM64 上的 x64 模拟当原生 x64。两种探测都失败时返回 `ARCHITECTURE_CHECK_FAILED`,保持 BLOCKED;恢复系统探测后可只读复检,不扣安装预算、不自动转站点。明确为不支持的架构才走原 LEGACY。
5
+ 只校验当前 Node 进程报告的 `platform=win32`、`arch=x64`、版本与实际可执行性;Git Bash、jq、CLI 仍各自通过实际启动和功能检查。非 x64 Node 走既有 LEGACY,系统信息查询失败不再额外阻断可运行的工具。Node 引导复用或安装 x64 Node 后也以实际启动验证为准。
6
6
 
7
- Windows 原生 Node 调度明确选定的 Git for Windows Bash,原 Shell 脚本使用 Windows 原生 jq 与 alipay-cli。兼容层只处理依赖、路径、进程和 NTFS 权限;产品映射、CLI/MCP 命令、请求、业务确认、失败恢复和完成条件仍以真实 flow 和原脚本为准。Windows 的 Integration 继续由当前 Agent 串行执行,不启用 Worker。
7
+ Windows 原生 Node 调度明确选定的 Git for Windows Bash,原 Shell 脚本使用 Windows 原生 jq 与 alipay-cli。兼容层处理依赖、路径和进程;产品映射、CLI/MCP 命令、请求、业务确认、失败恢复和完成条件仍以真实 flow 和原脚本为准。Windows 的 Integration 继续由当前 Agent 串行执行,不启用 Worker。
8
8
 
9
9
  ## 启动与分流
10
10
 
@@ -16,11 +16,11 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" windows-entry --root-in
16
16
 
17
17
  用户明确要求安装并通过 Skill 完成签约/开通或完整接入时附加 `--prepare-authorized true`;仅代码开发且没有依赖准备授权时先只读检查。用户明确选择站点时附加 `--site-selected true`。准备授权只覆盖用户级依赖,不包含登录、签约或应用写入。
18
18
 
19
- 退出 0 只表示分流完成。必须取得唯一 `ALIPAY_AIPAY_INTERNAL:WINDOWS_ROUTE=<JSON>`,校验 schemaVersion=1、rootIntent 等于当前根意图、route 为 READY、BLOCKED、LEGACY 或 CODE_ONLY;非零、超时、缺失或冲突均按 BLOCKED 停止。READY 才执行本地快速沙箱和签约;BLOCKED 说明内部 `WINDOWS_ENV_RESULT` 的实际 reason/dependency 及恢复动作,等待环境修复,不能输出站点引导;LEGACY 仅用于不支持的执行映射或用户明确选择站点。后续 Windows runtime 命令附加当前 `--windows-route`;BLOCKED 不进入 bootstrap、业务 flow 或项目写入。修复后必须重新只读复检,取得唯一 READY 才继续原任务。环境故障不关闭遥测,也不改变 SESSION_ID、run_id、计数或会话采集选择。
19
+ 退出 0 只表示分流完成。必须取得唯一 `ALIPAY_AIPAY_INTERNAL:WINDOWS_ROUTE=<JSON>`,校验 schemaVersion=1、rootIntent 等于当前根意图、route 为 READY、BLOCKED、LEGACY 或 CODE_ONLY;非零、超时、缺失或冲突均按 BLOCKED 停止。READY 才执行本地快速沙箱和签约;BLOCKED 说明内部 `WINDOWS_ENV_RESULT` 的实际 reason/dependency 及恢复动作,等待环境修复,不能输出站点引导;LEGACY 仅用于不支持的执行映射或用户明确选择站点。后续 Windows runtime 命令附加当前 `--windows-route`;BLOCKED 不进入 bootstrap、业务 flow 或项目写入。CODE_ONLY 只放行独立代码开发,根意图和诊断原因沿用本次入口事实。修复可选工具后重新只读复检,取得唯一 READY 才恢复快速沙箱和开通动作。环境故障不关闭遥测,也不改变 SESSION_ID、run_id、计数或会话采集选择。
20
20
 
21
- 仅开发入口的例外:`integration_only` 遇到已识别的 Git Bash、jq、CLI 或安装辅助工具缺失/运行失败,以及这些工具的有限安装、下载、哈希/签名校验或预算耗尽失败时,返回 `CODE_ONLY`。失败工具不得继续执行,校验失败的安装包不得运行;保留实际失败诊断,但不等待用户修环境才开始独立代码开发。以唯一 WINDOWS_ROUTE 的 route 决定是否进入开发;WINDOWS_ENV_RESULT 的失败诊断仅表示兼容工具仍不可用,不能将其误读为 CODE_ONLY 也禁止开发。该分流只允许 Integration 的原生 Node 路径,默认 `VERIFY_PENDING`,跳过快速沙箱和依赖沙箱的测试,最终如实列出未完成项,不自动引导站点开通。Node 版本不足、架构探测未知、存储/ACL、状态损坏、回滚失败或未知错误仍为 BLOCKED;项目路径和敏感配置保护要求不变。下文“失败保持 BLOCKED”对该可选工具例外按 CODE_ONLY 处理,签约/完整接入不适用此例外。
21
+ `integration_only/full_process` 遇到已识别的 Git Bash、jq、CLI 或安装辅助工具缺失/运行失败,以及这些工具的有限安装、下载、哈希/签名校验或预算耗尽失败时,返回 `CODE_ONLY`。失败工具不得继续执行,校验失败的安装包不得运行。以唯一 WINDOWS_ROUTE 的 route 决定是否进入开发;WINDOWS_ENV_RESULT 的失败诊断保留实际原因,不能将其误读为业务能力可用。该分流只允许 Integration 的原生 Node 路径,默认 `VERIFY_PENDING`,跳过快速沙箱和依赖沙箱的测试,最终如实列出未完成项,不自动引导站点开通。完整接入在代码部分检查后停在 Onboarding 前,待工具恢复且复检 READY 才恢复。Node 版本不足、实际存储故障、状态损坏、回滚失败或未知错误仍为 BLOCKED;项目路径和生产配置保护要求不变。下文“失败保持 BLOCKED”对上述可选工具例外按 CODE_ONLY 处理。
22
22
 
23
- CODE_ONLY 后用户已自行修好工具时,原样执行上述 `windows-entry --root-intent integration_only` 只读复检;明确授权准备时附加 `--prepare-authorized true`,只有明确修复后重试才再附加 `--repair-confirmed true`,沿用原有分阶段预算。唯一 READY 才恢复快速沙箱能力;仍为 CODE_ONLY 时继续独立开发,不反复安装。转为产品开通或完整接入必须先按新根意图重新执行入口,不把 CODE_ONLY 当成 LEGACY 的站点选择,也不沿用仅开发的能力事实。
23
+ CODE_ONLY 后用户已自行修好环境时,按原根意图执行上述 `windows-entry` 只读复检;明确授权准备时附加 `--prepare-authorized true`,只有明确修复后重试才再附加 `--repair-confirmed true`,沿用原有分阶段预算。唯一 READY 才恢复快速沙箱和开通能力;仍为 CODE_ONLY 时只继续独立代码开发,不反复安装。根意图变化必须按新根意图重新执行入口,不把 CODE_ONLY 当成 LEGACY 的站点选择,也不沿用旧能力事实。
24
24
 
25
25
  ## Windows CLI 安装契约
26
26
 
@@ -32,13 +32,13 @@ stdout 为空、原始安装器输出不对客,只映射根协议的唯一刷
32
32
 
33
33
  ## 有限环境准备
34
34
 
35
- CLI 刷新策略与非 Windows 保持同一会话预算,不锁定 0.1.7 或其哈希;Windows 交由正式 PowerShell 安装器选择版本。jq 优先复用能通过版本和实际 filter 检查的既有原生工具;缺失或不可用时安装固定 1.7.1,官方 HTTPS 资产和 SHA-256 位于 `scripts/windows/policy.json`。Node 优先复用当前原生 x64 Node >=18;缺失时由根启动器的 PowerShell 引导准备官方 LTS 用户级 ZIP,SHA-256 验证后返回绝对可执行路径,首次一次、明确修复最多一次。Git Bash 从常见目录和本机 PATH 的 Git 安装布局定位,也可由 `ALIPAY_GIT_BASH` 指定绝对路径;必须验证 Git for Windows 版本、cygpath 和 Bash 的 MINGW/MSYS 平台,不使用 System32/WSL Bash,显式指定错误时停止而不偷偷换工具。
35
+ CLI 刷新策略与非 Windows 保持同一会话预算,不锁定 0.1.7 或其哈希;Windows 交由正式 PowerShell 安装器选择版本。jq 优先复用能通过版本和实际 filter 检查的既有原生工具;缺失或不可用时安装固定 1.7.1,官方 HTTPS 资产和 SHA-256 位于 `scripts/windows/policy.json`。Node 优先复用当前可运行的 Windows x64 Node >=18;缺失时由根启动器的 PowerShell 引导准备官方 LTS 用户级 ZIP,SHA-256 验证后返回绝对可执行路径,首次一次、明确修复最多一次。Git Bash 从常见目录和本机 PATH 的 Git 安装布局定位,也可由 `ALIPAY_GIT_BASH` 指定绝对路径;必须验证 Git for Windows 版本、cygpath 和 Bash 的 MINGW/MSYS 平台,不使用 System32/WSL Bash,显式指定错误时停止而不偷偷换工具。
36
36
 
37
- 初次缺 Git Bash、jq 或本机 CLI 无法启动且当前任务授权准备时,按 Git Bash → jq → 必要的 CLI 修复 → 完整复检执行。CLI 已可用时不重复安装。Git 优先复用已有可运行的 Git for Windows;缺失时优先调用用户级 winget,无法运行或未通过复检时使用 `scripts/windows/policy.json` 固定的 Git for Windows 官方 x64 安装包(当前 2.56.0.2)。官方包下载后先校验固定 SHA-256,再由系统 PowerShell 验证 Authenticode 有效,之后安装到 `%LOCALAPPDATA%/Programs/Git-Aipay-<version>`,并实测 git、bash、cygpath。下载或校验失败不运行安装包。两种来源均不可用时保持 BLOCKED,展示原因及官方手工安装路径,不静默跳站点。
37
+ 初次缺 Git Bash、jq 或本机 CLI 无法启动且当前任务授权准备时,按 Git Bash → jq → 必要的 CLI 修复 → 完整复检执行。CLI 已可用时不重复安装。Git 优先复用已有可运行的 Git for Windows;缺失时优先调用用户级 winget,无法运行或未通过复检时使用 `scripts/windows/policy.json` 固定的 Git for Windows 官方 x64 安装包(当前 2.56.0.2)。官方包下载后先校验固定 SHA-256,再由系统 PowerShell 验证 Authenticode 有效,之后安装到 `%LOCALAPPDATA%/Programs/Git-Aipay-<version>`,并实测 git、bash、cygpath。下载或校验失败不运行安装包。两种来源均不可用时按根意图返回 CODE_ONLY 或 BLOCKED,展示原因及官方手工安装路径,不静默跳站点。
38
38
 
39
- jq 使用固定 1.7.1 官方资产和 SHA-256,已有健康版本直接复用;CLI 仍使用正式 PowerShell 安装器选择当前版本。每个依赖和 Git 的两种安装来源分别记录实际开始的安装尝试,确认来源前的只读探测不消耗预算;Git 的 winget 失败可在同次准备中尝试官方固定包。初次每个来源最多一次,用户明确修复后每个来源最多再一次;失败阶段可续跑,已健康的阶段不重装。状态记录不含业务材料;复检始终只读且不消耗安装预算。网络、架构探测、权限、签名或完整性失败保持 BLOCKED,不轮换未经验证的镜像、使用 WSL、提权或修改执行策略。
39
+ jq 使用固定 1.7.1 官方资产和 SHA-256,已有健康版本直接复用;CLI 仍使用正式 PowerShell 安装器选择当前版本。每个依赖和 Git 的两种安装来源分别记录实际开始的安装尝试,确认来源前的只读探测不消耗预算;Git 的 winget 失败可在同次准备中尝试官方固定包。初次每个来源最多一次,用户明确修复后每个来源最多再一次;失败阶段可续跑,已健康的阶段不重装。状态记录不含业务材料;复检始终只读且不消耗安装预算。可选工具的网络、签名或完整性失败按第 21 行分流;实际存储故障仍为 BLOCKED。不轮换未经验证的镜像、使用 WSL、提权或修改执行策略。
40
40
 
41
- 环境诊断和次数保存在 `%LOCALAPPDATA%/alipay-aipay-pre/windows/environment.json`;旧版全局次数无法归属具体安装阶段,迁移时保留阻断原因并从新版本的有限分阶段预算重新计数。锁短时占用或状态损坏时,完整工具检查通过仍可 READY;自动安装保持阻断,不删除锁或状态绕过预算。用户修好依赖后,只读复检可再次执行,无论此前安装预算是否用尽:
41
+ 环境诊断和次数保存在 `%LOCALAPPDATA%/alipay-aipay-pre/windows/environment.json`;旧版全局次数无法归属具体安装阶段,迁移时保留阻断原因并从新版本的有限分阶段预算重新计数。锁短时占用或状态损坏时,完整工具检查通过仍可 READY;自动安装保持阻断,不删除锁或状态绕过预算。CODE_ONLY 的恢复使用第 23 行 `windows-entry`;以下仅适用于 BLOCKED。用户修好依赖后,只读复检可再次执行,无论此前安装预算是否用尽:
42
42
 
43
43
  ```text
44
44
  node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" env retry --repair-confirmed true --root-intent "<ROOT_INTENT>" --agent-platform "<AGENT_PLATFORM>" --session-id "<SESSION_ID>" --windows-route BLOCKED
@@ -52,7 +52,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" env prepare --authorize
52
52
 
53
53
  如果之前只是缺少授权或没有找到安装来源,尚未实际尝试该阶段,则使用同一命令但省略 `--repair-confirmed true`,继续首次准备;不能把新授权当成已消耗预算后的额外重试许可。
54
54
 
55
- 仅退出 0、唯一 `CLI_ENV:READY` 和唯一根意图匹配的 `WINDOWS_ROUTE=READY` 可更新分流。其他结果保持 BLOCKED,说明具体失败原因;不重建会话、重复计数或重放业务操作。用户明确选择站点或平台执行映射不支持时,可走原 LEGACY 分支;可恢复的依赖失败不能自动转站点。
55
+ 仅退出 0、唯一 `CLI_ENV:READY` 和唯一根意图匹配的 `WINDOWS_ROUTE=READY` 可恢复沙箱及开通能力。BLOCKED 恢复命令的其他结果保持 BLOCKED,说明具体失败原因;不重建会话、重复计数或重放业务操作。CODE_ONLY 按第 23 行重新取得入口分流。用户明确选择站点或平台执行映射不支持时,可走原 LEGACY 分支;可恢复的依赖失败不能自动转站点。
56
56
 
57
57
  ## 路径、权限与进程
58
58
 
@@ -60,13 +60,13 @@ Skill 自有动作始终执行 `node "<SKILL_DIR>/references/normal/scripts/runt
60
60
 
61
61
  只转换登记的项目、配置、上传文件和脚本路径;JSON、URL、scope、公钥和业务上下文保持原样。禁用 MSYS 隐式参数转换后,适配器显式转换 `git -C` 和 native Node/jq/CLI 文件参数。快速沙箱终态的路径由 gateway 归一化为原生盘符绝对路径,脚本内部比较不变。
62
62
 
63
- 大 jq `--argjson` 值使用受保护临时文件,MCC 目录使用 Node stdin,原 CLI 的 `-d/--data` 不改写;超出原生进程参数预算时明确失败,不能换未经确认的 CLI 参数。同步/异步 Shell 执行共享同一 Git Bash 适配器;CLI 最多 60 秒、业务 Shell 最多 180 秒,超时不等于未发送或业务成功,仍按原只读核验/UNKNOWN 规则处理。
63
+ 大 jq `--argjson` 值使用当前动作的临时文件并在调用后删除,MCC 目录使用 Node stdin,原 CLI 的 `-d/--data` 不改写;超出原生进程参数预算时明确失败,不能换未经确认的 CLI 参数。同步/异步 Shell 执行共享同一 Git Bash 适配器;CLI 最多 60 秒、业务 Shell 最多 180 秒,超时不等于未发送或业务成功,仍按原只读核验/UNKNOWN 规则处理。
64
64
 
65
- 敏感存储要求本地 NTFS。icacls 设置 DACL,固定系统 PowerShell 只读回权限;仅允许当前用户、SYSTEM、Administrators。拒绝符号链接和根目录,不能用 chmod 成功替代 ACL 证据。沙箱敏感内容写入前先保护临时文件,保护失败不得写入或使用字段。
65
+ Windows 沙箱项目配置不要求精确 NTFS ACL 白名单;项目可继承宿主或 Agent 正常工作所需的权限。环境状态、Shell 临时目录、CLI 凭据和日志目录的 ACL 不作为入口门禁。进入登录、签约等需用 CLI 登录凭据的动作前,才验证 `~/.alipay-cli/credentials` 实际可写;失败仅阻断依赖登录凭据的动作,不阻断沙箱和项目代码开发。授权状态和公钥页状态文件只检查实际可写性与非符号链接路径,不改写继承权限;这不表示 ACL 已安全配置,密钥文件访问控制由部署环境管理。生产配置仍遵守各自脚本的保护规则。
66
66
 
67
- Shell 业务子进程的临时文件放在上述环境目录的私有 tmp 中;gateway 本身不切换 TEMP/TMP,不改变现有遥测目录。Shell 子进程用原 `telemetryStoragePaths()` 明确绑定原来的遥测目录及既有覆盖项,保证声明选择、会话、run、outbox 和上传状态连续。遥测文件仅增加 Windows 权限适配,业务计数、采集及上传逻辑不变;权限失败沿用原静默失败语义。
67
+ Shell 业务子进程的临时文件放在上述环境目录的专用 tmp 中;gateway 本身不切换 TEMP/TMP,不改变现有遥测目录。Shell 子进程用原 `telemetryStoragePaths()` 明确绑定原来的遥测目录及既有覆盖项,保证声明选择、会话、run、outbox 和上传状态连续。遥测文件的权限失败沿用原静默失败语义,业务计数、采集及上传逻辑不变。
68
68
 
69
- 402 联调仍由原生 Python 3 执行,不依赖 Git Bash。其 Node 启动器在系统临时目录下创建本次命令的独立空目录,经同一 NTFS ACL 设置和读回验证后才启动 Python;仅该子进程的 TEMP/TMP/TMPDIR 与 Python `tempfile.tempdir` 指向此目录,禁止不可用时回退到其他目录。过程产物及 curl 响应文件继承受限 ACL;ACL 失败不启动联调,子进程结束或启动异常后尽力清理本次目录,清理失败只输出内部诊断且不覆盖原始执行结果,不重放请求。父进程环境、原 Python 业务脚本、参数和退出码不变;强制终止父进程可能留下受保护目录,不作为恢复依据。
69
+ 402 联调仍由原生 Python 3 执行,不依赖 Git Bash。其 Node 启动器在系统临时目录下创建本次命令的独立空目录,确认实际可写后才启动 Python;仅该子进程的 TEMP/TMP/TMPDIR 与 Python `tempfile.tempdir` 指向此目录,禁止不可用时回退到其他目录。过程产物及 curl 响应文件继承用户现有目录权限;实际写入失败时不伪报 ACL 问题。子进程结束或启动异常后尽力清理本次目录,清理失败只输出内部诊断且不覆盖原始执行结果,不重放请求。父进程环境、原 Python 业务脚本、参数和退出码不变;强制终止父进程可能留下该目录,不作为恢复依据。
70
70
 
71
71
  Windows 签约请求的 UUID 使用已有 Node 生成,仅此步骤不要求 python3/uuidgen;Unix/macOS/Linux 保留原依赖检查与生成方式。402 联调仍需 Python 3,此项依赖未被取消。
72
72
 
@@ -38,6 +38,6 @@ CLI `login --complete` 成功只表示页面侧确认完成,不代表本机已
38
38
 
39
39
  ## 状态保护
40
40
 
41
- 短时授权上下文使用当前用户私有临时目录和 `0600` 原子文件,拒绝符号链接。状态可见时显式参数必须逐项一致;状态不可见时仅允许完整显式上下文降级。新 init 只有先通过固定上下文校验才可替换旧状态;上下文改变且 CLI 仍登录时必须进入 mismatch,不得复用旧 deviceCode。
41
+ 短时授权上下文使用临时目录和原子文件,拒绝符号链接;Unix/macOS/Linux 校验目录 `0700`、文件 `0600`,Windows 校验路径实际可访问,不声称已配置 ACL。状态可见时显式参数必须逐项一致;状态不可见时仅允许完整显式上下文降级。新 init 只有先通过固定上下文校验才可替换旧状态;上下文改变且 CLI 仍登录时必须进入 mismatch,不得复用旧 deviceCode。
42
42
 
43
43
  任何授权 token、deviceCode、未脱敏 CLI/MCP 输出和临时 URL 都不得进入普通日志、对话摘要、跨会话状态或宽权限临时文件。只有经过脚本白名单和当前上下文校验、且当前动作必须交付的授权 URL 可以进入该动作唯一对客正文。
@@ -31,7 +31,7 @@ fi
31
31
 
32
32
  app_key_secure_mode() {
33
33
  local target="$1" expected="$2" mode
34
- if declare -F windows_protect >/dev/null; then windows_protect "$target"; return $?; fi
34
+ if declare -F windows_check_access >/dev/null; then windows_check_access "$target"; return $?; fi
35
35
  if mode=$(stat -f '%Lp' "$target" 2>/dev/null); then
36
36
  :
37
37
  elif mode=$(stat -c '%a' "$target" 2>/dev/null); then
@@ -49,7 +49,7 @@ prepare_app_key_state_dir() {
49
49
  return 1
50
50
  fi
51
51
  mkdir -p "$APP_KEY_STATE_DIR" || return 1
52
- chmod 700 "$APP_KEY_STATE_DIR" 2>/dev/null || return 1
52
+ if ! declare -F windows_check_access >/dev/null; then chmod 700 "$APP_KEY_STATE_DIR" 2>/dev/null || return 1; fi
53
53
  app_key_secure_mode "$APP_KEY_STATE_DIR" 700 || return 1
54
54
  else
55
55
  [ -d "$APP_KEY_STATE_DIR" ] && [ ! -L "$APP_KEY_STATE_DIR" ] || {
@@ -61,7 +61,7 @@ prepare_app_key_state_dir() {
61
61
 
62
62
  validate_app_key_state_file() {
63
63
  [ -f "$APP_KEY_STATE_FILE" ] && [ ! -L "$APP_KEY_STATE_FILE" ] || return 1
64
- chmod 600 "$APP_KEY_STATE_FILE" 2>/dev/null || return 1
64
+ if ! declare -F windows_check_access >/dev/null; then chmod 600 "$APP_KEY_STATE_FILE" 2>/dev/null || return 1; fi
65
65
  app_key_secure_mode "$APP_KEY_STATE_FILE" 600
66
66
  }
67
67
 
@@ -74,7 +74,7 @@ save_app_key_page_state() {
74
74
  return 1
75
75
  fi
76
76
  temp_state=$(mktemp "${APP_KEY_STATE_DIR}/.key-page-state.XXXXXX") || return 1
77
- chmod 600 "$temp_state" 2>/dev/null || { rm -f "$temp_state"; return 1; }
77
+ if ! declare -F windows_check_access >/dev/null; then chmod 600 "$temp_state" 2>/dev/null || { rm -f "$temp_state"; return 1; }; fi
78
78
  jq -n \
79
79
  --arg appId "$APP_ID" \
80
80
  --arg confirmPageUrl "$CONFIRM_PAGE_URL" \
@@ -82,7 +82,7 @@ save_app_key_page_state() {
82
82
  mv "$temp_state" "$APP_KEY_STATE_FILE" || { rm -f "$temp_state"; return 1; }
83
83
  validate_app_key_state_file || {
84
84
  rm -f "$APP_KEY_STATE_FILE"
85
- echo "❌ 无法确认公钥确认页状态文件权限为 0600,已停止" >&2
85
+ echo "❌ 公钥确认页状态文件不可访问,已停止" >&2
86
86
  return 1
87
87
  }
88
88
  }
@@ -28,7 +28,7 @@ require_command jq || exit 1
28
28
  require_command alipay-cli || exit 1
29
29
 
30
30
  # 生成 UUID 作为 bizRequestNo
31
- if declare -F windows_protect >/dev/null; then
31
+ if declare -F windows_check_access >/dev/null; then
32
32
  require_command node || exit 1
33
33
  BIZ_REQUEST_NO=$(node -e 'console.log(require("node:crypto").randomUUID())') || exit 78
34
34
  else
@@ -78,7 +78,7 @@ fi
78
78
 
79
79
  secure_mode() {
80
80
  local target="$1" expected="$2" mode
81
- if declare -F windows_protect >/dev/null; then windows_protect "$target"; return $?; fi
81
+ if declare -F windows_check_access >/dev/null; then windows_check_access "$target"; return $?; fi
82
82
  if mode=$(stat -f '%Lp' "$target" 2>/dev/null); then
83
83
  :
84
84
  elif mode=$(stat -c '%a' "$target" 2>/dev/null); then
@@ -96,7 +96,7 @@ prepare_auth_state_dir() {
96
96
  return 1
97
97
  fi
98
98
  mkdir -p "$AUTH_STATE_DIR" || return 1
99
- chmod 700 "$AUTH_STATE_DIR" 2>/dev/null || return 1
99
+ if ! declare -F windows_check_access >/dev/null; then chmod 700 "$AUTH_STATE_DIR" 2>/dev/null || return 1; fi
100
100
  secure_mode "$AUTH_STATE_DIR" 700 || return 1
101
101
  else
102
102
  [ -d "$AUTH_STATE_DIR" ] && [ ! -L "$AUTH_STATE_DIR" ] || {
@@ -108,7 +108,7 @@ prepare_auth_state_dir() {
108
108
 
109
109
  validate_auth_state_file() {
110
110
  [ -f "$AUTH_STATE_FILE" ] && [ ! -L "$AUTH_STATE_FILE" ] || return 1
111
- chmod 600 "$AUTH_STATE_FILE" 2>/dev/null || return 1
111
+ if ! declare -F windows_check_access >/dev/null; then chmod 600 "$AUTH_STATE_FILE" 2>/dev/null || return 1; fi
112
112
  secure_mode "$AUTH_STATE_FILE" 600
113
113
  }
114
114
 
@@ -160,7 +160,7 @@ save_auth_state() {
160
160
  return 1
161
161
  fi
162
162
  temp_state=$(mktemp "${AUTH_STATE_DIR}/.auth-state.XXXXXX") || return 1
163
- chmod 600 "$temp_state" 2>/dev/null || { rm -f "$temp_state"; return 1; }
163
+ if ! declare -F windows_check_access >/dev/null; then chmod 600 "$temp_state" 2>/dev/null || { rm -f "$temp_state"; return 1; }; fi
164
164
  jq -n \
165
165
  --arg salesCode "$SALES_CODE" \
166
166
  --arg mccCode "$MCC_CODE" \
@@ -183,7 +183,7 @@ save_auth_state() {
183
183
  mv "$temp_state" "$AUTH_STATE_FILE" || { rm -f "$temp_state"; return 1; }
184
184
  validate_auth_state_file || {
185
185
  cleanup_auth_state
186
- echo "❌ 无法确认授权状态文件权限为 0600,已停止" >&2
186
+ echo "❌ 授权状态文件不可访问,已停止" >&2
187
187
  return 1
188
188
  }
189
189
  }
@@ -70,7 +70,7 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" telemetry resolve-sessi
70
70
  node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" windows-entry --root-intent "<ROOT_INTENT>" --agent-platform "<AGENT_PLATFORM>" --session-id "<SESSION_ID>"
71
71
  ```
72
72
 
73
- 用户明确要求安装并通过 Skill 完成签约/开通或完整接入时(包括正式版保留的原任务与用户随后明确同意安装内测版的选择),当前任务已授权准备所需用户级依赖,附加 `--prepare-authorized true`;仅代码开发且未授权准备依赖时先只读检查。用户明确选择站点时才附加 `--site-selected true`。这不构成登录、签约或应用写入授权。只接受退出 0、唯一 `ALIPAY_AIPAY_INTERNAL:WINDOWS_ROUTE=<JSON>` 且 rootIntent 匹配、route 为 `READY|BLOCKED|LEGACY|CODE_ONLY`。非零、缺失或冲突按 `BLOCKED` 停止,不推定站点分支。`BLOCKED` 须说明内部结果给出的实际缺项/失败原因和有限恢复动作;用户修好依赖后先只读复检,必要且获授权时才按环境文档继续准备。只有不支持的执行映射或用户明确选择站点才进入 `LEGACY`。`integration_only` 入口因 Git Bash、jq、CLI 等可选工具缺失、不可运行或有限准备失败时,唯一分流可返回 `CODE_ONLY`:继续原生 Node 的独立开发分支,沙箱记 `VERIFY_PENDING`,不输出站点开通引导。Node 不足、架构探测未知、存储/ACL、状态异常及未知错误仍阻断,不由 Agent 自行降级。所有后续 Windows runtime 命令追加 `--windows-route "<READY|BLOCKED|LEGACY|CODE_ONLY>"`;`BLOCKED` 不进入业务 bootstrap/flow。仅修复后唯一、根意图匹配的 `WINDOWS_ROUTE=READY` 才更新分流事实;`READY` 的快速沙箱与本地开通使用原 flow,`LEGACY` 保留原独立开发/手工沙箱及站点开通分支。`CODE_ONLY` 仅绑定当前 `integration_only`;改为开通或完整接入前必须用新根意图重新执行 `windows-entry`,不得继承该分流。用户修好依赖或明确授权准备后,按 Windows 环境文档重新检查;未取得 READY 时继续独立开发,不循环安装。实际业务执行前仍复检能力。环境、沙箱准备或业务失败不修改采集选择、SESSION_ID、run_id、用户输入计数或阶段责任方。
73
+ 用户明确要求安装并通过 Skill 完成签约/开通或完整接入时(包括正式版保留的原任务与用户随后明确同意安装内测版的选择),当前任务已授权准备所需用户级依赖,附加 `--prepare-authorized true`;仅代码开发且未授权准备依赖时先只读检查。用户明确选择站点时才附加 `--site-selected true`。这不构成登录、签约或应用写入授权。只接受退出 0、唯一 `ALIPAY_AIPAY_INTERNAL:WINDOWS_ROUTE=<JSON>` 且 rootIntent 匹配、route 为 `READY|BLOCKED|LEGACY|CODE_ONLY`。非零、缺失或冲突按 `BLOCKED` 停止,不推定站点分支。`BLOCKED` 须说明内部结果给出的实际缺项/失败原因和有限恢复动作;用户修好依赖后先只读复检,必要且获授权时才按环境文档继续准备。只有不支持的执行映射或用户明确选择站点才进入 `LEGACY`。`integration_only/full_process` 因 Git Bash、jq、CLI 等可选工具缺失、不可运行或有限准备失败时可返回 `CODE_ONLY`,继续原生 Node 代码开发,沙箱记 `VERIFY_PENDING`;完整接入的代码部分完成后停在开通前,不宣称完整接入完成。Windows 沙箱项目配置不要求精确 ACL 白名单;CLI 登录凭据目录仅在依赖登录的动作前检查实际可写性,失败只阻断该动作。Node 不足、实际存储故障、状态异常及未知错误仍阻断,不由 Agent 自行降级。所有后续 Windows runtime 命令追加 `--windows-route "<READY|BLOCKED|LEGACY|CODE_ONLY>"`;`BLOCKED` 不进入业务 bootstrap/flow。修复后只有唯一、根意图匹配的 `WINDOWS_ROUTE=READY` 才恢复快速沙箱与本地开通;`LEGACY` 保留原独立开发/手工沙箱及站点开通分支。`CODE_ONLY` 绑定本次根意图;根意图变化必须重新执行 `windows-entry`,不得继承旧分流。用户修好依赖或明确授权准备后,按 Windows 环境文档重新检查;未取得 READY 时继续独立开发,不循环安装。实际业务执行前仍复检能力。环境、沙箱准备或业务失败不修改采集选择、SESSION_ID、run_id、用户输入计数或阶段责任方。
74
74
 
75
75
 
76
76
  `integration_only` 的 `integration-bootstrap` 在产品已唯一确定时必须携带 `--product-type`,并串行执行 `telemetry start-run` 与 `flow integration entry`;产品尚未唯一确定时省略产品且不创建正式 run,后续由 `integration-message start-confirm` 首次开 run并生成 `clarify_intent`。`onboarding_only` 的 `onboarding-bootstrap` 同理:产品已唯一时携带产品并先启动 run,尚未唯一时由 `mcc resolve-plan` 或 `onboarding-message plan-confirm` 首次启动。两者均为既有无业务写入动作,执行后不得重复原子命令。Windows READY 返回本地 entry 契约;Windows LEGACY 引导渲染依次使用现有平台项目 ID、正式 run_id;两者均不存在时使用已绑定 `SESSION_ID` 的稳定匿名哈希,不提前创建 run。非 Windows 返回 entry 契约。`full_process` 不使用 `integration-bootstrap`;衔接 Onboarding 也不使用带 `start-run` 的 bootstrap,仍执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message platform-entry --agent-platform "<AGENT_PLATFORM>" --session-id "<SESSION_ID>"` 后按需加载 entry,避免重复 `clarify_intent`。
@@ -1,3 +0,0 @@
1
- import {protect} from './acl.mjs';
2
- try { if(process.argv.length!==3)throw new Error('ACL target required');protect(process.argv[2]); }
3
- catch(e){process.stderr.write('ALIPAY_AIPAY_INTERNAL:WINDOWS_ACL_ERROR='+(e.reason||'ACL_FAILED')+'\n');process.exitCode=78;}
@@ -1,41 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import {spawnSync} from 'node:child_process';
4
-
5
- export function fail(reason, dependency = '') { throw Object.assign(new Error(reason), {reason, dependency}); }
6
- export function run(command,args,options={}) {
7
- return spawnSync(command,args,{encoding:'utf8',windowsHide:true,shell:false,timeout:15000,maxBuffer:4*1024*1024,...options});
8
- }
9
- export function powershell() {
10
- const root=process.env.SystemRoot || 'C:\\Windows';
11
- const command=path.join(root,'System32/WindowsPowerShell/v1.0/powershell.exe');
12
- if(!fs.existsSync(command)) fail('DEPENDENCY_MISSING','PowerShell');
13
- return command;
14
- }
15
- export function psEnv(env=process.env) {
16
- return Object.fromEntries(Object.entries(env).filter(([k])=>k.toLowerCase()!=='psmodulepath'));
17
- }
18
- export function protect(target) {
19
- if(fs.lstatSync(target).isSymbolicLink()||path.resolve(target)===path.parse(target).root)fail('ACL_FAILED','unsafe target');
20
- const system=path.join(process.env.SystemRoot||'C:/Windows','System32');
21
- const who=run(path.join(system,'whoami.exe'),['/user','/fo','csv','/nh']);
22
- const sid=who.stdout?.match(/S-1-5-[0-9-]+/)?.[0];if(who.status!==0||!sid)fail('ACL_FAILED');
23
- const env={...psEnv(),ALIPAY_WINDOWS_ACL_TARGET:path.resolve(target)};
24
- // Native icacls changes permissions; PowerShell only reads ACL metadata.
25
- // No .ps1 execution, ExecutionPolicy override, elevation, or policy mutation.
26
- const inspect=()=>{
27
- const r=run(powershell(),['-NoProfile','-NonInteractive','-Command',
28
- '$ErrorActionPreference="Stop"; $p=$env:ALIPAY_WINDOWS_ACL_TARGET; $d=New-Object IO.DriveInfo([IO.Path]::GetPathRoot($p)); if($d.DriveFormat -ne "NTFS"){exit 2}; $a=Get-Acl -LiteralPath $p; @{protected=$a.AreAccessRulesProtected;rules=@($a.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier]) | ForEach-Object { @{sid=$_.IdentityReference.Value;type=[string]$_.AccessControlType;rights=[int]$_.FileSystemRights} })} | ConvertTo-Json -Depth 4 -Compress'],{env});
29
- if(r.status!==0)fail('ACL_FAILED','NTFS');try{return JSON.parse(r.stdout);}catch{fail('ACL_FAILED');}
30
- };
31
- const allowed=[sid,'S-1-5-18','S-1-5-32-544'];inspect();
32
- const inherited=fs.statSync(target).isDirectory()?'(OI)(CI)F':'F';
33
- const args=[target,'/inheritance:r','/grant:r',...allowed.map(s=>'*'+s+':'+inherited)];
34
- if(run(path.join(system,'icacls.exe'),args).status!==0)fail('ACL_FAILED','grant');
35
- const remove=[...new Set(inspect().rules.filter(r=>!allowed.includes(r.sid)||r.type!=='Allow').map(r=>r.sid))];
36
- for(const s of remove)if(run(path.join(system,'icacls.exe'),[target,'/remove','*'+s]).status!==0)fail('ACL_FAILED','remove');
37
- // Reapply after removing any explicit deny ACE for an allowed identity.
38
- if(remove.length&&run(path.join(system,'icacls.exe'),args).status!==0)fail('ACL_FAILED','regrant');
39
- const after=inspect();
40
- if(!after.protected||after.rules.some(r=>!allowed.includes(r.sid)||r.type!=='Allow')||!after.rules.some(r=>r.sid===sid&&(r.rights&2032127)===2032127))fail('ACL_FAILED','readback');
41
- }