@hanamesh/dsh-app-host 0.6.24 → 0.6.26

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 CHANGED
@@ -1,3 +1,7 @@
1
+ 0.6.26 候选:Codex 官方 GitHub 市场 URL 的 `.git` 后缀按精确仓库身份归一;市场映射与每项安装来源独立校验,不同仓库、凭据、查询、fragment 仍拒绝。CD2 扩大只读覆盖:ChatGPT 桌面 App 内置 Codex CLI、SemVer 预发布版本(仍须不低于最低版本);官方用户资料目录不存在即判未安装;HanaMesh 客户端从磁盘读取 DSH 桌面版 profile(`~/.dsh/profiles/desktop`)给出 DSH 渠道已安装/未安装。检测不执行安装或配置写入,见[安装状态](docs/INSTALLATION_STATUS.md)。
2
+
3
+ 0.6.25 候选:本机 HTTPS 目录允许回环端口及 *.localhost,修复彩排种子初始化阻断检测监听;公网 HTTPS 非标准端口仍拒绝。Claude 官方 GitHub 市场清单按 repo 身份核对,保留不同来源拒绝。安装物回归命令见[安装状态](docs/INSTALLATION_STATUS.md),不以页面打开代替检测通过。
4
+
1
5
  PX3 跨平台候选:Windows 可以运行插件/公开服务、attach 应用、受控只读 CLI 与普通包安装;`owned` 应用仍在创建进程、数据目录及请求凭据之前明确拒绝 `PLATFORM_UNSUPPORTED`。Windows 单测保留真实 attach 的 HTTP/租约/网关断言,并验证 owned 的持久失败、无进程/文件写入及不消费凭据;这些断言不构成 Windows owned 运行支持。独立 Node 可用不扩大 owned 支持;受管 Node 准备仍遵守原平台限制。Windows 本地快照采用文件 flush 加同目录 rename,不承诺断电后目录项持久;Coding OAuth Gateway 文件源因缺私有 ACL 证明继续拒绝,HTTP 提供方不变。各平台实际结果以本分支 GitHub Actions 矩阵收据为准。
2
6
 
3
7
  R115返修(同0.6.23候选):登录shell按退出结果和封装JSON解析成功,忽略正常stderr;所选CLI保留管理器PATH与XDG目录/缺省,不再用空临时XDG隐藏全局版本。真实Bash/asdf/mise启动链回归与工具来源见 [安装状态](docs/INSTALLATION_STATUS.md)。
@@ -12,7 +12,7 @@ export function NodeRuntimeSection():unknown;
12
12
  export function LibraryAction():unknown;
13
13
  /** The HanaMesh market page (applications + plugins; install / upgrade / uninstall / restart notice).
14
14
  * `embedded` renders it inline for the Extension Management tab instead of as the shell overlay. */
15
- export function LibraryOverlay(props?:{embedded?:boolean,preferredSubsectionId?:string,verification?:boolean}):unknown;
15
+ export function LibraryOverlay(props?:{embedded?:boolean,mainPage?:boolean,preferredSubsectionId?:string,verification?:boolean}):unknown;
16
16
  /** rc.32: the「只能其一」notice shown at the top of the market page when dshmarket holds the seat. */
17
17
  export function MarketConflictNotice():unknown;
18
18
  /** Identifiers only; metadata and confirmation are owned by the client's market. */
@@ -38,3 +38,13 @@ export const INSTALL_TARGET_CONTRACT_VERSION:'1';
38
38
 
39
39
  /** AppHost-owned registry selection, independent of catalog manifest sources. */
40
40
  export function SuiteSourceSettingsSection():unknown;
