@rockysoft/opencode-hybrid-workflow 0.3.2-beta.2 → 0.3.2-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -22
- package/core/workers.mjs +2 -2
- package/docs/images/19-cli-model.png +0 -0
- package/docs/images/20-cli-effort.png +0 -0
- package/docs/images/21-claude-model.png +0 -0
- package/lib/cli-models.mjs +77 -0
- package/lib/select-reviewers.mjs +11 -4
- package/lib/settings-rpc.mjs +1 -0
- package/lib/settings-service.mjs +6 -0
- package/package.json +12 -3
- package/sidebar/settings.mjs +19 -2
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ One plugin. One or two reviewers. Configure everything inside OpenCode.
|
|
|
12
12
|
opencode plugin add @rockysoft/opencode-hybrid-workflow@beta
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-

|
|
16
16
|
|
|
17
17
|
Open `opencode` in your project. The setup wizard opens automatically.
|
|
18
18
|
|
|
@@ -20,28 +20,28 @@ Open `opencode` in your project. The setup wizard opens automatically.
|
|
|
20
20
|
|
|
21
21
|
Select **one** reviewer or **two** for independent planning and review.
|
|
22
22
|
|
|
23
|
-

|
|
24
24
|
|
|
25
25
|
## 3. Pick a connection, model and effort
|
|
26
26
|
|
|
27
27
|
Use any connected OpenCode provider, or your Codex/Claude CLI login. Each reviewer can use a different connection.
|
|
28
28
|
|
|
29
|
-

|
|
30
30
|
|
|
31
31
|
<details>
|
|
32
32
|
<summary><strong>OpenCode provider — see every step</strong></summary>
|
|
33
33
|
|
|
34
34
|
**Choose your provider.**
|
|
35
35
|
|
|
36
|
-

|
|
37
37
|
|
|
38
38
|
**Choose a model.** Type to search. Models shown depend on your connections.
|
|
39
39
|
|
|
40
|
-

|
|
41
41
|
|
|
42
42
|
**Choose reasoning effort.** Options come from the selected model.
|
|
43
43
|
|
|
44
|
-

|
|
45
45
|
|
|
46
46
|
Repeat for reviewer 2 if enabled.
|
|
47
47
|
|
|
@@ -52,15 +52,19 @@ Repeat for reviewer 2 if enabled.
|
|
|
52
52
|
|
|
53
53
|
**Choose Codex CLI or Claude Code CLI.** Install and sign into that CLI first.
|
|
54
54
|
|
|
55
|
-

|
|
56
56
|
|
|
57
|
-
**
|
|
57
|
+
**Select a model by name.** Models and effort levels come from your installed CLI. Custom IDs are an advanced option.
|
|
58
58
|
|
|
59
|
-

|
|
60
|
+
|
|
61
|
+
Claude Code shows its own model list:
|
|
62
|
+
|
|
63
|
+

|
|
60
64
|
|
|
61
65
|
**Choose effort.** Use a level supported by that CLI and model.
|
|
62
66
|
|
|
63
|
-

|
|
64
68
|
|
|
65
69
|
</details>
|
|
66
70
|
|
|
@@ -69,7 +73,7 @@ Repeat for reviewer 2 if enabled.
|
|
|
69
73
|
|
|
70
74
|
Cancel the wizard, choose **Connect provider**, and complete OpenCode's sign-in flow. Then reopen **Complete setup**.
|
|
71
75
|
|
|
72
|
-

|
|
73
77
|
|
|
74
78
|
</details>
|
|
75
79
|
|
|
@@ -77,17 +81,17 @@ Cancel the wizard, choose **Connect provider**, and complete OpenCode's sign-in
|
|
|
77
81
|
|
|
78
82
|
With two reviewers, choose who leads planning and design.
|
|
79
83
|
|
|
80
|
-

|
|
81
85
|
|
|
82
86
|
Confirm your choices. They apply across projects; active tasks keep their assigned reviewers.
|
|
83
87
|
|
|
84
|
-

|
|
85
89
|
|
|
86
90
|
## 5. Choose who builds
|
|
87
91
|
|
|
88
92
|
Open **Implementation model** and select your coding model. It follows the approved plan and design.
|
|
89
93
|
|
|
90
|
-

