@yunhozz/arc 0.2.3 → 0.2.5
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/MANIFEST.sha256 +86 -88
- package/claude/agents/arc-implementer.md +32 -42
- package/claude/agents/arc-planner.md +33 -47
- package/claude/agents/arc-qa.md +28 -44
- package/claude/agents/arc-reviewer.md +28 -41
- package/claude/agents/arc-scouter.md +26 -38
- package/claude/skill/SKILL.md +41 -59
- package/claude/skill/models.toml +11 -11
- package/claude/skill/references/arc-visual-companion.md +19 -24
- package/claude/skill/references/brainstorming.md +111 -42
- package/claude/skill/references/commit.md +38 -70
- package/claude/skill/references/coordination.md +77 -126
- package/claude/skill/references/git-recovery.md +17 -35
- package/claude/skill/references/handoff-prep.md +91 -103
- package/claude/skill/references/handoff.md +110 -208
- package/claude/skill/references/limits.md +19 -31
- package/claude/skill/references/planning.md +93 -244
- package/claude/skill/references/superpowers-provenance.json +3 -3
- package/claude/skill/references/verification.md +105 -261
- package/claude/skill/runtime/adapters/claude.mjs +10 -9
- package/claude/skill/runtime/adapters/codex.mjs +7 -8
- package/claude/skill/runtime/bootstrap.mjs +3 -2
- package/claude/skill/runtime/capabilities.mjs +12 -0
- package/claude/skill/runtime/cooperative-schema.mjs +3 -2
- package/claude/skill/runtime/cooperative.mjs +52 -4
- package/claude/skill/runtime/evaluate.mjs +115 -2
- package/claude/skill/runtime/identity.mjs +22 -0
- package/claude/skill/runtime/role-policy.json +18 -18
- package/claude/skill/runtime/schema.mjs +19 -0
- package/claude/skill/scripts/model_config.py +20 -84
- package/codex/agents/arc-implementer.toml +34 -30
- package/codex/agents/arc-planner.toml +27 -23
- package/codex/agents/arc-qa.toml +24 -15
- package/codex/agents/arc-reviewer.toml +25 -16
- package/codex/agents/arc-scouter.toml +20 -26
- package/codex/skill/SKILL.md +42 -57
- package/codex/skill/models.toml +13 -13
- package/codex/skill/references/arc-visual-companion.md +21 -27
- package/codex/skill/references/brainstorming.md +111 -42
- package/codex/skill/references/commit.md +35 -81
- package/codex/skill/references/coordination.md +76 -104
- package/codex/skill/references/git-recovery.md +29 -51
- package/codex/skill/references/handoff-prep.md +76 -134
- package/codex/skill/references/handoff-worktree.md +32 -66
- package/codex/skill/references/handoff.md +111 -271
- package/codex/skill/references/limits.md +21 -34
- package/codex/skill/references/planning.md +125 -299
- package/codex/skill/references/superpowers-provenance.json +3 -3
- package/codex/skill/references/verification.md +90 -272
- package/codex/skill/runtime/adapters/claude.mjs +10 -9
- package/codex/skill/runtime/adapters/codex.mjs +7 -8
- package/codex/skill/runtime/bootstrap.mjs +3 -2
- package/codex/skill/runtime/capabilities.mjs +12 -0
- package/codex/skill/runtime/cooperative-schema.mjs +3 -2
- package/codex/skill/runtime/cooperative.mjs +52 -4
- package/codex/skill/runtime/evaluate.mjs +115 -2
- package/codex/skill/runtime/identity.mjs +22 -0
- package/codex/skill/runtime/role-policy.json +18 -18
- package/codex/skill/runtime/schema.mjs +19 -0
- package/codex/skill/scripts/model_config.py +25 -6
- package/lib/installer/global-config.mjs +2 -1
- package/lib/installer/manifest.mjs +1 -1
- package/lib/installer/plan.mjs +6 -8
- package/lib/installer/preset.mjs +7 -4
- package/lib/installer/uninstall.mjs +2 -2
- package/lib/management/operations.mjs +4 -2
- package/lib/management/update.mjs +3 -3
- package/lib/models/catalog-anthropic.mjs +46 -0
- package/lib/models/catalog.mjs +62 -0
- package/lib/models/codex-catalog.mjs +103 -0
- package/lib/models/config.mjs +19 -11
- package/lib/models/index.mjs +1 -1
- package/lib/models/profiles.mjs +1 -40
- package/lib/policy/render.mjs +1 -5
- package/lib/policy/role-policy.json +18 -18
- package/lib/runtime/adapters/claude.mjs +10 -9
- package/lib/runtime/adapters/codex.mjs +7 -8
- package/lib/runtime/bootstrap.mjs +3 -2
- package/lib/runtime/capabilities.mjs +12 -0
- package/lib/runtime/cooperative-schema.mjs +3 -2
- package/lib/runtime/cooperative.mjs +52 -4
- package/lib/runtime/evaluate.mjs +115 -2
- package/lib/runtime/identity.mjs +22 -0
- package/lib/runtime/schema.mjs +19 -0
- package/lib/tui/app.mjs +26 -16
- package/lib/tui/custom-preset.mjs +122 -0
- package/lib/tui/render.mjs +3 -2
- package/lib/tui/setup.mjs +65 -12
- package/package.json +1 -2
- package/claude/skill/hooks/git-policy.json +0 -208
- package/claude/skill/hooks/role-git-guard.mjs +0 -18
- package/claude/skill/hooks/runtime-gate.mjs +0 -12
- package/codex/skill/hooks/runtime-gate.mjs +0 -10
- package/lib/policy/claude-hook.mjs +0 -80
- package/scripts/probe-runtime-hooks.mjs +0 -55
package/lib/runtime/evaluate.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import {prepareCooperativeAssignment} from './assignment.mjs';
|
|
|
15
15
|
*/
|
|
16
16
|
import path from 'node:path';
|
|
17
17
|
import {validateCooperativeState,validateCooperativeCommand} from './cooperative-schema.mjs';
|
|
18
|
-
import {validateWorkflowState, validateRequest, validateEvidence} from './schema.mjs';
|
|
18
|
+
import {validateWorkflowState, validateRequest, validateEvidence,validatePlannerStage} from './schema.mjs';
|
|
19
19
|
import {ROLES, ROLE_OPERATIONS, GIT_OPERATIONS, EXTERNAL_OPERATIONS} from './operations.mjs';
|
|
20
20
|
const same=(a,b)=>JSON.stringify(a)===JSON.stringify(b);
|
|
21
21
|
const within=(base,file)=>{const relative=path.relative(base,file);return relative===''||(!relative.startsWith('..'+path.sep)&&relative!=='..'&&!path.isAbsolute(relative));};
|
|
@@ -82,7 +82,17 @@ export function evaluate(policy, state, request, evidence){
|
|
|
82
82
|
if(d.checks&&(!Array.isArray(approval.scope.checks)||d.checks.some(c=>!approval.scope.checks.some(approved=>same(approved,c)))))return result('hold','runtime.assignment','delegated checks not approved',['delegated-checks']);
|
|
83
83
|
if(d.temporary&&!same(d.temporary,approval.scope.temporary))return result('hold','runtime.assignment','delegated temporary scope not approved',['delegated-temporary-scope']);
|
|
84
84
|
if(state.assignments.some(a=>a.assignmentId===request.requestId||a.correlationRef===d.correlationRef))return result('hold','runtime.identity','assignment correlation already registered',['new-assignment-correlation']);
|
|
85
|
-
const next=structuredClone(state);
|
|
85
|
+
const next=structuredClone(state);
|
|
86
|
+
if(d.role==='planner'){
|
|
87
|
+
const stage=state.plannerLedger?.stages.find(s=>s.id===d.plannerCall?.stageId),proof=fact('planner-call');
|
|
88
|
+
const contract=stage&&{document:stage.document,inputVersion:stage.inputVersion,criteriaVersion:stage.criteriaVersion,scopeVersion:stage.scopeVersion};
|
|
89
|
+
if(!stage||stage.workflowId!==state.workflowId||stage.approvalRef!==approval.id||!same(approval.scope.plannerStage,contract)||!d.paths.includes(stage.document.path)||proof?.sourceRef!==correlation.sourceRef||!same({stageId:proof?.data.stageId,inputVersion:proof?.data.inputVersion,criteriaVersion:proof?.data.criteriaVersion,scopeVersion:proof?.data.scopeVersion},{stageId:stage.id,inputVersion:stage.inputVersion,criteriaVersion:stage.criteriaVersion,scopeVersion:stage.scopeVersion}))return result('hold','runtime.planner-call','Current approved Planner stage and provider evidence required',['planner-stage-ledger','planner-call-evidence']);
|
|
90
|
+
if(stage.document.kind==='plan'&&!state.documents.some(doc=>doc.kind==='spec'&&state.approvals.some(a=>a.id===doc.approvalRef&&a.contentHash===doc.contentHash&&evidence.provenance.some(p=>p.id===a.id&&p.messageRef===a.messageRef&&p.contentHash===doc.contentHash&&p.verification.sourceRef===a.messageRef))))return result('hold','runtime.planner-call','Approved spec prerequisite required',['approved-spec']);
|
|
91
|
+
const planner=evaluatePlannerCall({stage,request:{...d.plannerCall,requestId:request.requestId},evidence:{artifacts:proof.data.artifacts,semanticProvenance:'operator-attested'}});
|
|
92
|
+
if(planner.decision!=='allow')return planner;
|
|
93
|
+
next.plannerLedger.stages[next.plannerLedger.stages.findIndex(s=>s.id===stage.id)]=planner.proposedTransition;
|
|
94
|
+
}
|
|
95
|
+
next.assignments.push({assignmentId:request.requestId,requestId:request.requestId,requestedBy:request.actorRef,status:'pending',actorRef:null,platform:request.platform,correlationRef:d.correlationRef,role:d.role,taskId:d.taskId,paths:[...d.paths],operations:[...d.operations],approvalRef:d.approvalRef,...(d.role==='planner'?{plannerStageId:d.plannerCall.stageId}:{}),...(d.checks?{checks:structuredClone(d.checks)}:{}),...(d.temporary?{temporary:structuredClone(d.temporary)}:{})});
|
|
86
96
|
try{validateWorkflowState(next);}catch{return result('hold','runtime.assignment','invalid pending assignment scope',['valid-assignment']);}
|
|
87
97
|
return {...result('allow','runtime.assignment','pending assignment authorized; observed spawn result still required'),proposedTransition:next};
|
|
88
98
|
}
|
|
@@ -119,6 +129,7 @@ export function evaluateCooperative({policy,state,request,evidence}={}){
|
|
|
119
129
|
if(!same(evidence.versions,state.versions))return hold('cooperative.versions','Policy or runtime changed');
|
|
120
130
|
if(request.input&&request.input.expectedRevision!==state.revision)return hold('cooperative.revision','Stale revision');
|
|
121
131
|
if(request.command==='record'&&request.input.evidence.kind==='external-approval'&&request.input.actor.role!=='orchestrator')return hold('cooperative.external-approval','Orchestrator records explicit external approval');
|
|
132
|
+
if(request.command==='record'&&request.input.evidence.kind.startsWith('planner-')||request.command==='prepare'&&request.input.operation==='planner-call')return evaluateCooperativePlanner({state,request,evidence});
|
|
122
133
|
if(request.command==='prepare'){
|
|
123
134
|
if(unresolvedExternal(state))return hold('cooperative.external-unresolved','Investigate unresolved external effects before follow-up');
|
|
124
135
|
if(EXTERNAL_ACTIONS.includes(request.input.operation))return evaluateCooperativeExternal({state,request:request.input,approvalEvidence:state.records.findLast(r=>r.kind==='external-approval'&&r.messageRef===request.input.arguments?.approvalRef)});
|
|
@@ -137,3 +148,105 @@ export function reconcileCooperativeResult({state,intent,result,observations}={}
|
|
|
137
148
|
try{validateCooperativeState(state);}catch(error){return {...base,decision:'hold',ruleIds:['cooperative.schema'],reason:error.message};}
|
|
138
149
|
return {...base,decision:'hold',ruleIds:[!intent?'cooperative.intent':result?.status==='unknown'?'cooperative.unknown':'cooperative.reconciliation'],requiredEvidence:[],nextActions:[],proposedTransition:null,reason:!intent?'No matching durable intent':'Recorded operator statement requires reconciliation; no execution authorized'};
|
|
139
150
|
}
|
|
151
|
+
|
|
152
|
+
/** Pure Planner decision. Artifact verification belongs to the trusted reader;
|
|
153
|
+
* semantic judgments retain their recorded source and never become native proof. */
|
|
154
|
+
export function evaluatePlannerCall({stage,request,evidence}={}){
|
|
155
|
+
const base={expectedRevision:stage?.revision??0,proposedTransition:null,coverage:{enforcement:'decision-only',semanticProvenance:evidence?.semanticProvenance==='operator-attested'?'operator-attested':'unverified',nativeVerified:false}};
|
|
156
|
+
const result=(decision,reason,requiredEvidence=[])=>({...base,decision,ruleIds:['runtime.planner-call'],reason,requiredEvidence});
|
|
157
|
+
if(['format-only','re-expression-only','save-error-only','patch-error-only'].includes(request?.intent))return result('deny','Orchestrator handles non-progress delivery and formatting work');
|
|
158
|
+
try{validatePlannerStage(stage);}catch(error){return result('hold',error.message,['planner-stage-ledger']);}
|
|
159
|
+
if(!request||!['initial','correction','further'].includes(request.kind)||request.intent!=='substantive'||typeof request.requestId!=='string'||!request.requestId||request.stageId!==stage.id||!Array.isArray(request.criterionIds)||new Set(request.criterionIds).size!==request.criterionIds.length||!Array.isArray(request.evidenceRefs)||new Set(request.evidenceRefs).size!==request.evidenceRefs.length)return result('hold','Valid stage-bound Planner request required',['planner-request']);
|
|
160
|
+
if(stage.pending)return result('hold','Reconcile the pending Planner call before another request',['confirmed-planner-result']);
|
|
161
|
+
if(stage.calls.some(c=>c.requestId===request.requestId))return result('hold','Planner request already recorded',['new-request-id']);
|
|
162
|
+
const initial=stage.calls.find(c=>c.kind==='initial'),correction=stage.calls.find(c=>c.kind==='correction');
|
|
163
|
+
if(request.kind==='initial'&&stage.calls.length)return result('deny','Initial draft opportunity already used');
|
|
164
|
+
if(request.kind!=='initial'){
|
|
165
|
+
if(!initial||!['succeeded','failed'].includes(initial.status)||!stage.result||!['succeeded','failed'].includes(stage.result.status)||stage.result.requestId!==stage.calls.at(-1).requestId||!['succeeded','failed'].includes(stage.calls.at(-1).status))return result('hold','A confirmed prior result is required',['confirmed-planner-result']);
|
|
166
|
+
if(!stage.review?.full||stage.review.resultRef!==stage.result.ref||stage.review.criteriaVersion!==stage.criteriaVersion||request.reviewRef!==stage.review.ref||stage.review.source!=='operator-attested')return result('hold','A complete review of the recorded result is required',['consolidated-review']);
|
|
167
|
+
if(request.kind==='correction'&&correction)return result('deny','Consolidated correction opportunity already used');
|
|
168
|
+
if(request.kind==='further'&&!correction)return result('hold','Use the consolidated correction before further calls',['consolidated-correction']);
|
|
169
|
+
}
|
|
170
|
+
const used=new Set(stage.calls.flatMap(c=>c.evidenceHashes)),artifacts=request.evidenceRefs.map(ref=>evidence?.artifacts?.find(a=>a.ref===ref));
|
|
171
|
+
if(request.kind==='further'){
|
|
172
|
+
if(!request.criterionIds.length||request.criterionIds.some(id=>!stage.criteria.some(c=>c.id===id&&c.status==='open')))return result('deny','Concrete open criteria are required');
|
|
173
|
+
if(!request.evidenceRefs.length)return result('hold','Unresolved criteria alone are not new progress',['new-criterion-evidence']);
|
|
174
|
+
}
|
|
175
|
+
if(artifacts.some(a=>a&&a.stageId!==stage.id))return result('deny','Evidence belongs to another stage');
|
|
176
|
+
if(artifacts.some(a=>!a||!['input','progress','finding'].includes(a.purpose)||a.verification?.status!=='verified'||a.verification.source!=='local-bytes'||!/^sha256:[a-f0-9]{64}$/.test(a.hash??'')))return result('hold','Accessible locally verified input or progress bytes are required',['verified-artifact-bytes']);
|
|
177
|
+
if(request.kind==='further'){
|
|
178
|
+
if(artifacts.some(a=>used.has(a.hash)))return result('deny','Identical evidence bytes cannot authorize another call');
|
|
179
|
+
if(request.criterionIds.some(id=>!artifacts.some(a=>a.criterionIds?.includes(id))))return result('hold','Evidence must address every requested criterion',['criterion-evidence-link']);
|
|
180
|
+
if(artifacts.some(a=>!a.semantic||a.semantic.meaningful!==true||a.semantic.source!=='operator-attested'||!a.semantic.judgmentRef||a.semantic.targetHash!==a.hash))return result('hold','A hash-bound semantic judgment is required',['semantic-attestation']);
|
|
181
|
+
}
|
|
182
|
+
const next=structuredClone(stage),call={requestId:request.requestId,kind:request.kind,intent:request.intent,criterionIds:[...request.criterionIds],evidenceHashes:artifacts.map(a=>a.hash),evidenceRefs:[...request.evidenceRefs],reviewRef:request.reviewRef??null,status:'reserved',ordinal:stage.calls.length+1};
|
|
183
|
+
next.calls.push(call);next.pending=request.requestId;next.ready=false;
|
|
184
|
+
return {...result('allow','Planner call reserved; dispatch and result remain unverified'),proposedTransition:next};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Cooperative Planner records are attestations with separately observed bytes. */
|
|
188
|
+
export function evaluateCooperativePlanner({state,request,evidence}={}){
|
|
189
|
+
const base={expectedRevision:state.revision,coverage:{enforcement:'cooperative',provenance:'operator-attested',nativeVerified:false},proposedTransition:null,requiredEvidence:[]};
|
|
190
|
+
const result=(decision,reason)=>({...base,decision,ruleIds:['cooperative.planner'],reason});
|
|
191
|
+
if(request.input.actor.role!=='orchestrator')return result('hold','Orchestrator records and prepares Planner work');
|
|
192
|
+
if(!state.plannerLedger)return result('hold','Existing workflow Planner history has no ledger; reconciliation required');
|
|
193
|
+
const ledger=structuredClone(state.plannerLedger),input=request.input,data=input.evidence?.data;
|
|
194
|
+
const stage=ledger.stages.find(s=>s.id===(data?.stageId??input.arguments?.stageId));
|
|
195
|
+
const observed=evidence.plannerArtifacts??[];
|
|
196
|
+
const artifact=ref=>observed.find(a=>a.ref===ref&&a.stageId===stage?.id&&a.verification?.status==='verified');
|
|
197
|
+
const transition=(s,reason,decision='allow',uncertainty=false)=>({...result(decision,reason),proposedTransition:{plannerLedger:ledger,...(s?{stageId:s.id}:{}),...(uncertainty?{recordUncertainty:true}:{})}});
|
|
198
|
+
if(request.command==='prepare'){
|
|
199
|
+
if(!stage)return result('hold','Planner stage ledger unavailable');
|
|
200
|
+
if(stage.workflowId!==state.workflowId||stage.scopeVersion!==state.scopeVersion||stage.targetVersion!==state.targetVersion)return result('hold','Planner stage contract version changed');
|
|
201
|
+
const d=evaluatePlannerCall({stage,request:{...input.arguments,requestId:input.requestId},evidence:{artifacts:observed,semanticProvenance:'operator-attested'}});
|
|
202
|
+
if(d.decision!=='allow')return {...d,coverage:base.coverage,expectedRevision:state.revision};
|
|
203
|
+
ledger.stages[ledger.stages.indexOf(stage)]=d.proposedTransition;
|
|
204
|
+
return {...transition(stage,d.reason),proposedTransition:{plannerLedger:ledger,intent:{requestId:input.requestId,operation:'planner-call',stageId:stage.id,taskId:input.taskId??null,status:'prepared',fingerprint:evidence.requestFingerprint,source:'operator-attested',identityVerified:false,target:structuredClone(state.target),kind:input.arguments.kind}}};
|
|
205
|
+
}
|
|
206
|
+
const kind=input.evidence.kind,ref=input.evidence.messageRef;
|
|
207
|
+
if(kind==='planner-stage'){
|
|
208
|
+
if(ledger.stages.some(s=>s.id===data.id))return result('deny','Planner stage ID already exists');
|
|
209
|
+
const contract={document:data.document,inputVersion:data.inputVersion,criteriaVersion:data.criteriaVersion,scopeVersion:data.scopeVersion};
|
|
210
|
+
const approval=state.records.find(r=>r.kind==='approval'&&r.messageRef===data.approvalRef&&r.source==='operator-attested'&&r.scopeVersion===state.scopeVersion&&r.targetVersion===state.targetVersion);
|
|
211
|
+
const approved=approval?.data?.plannerStage;
|
|
212
|
+
if(!approved||!same(contract.document,approved.document)||['inputVersion','criteriaVersion','scopeVersion'].some(k=>contract[k]!==approved[k])||contract.scopeVersion!==state.scopeVersion||!within(state.target.realpath,data.document?.path))return result('hold','Approved stage contract and current scope are required');
|
|
213
|
+
if(data.document.kind==='plan'&&!approval.data.prerequisiteRefs?.some(ref=>state.records.some(r=>r.kind==='approval'&&r.messageRef===ref&&r.data.kind==='spec'&&r.source==='operator-attested')))return result('hold','Approved spec prerequisite is required for plan drafting');
|
|
214
|
+
const previous=[...ledger.stages,...(evidence.plannerHistory??[]).map(h=>h.stage)].filter(s=>s.document.path===data.document.path&&s.document.kind===data.document.kind&&s.document.phase===data.document.phase);
|
|
215
|
+
if(evidence.plannerHistoryError)return result('hold','Planner history could not be verified: '+evidence.plannerHistoryError);
|
|
216
|
+
if(previous.length){
|
|
217
|
+
const prior=previous.find(s=>data.previousStageRef?.workflowId===s.workflowId&&data.previousStageRef?.stageId===s.id);
|
|
218
|
+
if(!prior||approved.newScope!==true||!same(approved.previousStageRef,data.previousStageRef)||data.inputVersion<=prior.inputVersion||data.approvalRef===prior.approvalRef)return result('hold','Existing stage history requires an approved scope transition and predecessor link');
|
|
219
|
+
}
|
|
220
|
+
const created={...structuredClone(data),workflowId:state.workflowId,targetVersion:state.targetVersion,targetIdentity:{realpath:state.target.realpath,gitCommonDir:state.target.gitCommonDir,gitDir:state.target.gitDir},calls:[],pending:null,result:null,review:null,artifacts:[],ready:false};
|
|
221
|
+
try{validatePlannerStage(created);}catch(error){return result('hold',error.message);}
|
|
222
|
+
ledger.stages.push(created);return transition(created,'Planner stage registered without resetting prior history');
|
|
223
|
+
}
|
|
224
|
+
if(!stage)return result('hold','Planner stage ledger unavailable');
|
|
225
|
+
if(stage.scopeVersion!==state.scopeVersion||stage.targetVersion!==state.targetVersion)return result('hold','Planner stage contract version changed');
|
|
226
|
+
if(kind==='planner-evidence'){
|
|
227
|
+
if(stage.artifacts.some(a=>a.ref===ref))return result('deny','Planner evidence reference already recorded');
|
|
228
|
+
const item=observed.find(a=>a.ref===ref&&a.stageId===stage.id);
|
|
229
|
+
if(!item||item.verification?.status!=='verified'||!['draft','self-review','input','progress','finding'].includes(data.purpose)||!Array.isArray(data.criterionIds)||data.criterionIds.some(id=>!stage.criteria.some(c=>c.id===id)))return result('hold','Accessible stage-bound artifact bytes and purpose required');
|
|
230
|
+
if(data.semantic&&(data.semantic.source!==undefined&&data.semantic.source!=='operator-attested'||typeof data.semantic.meaningful!=='boolean'||!data.semantic.judgmentRef||data.semantic.targetHash!==item.hash))return result('hold','Semantic attestation must bind the observed artifact hash');
|
|
231
|
+
stage.artifacts.push({...item,purpose:data.purpose,criterionIds:[...data.criterionIds],...(data.semantic?{semantic:{...data.semantic,source:'operator-attested'}}:{})});return transition(stage,'Artifact bytes recorded separately from semantic attestation');
|
|
232
|
+
}
|
|
233
|
+
if(kind==='planner-result'){
|
|
234
|
+
const call=stage.calls.find(c=>c.requestId===data.callRequestId);
|
|
235
|
+
if(!call||stage.pending!==call.requestId||!['reserved','unknown'].includes(call.status)||!['succeeded','failed','unknown'].includes(data.status))return result('hold','A pending Planner call and confirmed result identity are required');
|
|
236
|
+
if(data.status==='succeeded'&&(!artifact(data.artifactRef)||artifact(data.artifactRef).purpose!=='draft'||!artifact(data.selfReviewRef)||artifact(data.selfReviewRef).purpose!=='self-review'))return result('hold','Verified full draft and self-review artifacts are required');
|
|
237
|
+
call.status=data.status;call.resultRef=ref;stage.result={ref,requestId:call.requestId,status:data.status,artifactRef:data.artifactRef??null,selfReviewRef:data.selfReviewRef??null,source:'operator-attested'};stage.ready=false;
|
|
238
|
+
if(data.status!=='unknown')stage.pending=null;
|
|
239
|
+
return transition(stage,data.status==='unknown'?'Planner execution remains unknown':'Planner result statement recorded',data.status==='unknown'?'hold':'allow',data.status==='unknown');
|
|
240
|
+
}
|
|
241
|
+
if(kind==='planner-review'){
|
|
242
|
+
if(stage.pending||!stage.result||!['succeeded','failed'].includes(stage.result.status)||data.resultRef!==stage.result.ref||data.full!==true||typeof data.complete!=='boolean'||!Array.isArray(data.criteria)||data.criteria.length!==stage.criteria.length||new Set(data.criteria.map(c=>c.id)).size!==stage.criteria.length||data.criteria.some(c=>!stage.criteria.some(old=>old.id===c.id)||!['open','resolved'].includes(c.status)||!c.judgmentRef))return result('hold','Full review must bind the confirmed result and all criteria');
|
|
243
|
+
for(const old of stage.criteria)if(old.status==='resolved'&&data.criteria.find(c=>c.id===old.id).status==='open'){
|
|
244
|
+
const reassessment=data.reassessments?.find(r=>r.criterionId===old.id&&r.previousJudgmentRef===old.judgmentRef&&r.reasonRef),proof=artifact(reassessment?.newEvidenceRef);
|
|
245
|
+
if(!reassessment||!proof||!proof.criterionIds.includes(old.id)||!proof.semantic?.meaningful||stage.calls.some(c=>c.evidenceHashes.includes(proof.hash)))return result('hold','Resolved criteria require a new evidence-bound reassessment');
|
|
246
|
+
}
|
|
247
|
+
if(data.complete&&(data.criteria.some(c=>c.status!=='resolved')||!artifact(stage.result.artifactRef)||!artifact(stage.result.selfReviewRef)))return result('hold','Incomplete draft cannot become ready or accepted');
|
|
248
|
+
stage.criteria=structuredClone(data.criteria);stage.review={ref,resultRef:data.resultRef,criteriaVersion:stage.criteriaVersion,full:true,complete:data.complete,source:'operator-attested'};stage.ready=data.complete;
|
|
249
|
+
return transition(stage,'Semantic completeness and criterion judgments remain operator-attested');
|
|
250
|
+
}
|
|
251
|
+
return result('hold','Unsupported Planner record');
|
|
252
|
+
}
|
package/lib/runtime/identity.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import {validateWorkflowState, text} from './schema.mjs';
|
|
2
2
|
import {ROLES} from './operations.mjs';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {fingerprintRequest,saveDurableState} from './store.mjs';
|
|
3
6
|
const same=(a,b)=>JSON.stringify(a)===JSON.stringify(b);
|
|
4
7
|
/** Observation is supplied by the trusted platform provider, never agent/tool arguments. */
|
|
5
8
|
export function registerAgentObservation(state, observation){
|
|
@@ -25,3 +28,22 @@ export function registerAgentObservation(state, observation){
|
|
|
25
28
|
validateWorkflowState(next);
|
|
26
29
|
return {decision:'allow',reason:'matching observed spawn bound to pending assignment',state:next,proposedTransition:next};
|
|
27
30
|
}
|
|
31
|
+
|
|
32
|
+
/** A raw start is audit only; verified correlation may bind an existing assignment. */
|
|
33
|
+
export async function recordPlannerAgentObservation({statePath,expectedRevision,input,platform,sessionRef,observation}={}){
|
|
34
|
+
if(!path.isAbsolute(statePath??'')||input?.hook_event_name!=='SubagentStart'||input.session_id!==sessionRef||!text(sessionRef)||!text(input.agent_id)||!['codex','claude'].includes(platform))throw Error('Trusted observation target and complete start required');
|
|
35
|
+
const parent=await fs.realpath(path.dirname(statePath));statePath=path.join(parent,path.basename(statePath));let lock;
|
|
36
|
+
try{
|
|
37
|
+
lock=await fs.open(statePath+'.lock','wx',0o600);const stat=await fs.lstat(statePath);if(!stat.isFile()||stat.isSymbolicLink())throw Error('Observation state must be regular file');
|
|
38
|
+
const state=validateWorkflowState(JSON.parse(await fs.readFile(statePath,'utf8'))),eventHash=fingerprintRequest({input,observation:observation??null});
|
|
39
|
+
if(state.plannerObservations?.some(o=>o.eventHash===eventHash))return {decision:'hold',reason:'Observation replay',proposedTransition:null,replayed:true};
|
|
40
|
+
const candidates=state.assignments.filter(a=>a.status==='pending'&&a.role==='planner');
|
|
41
|
+
let decision={decision:'hold',reason:'Raw event lacks verified assignment correlation',proposedTransition:null};
|
|
42
|
+
if(state.revision!==expectedRevision)decision.reason='Stale provider context cannot bind identity';
|
|
43
|
+
else if(observation?.actorRef===input.agent_id&&observation.platform===platform)decision=registerAgentObservation(state,observation);
|
|
44
|
+
const correlated=decision.decision==='allow'&&Boolean(decision.proposedTransition),next=correlated?decision.proposedTransition:structuredClone(state);
|
|
45
|
+
const entry={eventHash,event:'SubagentStart',source:correlated?'verified-provider':'hook-stdin',sessionRef,agentRef:input.agent_id,classification:correlated?'correlated':candidates.length?'uncorrelated':'unmatched',identityVerified:correlated,reason:decision.reason,stageCandidates:candidates.map(a=>({assignmentId:a.assignmentId,stageId:a.plannerStageId??null,requestId:a.requestId})),coverage:{enforcement:'observation-only',nativeVerified:false,prevention:false},deduplication:'content-hash-only'};
|
|
46
|
+
next.plannerObservations=[...(state.plannerObservations??[]),entry];next.revision=state.revision+1;next.audit.push({event:'SubagentStart',eventHash,revision:next.revision,classification:entry.classification,source:entry.source});validateWorkflowState(next);await saveDurableState({statePath,state:next});
|
|
47
|
+
return {...decision,proposedTransition:correlated?next:null,observation:entry,persisted:true};
|
|
48
|
+
}finally{if(lock){await lock.close();await fs.unlink(statePath+'.lock');}}
|
|
49
|
+
}
|
package/lib/runtime/schema.mjs
CHANGED
|
@@ -59,6 +59,7 @@ export function validateWorkflowState(s){
|
|
|
59
59
|
if(s.acceptances.some(a=>!object(a)||!text(a.taskId)||!text(a.actorRef)||!text(a.fingerprint)||!strings(a.criteria)||!strings(a.checkIds)||!strings(a.findingIds)))fail('acceptances');
|
|
60
60
|
if(s.commits.some(c=>!object(c)||!text(c.purpose)||!text(c.taskId)||!text(c.expectedParent)||!text(c.expectedTree)))fail('commits');
|
|
61
61
|
if(s.requests.some(r=>!object(r)||!text(r.requestId)||!hash(r.requestFingerprint)||!object(r.decision))||new Set(s.requests.map(r=>r.requestId)).size!==s.requests.length)fail('request replay');
|
|
62
|
+
if(s.plannerLedger!==undefined){validatePlannerLedger(s.plannerLedger);if(s.plannerLedger.stages.some(stage=>stage.workflowId!==s.workflowId))fail('Planner workflow binding');}
|
|
62
63
|
return s;
|
|
63
64
|
}
|
|
64
65
|
export function validateRequest(r){
|
|
@@ -77,3 +78,21 @@ export function validateEvidence(e){
|
|
|
77
78
|
}
|
|
78
79
|
return e;
|
|
79
80
|
}
|
|
81
|
+
|
|
82
|
+
/** Shared stage shape. Missing ledgers remain valid legacy state, never fresh history. */
|
|
83
|
+
export function validatePlannerStage(stage){
|
|
84
|
+
if(stage?.ready!==undefined&&typeof stage.ready!=='boolean')fail('Planner readiness type');
|
|
85
|
+
if(!object(stage)||!text(stage.id)||!text(stage.workflowId)||!object(stage.document)||!absolute(stage.document.path)||!['spec','plan'].includes(stage.document.kind)||!text(stage.document.phase)||!text(stage.approvalRef)||!Number.isSafeInteger(stage.inputVersion)||stage.inputVersion<1||!Number.isSafeInteger(stage.scopeVersion)||stage.scopeVersion<0||!Number.isSafeInteger(stage.criteriaVersion)||stage.criteriaVersion<1)fail('Planner stage identity');
|
|
86
|
+
if(!Array.isArray(stage.criteria)||!stage.criteria.length||!unique(stage.criteria,'id')||stage.criteria.some(c=>!['open','resolved'].includes(c.status)||!text(c.judgmentRef)))fail('Planner criteria');
|
|
87
|
+
if(!Array.isArray(stage.calls)||!unique(stage.calls,'requestId')||stage.calls.some(c=>!['initial','correction','further'].includes(c.kind)||!['reserved','unknown','succeeded','failed'].includes(c.status)||!Array.isArray(c.evidenceHashes)||c.evidenceHashes.some(h=>!hash(h)))||stage.calls.filter(c=>c.kind==='initial').length>1||stage.calls.filter(c=>c.kind==='correction').length>1)fail('Planner call history');
|
|
88
|
+
if(stage.pending!==null&&(!text(stage.pending)||!stage.calls.some(c=>c.requestId===stage.pending&&['reserved','unknown'].includes(c.status))))fail('Planner pending call');
|
|
89
|
+
const pending=stage.calls.filter(c=>['reserved','unknown'].includes(c.status));if(pending.length>1||pending.length!==(stage.pending===null?0:1))fail('Planner orphan reservation');
|
|
90
|
+
if(stage.artifacts!==undefined&&(!Array.isArray(stage.artifacts)||!unique(stage.artifacts,'ref')||stage.artifacts.some(a=>!absolute(a.path)||!hash(a.hash)||!Array.isArray(a.criterionIds)||!['draft','self-review','input','progress','finding'].includes(a.purpose)||a.verification?.status!=='verified'||a.verification.source!=='local-bytes'||a.semantic&&a.semantic.source!=='operator-attested')))fail('Planner artifact evidence');
|
|
91
|
+
if(stage.ready===true&&(stage.pending!==null||stage.criteria.some(c=>c.status!=='resolved')||stage.result?.status!=='succeeded'||stage.review?.complete!==true||stage.review?.full!==true||stage.review?.resultRef!==stage.result.ref||!stage.artifacts?.some(a=>a.ref===stage.result.artifactRef&&a.purpose==='draft')||!stage.artifacts?.some(a=>a.ref===stage.result.selfReviewRef&&a.purpose==='self-review')))fail('Planner incomplete readiness');
|
|
92
|
+
return stage;
|
|
93
|
+
}
|
|
94
|
+
export function validatePlannerLedger(ledger){
|
|
95
|
+
if(!object(ledger)||ledger.version!==1||!Array.isArray(ledger.stages)||!unique(ledger.stages,'id'))fail('Planner ledger');
|
|
96
|
+
for(const stage of ledger.stages)validatePlannerStage(stage);
|
|
97
|
+
return ledger;
|
|
98
|
+
}
|
package/lib/tui/app.mjs
CHANGED
|
@@ -2,23 +2,27 @@ import {createTerminalSession} from './terminal.mjs';
|
|
|
2
2
|
import {escapeTerminalText,navigationHint,wrapDetailRows} from './render.mjs';
|
|
3
3
|
import {createManagementContext,prepareOperation,executeOperation} from '../management/operations.mjs';
|
|
4
4
|
import {collectStatus} from '../management/status.mjs';
|
|
5
|
-
import {runSetup} from './setup.mjs';
|
|
5
|
+
import {runSetup,loadPresetPreviewConfig} from './setup.mjs';
|
|
6
|
+
import {loadCustomCatalog} from './custom-preset.mjs';
|
|
6
7
|
const home=['Install / Update','Change Preset','Check Version','Uninstall','Exit'];
|
|
7
8
|
function goHome(){throw Object.assign(Error('Return home'),{code:'ARC_TUI_HOME'});}
|
|
8
9
|
function interrupted(){return Object.assign(Error('Session interrupted'),{code:'ARC_CANCELLED'});}
|
|
9
10
|
export function createTuiInterface(session,{input,controller}={}){
|
|
10
11
|
async function event(){const e=await session.nextEvent();if(e.name==='interrupt')throw interrupted();if(e.name==='eof')throw Object.assign(Error('Session closed'),{code:'ARC_TUI_EOF'});if(e.name==='error')throw e.error;return e;}
|
|
11
|
-
async function menu(title,rows,{selected=0,info=[],separatorBefore,backRow=false,backLabel='back',wordmark=false}={}){
|
|
12
|
+
async function menu(title,rows,{selected=0,info=[],separatorBefore,backRow=false,backLabel='back',escapeBack=false,wordmark=false,renderBody,refreshMs}={}){
|
|
12
13
|
const actions=backRow?[...rows,'Back']:rows;
|
|
13
14
|
selected=actions.length?Math.max(0,Math.min(selected,actions.length-1)):0;
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
let pending,timer,blink=true;
|
|
16
|
+
try{while(true){
|
|
17
|
+
const viewport=session.viewport(),display=actions.flatMap((row,index)=>(index===separatorBefore||backRow&&index===rows.length)?['',row]:[row]),displayIndex=selected+(Number.isInteger(separatorBefore)&&selected>=separatorBefore?1:0)+(backRow&&selected===rows.length?1:0),information=wrapDetailRows(typeof info==='function'?info({selected,...viewport}):info,viewport.columns);
|
|
16
18
|
if(wordmark&&information.length)information.push('');
|
|
17
19
|
const capacity=Math.max(1,viewport.rows-(wordmark?6:1)-information.length-4),offset=Math.max(0,Math.min(displayIndex-capacity+1,display.length-capacity)),visible=display.slice(offset,offset+capacity);
|
|
18
20
|
const screen={title,wordmark,rows:[...information,...visible],selected:actions.length?information.length+displayIndex-offset:undefined,hint:navigationHint('menu',{backLabel}),...(display.length>capacity?{position:(offset+1)+'-'+Math.min(offset+capacity,display.length)+'/'+display.length}:{})};
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
const body=renderBody?.({selected,blink,...viewport});if(body){screen.rows=body.rows;screen.selected=body.selected;screen.showSelection=body.showSelection;}
|
|
22
|
+
const rendered=session.render(screen);pending??=event();
|
|
23
|
+
let e;if(refreshMs>0){e=await Promise.race([pending,new Promise(resolve=>{timer=setTimeout(()=>resolve(null),refreshMs);})]);clearTimeout(timer);timer=undefined;if(e===null){blink=!blink;continue;}}else e=await pending;pending=undefined;
|
|
24
|
+
if(e.name==='up'&&actions.length)selected=(selected+actions.length-1)%actions.length;else if(e.name==='down'&&actions.length)selected=(selected+1)%actions.length;else if(e.name==='escape'){if(backLabel==='exit'||escapeBack)return null;goHome();}else if(e.name==='enter'&&actions.length&&rendered.selectionEnabled!==false)return backRow&&selected===rows.length?null:selected;
|
|
25
|
+
}}finally{if(timer!==undefined)clearTimeout(timer);}
|
|
22
26
|
}
|
|
23
27
|
async function text(title,initial=''){let value=initial,selected=2;while(true){const rendered=session.render({title,mode:'text',rows:[value,'','Save','Back',''],selected,hint:navigationHint('text')});const e=await event();if(e.name==='text'||e.name==='paste')value+=e.text;else if(e.name==='backspace')value=[...value].slice(0,-1).join('');else if(e.name==='up'||e.name==='down')selected=selected===2?3:2;else if(e.name==='escape')goHome();else if(e.name==='enter'&&rendered.selectionEnabled!==false)return selected===2?value:null;}}
|
|
24
28
|
async function page(title,rows){
|
|
@@ -32,21 +36,27 @@ export function createTuiInterface(session,{input,controller}={}){
|
|
|
32
36
|
async function busy(action){session.render({title:'Working',mode:'busy',rows:['Operation in progress. Please wait.'],hint:navigationHint('busy')});session.setBusy(true);const wasFlowing=input?.readableFlowing;const onData=chunk=>{if(String(chunk).includes('\x03')||String(chunk).includes('\x04'))controller?.abort();};input?.on('data',onData);try{const result=await action();if(controller?.signal.aborted){if(typeof result?.code!=='number')throw interrupted();return{...result,code:130,interrupted:true};}return result;}finally{input?.removeListener('data',onData);if(wasFlowing!==true)input?.pause?.();session.setBusy(false);session.discardInput();}}
|
|
33
37
|
return{menu,text,page,busy};
|
|
34
38
|
}
|
|
35
|
-
export function resultRows(result){
|
|
39
|
+
export function resultRows(result){
|
|
40
|
+
const clean=text=>text.replace(/; rollback: [^\r\n]*/g,''),label=platform=>({codex:'Codex',claude:'Claude'}[platform]??platform);
|
|
41
|
+
const removed=result.code===0&&['removed','absent'].includes(result.cli?.status);
|
|
42
|
+
return[...(result.messages??[]).map(s=>clean(s.trim())),...[result.stdout,result.stderr].flatMap(text=>text?text.trimEnd().split(/\r?\n/).map(clean):[]),...(result.results??[]).flatMap(item=>{const platform=item.platform??item.target?.platform??'Target';return[...(removed&&item.status==='uninstalled'?[]:[item.status==='installed'?`ARC installed for ${label(platform)}.`:item.status==='uninstalled'?`${label(platform)} ARC settings removed.`:`${platform}: ${item.status}`]),...(item.residualPaths??[]).map(p=>'Residual path: '+p),...(item.error?['Error: '+item.error.message]:[])];}),...(removed?[result.cli.status==='removed'?'ARC settings and CLI removed.':'ARC settings removed. CLI is not installed.']:result.cli?['CLI: '+result.cli.status,...(result.cli.error?['CLI error: '+result.cli.error.message]:[])]:[]),...(result.error?['Error: '+result.error.message]:[]),'Exit code: '+result.code,...(result.exitSession?[result.cli?'CLI removal ended this session.':'CLI replacement ended this session.']:[])];
|
|
43
|
+
}
|
|
36
44
|
export async function runTui(options={}){
|
|
37
45
|
let session=options.session,code=0,finalReport;const controller=new AbortController(),signals=options.signals??process;const abort=()=>controller.abort();signals.on('SIGINT',abort);signals.on('SIGTERM',abort);
|
|
38
46
|
try{session??=createTerminalSession(options);const ui=createTuiInterface(session,{input:options.input??process.stdin,controller}),api={collectStatus,prepareOperation,executeOperation,...options.services},context=options.context?{...options.context,signal:controller.signal}:await createManagementContext({...options.contextOptions,signal:controller.signal});const draft={};let selected=0;
|
|
39
47
|
async function execute(prepared){
|
|
40
48
|
if(!prepared)return;const result=await ui.busy(()=>api.executeOperation(context,prepared));code=result.code;
|
|
41
|
-
if(code===
|
|
42
|
-
|
|
49
|
+
if(code===0&&['install','preset'].includes(prepared.kind))for(const plan of prepared.plans??[]){const platform=plan.target?.platform;if(draft[platform])delete draft[platform].customEdited;}
|
|
50
|
+
const rows=prepared.kind==='update'&&code===0?[`ARC updated to ${prepared.latestVersion}.`,'Start a new agent session to use the updated settings.']:resultRows(result);
|
|
51
|
+
if(code===130||result.exitSession){finalReport=rows;if(result.exitSession)session.render({title:'Result',rows:finalReport,hint:'Session ended'});return true;}
|
|
52
|
+
await ui.page('Result',rows);return false;
|
|
43
53
|
}
|
|
44
54
|
async function uninstall(){
|
|
45
|
-
let
|
|
46
|
-
while(true){const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
55
|
+
let selected=0,target=0;
|
|
56
|
+
while(true){const choice=await ui.menu('Uninstall',['Settings','Remove CLI'],{selected,backRow:true});if(choice===null)return null;selected=choice;
|
|
57
|
+
if(choice===0){const next=await ui.menu('Uninstall settings',['Codex','Claude'],{selected:target,backRow:true});if(next===null)continue;target=next;return ui.busy(()=>api.prepareOperation(context,{kind:'uninstall',target:['codex','claude'][target],removeCli:false}));}
|
|
58
|
+
const proceed=await ui.menu('Remove CLI',['Continue','Back'],{selected:1,escapeBack:true,info:['Removes ARC settings for Codex and Claude, then removes the ARC CLI.','']});
|
|
59
|
+
if(proceed===0)return ui.busy(()=>api.prepareOperation(context,{kind:'uninstall',target:'all',removeCli:true}));
|
|
50
60
|
}
|
|
51
61
|
}
|
|
52
62
|
while(true){const statuses=await api.collectStatus(context),ordered=[...statuses].sort((a,b)=>({claude:0,codex:1}[a.platform]??2)-({claude:0,codex:1}[b.platform]??2));
|
|
@@ -54,7 +64,7 @@ export async function runTui(options={}){
|
|
|
54
64
|
const info=ordered.length?[ordered.map(status=>label(status)+': '+state(status)).join(' / '),...ordered.flatMap(status=>(status.diagnostics??[]).map(error=>label(status)+': '+error.message))]:[];const choice=await ui.menu('ARC',home,{selected,info,backLabel:'exit',wordmark:true});if(choice===null||choice===4)return code;selected=choice;
|
|
55
65
|
try{
|
|
56
66
|
let prepared;
|
|
57
|
-
if(choice===0||choice===1)prepared=await runSetup({ui,statuses,draft,kind:
|
|
67
|
+
if(choice===0||choice===1){const kind=choice===0?'install':'preset';prepared=await runSetup({ui,statuses,draft,kind,loadPreviewConfig:target=>loadPresetPreviewConfig({context,statuses,kind,target}),loadCatalog:target=>api.loadCatalog?api.loadCatalog(target):loadCustomCatalog({platform:target,env:context.env??process.env,invocationCwd:context.cwd??process.cwd(),userHome:context.userHome,signal:controller.signal}),prepare:settings=>ui.busy(()=>api.prepareOperation(context,settings))});}
|
|
58
68
|
else if(choice===2){prepared=await ui.busy(()=>api.prepareOperation(context,{kind:'update'}));const action=await ui.menu('Check Version',prepared.canUpgrade?['Version Upgrade']:[],{backRow:true,info:['Current version: '+prepared.currentVersion,'Latest version: '+(prepared.latestVersion??'Unavailable'),...(prepared.latestError?[prepared.latestError.message]:[]),...(prepared.canUpgrade?['Continue?']:[])]});if(action!==0)prepared=null;}
|
|
59
69
|
else prepared=await uninstall();
|
|
60
70
|
if(await execute(prepared))return code;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { ROLES, effortField, validateRoles } from '../models/config.mjs';
|
|
5
|
+
import { assertCatalogRoles, discoverModelCatalog, listCodexModelCapabilities } from '../models/catalog.mjs';
|
|
6
|
+
import { escapeTerminalText, wrapDetailRows } from './render.mjs';
|
|
7
|
+
import { resolveTarget } from '../installer/targets.mjs';
|
|
8
|
+
import { presetPreviewRows } from './setup.mjs';
|
|
9
|
+
|
|
10
|
+
const names = ['Scouter', 'Planner', 'Implementer', 'QA', 'Reviewer'];
|
|
11
|
+
const unavailable = () => ({ status: 'unavailable', code: 'unavailable', message: 'Catalog is unavailable.' });
|
|
12
|
+
|
|
13
|
+
async function assertProjectless(directory) {
|
|
14
|
+
for (let current = directory;; current = path.dirname(current)) {
|
|
15
|
+
for (const name of ['.git', '.codex']) {
|
|
16
|
+
try { await fs.lstat(path.join(current, name)); throw Error('Project configuration is present'); }
|
|
17
|
+
catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
18
|
+
}
|
|
19
|
+
if (path.dirname(current) === current) break;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function loadCustomCatalog({ platform, env = process.env, signal, fetchImpl, listCodexModels = listCodexModelCapabilities, tempDir = os.tmpdir(), invocationCwd = process.cwd(), userHome = os.homedir() } = {}) {
|
|
24
|
+
if (platform !== 'codex') return discoverModelCatalog({ platform, env, signal, fetchImpl });
|
|
25
|
+
let directory, result;
|
|
26
|
+
try {
|
|
27
|
+
const childEnv = env?.CODEX_HOME === undefined ? env : { ...env, CODEX_HOME: resolveTarget('codex', { env, cwd: invocationCwd, userHome }).home };
|
|
28
|
+
const base = await fs.realpath(tempDir);
|
|
29
|
+
await assertProjectless(base);
|
|
30
|
+
directory = await fs.mkdtemp(path.join(base, 'arc-model-catalog-'));
|
|
31
|
+
const cwd = await fs.realpath(directory);
|
|
32
|
+
await assertProjectless(cwd);
|
|
33
|
+
result = await discoverModelCatalog({ platform, env: childEnv, signal, listCodexModels: options => listCodexModels({ ...options, cwd }) });
|
|
34
|
+
} catch { result = unavailable(); }
|
|
35
|
+
finally { if (directory) try { await fs.rm(directory, { recursive: true, force: true }); } catch { result = unavailable(); } }
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function pairKey(model, effort) { return JSON.stringify([model, effort]); }
|
|
40
|
+
function short(value, limit = 12) {
|
|
41
|
+
const characters = [...escapeTerminalText(value)];
|
|
42
|
+
return characters.length > limit ? characters.slice(0, limit - 1).join('') + '…' : characters.join('');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function details(model, effort, { columns = 80, rows = 24 } = {}) {
|
|
46
|
+
return [...wrapDetailRows([`Model: ${model}`], columns), ...wrapDetailRows([`Effort: ${effort}`], columns), ''];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function detailGuard(info) {
|
|
50
|
+
return state => {
|
|
51
|
+
const requiredRows = wrapDetailRows(info(state), state.columns).length + 6;
|
|
52
|
+
return state.rows < requiredRows ? { rows: [`Menu requires ${requiredRows} rows at ${state.columns} columns.`], selected: undefined } : undefined;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function editCustomPreset({ ui, config, initialRoles, catalog }) {
|
|
57
|
+
const platform = config.platform, field = effortField(platform), roles = validateRoles(initialRoles, platform);
|
|
58
|
+
if (catalog?.platform !== platform || !Array.isArray(catalog.pairs) || !catalog.pairs.length) return null;
|
|
59
|
+
const available = new Set(catalog.pairs.map(pair => pairKey(pair.model, pair.effort)));
|
|
60
|
+
let selectedRole = 0;
|
|
61
|
+
while (true) {
|
|
62
|
+
const labels = ROLES.map((role, index) => {
|
|
63
|
+
const value = roles[role], missing = !available.has(pairKey(value.model, value[field]));
|
|
64
|
+
return `${names[index]}: ${short(value.model)} / ${short(value[field], 8)}${missing ? ' !' : ''}`;
|
|
65
|
+
});
|
|
66
|
+
const selected = await ui.menu('Custom preset', [...labels, 'Done'], { selected: selectedRole, backRow: true, separatorBefore: labels.length, refreshMs:600, renderBody:({selected,blink,columns,rows})=>{
|
|
67
|
+
const values=ROLES.map((role,index)=>({role:names[index]+(!available.has(pairKey(roles[role].model,roles[role][field]))?' !':''),model:roles[role].model,effort:roles[role][field]}));
|
|
68
|
+
const diagram=presetPreviewRows(values,{columns,rows,selectedRole:selected<ROLES.length?selected:undefined,blink});
|
|
69
|
+
const body=[...diagram,'','Done','Back'];
|
|
70
|
+
return {rows:body,selected:diagram.length===1?undefined:selected<3?1:selected<5?9:selected===5?diagram.length+1:diagram.length+2,showSelection:selected>=ROLES.length};
|
|
71
|
+
}});
|
|
72
|
+
if (selected === null) return null;
|
|
73
|
+
if (selected === ROLES.length) {
|
|
74
|
+
try { assertCatalogRoles({ platform, roles, catalog }); return roles; }
|
|
75
|
+
catch { await ui.page('Custom preset unavailable', ['Select an available model and effort for each role.']); continue; }
|
|
76
|
+
}
|
|
77
|
+
if (!ROLES[selected]) continue;
|
|
78
|
+
selectedRole = selected;
|
|
79
|
+
const role = ROLES[selected], current = roles[role], initial = catalog.pairs.findIndex(pair => pairKey(pair.model, pair.effort) === pairKey(current.model, current[field]));
|
|
80
|
+
const models = [...new Set(catalog.pairs.map(pair => pair.model))];
|
|
81
|
+
const excluded = Array.isArray(catalog.unavailable) ? catalog.unavailable.filter(item => typeof item?.model === 'string' && typeof item.reason === 'string') : [];
|
|
82
|
+
const rows = [...models, ...(excluded.length ? ['Unavailable models'] : [])];
|
|
83
|
+
while (true) {
|
|
84
|
+
const info = state => {
|
|
85
|
+
const model = models[state.selected];
|
|
86
|
+
return [...(initial < 0 ? ['Current model and effort are unavailable.'] : []), ...details(model ?? '-', model ? catalog.pairs.filter(pair => pair.model === model).map(pair => pair.effort).join(', ') : '-', state)];
|
|
87
|
+
};
|
|
88
|
+
const renderModelMenu = state => {
|
|
89
|
+
const guarded = detailGuard(info)(state);
|
|
90
|
+
if (guarded) return guarded;
|
|
91
|
+
const information = wrapDetailRows(info(state), state.columns), items = [...rows, 'Back'], display = [], starts = [], lengths = [];
|
|
92
|
+
for (let index = 0; index < items.length; index++) {
|
|
93
|
+
if (index === rows.length) display.push('');
|
|
94
|
+
starts[index] = display.length;
|
|
95
|
+
const block = wrapDetailRows([items[index]], state.columns);
|
|
96
|
+
lengths[index] = block.length;
|
|
97
|
+
display.push(...block);
|
|
98
|
+
}
|
|
99
|
+
const selected = Math.max(0, Math.min(state.selected, items.length - 1)), commonCapacity = Math.max(1, state.rows - information.length - 5), positionNeeded = rows.length + 2 > commonCapacity, reservedRows = positionNeeded ? 5 : 4, requiredRows = information.length + lengths[selected] + reservedRows;
|
|
100
|
+
if (state.rows < requiredRows) return { rows: [`Menu requires ${requiredRows} rows at ${state.columns} columns.`], selected: undefined };
|
|
101
|
+
const capacity = Math.max(1, state.rows - information.length - reservedRows), maxOffset = Math.max(0, display.length - capacity);
|
|
102
|
+
const offset = Math.max(0, Math.min(maxOffset, starts[selected] + lengths[selected] - capacity));
|
|
103
|
+
return { rows: [...information, ...display.slice(offset, offset + capacity)], selected: information.length + starts[selected] - offset };
|
|
104
|
+
};
|
|
105
|
+
const choice = await ui.menu(`Select model — ${names[selected]}`, rows, {
|
|
106
|
+
selected: Math.max(0, models.indexOf(current.model)), backRow: true, info,
|
|
107
|
+
renderBody: renderModelMenu,
|
|
108
|
+
});
|
|
109
|
+
if (choice === null) break;
|
|
110
|
+
if (choice === models.length && excluded.length) {
|
|
111
|
+
await ui.page('Unavailable models', excluded.map(item => `${escapeTerminalText(item.model)}: ${escapeTerminalText(item.reason)}`));
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (models[choice]) {
|
|
115
|
+
const model=models[choice], efforts=[...new Set(catalog.pairs.filter(pair=>pair.model===model).map(pair=>pair.effort))];
|
|
116
|
+
const effortInfo=state=>details(model,efforts[state.selected]??'-',state);
|
|
117
|
+
const effort=await ui.menu(`Select effort — ${names[selected]}`,efforts,{selected:Math.max(0,efforts.indexOf(current[field])),backRow:true,info:effortInfo,renderBody:detailGuard(effortInfo)});
|
|
118
|
+
if(effort!==null&&efforts[effort]){roles[role]={model,[field]:efforts[effort]};break;}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
package/lib/tui/render.mjs
CHANGED
|
@@ -28,13 +28,14 @@ export function renderScreen(screen={}, {columns=80,rows=24}={}){
|
|
|
28
28
|
const entries=screen.rows??[],hint=screen.hint??navigationHint('menu');
|
|
29
29
|
const heading=screen.wordmark?[...wordmark,'─'.repeat(Math.min(columns,34))]:[escapeTerminalText(screen.title)],position=screen.position?[escapeTerminalText(screen.position)]:[];
|
|
30
30
|
const footer=screen.mode==='busy'||screen.hint==='Session ended'?[escapeTerminalText(hint)]:[...(screen.mode==='text'||screen.mode==='page'?[]:['']),'─'.repeat(columns),escapeTerminalText(hint)];
|
|
31
|
-
const
|
|
31
|
+
const showSelection=screen.showSelection!==false;
|
|
32
|
+
const lines=[...heading,...entries.map((row,index)=>(index===screen.selected&&showSelection?'> ':' ')+escapeTerminalText(row)),...position,...footer];
|
|
32
33
|
const fitted=lines.map(line=>fit(line,columns));
|
|
33
34
|
const bodySize=Math.max(0,rows-footer.length),visible=[...fitted.slice(0,Math.min(lines.length-footer.length,bodySize)),...fitted.slice(-Math.min(rows,footer.length))].map(line=>line.text);
|
|
34
35
|
const styles=[];
|
|
35
36
|
if(screen.wordmark){for(let index=0;index<Math.min(wordmark.length,visible.length);index++){if(visible[index]===fitted[index].text)styles.push({line:index,color:'logo',cells:[...visible[index]].flatMap((character,column)=>character==='█'?[{column,rgb:logoColor(column)}]:[])});}if(visible.length>wordmark.length&&visible[wordmark.length]===fitted[wordmark.length].text)styles.push({line:wordmark.length,color:'green'});}
|
|
36
37
|
const selectedLine=heading.length+screen.selected;
|
|
37
|
-
if(Number.isInteger(screen.selected)&&screen.selected>=0&&screen.selected<entries.length&&selectedLine<Math.min(bodySize,lines.length-footer.length)){const used=[...visible[selectedLine]].reduce((sum,character)=>sum+width(character),0);visible[selectedLine]+=' '.repeat(Math.max(0,Math.min(columns,34)-used));styles.push({line:selectedLine,color:'selected'});}
|
|
38
|
+
if(showSelection&&Number.isInteger(screen.selected)&&screen.selected>=0&&screen.selected<entries.length&&selectedLine<Math.min(bodySize,lines.length-footer.length)){const used=[...visible[selectedLine]].reduce((sum,character)=>sum+width(character),0);visible[selectedLine]+=' '.repeat(Math.max(0,Math.min(columns,34)-used));styles.push({line:selectedLine,color:'selected'});}
|
|
38
39
|
|
|
39
40
|
const selectionEnabled=Number.isInteger(screen.selected)&&screen.selected>=0&&screen.selected<entries.length&&selectedLine<rows-footer.length-position.length&&fitted[selectedLine].complete;
|
|
40
41
|
return{text:visible.join('\n'),selectionEnabled,styles};
|
package/lib/tui/setup.mjs
CHANGED
|
@@ -1,22 +1,75 @@
|
|
|
1
|
-
const presetLabel=value=>({economy:'Economy',balanced:'Balanced',smart:'Smart'}[value]??value);
|
|
1
|
+
const presetLabel=value=>({economy:'Economy',balanced:'Balanced',smart:'Smart',custom:'Custom'}[value]??value);
|
|
2
2
|
const languageLabel=value=>({en:'EN',ko:'KR'}[value]??value);
|
|
3
3
|
import {parsePositiveLimit} from '../limits.mjs';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import {readFileSync} from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {loadModelConfig,applyPreset,applyCustomRoles,resolveModelSettings,ModelValidationError} from '../models/index.mjs';
|
|
7
|
+
import {escapeTerminalText,wrapDetailRows} from './render.mjs';
|
|
8
|
+
import {editCustomPreset} from './custom-preset.mjs';
|
|
9
|
+
import {assertCatalogRoles} from '../models/catalog.mjs';
|
|
10
|
+
import {ROLES,effortField,validateRoles} from '../models/config.mjs';
|
|
11
|
+
const presets=['economy','balanced','smart','custom'];
|
|
12
|
+
const roleNames=['Scouter','Planner','Implementer','QA','Reviewer'];
|
|
13
|
+
export function loadPresetPreviewConfig({context,statuses,kind,target,readText=filename=>readFileSync(filename,'utf8')}){
|
|
14
|
+
if(!['codex','claude'].includes(target)||!['install','preset'].includes(kind))throw Object.assign(Error('Unsupported preset preview target or operation'),{code:'ARC_CLI_INPUT'});
|
|
15
|
+
if(kind==='install')return loadModelConfig(readText(path.join(context.packageRoot,target,'skill/models.toml')),target);
|
|
16
|
+
const config=statuses.find(status=>status.platform===target)?.settings?.modelConfig;
|
|
17
|
+
if(!config)throw new ModelValidationError('Installed model configuration unavailable for '+target);
|
|
18
|
+
const validated=applyPreset(config,config.preset);
|
|
19
|
+
if(validated.platform!==target)throw new ModelValidationError('Model configuration platform does not match target');
|
|
20
|
+
return validated;
|
|
21
|
+
}
|
|
22
|
+
export function presetRoleRows(config,preset){const resolved=resolveModelSettings(applyPreset(config,preset));return Object.values(resolved.roles).map((settings,index)=>({role:roleNames[index],model:settings.model,effort:settings[resolved.platform==='codex'?'model_reasoning_effort':'effort']}));}
|
|
23
|
+
export function presetPreviewRows(roleRows,{columns=80,rows=24,selectedRole,blink=true,compact=false}={}){
|
|
24
|
+
const values=roleRows.map(({role,model,effort})=>({role:escapeTerminalText(role),model:escapeTerminalText(model),effort:escapeTerminalText(effort)}));
|
|
25
|
+
const cells=value=>[...value].reduce((total,character)=>total+(/[\p{Mark}]/u.test(character)?0:/[\u2190-\u2193\u2500-\u257f\u2588]/u.test(character)?1:character.codePointAt(0)>=0x1100?2:1),0);
|
|
26
|
+
const width=Math.max(20,...values.flatMap(value=>[cells(value.role)+2,cells(value.model),cells('effort: '+value.effort)])),boxWidth=width+4,total=boxWidth*3+4,requiredRows=24;
|
|
27
|
+
if(columns<total+3||rows<requiredRows){
|
|
28
|
+
if(compact){
|
|
29
|
+
const available=Math.max(2,Math.floor(Number(columns)||80)-3),shorten=(value,limit)=>{if(cells(value)<=limit)return value;let result='',used=0;for(const character of value){const size=cells(character);if(used+size>Math.max(0,limit-1))break;result+=character;used+=size;}return result+'…';};
|
|
30
|
+
const roleRow=value=>{const limit=Math.max(1,available-cells(value.role)-cells(': / '+value.effort));return value.role+': '+shorten(value.model,limit)+' / '+value.effort;};
|
|
31
|
+
return [...values.map(roleRow),''];
|
|
32
|
+
}
|
|
33
|
+
return [`Diagram requires ${total+3} columns and ${requiredRows} rows.`];
|
|
34
|
+
}
|
|
35
|
+
const border=(left,right,junction,hidden)=>{const line=[left,...'─'.repeat(width+2),right];if(junction)line[Math.floor(boxWidth/2)]=junction;return hidden?' '.repeat(boxWidth):line.join('');};
|
|
36
|
+
const box=(value,index,up)=>{const hidden=index===selectedRole&&!blink,edge=hidden?' ':'│',label=(index===selectedRole?'> ':'')+value.role;return [border('┌','┐',up?'┴':null,hidden),...[label,value.model,'effort: '+value.effort].map(text=>edge+' '+text+' '.repeat(width-cells(text))+' '+edge),border('└','┘',up?null:'┬',hidden)];};
|
|
37
|
+
const group=(indices,indent,up)=>{const boxes=indices.map(index=>box(values[index],index,up));return Array.from({length:5},(_,row)=>' '.repeat(indent)+boxes.map(lines=>lines[row]).join(' '));};
|
|
38
|
+
const center=Math.floor(boxWidth/2),middle=boxWidth+2+center,right=2*(boxWidth+2)+center,indent=Math.floor((total-(boxWidth*2+2))/2),bottomLeft=indent+center,bottomRight=bottomLeft+boxWidth+2;
|
|
39
|
+
const bus=(left,right,branches,up=false)=>{const line=Array(total).fill(' ');for(let index=left;index<=right;index++)line[index]='─';for(const index of branches)line[index]=index===middle?'┼':up?'┴':'┬';return line.join('').trimEnd();};
|
|
40
|
+
return [...group([0,1,2],0,false),bus(center,right,[center,middle,right],true),' '.repeat(Math.max(0,middle-6))+'Orchestrator',bus(bottomLeft,bottomRight,[bottomLeft,middle,bottomRight]),...group([3,4],indent,true)];
|
|
41
|
+
}
|
|
42
|
+
export function initialSettings(statuses){return Object.fromEntries(['codex','claude'].map(platform=>{const settings=statuses.find(s=>s.platform===platform)?.settings,custom=settings?.modelConfig?.custom;return[platform,{preset:settings?.modelConfig?.preset??'balanced',language:settings?.language??'en',maxTotalSubagents:settings?.limits?.max_total_subagents??100,maxConcurrentSubagents:settings?.limits?.max_concurrent_subagents??5,skipGlobal:false,...(custom?{customRoles:structuredClone(custom)}:{})}];}));}
|
|
43
|
+
export function setupOptions(draft,target,verifiedCatalog,kind){if(!['codex','claude'].includes(target))throw Object.assign(Error('Unsupported setup target'),{code:'ARC_CLI_INPUT'});const {customEdited,customRoles,...settings}=draft[target];return{...settings,target,...(settings.preset==='custom'||customEdited?{customRoles,verifiedCatalog}:kind==='install'&&customRoles?{preservedCustomRoles:customRoles}:{})};}
|
|
44
|
+
export async function runSetup({ui,statuses,draft,kind,prepare,loadPreviewConfig,loadCatalog}){
|
|
7
45
|
for(const[key,value]of Object.entries(initialSettings(statuses)))draft[key]??=value;
|
|
46
|
+
const catalogs=new Map();
|
|
47
|
+
async function catalogFor(target){
|
|
48
|
+
if(catalogs.has(target))return catalogs.get(target);
|
|
49
|
+
let result;try{result=typeof loadCatalog==='function'?await(ui.busy?ui.busy(()=>loadCatalog(target)):loadCatalog(target)):null;}catch(error){if(['ARC_CANCELLED','ARC_TUI_EOF','ARC_TUI_HOME'].includes(error?.code))throw error;}
|
|
50
|
+
if(result?.status==='ready'&&result.catalog?.platform===target&&Array.isArray(result.catalog.pairs)&&result.catalog.pairs.length){try{const pair=result.catalog.pairs[0],field=effortField(target),sample=Object.fromEntries(ROLES.map(role=>[role,{model:pair.model,[field]:pair.effort}]));assertCatalogRoles({platform:target,roles:sample,catalog:result.catalog});for(const candidate of result.catalog.pairs)validateRoles(Object.fromEntries(ROLES.map(role=>[role,{model:candidate.model,[field]:candidate.effort}])),target);catalogs.set(target,result.catalog);return result.catalog;}catch{}}
|
|
51
|
+
await ui.page('Catalog unavailable',['Model catalog could not be loaded.']);return null;
|
|
52
|
+
}
|
|
8
53
|
let selectedTarget=0;
|
|
9
|
-
while(true){const targetIndex=await ui.menu(kind==='preset'?'Change Preset':'Install / Update',['Codex','Claude'
|
|
10
|
-
|
|
11
|
-
const choice=await ui.menu('Target settings differ',['Use Codex values for both','Use Claude values for both'],{backRow:true,info:['Codex: '+summary(draft.codex),'Claude: '+summary(draft.claude),'Choosing common values replaces the other target values.']});if(choice===null)continue;const source=choice===0?'codex':'claude';draft.codex={...draft[source]};draft.claude={...draft[source]};
|
|
12
|
-
}
|
|
13
|
-
const settings=draft[target==='all'?'codex':target];let editing=true,selectedField=kind==='preset'?1:0;
|
|
54
|
+
while(true){const targetIndex=await ui.menu(kind==='preset'?'Change Preset':'Install / Update',['Codex','Claude'],{selected:selectedTarget,backRow:true});if(targetIndex===null)return null;selectedTarget=targetIndex;const target=['codex','claude'][targetIndex];
|
|
55
|
+
const settings=draft[target];let editing=true,selectedField=0;
|
|
14
56
|
while(editing){const labels=kind==='preset'?['Preset: '+presetLabel(settings.preset),'Apply','Back']:['Preset: '+presetLabel(settings.preset),'Harness language: '+languageLabel(settings.language),'Total subagents: '+settings.maxTotalSubagents,'Concurrent subagents: '+settings.maxConcurrentSubagents,'Apply global settings: '+!settings.skipGlobal,'Apply','Back'];const field=await ui.menu('Settings — '+target,labels,{selected:selectedField,separatorBefore:labels.length-2});if(field===null||field===labels.length-1)break;selectedField=field;
|
|
15
|
-
if(field===0){
|
|
57
|
+
if(field===0){
|
|
58
|
+
const config=loadPreviewConfig(target);let preview=presets.indexOf(settings.preset);if(preview<0)preview=1;
|
|
59
|
+
const value=await ui.menu('Performance preset',['Economy','Balanced','Smart','Custom'],{backRow:true,selected:preview,info:({selected,columns,rows})=>{
|
|
60
|
+
const custom=settings.customRoles??config.custom;
|
|
61
|
+
const choice=selected>=presets.length||selected===3&&!custom?settings.preset:presets[selected];
|
|
62
|
+
const previewConfig=choice==='custom'&&custom?applyCustomRoles(config,custom):config;
|
|
63
|
+
return presetPreviewRows(presetRoleRows(previewConfig,choice),{columns,rows,compact:true});
|
|
64
|
+
}});
|
|
65
|
+
if(value!==null&&value<3)settings.preset=presets[value];
|
|
66
|
+
else if(value===3){const catalog=await catalogFor(target);if(catalog){const initial=settings.customRoles??config.custom??resolveModelSettings(applyPreset(config,settings.preset==='custom'?'balanced':settings.preset)).roles;const roles=await editCustomPreset({ui,config,initialRoles:initial,catalog});if(roles){settings.customRoles=roles;settings.customEdited=true;settings.preset='custom';}}}
|
|
67
|
+
if(kind==='preset')selectedField=1;
|
|
68
|
+
}
|
|
16
69
|
else if(kind==='install'&&field===1){const value=await ui.menu('Harness output language',['EN','KR'],{backRow:true,selected:settings.language==='ko'?1:0});if(value!==null)settings.language=['en','ko'][value];}
|
|
17
|
-
else if(kind==='install'&&(field===2||field===3)){const key=field===2?'maxTotalSubagents':'maxConcurrentSubagents';let raw=String(settings[key]);while(true){raw=await ui.text(field===2?'Maximum total subagents':'Maximum concurrent subagents',raw);if(raw===null)break;let value;try{value=parsePositiveLimit(raw);}catch{await ui.page('Invalid limit',['Enter a positive safe ASCII decimal integer.']);continue;}settings[key]=value;
|
|
70
|
+
else if(kind==='install'&&(field===2||field===3)){const key=field===2?'maxTotalSubagents':'maxConcurrentSubagents';let raw=String(settings[key]);while(true){raw=await ui.text(field===2?'Maximum total subagents':'Maximum concurrent subagents',raw);if(raw===null)break;let value;try{value=parsePositiveLimit(raw);}catch{await ui.page('Invalid limit',['Enter a positive safe ASCII decimal integer.']);continue;}settings[key]=value;break;}}
|
|
18
71
|
else if(kind==='install'&&field===4)settings.skipGlobal=!settings.skipGlobal;
|
|
19
|
-
else{if(
|
|
72
|
+
else{let catalog;if(settings.preset==='custom'||settings.customEdited){catalog=await catalogFor(target);if(!catalog)continue;try{assertCatalogRoles({platform:target,roles:settings.customRoles,catalog});}catch{await ui.page('Custom preset unavailable',['Select an available model and effort for each role.']);continue;}}return prepare({kind,...setupOptions(draft,target,catalog,kind)});}
|
|
20
73
|
}
|
|
21
74
|
}
|
|
22
75
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yunhozz/arc",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
"scripts/check-installer-parity.mjs",
|
|
32
32
|
"scripts/pack-release.mjs",
|
|
33
33
|
"scripts/postinstall.mjs",
|
|
34
|
-
"scripts/probe-runtime-hooks.mjs",
|
|
35
34
|
"scripts/update-manifest.mjs",
|
|
36
35
|
"scripts/update-role-policy.mjs",
|
|
37
36
|
"scripts/update-runtime-assets.mjs",
|