agent-policy-kit 0.4.0 → 0.5.1
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/AGENT_INSTALL.md +7 -7
- package/CHANGELOG.md +56 -0
- package/README.md +11 -1
- package/docs/design/2026-11-01-grok-build-support.md +6 -0
- package/docs/guide/commands.md +4 -4
- package/docs/guide/configurator.md +35 -8
- package/docs/guide/getting-started.md +5 -3
- package/docs/guide/preferences.md +6 -6
- package/docs/guide/updates-and-removal.md +9 -9
- package/docs/maintainers/verification.md +30 -0
- package/docs/reference/assistants.md +51 -3
- package/docs/reference/model-catalog.md +31 -15
- package/docs/reference/presets.md +7 -5
- package/docs/reference/roles.md +7 -5
- package/docs/research/2026-11-01-model-review.md +64 -33
- package/models/catalog.json +51 -7
- package/package.json +2 -2
- package/policies/balanced.json +1 -9
- package/schema/policy.schema.json +8 -1
- package/site/index.html +86 -37
- package/src/explicit.mjs +8 -6
- package/src/inspect.mjs +16 -7
- package/src/native.mjs +8 -4
- package/src/output.mjs +3 -1
- package/src/share.mjs +1 -1
package/src/explicit.mjs
CHANGED
|
@@ -11,7 +11,8 @@ function explicitList(value,limit){
|
|
|
11
11
|
return list;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
// Grok
|
|
14
|
+
// Grok accepts 'role=model' (native default, as in 0.4.0), 'role=model:<effort>' and 'role=model:native'.
|
|
15
|
+
// A model id may contain colons, so only a trailing effort name or 'native' is read as the effort.
|
|
15
16
|
const grokEffortSuffix=model=>{const colon=model.lastIndexOf(':');return colon>=0&&['native',...EXPLICIT_EFFORTS].includes(model.slice(colon+1));};
|
|
16
17
|
export function parseExplicitOptions(options,roleIds){
|
|
17
18
|
if(!options||typeof options!=='object'||Array.isArray(options))invalidExplicit();
|
|
@@ -32,8 +33,8 @@ export function parseExplicitOptions(options,roleIds){
|
|
|
32
33
|
if(at<1||!roles.includes(role)||Object.hasOwn(choices,role))invalidExplicit();
|
|
33
34
|
let model=value,effort=null;
|
|
34
35
|
if(client==='gemini'){if(!['pro','flash','flash_lite','inherit'].includes(model))invalidExplicit();}
|
|
35
|
-
|
|
36
|
-
else{
|
|
36
|
+
// A Grok value without an effort suffix is the whole model id.
|
|
37
|
+
else if(client!=='grok'||grokEffortSuffix(value)){
|
|
37
38
|
const colon=value.lastIndexOf(':');if(colon<1)invalidExplicit();
|
|
38
39
|
model=value.slice(0,colon);const level=value.slice(colon+1);
|
|
39
40
|
if(level!=='native'&&!EXPLICIT_EFFORTS.includes(level))invalidExplicit();effort=level==='native'?null:level;
|
|
@@ -68,9 +69,10 @@ export function explicitOptions(config,roleCatalog){
|
|
|
68
69
|
options[flag]=roles.map(role=>{
|
|
69
70
|
const selected=preferences.roles?.[role]??preferences[kinds.get(role)];
|
|
70
71
|
if(!selected||typeof selected.model!=='string'||!EXPLICIT_MODEL.test(selected.model)||(selected.effort!==null&&!EXPLICIT_EFFORTS.includes(selected.effort)))invalidExplicit();
|
|
71
|
-
if(
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
if(client==='gemini'&&selected.effort!==null)invalidExplicit();
|
|
73
|
+
// A Grok native default keeps the 0.4.0 form unless the model id itself ends like an effort suffix.
|
|
74
|
+
const plain=client==='gemini'||(client==='grok'&&selected.effort===null&&!grokEffortSuffix(selected.model));
|
|
75
|
+
return `${role}=${selected.model}${plain?'':':'+(selected.effort??'native')}`;
|
|
74
76
|
}).join(',');
|
|
75
77
|
if(preferences.maxConcurrent!==undefined)options[flag+'-max-concurrent']=String(preferences.maxConcurrent);
|
|
76
78
|
if(!Array.isArray(preferences.avoidModels)||preferences.avoidModels.some(model=>typeof model!=='string'||!EXPLICIT_MODEL.test(model)))invalidExplicit();
|
package/src/inspect.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import {constants} from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import {parseToml} from './toml.mjs';
|
|
5
|
-
import {CLIENTS} from './policy.mjs';
|
|
5
|
+
import {CLIENTS,NATIVE_EFFORTS} from './policy.mjs';
|
|
6
6
|
import {inspectConfiguration} from './install.mjs';
|
|
7
7
|
|
|
8
8
|
const ROOTS={chatgpt:'.codex',claude:'.claude',kimi:'.kimi-code',gemini:'.gemini',grok:'.grok'};
|
|
@@ -43,7 +43,7 @@ async function readFile(file,budget){
|
|
|
43
43
|
finally{await handle?.close();}
|
|
44
44
|
}
|
|
45
45
|
function warning(client,error){
|
|
46
|
-
const allowed=['UNSAFE_FILE','INSPECTION_LIMIT','CONFIG_CHANGED_DURING_READ','UNSUPPORTED_NATIVE_SETTING','UNSUPPORTED_ROLE_FORMAT','UNSUPPORTED_ROLE_EFFORT','INVALID_TOML','INVALID_JSON','EACCES','EPERM','NATIVE_CAP_CONFLICT','EXTERNAL_ROLE_REFERENCES_NOT_READ'];
|
|
46
|
+
const allowed=['UNSAFE_FILE','INSPECTION_LIMIT','CONFIG_CHANGED_DURING_READ','UNSUPPORTED_NATIVE_SETTING','UNSUPPORTED_ROLE_FORMAT','UNSUPPORTED_ROLE_EFFORT','UNSUPPORTED_ROLE_CAPABILITY','INVALID_TOML','INVALID_JSON','EACCES','EPERM','NATIVE_CAP_CONFLICT','EXTERNAL_ROLE_REFERENCES_NOT_READ'];
|
|
47
47
|
const code=allowed.includes(error?.code)?error.code:'NATIVE_READ_FAILED';
|
|
48
48
|
if(!client.warnings.includes(code))client.warnings.push(code);
|
|
49
49
|
client.status='attention';
|
|
@@ -56,7 +56,9 @@ function scalar(value){
|
|
|
56
56
|
if(!match)throw failure('UNSUPPORTED_ROLE_FORMAT');
|
|
57
57
|
return match[1];
|
|
58
58
|
}
|
|
59
|
-
|
|
59
|
+
// Values that Grok Build 1.0.70 accepts for capabilityMode (serde kebab-case names and aliases of SubagentCapabilityMode).
|
|
60
|
+
const GROK_CAPABILITY_MODES=['read-only','readonly','readOnly','read_only','ReadOnly','read-write','readwrite','readWrite','read_write','ReadWrite','execute','Execute','EXECUTE','all','All','ALL'];
|
|
61
|
+
function frontmatter(text,id){
|
|
60
62
|
const match=text.replace(/^\uFEFF/,'').match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
61
63
|
if(!match)throw failure('UNSUPPORTED_ROLE_FORMAT');
|
|
62
64
|
const fields={};
|
|
@@ -64,13 +66,17 @@ function frontmatter(text){
|
|
|
64
66
|
// YAML merges, quoted keys and flow mappings can supply model fields that
|
|
65
67
|
// this deliberately small scalar reader cannot resolve safely.
|
|
66
68
|
if(/^(?:<<\s*:|["'{\[])/.test(line))throw failure('UNSUPPORTED_ROLE_FORMAT');
|
|
67
|
-
const field=line.match(
|
|
69
|
+
const field=line.match(id==='grok'?/^(name|model|effort|subagent|capabilityMode)[ \t]*:\s*(.*)$/:/^(name|model|effort|subagent)[ \t]*:\s*(.*)$/);
|
|
68
70
|
if(!field)continue;
|
|
69
71
|
if(Object.hasOwn(fields,field[1]))throw failure('UNSUPPORTED_ROLE_FORMAT');
|
|
70
72
|
if(field[1]==='subagent'){
|
|
71
73
|
const boolean=field[2].trim().match(/^(true|false)(?:\s+#.*)?$/);
|
|
72
74
|
if(!boolean)throw failure('UNSUPPORTED_ROLE_FORMAT');
|
|
73
75
|
fields.subagent=boolean[1]==='true';
|
|
76
|
+
}else if(field[1]==='capabilityMode'){
|
|
77
|
+
// Keep a value Grok Build would reject without echoing it; it only selects a warning.
|
|
78
|
+
const raw=field[2].trim().replace(/\s+#.*$/,''),quoted=raw.match(/^(["'])(.*)\1$/),value=quoted?quoted[2]:raw;
|
|
79
|
+
fields.capabilityMode=GROK_CAPABILITY_MODES.includes(value)?value:null;
|
|
74
80
|
}else fields[field[1]]=scalar(field[2]);
|
|
75
81
|
}
|
|
76
82
|
if(!fields.name)throw failure('UNSUPPORTED_ROLE_FORMAT');
|
|
@@ -139,7 +145,7 @@ async function rolesIn(directory,client,id,budget,models,depth=0){
|
|
|
139
145
|
}
|
|
140
146
|
if(!entry.name.endsWith(id==='chatgpt'?'.toml':'.md'))continue;
|
|
141
147
|
const body=await readFile(file,budget);if(body===null)throw failure('CONFIG_CHANGED_DURING_READ');
|
|
142
|
-
const fields=id==='chatgpt'?parseToml(body):frontmatter(body);
|
|
148
|
+
const fields=id==='chatgpt'?parseToml(body):frontmatter(body,id);
|
|
143
149
|
const role={name:safeString(fields.name,namePattern),model:optionalString(fields.model),effort:optionalString(id==='chatgpt'?fields.model_reasoning_effort:fields.effort)};
|
|
144
150
|
if(id==='kimi'){
|
|
145
151
|
// APK's Markdown role files contain explicit routing guidance, not a
|
|
@@ -151,9 +157,12 @@ async function rolesIn(directory,client,id,budget,models,depth=0){
|
|
|
151
157
|
}
|
|
152
158
|
}
|
|
153
159
|
if(id==='grok'){
|
|
154
|
-
// The native per-subagent model override lives in config.toml, not in the agent file.
|
|
160
|
+
// The native per-subagent model override lives in config.toml, not in the agent file; the effort lives in the agent frontmatter.
|
|
155
161
|
role.model=models.get(role.name)??role.model;
|
|
156
|
-
|
|
162
|
+
// Grok Build drops an agent whose frontmatter effort is not a level it accepts.
|
|
163
|
+
if(role.effort!==null&&!NATIVE_EFFORTS.grok.includes(role.effort))warning(client,failure('UNSUPPORTED_ROLE_EFFORT'));
|
|
164
|
+
// It also drops an agent whose capabilityMode is not a mode it accepts. The snake_case key is not a Grok field.
|
|
165
|
+
if(fields.capabilityMode===null)warning(client,failure('UNSUPPORTED_ROLE_CAPABILITY'));
|
|
157
166
|
}
|
|
158
167
|
if(id==='gemini'){
|
|
159
168
|
if(role.model!==null&&!['pro','flash','flash_lite','inherit'].includes(role.model))throw failure('UNSUPPORTED_NATIVE_SETTING');
|
package/src/native.mjs
CHANGED
|
@@ -24,9 +24,13 @@ export function nativeRoles(client,config,selectedRoles=ROLE_IDS) {
|
|
|
24
24
|
const {description,prompt}=ROLE_DEFINITIONS.find(value=>value.id===role);
|
|
25
25
|
if(client==='chatgpt') return Object.entries({name,description,model:selected.model,...(selected.effort===null?{}:{model_reasoning_effort:selected.effort}),developer_instructions:prompt}).map(([k,v])=>`${k} = ${JSON.stringify(v)}\n`).join('');
|
|
26
26
|
const fields=[`name: ${name}`,`description: ${JSON.stringify(description)}`];
|
|
27
|
-
// Grok selects the model from config.toml [subagents.models]
|
|
27
|
+
// Grok selects the model from config.toml [subagents.models] and reads a role effort from the agent frontmatter;
|
|
28
|
+
// a null effort writes nothing, so the role keeps its model's native default. Grok Build reads camelCase agent fields:
|
|
29
|
+
// capabilityMode read-only removes the edit, write, delete and shell tools when a subagent of this type starts.
|
|
30
|
+
// Releases 0.4.0 and 0.5.0 wrote `permission_mode: plan`, which Grok Build ignores; `permissionMode: plan` has no effect at spawn.
|
|
28
31
|
if(client==='grok') {
|
|
29
|
-
if(
|
|
32
|
+
if(selected.effort!==null)fields.push(`effort: ${selected.effort}`);
|
|
33
|
+
if(['reviewer','explorer'].includes(role))fields.push('capabilityMode: read-only');
|
|
30
34
|
} else if(client==='kimi') {
|
|
31
35
|
if(['reviewer','explorer'].includes(role))fields.push('tools: [Read, Grep, Glob]');
|
|
32
36
|
} else {
|
|
@@ -51,7 +55,7 @@ export function instructionBlock(client,config,maxConcurrent,selectedRoles=ROLE_
|
|
|
51
55
|
for(const role of selectedRoles)text.push(`When delegating to apk-${role}, use model tier ${rolePreference(config,role).model}.`);
|
|
52
56
|
text.push('Per-role effort and a hard concurrency cap are not supported by the documented native agent settings.');
|
|
53
57
|
}
|
|
54
|
-
else if(client==='grok')text.push(
|
|
58
|
+
else if(client==='grok')text.push("Use the named role definitions for model and effort selection. A role effort applies only when its model supports effort; a role without an effort uses its model's native default effort. Explicit spawn settings and higher-priority instructions can supersede user defaults.");
|
|
55
59
|
else if(client==='claude')text.push('Use the named role definitions for model and effort selection. Session, project, managed settings, environment overrides and some spawn paths can supersede these user defaults; the native cap applies to ordinary Agent-tool spawns with documented exceptions.');
|
|
56
60
|
else text.push('Use the named role definitions for model and effort selection. Explicit spawn settings and higher-priority instructions can supersede user defaults.');
|
|
57
61
|
if(client==='chatgpt'||client==='claude')text.push('An omitted role effort uses the native client default; it does not pin a level.');
|
|
@@ -60,5 +64,5 @@ export function instructionBlock(client,config,maxConcurrent,selectedRoles=ROLE_
|
|
|
60
64
|
return text.join('\n')+'\n';
|
|
61
65
|
}
|
|
62
66
|
export function nativeWarnings(client) {
|
|
63
|
-
return client==='kimi'?['kimi-concurrency-guidance-only','kimi-model-pool-routing-guidance']:client==='gemini'?['gemini-concurrency-guidance-only','gemini-per-role-effort-unsupported']:client==='grok'?['grok-
|
|
67
|
+
return client==='kimi'?['kimi-concurrency-guidance-only','kimi-model-pool-routing-guidance']:client==='gemini'?['gemini-concurrency-guidance-only','gemini-per-role-effort-unsupported']:client==='grok'?['grok-effort-model-dependent','grok-native-precedence-limits']:client==='claude'?['claude-native-precedence-limits']:['chatgpt-native-precedence-limits'];
|
|
64
68
|
}
|
package/src/output.mjs
CHANGED
|
@@ -11,7 +11,9 @@ const NOTES={
|
|
|
11
11
|
'kimi-model-pool-routing-guidance':'Kimi: the parent selects the model alias for each role.',
|
|
12
12
|
'gemini-concurrency-guidance-only':'Gemini: the concurrency limit is guidance.',
|
|
13
13
|
'gemini-per-role-effort-unsupported':'Gemini: native tiers do not support per-role effort.',
|
|
14
|
-
'grok-
|
|
14
|
+
'grok-effort-model-dependent':'Grok: a role effort applies only when its model supports effort; a role without one uses its model default.',
|
|
15
|
+
// Receipts written by 0.4.0 still carry this code; it is shown with the corrected text.
|
|
16
|
+
'grok-per-role-effort-unsupported':'Grok: a role effort applies only when its model supports effort; a role without one uses its model default.',
|
|
15
17
|
'grok-native-precedence-limits':'Project and explicit client settings can override these defaults.',
|
|
16
18
|
'kimi-catalog-alias-resolved':'Kimi: an existing matching model alias was selected.',
|
|
17
19
|
};
|
package/src/share.mjs
CHANGED
|
@@ -55,7 +55,7 @@ function shareRelease(release){
|
|
|
55
55
|
}
|
|
56
56
|
function shareChoice(choice,id,metadata){
|
|
57
57
|
shareRecord(choice,['model','effort']);
|
|
58
|
-
if(typeof choice.model!=='string'||choice.model.length>128||(choice.effort!==null&&(
|
|
58
|
+
if(typeof choice.model!=='string'||choice.model.length>128||(choice.effort!==null&&(id==='gemini'||!metadata.catalog[id].efforts.includes(choice.effort))))invalidShare();
|
|
59
59
|
return {model:choice.model,effort:choice.effort};
|
|
60
60
|
}
|
|
61
61
|
function sharePayload(payload,release,metadata){
|