@josephyulei/summon-foundation 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/bin/summon.mjs +7 -6
- package/lib/environment-check.mjs +2 -11
- package/lib/journey-control.mjs +8 -6
- package/lib/journey-view.mjs +41 -0
- package/lib/maintenance-handoff.mjs +1 -0
- package/lib/operation-result.mjs +12 -2
- package/lib/progress-page.mjs +7 -4
- package/lib/single-page-view.mjs +16 -13
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# summon foundation
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
当前用户共用一份程序与一份可选对话功能。安装仅选择软件位置,不选择业务项目。各项目随后单独接入,资料各自保管;旧安装不自动迁移或覆盖。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
保留真实下载状态与独立确认。基础环境不能安全启动页面时,由 Codex 对话先披露并取得独立批准,再准备专用环境;页面启动后全程同页。具体方法见仓库 `docs/install-with-codex.md`,不能先运行 npx 来准备缺失的 Node。不修改共享 PATH;获取器专用环境及其回执按缓存保留,程序另用随包运行时。发行发现仅需 curl,获取还需 tar;已验证载荷的确认、恢复和卸载不依赖下载工具。
|
|
6
|
+
|
|
7
|
+
0.1.9 单页试用入口与 Foundation 0.2.15 的运行引擎共同支持当前用户共用安装及清晰的完成、取消和部分完成反馈。同一页面内保留目录选择、程序和 Skill 独立确认。真实安装、新任务发现和主动告知仍需独立验收。
|
|
6
8
|
|
|
7
9
|
首次 URL 持续显示本次流程;确认仅转交本次受控子进程的当前计划,不新开manager页。--acquire仍只读获取;--resume仅在用户明确要求后恢复未完成Skill,不重放程序安装。
|
|
8
10
|
|
|
@@ -14,7 +16,7 @@ Foundation 的薄获取入口,不包含完整运行包,没有 npm 安装生
|
|
|
14
16
|
确认 registry 中存在本版本及对应不可变 Release 后,在具备安装权限的新 Codex 对话粘贴:
|
|
15
17
|
|
|
16
18
|
```text
|
|
17
|
-
npx --yes --package @josephyulei/summon-foundation@0.1.
|
|
19
|
+
npx --yes --package @josephyulei/summon-foundation@0.1.9 summon foundation
|
|
18
20
|
```
|
|
19
21
|
|
|
20
22
|
旧版退出推荐入口;支持边界见[现行支持策略](https://github.com/y15801172513-lang/foundation/blob/main/docs/install-platform-024.md#现行支持策略)。旧不可变版本不修改,不自动迁移或清除旧安装。
|
package/bin/summon.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import {followMaintenance} from '../lib/maintenance-handoff.mjs';
|
|
|
11
11
|
import {installedClient} from '../lib/manager-step.mjs';
|
|
12
12
|
import {resumeSkillHandoff} from '../lib/resume-handoff.mjs';
|
|
13
13
|
import {parseSummonArgs} from '../lib/cli-options.mjs';
|
|
14
|
-
import {createOperation,saveOperation,readOperation,runtimeObservation} from '../lib/operation-result.mjs';
|
|
14
|
+
import {createOperation,saveOperation,readOperation,runtimeObservation,operationExitCode} from '../lib/operation-result.mjs';
|
|
15
15
|
import {createJourneyControl} from '../lib/journey-control.mjs';
|
|
16
16
|
import {inspectEntryEnvironment} from '../lib/environment-check.mjs';
|
|
17
17
|
|
|
@@ -52,7 +52,7 @@ function stageDirectory(){
|
|
|
52
52
|
}
|
|
53
53
|
let operation=null,stage=null,progressPage=null;
|
|
54
54
|
const journeyControl=createJourneyControl(()=>operation,patch=>patch?updateOperation(patch):progressPage?.publish());
|
|
55
|
-
function updateOperation(patch){if(!operation)return;Object.assign(operation,patch,{updatedAt:new Date().toISOString()});const file=saveOperation(operation,stage);if(file)operation.resultFile=file;progressPage?.publish();console.log(JSON.stringify({status:'FOUNDATION_INSTALL_OPERATION',...operation,resultFile:file,recordSaved:!!file}));}
|
|
55
|
+
function updateOperation(patch){if(!operation)return;Object.assign(operation,patch,{updatedAt:new Date().toISOString()});if(operation.terminal)operation.exitCode=operationExitCode(operation);const file=saveOperation(operation,stage);if(file)operation.resultFile=file;progressPage?.publish();console.log(JSON.stringify({status:'FOUNDATION_INSTALL_OPERATION',...operation,resultFile:file,recordSaved:!!file}));}
|
|
56
56
|
try{
|
|
57
57
|
const args=parseSummonArgs(process.argv.slice(2));
|
|
58
58
|
if(args.help){console.log(help);process.exit(0);}
|
|
@@ -74,7 +74,7 @@ try{
|
|
|
74
74
|
console.log(JSON.stringify({status:'FOUNDATION_SINGLE_PAGE',url:progressPage.url,operationId:operation.operationId}));
|
|
75
75
|
const env={...process.env};for(const key of ['NODE_OPTIONS','NODE_PATH','NODE_V8_COVERAGE','NODE_REDIRECT_WARNINGS','NODE_COMPILE_CACHE','NODE_COMPILE_CACHE_PORTABLE','NODE_PRESERVE_SYMLINKS'])delete env[key];
|
|
76
76
|
const result=await resumeSkillHandoff({file:args.resume,env,journeyControl,onChange:updateOperation});
|
|
77
|
-
updateOperation(result);process.exitCode=result
|
|
77
|
+
updateOperation(result);process.exitCode=operationExitCode(result);
|
|
78
78
|
}else if(maintenance){
|
|
79
79
|
plainPath(args.root);
|
|
80
80
|
const env={...process.env};for(const key of ['NODE_OPTIONS','NODE_PATH','NODE_V8_COVERAGE','NODE_REDIRECT_WARNINGS','NODE_COMPILE_CACHE','NODE_COMPILE_CACHE_PORTABLE','NODE_PRESERVE_SYMLINKS'])delete env[key];
|
|
@@ -96,7 +96,7 @@ try{
|
|
|
96
96
|
updateOperation({phase:'acquired'});
|
|
97
97
|
}
|
|
98
98
|
updateOperation(await followMaintenance({operation,kind:operation.kind,candidate,env,onChange:updateOperation,journeyControl}));
|
|
99
|
-
process.exitCode=operation
|
|
99
|
+
process.exitCode=operationExitCode(operation);
|
|
100
100
|
await progressPage.close();progressPage=null;
|
|
101
101
|
}else if(!args.prepare&&!args.acquire){
|
|
102
102
|
if(initialEnvironment.state!=='ready'){operation=createOperation();updateOperation({phase:'checking-environment',environment:initialEnvironment});fail('发行查询所需环境无法核实;尚未联网');}
|
|
@@ -114,6 +114,7 @@ try{
|
|
|
114
114
|
const environment=inspectEntryEnvironment({purpose:'acquire'});updateOperation({phase:'checking-environment',environment});
|
|
115
115
|
if(environment.state!=='ready')fail('获取环境检查未通过:'+environment.state+';缺少 '+environment.missing.join('、')+';不会以安装 Node 替代缺失的系统工具');
|
|
116
116
|
if(!args.acquire){
|
|
117
|
+
updateOperation({phase:'discovering'});
|
|
117
118
|
const metadata=inspectRelease(args.version);args.version=metadata.version;
|
|
118
119
|
updateOperation({phase:'choosing-intent',environment,version:metadata.version,sourceCommit:metadata.sourceCommit,acquisitionRoot:stage,destinationIntent:args.destination||'',journeyContext:{id:operation.operationId,skillChoice:'undecided'}});
|
|
119
120
|
const choice=await journeyControl.waitChoice();
|
|
@@ -144,6 +145,6 @@ try{
|
|
|
144
145
|
catch(error){updateOperation({state:'partial',phase:'finished',terminal:true,skillRegistered:false,handoffFailure:{code:error.code||'SKILL_HANDOFF_REQUIRES_VERIFICATION',command:error.command||null,exitCode:error.exitCode??null},next:'程序已安装,Skill 接续未完成;保留当前计划与结果,仅核实未完成步骤,不重装程序或重放确认。'});}
|
|
145
146
|
}
|
|
146
147
|
if(!operation.terminal)updateOperation({state:'verification-required',terminal:true,childExitCode:ended.code,next:'服务退出不等于成功或取消;只读核验原 session 与 installed 状态,不自动重试'});
|
|
147
|
-
process.exitCode=operation
|
|
148
|
+
process.exitCode=operationExitCode(operation);
|
|
148
149
|
}
|
|
149
|
-
}catch(e){const message=String(e.message).replace(/https?:\/\/\S+/g,'[已脱敏网址]').replace(/(?:github_pat_|ghp_|npm_)[A-Za-z0-9_]+/g,'[已脱敏凭据]');updateOperation({state:e.code==='ENTRY_CHOICE_CANCELLED'?'cancelled-no-install':e.code==='ENTRY_CHOICE_EXPIRED'?'expired-no-install':operation?.programState==='completed'?'partial':operation&&operation.installationWrites!=='none'?'verification-required':'failed',terminal:true,...(operation?.programState==='completed'?{failedPhase:operation.phase,phase:'finished'}:{}),errorCode:['ACQUISITION_TRANSPORT_FAILED','ACQUISITION_VALIDATION_FAILED','ENTRY_CHOICE_CANCELLED','ENTRY_CHOICE_EXPIRED'].includes(e.code)?e.code:'INSTALL_ENTRY_FAILED',diagnostic:e.diagnostic||null,next:message+'。先核验旧工具进程已结束及同次结果;保留材料,不自动重试或重放安装确认'});console.error(`错误:${message}`);process.exitCode=
|
|
150
|
+
}catch(e){const message=String(e.message).replace(/https?:\/\/\S+/g,'[已脱敏网址]').replace(/(?:github_pat_|ghp_|npm_)[A-Za-z0-9_]+/g,'[已脱敏凭据]');updateOperation({state:e.code==='ENTRY_CHOICE_CANCELLED'?'cancelled-no-install':e.code==='ENTRY_CHOICE_EXPIRED'?'expired-no-install':operation?.programState==='completed'?'partial':operation&&operation.installationWrites!=='none'?'verification-required':'failed',terminal:true,...(operation?.programState==='completed'?{failedPhase:operation.phase,phase:'finished'}:{}),errorCode:['ACQUISITION_TRANSPORT_FAILED','ACQUISITION_VALIDATION_FAILED','ENTRY_CHOICE_CANCELLED','ENTRY_CHOICE_EXPIRED'].includes(e.code)?e.code:'INSTALL_ENTRY_FAILED',diagnostic:e.diagnostic||null,next:message+'。先核验旧工具进程已结束及同次结果;保留材料,不自动重试或重放安装确认'});console.error(`错误:${message}`);process.exitCode=operationExitCode(operation);}finally{if(progressPage)await progressPage.close();}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {spawnSync} from 'node:child_process';
|
|
4
3
|
import crypto from 'node:crypto';
|
|
5
4
|
import {plainPath} from './acquire.mjs';
|
|
6
5
|
|
|
@@ -43,14 +42,6 @@ export function inspectEntryDestination(input){
|
|
|
43
42
|
// Intent preflight only. The installed engine repeats identity checks and seals
|
|
44
43
|
// the actual scope in the independently confirmed lifecycle plan.
|
|
45
44
|
export function inspectScopeIntent(kind, input, destination){
|
|
46
|
-
if(kind==='user')return {kind,projectRoot:null};
|
|
47
|
-
|
|
48
|
-
let projectRoot=input.trim();
|
|
49
|
-
if((projectRoot.startsWith('"')&&projectRoot.endsWith('"'))||(projectRoot.startsWith("'")&&projectRoot.endsWith("'")))projectRoot=projectRoot.slice(1,-1);
|
|
50
|
-
plainPath(projectRoot);
|
|
51
|
-
if(!fs.lstatSync(projectRoot).isDirectory())throw Error('项目根不是已有目录');
|
|
52
|
-
const git=spawnSync('/usr/bin/git',['-C',projectRoot,'rev-parse','--show-toplevel'],{encoding:'utf8',env:{PATH:'/usr/bin:/bin',GIT_OPTIONAL_LOCKS:'0'},timeout:10000});
|
|
53
|
-
if(git.status!==0||git.stdout.trim()!==projectRoot)throw Error('仅当前项目模式需要明确的 Git 仓库根;不会自动创建仓库');
|
|
54
|
-
if(destination===projectRoot||destination.startsWith(projectRoot+path.sep)||projectRoot.startsWith(destination+path.sep))throw Error('程序目录不能与业务项目互相包含;请选择独立目录');
|
|
55
|
-
return {kind,projectRoot};
|
|
45
|
+
if(kind==='user'&&input==null)return {kind,projectRoot:null};
|
|
46
|
+
throw Error('新安装供当前用户的不同项目共用,不接受项目范围;已有项目安装不会自动迁移');
|
|
56
47
|
}
|
package/lib/journey-control.mjs
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import {inspectEntryDestination,inspectScopeIntent,inspectEntryEnvironment} from './environment-check.mjs';
|
|
3
3
|
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
4
|
+
// Fresh installs are shared by the current user. Reject stale project intent;
|
|
5
|
+
// never silently reinterpret it. Existing maintenance keeps its signed scope.
|
|
6
|
+
function scopeFields(value){
|
|
7
|
+
inspectScopeIntent(value.scopeKind===undefined?'user':value.scopeKind,value.projectRoot);
|
|
8
|
+
return {};
|
|
9
|
+
}
|
|
7
10
|
|
|
8
11
|
// Only the exact child created by this invocation may supply an active view.
|
|
9
12
|
// Browser input cannot name a URL, route, transport token or future plan.
|
|
@@ -55,10 +58,9 @@ export function createJourneyControl(readRecord, changed) {
|
|
|
55
58
|
}
|
|
56
59
|
if(body.action==='choose'&&resolveChoice){
|
|
57
60
|
if(typeof body.destination!=='string'||!body.destination.trim()||!['selected','skipped'].includes(body.skillChoice))throw Error('请选择完整目录和 Skill 意向');
|
|
61
|
+
scopeFields(body);
|
|
58
62
|
const destinationCheck=inspectEntryDestination(body.destination);
|
|
59
|
-
|
|
60
|
-
const scope=inspectScopeIntent(scopeKind,body.projectRoot,destinationCheck.destination);
|
|
61
|
-
choice={destination:destinationCheck.destination,skillChoice:body.skillChoice,scopeKind,projectRoot:scope.projectRoot,destinationCheck};const resolve=resolveChoice;clearTimeout(choiceTimer);resolveChoice=null;rejectChoice=null;resolve(choice);changed();return {status:200,body:{state:'intent-selected'}};
|
|
63
|
+
choice={destination:destinationCheck.destination,skillChoice:body.skillChoice,scopeKind:'user',projectRoot:null,destinationCheck};const resolve=resolveChoice;clearTimeout(choiceTimer);resolveChoice=null;rejectChoice=null;resolve(choice);changed();return {status:200,body:{state:'intent-selected'}};
|
|
62
64
|
}
|
|
63
65
|
if(!active)throw Error('当前没有可确认的计划;请等待或查询结果');
|
|
64
66
|
const a=active;
|
package/lib/journey-view.mjs
CHANGED
|
@@ -1,4 +1,45 @@
|
|
|
1
1
|
// Presentation only. Planned steps never grant execution authority.
|
|
2
|
+
export function lifecycleContent(record, observations) {
|
|
3
|
+
const kind=record.kind||'install',verb={install:'安装',update:'更新',uninstall:'卸载'}[kind]||'处理';
|
|
4
|
+
const programDone=record.programState==='completed'||observations.program?.state==='completed';
|
|
5
|
+
const registration=observations.register?.state,removal=observations.remove?.state;
|
|
6
|
+
const skipped=record.journeyContext?.skillChoice==='skipped';
|
|
7
|
+
const stepStates=Object.values(observations).map(x=>x?.state);
|
|
8
|
+
const cancelled=['cancelled','cancelled-no-install','shutdown-no-install'].includes(record.state)||record.terminal&&stepStates.includes('cancelled');
|
|
9
|
+
const expired=['expired','expired-no-install'].includes(record.state)||record.terminal&&stepStates.includes('expired');
|
|
10
|
+
const environmentBlocked=record.environment&&record.environment.state!=='ready';
|
|
11
|
+
const anyCompleted=Object.entries(observations).some(([key,value])=>['program','material','register','remove'].includes(key)&&value?.state==='completed');
|
|
12
|
+
let title=verb+' Foundation';
|
|
13
|
+
if(record.terminal){
|
|
14
|
+
if(programDone)title='Foundation 已'+verb;
|
|
15
|
+
else if(cancelled)title='已取消'+verb;
|
|
16
|
+
else if(expired)title=verb+'确认已到期';
|
|
17
|
+
else if(environmentBlocked)title='运行环境需要处理';
|
|
18
|
+
else if(record.state==='failed'||stepStates.includes('failed'))title=verb+'未完成';
|
|
19
|
+
else title=anyCompleted?'部分步骤已完成':'操作结果待确认';
|
|
20
|
+
if(programDone&&kind!=='uninstall'&&!skipped&&!record.skillRegistered)title+=';对话功能'+(['cancelled','skipped'].includes(registration)?'未启用':registration==='failed'?'未处理成功':'尚未完成');
|
|
21
|
+
}
|
|
22
|
+
let program=programDone?'程序'+verb+'已完成。'+(kind==='uninstall'?'卸载结果已保存。':record.runtimeHealth==='passed'?'已检查,可以正常打开。':'能否正常打开仍需检查。')
|
|
23
|
+
:record.installationWrites==='none'||['not-started','cancelled','expired'].includes(record.programState)||['cancelled','expired'].includes(observations.program?.state)?'尚未'+(kind==='install'?'安装 Foundation':'执行程序'+verb)+'。':'程序是否发生变化仍需查看本次结果,请勿重复执行。';
|
|
24
|
+
if(programDone&&kind==='update'&&record.programResult?.cleanup)program+=record.programResult.cleanup.state==='completed'?'本次下载暂存已按计划清理。':'部分下载文件已保留,不影响已完成的更新。';
|
|
25
|
+
if(!programDone&&record.programState==='failed')program=verb+'未完成。'+(record.rollback==='completed'?'本次程序变更已回退;不表示安装成功。':'是否已回退仍待核实;不要重复执行。');
|
|
26
|
+
const skill=kind==='uninstall'?(removal==='completed'?'已处理本安装的对话功能文件;修改或未知文件按计划保留。':removal?'对话功能尚未移除成功,原文件保留。':record.hadSkill===false?'本安装没有已登记的对话功能,此次未删除其他 Skill。':'尚未确认是否需要移除对话功能。')
|
|
27
|
+
:record.skillRegistered===true?'对话功能文件已就位,请在新 Codex 对话中尝试“打开 Foundation”。'
|
|
28
|
+
:skipped?(kind==='update'?'本次未新增对话功能。':'本次不启用 Codex 对话功能。')
|
|
29
|
+
:['cancelled','skipped'].includes(registration)?'本次未启用 Codex 对话功能。'
|
|
30
|
+
:registration==='failed'?'对话功能未处理成功;只需检查这一项,不必重装程序。'
|
|
31
|
+
:record.terminal&&!programDone&&(cancelled||expired||record.programState==='failed')?'本次未安装或启用 Skill;程序失败不会继续安装 Skill。'
|
|
32
|
+
:observations.material?.state==='completed'?'Skill 已准备,还需要你确认启用到 Codex。'
|
|
33
|
+
:record.journeyContext?.skillChoice==='selected'?'已选择对话功能,仍需单独确认后才能启用。':'可选择是否启用 Codex 对话功能。';
|
|
34
|
+
const description=record.terminal?program+' '+skill:kind==='install'?'给当前用户安装一份 Foundation,不同项目按需使用,项目资料各自保管。'+(skipped?'本次不启用对话功能。':'对话功能可选,需另外确认。')
|
|
35
|
+
:kind==='update'?'更新这份 Foundation,具体保留范围见本步计划。'+(record.hadSkill===true?'已有对话功能会另行询问是否更新。':record.hadSkill===false?'本次不新增对话功能。':'正在检查已有对话功能。')
|
|
36
|
+
:'只移除本次确认的 Foundation 文件,不清空整个文件夹。'+(removal==='completed'?'对话功能已按前一步结果处理,现在确认程序卸载。':record.hadSkill===true?'先单独确认移除它的对话功能。':record.hadSkill===false?'本安装未登记对话功能,不删除其他 Skill。':'正在检查是否存在关联的对话功能。');
|
|
37
|
+
const environment=environmentBlocked?'运行环境检查未通过。'+(record.environment.missing?.length?'缺少:'+record.environment.missing.join('、')+'。':'')+(record.environment.preparation?.reason||'请在当前 Codex 对话中检查所需环境;不会自动安装或更改系统工具。'):'环境已就绪。';
|
|
38
|
+
const summaries={select:'已确认本次软件位置;项目仍在各自的位置。',acquire:'下载文件已通过来源与完整性检查。',program,material:'Skill 已准备;是否启用到 Codex,请看后续结果。',register:skill,remove:skill,health:'已检查程序,能够正常打开。',environment,cleanup:'本次下载文件已按结果处理;未能安全删除的文件保留。'};
|
|
39
|
+
const next=environmentBlocked?environment:record.terminal&&programDone?(kind==='uninstall'?'保留本次卸载结果;其中列有剩余文件和缓存清理说明。':record.skillRegistered?'请在新 Codex 对话中尝试“打开 Foundation”;使用具体项目时再单独接入。':skipped?'可从程序安装位置重新打开 Foundation。需要对话功能时,再在当前对话中提出。':'在当前对话中说明只继续未完成的对话功能;先检查结果,不重复安装程序。'):record.terminal&&(cancelled||expired)?'本次不会继续执行。如仍需'+verb+',请在当前对话中重新提出,并核对新的计划。':record.next;
|
|
40
|
+
return {title,program,skill,description,environment,summaries,next,project:kind==='install'?'本次没有接入任何项目;以后在需要的项目中单独确认。':'项目文件仍按各自项目管理;本次不会自动接入其他项目。'};
|
|
41
|
+
}
|
|
42
|
+
|
|
2
43
|
export function journeyView({id,kind='install',observations={},skillChoice='undecided',includeAcquisition=false,includeSelection=false,includeHealth=true,includeCleanup=false,includeSkillRemoval=false}) {
|
|
3
44
|
const labels={acquire:'获取并核验运行文件',select:'选择安装目录',program:({install:'安装程序',update:'更新程序',uninstall:'移除程序'})[kind]||'执行当前维护',health:'验证稳定入口',cleanup:'处理本次暂存',material:'准备对话能力材料',register:'注册到 Codex',remove:'移除 Codex 注册'};
|
|
4
45
|
const statusLabels={completed:'已完成',pending:'待确认',planned:'尚待处理',running:'进行中',executing:'进行中',consumed:'进行中',failed:'失败',cancelled:'已取消',expired:'已过期',skipped:'已跳过',undecided:'可选,待选择',retained:'已处理,保留部分材料','verification-required':'待核实'};
|
|
@@ -30,6 +30,7 @@ export async function followMaintenance({operation,kind,candidate,env,onChange,j
|
|
|
30
30
|
// validates its receipt and files. It does not authorize removal.
|
|
31
31
|
const registration=plainPath(path.join(root,'state/codex-skill-registration.json'));
|
|
32
32
|
const hadSkill=fs.existsSync(registration);
|
|
33
|
+
onChange({hadSkill});
|
|
33
34
|
let previousPlanRef=null;
|
|
34
35
|
const steps={};
|
|
35
36
|
onChange({kind,currentVersion:current.version,installId:current.identity.installId,previousCandidateHash:current.candidateHash,journeyContext:{id:operation.operationId,skillChoice:kind==='update'&&hadSkill?'selected':'skipped'},next:hadSkill?(kind==='update'?'先确认程序更新,再确认刷新完整归属的 Codex 文件;未知或修改文件保留。':'先核对并确认解除已关联的 Codex 副本,再继续程序卸载;未知或修改文件保留。'):'核对程序操作;本次不会新增 Codex Skill。'});
|
package/lib/operation-result.mjs
CHANGED
|
@@ -5,6 +5,14 @@ import crypto from 'node:crypto';
|
|
|
5
5
|
export function createOperation() {
|
|
6
6
|
return {schemaVersion:'1.0.0',operationId:crypto.randomUUID(),pid:process.pid,startedAt:new Date().toISOString(),phase:'discovering',state:'running',terminal:false,installationWrites:'none',skillRegistered:false};
|
|
7
7
|
}
|
|
8
|
+
// Product outcome, never the confirmation server's exit status, controls the CLI.
|
|
9
|
+
export function operationExitCode(record) {
|
|
10
|
+
if (!record?.terminal) return 1;
|
|
11
|
+
if (record.state === 'completed') return 0;
|
|
12
|
+
if (['cancelled','cancelled-no-install'].includes(record.state)) return 2;
|
|
13
|
+
if (['expired','expired-no-install'].includes(record.state)) return 3;
|
|
14
|
+
return 1;
|
|
15
|
+
}
|
|
8
16
|
// Observations from the verified runtime, never confirmation or execution authority.
|
|
9
17
|
export function runtimeObservation(event) {
|
|
10
18
|
let confirmationUrl;
|
|
@@ -13,9 +21,11 @@ export function runtimeObservation(event) {
|
|
|
13
21
|
if(['FOUNDATION_SELECTION_BOUND','AWAITING_FOUNDATION_UI_CONFIRMATION'].includes(event.status))return {phase:'awaiting-confirmation',...(event.journeyContext?{journeyContext:event.journeyContext}:{}),sessionId:event.sessionId,planHash:event.planHash,installationWrites:'none',...(confirmationUrl?{confirmationUrl}:{})};
|
|
14
22
|
if(event.status==='FOUNDATION_OPERATION_STATE'&&['executing','consumed'].includes(event.state))return {phase:'executing',sessionId:event.sessionId,installationWrites:'possible'};
|
|
15
23
|
if(event.status==='BOOTSTRAP_OPERATION_ENDED'){
|
|
16
|
-
const state=['completed','failed','cancelled','expired'].includes(event.state)?event.state:'verification-required';
|
|
24
|
+
const state=event.state==='completed'&&event.result?.ok!==true?'verification-required':['completed','failed','cancelled','expired'].includes(event.state)?event.state:'verification-required';
|
|
17
25
|
const selectedPending=state==='completed'&&event.journeyContext?.skillChoice==='selected';
|
|
18
|
-
|
|
26
|
+
const rollback=event.failure?.details?.rollback||'unknown';
|
|
27
|
+
const failureNext=rollback==='completed'?'本次程序变更已回退;Skill 未安装。保留本次记录,在原对话查看健康检查错误,修复后再申请新计划;不要重放旧确认。':'回退情况待核实;Skill 未安装。保留本次记录,在原对话核实健康检查与恢复状态;不要自动重试。';
|
|
28
|
+
return {phase:'runtime-ended',programState:state,state:selectedPending?'awaiting-skill':state,terminal:state!=='verification-required'&&!selectedPending,sessionId:event.sessionId,installationRoot:event.installationRoot,installationWrites:state==='completed'?'runtime-reported-installed':rollback==='completed'?'rolled-back':'unknown',runtimeHealth:event.result?.stableLauncherHealth||event.result?.lifecycleResult?.stableLauncherHealth||event.result?.executableHealth||'unknown',failure:event.failure||null,rollback,journeyContext:event.journeyContext,next:selectedPending?'程序已完成,正在准备所选 Codex 接入的独立确认':state==='failed'?failureNext:'只读核验稳定 installed launcher 或同次 session 结果,不重放确认'};
|
|
19
29
|
}
|
|
20
30
|
if(['cancelled-no-install','expired-no-install','shutdown-no-install'].includes(event.state))return {phase:'directory-selection-ended',state:event.state,terminal:true,installationWrites:'none'};
|
|
21
31
|
return null;
|
package/lib/progress-page.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
2
|
import crypto from 'node:crypto';
|
|
3
|
-
import {journeyView} from './journey-view.mjs';
|
|
3
|
+
import {journeyView,lifecycleContent} from './journey-view.mjs';
|
|
4
4
|
import {singlePageDocument} from './single-page-view.mjs';
|
|
5
5
|
|
|
6
6
|
export function downloadText(download, compact = false) {
|
|
@@ -15,7 +15,7 @@ export function downloadText(download, compact = false) {
|
|
|
15
15
|
|
|
16
16
|
export function downloadStatus(record) {
|
|
17
17
|
if(record.kind==='uninstall')return '';
|
|
18
|
-
if(record.terminal)return record.state==='failed'?'下载检查失败':'本次获取已结束';
|
|
18
|
+
if(record.terminal)return record.state==='failed'&&['discovering','verifying-trust','fetching-and-verifying-catalog','fetching-and-verifying-runtime','verifying-runtime','extracting-and-validating'].includes(record.phase)?'下载检查失败':'本次获取已结束';
|
|
19
19
|
if(record.phase==='fetching-and-verifying-runtime'){
|
|
20
20
|
const d=record.download,prefix=Number.isSafeInteger(d?.attempt)&&d.attempt>1?'重试 '+d.attempt+' · ':'';
|
|
21
21
|
if(d?.downloadedBytes===0)return prefix+'等待下载';
|
|
@@ -29,7 +29,9 @@ export function acquisitionProgress(record) {
|
|
|
29
29
|
const phases={discovering:'查询正式发行','verifying-trust':'核验发行来源','fetching-and-verifying-catalog':'获取并核验发行清单','fetching-and-verifying-runtime':'正在下载运行文件','verifying-runtime':'正在检查文件摘要与发行证明','extracting-and-validating':'展开并检查文件','acquired':'获取验证完成','starting-confirmation-service':'准备目录选择与确认页','selecting-directory':'等待你选择目录','awaiting-confirmation':'等待你确认安装',executing:'正在安装','runtime-ended':'读取安装结果',finished:'安装结果已返回'};
|
|
30
30
|
const terminal=record.terminal;
|
|
31
31
|
const acquired=['acquired','starting-confirmation-service','selecting-directory','directory-selection-ended','awaiting-confirmation','executing','runtime-ended','finished'].includes(record.phase);
|
|
32
|
-
const
|
|
32
|
+
const beforeAcquisition=['checking-environment','choosing-intent'].includes(record.phase);
|
|
33
|
+
const observations={acquire:{state:acquired?'completed':beforeAcquisition?'planned':terminal?'failed':'running',evidence:record.resultFile}};
|
|
34
|
+
if(record.phase==='choosing-intent')observations.select={state:record.state==='cancelled-no-install'?'cancelled':record.state==='expired-no-install'?'expired':'pending'};
|
|
33
35
|
if(record.phase==='selecting-directory')observations.select={state:'pending'};
|
|
34
36
|
if(record.phase==='directory-selection-ended')observations.select={state:record.state==='cancelled-no-install'?'cancelled':record.state==='expired-no-install'?'expired':'verification-required',evidence:record.selectionId};
|
|
35
37
|
if(['awaiting-confirmation','executing','runtime-ended','finished'].includes(record.phase))observations.select={state:'completed',evidence:record.selectionId};
|
|
@@ -48,9 +50,10 @@ export function acquisitionProgress(record) {
|
|
|
48
50
|
const journey=journeyView({id:record.operationId,kind,includeAcquisition:kind!=='uninstall',includeSelection:kind==='install',includeHealth:kind!=='uninstall',includeSkillRemoval:Boolean(observations.remove),observations,skillChoice:record.journeyContext?.skillChoice||'undecided'});
|
|
49
51
|
if(record.terminal&&record.state==='partial'){journey.ended=false;journey.title=record.programState==='completed'?'程序已完成,Codex 接入未完成':'本次操作尚未全部完成';journey.summary=record.next;}
|
|
50
52
|
const verb=kind==='uninstall'?'卸载':kind==='update'?'更新':'安装';
|
|
53
|
+
const content=lifecycleContent(record,observations);
|
|
51
54
|
Object.assign(phases,{'awaiting-confirmation':'等待你确认'+verb,executing:'正在'+verb,'runtime-ended':'读取'+verb+'结果',finished:verb+'结果已返回'});
|
|
52
55
|
if(record.terminal&&record.state==='partial'&&record.programState==='completed'&&kind==='update')journey.title='程序已更新,Skill 尚未更新';
|
|
53
|
-
return {journey,operationId:record.operationId,title:journey.title,phase:({'skill-confirmation':'等待你确认 Codex 操作','skill-executing':'正在处理 Codex 对话能力'})[record.phase]||phases[record.phase]||'正在核实本次操作',state:record.state,version:record.currentVersion&&kind==='update'?record.currentVersion+' → '+record.version:record.version||record.currentVersion||null,download:record.download||null,downloadStatus:downloadStatus(record),program:record.programState==='completed'?'程序'+verb+'已完成;'+(kind==='uninstall'?'结果由独立回执保留':record.runtimeHealth==='passed'?'稳定入口健康已核验':'健康待核实'):record.installationWrites==='none'?'尚未执行程序操作':'程序结果以同次记录为准',skill:kind==='uninstall'?(observations.remove?.state==='completed'?'已解除已归属 Codex 注册;修改和未知文件按计划保留':'本次没有已核实的注册移除结果'):record.skillRegistered===true?'Skill 文件与归属已核验;新对话识别另验':record.journeyContext?.skillChoice==='skipped'?'本次不接入 Codex':record.journeyContext?.skillChoice==='selected'?'已选择接入 Codex;仅在分别确认后执行':'Codex 接入可选,尚未选择',project:'未自动接入项目',installationRoot:record.installationRoot||null,skillDestination:record.skillDestination||null,next:record.next||(record.terminal&&record.state==='completed'&&record.journeyContext?.skillChoice==='skipped'?'本次已跳过 Codex 接入;程序操作已完成,项目另行确认。':journey.summary),nextUrl:record.terminal?(kind!=='uninstall'&&record.workbench?.state==='ready'?record.workbench.url:null):record.confirmationUrl||null,nextLabel:record.terminal?'打开已安装工作台':record.phase?.startsWith('skill-')?'查看 Codex 精确确认':kind==='install'?'继续到目录选择 / 本人确认页':'查看'+verb+'确认',resultFile:record.resultFile||null,terminal,diagnostic:record.diagnostic||null};
|
|
56
|
+
return {content,journey,operationId:record.operationId,title:journey.title,phase:({'skill-confirmation':'等待你确认 Codex 操作','skill-executing':'正在处理 Codex 对话能力'})[record.phase]||phases[record.phase]||'正在核实本次操作',state:record.state,version:record.currentVersion&&kind==='update'?record.currentVersion+' → '+record.version:record.version||record.currentVersion||null,download:record.download||null,downloadStatus:downloadStatus(record),program:record.programState==='completed'?'程序'+verb+'已完成;'+(kind==='uninstall'?'结果由独立回执保留':record.runtimeHealth==='passed'?'稳定入口健康已核验':'健康待核实'):record.installationWrites==='none'?'尚未执行程序操作':'程序结果以同次记录为准',skill:kind==='uninstall'?(observations.remove?.state==='completed'?'已解除已归属 Codex 注册;修改和未知文件按计划保留':'本次没有已核实的注册移除结果'):record.skillRegistered===true?'Skill 文件与归属已核验;新对话识别另验':record.journeyContext?.skillChoice==='skipped'?'本次不接入 Codex':record.journeyContext?.skillChoice==='selected'?'已选择接入 Codex;仅在分别确认后执行':'Codex 接入可选,尚未选择',project:'未自动接入项目',installationRoot:record.installationRoot||null,skillDestination:record.skillDestination||null,next:record.next||(record.terminal&&record.state==='completed'&&record.journeyContext?.skillChoice==='skipped'?'本次已跳过 Codex 接入;程序操作已完成,项目另行确认。':journey.summary),nextUrl:record.terminal?(kind!=='uninstall'&&record.workbench?.state==='ready'?record.workbench.url:null):record.confirmationUrl||null,nextLabel:record.terminal?'打开已安装工作台':record.phase?.startsWith('skill-')?'查看 Codex 精确确认':kind==='install'?'继续到目录选择 / 本人确认页':'查看'+verb+'确认',resultFile:record.resultFile||null,terminal,diagnostic:record.diagnostic||null};
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
// A bounded, read-only loopback view over the existing in-memory operation.
|
package/lib/single-page-view.mjs
CHANGED
|
@@ -7,17 +7,17 @@ export function singlePageDocument(csrf) {
|
|
|
7
7
|
|
|
8
8
|
function singlePageClient(csrf) {
|
|
9
9
|
const q=s=>document.querySelector(s),esc=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
10
|
-
let last=null,busy=false,revision=null,stopped=false;
|
|
10
|
+
let last=null,busy=false,revision=null,stopped=false,formIdentity=null;
|
|
11
11
|
const verbs={install:'安装',update:'更新',uninstall:'卸载'};
|
|
12
12
|
function cleanupText(session){const c=session?.hostCleanup;if(!c)return '';return '<div class="facts"><p>更新成功并通过健康检查后,清理本次下载的归档和展开候选。</p><p>清理位置:'+esc(c.root)+'</p><p>范围:'+esc(c.files?.length??'待核实')+' 个文件;'+(Number.isFinite(c.bytes)?(c.bytes/1048576).toFixed(2)+' MiB':'大小待核实')+'</p><p>保留获取回执、验证材料、回退版本、项目和未知或已修改文件;仍在使用的材料不会强删。</p></div>';}
|
|
13
13
|
function show(p){
|
|
14
|
-
last=p;const r=p.record,v=p.view,kind=r.kind||'install',verb=verbs[kind],choice=r.phase==='choosing-intent';
|
|
14
|
+
last=p;const r=p.record,v=p.view,c=p.progress.content,kind=r.kind||'install',verb=verbs[kind],choice=r.phase==='choosing-intent'&&!r.terminal;
|
|
15
15
|
const titles={select:'选择安装位置',acquire:kind==='update'?'下载并检查新版':'下载并检查文件',program:verb+' Foundation',material:'准备安装 Skill',register:kind==='update'?'更新 Codex 中的 Skill':'将 Skill 安装到 Codex',remove:'移除 Codex 中的 Skill',health:'检查'+verb+'结果','optional-skill':'Foundation Skill'};
|
|
16
16
|
const stateLabels={failed:'本步失败',cancelled:'已取消',expired:'已过期','verification-required':'结果待核实',running:'正在执行',executing:'正在执行',consumed:'正在执行',undecided:'可选,待选择'};
|
|
17
17
|
const order=kind==='uninstall'?['remove','program']:['select','acquire','program','material','register','optional-skill','health'];
|
|
18
18
|
const steps=p.progress.journey.steps.map(s=>({...s})).sort((a,b)=>order.indexOf(a.key)-order.indexOf(b.key));
|
|
19
19
|
if(choice)for(const s of steps)s.state=s.key==='select'?'pending':'planned';
|
|
20
|
-
if(r.environment)steps.unshift({key:'environment',title:'
|
|
20
|
+
if(r.environment)steps.unshift({key:'environment',title:'检查运行环境',state:r.environment.state==='ready'?'completed':'failed',evidence:(r.environment.preparation?'对话准备的基础环境已复检;':'复用现有入口环境;')+r.environment.platform+' '+r.environment.arch+';入口 Node '+r.environment.nodeVersion+';程序使用随包运行时,不修改共享设置'+(r.environment.preparation?'。独立环境回执:'+r.environment.preparation.receipt+';缓存不自动清理':'')});
|
|
21
21
|
if(r.intentSelected){const s=steps.find(s=>s.key==='select');if(s)s.state='completed';}
|
|
22
22
|
let active=choice?'select':v?.type==='selection'?'select':v?.session?.capabilityId?(v.session.operation==='install'?'material':v.session.operation==='uninstall'?'remove':'register'):v?.session?'program':steps.find(s=>!['completed','skipped','retained'].includes(s.state))?.key;
|
|
23
23
|
// The final result check must not look finished while a chosen Skill step
|
|
@@ -27,25 +27,28 @@ function singlePageClient(csrf) {
|
|
|
27
27
|
if(r.terminal)active=null;
|
|
28
28
|
const executing=['executing','consumed'].includes(v?.session?.state)||['executing','skill-executing'].includes(r.phase);
|
|
29
29
|
const count=steps.filter(s=>['completed','skipped','retained'].includes(s.state)).length;
|
|
30
|
-
q('#flow-title').textContent=
|
|
30
|
+
q('#flow-title').textContent=c.title;
|
|
31
31
|
q('#flow-eyebrow').textContent=r.terminal?'本次结果':executing?'正在执行':busy?'正在提交请求':v||choice?'等待你的操作':'正在进行';
|
|
32
|
-
q('#flow-description').textContent=
|
|
32
|
+
q('#flow-description').textContent=c.description;
|
|
33
33
|
q('#flow-version').textContent='版本:'+(p.progress.version||'待核实');q('#flow-destination').textContent='程序位置:'+(r.installationRoot||r.destinationIntent||'等待选择');
|
|
34
34
|
q('#flow-count').textContent='已处理 '+count+' / '+steps.length+' 步';q('#flow-status').textContent=executing?'正在执行当前步骤':busy?'正在提交请求':r.terminal?(r.state==='completed'?'本次流程已完成':'查看未完成项'):('当前:'+(titles[active]||p.progress.phase));
|
|
35
35
|
const percent=steps.length?count/steps.length*100:0;q('#flow-bar').style.width=percent+'%';q('.track').setAttribute('aria-valuenow',String(Math.round(percent)));
|
|
36
36
|
const key=JSON.stringify([active,v?.session?.sessionId,v?.session?.state,v?.selectionId,r.terminal,steps.map(s=>s.state),v?.error]);
|
|
37
37
|
if(key!==revision&&!busy){
|
|
38
|
-
|
|
38
|
+
const identity=JSON.stringify([r.operationId,active,v?.session?.sessionId]);
|
|
39
|
+
const previous=q('#current-form'),draft=formIdentity===identity&&previous?{destination:previous.elements.destination?.value,skill:previous.elements.includeSkill?.checked,error:q('#step-error')?.textContent,focus:document.activeElement?.id}:null;
|
|
40
|
+
formIdentity=identity;revision=key;
|
|
39
41
|
q('#flow-steps').innerHTML=steps.map((s,i)=>{
|
|
40
42
|
const done=['completed','retained'].includes(s.state),skip=s.state==='skipped',current=s.key===active;
|
|
41
43
|
let body='';
|
|
42
|
-
if(current&&(choice||v?.type==='selection'))body='<form id="current-form"><label class="field" for="install-path"
|
|
43
|
-
else if(current&&v?.session){const f=v.feedback;body='<p>'+esc(f.detail)+'</p><div class="facts">'+f.namedTargets.map(t=>'<p>'+esc(t.label)+':'+esc(t.path||'待核实')+'</p>').join('')+'<p>保留:'+esc(f.preserves.join(';'))+'</p><p>'+esc(f.notDone)+'</p>'+cleanupText(v.session)+((v.session.deletes||[]).length?'<details open><summary>删除范围</summary>'+v.session.deletes.map(x=>'<p>'+esc(x==='foundation-installation:full'?'本安装中经所有权核验的程序、运行时和状态文件;未知或用户修改的文件保留。':typeof x==='string'?x:JSON.stringify(x))+'</p>').join('')+'</details>':'')+'</div><p class="hint"
|
|
44
|
+
if(current&&(choice||v?.type==='selection'))body='<form id="current-form"><label class="field" for="install-path">Foundation 安装位置</label><input id="install-path" name="destination" type="text" value="'+esc(r.destinationIntent||v?.suggestion||'')+'" spellcheck="false" required><p class="hint">这里只存放 Foundation 程序。以后不同项目都可以使用它,项目文件仍保存在各自的位置。请输入完整路径,可以包含成对引号;不会覆盖已有文件。</p><label class="check"><input id="include-skill" name="includeSkill" type="checkbox" '+(r.journeyContext?.skillChoice==='skipped'?'':'checked')+'><span>同时启用 Codex 对话功能(Foundation Skill)<br><span class="hint">以后可在 Codex 中说“打开 Foundation”或“用 Foundation 做这个项目”。启用后请在新对话中尝试。</span></span></label><p class="hint">获取缓存:'+esc(r.acquisitionRoot||'以当前任务已披露位置为准')+'。选择不是安装批准。</p><div id="step-error" class="error" role="alert">'+esc(v?.error||'')+'</div><div class="actions"><button class="primary" name="action" value="'+(choice?'choose':'select')+'">使用这个位置</button><button name="action" value="'+(choice?'cancel-intent':'cancel')+'" formnovalidate>取消本步</button></div></form>';
|
|
45
|
+
else if(current&&v?.session){const f=v.feedback;body='<p>'+esc(f.detail)+'</p><div class="facts">'+f.namedTargets.map(t=>'<p>'+esc(t.label)+':'+esc(t.path||'待核实')+'</p>').join('')+'<p>保留:'+esc(f.preserves.join(';'))+'</p><p>'+esc(f.notDone)+'</p>'+cleanupText(v.session)+((v.session.deletes||[]).length?'<details open><summary>删除范围</summary>'+v.session.deletes.map(x=>'<p>'+esc(x==='foundation-installation:full'?'本安装中经所有权核验的程序、运行时和状态文件;未知或用户修改的文件保留。':typeof x==='string'?x:JSON.stringify(x))+'</p>').join('')+'</details>':'')+'</div><p class="hint">这次只执行上面列出的操作,不批准后续步骤。</p><form id="current-form"><div id="step-error" class="error" role="alert"></div><div class="actions">'+(v.session.state==='pending'?'<button class="'+(kind==='uninstall'?'danger':'primary')+'" name="action" value="'+esc(v.action)+'">'+esc(({program:verb+' Foundation',material:'准备安装 Skill',register:kind==='update'?'更新对话功能':'启用对话功能',remove:'移除对话功能'})[s.key]||'确认本步')+'</button><button name="action" value="cancel-no-change">取消本步</button>':'<p>本步已开始或结束,不能重复确认。</p>')+'</div></form>';}
|
|
44
46
|
else if(current)body='<p role="status" '+(s.key==='acquire'?'data-acquisition-phase':'')+'>'+esc(p.progress.phase)+'</p><p class="hint">下一步会在本页展开,不需要查找其他网址。</p>'+(s.key==='acquire'?'<p class="error" data-download-connection role="status"></p>':'');
|
|
45
|
-
else if(
|
|
46
|
-
|
|
47
|
-
return '<li data-step="'+esc(s.key)+'" class="step '+(current?'current':done||skip?'done':'later')+'"><div class="step-top"><span class="step-number">'+(skip?'−':done?'✓':i+1)+'</span><span class="step-title">'+esc(titles[s.key]||s.title)+'</span><span class="step-state">'+esc(skip?'已跳过':done?'已完成':stateLabels[s.state]||(current?(choice?'等待选择':p.progress.phase):r.terminal?'本次未执行':'等待前序步骤'))+'</span></div>'+(body?'<div class="step-body">'+body+'</div>':done||skip?'<details class="completed-detail"><summary>查看结果</summary><p>'+esc(skip?'本次未执行此步骤。':
|
|
47
|
+
else if(['failed','cancelled','expired','verification-required'].includes(s.state))body='<p class="error">'+esc(s.key==='environment'?c.environment:c.next||p.progress.next)+'</p>';
|
|
48
|
+
|
|
49
|
+
return '<li data-step="'+esc(s.key)+'" class="step '+(current?'current':done||skip?'done':'later')+'"><div class="step-top"><span class="step-number">'+(skip?'−':done?'✓':i+1)+'</span><span class="step-title">'+esc(titles[s.key]||s.title)+'</span><span class="step-state">'+esc(skip?'已跳过':done?'已完成':stateLabels[s.state]||(current?(choice?'等待选择':p.progress.phase):r.terminal?'本次未执行':'等待前序步骤'))+'</span></div>'+(body?'<div class="step-body">'+body+'</div>':done||skip?'<details class="completed-detail"><summary>查看结果</summary><p>'+esc(skip?'本次未执行此步骤。':c.summaries[s.key]||'本步已处理;详细记录见技术详情。')+'</p></details>':'')+'</li>';
|
|
48
50
|
}).join('');
|
|
51
|
+
const form=q('#current-form');if(draft&&form){if(form.elements.destination&&draft.destination!==undefined)form.elements.destination.value=draft.destination;if(form.elements.includeSkill&&draft.skill!==undefined)form.elements.includeSkill.checked=draft.skill;if(q('#step-error'))q('#step-error').textContent=draft.error||'';if(draft.focus)document.getElementById(draft.focus)?.focus();}
|
|
49
52
|
}
|
|
50
53
|
if(busy&&executing){const live=q('.current .step-state');if(live)live.textContent='正在执行';}
|
|
51
54
|
// Byte/phase updates must not rebuild the form or depend on its revision key.
|
|
@@ -56,11 +59,11 @@ function singlePageClient(csrf) {
|
|
|
56
59
|
const connection=downloadRow.querySelector('[data-download-connection]');if(connection)connection.textContent='';
|
|
57
60
|
}
|
|
58
61
|
q('#flow-result').hidden=!r.terminal;
|
|
59
|
-
if(r.terminal)q('#flow-result').innerHTML='<h2>'+esc(
|
|
62
|
+
if(r.terminal)q('#flow-result').innerHTML='<h2>'+esc(c.title)+'</h2><p>'+esc(c.program)+'</p><p>Codex 对话功能:'+esc(c.skill)+'</p><p>项目:'+esc(c.project)+'</p><p class="hint">下一步:'+esc(c.next||p.progress.next)+'</p>';
|
|
60
63
|
const safe=JSON.parse(JSON.stringify(p));if(safe.view){delete safe.view.managerNonce;delete safe.view.nonce;}q('#raw').textContent=JSON.stringify(safe,null,2);
|
|
61
64
|
}
|
|
62
65
|
document.addEventListener('submit',async e=>{if(e.target.id!=='current-form')return;e.preventDefault();if(busy||!last)return;busy=true;const form=e.target;form.querySelectorAll('button').forEach(b=>b.disabled=true);q('#flow-status').textContent='正在提交请求';const action=e.submitter.value,v=last.view;const body={csrf,operationId:last.record.operationId,action,...(['choose','select'].includes(action)?{destination:form.elements.destination.value,skillChoice:form.elements.includeSkill.checked?'selected':'skipped'}:{managerNonce:v?.managerNonce,sessionId:v?.session?.sessionId,planHash:v?.session?.planHash})};
|
|
63
|
-
|
|
66
|
+
|
|
64
67
|
try{const response=await fetch(location.pathname+'/action',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(body)});const result=await response.json();if(!response.ok){q('#step-error').textContent=result.message||result.code||'本步未完成,请核实';busy=false;form.querySelectorAll('button').forEach(b=>b.disabled=false);return;}busy=false;revision=null;await refresh();}catch{q('#connection').textContent='请求结果待核实,不自动重试。已收到的结果保留;刷新查询同次流程。';}});
|
|
65
68
|
async function refresh(){try{const response=await fetch(location.pathname+'/state');if(!response.ok)throw Error();show(await response.json());q('#connection').textContent='';}catch{q('#connection').textContent=last?.record?.terminal?'结果服务已结束,上方保留最后核实结果。可在对话中按本次操作记录再次查询。':'连接已中断,保留最后核实结果;不自动重试操作。';const note=q('.current [data-download-connection]');if(note){note.textContent='连接中断,进度待核实;保留上次收到的数值。';q('.current .step-state').textContent=(last.progress.downloadStatus||last.progress.phase)+' · 待核实';}}}
|
|
66
69
|
async function poll(){if(stopped)return;await refresh();if(!stopped)setTimeout(poll,750);}poll();window.addEventListener('pagehide',()=>{stopped=true},{once:true});
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@josephyulei/summon-foundation",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@josephyulei/summon-foundation",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.9",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@sigstore/bundle": "4.0.0",
|
package/package.json
CHANGED