Repository navigation
Expand file tree
/
Copy pathpatch.cjs
More file actions
72 lines (72 loc) · 8.54 KB
/
Copy pathpatch.cjs
File metadata and controls
72 lines (72 loc) · 8.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
'use strict';
const fs=require('node:fs'),path=require('node:path'),crypto=require('node:crypto');
const {extractFunctions,checkSyntax,walk,key,property}=require('./ast.cjs');
const {modelAnchors}=require('./model-anchors.cjs');
const MARKER='/* zcode-subagent-recovery-v2 */';
function patch(source){
if(source.includes('/* zcode-subagent-recovery-v'))throw new Error('Already patched; use the original bundle');
if(source.includes('__zcRecoverAgent'))throw new Error('Helper name collision');
const {declarations,labels}=extractFunctions(source,new Set(['createDefaultSubagentPort','sendMessageToLocalAgent','resumeTerminalAgentInBackground','createRuntimeTaskSnapshot','selectActiveConversationBranch','createSessionId','resolveAgentProfileForRequest','buildAgentProviderDescription']));
const get=label=>{const ids=labels.get(label);if(ids?.length!==1||!declarations.has(ids[0]))throw new Error(`Cannot uniquely resolve ${label}`);return declarations.get(ids[0]);};
const factory=get('createDefaultSubagentPort'),sender=get('sendMessageToLocalAgent'),resume=get('resumeTerminalAgentInBackground'),snapshot=get('createRuntimeTaskSnapshot'),branch=get('selectActiveConversationBranch'),sessionId=get('createSessionId');
const changes=[],replace=(start,end,text)=>changes.push({start,end,text});
const configs=[];
walk(factory,{ObjectExpression(n){if(property(n,'runtimeTaskRegistry')&&property(n,'runExploreAgent')&&property(n,'profiles'))configs.push(n);}});
if(configs.length!==1)throw new Error('Runner options structure changed');
const config=configs[0],registry=property(config,'runtimeTaskRegistry').value;
if(registry.type!=='MemberExpression'||registry.object.type!=='ThisExpression'||key(registry.property)!=='runtimeTaskRegistry')throw new Error('Unknown runner registry owner');
replace(config.start+1,config.start+1,'__zcSessionStore:this.sessionStore,');
const params=sender.params.map(p=>p.name);if(params.length!==6||params.some(p=>!p))throw new Error('SendMessage signature changed');
const [opts,profiles,reg,abort,req,sendOpts]=params,lookups=[];
walk(sender,{VariableDeclarator(n){const c=n.init;if(c?.type==='CallExpression'&&c.callee.type==='MemberExpression'&&c.callee.object.name===reg&&key(c.callee.property)==='get'&&c.arguments.length===1&&c.arguments[0].object?.name===req&&key(c.arguments[0].property)==='to')lookups.push(n);}});
if(lookups.length!==1)throw new Error('Missing-task lookup structure changed');
const lookup=lookups[0],original=source.slice(lookup.init.start,lookup.init.end),body=sender.body;
replace(body.start+1,body.start+1,`return __zcWithResumeLock(${reg},${req}.to,async()=>{`);
replace(lookup.init.start,lookup.init.end,`(${original}||await __zcRecoverAgent(${opts},${reg},${req},${branch.id.name},${sessionId.id.name}))`);
replace(body.end-1,body.end-1,'});');
const resumeParams=resume.params.map(p=>p.name);if(resumeParams.length!==7)throw new Error('Resume signature changed');
const task=resumeParams[4],finds=[];
walk(resume,{VariableDeclarator(n){if(n.init?.type==='CallExpression'&&key(n.init.callee.property)==='find'&&n.init.callee.object.name===resumeParams[1])finds.push(n);}});
if(finds.length!==1)throw new Error('Resume profile lookup changed');
const find=finds[0],profileValue=source.slice(find.init.start,find.init.end);
replace(find.init.start,find.init.end,`((_zcProfile)=>_zcProfile&&${task}.__zcResumeModelSelection?{..._zcProfile,modelSelection:${task}.__zcResumeModelSelection}:_zcProfile)(${profileValue})`);
const snapshots=[];
walk(snapshot,{ReturnStatement(n){const a=n.argument;if(a?.type==='ObjectExpression'&&property(a,'childSessionId')&&property(a,'taskId')&&property(a,'parentSessionId'))snapshots.push(a);}});
if(snapshots.length!==1)throw new Error('Task snapshot structure changed');
const child=property(snapshots[0],'childSessionId').value;
if(child.type!=='MemberExpression'||key(child.property)!=='childSessionId')throw new Error('Lifecycle shape changed');
const lifecycle=source.slice(child.object.start,child.object.end);
replace(snapshots[0].start+1,snapshots[0].start+1,`__zcResumeModelSelection:${lifecycle}.profile.modelSelection,`);
const anchors=modelAnchors(source);
const field=(name,description)=>`${name}:${anchors.zod}.string().trim().min(1).optional().describe(${JSON.stringify(description)}),`;
replace(anchors.schema.start+1,anchors.schema.start+1,
field('model','Exact model ID for this child. Highest priority; defaults to profile/parent when omitted. Invalid selections fail instead of falling back.')+
field('provider','Provider ID for model; defaults to the active parent model provider. Requires model.')+
field('reasoning_effort','Reasoning level for the requested model. Requires model; validated by the provider. Parent effort is not automatically inherited.'));
const launch=anchors.launch;
if(launch.arguments.length!==2||launch.arguments.some(a=>a.type!=='ObjectExpression'))throw Error('Agent launch argument shape changed');
replace(launch.arguments[0].start+1,launch.arguments[0].start+1,`__zcRequestedModelSelection:__zcSelectionFromTool(${anchors.parsed},${anchors.context}),`);
replace(launch.arguments[1].start,launch.arguments[1].end,`__zcToolLaunchOptions(${source.slice(launch.arguments[1].start,launch.arguments[1].end)},${anchors.parsed})`);
const resolver=get('resolveAgentProfileForRequest'),profileReturns=[];
walk(resolver,{ReturnStatement(n){if(property(n.argument,'profile')&&property(n.argument,'request'))profileReturns.push(n.argument);}});
if(profileReturns.length!==1||resolver.params.length!==2)throw Error('Profile resolver shape changed');
const profile=property(profileReturns[0],'profile'),requestParam=resolver.params[1].name;
if(profile.value.type!=='Identifier')throw Error('Profile return shape changed');
// Replace the entire shorthand property, not just its value.
replace(profile.start,profile.end,`profile:${requestParam}.__zcRequestedModelSelection?{...${profile.value.name},modelSelection:${requestParam}.__zcRequestedModelSelection}:${profile.value.name}`);
const description=get('buildAgentProviderDescription'),descriptionArrays=[];
walk(description,{ReturnStatement(n){if(n.argument?.type==='CallExpression'&&key(n.argument.callee.property)==='join'&&n.argument.callee.object?.type==='ArrayExpression')descriptionArrays.push(n.argument.callee.object);}});
if(descriptionArrays.length!==1)throw Error('Agent description structure changed');
replace(descriptionArrays[0].start+1,descriptionArrays[0].start+1,JSON.stringify('You may select a model per Agent call using model, optional provider, and optional reasoning_effort. An explicit model takes priority over internal overrides and profile settings. Omit all three fields to retain normal configuration. Use exact configured model/provider IDs; unavailable models or unsupported effort fail. Both foreground and background launches use this selection; resumed children retain it.')+',');
let helpers=fs.readFileSync(path.join(__dirname,'recovery.cjs'),'utf8');helpers=helpers.slice(0,helpers.lastIndexOf('module.exports ='));
let modelHelpers=fs.readFileSync(path.join(__dirname,'model.cjs'),'utf8');modelHelpers=modelHelpers.slice(0,modelHelpers.lastIndexOf('module.exports='));helpers+='\n'+modelHelpers;
const insertion=source.startsWith('#!')?source.indexOf('\n')+1:0;replace(insertion,insertion,`\n${MARKER}\n${helpers}\n`);
changes.sort((a,b)=>b.start-a.start||b.end-a.end);for(let i=1;i<changes.length;i++)if(changes[i].end>changes[i-1].start)throw new Error('Overlapping edits');
let result=source;for(const edit of changes)result=result.slice(0,edit.start)+edit.text+result.slice(edit.end);
// Only diagnostics are requested here; the second JS AST is not materialized.
const symbols={factory:factory.id.name,sender:sender.id.name,resume:resume.id.name,snapshot:snapshot.id.name,branch:branch.id.name,agentHandler:anchors.handlerSymbol,agentSchema:anchors.schemaSymbol,profileResolver:resolver.id.name};
checkSyntax(result);
return {source:result,report:{parser:'oxc-parser',symbols,edits:changes.length,originalSha256:crypto.createHash('sha256').update(source).digest('hex'),patchedSha256:crypto.createHash('sha256').update(result).digest('hex')}};
}
if(require.main===module){const [input,output]=process.argv.slice(2);if(!input||!output)throw new Error('Usage: node patch.cjs ORIGINAL.cjs OUTPUT.cjs');if(path.resolve(input)===path.resolve(output))throw new Error('Input must not equal output');if(fs.existsSync(output))throw new Error('Output already exists');const result=patch(fs.readFileSync(input,'utf8'));fs.writeFileSync(output,result.source,{flag:'wx',mode:0o755});console.log(JSON.stringify(result.report,null,2));}
module.exports={patch};