|
|
91
95
|
|
|
92
96
|
## 6. Start working
|
|
93
97
|
|
|
@@ -97,12 +101,12 @@ Continue when at least one reviewer passes and no reviewer has unresolved medium
|
|
|
97
101
|
|
|
98
102
|
Type **`/hybrid`** or click **Hybrid Workflow settings** whenever you want to change the setup.
|
|
99
103
|
|
|
100
|
-

|
|
101
105
|
|
|
102
106
|
<details>
|
|
103
107
|
<summary><strong>Reopen settings with /hybrid</strong></summary>
|
|
104
108
|
|
|
105
|
-

|
|
106
110
|
|
|
107
111
|
</details>
|
|
108
112
|
|
|
@@ -113,15 +117,15 @@ Type **`/hybrid`** or click **Hybrid Workflow settings** whenever you want to ch
|
|
|
113
117
|
|
|
114
118
|
Choose **Reviewer browser → Install browser support**.
|
|
115
119
|
|
|
116
|
-

|
|
117
121
|
|
|
118
122
|
Confirm installation of Playwright and Firefox.
|
|
119
123
|
|
|
120
|
-

|
|
121
125
|
|
|
122
126
|
Each reviewer gets an isolated browser. Run your app and provide its URL; use test data.
|
|
123
127
|
|
|
124
|
-

|
|
125
129
|
|
|
126
130
|
</details>
|
|
127
131
|
|
|
@@ -130,7 +134,7 @@ Each reviewer gets an isolated browser. Run your app and provide its URL; use te
|
|
|
130
134
|
|
|
131
135
|
Open **Tools and dependencies** and select what you need.
|
|
132
136
|
|
|
133
|
-

|
|
134
138
|
|
|
135
139
|
</details>
|
|
136
140
|
|
|
@@ -139,7 +143,7 @@ Open **Tools and dependencies** and select what you need.
|
|
|
139
143
|
|
|
140
144
|
Choose **Run diagnostics**. Select any row for details. This does not call a model.
|
|
141
145
|
|
|
142
|
-