41
+
42
+ /** Additive DSH layout seats and the Core-owned environment content extension. */
43
+ export function registerAppHostPages(ctx:unknown):()=>void;
44
+ export function EnvironmentContent():unknown;
45
+ /** Current catalog contract supplies none of these counts; null is explicit unknown, never zero. */
46
+ export interface CredibilityFacts {itemId?:string;signals?:{stars?:number|null;npmDownloads30d?:number|null;storeInstalls30d?:number|null;checkedAt?:string|null}|null;popularity?:{users30d?:number|null;asOf?:string|null}|null;checkedAt?:string|null;asOf?:string|null}
47
+ export function credibilityFacts(facts?:CredibilityFacts|null):{id:string;label:string;value:number|null;reason:'catalog-field-unavailable'|null}[];
48
+ export function readCredibility(source:unknown,item:{id:string},fetcher?:typeof fetch):Promise<CredibilityFacts>;
49
+ /** HanaMesh-view catalog website link; absent for fixtures and other provider protocols. */
50
+ export function catalogWebsiteLink(source:unknown,item:unknown):string|null;
package/dist/client-ui.js CHANGED
@@ -609,6 +609,41 @@ window.__ModuleLoader__.load({id:'@hanamesh/dsh-app-host',factory:function(requi
609
609
  };
610
610
  Object.assign(COPY.zh,{githubRepository:'来源仓库',githubCommit:'固定 commit',githubPath:'仓库子目录',githubBuild:'会在本机执行构建脚本',githubBuildPolicy:'确认后将按名单允许构建;pnpm 会把许可保存到当前 profile。',githubNoBuild:'未授予构建脚本许可'});
611
611
  Object.assign(COPY.en,{githubRepository:'Source repository',githubCommit:'Pinned commit',githubPath:'Repository subdirectory',githubBuild:'Build scripts will run on this computer',githubBuildPolicy:'Confirmation permits the listed builds; pnpm saves these permissions in the current profile.',githubNoBuild:'No build scripts are approved'});
612
+ // Owned copy for the additive HanaMesh pages.
613
+ COPY.zh.cl1Page0="应用库";COPY.en.cl1Page0="Library";
614
+ COPY.zh.cl1Page1="详情";COPY.en.cl1Page1="Details";
615
+ COPY.zh.cl1Page2="返回应用库";COPY.en.cl1Page2="Back to library";
616
+ COPY.zh.cl1Page3="关于这个应用";COPY.en.cl1Page3="About this app";
617
+ COPY.zh.cl1Page4="可信度";COPY.en.cl1Page4="Credibility";
618
+ COPY.zh.cl1Page5="GitHub 星数";COPY.en.cl1Page5="GitHub stars";
619
+ COPY.zh.cl1Page6="npm 下载 / 30 天";COPY.en.cl1Page6="npm downloads / 30 days";
620
+ COPY.zh.cl1Page7="安装人数";COPY.en.cl1Page7="People who installed";
621
+ COPY.zh.cl1Page8="使用人数";COPY.en.cl1Page8="People who used";
622
+ COPY.zh.cl1Page9="当前目录未提供";COPY.en.cl1Page9="Not supplied by this catalog";
623
+ COPY.zh.cl1Page10="可信度数据缺失不代表 0;本机安装状态与平台采用度分别显示。";COPY.en.cl1Page10="Missing credibility data does not mean zero. Local installation state is separate from platform adoption.";
624
+ COPY.zh.cl1Page11="来源与安装目标";COPY.en.cl1Page11="Source and installation target";
625
+ COPY.zh.cl1Page12="在网站查看";COPY.en.cl1Page12="View on website";
626
+ COPY.zh.cl1Page13="发现适合你的工具,查看来源后再安装;账号与模型授权分别管理。";COPY.en.cl1Page13="Find tools for your work and review their sources before installing. Account and model authorization are managed separately.";
627
+ COPY.zh.cl1Page14="安装确认";COPY.en.cl1Page14="Confirm installation";
628
+ COPY.zh.cl1Page15="我已核对来源与安装目标";COPY.en.cl1Page15="I reviewed the source and installation target";
629
+ COPY.zh.cl1Page16="设置与连接";COPY.en.cl1Page16="Settings and connections";
630
+ COPY.zh.cl1Page17="账号与设备";COPY.en.cl1Page17="Account and device";
631
+ COPY.zh.cl1Page18="模型连接";COPY.en.cl1Page18="Model connections";
632
+ COPY.zh.cl1Page19="环境与来源";COPY.en.cl1Page19="Environment and sources";
633
+ COPY.zh.cl1Page20="HanaMesh / 模型连接";COPY.en.cl1Page20="HanaMesh / Model connections";
634
+ COPY.zh.cl1Page21="连接模型,保持掌控。";COPY.en.cl1Page21="Connect models, stay in control.";
635
+ COPY.zh.cl1Page22="登录账号不提供模型凭据。沿用宿主连接,按应用分别查看授权。";COPY.en.cl1Page22="Signing in does not supply model credentials. Use host connections and review authorization for each app.";
636
+ COPY.zh.cl1Page23="对应页面暂不可用,请确认 HanaMesh Core 已启用。";COPY.en.cl1Page23="This page is unavailable. Check that HanaMesh Core is enabled.";
637
+ COPY.zh.cl1Page24="目录来源";COPY.en.cl1Page24="Catalog sources";
638
+ COPY.zh.cl1Page25="展开后读取当前设置;不会自动保存或安装。";COPY.en.cl1Page25="Expand to read the current settings. Nothing is saved or installed automatically.";
639
+ COPY.zh.cl1Extra0="HanaMesh / 应用库";COPY.en.cl1Extra0="HanaMesh / Library";
640
+ COPY.zh.cl1Extra1="好工具,让想法走得更远。";COPY.en.cl1Extra1="Good tools take your ideas further.";
641
+ COPY.zh.cl1Extra2="安装之前,先看清楚。";COPY.en.cl1Extra2="Review before you install.";
642
+ COPY.zh.cl1Extra3="精确版本";COPY.en.cl1Extra3="Exact version";
643
+ COPY.zh.cl1Extra4="应用筛选";COPY.en.cl1Extra4="App filter";
644
+ COPY.zh.cl1Extra5="全部应用";COPY.en.cl1Extra5="All apps";
645
+ COPY.zh.cl1Extra6="还没有已安装项。";COPY.en.cl1Extra6="No installed items yet.";
646
+ COPY.zh.cl1Extra7="没有找到符合条件的应用。";COPY.en.cl1Extra7="No apps match these filters.";
612
647
  const COPY_KEYS=new Map(Object.entries(COPY.zh).map(([key,text])=>[text,key]));
613
648
  let localeService=null;
614
649
  function t(text){if(typeof text==='function')return text();const key=COPY_KEYS.get(text);if(!key)return text;return localeService?localeService.bind('hanamesh.apphost')(key):COPY.zh[key];}
@@ -649,6 +684,8 @@ window.__ModuleLoader__.load({id:'@hanamesh/dsh-app-host',factory:function(requi
649
684
  h('details',null,h('summary',null,t('技术详情')),h('p',null,t('应用启动时按当前配置与应用声明匹配供应商并注入;这里只选择模型或撤回、恢复该连接的授权,不改变来源开关。'))),
650
685
  routeRows.length?h('table',{className:'hm-provider-records'},h('thead',null,h('tr',null,...[t('应用'),t('用途'),t('供应商'),t('模型'),t('状态'),''].map((label,index)=>h('th',{key:index},label)))),h('tbody',null,...routeRows)):h('p',{className:'hm-provider-hint'},t('尚无需要模型服务的已安装应用。')));
651
686
  }
687
+ let pageLayout=null;
688
+ const showLibrary=()=>{if(pageLayout){pageLayout.selectPanel('hanamesh-library');setLibraryVisible(false);}else setLibraryVisible(true);};
652
689
  let libraryVisible=false;const libraryListeners=new Set();
653
690
  const setLibraryVisible=value=>{libraryVisible=value;for(const listener of libraryListeners)listener(value);};
654
691
  let targetSelection=null,targetSequence=0,pendingOperations={};
@@ -661,7 +698,7 @@ window.__ModuleLoader__.load({id:'@hanamesh/dsh-app-host',factory:function(requi
661
698
  const setPending=update=>{pendingOperations=typeof update==='function'?update(pendingOperations):update;for(const listener of pendingListeners)listener(pendingOperations);};
662
699
  /** Public client entry: identifiers only; the market fetches its own catalog and owns confirmation. */
663
700
  async function openInstallTarget(input,{handoffId}={}){
664
- const sequence=++targetSequence;if(entryVisible)setLibraryVisible(true);
701
+ const sequence=++targetSequence;if(entryVisible)showLibrary();
665
702
  try{
666
703
  if(!input||typeof input!=='object'||Array.isArray(input))throw new Error('INVALID_INPUT');
667
704
  // rc.8: optional contract declaration; absent keeps v1 semantics, an unsupported version is refused before any request.
@@ -709,7 +746,7 @@ window.__ModuleLoader__.load({id:'@hanamesh/dsh-app-host',factory:function(requi
709
746
  function consumeInstallTargetHash(){
710
747
  const hash=globalThis.location?.hash;if(!hash?.startsWith(INSTALL_TARGET_HASH))return;
711
748
  const params=new URLSearchParams(hash.slice(INSTALL_TARGET_HASH.length));
712
- if(new Set(params.keys()).size!==[...params.keys()].length){targetSequence++;if(entryVisible)setLibraryVisible(true);setTarget({status:'failed',error:'INVALID_INPUT'});return;}
749
+ if(new Set(params.keys()).size!==[...params.keys()].length){targetSequence++;if(entryVisible)showLibrary();setTarget({status:'failed',error:'INVALID_INPUT'});return;}
713
750
  const input=Object.fromEntries(params);
714
751
  if(input.installTarget!==undefined){try{input.installTarget=JSON.parse(input.installTarget);}catch{setTarget({status:'failed',error:'INVALID_INPUT'});return;}}
715
752
  void openInstallTarget(input).catch(()=>{});
@@ -739,8 +776,8 @@ window.__ModuleLoader__.load({id:'@hanamesh/dsh-app-host',factory:function(requi
739
776
  function LibraryAction(){useLocale();
740
777
  const[shown,setShown]=React.useState(entryVisible);
741
778
  React.useEffect(()=>{entryListeners.add(setShown);return()=>entryListeners.delete(setShown);},[]);
742
- if(!shown)return null;
743
- return h('button',{type:'button',className:'hm-library-action',onClick:()=>setLibraryVisible(true)},t('市场'));
779
+ if(!shown||pageLayout)return null;
780
+ return h('button',{type:'button',className:'hm-library-action',onClick:()=>showLibrary()},t('市场'));
744
781
  }
745
782
  function LibrarySourcesSection(){useLocale();
746
783
  const[sources,setSources]=React.useState([]),[draft,setDraft]=React.useState(''),[error,setError]=React.useState('');
@@ -900,8 +937,12 @@ h('div',{className:'hm-source-add'},h('input',{value:draft,placeholder:'https://
900
937
  {method:'POST',path:'/apps/heartbeat',sample:{viewId:'verification-view',leaseToken:''}},
901
938
  {method:'POST',path:'/apps/close',sample:{viewId:'verification-view',leaseToken:''}},
902
939
  ];
903
- function LibraryOverlay({embedded,preferredSubsectionId,verification=false}={}){useLocale();
940
+ Object.assign(COPY.zh,{cl1Detail:'HanaMesh / 应用详情',cred0:'暂无',cred1:'当前暂无数据',cred2:'公开统计',cred3:'DSH 安装 / 30 天',cred4:'使用人数 / 30 天',cred5:'信号更新:',cred6:'使用人数更新:'});
941
+ Object.assign(COPY.en,{cl1Detail:'HanaMesh / Application details',cred0:'Not available',cred1:'No data available',cred2:'Public statistics',cred3:'DSH installs / 30 days',cred4:'Users / 30 days',cred5:'Signals updated: ',cred6:'Users updated: '});
942
+ function LibraryOverlay({embedded,mainPage=false,preferredSubsectionId,verification=false}={}){useLocale();
904
943
  const[visible,setVisible]=React.useState(embedded?true:libraryVisible),[snapshot,setSnapshot]=React.useState(null),[error,setError]=React.useState(''),[receipt,setReceipt]=React.useState(null),[query,setQuery]=React.useState(''),[filter,setFilter]=React.useState(''),[extra,setExtra]=React.useState([]),[pending,updatePending]=React.useState(pendingOperations),[seenCategories,setSeenCategories]=React.useState([]),[changed,setChanged]=React.useState(false),[openingAppId,setOpeningAppId]=React.useState(null),[closing,setClosing]=React.useState(false);
944
+ const[detailId,setDetailId]=React.useState(null),[acceptedTarget,setAcceptedTarget]=React.useState(null),[libraryTab,setLibraryTab]=React.useState('all'),[credibility,setCredibility]=React.useState(null);
945
+ React.useEffect(()=>{let live=true;setCredibility(null);if(detailId&&visible)void readCredibility(snapshot?.source,{id:detailId}).then(value=>{if(live)setCredibility(value);});return()=>{live=false;};},[detailId,visible,snapshot?.source?.manifestUrl]);
905
946
  const[target,updateTarget]=React.useState(targetSelection),[targetItemId,setTargetItemId]=React.useState(''),[targetPackageName,setTargetPackageName]=React.useState('');
906
947
  const[verificationCall,setVerificationCall]=React.useState('GET /hanamesh/library/install-target'),[verificationInput,setVerificationInput]=React.useState('{}'),[verificationOutput,setVerificationOutput]=React.useState(null),[verifying,setVerifying]=React.useState(false);
907
948
  const verificationBusy=React.useRef(false);
@@ -979,7 +1020,7 @@ h('div',{className:'hm-source-add'},h('input',{value:draft,placeholder:'https://
979
1020
  setPending(p=>({...p,[key]:outcome.ok?(awaitReadback?{label,text:()=>(t(label)+t('读回中…')),busy:true}:{label,text:()=>(liveLoaded?t(label)+t('完成,已生效,可以直接使用')+baselineText(outcome.result):t(label)+t('完成,重启 DSH 后生效'))}):{label,text:()=>(outcome.unknown?t(label)+t('结果不可追溯,请查看已安装列表后再明确操作'):t(label)+t('失败:')+outcome.code+'('+failureReason(outcome.code,outcome.details)+')'+recoveryText(outcome.recovery)+suiteRecoveryText(outcome.details)),failed:true,diagnostic:outcome.details?.diagnostic}}));
980
1021
  await load();return outcome;}catch(e){const code=String(e.message??e);setPending(p=>({...p,[key]:{label,text:()=>(t(label)+t('失败:')+code),failed:true}}));return{ok:false,code};}};
981
1022
  const confirmTarget=async()=>{
982
- const selected=targetSelection;if(selected?.status!=='confirmation-required'||pendingOperations[selected.item.id]?.busy)return;
1023
+ const selected=targetSelection;if(selected?.status!=='confirmation-required'||acceptedTarget!==selected||pendingOperations[selected.item.id]?.busy)return;
983
1024
  setTarget({...selected,status:'installing'});
984
1025
  const suite=selected.item.kind==='suite-upgrade';
985
1026
  const reviewed={itemId:selected.item.id,packageName:selected.item.package.name,...(selected.item.installTarget?{installTarget:selected.item.installTarget}:{})};
@@ -1041,7 +1082,7 @@ h('div',{className:'hm-source-add'},h('input',{value:draft,placeholder:'https://
1041
1082
  const restartRequired=changed||Boolean(snapshot?.restartRequired);
1042
1083
  const filters=[...BASE_FILTERS,...seenCategories.filter(c=>c!=='hanamesh-app').map(c=>[c,c])];
1043
1084
  const openButton=row=>h('button',{type:'button',disabled:Boolean(openingAppId),onClick:()=>void open(row)},openingAppId===row.appId?t('打开中…'):t('打开'));
1044
- const card=item=>{const row=item.installed,op=pending[item.id],busy=op?.busy;const installBody={itemId:item.id,...(item.package?.name?{packageName:item.package.name}:{}),...(item.installTarget?{installTarget:item.installTarget}:{})};
1085
+ const card=(item,detail=false)=>{const row=item.installed,op=pending[item.id],busy=op?.busy;const installBody={itemId:item.id,...(item.package?.name?{packageName:item.package.name}:{}),...(item.installTarget?{installTarget:item.installTarget}:{})};
1045
1086
  const reviewOrInstall=label=>item.installTarget?openInstallTarget(installBody).catch(()=>{}):operate('/hanamesh/library/install',installBody,item.id,label);
1046
1087
  let action=h('span',null,t('仅收录,不可安装')),state=null;
1047
1088
  if(row)state=h('span',{className:'hm-market-state','data-hanamesh-state':row.state},(t(STATE_TEXT[row.state]??row.state))+(row.version?' '+row.version:''));
@@ -1054,20 +1095,25 @@ h('div',{className:'hm-source-add'},h('input',{value:draft,placeholder:'https://
1054
1095
  else if(row?.state==='installed')action=PROTECTED.has(row.packageName)?h('span',null,t('HanaMesh 套件')):h('button',{type:'button',onClick:()=>void operate('/hanamesh/library/plugins/uninstall',{packageName:row.packageName},item.id,t('卸载'))},t('卸载'));
1055
1096
  else if(row?.state==='uninstalled-not-unloaded')action=h('span',null,t('已卸载,重启 DSH 后生效'));
1056
1097
  else if(row)action=h('span',null,t('已安装,重启 DSH 后生效'));
1057
- return h('article',{key:item.id,'data-hanamesh-library-item':item.id,'data-hanamesh-kind':item.kind},h('h3',null,item.displayName),h('p',null,item.summary),
1098
+ return h('article',{key:item.id,'data-hanamesh-library-item':item.id,'data-hanamesh-kind':item.kind,className:detail?'hm-app-detail':'hm-app-card'},detail&&mainPage?null:h('h3',null,item.displayName),detail&&mainPage?null:h('p',null,item.summary),
1099
+ detail?h('div',{className:'hm-app-detail-content'},h('h4',null,t('关于这个应用')),h('p',null,item.description||item.summary),h('h4',null,t('可信度')),h('div',{className:'hm-credibility'},...credibilityFacts(credibility?.itemId===item.id?credibility:null).map(row=>h('div',{key:row.id},h('span',null,t(row.label)),h('strong',{'data-hanamesh-credibility':row.id},row.value===null?t('暂无'):row.value.toLocaleString()),h('small',null,row.value===null?t('当前暂无数据'):t('公开统计'))))),h('p',{className:'hm-market-state'},t('可信度数据缺失不代表 0;本机安装状态与平台采用度分别显示。')),credibility?.checkedAt?h('small',null,t('信号更新:')+credibility.checkedAt):null,credibility?.asOf?h('small',null,t('使用人数更新:')+credibility.asOf):null,h('h4',null,t('来源与安装目标')),h('p',null,item.package?.name??t('仅收录,不可安装')),item.installTarget?h('code',null,item.installTarget.source==='github'?'https://github.com/'+item.installTarget.repo+' @ '+item.installTarget.ref:item.installTarget.name+' @ '+item.installTarget.version):null,...(item.source?.sourceLinks??[]).map((link,index)=>h('p',{key:index},h('a',{href:link.sourceUrl,target:'_blank',rel:'noopener noreferrer'},link.sourceId+' · '+link.sourceRef))),catalogWebsiteLink(snapshot?.source,item)?h('a',{href:catalogWebsiteLink(snapshot?.source,item),target:'_blank',rel:'noopener noreferrer',className:'hm-website-link'},t('在网站查看')):null):null,
1058
1100
  h('small',null,t(KIND_TEXT[item.kind])+' · '+(item.publisher?.name??t('未知发布者'))+((item.categories??[]).length?' · '+item.categories.join('、'):'')),
1059
- state,action,op&&!busy?operationNotice(op):null);};
1101
+ state,h('div',{className:'hm-market-actions'},action,!detail?h('button',{type:'button',onClick:()=>setDetailId(item.id)},t('详情')):null),op&&!busy?operationNotice(op):null);};
1102
+ const detailItem=items.find(item=>item.id===detailId);
1060
1103
  const catalogSection=receipt?h('div',{className:'hm-app-frame'},h('div',null,h('strong',null,receipt.instance.appId),h('button',{type:'button',disabled:closing,onClick:()=>void close()},closing?t('关闭中…'):t('关闭视图'))),h('iframe',{ref:frame,src:receipt.uiUrl,title:receipt.instance.appId,sandbox:'allow-forms allow-modals allow-popups allow-same-origin allow-scripts'})):
1061
- h('div',{className:'hm-library-grid',key:'catalog'},...items.map(card));
1062
- const moreButton=nextCursor&&!receipt?h('button',{type:'button',className:'hm-library-more',key:'more',onClick:()=>void more()},t('更多')):null;
1063
- const installedSection=!receipt&&(installed.length||plugins.length)?h('section',{className:'hm-market-installed',key:'installed'},h('h2',null,t('已安装')),h('ul',null,
1064
- ...installed.map(row=>h('li',{key:'app:'+row.packageName,'data-hanamesh-installed':row.packageName},t('应用 · ')+(row.name??row.packageName)+' · '+row.packageName+' '+(row.version??'')+' · '+(t(STATE_TEXT[row.state]??row.state)))),
1104
+ detailItem?h('div',{key:'detail'},h('button',{type:'button',onClick:()=>setDetailId(null)},t('返回应用库')),card(detailItem,true)):h('div',{className:'hm-library-grid',key:'catalog'},...items.map(item=>card(item)),snapshot&&items.length===0?h('p',{role:'status'},t('没有找到符合条件的应用。')):null);
1105
+ const moreButton=nextCursor&&!receipt&&!detailItem?h('button',{type:'button',className:'hm-library-more',key:'more',onClick:()=>void more()},t('更多')):null;
1106
+ const installedSection=!receipt&&!detailItem&&(installed.length||plugins.length)?h('section',{className:'hm-market-installed',key:'installed'},h('h2',null,t('已安装')),h('ul',null,
1107
+ ...installed.map(row=>h('li',{key:'app:'+row.packageName,'data-hanamesh-installed':row.packageName},t('应用 · ')+(row.name??row.packageName)+' · '+row.packageName+' '+(row.version??'')+' · '+(t(STATE_TEXT[row.state]??row.state)),row.state==='registered'?openButton(row):null)),
1065
1108
  ...plugins.filter(row=>!PROTECTED.has(row.packageName)).map(row=>h('li',{key:'plugin:'+row.packageName,'data-hanamesh-installed':row.packageName},t('插件 · ')+row.packageName+' '+(row.version??'')+' · '+(t(STATE_TEXT[row.state]??row.state))+(row.bundle&&!row.active&&row.state==='installed'?t('(未启用为 profile 层)'):''),
1066
1109
  row.state==='installed'&&!PROTECTED.has(row.packageName)&&!pending['plugin:'+row.packageName]?h('button',{type:'button',onClick:()=>void operate('/hanamesh/library/plugins/uninstall',{packageName:row.packageName},'plugin:'+row.packageName,t('卸载'))},t('卸载')):pending['plugin:'+row.packageName]?operationNotice(pending['plugin:'+row.packageName]):null)))):null;
1067
1110
  // The seat owner may ask for a subsection to come first; `installed` is the only one it names today.
1068
- const body=preferredSubsectionId==='installed'?[installedSection,catalogSection,moreButton]:[catalogSection,moreButton,installedSection];
1069
- return h('section',{className:embedded?'hm-market hm-market-embedded':'hm-library-overlay hm-market'},
1070
- h('header',null,h('div',null,h('h1',null,t('HanaMesh 市场'))),embedded?null:h('button',{type:'button',onClick:()=>setLibraryVisible(false)},t('关闭'))),
1111
+ const body=mainPage?(libraryTab==='installed'?[installedSection??h('p',{role:'status'},t('还没有已安装项。'))]:[catalogSection,moreButton]):preferredSubsectionId==='installed'?[installedSection,catalogSection,moreButton]:[catalogSection,moreButton,installedSection];
1112
+ const reviewing=mainPage&&target!==null,detailing=mainPage&&Boolean(detailItem)&&!reviewing;
1113
+ return h('section',{className:(embedded?'hm-market hm-market-embedded':'hm-library-overlay hm-market')+(mainPage?' hm-apphost-page':'')},
1114
+ mainPage?h('div',{className:'hm-apphost-breadcrumb'},t(detailing?'HanaMesh / 应用详情':'HanaMesh / 应用库')):null,
1115
+ h('header',null,h('div',null,mainPage?h('div',{className:'hm-apphost-eyebrow'},reviewing?'APPHOST / INSTALL':detailing?'APPLICATION / DETAIL':'APPHOST / LIBRARY'):null,h('h1',null,mainPage?(detailing?detailItem.displayName:t(reviewing?'安装之前,先看清楚。':'好工具,让想法走得更远。')):t('应用库'))),mainPage?h('button',{type:'button',onClick:()=>{try{pageLayout.selectPanel('hanamesh-environment');}catch{setError(new Error('CORE_PAGE_UNAVAILABLE'));}}},t('环境与来源')):embedded?null:h('button',{type:'button',onClick:()=>setLibraryVisible(false)},t('关闭'))),
1116
+ mainPage?h('p',null,detailing?detailItem.summary:t('发现适合你的工具,查看来源后再安装;账号与模型授权分别管理。')):null,
1071
1117
  h(MarketConflictNotice,null),h(HandoffIssueNotice,null),
1072
1118
  snapshot?.restartBaseline?.known===false?h('p',{role:'alert',className:'hm-library-failed'},t('启动时没能读取 profile(')+snapshot.restartBaseline.code+t('),「需重启」状态无法判断,下面的状态都按已加载显示')):null,
1073
1119
  verification===true?h('section',{className:'hm-install-target','aria-label':t('安装目标开发小界面')},h('h2',null,t('安装目标')),
@@ -1075,14 +1121,15 @@ h('div',{className:'hm-source-add'},h('input',{value:draft,placeholder:'https://
1075
1121
  h('form',{onSubmit:event=>{event.preventDefault();void openInstallTarget({...(targetItemId.trim()?{itemId:targetItemId.trim()}:{}),...(targetPackageName.trim()?{packageName:targetPackageName.trim()}:{} )}).catch(()=>{});}},
1076
1122
  h('input',{'aria-label':t('目录条目 ID'),value:targetItemId,placeholder:t('itemId(可选)'),onChange:event=>setTargetItemId(event.target.value)}),
1077
1123
  h('input',{'aria-label':t('包名'),value:targetPackageName,placeholder:t('packageName(可选)'),onChange:event=>setTargetPackageName(event.target.value)}),h('button',{type:'submit'},t('查看安装目标')))):null,
1078
- target?h('div',{role:target.status==='confirmation-required'?'dialog':'status','aria-label':t('市场安装确认')},
1124
+ target?h('div',{role:target.status==='confirmation-required'?'dialog':'status','aria-label':t('市场安装确认'),className:'hm-install-target hm-install-review'},h('h2',null,t('安装确认')),
1079
1125
  target.item?h('dl',null,h('dt',null,t('插件名')),h('dd',null,target.item.displayName),h('dt',null,t('包名')),h('dd',null,target.item.package.name),h('dt',null,t('来源')),h('dd',null,target.source?.kind==='fixture'?t('测试目录(fixture)'):target.source?.manifestUrl??t('当前市场目录'))):null,
1126
+ target.item?.installTarget?.source==='npm'?h('dl',{'data-npm-confirmation':true},h('dt',null,t('精确版本')),h('dd',null,target.item.installTarget.version)):null,
1080
1127
  target.item?.installTarget?.source==='github'?h('dl',{'data-github-confirmation':true},h('dt',null,t('来源仓库')),h('dd',null,'https://github.com/'+target.item.installTarget.repo),h('dt',null,t('固定 commit')),h('dd',null,target.item.installTarget.ref),...(target.item.installTarget.path?[h('dt',null,t('仓库子目录')),h('dd',null,target.item.installTarget.path)]:[])):null,
1081
1128
  target.item?.installTarget?(target.item.installTarget.allowBuild.length?h('div',{role:'alert','data-build-warning':true},h('strong',null,t('会在本机执行构建脚本')),h('p',null,target.item.installTarget.allowBuild.join(', ')),h('p',null,t('确认后将按名单允许构建;pnpm 会把许可保存到当前 profile。'))):h('p',null,t('未授予构建脚本许可'))):null,
1082
1129
  target.item?.suiteUpgrade?h('ul',null,...target.item.suiteUpgrade.components.map(c=>h('li',{key:c.name},c.label+':'+c.from+' → '+c.to)),h('li',null,t('确认后升级 HanaMesh 总包及其锁定组件;需要完全退出并重新打开客户端。'))):null,
1083
1130
  target.item?.suiteUpgrade?.registry?h('p',null,'Registry:'+target.item.suiteUpgrade.registry):null,
1084
1131
  ...(target.item?.suiteUpgrade?.blocked??target.details?.blocked??[]).map((row,i)=>h('p',{key:'suite-blocked-'+i,role:'status'},row.code+(row.moduleName?' · '+row.moduleName:'')+(row.bundleVersion?' · HanaMesh '+row.bundleVersion:''))),
1085
- target.status==='confirmation-required'?h('div',null,h('button',{type:'button',onClick:()=>void confirmTarget()},t('确认安装')),h('button',{type:'button',onClick:()=>{targetSequence++;void reportHandoff(targetSelection?.handoffId,'cancelled');setTarget({...targetSelection,status:'cancelled'});}},t('取消'))):
1132
+ target.status==='confirmation-required'?h('div',null,h('label',{className:'hm-install-ack'},h('input',{type:'checkbox','aria-label':t('我已核对来源与安装目标'),checked:acceptedTarget===target,onChange:event=>setAcceptedTarget(event.currentTarget.checked?target:null)}),t('我已核对来源与安装目标')),h('button',{type:'button',disabled:acceptedTarget!==target,onClick:()=>void confirmTarget()},t('确认安装')),h('button',{type:'button',onClick:()=>{targetSequence++;void reportHandoff(targetSelection?.handoffId,'cancelled');setTarget({...targetSelection,status:'cancelled'});}},t('取消'))):
1086
1133
  target.status==='failed'?FailureNotice({error:target.diagnostic??target.error}):h('p',{role:['readback-failed','suite-upgrade-failed'].includes(target.status)?'alert':'status'},({resolving:t('正在查询市场目录…'),installing:t('安装中…'),installed:t('安装完成,请查看已安装列表'),'installed-live':t('安装完成,已加载,可以直接使用'),cancelled:t('已取消,未安装'),'result-unavailable':t('安装结果不可追溯,请查看已安装列表;不会自动重试'),'already-installed':t('已安装,无需重复安装'),failed:t('未安装:')+(target.error??pending[target.item?.id]?.text??t('安装失败')),'readback-failed':target.error,'suite-upgrade-failed':suiteTargetFailure(target)})[target.status]),
1087
1134
  target.item?.source?h('div',{'aria-label':t('目录条目来源')},h('p',null,target.item.source.sourceId+' · '+target.item.source.sourceRef),...(target.item.source.sourceLinks??[]).map((link,index)=>h('p',{key:index},h('a',{href:link.sourceUrl,target:'_blank',rel:'noopener noreferrer'},link.sourceId+' · '+link.sourceRef),h('small',null,' '+link.firstSeenAt+' / '+link.lastSeenAt)))):null,
1088
1135
  target.item?operationNotice(pending[target.item.id]):null,
@@ -1093,11 +1140,13 @@ h('div',{className:'hm-source-add'},h('input',{value:draft,placeholder:'https://
1093
1140
  h('textarea',{'aria-label':t('合约样例输入'),value:verificationInput,onChange:event=>setVerificationInput(event.target.value)}),
1094
1141
  h('button',{type:'button',disabled:verifying,onClick:()=>void verify()},t('调用合约')),
1095
1142
  h('pre',{'aria-label':t('合约调用输出')},verificationOutput===null?t('尚未调用'):JSON.stringify(verificationOutput,null,2))):null,
1096
- h('div',{className:'hm-library-toolbar'},h('input',{type:'search',placeholder:t('搜索应用与插件'),value:query,onChange:e=>setQuery(e.target.value),onKeyDown:e=>{if(e.key==='Enter')void load();}}),h('button',{type:'button',onClick:()=>void load()},t('搜索')),
1143
+ reviewing||detailing?null:h('div',{className:'hm-library-toolbar'},h('input',{type:'search',placeholder:t('搜索应用与插件'),value:query,onChange:e=>setQuery(e.target.value),onKeyDown:e=>{if(e.key==='Enter')void load();}}),h('button',{type:'button',onClick:()=>void load()},t('搜索')),
1097
1144
  h('select',{'aria-label':t('类别'),value:filter,onChange:e=>setFilter(e.target.value)},...filters.map(([value,label])=>h('option',{key:value||'all',value},t(label))))),
1145
+ mainPage&&!reviewing&&!detailing?h('div',{role:'tablist','aria-label':t('应用筛选'),className:'hm-library-tabs'},...[["all","全部应用"],["installed","已安装"]].map(([value,label])=>h('button',{type:'button',role:'tab',key:value,'aria-selected':libraryTab===value,onClick:()=>{setLibraryTab(value);setDetailId(null);}},t(label)))):null,
1146
+ reviewing&&target.status!=='confirmation-required'&&target.status!=='installing'?h('button',{type:'button',onClick:()=>setTarget(null)},t('返回应用库')):null,
1098
1147
  restartRequired?h(RestartNotice,null):null,
1099
1148
  error?FailureNotice({error}):null,
1100
- ...body);
1149
+ ...(reviewing?[]:body),mainPage?h('footer',null,'HanaMesh · Let every use bloom.'):null);
1101
1150
  }
1102
1151
  /** The `market` seat object read by the shell's Extension Management panel. */
1103
1152
  function createMarketSeat({verification=false}={}){
@@ -1132,11 +1181,57 @@ h('div',{className:'hm-source-add'},h('input',{value:draft,placeholder:'https://
1132
1181
  })();
1133
1182
  return()=>{released=true;setMarketConflict('');setEntryVisible(true);dispose?.();};
1134
1183
  }
1184
+ // These four counts are not present in the current catalog-view contract.
1185
+ // Never substitute local package state, publisher badges or guessed zeroes.
1186
+ // WX11/12: the existing Web detail projects Host 0.6.54+ githubStars as stars.
1187
+ // The incremental signals feed is a server ingestion API, never a browser scan.
1188
+ const credibilityCount=value=>Number.isSafeInteger(value)&&value>=0?value:null;
1189
+ const credibilityTime=value=>typeof value==='string'&&Number.isFinite(Date.parse(value))?value:null;
1190
+ function credibilityFacts(facts){return[
1191
+ {id:'stars',label:'GitHub 星数',value:credibilityCount(facts?.signals?.stars)},
1192
+ {id:'npm30',label:'npm 下载 / 30 天',value:credibilityCount(facts?.signals?.npmDownloads30d)},
1193
+ {id:'installs',label:'DSH 安装 / 30 天',value:credibilityCount(facts?.signals?.storeInstalls30d)},
1194
+ {id:'users',label:'使用人数 / 30 天',value:credibilityCount(facts?.popularity?.users30d)}
1195
+ ].map(row=>({...row,reason:row.value===null?'catalog-field-unavailable':null}));}
1196
+ async function readCredibility(source,item,fetcher=globalThis.fetch){
1197
+ const link=catalogWebsiteLink(source,item),empty={itemId:item?.id,signals:null,popularity:null,checkedAt:null,asOf:null};
1198
+ if(!link||typeof fetcher!=='function')return empty;
1199
+ const origin=new URL(link).origin,id=encodeURIComponent(item.id),controller=new AbortController(),timer=setTimeout(()=>controller.abort(),8000);
1200
+ const read=async path=>{const response=await fetcher(origin+path,{method:'GET',credentials:'omit',redirect:'error',cache:'no-store',signal:controller.signal,headers:{accept:'application/json'}});if(!response.ok)throw Error('CREDIBILITY_UNAVAILABLE');const value=await response.json();if(value?.hanaId!==item.id)throw Error('CREDIBILITY_ID_MISMATCH');return value;};
1201
+ try{const [detail,popularity]=await Promise.allSettled([read('/api/v2/hanas/'+id),read('/hm/v1/custody/hanas/'+id+'/popularity')]);
1202
+ const signals=detail.status==='fulfilled'?detail.value.signals:null,pop=popularity.status==='fulfilled'?popularity.value:null;
1203
+ return {...empty,signals,popularity:pop,checkedAt:credibilityTime(signals?.checkedAt),asOf:credibilityTime(pop?.asOf)};
1204
+ }finally{clearTimeout(timer);}
1205
+ }
1206
+ function catalogWebsiteLink(source,item){
1207
+ if(source?.kind==='fixture'||typeof source?.manifestUrl!=='string'||typeof item?.id!=='string')return null;
1208
+ try{const url=new URL(source.manifestUrl);if(url.pathname!=='/catalog-source.json'||url.searchParams.get('view')!=='hanamesh')return null;return new URL('/hana/'+encodeURIComponent(item.id),url.origin).href;}catch{return null;}
1209
+ }
1210
+ function ConnectionTabs({selected,navigate}){return h('nav',{className:'hm-connection-tabs','aria-label':t('设置与连接')},...[["hanamesh-account","账号与设备"],["hanamesh-providers","模型连接"],["hanamesh-environment","环境与来源"]].map(([id,label])=>h('button',{type:'button',key:id,'aria-current':id===selected?'page':undefined,onClick:()=>navigate(id)},t(label))));}
1211
+ function ProvidersPage({navigate}){useLocale();const[error,setError]=React.useState(false);const go=id=>{try{navigate(id);setError(false);}catch{setError(true);}};return h('div',{className:'hm-apphost-page'},h('div',{className:'hm-apphost-breadcrumb'},t('HanaMesh / 模型连接')),h('header',null,h('div',{className:'hm-apphost-eyebrow'},'MODELS / PROVIDERS'),h('h1',null,t('连接模型,保持掌控。')),h('p',null,t('登录账号不提供模型凭据。沿用宿主连接,按应用分别查看授权。'))),h(ConnectionTabs,{selected:'hanamesh-providers',navigate:go}),error?h('p',{role:'alert'},t('对应页面暂不可用,请确认 HanaMesh Core 已启用。')):null,h('div',{className:'hm-apphost-card'},h(ProvidersSection)),h('footer',null,'HanaMesh · Let every use bloom.'));}
1212
+ function EnvironmentContent(){useLocale();const[expanded,setExpanded]=React.useState({});return h('div',{className:'hm-apphost-environment'},h('section',{className:'hm-apphost-card'},h(NodeRuntimeSection)),...[["sources","目录来源",LibrarySourcesSection],["suite","套件安装包来源",SuiteSourceSettingsSection],["handoff","网站安装交接",HandoffSettingsSection]].map(([id,label,Component])=>h('details',{key:id,className:'hm-apphost-card',onToggle:event=>{const open=event.currentTarget.open;setExpanded(previous=>({...previous,[id]:open}));}},h('summary',null,t(label)),expanded[id]?h(Component):h('p',null,t('展开后读取当前设置;不会自动保存或安装。')))));}
1213
+ function registerAppHostPages(ctx){
1214
+ const previous=pageLayout;pageLayout=ctx.layout;const releases=[];
1215
+ releases.push(ctx.slots.inject('main',()=>ctx.slots.register({name:'main',key:'hanamesh-library'},()=>h(LibraryOverlay,{embedded:true,mainPage:true}))));
1216
+ releases.push(ctx.slots.inject('main',()=>ctx.slots.register({name:'main',key:'hanamesh-providers'},()=>h(ProvidersPage,{navigate:id=>ctx.layout.selectPanel(id)}))));
1217
+ releases.push(ctx.slots.inject('sidebar.panellist',()=>ctx.slots.register({name:'sidebar.panellist',id:'hanamesh-library',order:-20,label:t('应用库')},AppLibraryIcon)));
1218
+ return()=>{for(const dispose of releases.reverse())dispose();if(pageLayout===ctx.layout)pageLayout=previous;};
1219
+ }
1220
+
1221
+ function AppLibraryIcon({size=20}){return h('span',{'aria-hidden':true,style:{display:'block',width:size,height:size,background:'currentColor',maskImage:'url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWdyaWQyeDIgbHVjaWRlLWdyaWQtMngyIiBhcmlhLWhpZGRlbj0idHJ1ZSI+PHBhdGggZD0iTTEyIDN2MTgiPjwvcGF0aD48cGF0aCBkPSJNMyAxMmgxOCI+PC9wYXRoPjxyZWN0IHg9IjMiIHk9IjMiIHdpZHRoPSIxOCIgaGVpZ2h0PSIxOCIgcng9IjIiPjwvcmVjdD48L3N2Zz4=")',maskSize:'contain',maskRepeat:'no-repeat'}});}
1222
+
1223
+ const appHostPageStyles=`
1224
+ .hm-apphost-page{height:100%;overflow:auto;box-sizing:border-box;padding:36px 38px;max-width:1360px;margin:auto;color:var(--dsw-alias-label-primary,CanvasText);background:var(--dsw-alias-bg-base,Canvas);font:14px/1.55 var(--dsw-font-family,system-ui)}.hm-apphost-page *{box-sizing:border-box}.hm-apphost-page h1{font-size:32px;line-height:1.25;letter-spacing:-.025em;margin:0}.hm-apphost-page h2{font-size:17px}.hm-apphost-page p{color:var(--dsw-alias-label-secondary);line-height:1.7}.hm-apphost-breadcrumb{font-size:12px;color:var(--dsw-alias-label-tertiary);margin-bottom:28px}.hm-apphost-eyebrow{font-size:11px;font-weight:700;letter-spacing:.16em;color:var(--dsw-alias-brand-primary);margin-bottom:10px}.hm-apphost-page>header{margin-bottom:28px}.hm-apphost-page button,.hm-apphost-page select,.hm-apphost-page input{font:inherit;color:inherit;min-height:44px;padding:10px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-3);max-width:100%}.hm-apphost-page button{cursor:pointer}.hm-apphost-page button:disabled{opacity:.5;cursor:default}.hm-apphost-page :focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:3px}.hm-connection-tabs{display:flex;gap:8px;flex-wrap:wrap;margin:28px 0 24px}.hm-connection-tabs [aria-current=page]{color:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}.hm-apphost-card{padding:22px;border:1px solid var(--dsw-alias-border-l1);border-radius:18px;background:var(--dsw-alias-bg-layer-2);min-width:0}.hm-apphost-environment{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px;align-items:start}.hm-apphost-environment>details summary{cursor:pointer;font-weight:600;min-height:28px}.hm-apphost-page .hm-library-grid{grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}.hm-apphost-page .hm-library-grid article{padding:22px;border-radius:18px;background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l1);display:flex;flex-direction:column;gap:16px}.hm-apphost-page .hm-library-grid h3{margin:0;font-size:16px}.hm-apphost-page .hm-market-actions{margin-top:auto}.hm-apphost-page .hm-library-toolbar{gap:12px;margin:24px 0}.hm-apphost-page .hm-app-detail{padding:22px;border:1px solid var(--dsw-alias-border-l1);border-radius:18px;background:var(--dsw-alias-bg-layer-2);margin:20px 0;overflow-wrap:anywhere}.hm-apphost-page .hm-app-detail h3{font-size:28px;margin:0 0 12px}.hm-credibility{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}.hm-credibility>div{padding:16px;border:1px solid var(--dsw-alias-border-l1);border-radius:14px;background:var(--dsw-alias-bg-layer-1)}.hm-credibility span,.hm-credibility small{display:block;font-size:12px;color:var(--dsw-alias-label-tertiary)}.hm-credibility strong{display:block;font:26px/1.8 var(--dsh-font-family-code-default,monospace)}.hm-apphost-page .hm-install-review{padding:22px;border-radius:18px;background:var(--dsw-alias-bg-layer-2);margin:24px 0}.hm-install-ack{display:flex;align-items:center;gap:10px;margin:18px 0}.hm-install-ack input{width:20px;min-height:20px;flex:none;accent-color:var(--dsw-alias-brand-primary)}.hm-apphost-page .hm-website-link{display:inline-block;min-height:44px;color:var(--dsw-alias-brand-primary);padding:10px 0}.hm-library-tabs{display:flex;gap:5px;padding:4px;border:1px solid var(--dsw-alias-border-l1);border-radius:11px;width:max-content;margin:20px 0}.hm-library-tabs [aria-selected=true]{background:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-brand-primary)}.hm-apphost-page footer{font-size:11px;color:var(--dsw-alias-label-tertiary);margin-top:26px}
1225
+ @media(max-width:1000px){.hm-apphost-page .hm-library-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:640px){.hm-apphost-page>header{display:block}.hm-apphost-page>header button{margin-top:16px}.hm-apphost-page{padding:24px 18px}.hm-apphost-page h1{font-size:26px}.hm-apphost-page .hm-library-grid,.hm-apphost-environment,.hm-credibility{grid-template-columns:minmax(0,1fr)}.hm-apphost-card{padding:18px}}
1226
+ `;
1227
+
1135
1228
  const name='hanamesh-app-host-client',inject=['slots','locale'];
1136
1229
  // No cross-plugin reads here: the browser-side cordis context never carries host services such as hanameshCore
1137
1230
  // (dsh-client-modules boots its own root context); Core status lives in Core's own Settings section.
1138
1231
  function localizedSection(ctx,id,order,label,component){return ctx.slots.inject('settings.section',()=>{let dispose;const refresh=()=>{dispose?.();dispose=ctx.slots.register({name:'settings.section',id,order,label:t(label)},component);};refresh();const unsubscribe=localeService?.subscribe(refresh);return()=>{unsubscribe?.();dispose?.();};});}
1139
- function apply(ctx,{verification=false}={}){localeService=ctx.locale??null;if(localeService)ctx.effect(()=>localeService.register('hanamesh.apphost',COPY),'hanamesh-app-host:locale');ctx.effect(()=>{const style=document.createElement('style');style.textContent='.hm-node-runtime{display:grid;gap:12px;max-width:900px;color:inherit}.hm-node-fields{display:grid;gap:10px}.hm-node-fields label{display:grid;gap:6px}.hm-node-runtime input,.hm-node-runtime select,.hm-node-runtime button{font:inherit;color:inherit;background:transparent;border:1px solid color-mix(in srgb,currentColor 30%,transparent);border-radius:8px;min-height:44px;padding:8px 12px;box-sizing:border-box;max-width:100%}.hm-node-runtime button{cursor:pointer}.hm-node-runtime button:hover:not(:disabled){background:color-mix(in srgb,currentColor 9%,transparent)}.hm-node-runtime button:focus-visible{outline:2px solid currentColor;outline-offset:2px}.hm-node-runtime button:disabled{opacity:.5;cursor:default}.hm-node-confirm,.hm-node-progress{border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:12px;padding:16px}.hm-node-confirm dl{display:grid;grid-template-columns:100px minmax(0,1fr);gap:8px}.hm-node-runtime dd{margin:0}.hm-node-runtime code,.hm-node-runtime dd{overflow-wrap:anywhere}.hm-node-runtime progress{width:100%}.hm-node-runtime [role=alert]{border-left:3px solid currentColor;padding-left:10px}'+'.hm-providers,.hm-library-sources{display:grid;gap:16px}.hm-providers table{width:100%;border-collapse:collapse}.hm-providers th,.hm-providers td{padding:8px;text-align:left;border-bottom:1px solid color-mix(in srgb,currentColor 14%,transparent)}.hm-provider-card{padding:16px;border:1px solid color-mix(in srgb,currentColor 18%,transparent);border-radius:12px}.hm-provider-row{display:grid;grid-template-columns:minmax(120px,1fr) auto minmax(180px,1fr) auto;gap:8px;align-items:center;margin:8px 0}.hm-source-row{display:grid;grid-template-columns:minmax(0,1fr) repeat(4,auto);gap:8px}.hm-source-row code{overflow-wrap:anywhere}.hm-source-add{display:flex;gap:8px}.hm-source-add input{flex:1}.hm-library-overlay{position:absolute;inset:0;z-index:30;pointer-events:auto;overflow:auto;padding:24px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}.hm-library-overlay button,.hm-library-overlay input,.hm-library-overlay select,.hm-market-embedded button,.hm-market-embedded input,.hm-market-embedded select{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l2)}.hm-library-overlay>header{display:flex;justify-content:space-between;align-items:center}.hm-library-toolbar{display:flex;gap:8px;align-items:center;margin:8px 0}.hm-library-toolbar input[type=search]{flex:1;padding:6px 10px}.hm-library-more{margin:12px auto;display:block}.hm-library-failed{color:var(--dsw-alias-state-error-primary);margin:0}.hm-library-ok{color:var(--dsw-alias-state-success-primary);margin:0}.hm-restart-notice{padding:10px 12px;border:1px solid var(--dsw-alias-state-warn-primary);border-radius:8px;background:var(--dsw-alias-state-warn-tertiary);margin:8px 0}.hm-restart-link{margin-left:4px;font-weight:600}.hm-market-state{font-size:12px;color:var(--dsw-alias-label-secondary)}.hm-market-actions{display:flex;gap:8px;flex-wrap:wrap}.hm-market-embedded{display:block}.hm-market-embedded>header{display:flex;justify-content:space-between;align-items:center}.hm-market-conflict{padding:10px 12px;border:1px solid var(--dsw-alias-state-business-primary);border-radius:8px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 8%,transparent);margin:8px 0}.hm-install-target{display:grid;gap:8px;padding:16px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;margin:12px 0}.hm-install-target form{display:flex;gap:8px;flex-wrap:wrap}.hm-install-target input{flex:1;min-width:180px;padding:6px 10px}.hm-install-target dl{display:grid;grid-template-columns:80px 1fr;gap:6px}.hm-install-target dd{margin:0;overflow-wrap:anywhere}.hm-market-installed{margin-top:24px}.hm-market-installed ul{padding-left:18px;display:grid;gap:6px}.hm-library-toolbar select{padding:6px 10px}.hm-library-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:16px}.hm-library-grid article{display:grid;gap:10px;padding:16px;border:1px solid color-mix(in srgb,currentColor 16%,transparent);border-radius:12px}.hm-app-frame{display:grid;grid-template-rows:auto 1fr;height:calc(100vh - 130px)}.hm-market-embedded>.hm-app-frame{position:fixed;inset:0;z-index:1001;height:100vh;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);padding:12px 16px}.hm-app-frame>div{display:flex;justify-content:space-between}.hm-app-frame iframe{width:100%;height:100%;border:0}';style.textContent+='/* Only AppHost surfaces: never resize the host settings shell. */\n.hm-node-runtime,.hm-library-sources,.hm-providers,.hm-market{min-width:0;width:100%;max-width:100%;box-sizing:border-box;overflow-wrap:anywhere}\n.hm-node-runtime>*,.hm-library-sources>*,.hm-node-fields>*,.hm-handoff-settings>*,.hm-library-toolbar>*{min-width:0;max-width:100%;box-sizing:border-box}\n.hm-library-sources input,.hm-library-sources textarea,.hm-library-sources select,.hm-node-runtime input,.hm-node-runtime select{min-width:0;width:100%;max-width:100%;box-sizing:border-box;font:inherit;color:inherit;background:var(--dsw-alias-bg-layer-2)}\n.hm-library-sources button,.hm-market button{min-height:44px;max-width:100%;white-space:normal;font:inherit;color:inherit;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px 12px}\n.hm-library-sources button:focus-visible,.hm-market button:focus-visible{outline:2px solid currentColor;outline-offset:2px}\n.hm-handoff-settings{display:grid;gap:12px}.hm-handoff-settings label{display:grid;gap:6px}.hm-handoff-settings input[type=checkbox]{width:auto}\n.hm-node-confirm,.hm-node-progress{min-width:0;max-width:100%;box-sizing:border-box}\n.hm-failure pre{max-width:100%;white-space:pre-wrap;overflow-wrap:anywhere;font-size:12px}.hm-failure summary{cursor:pointer}.hm-failure p{margin:8px 0}\n.hm-library-toolbar,.hm-source-add{flex-wrap:wrap}.hm-library-toolbar input[type=search]{min-width:0;width:100%;flex:1 1 180px}\n.hm-library-grid{grid-template-columns:repeat(auto-fill,minmax(min(240px,100%),1fr))}\n.hm-providers{overflow-x:auto}.hm-source-row{grid-template-columns:minmax(0,1fr);gap:6px}\n@media(max-width:640px){.hm-library-overlay{padding:12px}.hm-node-confirm{padding:10px}.hm-node-confirm dl,.hm-install-target dl{grid-template-columns:minmax(0,1fr)}.hm-node-confirm dt{font-weight:600}.hm-node-confirm dd{margin-bottom:6px}.hm-library-toolbar select{width:100%}}\n';style.textContent+='\n.hm-provider-records td{overflow-wrap:anywhere}.hm-providers select,.hm-providers input{min-width:0;max-width:100%;width:100%;box-sizing:border-box}.hm-provider-records details>summary{cursor:pointer;min-height:44px;display:flex;align-items:center}.hm-provider-records pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:12px}\n@media(max-width:640px){.hm-providers{overflow:visible}.hm-provider-records,.hm-provider-records tbody{display:block}.hm-provider-records thead{display:none}.hm-provider-records tr{display:grid;grid-template-columns:minmax(0,1fr);gap:12px;padding:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;margin:10px 0}.hm-provider-records td{display:block;min-width:0;padding:0;border:0}.hm-provider-records td::before{content:attr(data-label);display:block;font-weight:600;margin-bottom:4px}.hm-provider-records td:empty{display:none}}\n';style.textContent+="\n.hm-providers,.hm-library-sources,.hm-node-runtime{overflow-wrap:normal;word-break:normal}\n.hm-providers h2,.hm-library-sources h2,.hm-node-runtime h2{margin:0;font-weight:600;line-height:1.5}\n.hm-providers p,.hm-library-sources p,.hm-node-runtime p{margin:0;line-height:1.6}\n.hm-suite-fields,.hm-suite-fields>label,.hm-handoff-settings,.hm-source-add{display:grid;grid-template-columns:minmax(0,1fr);gap:10px;min-width:0}\n.hm-suite-fields>*,.hm-source-add>*,.hm-providers>*,.hm-library-sources details,.hm-node-runtime details{min-width:0;max-width:100%;box-sizing:border-box}\n.hm-library-sources input,.hm-library-sources textarea,.hm-library-sources select,.hm-suite-fields select,.hm-providers select,.hm-providers input{display:block;width:100%;min-width:0;max-width:100%;box-sizing:border-box;min-height:44px;padding:8px;font:inherit;border:1px solid var(--dsw-alias-border-l2);border-radius:8px}\n.hm-library-sources input[type=checkbox]{display:inline-block;width:20px;min-height:20px;max-width:20px}\n.hm-library-sources summary,.hm-providers summary,.hm-node-runtime summary{min-height:44px;display:flex;align-items:center;cursor:pointer}\n.hm-library-sources code,.hm-library-sources dd,.hm-providers pre{overflow-wrap:anywhere;white-space:pre-wrap}\n.hm-handoff-settings>button,.hm-suite-fields>button{width:100%}\n@media(max-width:640px){.hm-provider-records tr{padding:8px}.hm-node-fields{grid-template-columns:minmax(0,1fr)}}\n";document.head.append(style);return()=>style.remove();},'hanamesh-app-host:style');
1232
+ function apply(ctx,{verification=false}={}){localeService=ctx.locale??null;if(localeService)ctx.effect(()=>localeService.register('hanamesh.apphost',COPY),'hanamesh-app-host:locale');ctx.effect(()=>{const style=document.createElement('style');style.textContent='.hm-node-runtime{display:grid;gap:12px;max-width:900px;color:inherit}.hm-node-fields{display:grid;gap:10px}.hm-node-fields label{display:grid;gap:6px}.hm-node-runtime input,.hm-node-runtime select,.hm-node-runtime button{font:inherit;color:inherit;background:transparent;border:1px solid color-mix(in srgb,currentColor 30%,transparent);border-radius:8px;min-height:44px;padding:8px 12px;box-sizing:border-box;max-width:100%}.hm-node-runtime button{cursor:pointer}.hm-node-runtime button:hover:not(:disabled){background:color-mix(in srgb,currentColor 9%,transparent)}.hm-node-runtime button:focus-visible{outline:2px solid currentColor;outline-offset:2px}.hm-node-runtime button:disabled{opacity:.5;cursor:default}.hm-node-confirm,.hm-node-progress{border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:12px;padding:16px}.hm-node-confirm dl{display:grid;grid-template-columns:100px minmax(0,1fr);gap:8px}.hm-node-runtime dd{margin:0}.hm-node-runtime code,.hm-node-runtime dd{overflow-wrap:anywhere}.hm-node-runtime progress{width:100%}.hm-node-runtime [role=alert]{border-left:3px solid currentColor;padding-left:10px}'+'.hm-providers,.hm-library-sources{display:grid;gap:16px}.hm-providers table{width:100%;border-collapse:collapse}.hm-providers th,.hm-providers td{padding:8px;text-align:left;border-bottom:1px solid color-mix(in srgb,currentColor 14%,transparent)}.hm-provider-card{padding:16px;border:1px solid color-mix(in srgb,currentColor 18%,transparent);border-radius:12px}.hm-provider-row{display:grid;grid-template-columns:minmax(120px,1fr) auto minmax(180px,1fr) auto;gap:8px;align-items:center;margin:8px 0}.hm-source-row{display:grid;grid-template-columns:minmax(0,1fr) repeat(4,auto);gap:8px}.hm-source-row code{overflow-wrap:anywhere}.hm-source-add{display:flex;gap:8px}.hm-source-add input{flex:1}.hm-library-overlay{position:absolute;inset:0;z-index:30;pointer-events:auto;overflow:auto;padding:24px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}.hm-library-overlay button,.hm-library-overlay input,.hm-library-overlay select,.hm-market-embedded button,.hm-market-embedded input,.hm-market-embedded select{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l2)}.hm-library-overlay>header{display:flex;justify-content:space-between;align-items:center}.hm-library-toolbar{display:flex;gap:8px;align-items:center;margin:8px 0}.hm-library-toolbar input[type=search]{flex:1;padding:6px 10px}.hm-library-more{margin:12px auto;display:block}.hm-library-failed{color:var(--dsw-alias-state-error-primary);margin:0}.hm-library-ok{color:var(--dsw-alias-state-success-primary);margin:0}.hm-restart-notice{padding:10px 12px;border:1px solid var(--dsw-alias-state-warn-primary);border-radius:8px;background:var(--dsw-alias-state-warn-tertiary);margin:8px 0}.hm-restart-link{margin-left:4px;font-weight:600}.hm-market-state{font-size:12px;color:var(--dsw-alias-label-secondary)}.hm-market-actions{display:flex;gap:8px;flex-wrap:wrap}.hm-market-embedded{display:block}.hm-market-embedded>header{display:flex;justify-content:space-between;align-items:center}.hm-market-conflict{padding:10px 12px;border:1px solid var(--dsw-alias-state-business-primary);border-radius:8px;background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 8%,transparent);margin:8px 0}.hm-install-target{display:grid;gap:8px;padding:16px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;margin:12px 0}.hm-install-target form{display:flex;gap:8px;flex-wrap:wrap}.hm-install-target input{flex:1;min-width:180px;padding:6px 10px}.hm-install-target dl{display:grid;grid-template-columns:80px 1fr;gap:6px}.hm-install-target dd{margin:0;overflow-wrap:anywhere}.hm-market-installed{margin-top:24px}.hm-market-installed ul{padding-left:18px;display:grid;gap:6px}.hm-library-toolbar select{padding:6px 10px}.hm-library-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:16px}.hm-library-grid article{display:grid;gap:10px;padding:16px;border:1px solid color-mix(in srgb,currentColor 16%,transparent);border-radius:12px}.hm-app-frame{display:grid;grid-template-rows:auto 1fr;height:calc(100vh - 130px)}.hm-market-embedded>.hm-app-frame{position:fixed;inset:0;z-index:1001;height:100vh;box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);padding:12px 16px}.hm-app-frame>div{display:flex;justify-content:space-between}.hm-app-frame iframe{width:100%;height:100%;border:0}';style.textContent+='/* Only AppHost surfaces: never resize the host settings shell. */\n.hm-node-runtime,.hm-library-sources,.hm-providers,.hm-market{min-width:0;width:100%;max-width:100%;box-sizing:border-box;overflow-wrap:anywhere}\n.hm-node-runtime>*,.hm-library-sources>*,.hm-node-fields>*,.hm-handoff-settings>*,.hm-library-toolbar>*{min-width:0;max-width:100%;box-sizing:border-box}\n.hm-library-sources input,.hm-library-sources textarea,.hm-library-sources select,.hm-node-runtime input,.hm-node-runtime select{min-width:0;width:100%;max-width:100%;box-sizing:border-box;font:inherit;color:inherit;background:var(--dsw-alias-bg-layer-2)}\n.hm-library-sources button,.hm-market button{min-height:44px;max-width:100%;white-space:normal;font:inherit;color:inherit;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px 12px}\n.hm-library-sources button:focus-visible,.hm-market button:focus-visible{outline:2px solid currentColor;outline-offset:2px}\n.hm-handoff-settings{display:grid;gap:12px}.hm-handoff-settings label{display:grid;gap:6px}.hm-handoff-settings input[type=checkbox]{width:auto}\n.hm-node-confirm,.hm-node-progress{min-width:0;max-width:100%;box-sizing:border-box}\n.hm-failure pre{max-width:100%;white-space:pre-wrap;overflow-wrap:anywhere;font-size:12px}.hm-failure summary{cursor:pointer}.hm-failure p{margin:8px 0}\n.hm-library-toolbar,.hm-source-add{flex-wrap:wrap}.hm-library-toolbar input[type=search]{min-width:0;width:100%;flex:1 1 180px}\n.hm-library-grid{grid-template-columns:repeat(auto-fill,minmax(min(240px,100%),1fr))}\n.hm-providers{overflow-x:auto}.hm-source-row{grid-template-columns:minmax(0,1fr);gap:6px}\n@media(max-width:640px){.hm-library-overlay{padding:12px}.hm-node-confirm{padding:10px}.hm-node-confirm dl,.hm-install-target dl{grid-template-columns:minmax(0,1fr)}.hm-node-confirm dt{font-weight:600}.hm-node-confirm dd{margin-bottom:6px}.hm-library-toolbar select{width:100%}}\n';style.textContent+='\n.hm-provider-records td{overflow-wrap:anywhere}.hm-providers select,.hm-providers input{min-width:0;max-width:100%;width:100%;box-sizing:border-box}.hm-provider-records details>summary{cursor:pointer;min-height:44px;display:flex;align-items:center}.hm-provider-records pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:12px}\n@media(max-width:640px){.hm-providers{overflow:visible}.hm-provider-records,.hm-provider-records tbody{display:block}.hm-provider-records thead{display:none}.hm-provider-records tr{display:grid;grid-template-columns:minmax(0,1fr);gap:12px;padding:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;margin:10px 0}.hm-provider-records td{display:block;min-width:0;padding:0;border:0}.hm-provider-records td::before{content:attr(data-label);display:block;font-weight:600;margin-bottom:4px}.hm-provider-records td:empty{display:none}}\n';style.textContent+="\n.hm-providers,.hm-library-sources,.hm-node-runtime{overflow-wrap:normal;word-break:normal}\n.hm-providers h2,.hm-library-sources h2,.hm-node-runtime h2{margin:0;font-weight:600;line-height:1.5}\n.hm-providers p,.hm-library-sources p,.hm-node-runtime p{margin:0;line-height:1.6}\n.hm-suite-fields,.hm-suite-fields>label,.hm-handoff-settings,.hm-source-add{display:grid;grid-template-columns:minmax(0,1fr);gap:10px;min-width:0}\n.hm-suite-fields>*,.hm-source-add>*,.hm-providers>*,.hm-library-sources details,.hm-node-runtime details{min-width:0;max-width:100%;box-sizing:border-box}\n.hm-library-sources input,.hm-library-sources textarea,.hm-library-sources select,.hm-suite-fields select,.hm-providers select,.hm-providers input{display:block;width:100%;min-width:0;max-width:100%;box-sizing:border-box;min-height:44px;padding:8px;font:inherit;border:1px solid var(--dsw-alias-border-l2);border-radius:8px}\n.hm-library-sources input[type=checkbox]{display:inline-block;width:20px;min-height:20px;max-width:20px}\n.hm-library-sources summary,.hm-providers summary,.hm-node-runtime summary{min-height:44px;display:flex;align-items:center;cursor:pointer}\n.hm-library-sources code,.hm-library-sources dd,.hm-providers pre{overflow-wrap:anywhere;white-space:pre-wrap}\n.hm-handoff-settings>button,.hm-suite-fields>button{width:100%}\n@media(max-width:640px){.hm-provider-records tr{padding:8px}.hm-node-fields{grid-template-columns:minmax(0,1fr)}}\n";style.textContent+=appHostPageStyles;document.head.append(style);return()=>style.remove();},'hanamesh-app-host:style');
1233
+ ctx.inject?.(['layout'],page=>{page.effect(()=>registerAppHostPages(page),'hanamesh-app-host:pages');});
1234
+ ctx.effect(()=>ctx.slots.inject('hanamesh.environment.content',()=>ctx.slots.register({name:'hanamesh.environment.content',id:'apphost',order:10},EnvironmentContent)),'hanamesh-app-host:environment-content');
1140
1235
  ctx.effect(()=>localizedSection(ctx,'hanamesh-providers',18,"供应商",ProvidersSection),'hanamesh-app-host:providers');
1141
1236
  ctx.effect(()=>localizedSection(ctx,'hanamesh-library-sources',19,"市场目录源",LibrarySourcesSection),'hanamesh-app-host:library-sources');
1142
1237
  ctx.effect(()=>localizedSection(ctx,'hanamesh-suite-source',19.5,"套件安装包来源",SuiteSourceSettingsSection),'hanamesh-app-host:suite-source');
@@ -1148,5 +1243,5 @@ h('div',{className:'hm-source-add'},h('input',{value:draft,placeholder:'https://
1148
1243
  ctx.effect(()=>{let stopped=false,timer;const tick=async()=>{await pollHandoffsVisibly();if(!stopped)timer=setTimeout(()=>void tick(),handoffPollMs);};void tick();return()=>{stopped=true;clearTimeout(timer);};},'hanamesh-app-host:website-handoff');
1149
1244
  ctx.effect(()=>{globalThis.addEventListener?.('hashchange',consumeInstallTargetHash);consumeInstallTargetHash();return()=>globalThis.removeEventListener?.('hashchange',consumeInstallTargetHash);},'hanamesh-app-host:install-target-navigation');}
1150
1245
  return{name,inject,apply,ProvidersSection,SuiteSourceSettingsSection,LibrarySourcesSection,HandoffSettingsSection,NodeRuntimeSection,LibraryAction,LibraryOverlay,MarketConflictNotice,createMarketSeat,openInstallTarget,
1151
- MARKET_SEAT,MARKET_CONFLICT_TEXT,INSTALL_TARGET_HASH,INSTALL_TARGET_CONTRACT_VERSION,marketState:()=>({conflict:marketConflict,entryVisible})};
1246
+ registerAppHostPages,EnvironmentContent,credibilityFacts,readCredibility,catalogWebsiteLink,MARKET_SEAT,MARKET_CONFLICT_TEXT,INSTALL_TARGET_HASH,INSTALL_TARGET_CONTRACT_VERSION,marketState:()=>({conflict:marketConflict,entryVisible})};
1152
1247
  }});
package/dist/dsh.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import {pathToFileURL} from 'node:url';
2
- import { createInstallationStatusReader } from './library/installation-status.js';
2
+ import { createInstallationStatusReader,officialDshDesktopProfile } from './library/installation-status.js';
3
3
  /**
4
4
  * DSH plugin entry for @hanamesh/dsh-app-host, using the public APIs audited in
5
5
  * official DSH 0.2.0-rc.2 and 0.2.1-alpha.2:
@@ -258,7 +258,7 @@ export async function apply(ctx, config) {
258
258
  };
259
259
  const identity=await detectClient({desktop});
260
260
  if(typeof ctx.logger?.info==='function')ctx.logger.info('hanamesh-app-host library: client %s (%s)',identity.client,identity.evidence);
261
- library=createLibraryService({domain:{global:libraryDomain.global,close:closeLibraryDomain},host,config:libraryConfig,dataRoot,ledgerReader:provisionApi?.ledger??(async()=>({schema:1,items:{}})),installer,client:identity.client,unavailable,suiteSourceService:()=>suiteSourceService,installationStatus:nodeStatusBinding(()=>nodeRuntime.executable,workerNode=>createInstallationStatusReader({client:identity.client,core:()=>coreService,workerNode})),logger:ctx.logger});
261
+ library=createLibraryService({domain:{global:libraryDomain.global,close:closeLibraryDomain},host,config:libraryConfig,dataRoot,ledgerReader:provisionApi?.ledger??(async()=>({schema:1,items:{}})),installer,client:identity.client,unavailable,suiteSourceService:()=>suiteSourceService,installationStatus:nodeStatusBinding(()=>nodeRuntime.executable,workerNode=>createInstallationStatusReader({client:identity.client,core:()=>coreService,workerNode,otherProfileDir:identity.client==='hanamesh'?()=>officialDshDesktopProfile(locations.profileDir):null})),logger:ctx.logger});
262
262
  own('library', () => library.close());
263
263
  await library.init();
264
264
  // rc.27: app bundles register plain `app.json` (no package name); the installed scan knows which npm package ships
@@ -34,6 +34,8 @@ export async function loadClient({fetch:transport}){
34
34
  render();await idle();
35
35
  return{module,
36
36
  text:()=>JSON.stringify(tree),
37
+ find:predicate=>find(tree,predicate),
38
+ async acknowledgeInstall(){const checkbox=find(tree,node=>node.type==='input'&&node.props['aria-label']==='我已核对来源与安装目标');if(!checkbox)throw new Error('Installation review acknowledgement is not visible');checkbox.props.onChange({currentTarget:{checked:true}});await idle();},
37
39
  button:label=>find(tree,node=>node.type==='button'&&node.children.includes(label)),
38
40
  async click(label){const button=find(tree,node=>node.type==='button'&&node.children.includes(label));if(!button)throw new Error('No visible button '+label);button.props.onClick();await idle();},
39
41
  idle,
@@ -173,7 +173,7 @@ async function runRecoveryProvider(contract,record){
173
173
  await record(id,'recovery','market shows '+caseId+' as '+(id==='RU01'?'a failure that was restored':'a failure whose restoration failed, never as installed or rolled back'),async()=>{
174
174
  const item=recovery.cases.find(row=>row.id===caseId),source=await sourceRecoveryProfile(contract,item);
175
175
  const host=await startProvider(contract,{profile:source.profile,installer:source.installer});const client=await loadClient({fetch:host.browserFetch});
176
- try{await client.module.openInstallTarget({packageName:source.target.packageName});await client.idle();await client.click('确认安装');await host.settled();await client.tick(1000);
176
+ try{await client.module.openInstallTarget({packageName:source.target.packageName});await client.idle();await client.acknowledgeInstall();await client.click('确认安装');await host.settled();await client.tick(1000);
177
177
  const text=client.text();
178
178
  if(!text.includes('安装失败:'+item.expect.code)||!text.includes(expected))throw new Error('market text: '+text.slice(text.indexOf('安装失败')>=0?text.indexOf('安装失败'):0).slice(0,300));
179
179
  if(text.includes(refused)||text.includes('安装完成'))throw new Error('market misreports the recovery result');
@@ -247,7 +247,7 @@ export async function runProviderSuite(){
247
247
  await record('PC02','confirm','only an explicit confirmation installs, once, through the existing guarded route',async()=>{
248
248
  expectEqual(host.installs.length,0,'installs before confirmation');
249
249
  await client.module.openInstallTarget({packageName:'@hanamesh-fixture/install-target-app',contractVersion:'1'});await client.idle();
250
- const confirm=client.button('确认安装');confirm.props.onClick();confirm.props.onClick();await client.idle();
250
+ await client.acknowledgeInstall();const confirm=client.button('确认安装');confirm.props.onClick();confirm.props.onClick();await client.idle();
251
251
  expectEqual(host.posts.map(p=>[p.path,p.headers['x-hanamesh-client']]),[['/hanamesh/library/install','workspace-v1']],'install requests');
252
252
  expectEqual(host.installs,['@hanamesh-fixture/install-target-app'],'installer calls');
253
253
  });
@@ -258,7 +258,7 @@ export async function runProviderSuite(){
258
258
  if(!client.button('确认安装'))throw new Error('localhost fragment did not reach confirmation');
259
259
  await client.click('取消');expectEqual([host.posts.length,host.installs.length],[postsBefore,installsBefore],'localhost cancellation');
260
260
  await client.module.openInstallTarget({packageName:'@hanamesh-fixture/install-target-plugin',contractVersion:'1'});await client.idle();
261
- const confirm=client.button('确认安装');confirm.props.onClick();confirm.props.onClick();await client.idle();
261
+ await client.acknowledgeInstall();const confirm=client.button('确认安装');confirm.props.onClick();confirm.props.onClick();await client.idle();
262
262
  expectEqual([host.posts.length,host.installs.length],[postsBefore+1,installsBefore+1],'localhost confirmed install once');
263
263
  expectEqual(host.installs.at(-1),'@hanamesh-fixture/install-target-plugin','localhost installer target');
264
264
  });
@@ -3,7 +3,7 @@ export const INSTALLATION_STATUS_CAPABILITY:Readonly<{contract:'hanamesh.install
3
3
  export interface InstallationStatusRequest {contractVersion:'1';consent:'read-installations'}
4
4
  export interface InstallationObservation {
5
5
  channel:'hanamesh'|'dsh'|'claude'|'codex';
6
- scope:{id:'current-runtime'|'other-runtime'|'agent-cli'|'desktop-app';kind:'responding-runtime'|'unobserved-runtime'|'official-user-profile'|'desktop-app';label:string};
6
+ scope:{id:'current-runtime'|'other-runtime'|'agent-cli'|'desktop-app';kind:'responding-runtime'|'other-client'|'official-user-profile'|'desktop-app';label:string};
7
7
  status:'installed'|'not-installed'|'incomplete'|'unknown';version:string|null;versionKind:'suite-bundle'|'agent-distribution';
8
8
  components:readonly {name:string;version:string|null;active?:boolean;status?:string}[];
9
9
  identity:{pluginName:string;marketplace:string}|null;
@@ -12,14 +12,15 @@ const MAX_SOURCE_LINKS=500;
12
12
  const plain=value=>typeof value==='string'&&value.length>0&&!/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/u.test(value);
13
13
  const own=(value,keys)=>value&&typeof value==='object'&&!Array.isArray(value)&&Object.keys(value).every(key=>keys.includes(key));
14
14
  // WHATWG URL canonicalizes hostnames (including IPv6); no DNS lookup or origin aliasing.
15
- const catalogOrigin=url=>(url.protocol==='https:'&&!url.port)||
16
- (url.protocol==='http:'&&['127.0.0.1','[::1]','localhost'].includes(url.hostname));
15
+ const loopback=url=>['127.0.0.1','[::1]','localhost'].includes(url.hostname);
16
+ const catalogOrigin=url=>(url.protocol==='https:'&&(!url.port||loopback(url)||url.hostname.endsWith('.localhost')))||
17
+ (url.protocol==='http:'&&loopback(url));
17
18
 
18
19
  export function validateManifestUrl(value){
19
20
  let url;try{url=new URL(value);}catch{throw new AppHostError('INVALID_CATALOG_URL','Catalog manifest URL is invalid.');}
20
21
  const viewQuery=!url.search||(url.searchParams.size===1&&url.searchParams.get('view')==='hanamesh');
21
22
  requireCondition(catalogOrigin(url)&&!url.username&&!url.password&&viewQuery&&!url.hash,
22
- 'INVALID_CATALOG_URL','Catalog manifests require standard-port HTTPS or loopback HTTP (127.0.0.1, ::1, localhost), with only an optional view=hanamesh query.');
23
+ 'INVALID_CATALOG_URL','Catalog manifests require standard-port HTTPS, local HTTPS (loopback or *.localhost), or loopback HTTP, with only an optional view=hanamesh query.');
23
24
  return url;
24
25
  }
25
26
 
@@ -22,7 +22,8 @@ export function installationEnvironment(source,platform=process.platform){
22
22
  const fail=code=>{throw new AppHostError(code,'The automatically discovered official installation reader could not complete.',{},503)};
23
23
  export const MINIMUM_READER_VERSIONS=Object.freeze({claude:'2.1.301',codex:'0.162.1'});
24
24
  export function readerVersionFailure(channel,output){
25
- const pattern=channel==='claude'?/^(\d+\.\d+\.\d+) \(Claude Code\)$/:/^codex-cli (\d+\.\d+\.\d+)$/;
25
+ // SemVer prerelease is allowed: desktop apps bundle prerelease CLIs (ChatGPT.app: codex-cli 0.167.0-alpha.3.2).
26
+ const pattern=channel==='claude'?/^(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?) \(Claude Code\)$/:/^codex-cli (\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/;
26
27
  const version=pattern.exec(output.trim())?.[1];
27
28
  if(!version||semver.valid(version)!==version)return 'AGENT_READER_VERSION_INVALID';
28
29
  return semver.gte(version,MINIMUM_READER_VERSIONS[channel])?null:'AGENT_READER_VERSION_UNSUPPORTED';
@@ -36,15 +36,26 @@ export async function readLoginEnvironment({home=homedir(),env=process.env,signa
36
36
  if(!value||typeof value!=='object'||Array.isArray(value)||typeof value.PATH!=='string'||!Object.entries(value).every(([key,path])=>INSTALLATION_ENV_KEYS.includes(key)&&typeof path==='string'&&!/[\x00-\x1f]/.test(path)))throw failure('AGENT_SHELL_ENV_UNAVAILABLE');
37
37
  return installationEnvironment(value);
38
38
  }
39
- export async function readLoginPath(options){return(await readLoginEnvironment(options)).PATH}
40
39
 
41
40
  /** Select once: inherited PATH, login PATH, then documented layouts; never try another CLI after execution. */
42
- export async function discoverOfficialReader({channel,home=process.env.HOME??process.env.USERPROFILE??homedir(),env=process.env,platform=process.platform,
43
- systemBinDirs=platform==='win32'?[]:['/opt/homebrew/bin','/usr/local/bin','/home/linuxbrew/.linuxbrew/bin','/usr/bin','/bin'],loginPath,signal,workerNode}={}){
41
+ /** The official user profile directory each CLI reads (CLAUDE_CONFIG_DIR / CODEX_HOME, else ~/.claude / ~/.codex). */
42
+ export function agentProfileDir({channel,home=process.env.HOME??process.env.USERPROFILE??homedir(),env=process.env}={}){
44
43
  if(!['claude','codex'].includes(channel)||!absolute(home))throw failure('AGENT_PROFILE_UNAVAILABLE');
45
- signal?.throwIfAborted();
46
44
  const configDir=env[channel==='claude'?'CLAUDE_CONFIG_DIR':'CODEX_HOME']??join(home,channel==='claude'?'.claude':'.codex');
47
45
  if(!absolute(configDir))throw failure('AGENT_PROFILE_UNAVAILABLE');
46
+ return configDir;
47
+ }
48
+ /** Read-only existence check: an absent profile directory holds no user-scope plugin; unreadable is not absence. */
49
+ export async function agentProfileState({channel,home,env}={}){
50
+ const dir=agentProfileDir({channel,...(home===undefined?{}:{home}),...(env===undefined?{}:{env})});
51
+ try{if(!(await stat(dir)).isDirectory())throw failure('AGENT_PROFILE_UNAVAILABLE');return 'present'}
52
+ catch(error){if(['ENOENT','ENOTDIR'].includes(error.code))return 'absent';throw error.code==='AGENT_PROFILE_UNAVAILABLE'?error:failure('AGENT_PROFILE_UNAVAILABLE')}
53
+ }
54
+ export async function discoverOfficialReader({channel,home=process.env.HOME??process.env.USERPROFILE??homedir(),env=process.env,platform=process.platform,
55
+ systemBinDirs=platform==='win32'?[]:['/opt/homebrew/bin','/usr/local/bin','/home/linuxbrew/.linuxbrew/bin','/usr/bin','/bin'],
56
+ applicationDirs=platform==='darwin'?['/Applications',join(home,'Applications')]:[],loginPath,signal,workerNode}={}){
57
+ const configDir=agentProfileDir({channel,home,env});
58
+ signal?.throwIfAborted();
48
59
  const appData=env.APPDATA??join(home,'AppData','Roaming'),local=env.LOCALAPPDATA??join(home,'AppData','Local');
49
60
  const runtimeEnv=installationEnvironment(env,platform);
50
61
  const reader={home,configDir,...AGENT_INSTALLATION_TARGET,...INSTALLATION_READ_BUDGET,runtimeEnv};
@@ -81,7 +92,9 @@ export async function discoverOfficialReader({channel,home=process.env.HOME??pro
81
92
  ...[env.npm_config_prefix,env.NPM_CONFIG_PREFIX,join(home,'.npm-global'),join(home,'.npm')].filter(absolute).map(root=>platform==='win32'?root:join(root,'bin')),
82
93
  env.PNPM_HOME,...(absolute(env.PNPM_HOME)?[join(env.PNPM_HOME,'bin')]:[]),join(home,'.local','share','pnpm'),join(home,'.local','share','pnpm','bin'),join(home,'Library','pnpm'),join(home,'Library','pnpm','bin'),join(home,'.yarn','bin'),join(home,'.config','yarn','global','node_modules','.bin'),
83
94
  join(env.VOLTA_HOME??join(home,'.volta'),'bin'),join(asdf,'shims'),join(mise,'shims'),
84
- ...(platform==='win32'?[join(appData,'npm'),join(local,'pnpm'),join(local,'Yarn','bin'),join(local,'Programs','OpenAI','Codex','bin'),join(local,'Microsoft','WinGet','Links'),join(local,'Volta','bin'),...(absolute(env.ProgramFiles)?[join(env.ProgramFiles,'nodejs')]:[])]:[]),...systemBinDirs];
95
+ ...(platform==='win32'?[join(appData,'npm'),join(local,'pnpm'),join(local,'Yarn','bin'),join(local,'Programs','OpenAI','Codex','bin'),join(local,'Microsoft','WinGet','Links'),join(local,'Volta','bin'),...(absolute(env.ProgramFiles)?[join(env.ProgramFiles,'nodejs')]:[])]:[]),...systemBinDirs,
96
+ // Desktop app bundles last, so a standalone CLI the user installed wins: ChatGPT.app ships the Codex CLI.
97
+ ...(channel==='codex'?applicationDirs.map(root=>join(root,'ChatGPT.app','Contents','Resources','codex-cli','bin')):[])];
85
98
  selected=await find(fixed);if(selected)return selected;
86
99
  const managed=[];
87
100
  for(const [root,suffix]of [[join(nvm,'versions','node'),['bin']],[env.NVM_HOME,[]],[join(fnm,'node-versions'),['installation',...(platform==='win32'?[]:['bin'])]],
@@ -1,9 +1,13 @@
1
1
  /** Read fresh public installation facts. No compatibility calculation, persistence or polling. */
2
2
  import semver from 'semver';
3
3
  import {AppHostError} from '../errors.js';
4
- import {SUITE_BUNDLE,createCoreSuiteAdapter,SUITE_PACKAGES} from './suite.js';
4
+ import {readFile} from 'node:fs/promises';
5
+ import {join} from 'node:path';
6
+ import {realpathSync} from 'node:fs';
7
+ import {homedir} from 'node:os';
8
+ import {SUITE_BUNDLE,createCoreSuiteAdapter,SUITE_PACKAGES,readSuiteStatus} from './suite.js';
5
9
  import {readerVersionFailure,validReader,runOfficialInstallationCommand} from './installation-cli.js';
6
- import {discoverOfficialReader} from './installation-discovery.js';
10
+ import {discoverOfficialReader,agentProfileState,AGENT_INSTALLATION_TARGET} from './installation-discovery.js';
7
11
  const object=x=>x!==null&&typeof x==='object'&&!Array.isArray(x);
8
12
  const exact=x=>typeof x==='string'&&semver.valid(x)===x?x:null;
9
13
  const reason=(code,message,action)=>({code,message,action});
@@ -17,6 +21,13 @@ const unknown=(row,code,message,action)=>({...row,status:'unknown',version:null,
17
21
  const agentBase=channel=>base(channel,'agent-cli','official-user-profile',channel==='claude'?'Claude Code 用户级插件(不覆盖项目或其他隔离资料)':'Codex CLI 本地市场用户资料(不代表桌面 App、项目或其他隔离资料)');
18
22
  const inventoryError=()=>{throw new AppHostError('AGENT_INVENTORY_INVALID','Official inventory shape is unsupported.',{},503)};
19
23
  const qualified=x=>typeof x==='string'&&/^[a-z0-9][a-z0-9._-]*@[a-z0-9][a-z0-9._-]*$/.test(x)&&x.length<=257;
24
+ function githubGitUrl(url){return typeof url==='string'&&/^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\.git$/.test(url)?url.slice(0,-4):url;}
25
+ function codexMarketplaceUrl(source){return source?.sourceType==='git'?githubGitUrl(source.source):source?.sourceType==='url'?source.source:null;}
26
+ function claudeMarketplaceUrl(value){
27
+ if(value.source==='github'&&typeof value.repo==='string'&&/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(value.repo))return `https://github.com/${value.repo}`;
28
+ const url=['url','git'].includes(value.source)?value.url:null;
29
+ return value.source==='git'?githubGitUrl(url):url;
30
+ }
20
31
  function parseInventory(channel,plugins,marketplaces,target){
21
32
  let rows,markets;
22
33
  if(channel==='claude'){
@@ -29,7 +40,7 @@ function parseInventory(channel,plugins,marketplaces,target){
29
40
  if(!markets.every(r=>object(r)&&typeof r.name==='string'&&r.name.length>0&&r.name.length<=128))inventoryError();
30
41
  const sourceRows=markets.filter(r=>r.name===target.marketplace);
31
42
  if(sourceRows.length>1)throw new AppHostError('AGENT_INVENTORY_CONFLICT','Conflicting marketplace identities.',{},503);
32
- if(sourceRows.length){const m=sourceRows[0],url=channel==='claude'?(['url','git'].includes(m.source)?m.url:null):(['git','url'].includes(m.marketplaceSource?.sourceType)?m.marketplaceSource.source:null);
43
+ if(sourceRows.length){const m=sourceRows[0],url=channel==='claude'?claudeMarketplaceUrl(m):codexMarketplaceUrl(m.marketplaceSource);
33
44
  if(url!==target.marketplaceUrl)throw new AppHostError('AGENT_MARKETPLACE_SOURCE_MISMATCH','The configured marketplace source is not the registered target.',{},503);
34
45
  }
35
46
  const id=`${target.pluginName}@${target.marketplace}`;
@@ -42,7 +53,7 @@ function parseInventory(channel,plugins,marketplaces,target){
42
53
  const installedSource=scoped[0].marketplaceSource;
43
54
  if(channel==='codex'){
44
55
  if(!installedSource||!['git','url'].includes(installedSource.sourceType)||typeof installedSource.source!=='string')throw new AppHostError('AGENT_MARKETPLACE_SOURCE_UNAVAILABLE','Installed marketplace source is not observable.',{},503);
45
- if(installedSource.source!==target.marketplaceUrl)throw new AppHostError('AGENT_MARKETPLACE_SOURCE_MISMATCH','Installed marketplace source conflicts with the registered target.',{},503);
56
+ if(codexMarketplaceUrl(installedSource)!==target.marketplaceUrl)throw new AppHostError('AGENT_MARKETPLACE_SOURCE_MISMATCH','Installed marketplace source conflicts with the registered target.',{},503);
46
57
  }
47
58
  const row=scoped[0],version=exact(channel==='claude'&&row.readFromFolder!==undefined?row.folderVersion:row.version);
48
59
  const failed=channel==='claude'?Boolean(row.errors?.length||row.notes?.length):!row.installed;
@@ -50,8 +61,27 @@ function parseInventory(channel,plugins,marketplaces,target){
50
61
  reason:failed?reason(channel==='claude'?'AGENT_PLUGIN_LOAD_FAILED':'AGENT_PLUGIN_PAYLOAD_MISSING','官方清单报告插件负载或加载问题。','在该官方客户端的插件详情中检查并修复。'):version===null?reason('AGENT_VERSION_UNAVAILABLE','已确认该作用域的安装记录,但实际版本未知。','在该官方客户端插件详情中核对实际版本。'):null};
51
62
  }
52
63
  const failureCodes=new Set(['AGENT_SHELL_ENV_UNAVAILABLE','AGENT_READER_DISCOVERY_INVALID','AGENT_READER_ENTRYPOINT_UNAVAILABLE','AGENT_READER_UNAVAILABLE','AGENT_READER_PERMISSION_DENIED','AGENT_READER_NODE_REQUIRED','AGENT_PROFILE_UNAVAILABLE','AGENT_READER_TIMEOUT','AGENT_READER_OUTPUT_LIMIT','AGENT_READER_CLEANUP_FAILED','AGENT_READER_DIAGNOSTIC','AGENT_READER_FAILED','AGENT_INVENTORY_INVALID','AGENT_INVENTORY_CONFLICT','AGENT_MARKETPLACE_SOURCE_MISMATCH','AGENT_MARKETPLACE_SOURCE_UNAVAILABLE','AGENT_SCOPE_UNOBSERVABLE']);
53
- /** Automatic current-user discovery only; `core` is the optional public protocol-1 Cordis service accessor. */
54
- export function createInstallationStatusReader({client='dsh',core=()=>undefined,workerNode,runCommand=runOfficialInstallationCommand,discoverReader=discoverOfficialReader}={}){
64
+ /** Physical on-disk DSH profile inventory: no @hanamesh/hanamesh dependency (or no profile) is authoritative absence. */
65
+ async function profileSuite(row,profileDir){
66
+ let profile;try{profile=JSON.parse(await readFile(join(profileDir,'package.json'),'utf8'))}
67
+ catch(error){if(['ENOENT','ENOTDIR'].includes(error.code))return{...row,status:'not-installed'};return unknown(row,'DSH_PROFILE_UNREADABLE','DSH 桌面版 profile 清单无法读取。','打开 DSH 确认其插件页可用后重新检测。')}
68
+ if(!object(profile)||!object(profile.dependencies))return unknown(row,'DSH_PROFILE_UNREADABLE','DSH 桌面版 profile 清单无法读取。','打开 DSH 确认其插件页可用后重新检测。');
69
+ if(!Object.hasOwn(profile.dependencies,SUITE_BUNDLE))return{...row,status:'not-installed'};
70
+ const value=await readSuiteStatus(profileDir);
71
+ const components=value.components.map(c=>({name:c.name,version:c.version,active:c.active}));
72
+ if(value.status==='installed'&&value.version)return{...row,status:'installed',version:value.version,components};
73
+ if(value.status==='unknown')return unknown(row,'DSH_PROFILE_UNREADABLE','DSH 桌面版 profile 清单无法读取。','打开 DSH 确认其插件页可用后重新检测。');
74
+ return{...row,status:'incomplete',version:value.version,components,reason:reason('DSH_PROFILE_SUITE_INCOMPLETE','DSH 桌面版 profile 中的 HanaMesh 套件缺组件或未启用。','在 DSH 插件页启用 HanaMesh,或按 DSH 安装说明重新安装总包后重启 DSH。')};
75
+ }
76
+ /** Official DSH desktop profile; only distinct, resolvable physical profiles may answer for another client. */
77
+ export function officialDshDesktopProfile(ownProfileDir,home=homedir()){
78
+ const dir=join(home,'.dsh','profiles','desktop');
79
+ if(typeof ownProfileDir!=='string'||!ownProfileDir)return null;
80
+ try{return realpathSync(ownProfileDir)===realpathSync(dir)?null:dir}catch{return null}
81
+ }
82
+ /** Automatic current-user discovery only; `core` is the optional public protocol-1 Cordis service accessor.
83
+ * `otherProfileDir`: a fresh selector of the official DSH desktop profile (HanaMesh client only); trusted fixtures may supply a path. */
84
+ export function createInstallationStatusReader({client='dsh',core=()=>undefined,workerNode,runCommand=runOfficialInstallationCommand,discoverReader=discoverOfficialReader,profileState=agentProfileState,otherProfileDir=null}={}){
55
85
  if(!['dsh','hanamesh'].includes(client))throw new TypeError('client must be dsh or hanamesh');
56
86
  const active=new Set();let closed=false;
57
87
  const suite=createCoreSuiteAdapter({core});
@@ -67,6 +97,7 @@ export function createInstallationStatusReader({client='dsh',core=()=>undefined,
67
97
  async function agent(channel,signal){
68
98
  const initial=agentBase(channel);let row=initial;
69
99
  try{
100
+ if(await abortable(profileState({channel}),signal)==='absent')return{...initial,status:'not-installed',identity:{pluginName:AGENT_INSTALLATION_TARGET.pluginName,marketplace:AGENT_INSTALLATION_TARGET.marketplace}};
70
101
  const config=await abortable(discoverReader({channel,signal,workerNode}),signal);
71
102
  if(!validReader(config))throw new AppHostError('AGENT_READER_DISCOVERY_INVALID','Invalid automatic reader.',{},503);
72
103
  row={...initial,identity:{pluginName:config.pluginName,marketplace:config.marketplace}};
@@ -80,11 +111,16 @@ export function createInstallationStatusReader({client='dsh',core=()=>undefined,
80
111
  return{...row,...parseInventory(channel,plugins,marketplaces,config)};
81
112
  }catch(error){if(signal.aborted||error.code==='INSTALLATION_STATUS_CANCELLED')throw interrupted();const code=failureCodes.has(error.code)?error.code:'AGENT_READER_FAILED';const messages={AGENT_READER_UNAVAILABLE:'未在当前用户的 PATH 或常见安装位置找到官方 CLI。',AGENT_SHELL_ENV_UNAVAILABLE:'登录 shell 的 PATH 未能读取。',AGENT_PROFILE_UNAVAILABLE:'当前用户的官方资料目录不存在或不可访问。',AGENT_READER_TIMEOUT:'本次官方只读检测超时。',AGENT_INVENTORY_INVALID:'官方清单 JSON 不符合受支持的格式。',AGENT_READER_DISCOVERY_INVALID:'自动发现的官方 CLI 信息无效。',AGENT_READER_ENTRYPOINT_UNAVAILABLE:'已发现 Windows 命令入口,但官方 npm 程序文件缺失。'};return unknown(row,code,messages[code]??'本次官方清单读取未能提供完整且一致的安装事实。','在终端确认官方 CLI 可运行,并在其用户级插件页面检查清单、权限和市场来源后重新检测。')}
82
113
  }
114
+ async function other(signal){
115
+ const profileDir=client==='hanamesh'?(typeof otherProfileDir==='function'?otherProfileDir():otherProfileDir):null;
116
+ if(profileDir)return abortable(profileSuite(base('dsh','other-runtime','other-client','DSH 桌面版 profile(磁盘只读清单;运行中的 DSH 由其自身监听回答)'),profileDir),signal);
117
+ return unknown(base(client==='dsh'?'hanamesh':'dsh','other-runtime','other-client','另一客户端 runtime(须由其自身监听回答)'),'OTHER_RUNTIME_NOT_OBSERVED','本监听未读取另一客户端的 runtime。','启动该客户端,并向其已广告的状态入口显式检测。');
118
+ }
83
119
  const read=async({signal}={})=>{
84
120
  if(closed||signal?.aborted)throw interrupted();const controller=new AbortController();active.add(controller);const combined=signal?AbortSignal.any([signal,controller.signal]):controller.signal;
85
121
  try{const current=await runtime(combined);const agents=await Promise.all(['claude','codex'].map(channel=>agent(channel,combined)));if(combined.aborted)throw interrupted();
86
122
  return{schemaVersion:1,client,checkedAt:new Date().toISOString(),observations:[current,
87
- unknown(base(client==='dsh'?'hanamesh':'dsh','other-runtime','unobserved-runtime','另一客户端 runtime(须由其自身监听回答)'),'OTHER_RUNTIME_NOT_OBSERVED','本监听未读取另一客户端的 runtime。','启动该客户端,并向其已广告的状态入口显式检测。'),...agents,
123
+ await other(combined),...agents,
88
124
  unknown(base('codex','desktop-app','desktop-app','Codex / ChatGPT 桌面 App(独立作用域)'),'CODEX_DESKTOP_SCOPE_UNOBSERVABLE','Codex CLI 清单不能证明桌面 App 的安装。','在桌面 App 的插件目录核对;需官方公开桌面状态协议后才可接入。')]};
89
125
  }finally{active.delete(controller)}
90
126
  };
package/docs/CONTRACT.md CHANGED
@@ -1,3 +1,5 @@
1
+ 0.6.26:Codex 官方 GitHub `git` 来源的精确 `.git` 后缀在市场映射与每项安装来源分别归一;只读覆盖扩大到 ChatGPT.app 内置 Codex CLI、预发布 CLI 版本、不存在的官方资料目录(未安装)和 HanaMesh 客户端读取的 DSH 桌面版磁盘 profile;`other-runtime` 的 kind 改名为 `other-client`。只读命令与来源冲突拒绝不变,见 INSTALLATION_STATUS.md。
2
+
1
3
  0.6.7 adds the explicitly consented public installation-status capability on both existing library listeners. Full request/response, strict refusals, automatic official-reader discovery and minimum-version support and scope/lifecycle are defined in [INSTALLATION_STATUS.md](INSTALLATION_STATUS.md); it does not authorize installation or alter recording consent.
2
4
 
3
5
  # 公开契约 v1(候选,0.1.0-rc.41;rc.41 卸载 runtime 时清理生成的 Python 字节码缓存、未知残留仍 fail-closed;rc.40 市场只读查询待启动实例,ready 后一次 resume;前台即时 heartbeat 与失效帧显式清理;rc.39 Market seat 内打开应用时将原有应用帧固定铺满原生窗口并覆盖 Settings,同一回执/租约/关闭动作;rc.38 投射时仅提升 shell.overlay 层级以盖过原生 Settings;rc.35 市场开关按钮即时反馈、owned 应用已退出时提前结束 TERM 等待并确认精确进程组终止;rc.34 内置市场消费 heartbeat/视图清理;rc.33 owned 回收与启动握手分阶段限时;rc.32 客户端 `market` 席位与 dshmarket 互斥、壳内重启改走 postMessage 桥(见「市场席位」);rc.28 HanaMesh 市场:插件与应用同路径装卸、已装含插件、可升级/需重启、`installedPlugins` 与 `plugins/*` 路由、`LIB-PROVISION-INPUT` 400(见「市场」);rc.27 应用使用证据经 usage 插件 record 座位上报(见「应用使用证据」);rc.26 应用库默认目录源 + 纯 DSH 位置推断(见「应用库配置」);rc.15 目录条目校验补 updatedAt;rc.16 应用库默认 category=hanamesh-app、q/cursor 透传;rc.13/rc.14 与 rc.12 契约相同:rc.13 补许可证/repository/精确 peer,rc.14 去掉客户端对 hanameshCore 的读取)
@@ -227,7 +229,7 @@ Support implementation: macOS13.5+ arm64/x64, known Node24.13.1 or24.22.0 (defau
227
229
  Storage space budget is archive bytes + twice measured regular-file payload (new tree + staging); never delete another version or unknown files to make space. lib-provision alone owns staging/ledger/manifests; interrupted or cancelled downloads may leave its resumable owned cache. Another attempt requires a new visible confirmation. Existing versions remain until an explicit owned cleanup is designed; no automatic deletion or implied rollback. Offline startup verifies an existing active tree/ledger and standalone executable; failures are visible and never trigger a download.
228
230
 
229
231
 
230
- 目录来源 URL 接受标准端口 HTTPS,另接受本机回环 `127.0.0.1`、`::1`、`localhost` 的任意端口 HTTP。使用 Node `URL` 解析后的主机名精确比较;其它来源保持原规则。凭据、fragment、查询和完整同源校验不变;不增加设置、映射或别名。目录来源自身 attribution 可使用该回环来源,普通条目展示链接仍按原 HTTPS 校验。
232
+ 目录来源 URL 接受标准端口 HTTPS;HTTPS 本机回环 127.0.0.1、::1、localhost 及 *.localhost 可使用任意端口,另接受本机回环 `127.0.0.1`、`::1`、`localhost` 的任意端口 HTTP。使用 Node `URL` 解析后的主机名精确比较;其它来源保持原规则。凭据、fragment、查询和完整同源校验不变;不增加设置、映射或别名。目录来源自身 attribution 可使用该回环来源,普通条目展示链接仍按原 HTTPS 校验。
231
233
 
232
234
  ## B17 套件安装包来源(2026-10-21 候选)
233
235
 
@@ -259,3 +261,34 @@ B17 review repair(同0.6.8候选):`AppHost` 未注入 `runtimeLedgerReader
259
261
  A2 0.6.22:普通插件必须有 Indexer 已审核的 installMethods 和 oneClickInstall.allowed:true;仅执行其 npm 精确版本或完整 GitHub SHA。缺目标具名拒绝,不根据包名查可变版本标签。CLI 使用显式 library.registry(若有),否则保留宿主原生 registry 配置。删除普通插件按包名内部直装方法、元数据查询及其专用设置/规则加载。套件仅透传 Core installCommand。安装失败保留脱敏 CLI/读回诊断及补偿结果;精确版本发布后即可原生验收。
260
262
 
261
263
  A2 0.6.22:升级tuple必须带Core生成的installation(name/version/command/args),name/version与该tuple一致。AppHost传实际profile和可选配置来源;未配置来源由Core显式声明。CLI直接执行args;confirmation同时绑定该定义。installCommand是Core的可空修复展示文本,不解析命令前缀。旧Core缺定义不回退自拼。
264
+
265
+ ## CL1 HanaMesh client pages (2026-11-10 candidate)
266
+
267
+ AppHost adds keyed main entries `hanamesh-library` and `hanamesh-providers`. Sidebar list id `hanamesh-library` (order 11, label 应用库) addresses the matching main key. The existing market seat, overlay fallback, website install-target entry, exact install/readback, app receipts, leases and close ownership remain intact; new layout-aware entry actions select the main page without creating a Session. The legacy footer action is hidden when the native main navigation is available.
268
+
269
+ Core declares root-scoped list slot `hanamesh.environment.content` as a child of main key `hanamesh-environment`. AppHost registers its own `EnvironmentContent` there: installation environment and lazily opened catalog/suite-source/handoff forms. Those forms use their original authenticated routes and stores; opening the environment does not read every closed form or save/install anything. Tabs select Core keys `hanamesh-account` / `hanamesh-environment` and AppHost `hanamesh-providers`. No sibling source import, Core allowlist change or host root/sidebar/conversation replacement is involved.
270
+
271
+ The library includes local installed/all tabs and a source-preserving details view. Installation confirmation shows the exact reviewed npm version or full GitHub ref/path/build permission, and requires acknowledgement of the **current resolved target object** before its original confirm action can post. A newly resolved target clears that acknowledgement; server-side fixed-target reread remains authoritative. The shipped conformance harness includes an explicit `acknowledgeInstall()` step before confirmation. Old directory `latestVersion` is still rejected/not displayed.
272
+
273
+ The current catalog-view DTO has no public GitHub stars / npm downloads / platform install people / usage people fields. Details render each as unavailable with a reason, never as zero or a local-inventory substitute. The website SearchSignals DTO is a different contract, and store installation次数 is not 人数. Supplying these figures needs a formally added catalog-view DTO; this candidate does not relax validation or perform request-time GitHub/npm calls. A “在网站查看” link is derived only from the reviewed HanaMesh-view catalog provider; fixture/other-provider routes are not guessed. No trading or asset UI is added to the client.
274
+
275
+ Scoped page styles consume the same host theme tokens. The library glyph reuses the supplied DS1 asset. Source/fixture checks do not establish real DSH loading or design screenshot acceptance; those remain separate CL1 gates.
276
+
277
+ ### CL1 existing public credibility reads (2026-11-10)
278
+
279
+ On detail open only, the client uses the reviewed HanaMesh catalog origin's existing
280
+ `GET /api/v2/hanas/:id` and `GET /hm/v1/custody/hanas/:id/popularity` public reads.
281
+ The first is WX11/WX12's projection of Host 0.6.54+ `/v1/indexer/signals/changes`:
282
+ `githubStars` becomes `signals.stars`, with `npmDownloads30d`, nullable
283
+ `storeInstalls30d` and `checkedAt`. The second supplies `users30d` and its own
284
+ `asOf`. DSH installs are 30-day counts, never unique installation people; neither
285
+ npm downloads nor `installCount` is substituted. All missing/invalid/error values
286
+ show “暂无”; real zero stays zero, and timestamps remain separate.
287
+
288
+ No new backend, signal-feed consumption, source scraping, polling or catalog DTO
289
+ extension. Both requests omit credentials, refuse redirects and time out after
290
+ 8 seconds; identity must match the selected item. Source routes are derived only
291
+ for the reviewed `/catalog-source.json?view=hanamesh` provider. Other catalog
292
+ providers and unavailable/CORS-denied public reads retain “暂无”. The source's
293
+ browser access rules are respected; there is no proxy/CORS bypass. Closing or
294
+ switching details ignores stale replies.
@@ -37,7 +37,7 @@ interface InstallationStatus {
37
37
  interface InstallationObservation {
38
38
  channel:'hanamesh'|'dsh'|'claude'|'codex';
39
39
  scope:{id:'current-runtime'|'other-runtime'|'agent-cli'|'desktop-app';
40
- kind:'responding-runtime'|'unobserved-runtime'|'official-user-profile'|'desktop-app';label:string};
40
+ kind:'responding-runtime'|'other-client'|'official-user-profile'|'desktop-app';label:string};
41
41
  status:'installed'|'not-installed'|'incomplete'|'unknown';
42
42
  version:string|null;
43
43
  versionKind:'suite-bundle'|'agent-distribution';
@@ -69,6 +69,7 @@ Selection is deterministic: the first executable in the inherited absolute PATH
69
69
  | Homebrew / system | `/opt/homebrew/bin`, `/usr/local/bin`, `/home/linuxbrew/.linuxbrew/bin`, `/usr/bin`, `/bin`. [Homebrew prefixes](https://docs.brew.sh/Installation); [Claude Homebrew setup](https://code.claude.com/docs/en/setup), [Codex distribution](https://learn.chatgpt.com/docs/cli). Brew-linked native binaries use the same discovery path. |
70
70
  | nvm / fnm / installed manager versions | `NVM_BIN`, `NVM_SYMLINK`; `NVM_DIR/versions/node/*/bin` or `~/.nvm/versions/node/*/bin`; Windows `NVM_HOME/*`; `FNM_MULTISHELL_PATH`; `FNM_DIR/node-versions/*/installation[/bin]`, `~/.local/share/fnm/node-versions/*/installation/bin`, `~/Library/Application Support/fnm/node-versions/*/installation/bin`, `%APPDATA%/fnm/node-versions/*/installation`; asdf `installs/nodejs/*/bin`, mise `installs/node/*/bin`. Only SemVer directory names are scanned. [nvm](https://github.com/nvm-sh/nvm), [fnm](https://github.com/Schniz/fnm/blob/master/docs/commands.md), [asdf](https://asdf-vm.com/manage/configuration.html), [mise](https://mise.jdx.dev/directories.html). |
71
71
  | Windows native links / npm shims | `%LOCALAPPDATA%/Microsoft/WinGet/Links` ([Claude WinGet](https://code.claude.com/docs/en/setup)). `.exe` precedes `.cmd` per directory. A `.cmd` is never sent to a shell: resolve only the adjacent official npm `node_modules/@anthropic-ai/claude-code/cli.js` or `node_modules/@openai/codex/bin/codex.js`, run it with the verified standalone Node. Missing JS is a named failure, not a guessed shell command. |
72
+ | Desktop app bundle (macOS, Codex only, searched last) | `/Applications` and `~/Applications` `ChatGPT.app/Contents/Resources/codex-cli/bin/codex` (the ChatGPT desktop app ships the Codex CLI; it reads the same `CODEX_HOME` / `~/.codex`). A standalone CLI in any earlier layout wins. |
72
73
 
73
74
  HOME is the host user's HOME/USERPROFILE. Honor existing `CLAUDE_CONFIG_DIR` / `CODEX_HOME` environment paths, otherwise use `~/.claude` / `~/.codex`. These directories must already exist; discovery never creates them, reads their contents, or reads credentials, sessions or private configuration files. Only official read commands observe plugin inventory. The named profile scope is not a machine-wide inventory. Electron hosts retain the existing verified standalone Node requirement.
74
75
 
@@ -78,12 +79,12 @@ Minimum CLI version and fixed syntax (no new upper bound):
78
79
 
79
80
  | Channel | Minimum version / accepted `--version` syntax | Fixed subsequent read argv |
80
81
  |---|---|---|
81
- | Claude Code | `>=2.1.301`, `MAJOR.MINOR.PATCH (Claude Code)` | `plugin list --json`; `plugin marketplace list --json` |
82
- | Codex CLI | `>=0.162.1`, `codex-cli MAJOR.MINOR.PATCH` | `plugin list --json --disable remote_plugin`; `plugin marketplace list --json --disable remote_plugin` |
82
+ | Claude Code | `>=2.1.301`, `MAJOR.MINOR.PATCH[-prerelease] (Claude Code)` | `plugin list --json`; `plugin marketplace list --json` |
83
+ | Codex CLI | `>=0.162.1`, `codex-cli MAJOR.MINOR.PATCH[-prerelease]` (desktop bundles report e.g. `0.167.0-alpha.3.2`; SemVer ordering applies, so `0.162.1-alpha.1` is below the minimum) | `plugin list --json --disable remote_plugin`; `plugin marketplace list --json --disable remote_plugin` |
83
84
 
84
85
  Malformed version output is `AGENT_READER_VERSION_INVALID`; below-minimum versions are `AGENT_READER_VERSION_UNSUPPORTED`; JSON parse/shape failure is `AGENT_INVENTORY_INVALID`. New versions such as 2.1.319 / 0.167.0 must pass exactly the existing inventory validation.
85
86
 
86
- The Codex flag was checked through this exact CLI's public `features list`; it excludes remote directory reads. Passing the version floor is necessary but not sufficient: both JSON shapes must validate. Claude uses the official installed array's `id`, user `scope`, enabled/version and load diagnostics. Other project-only target rows are unknown for this reader. Codex uses the official `installed` array and its qualified identity, installed/enabled/version. The exact target marketplace must match the registered HTTPS source in marketplace list; Codex's per-install `marketplaceSource` must also be present, supported and consistent. A local/different/unreadable source, duplicate/conflicting target or malformed/truncated/empty raw output is unknown. Only a complete valid clean inventory with no target in the observed user scope is not-installed. A known install may have version null; consumers must show version unknown. Official load/payload issues produce incomplete, not successful readiness. No plugin MCP/hook definitions, unrelated inventory or CLI raw diagnostics are returned.
87
+ The Codex flag was checked through this exact CLI's public `features list`; it excludes remote directory reads. Passing the version floor is necessary but not sufficient: both JSON shapes must validate. Claude uses the official installed array's `id`, user `scope`, enabled/version and load diagnostics. Other project-only target rows are unknown for this reader. Codex uses the official `installed` array and its qualified identity, installed/enabled/version. The exact target marketplace must match the registered HTTPS source in marketplace list; Codex's per-install `marketplaceSource` must also be present, supported and consistent. For official GitHub `sourceType:'git'` records, the exact credential/query/fragment-free `https://github.com/owner/repository.git` spelling identifies the same repository as the registered suffix-free URL. Normalize both the marketplace mapping and the per-install source independently; no other URL rewrite, repository substitution or source fallback is accepted. A local/different/unreadable source, duplicate/conflicting target or malformed/truncated/empty raw output is unknown. Only a complete valid clean inventory with no target in the observed user scope is not-installed. A known install may have version null; consumers must show version unknown. Official load/payload issues produce incomplete, not successful readiness. No plugin MCP/hook definitions, unrelated inventory or CLI raw diagnostics are returned.
87
88
 
88
89
  Commands run with an empty stdin in a disposable neutral cwd, only named HOME/config/platform paths, only the selected inherited/recovered absolute PATH plus allowlisted manager and standard XDG directory variables, no inherited model/API keys, proxy/auth/askpass/Node preload, and disabled Git credential helpers. The verified Node and selected executable directories prefix the selected PATH; retain its manager binary directories, so an official asdf shim can resolve `asdf`. Preserve existing XDG_CONFIG_HOME/CACHE_HOME/DATA_HOME/STATE_HOME/RUNTIME_DIR and CONFIG_DIRS/DATA_DIRS; absent variables stay absent and retain manager OS defaults. The supervisor never overwrites XDG paths with empty temporary directories. Only TMPDIR/TMP/TEMP and cwd use the owned disposable directory. Manager paths additionally include ASDF_CONFIG_FILE, MISE_CONFIG_DIR/GLOBAL_CONFIG_FILE/CACHE_DIR/STATE_DIR alongside the earlier manager-home keys; credentials, proxy, Node preloads and askpass remain excluded. Recovered shell manager/XDG paths follow the same allowlist; there is no fallback to a different installation. No model/install/upgrade/marketplace refresh command runs. On POSIX, a detached IPC reaper owns the neutral cwd outside a separate live command-group anchor. The anchor remains alive until output closes; abort, budget exhaustion, overflow, provider disposal or parent IPC disconnect makes the surviving reaper terminate only that live anchor's group. The reaper waits for group close before removing the cwd, so active writers cannot race deletion, including after caller death. No released PID/PGID or arbitrary process name is used. The caller also performs exact-path cleanup when alive. On Windows the same live anchor/reaper ownership uses the OS taskkill tree operation for its precise owned anchor; no process-name match or command-shell execution. Windows/Linux runtime evidence is separate from macOS fixture execution. There is no retry or stale-result cache. Automatic recovery after an internal reaper/anchor itself is externally SIGKILLed or suffers a native crash is outside this lifecycle guarantee; an external intervention can leave command processes or temporary files and is never reported as an installed/absent inventory.
89
90
 
@@ -94,7 +95,11 @@ HTTP refusals: `HOST_DENIED`, `ORIGIN_DENIED`, `FRAME_CONTROL_DENIED`, `CREDENTI
94
95
  Per-scope unknown reasons always include a safe code/message/action:
95
96
 
96
97
  - `CORE_SUITE_DECLARATION_MISSING`, `CORE_SUITE_DECLARATION_INVALID`, `CORE_SUITE_READ_FAILED`, `CORE_SUITE_DECLARATION_UNKNOWN`; incomplete: `CORE_SUITE_CURRENT_INCOMPLETE`, plus Core blocked codes.
97
- - `OTHER_RUNTIME_NOT_OBSERVED`, `CODEX_DESKTOP_SCOPE_UNOBSERVABLE`.
98
+ - `OTHER_RUNTIME_NOT_OBSERVED`, `CODEX_DESKTOP_SCOPE_UNOBSERVABLE`, `DSH_PROFILE_UNREADABLE`; incomplete: `DSH_PROFILE_SUITE_INCOMPLETE`.
99
+
100
+ Authoritative absence (read-only, no CLI execution): an official user profile directory (`CLAUDE_CONFIG_DIR` / `CODEX_HOME`, else `~/.claude` / `~/.codex`) that does not exist holds no user-scope plugin, so that channel is `not-installed` with the registered identity, whether or not the CLI is installed. An existing but inaccessible directory stays `AGENT_PROFILE_UNAVAILABLE`; an existing profile without a readable CLI stays `AGENT_READER_UNAVAILABLE`.
101
+
102
+ The HanaMesh client's `other-runtime` row (`kind:'other-client'`) reads the official DSH desktop profile on disk at the upstream default home (`~/.dsh/profiles/desktop`, `@deepseek-ai/dsh-home-paths` `.dsh`): after distinct physical profile identities have been established, a missing profile manifest or no `@hanamesh/hanamesh` dependency is `not-installed`; the bundle and all three members resolved at the bundle's exact versions and selected in `dsh.profile.bundles` is `installed` with the bundle version (a physical inventory, not that runtime's Core completeness); otherwise `incomplete`; unreadable JSON is unknown. Before every explicit read, resolve both the current profile and the target with `realpath`; the reused reader retains a selector, never a cached identity decision. The same physical directory (including symlink aliases), a missing current profile identity, or any identity-resolution failure keeps the row `unknown / OTHER_RUNTIME_NOT_OBSERVED`; no target inventory is read. A missing target directory also fails this identity check conservatively. A DSH listener never reports the HanaMesh client: that row is always `OTHER_RUNTIME_NOT_OBSERVED`.
98
103
  - `AGENT_READER_VERSION_INVALID`, `AGENT_READER_VERSION_UNSUPPORTED`, `AGENT_READER_NODE_REQUIRED`, `AGENT_SHELL_ENV_UNAVAILABLE`, `AGENT_READER_ENTRYPOINT_UNAVAILABLE`, `AGENT_READER_DISCOVERY_INVALID`.
99
104
  - `AGENT_PROFILE_UNAVAILABLE`, `AGENT_READER_UNAVAILABLE`, `AGENT_READER_PERMISSION_DENIED`, `AGENT_READER_TIMEOUT`, `AGENT_READER_OUTPUT_LIMIT`, `AGENT_READER_DIAGNOSTIC`, `AGENT_READER_CLEANUP_FAILED`, `AGENT_READER_FAILED`.
100
105
  - `AGENT_INVENTORY_INVALID`, `AGENT_INVENTORY_CONFLICT`, `AGENT_SCOPE_UNOBSERVABLE`, `AGENT_MARKETPLACE_SOURCE_UNAVAILABLE`, `AGENT_MARKETPLACE_SOURCE_MISMATCH`.
@@ -107,3 +112,9 @@ Official command references: [Claude Code plugin commands](https://code.claude.c
107
112
  For DSH/HanaMesh runtime observations, versionKind is suite-bundle and version is the installed @hanamesh/hanamesh version. Component rows retain their physical versions. Agent distribution observations retain their existing semantics.
108
113
 
109
114
  R115 regression fixtures execute real non-TTY Bash, asdf 0.20.2 official shim and mise 2026.11.4 with default/custom/shell-defined global XDG selection. Tool provenance, hashes and the explicit test-only binary inputs are in source `tests/fixtures/installation-managers/README.md`; binaries do not enter the npm package.
115
+
116
+ 0.6.25:Claude 官方市场清单的 `source: "github"` 使用 `repo` 的 owner/repository 身份与登记的 GitHub HTTPS 市场核对;`url` / `git` 继续核对其 URL;官方 GitHub HTTPS Git 来源允许同一仓库的 `.git` 后缀。来源不同仍为 `AGENT_MARKETPLACE_SOURCE_MISMATCH`,不会把无响应或缺少 CLI 当作未安装。
117
+
118
+ 安装物回归:`node tests/native/installed-detection.mjs /absolute/HanaMesh.app /absolute/new-evidence-directory https://website-origin` 从实际 app 资源读取种子版本,以全新 HOME / DSH_HOME / shared home / user-data 启动同一可执行文件;仅在出现该按钮时选择 Set up later,不登录或开启记录;随后断言获准 Origin 的发现响应、真实 POST、五作用域及本 runtime 的完整套件/版本。检测失败退出非零,保留逐端口响应、UI 与首错;不以页面可打开或目录中存在包文件代替运行结果。该手工安装物测试需要 macOS GUI 会话,默认源码单测另覆盖全新彩排配置初始化。
119
+
120
+ 未发布候选可在最后追加本人自建、无凭据且由正常 DSH CLI 安装的 profile 路径。回归把该资料复制到新 DSH_HOME,再运行同一安装物;不手改 app 或 dist。默认不传此参数时仍检查 app 原始种子。
package/docs/LIBRARY.md CHANGED
@@ -9,7 +9,7 @@
9
9
  ## 目录源
10
10
 
11
11
  - `library.fixture` 只用于受控验收;内容必须符合 `schemas/catalog-provider-page.schema.json`。
12
- - `library.sources` **未配置**时默认为 HanaMesh 目录源 `https://market.hanamesh.com/catalog-source.json?view=hanamesh`(rc.26;显式 `[]` = 不要来源);可有多项,但浏览时必须恰好启用一项。目录 manifest 接受标准端口 HTTPS;本机回环 127.0.0.1、::1、localhost 另接受任意端口 HTTP(Node URL 解析后精确匹配主机名,安全上下文回环例外)。无凭据/fragment,仅允许可选查询 view=hanamesh;endpoint 和重定向必须保持完整同源(协议、主机、端口),不把主机别名互换。新默认仅在初始 revision=0 且来源为空时播种,已保存旧来源、自定义来源和手动清空列表均不自动改写。
12
+ - `library.sources` **未配置**时默认为 HanaMesh 目录源 `https://market.hanamesh.com/catalog-source.json?view=hanamesh`(rc.26;显式 `[]` = 不要来源);可有多项,但浏览时必须恰好启用一项。目录 manifest 接受标准端口 HTTPS;HTTPS 本机回环 127.0.0.1、::1、localhost 及 *.localhost 可使用任意端口;本机回环 127.0.0.1、::1、localhost 另接受任意端口 HTTP(Node URL 解析后精确匹配主机名,安全上下文回环例外)。无凭据/fragment,仅允许可选查询 view=hanamesh;endpoint 和重定向必须保持完整同源(协议、主机、端口),不把主机别名互换。新默认仅在初始 revision=0 且来源为空时播种,已保存旧来源、自定义来源和手动清空列表均不自动改写。
13
13
  - 请求固定 `Accept: application/json` 与 `Accept-Encoding: identity`;最多三次同源跳转,拒绝压缩响应与超过 2 MiB 的响应。
14
14
  - `categories` 含 `hanamesh-app` 且 `package.registry` 为 `npm` 的条目是应用(安装后读 `app.json`、供给 runtime);其它 `package.registry` 为 `npm` 的条目是插件(rc.28 起同样可装卸,只跑 `dsh plugin add/remove`);无 npm 包的条目(repository-only)只展示。
15
15
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "package": {
3
3
  "name": "@hanamesh/dsh-app-host",
4
- "version": "0.6.24",
4
+ "version": "0.6.26",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "source": "git+https://github.com/yzsnstotz/hanamesh-plugin-app-host.git",
7
7
  "purpose": "Application ownership, view leases, Router forwarding, and application library for official DSH"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanamesh/dsh-app-host",
3
- "version": "0.6.24",
3
+ "version": "0.6.26",
4
4
  "description": "HanaMesh application ownership, durable view leases, and application library for official DSH 0.2.x",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -68,6 +68,28 @@
68
68
  "docs/DSH_COMPATIBILITY.md",
69
69
  "docs/INSTALLATION_STATUS.md"
70
70
  ],
71
+ "scripts": {
72
+ "build": "node scripts/build.mjs",
73
+ "test": "node scripts/safe-git.mjs node --test --test-reporter=tap --test-concurrency=1 tests/*.test.mjs",
74
+ "test:contract": "pnpm run build && node --test --test-reporter=tap --test-concurrency=1 tests/install-target-contract.test.mjs tests/catalog-major-compat.test.mjs",
75
+ "test:mutation": "node scripts/safe-git.mjs node scripts/mutations.mjs",
76
+ "test:types": "tsc --noEmit --strict --target ES2022 --module NodeNext --moduleResolution NodeNext tests/consumer.mts",
77
+ "test:native-driver": "node scripts/safe-git.mjs node --test --test-reporter=tap tests/native/driver.test.mjs",
78
+ "check": "node scripts/safe-git.mjs node scripts/check.mjs",
79
+ "prepack": "npm run build",
80
+ "demo": "node examples/demo.mjs",
81
+ "preflight:dsh": "node scripts/preflight-dsh.mjs",
82
+ "check:consistency": "node scripts/check-consistency.mjs",
83
+ "check:toolchain": "node scripts/check-toolchain.mjs",
84
+ "preflight": "node scripts/preflight.mjs",
85
+ "verify:pack": "node scripts/safe-git.mjs node scripts/verify-package.mjs",
86
+ "check:devkit-supply": "node scripts/check-devkit-consumption.mjs",
87
+ "check:provision-supply": "node scripts/check-provision-consumption.mjs",
88
+ "install:safe": "node scripts/safe-git.mjs pnpm install --frozen-lockfile",
89
+ "release:check": "hanamesh-release check --lockfile pnpm-lock.yaml --installation-targets release/installation-targets.json",
90
+ "release:publish": "hanamesh-release publish --lockfile pnpm-lock.yaml --installation-targets release/installation-targets.json",
91
+ "prepublishOnly": "hanamesh-release source-guard"
92
+ },
71
93
  "dsh": {
72
94
  "bundle": {
73
95
  "patch": "./profile/cordis.patch.yml"
@@ -146,27 +168,8 @@
146
168
  "@types/node": "24.13.4",
147
169
  "@types/js-yaml": "4.0.9"
148
170
  },
171
+ "packageManager": "pnpm@10.33.0",
149
172
  "bundleDependencies": [
150
173
  "@hanamesh/lib-provision"
151
- ],
152
- "scripts": {
153
- "build": "node scripts/build.mjs",
154
- "test": "node scripts/safe-git.mjs node --test --test-reporter=tap --test-concurrency=1 tests/*.test.mjs",
155
- "test:contract": "pnpm run build && node --test --test-reporter=tap --test-concurrency=1 tests/install-target-contract.test.mjs tests/catalog-major-compat.test.mjs",
156
- "test:mutation": "node scripts/safe-git.mjs node scripts/mutations.mjs",
157
- "test:types": "tsc --noEmit --strict --target ES2022 --module NodeNext --moduleResolution NodeNext tests/consumer.mts",
158
- "test:native-driver": "node scripts/safe-git.mjs node --test --test-reporter=tap tests/native/driver.test.mjs",
159
- "check": "node scripts/safe-git.mjs node scripts/check.mjs",
160
- "demo": "node examples/demo.mjs",
161
- "preflight:dsh": "node scripts/preflight-dsh.mjs",
162
- "check:consistency": "node scripts/check-consistency.mjs",
163
- "check:toolchain": "node scripts/check-toolchain.mjs",
164
- "preflight": "node scripts/preflight.mjs",
165
- "verify:pack": "node scripts/safe-git.mjs node scripts/verify-package.mjs",
166
- "check:devkit-supply": "node scripts/check-devkit-consumption.mjs",
167
- "check:provision-supply": "node scripts/check-provision-consumption.mjs",
168
- "install:safe": "node scripts/safe-git.mjs pnpm install --frozen-lockfile",
169
- "release:check": "hanamesh-release check --lockfile pnpm-lock.yaml --installation-targets release/installation-targets.json",
170
- "release:publish": "hanamesh-release publish --lockfile pnpm-lock.yaml --installation-targets release/installation-targets.json"
171
- }
172
- }
174
+ ]
175
+ }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "urn:dsh-community-market:schema:catalog-source:1.1.0",
4
4
  "title": "DSH Community Market catalog source manifest",
5
- "description": "A provider-neutral declaration for one user-selectable JSON catalog endpoint (standard-port HTTPS or loopback HTTP on 127.0.0.1, ::1 or localhost). A user registers its manifest URL; selection and all other user-owned state stay local. The smallest recommended profile supports q, category, cursor, and limit and uses 50 as an example page size, not a global cap.",
5
+ "description": "A provider-neutral declaration for one user-selectable JSON catalog endpoint (standard-port HTTPS, local HTTPS on loopback or *.localhost, or loopback HTTP on 127.0.0.1, ::1 or localhost). A user registers its manifest URL; selection and all other user-owned state stay local. The smallest recommended profile supports q, category, cursor, and limit and uses 50 as an example page size, not a global cap.",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": [
@@ -41,7 +41,7 @@
41
41
  "type": "string",
42
42
  "format": "uri",
43
43
  "maxLength": 2048,
44
- "pattern": "^(?:https://(?![^/?#]*@)(?![^/?#]*:)[^/?#\\s]+|http://(?:127\\.0\\.0\\.1|\\[::1\\]|localhost)(?::[0-9]+)?)(?:/[^#]*)?$"
44
+ "pattern": "^(?:https://(?![^/?#]*@)(?![^/?#]*:)[^/?#\\s]+|https://(?:127\\.0\\.0\\.1|\\[::1\\]|(?:[^/?#:@\\s]+\\.)*localhost)(?::[0-9]+)?|http://(?:127\\.0\\.0\\.1|\\[::1\\]|localhost)(?::[0-9]+)?)(?:/[^#]*)?$"
45
45
  },
46
46
  "attribution": {
47
47
  "type": "object",
@@ -61,7 +61,7 @@
61
61
  "type": "string",
62
62
  "format": "uri",
63
63
  "maxLength": 2048,
64
- "pattern": "^(?:https://(?![^/?#]*@)(?![^/?#]*:)[^/?#\\s]+|http://(?:127\\.0\\.0\\.1|\\[::1\\]|localhost)(?::[0-9]+)?)(?:/[^#]*)?$"
64
+ "pattern": "^(?:https://(?![^/?#]*@)(?![^/?#]*:)[^/?#\\s]+|https://(?:127\\.0\\.0\\.1|\\[::1\\]|(?:[^/?#:@\\s]+\\.)*localhost)(?::[0-9]+)?|http://(?:127\\.0\\.0\\.1|\\[::1\\]|localhost)(?::[0-9]+)?)(?:/[^#]*)?$"
65
65
  },
66
66
  "notice": {
67
67
  "type": "string",
@@ -86,8 +86,8 @@
86
86
  "type": "string",
87
87
  "format": "uri",
88
88
  "maxLength": 2048,
89
- "pattern": "^(?:https://(?![^/?#]*@)(?![^/?#]*:)[^/?#\\s]+|http://(?:127\\.0\\.0\\.1|\\[::1\\]|localhost)(?::[0-9]+)?)(?:/[^?#\\s]*)?/v1/plugins$",
90
- "description": "Standard-port HTTPS, or HTTP on 127.0.0.1, ::1 or localhost at any port, without credentials, query or fragment. Endpoint must share the exact user-approved manifest origin and end in /v1/plugins. URL parsing and port validity are enforced by the consumer."
89
+ "pattern": "^(?:https://(?![^/?#]*@)(?![^/?#]*:)[^/?#\\s]+|https://(?:127\\.0\\.0\\.1|\\[::1\\]|(?:[^/?#:@\\s]+\\.)*localhost)(?::[0-9]+)?|http://(?:127\\.0\\.0\\.1|\\[::1\\]|localhost)(?::[0-9]+)?)(?:/[^?#\\s]*)?/v1/plugins$",
90
+ "description": "Standard-port HTTPS, HTTPS on loopback or *.localhost at any port, or HTTP on 127.0.0.1, ::1 or localhost at any port, without credentials, query or fragment. Endpoint must share the exact user-approved manifest origin and end in /v1/plugins. URL parsing and port validity are enforced by the consumer."
91
91
  },
92
92
  "method": {
93
93
  "const": "GET"