@givolt/i3-composer-agent 2.3.5 → 2.3.7
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/.agents/plugins/marketplace.json +1 -1
- package/.claude/commands/i3-prototype.md +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.gemini/commands/i3-prototype.toml +1 -1
- package/README.md +1 -1
- package/commands/i3-prototype.toml +1 -1
- package/docs/codebuddy-setup.md +1 -1
- package/docs/codex-setup.md +1 -1
- package/docs/other-hosts.md +1 -1
- package/docs/workbuddy-setup.md +1 -1
- package/hooks/PROTOTYPE.md +23 -0
- package/hooks/i3-prototype.cjs +2 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/scripts/ui-prototype-buddy-lifecycle-test.js +10 -5
- package/scripts/ui-prototype-codebuddy-hook-test.js +8 -2
- package/scripts/ui-prototype-recovery-test.js +11 -2
- package/skills/i3-ui-prototype/SKILL.md +2 -8
- package/skills/i3-ui-prototype/references/execution-evidence.md +9 -3
- package/skills/i3-ui-prototype/scripts/codebuddy-context.cjs +20 -3
- package/skills/i3-ui-prototype/scripts/codebuddy-hook.cjs +123 -36
- package/skills/i3-ui-prototype/scripts/execution-diagnostics.cjs +91 -0
- package/skills/i3-ui-prototype/scripts/execution-gates.cjs +46 -2
- package/skills/i3-ui-prototype/scripts/execution-recovery.cjs +10 -9
- package/skills/i3-ui-prototype/scripts/execution-runtime.cjs +4 -3
- package/skills/i3-ui-prototype/scripts/native-command.cjs +1 -1
- package/skills/i3-ui-prototype/scripts/native-delivery.cjs +4 -3
- package/skills/i3-ui-prototype/scripts/native-files.cjs +10 -1
- package/skills/i3-ui-prototype/scripts/native-pilot.cjs +6 -1
- package/skills/i3-ui-prototype/scripts/native-tool-result.cjs +14 -0
- package/skills/i3-ui-prototype/scripts/prototype-dependencies.cjs +1 -1
- package/skills/i3-ui-prototype/scripts/prototype-hooks.cjs +7 -3
- package/skills/i3-ui-prototype/scripts/ptc-callbacks.cjs +131 -0
|
@@ -28,8 +28,17 @@ function write(root,relative,value) {
|
|
|
28
28
|
try { fs.writeFileSync(tmp,typeof value==='string'?value:JSON.stringify(value,null,2)+'\n',{flag:'wx'}); fs.renameSync(tmp,file); }
|
|
29
29
|
finally { if(fs.existsSync(tmp)) fs.unlinkSync(tmp); }
|
|
30
30
|
}
|
|
31
|
+
function withLock(root,name,work) {
|
|
32
|
+
fs.mkdirSync(root,{recursive:true,mode:0o700});const lock=inside(root,name);let acquired=false;
|
|
33
|
+
for(let attempt=0;attempt<100;attempt++) {
|
|
34
|
+
try{fs.mkdirSync(lock);acquired=true;break;}
|
|
35
|
+
catch(error){if(error.code!=='EEXIST')throw error;Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,10);}
|
|
36
|
+
}
|
|
37
|
+
assert.ok(acquired,'Concurrent native audit update; no success recorded');
|
|
38
|
+
try{return work();}finally{fs.rmdirSync(lock);}
|
|
39
|
+
}
|
|
31
40
|
function entries(root,files) { return Object.fromEntries([...new Set(files)].sort().map(file=>[file,hash(fs.readFileSync(inside(root,file)))])); }
|
|
32
41
|
const cacheRoots = new Set(['node_modules','dist','.vite','.git','coverage',...['app','starter'].flatMap(root=>['node_modules','dist','.vite','.git','coverage'].map(name=>`${root}/${name}`))]);
|
|
33
42
|
// A source/public subdirectory named "dist", or an imported .log file, is still source.
|
|
34
43
|
const excluded = (relative,name) => cacheRoots.has(relative) || name==='.DS_Store' || ['app/tsconfig.tsbuildinfo','starter/tsconfig.tsbuildinfo'].includes(relative);
|
|
35
|
-
module.exports={fs,path,crypto,assert,hash,json,id,inside,walk,write,entries,excluded};
|
|
44
|
+
module.exports={fs,path,crypto,assert,hash,json,id,inside,walk,write,withLock,entries,excluded};
|
|
@@ -50,5 +50,10 @@ async function main(argv=process.argv.slice(2)) {
|
|
|
50
50
|
default: throw new Error(`Unknown operation: ${operation}`);
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
|
|
53
|
+
function capture(result,exitCode){
|
|
54
|
+
const args=process.argv.slice(2),at=args.indexOf('--project');let project=at>=0&&args[at+1]?path.resolve(args[at+1]):process.cwd();
|
|
55
|
+
const target=args[1];if(target&&path.isAbsolute(target)&&target.includes(path.sep+'prototypes'+path.sep))project=target.slice(0,target.indexOf(path.sep+'prototypes'+path.sep));
|
|
56
|
+
require('./ptc-callbacks.cjs').captureResult({script:__filename,argv:args,project,result,exitCode});
|
|
57
|
+
}
|
|
58
|
+
if(require.main===module) main().then(result=>{if(result!==null){const code=result.status==='failed'?1:0;capture(result,code);process.stdout.write(JSON.stringify(result,null,2)+'\n');process.exitCode=code;}}).catch(error=>{const result={status:'failed',operation:process.argv[2]||'analyze',errors:[error.message]};try{capture(result,1);}catch{/* The primary failure remains authoritative. */}process.stderr.write(JSON.stringify(result)+'\n');process.exitCode=1;});
|
|
54
59
|
module.exports={main};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// Capture a real read-only query result when the host's Bash callback omits stdout.
|
|
4
|
+
const fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict'),{spawnSync}=require('node:child_process');
|
|
5
|
+
const J=require('./ptc-callbacks.cjs');
|
|
6
|
+
function main(argv=process.argv.slice(2)){
|
|
7
|
+
const args=[...argv];assert.equal(args.shift(),'--project','Explicit query project required');const project=fs.realpathSync(args.shift());assert.equal(args.shift(),'--','Separate the executable and its arguments');
|
|
8
|
+
const executable=args.shift();assert.ok(path.isAbsolute(executable)&&fs.statSync(executable).isFile(),'Use the detected absolute local executable; this command does not install tools');
|
|
9
|
+
const run=spawnSync(executable,args,{cwd:project,encoding:'utf8',timeout:30000,maxBuffer:8*1024*1024});
|
|
10
|
+
const result={type:'execute_command_result',exitCode:run.status??1,stdout:run.stdout||'',stderr:run.stderr||'',...(run.error?{error:run.error.message}:{})};
|
|
11
|
+
J.captureResult({script:__filename,argv,project,result,exitCode:result.exitCode});return result;
|
|
12
|
+
}
|
|
13
|
+
module.exports={main};
|
|
14
|
+
if(require.main===module)try{const result=main();process.stdout.write(JSON.stringify(result)+'\n');process.exitCode=result.exitCode;}catch(error){process.stderr.write(JSON.stringify({status:'failed',errors:[error.message]})+'\n');process.exitCode=1;}
|
|
@@ -99,5 +99,5 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
99
99
|
return operation === 'check' ? check(options) : operation==='query'?query(options):require('./optional-skill-install.cjs').install(options);
|
|
100
100
|
}
|
|
101
101
|
module.exports = { check, skillAt, antdvTool, query, parse, main };
|
|
102
|
-
if (require.main === module) main().then(result => {process.stdout.write(JSON.stringify(result, null, 2) + '\n');if(result.status==='failed')process.exitCode=1;})
|
|
102
|
+
if (require.main === module) main().then(result => {const {options}=parse(process.argv.slice(2));require('./ptc-callbacks.cjs').captureResult({script:__filename,argv:process.argv.slice(2),project:path.resolve(options.project||process.cwd()),result,exitCode:result.status==='failed'?1:0});process.stdout.write(JSON.stringify(result, null, 2) + '\n');if(result.status==='failed')process.exitCode=1;})
|
|
103
103
|
.catch(error => { process.stderr.write(JSON.stringify({ status: 'failed', errors: [error.message] }) + '\n'); process.exitCode = 1; });
|
|
@@ -56,8 +56,9 @@ function check(options,runtime={}) {
|
|
|
56
56
|
const entries=runtime.entries||[];
|
|
57
57
|
if(!runtime.entries&&fs.existsSync(parent))for(const name of fs.readdirSync(parent)) {
|
|
58
58
|
if(!/^[a-f0-9]{24}$/.test(name))continue;
|
|
59
|
-
const raw=R.fileIO.read(path.join(parent,name,'session.json'));
|
|
60
|
-
entries.push(...Object.values(JSON.parse(raw).bindings||{}));
|
|
59
|
+
const raw=R.fileIO.read(path.join(parent,name,'session.json'));
|
|
60
|
+
if(raw)entries.push(...Object.values(JSON.parse(raw).bindings||{}));
|
|
61
|
+
const observed=R.fileIO.read(path.join(parent,name,'observation.json'));if(observed)entries.push(JSON.parse(observed));
|
|
61
62
|
}
|
|
62
63
|
for(const entry of entries)if((entry.host||'codebuddy')===host&&entry.project===current.project&&entry.registrationDigest===record.registrationDigest&&entry.settingsDigest===record.settingsDigest&&(!observation||entry.lastHook.at>observation.lastHook.at))observation=entry;
|
|
63
64
|
if(observation) {
|
|
@@ -68,9 +69,12 @@ function check(options,runtime={}) {
|
|
|
68
69
|
result.observation={sessionId:observation.sessionId,projectKey:observation.projectKey,runId:observation.runId,event:observation.lastHook.event,at:observation.lastHook.at};
|
|
69
70
|
const observedProduct=observation.product||(record.product!=='auto'?record.product:null);
|
|
70
71
|
assert.ok(['ide','cli'].includes(observedProduct),'Observed native product identity missing');
|
|
71
|
-
Object.assign(result,{product:observedProduct,productVersion:observation.productVersion||'unreported',effective:effectiveByProduct[observedProduct]===true,identitySource:observation.identitySource||'legacy-profile',recovery:observation.recovery||null});
|
|
72
|
+
Object.assign(result,{product:observedProduct,productVersion:observation.productVersion||'unreported',effective:effectiveByProduct[observedProduct]===true,identitySource:observation.identitySource||'legacy-profile',recovery:observation.recovery||null,...(observation.callMode?{callMode:observation.callMode,skills:observation.skills}:{})});
|
|
72
73
|
}
|
|
73
74
|
const recent=observation&&Date.now()-Date.parse(observation.lastHook.at)>=0&&Date.now()-Date.parse(observation.lastHook.at)<30*60*1000;
|
|
75
|
+
const failure=require('./execution-diagnostics.cjs').latest({base:current.base,project:current.project,host,record});
|
|
76
|
+
if(failure&&(!observation||failure.at>=observation.lastHook.at))return {...result,status:'failed',failure,errors:[failure.reason],transcript:{...result.transcript,status:'failed',invocationVerified:false},
|
|
77
|
+
next:failure.status==='blocked'?'修复宿主或适配后,由用户发起新的继续任务;只读诊断不消耗恢复次数。':'按 failure.reason 检查当前宿主协议;最多两次补调,不能将注册状态当作执行成功。'};
|
|
74
78
|
return {...result,status:result.effective&&recent?'active':record.product==='cli'?'pending-host-review':'registered',
|
|
75
79
|
activationScope:'Only the displayed project/session/configuration and adapter bytes; not all projects or business acceptance',
|
|
76
80
|
next:'CLI: review /hooks; desktop: reload. Verify an actual Skill invocation and missing-Skill recovery in the chosen host.'};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Native Pre/Post callbacks are the evidence. REPL source and claimed returns are never interpreted.
|
|
3
|
+
const fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict');
|
|
4
|
+
const F=require('./native-files.cjs'),E=require('./execution-evidence.cjs'),C=require('./codebuddy-context.cjs'),P=require('./execution-policy.cjs');
|
|
5
|
+
const {toolName}=require('./native-tool-name.cjs');
|
|
6
|
+
const canonical=x=>Array.isArray(x)?x.map(canonical):x&&typeof x==='object'?Object.fromEntries(Object.keys(x).sort().map(k=>[k,canonical(x[k])])):x;
|
|
7
|
+
const digest=x=>E.sha(JSON.stringify(canonical(x)));
|
|
8
|
+
const fileFor=(record,project,sessionId)=>path.join(C.sessionDirectory(record,project,sessionId),'ptc-callbacks.json');
|
|
9
|
+
function load(file){
|
|
10
|
+
if(!fs.existsSync(file))return null;
|
|
11
|
+
assert.ok(!fs.lstatSync(file).isSymbolicLink()&&fs.statSync(file).size<=64*1024*1024,'Invalid PTC callback journal');return JSON.parse(fs.readFileSync(file,'utf8'));
|
|
12
|
+
}
|
|
13
|
+
function locked(file,work){
|
|
14
|
+
const folder=path.dirname(file);fs.mkdirSync(folder,{recursive:true,mode:0o700});const lock=file+'.lock';let acquired=false;
|
|
15
|
+
for(let n=0;n<40;n++){try{fs.mkdirSync(lock);acquired=true;break;}catch(error){if(error.code!=='EEXIST')throw error;Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,10);}}
|
|
16
|
+
assert.ok(acquired,'Concurrent native PTC callback; retry without inventing evidence');
|
|
17
|
+
try{return work();}finally{fs.rmdirSync(lock);}
|
|
18
|
+
}
|
|
19
|
+
function validate(store,source,{record,project,sessionId}){
|
|
20
|
+
assert.equal(store.schemaVersion,1,'Unsupported PTC callback schema');assert.equal(store.sessionId,sessionId,'PTC callback session differs');assert.equal(store.project,project,'PTC callback project differs');
|
|
21
|
+
assert.equal(store.registrationDigest,record.registrationDigest,'PTC callback registration digest changed');assert.equal(store.settingsDigest,record.settingsDigest,'PTC callback settings digest changed');
|
|
22
|
+
assert.equal(store.sourcePath,source.path,'PTC native transcript path changed');
|
|
23
|
+
assert.ok(Number.isSafeInteger(store.sequence)&&store.sequence>=0&&store.sequence<900000,'Invalid native callback sequence');
|
|
24
|
+
const sequences=new Set();
|
|
25
|
+
const sequence=value=>{assert.ok(Number.isSafeInteger(value)&&value>0&&value<=store.sequence&&!sequences.has(value),'Invalid or duplicate native callback sequence');sequences.add(value);};
|
|
26
|
+
for(const [id,item] of Object.entries(store.calls)) {
|
|
27
|
+
assert.equal(item.id,id,'Native callback call ID changed');assert.equal(item.sessionId,sessionId,'Native callback call session changed');
|
|
28
|
+
assert.equal(digest(item.pre),digest({id:item.id,name:item.name,input:item.input,sessionId:item.sessionId,parentId:item.parentId,cwd:item.cwd}),'Native callback request changed');
|
|
29
|
+
sequence(item.preSequence);
|
|
30
|
+
if(item.post){sequence(item.postSequence);assert.ok(item.postSequence>item.preSequence,'Native callback Post must follow Pre');}
|
|
31
|
+
else assert.equal(item.postSequence,undefined,'Native callback result sequence without a result');
|
|
32
|
+
}
|
|
33
|
+
for(const parent of Object.values(store.parents)){
|
|
34
|
+
const call=source.calls.find(c=>c.toolCallId===parent.toolCallId);
|
|
35
|
+
assert.ok(call?.name==='REPL'&&call.sessionId===sessionId,'Native parent REPL request missing or session differs');
|
|
36
|
+
assert.equal(call.requestDigest,parent.requestDigest,'Native parent request digest changed');assert.equal(call.requestLine,parent.requestLine,'Native parent request cursor changed');
|
|
37
|
+
assert.ok(source.prompts.some(p=>p.line===parent.promptLine&&p.digest===parent.promptDigest),'Native parent task source changed');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function read(source,{record,project,sessionId}){
|
|
41
|
+
source={...source,calls:source.calls.filter(c=>c.evidenceSource!=='native-hook-callback')};
|
|
42
|
+
const store=load(fileFor(record,project,sessionId));if(!store)return source;
|
|
43
|
+
validate(store,source,{record,project,sessionId});
|
|
44
|
+
const inner=[];
|
|
45
|
+
for(const item of Object.values(store.calls)){
|
|
46
|
+
const parent=store.parents[item.parentId];assert.ok(parent,'PTC parent callback anchor missing');
|
|
47
|
+
const name=toolName(item.name),input=item.input,skillName=name==='Skill'?input?.skill||input?.name||input?.command:null;
|
|
48
|
+
assert.ok(!source.calls.some(c=>c.toolCallId===item.id),'Duplicate native inner tool call ID');
|
|
49
|
+
const call={toolCallId:item.id,name,input,skillName:skillName||null,skill:P.skillKey(skillName),sessionId,cwd:item.cwd,contextLine:parent.contextLine,promptLine:parent.promptLine,
|
|
50
|
+
requestLine:parent.requestLine+item.preSequence/1e6,requestDigest:digest(item.pre),success:false,parentToolCallId:item.parentId,evidenceSource:'native-hook-callback'};
|
|
51
|
+
if(item.post){
|
|
52
|
+
let result=item.post.response;
|
|
53
|
+
if(item.cliResult){
|
|
54
|
+
const value=item.cliResult;assert.equal(E.sha(fs.readFileSync(value.script)),value.scriptDigest,'Captured CLI script bytes changed');
|
|
55
|
+
assert.equal(value.inputDigest,digest(item.input),'Captured CLI input differs');
|
|
56
|
+
assert.equal(item.post.response?.exitCode,value.exitCode,'Native Bash exit differs from captured CLI result');
|
|
57
|
+
result={type:'execute_command_result',stdout:typeof value.result==='string'?value.result:JSON.stringify(value.result),exitCode:value.exitCode};
|
|
58
|
+
}
|
|
59
|
+
call.result=result;call.resultLine=parent.requestLine+item.postSequence/1e6;call.resultDigest=digest({post:item.post,cliResult:item.cliResult||null});
|
|
60
|
+
call.success=!item.denied&&!result?.interrupted&&!result?.sandboxDenied&&!result?.signal&&E.successful({content:result});
|
|
61
|
+
}
|
|
62
|
+
inner.push(call);
|
|
63
|
+
}
|
|
64
|
+
return {...source,calls:[...source.calls,...inner].sort((a,b)=>a.requestLine-b.requestLine),callMode:'ptc-repl',callbackJournal:{path:fileFor(record,project,sessionId),digest:digest(store),tamperProof:false}};
|
|
65
|
+
}
|
|
66
|
+
function observe({payload,record,project,source}){
|
|
67
|
+
const sessionId=payload.session_id,name=toolName(payload.tool_name),id=payload.tool_use_id||payload.call_id;
|
|
68
|
+
assert.ok(sessionId&&id,'Native PTC session and tool call ID required');
|
|
69
|
+
assert.ok(!payload.agent_id&&!payload.parent_agent_id,'PTC main task cannot inherit subagent callbacks');
|
|
70
|
+
assert.ok(source.calls.every(c=>c.sessionId===sessionId),'Native PTC callback session differs');
|
|
71
|
+
const task=source.prompts.at(-1);assert.ok(task,'Native PTC user task missing');
|
|
72
|
+
const candidates=source.calls.filter(c=>c.name==='REPL'&&!c.resultLine&&c.requestLine>task.line&&c.contextLine===source.contextLine);
|
|
73
|
+
const direct=source.calls.find(c=>c.toolCallId===id);
|
|
74
|
+
if(direct&&!direct.evidenceSource&&direct.name!=='REPL')return source;
|
|
75
|
+
if(name==='REPL'&&payload.hook_event_name==='PostToolUse'&&direct?.name==='REPL')return read(source,{record,project,sessionId});
|
|
76
|
+
assert.equal(candidates.length,1,'Native PTC callback requires a unique pending parent REPL');
|
|
77
|
+
const parent=candidates[0],file=fileFor(record,project,sessionId);
|
|
78
|
+
locked(file,()=>{
|
|
79
|
+
const store=load(file)||{schemaVersion:1,sessionId,project,sourcePath:source.path,registrationDigest:record.registrationDigest,settingsDigest:record.settingsDigest,sequence:0,parents:{},calls:{}};
|
|
80
|
+
validate(store,source,{record,project,sessionId});
|
|
81
|
+
const next=()=>{assert.ok(++store.sequence<900000,'Native PTC callback journal limit reached');return store.sequence;};
|
|
82
|
+
if(name==='REPL'){
|
|
83
|
+
assert.equal(id,parent.toolCallId,'Native outer REPL call differs');assert.equal(digest(payload.tool_input||{}),digest(parent.input),'Native outer REPL input differs');
|
|
84
|
+
if(payload.hook_event_name==='PreToolUse')store.parents[id]||={toolCallId:id,requestDigest:parent.requestDigest,requestLine:parent.requestLine,promptLine:task.line,promptDigest:task.digest,contextLine:source.contextLine};
|
|
85
|
+
}else{
|
|
86
|
+
assert.ok(store.parents[parent.toolCallId],'Native parent PreToolUse callback missing');
|
|
87
|
+
const input=payload.tool_input||{},pre={id,name,input,sessionId,parentId:parent.toolCallId,cwd:payload.cwd};
|
|
88
|
+
if(payload.hook_event_name==='PreToolUse'){
|
|
89
|
+
if(store.calls[id])assert.equal(digest(store.calls[id].pre),digest(pre),'Conflicting native PTC call input or parent changed');
|
|
90
|
+
else store.calls[id]={...pre,pre,preSequence:next()};
|
|
91
|
+
}else if(payload.hook_event_name==='PostToolUse'){
|
|
92
|
+
const item=store.calls[id];assert.ok(item,'Paired native PTC PreToolUse request missing');assert.equal(digest(item.pre),digest(pre),'Native PTC Post input differs');
|
|
93
|
+
assert.ok(Object.hasOwn(payload,'tool_response'),'Native PTC Post response missing');const post={response:payload.tool_response};
|
|
94
|
+
if(item.post)assert.equal(digest(item.post),digest(post),'Conflicting native PTC Post result changed');
|
|
95
|
+
else {item.post=post;item.postSequence=next();}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
F.write(path.dirname(file),path.basename(file),store);fs.chmodSync(file,0o600);
|
|
99
|
+
});
|
|
100
|
+
return read(source,{record,project,sessionId});
|
|
101
|
+
}
|
|
102
|
+
function captureResult({script,argv,project=process.cwd(),result,exitCode}){
|
|
103
|
+
// Only an actually running first-party entry may capture stdout. Match its exact pending native command.
|
|
104
|
+
const candidates=[path.join(project,'.codebuddy')];let folder=path.dirname(script);
|
|
105
|
+
for(let n=0;n<5;n++,folder=path.dirname(folder))candidates.push(folder);
|
|
106
|
+
const global=C.globalBase();candidates.push(global);
|
|
107
|
+
const matches=[];
|
|
108
|
+
for(const base of [...new Set(candidates)]){
|
|
109
|
+
if(!fs.existsSync(path.join(base,'state/i3-prototype/installation.json')))continue;
|
|
110
|
+
const record=C.registration(base),parent=path.join(base,'state/i3-prototype',C.projectKey(project));if(!fs.existsSync(parent))continue;
|
|
111
|
+
for(const session of fs.readdirSync(parent)){
|
|
112
|
+
if(!/^[a-f0-9]{24}$/.test(session))continue;const file=path.join(parent,session,'ptc-callbacks.json'),store=load(file);if(!store)continue;
|
|
113
|
+
if(store.registrationDigest!==record.registrationDigest||store.project!==project)continue;
|
|
114
|
+
for(const item of Object.values(store.calls)){
|
|
115
|
+
if(item.name!=='Bash'||item.post||item.cliResult)continue;
|
|
116
|
+
const tokens=require('./native-command.cjs').tokens(item.input?.command);
|
|
117
|
+
if(!tokens||tokens.length!==argv.length+2||digest(tokens.slice(2))!==digest(argv))continue;
|
|
118
|
+
const cwd=item.cwd||project,real=file=>{try{return fs.realpathSync(file);}catch{return null;}};
|
|
119
|
+
const executable=tokens[0].includes(path.sep)?path.resolve(cwd,tokens[0]):(process.env.PATH||'').split(path.delimiter).map(dir=>path.resolve(cwd,dir,tokens[0])).find(file=>{try{fs.accessSync(file,fs.constants.X_OK);return fs.statSync(file).isFile();}catch{return false;}});
|
|
120
|
+
if(executable&&real(executable)===real(process.execPath)&&real(path.resolve(cwd,tokens[1]))===real(script))matches.push({file,item});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if(!matches.length)return false;assert.equal(matches.length,1,'Captured CLI result has multiple pending native requests');
|
|
125
|
+
const {file,item}=matches[0];locked(file,()=>{
|
|
126
|
+
const store=load(file),current=store.calls[item.id];assert.ok(current&&!current.post&&!current.cliResult,'Captured CLI callback changed');
|
|
127
|
+
current.cliResult={script,scriptDigest:E.sha(fs.readFileSync(script)),argv,inputDigest:digest(current.input),result,exitCode,at:new Date().toISOString()};
|
|
128
|
+
F.write(path.dirname(file),path.basename(file),store);
|
|
129
|
+
});return true;
|
|
130
|
+
}
|
|
131
|
+
module.exports={observe,read,captureResult};
|