|
|
143
147
|
|
|
144
148
|
</details>
|
|
145
149
|
|
|
@@ -160,4 +164,4 @@ Remove the old plugin entries from `opencode.json` and `cli.json` before adding
|
|
|
160
164
|
|
|
161
165
|
</details>
|
|
162
166
|
|
|
163
|
-
[Advanced configuration](https://cdn.jsdelivr.net/npm/@rockysoft/opencode-hybrid-workflow@0.3.2-beta.
|
|
167
|
+
[Advanced configuration](https://cdn.jsdelivr.net/npm/@rockysoft/opencode-hybrid-workflow@0.3.2-beta.3/docs/configuration.md) · [MIT license](https://cdn.jsdelivr.net/npm/@rockysoft/opencode-hybrid-workflow@0.3.2-beta.3/LICENSE) · [Third-party notices](https://cdn.jsdelivr.net/npm/@rockysoft/opencode-hybrid-workflow@0.3.2-beta.3/THIRD_PARTY_NOTICES.md)
|
package/core/workers.mjs
CHANGED
|
@@ -29,7 +29,7 @@ export function workerArgs(who, config, folder, images = [], browser = null) {
|
|
|
29
29
|
// implementation must be explicit too. Without it shell reads are denied.
|
|
30
30
|
...(process.platform === 'win32' ? ['-c', 'windows.sandbox="elevated"'] : []),
|
|
31
31
|
'-c', 'approval_policy="never"',
|
|
32
|
-
'-c', `model_reasoning_effort="${lane.effort??'high'}"
|
|
32
|
+
...(lane.effort==='default'?[]:['-c', `model_reasoning_effort="${lane.effort??'high'}"`]), '-c', 'web_search="live"',
|
|
33
33
|
...(browser ? ['-c',`mcp_servers.review_browser.command=${JSON.stringify(browser.command)}`,
|
|
34
34
|
'-c',`mcp_servers.review_browser.args=${JSON.stringify(browser.args)}`,
|
|
35
35
|
'-c','mcp_servers.review_browser.startup_timeout_sec=60',
|
|
@@ -41,7 +41,7 @@ export function workerArgs(who, config, folder, images = [], browser = null) {
|
|
|
41
41
|
return ['-p', ...(browser?['--setting-sources','','--disable-slash-commands','--settings','{"disableAllHooks":true,"enabledPlugins":{}}']:['--safe-mode']), '--restricted', '--strict-mcp-config', '--mcp-config', JSON.stringify({mcpServers:browser?{review_browser:browser}:{}}),
|
|
42
42
|
'--no-chrome', '--no-session-persistence', '--permission-mode', 'dontAsk', '--permission-prompts', 'none',
|
|
43
43
|
'--tools', 'Read,Glob,Grep,WebSearch,WebFetch', '--allowedTools', ['Read','Glob','Grep','WebSearch','WebFetch',...(browser?browserTools.map(name=>'mcp__review_browser__'+name):[])].join(','),
|
|
44
|
-
'--model', lane.model, '--effort',
|
|
44
|
+
'--model', lane.model, ...(lane.effort==='default'?[]:['--effort',lane.effort??'high']), '--output-format', 'stream-json', '--verbose',
|
|
45
45
|
'--json-schema', JSON.stringify(resultSchema)];
|
|
46
46
|
}
|
|
47
47
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {spawn,spawnSync} from 'node:child_process';
|
|
2
|
+
import {mkdtempSync,rmSync} from 'node:fs';
|
|
3
|
+
import {tmpdir} from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {commandSpec,nativeCodex} from './commands.mjs';
|
|
6
|
+
import {workerEnvironment} from '../core/workers.mjs';
|
|
7
|
+
|
|
8
|
+
// Only initialize and inspect catalogs. Never start a thread, send a user
|
|
9
|
+
// message, read credentials, or return account metadata from CLI responses.
|
|
10
|
+
export function normalizeCLIModels(adapter,rows){
|
|
11
|
+
if(!Array.isArray(rows))throw Error('The CLI did not return a model catalog. Update the CLI and retry.');
|
|
12
|
+
const seen=new Set();
|
|
13
|
+
return rows.filter(m=>m&&!m.hidden).map(m=>{
|
|
14
|
+
const codex=adapter==='codex-cli';
|
|
15
|
+
const id=codex?(m.model??m.id):(m.value==='default'?m.resolvedModel:m.value);
|
|
16
|
+
const efforts=codex?m.supportedReasoningEfforts?.map(e=>e.reasoningEffort):m.supportedEffortLevels;
|
|
17
|
+
return {id,name:codex?(m.displayName??id):(m.displayName??id),description:typeof m.description==='string'?m.description:'',
|
|
18
|
+
efforts:[...new Set((efforts??[]).filter(e=>typeof e==='string'&&/^[a-zA-Z0-9_-]+$/.test(e)))],
|
|
19
|
+
...(codex&&typeof m.defaultReasoningEffort==='string'?{defaultEffort:m.defaultReasoningEffort}:{}),isDefault:codex?!!m.isDefault:m.value==='default'};
|
|
20
|
+
}).filter(m=>typeof m.id==='string'&&m.id.trim()&&!seen.has(m.id)&&seen.add(m.id));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function discoverCLIModels(adapter,{executable,signal,timeoutMs=30000}={}){
|
|
24
|
+
if(!['codex-cli','claude-cli'].includes(adapter))throw Error('Unknown CLI connection.');
|
|
25
|
+
if(signal?.aborted)throw Error('Model discovery cancelled.');
|
|
26
|
+
const codex=adapter==='codex-cli';
|
|
27
|
+
const spec=commandSpec(codex?nativeCodex(executable??'codex'):(executable??'claude'));
|
|
28
|
+
const folder=mkdtempSync(path.join(tmpdir(),'hybrid-models-'));
|
|
29
|
+
const args=codex?['app-server','-c','model_provider="openai"','--listen','stdio://']:['-p','--safe-mode','--restricted','--strict-mcp-config','--mcp-config','{"mcpServers":{}}','--no-chrome','--no-session-persistence','--tools','','--input-format','stream-json','--output-format','stream-json','--verbose'];
|
|
30
|
+
try{return await new Promise((resolve,reject)=>{
|
|
31
|
+
const child=spawn(spec.executable,[...spec.args,...args],{cwd:folder,env:workerEnvironment(),shell:false,windowsHide:true,detached:process.platform!=='win32',stdio:['pipe','pipe','pipe']});
|
|
32
|
+
let done=false,buffer='',rows=[],nextId=2,expected=1,timer;const cursors=new Set();
|
|
33
|
+
const finish=(error,value)=>{
|
|
34
|
+
if(done)return;done=true;clearTimeout(timer);signal?.removeEventListener('abort',abort);
|
|
35
|
+
child.stdin.destroy();child.stdout.destroy();child.stderr.destroy();
|
|
36
|
+
if(child.pid&&child.exitCode===null){
|
|
37
|
+
if(process.platform==='win32')spawnSync(path.join(process.env.SystemRoot??'C:/Windows','System32/taskkill.exe'),['/PID',String(child.pid),'/T','/F'],{windowsHide:true,timeout:5000,stdio:'ignore'});
|
|
38
|
+
else try{process.kill(-child.pid,'SIGKILL');}catch{child.kill('SIGKILL');}
|
|
39
|
+
}
|
|
40
|
+
child.unref();error?reject(error):resolve(value);
|
|
41
|
+
};
|
|
42
|
+
const abort=()=>finish(Error('Model discovery cancelled.'));
|
|
43
|
+
const send=value=>{if(!done)child.stdin.write(JSON.stringify(value)+'\n');};
|
|
44
|
+
const list=cursor=>{expected=nextId++;send({id:expected,method:'model/list',params:{includeHidden:false,...(cursor?{cursor}:{})}});};
|
|
45
|
+
child.on('error',()=>finish(Error('Could not start the CLI. Check its installation and retry.')));
|
|
46
|
+
child.stdin.on('error',()=>finish(Error('The CLI closed model discovery. Update the CLI and retry.')));
|
|
47
|
+
child.on('close',()=>finish(Error('The CLI exited without a model catalog. Check its installation and login.')));
|
|
48
|
+
child.stderr.resume();
|
|
49
|
+
child.stdout.setEncoding('utf8');child.stdout.on('data',chunk=>{
|
|
50
|
+
buffer+=chunk;
|
|
51
|
+
if(buffer.length>8*1024*1024)return finish(Error('The CLI returned an invalid model response.'));
|
|
52
|
+
let pos;while(!done&&(pos=buffer.indexOf('\n'))>=0){
|
|
53
|
+
const line=buffer.slice(0,pos);buffer=buffer.slice(pos+1);let message;try{message=JSON.parse(line);}catch{continue;}
|
|
54
|
+
try{
|
|
55
|
+
if(codex){
|
|
56
|
+
if(message.id!==expected)continue;
|
|
57
|
+
if(message.error)throw Error('Codex could not list models. Check its login/version and retry.');
|
|
58
|
+
if(expected===1){send({method:'initialized',params:{}});list();continue;}
|
|
59
|
+
if(!Array.isArray(message.result?.data))throw Error('Codex returned an invalid model catalog.');
|
|
60
|
+
rows.push(...message.result.data);
|
|
61
|
+
const cursor=message.result.nextCursor;
|
|
62
|
+
if(cursor){if(cursors.has(cursor))throw Error('Codex repeated a model catalog page.');cursors.add(cursor);list(cursor);continue;}
|
|
63
|
+
}else{
|
|
64
|
+
if(message.type!=='control_response'||message.response?.request_id!=='hybrid-models')continue;
|
|
65
|
+
if(message.response.subtype!=='success')throw Error('Claude could not list models. Check its login/version and retry.');
|
|
66
|
+
rows=message.response.response?.models;
|
|
67
|
+
}
|
|
68
|
+
const models=normalizeCLIModels(adapter,rows);if(!models.length)throw Error('No models were returned. Check the CLI login and retry.');
|
|
69
|
+
finish(null,{models,source:codex?'Codex CLI catalog':'Claude Code CLI catalog'});
|
|
70
|
+
}catch(error){finish(error);}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
timer=setTimeout(()=>finish(Error('Loading CLI models timed out. Check the CLI login and retry.')),timeoutMs);
|
|
74
|
+
signal?.addEventListener('abort',abort,{once:true});if(signal?.aborted){abort();return;}
|
|
75
|
+
send(codex?{id:1,method:'initialize',params:{clientInfo:{name:'opencode_hybrid',title:'Hybrid model picker',version:'1.0.0'}}}:{type:'control_request',request_id:'hybrid-models',request:{subtype:'initialize',hooks:{}}});
|
|
76
|
+
});}finally{rmSync(folder,{recursive:true,force:true});}
|
|
77
|
+
}
|
package/lib/select-reviewers.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {spawnSync} from 'node:child_process';
|
|
2
2
|
import {createInterface} from 'node:readline/promises';
|
|
3
3
|
import {commandSpec,nativeCodex} from './commands.mjs';
|
|
4
|
+
import {discoverCLIModels} from './cli-models.mjs';
|
|
4
5
|
|
|
5
6
|
export function modelCatalog(directory=process.cwd()) {
|
|
6
7
|
const spec=commandSpec('opencode');
|
|
@@ -45,10 +46,16 @@ export async function selectInteractively(config) {
|
|
|
45
46
|
const variant=await menu('Reasoning variant',[{label:'Provider default',value:undefined},...(model.variants??[]).map(v=>({label:v.id,value:v.id}))]);
|
|
46
47
|
reviewers[who]=selection(`opencode:${provider}/${model.id}`,{catalog,variant});
|
|
47
48
|
}else {
|
|
48
|
-
const old=config.reviewers?.[who]
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
const old=config.reviewers?.[who];let models=[];
|
|
50
|
+
try{({models}=await discoverCLIModels(adapter,{executable:old?.adapter===adapter?old.executable:undefined}));}
|
|
51
|
+
catch(error){console.log(error.message);}
|
|
52
|
+
const items=models.map(m=>({label:m.name,value:m.id}));
|
|
53
|
+
if(old?.adapter===adapter&&!models.some(m=>m.id===old.model))items.push({label:`Keep current model (${old.model})`,value:old.model});
|
|
54
|
+
items.push({label:'Enter custom model ID (advanced)',value:'__custom'});
|
|
55
|
+
let model=await menu('CLI model',items);
|
|
56
|
+
if(model==='__custom'){model=(await rl.question('Exact model ID or CLI alias: ')).trim();if(!model)throw Error('Model is required.');}
|
|
57
|
+
const chosen=models.find(m=>m.id===model);
|
|
58
|
+
const effort=await menu('Reasoning effort',[{label:'CLI default',value:'default'},...(chosen?.efforts??['low','medium','high','xhigh','max']).map(v=>({label:v,value:v}))]);
|
|
52
59
|
reviewers[who]=selection(`${adapter}:${model}`,{previous:old??{},variant:effort});
|
|
53
60
|
}
|
|
54
61
|
}
|
package/lib/settings-rpc.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
const object={type:'object',additionalProperties:true};
|
|
3
3
|
export const SettingsRPC={id:'hybrid.settings',methods:{
|
|
4
4
|
get:{input:{type:'object',additionalProperties:false},output:object},
|
|
5
|
+
cliModels:{input:{type:'object',additionalProperties:false,properties:{adapter:{enum:['codex-cli','claude-cli']},who:{enum:['astra','opus']}},required:['adapter','who']},output:object},
|
|
5
6
|
save:{input:{type:'object',additionalProperties:false,properties:{revision:{type:'string'},reviewers:object,designAuthor:{enum:['astra','opus']}},required:['revision','reviewers','designAuthor']},output:object},
|
|
6
7
|
browser:{input:{type:'object',additionalProperties:false,properties:{action:{enum:['enable','disable','install']}},required:['action']},output:object},
|
|
7
8
|
diagnostics:{input:{type:'object',additionalProperties:false},output:object},
|
package/lib/settings-service.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import {commandSpec} from './commands.mjs';
|
|
|
8
8
|
import {runProcess,requireSubscription} from '../core/workers.mjs';
|
|
9
9
|
import {NativeBrowser} from './native-browser.mjs';
|
|
10
10
|
import {SettingsRPC} from './settings-rpc.mjs';
|
|
11
|
+
import {discoverCLIModels} from './cli-models.mjs';
|
|
11
12
|
const writes=new Set();
|
|
12
13
|
const unwrap=x=>x?.data??x;
|
|
13
14
|
export function revision(file){return createHash('sha256').update(existsSync(file)?readFileSync(file):'missing').digest('hex');}
|
|
@@ -73,6 +74,11 @@ export function createSettingsService(ctx,options={}){
|
|
|
73
74
|
return folder;
|
|
74
75
|
}
|
|
75
76
|
return {get,diagnostics,
|
|
77
|
+
cliModels:(input,context={})=>{
|
|
78
|
+
if(!['astra','opus'].includes(input.who)||!['codex-cli','claude-cli'].includes(input.adapter))throw Error('Unknown reviewer connection.');
|
|
79
|
+
const previous=load().reviewers?.[input.who];
|
|
80
|
+
return discoverCLIModels(input.adapter,{executable:previous?.adapter===input.adapter?previous.executable:undefined,signal:context.signal});
|
|
81
|
+
},
|
|
76
82
|
save:input=>exclusive(async()=>{
|
|
77
83
|
if(input.revision!==revision(file))throw Error('Settings changed in another window. Reopen settings and try again.');
|
|
78
84
|
const patch=validateChoices(input,await catalog(),load());
|
package/package.json
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rockysoft/opencode-hybrid-workflow",
|
|
3
|
-
"version": "0.3.2-beta.
|
|
3
|
+
"version": "0.3.2-beta.3",
|
|
4
4
|
"description": "Portable configurable-reviewer planning, design, implementation checkpoints and usage sidebar for OpenCode 2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "RockySoft",
|
|
8
|
-
"keywords": [
|
|
9
|
-
|
|
8
|
+
"keywords": [
|
|
9
|
+
"opencode",
|
|
10
|
+
"opencode-plugin",
|
|
11
|
+
"agents",
|
|
12
|
+
"code-review",
|
|
13
|
+
"workflow"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public",
|
|
17
|
+
"tag": "beta"
|
|
18
|
+
},
|
|
10
19
|
"exports": {
|
|
11
20
|
".": "./index.js",
|
|
12
21
|
"./tui": "./tui.js"
|
package/sidebar/settings.mjs
CHANGED
|
@@ -25,8 +25,25 @@ export function createSettingsUI(context,onSaved=()=>{}){
|
|
|
25
25
|
return {adapter:'opencode',providerID:provider,model,...(variant==='__default'?{}:{variant})};
|
|
26
26
|
}
|
|
27
27
|
const adapter=await select('External CLI',[{title:'Codex CLI',value:'codex-cli'},{title:'Claude Code CLI',value:'claude-cli'}],current?.adapter);if(!adapter)return;
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
let catalog;
|
|
29
|
+
while(!catalog){
|
|
30
|
+
context.ui.toast.show({message:'Loading models from '+(adapter==='codex-cli'?'Codex':'Claude Code')+'…',variant:'info'});
|
|
31
|
+
try{catalog=await invoke('cliModels',{adapter,who});}
|
|
32
|
+
catch(error){
|
|
33
|
+
const action=await select('Could not load CLI models',[{title:'Retry',value:'retry',description:error.message},{title:'Enter custom model ID (advanced)',value:'custom'},{title:'Back to connections',value:'back'}]);
|
|
34
|
+
if(action==='retry')continue;if(action==='back')return reviewer(who,state,current);if(action!=='custom')return;catalog={models:[]};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const existing=current?.adapter===adapter?current.model:undefined;
|
|
38
|
+
const options=catalog.models.map(m=>({title:m.name,value:m.id,description:m.description||m.id}));
|
|
39
|
+
if(existing&&!catalog.models.some(m=>m.id===existing))options.push({title:'Keep current model',value:existing,description:existing+' · not in current CLI catalog'});
|
|
40
|
+
options.push({title:'Enter custom model ID (advanced)',value:'__custom'});
|
|
41
|
+
let model=await select('CLI model',options,existing??catalog.models.find(m=>m.isDefault)?.id);if(!model)return;
|
|
42
|
+
if(model==='__custom')model=await context.ui.dialog.prompt({title:'Custom model ID',placeholder:'Exact model ID or CLI alias',value:existing??''});if(!model?.trim())return;
|
|
43
|
+
const chosen=catalog.models.find(m=>m.id===model);
|
|
44
|
+
const efforts=chosen?.efforts??['low','medium','high','xhigh','max'];
|
|
45
|
+
const preferred=current?.adapter===adapter&&(current.effort==='default'||efforts.includes(current.effort))?current.effort:efforts.includes('high')?'high':'default';
|
|
46
|
+
const effort=await select('CLI reasoning effort',[{title:'CLI default',value:'default'},...efforts.map(v=>({title:v,value:v}))],preferred);if(!effort)return;
|
|
30
47
|
return {adapter,model:model.trim(),effort};
|
|
31
48
|
}
|
|
32
49
|
async function wizard(state){
|