sprag-cli 3.74.0 → 3.75.0
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/package.json +1 -1
- package/src/agents.js +51 -3
- package/src/model-rules.js +11 -2
- package/src/route-inject.js +4 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sprag-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.75.0",
|
|
4
4
|
"description": "Model-delegation router for Claude Code and Codex: lookups, checks and test runs go to cheaper models, documents go in as compact Markdown, and a live statusline shows spend and savings.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/agents.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* directory and a copied rule would otherwise name an agent that is not there.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { existsSync } from 'node:fs';
|
|
19
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
20
20
|
import { join } from 'node:path';
|
|
21
21
|
import { claudeUserDir } from './paths.js';
|
|
22
22
|
import { agentDefinitions } from './presets-source.js';
|
|
@@ -134,7 +134,8 @@ export function agentPhrase(name, { model, root } = {}) {
|
|
|
134
134
|
const resolved = resolveAgentName(name, root);
|
|
135
135
|
if (!resolved) return `model: ${model || modelOf(name) || 'haiku'}`;
|
|
136
136
|
const tier = modelOf(resolved) || model || 'haiku';
|
|
137
|
-
|
|
137
|
+
const spec = agentSpecLabel(resolved, root);
|
|
138
|
+
return `${resolved}(${spec ? `${spec}. ` : ''}model 인자 생략. 이 에이전트가 없거나 MCP 도구가 필요하면 general-purpose에 model: ${tier})`;
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
/**
|
|
@@ -146,5 +147,52 @@ export function agentPhraseEn(name, { model, root } = {}) {
|
|
|
146
147
|
const resolved = resolveAgentName(name, root);
|
|
147
148
|
if (!resolved) return `a model: ${model || modelOf(name) || 'haiku'} subagent`;
|
|
148
149
|
const tier = modelOf(resolved) || model || 'haiku';
|
|
149
|
-
|
|
150
|
+
const spec = agentSpecLabel(resolved, root, ', ');
|
|
151
|
+
return `the ${resolved} subagent (${spec ? `${spec}; ` : ''}omit the model argument; if it is unavailable or MCP tools are needed, use general-purpose with model: ${tier})`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The model and effort an installed agent file really pins, read from its
|
|
156
|
+
* frontmatter, or null when the file has none.
|
|
157
|
+
*
|
|
158
|
+
* The agent name says only the tier (`t1-worker`), and Claude Code draws a
|
|
159
|
+
* delegated run as `t1-worker(…)` with no model beside it. The phrase carries
|
|
160
|
+
* what the file says so the session can show it. The file is read rather
|
|
161
|
+
* than the preset because a user's own copy is never overwritten, and it may
|
|
162
|
+
* pin something else. A file without `model:` inherits the session model,
|
|
163
|
+
* which Claude Code writes as `inherit`.
|
|
164
|
+
*/
|
|
165
|
+
export function agentSpec(name, root = process.cwd()) {
|
|
166
|
+
const paths = [];
|
|
167
|
+
if (root !== null) paths.push(join(root, '.claude', 'agents', `${name}.md`));
|
|
168
|
+
paths.push(join(claudeUserDir(), 'agents', `${name}.md`));
|
|
169
|
+
for (const p of paths) {
|
|
170
|
+
let text;
|
|
171
|
+
try { text = readFileSync(p, 'utf8'); } catch { continue; }
|
|
172
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
|
|
173
|
+
if (!m) return null;
|
|
174
|
+
const field = (key) => {
|
|
175
|
+
const hit = new RegExp(`^${key}:[ \\t]*(.*?)[ \\t]*$`, 'm').exec(m[1]);
|
|
176
|
+
return hit && hit[1] ? hit[1] : null;
|
|
177
|
+
};
|
|
178
|
+
return { model: field('model') || 'inherit', effort: field('effort') };
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Display name for a model value: `claude-sonnet-5-5` → `Sonnet 5.5`, a dated
|
|
185
|
+
* id loses its date. An alias or a gateway id stays as written, since an alias
|
|
186
|
+
* can resolve to an older model and that is worth seeing.
|
|
187
|
+
*/
|
|
188
|
+
export function modelLabel(model) {
|
|
189
|
+
const m = /^claude-([a-z]+)-(\d+)-(\d+)(?:-\d{8})?$/.exec(String(model || ''));
|
|
190
|
+
return m ? `${m[1][0].toUpperCase()}${m[1].slice(1)} ${m[2]}.${m[3]}` : String(model || '');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** `Sonnet 5.5 · effort medium` for an installed agent, or '' when unknown. */
|
|
194
|
+
function agentSpecLabel(name, root, sep = ' · ') {
|
|
195
|
+
const spec = agentSpec(name, root);
|
|
196
|
+
if (!spec) return '';
|
|
197
|
+
return spec.effort ? `${modelLabel(spec.model)}${sep}effort ${spec.effort}` : modelLabel(spec.model);
|
|
150
198
|
}
|
package/src/model-rules.js
CHANGED
|
@@ -263,7 +263,11 @@ export function renderModelRatchet(rules, lang = userLanguage(), { root } = {})
|
|
|
263
263
|
'',
|
|
264
264
|
'위임을 실행할 때는 사용자에게 다음 한 줄을 먼저 표시할 것',
|
|
265
265
|
'(어떤 도구가 토큰을 아끼고 있는지 가시화):',
|
|
266
|
-
'`🔀 [sprag] 모델 피팅: "<유형>" → <agent> 위임`',
|
|
266
|
+
'`🔀 [sprag] 모델 피팅: "<유형>" → <agent> (<모델> · effort <effort>) 위임`',
|
|
267
|
+
'',
|
|
268
|
+
'<모델>과 <effort>에는 룰의 에이전트 표기 괄호 안에 적힌 값(예: Sonnet 5.5 · effort medium)을',
|
|
269
|
+
'그대로 옮기고, 값이 적혀 있지 않으면 그 부분을 뺍니다. Claude Code 호출 행에는 에이전트',
|
|
270
|
+
'이름만 보이므로, Agent 도구의 description 맨 앞에도 `[<모델> · <effort>]` 를 붙이십시오.',
|
|
267
271
|
'',
|
|
268
272
|
'## 공통 조항 (룰마다 반복하지 않으므로 여기서 한 번만 읽으십시오)',
|
|
269
273
|
'',
|
|
@@ -326,7 +330,12 @@ export function renderModelRatchet(rules, lang = userLanguage(), { root } = {})
|
|
|
326
330
|
'',
|
|
327
331
|
'When delegating under a rule below, show the user this line first so it is',
|
|
328
332
|
'visible which tool is saving tokens:',
|
|
329
|
-
'`🔀 [sprag] model fitting: "<category>" → delegated to <agent
|
|
333
|
+
'`🔀 [sprag] model fitting: "<category>" → delegated to <agent> (<model> · effort <effort>)`',
|
|
334
|
+
'',
|
|
335
|
+
'Fill <model> and <effort> from the parentheses after the agent in the rule',
|
|
336
|
+
'(e.g. Sonnet 5.5, effort medium), and leave out whichever the rule does not',
|
|
337
|
+
'give. Claude Code shows only the agent name on the call row, so also start',
|
|
338
|
+
'the Agent tool\'s description with `[<model> · <effort>]`.',
|
|
330
339
|
'',
|
|
331
340
|
'## Shared clauses (stated once here, not repeated per rule)',
|
|
332
341
|
'',
|
package/src/route-inject.js
CHANGED
|
@@ -296,9 +296,11 @@ export function routeHint(text, { rules, lang = userLanguage(), root, sessionRan
|
|
|
296
296
|
// string whichever path decided to delegate.
|
|
297
297
|
return ko
|
|
298
298
|
? `[sprag] 이 요청은 사용자가 이미 승인한 위임 룰 "${label}" 에 해당합니다: ${target} 로 위임하십시오 (${capText}). `
|
|
299
|
-
+ `되묻지 말고 위임하고, 위임할 때 \`🔀 [sprag] 모델 피팅: "${label}" → <agent> 위임\` 을 먼저 표시하십시오. `
|
|
299
|
+
+ `되묻지 말고 위임하고, 위임할 때 \`🔀 [sprag] 모델 피팅: "${label}" → <agent> (<모델> · effort <effort>) 위임\` 을 먼저 표시하십시오. `
|
|
300
|
+
+ `모델과 effort 값은 위 에이전트 표기에 적힌 그대로 쓰고, Agent 도구의 description 맨 앞에도 \`[<모델> · <effort>]\` 를 붙이십시오. `
|
|
300
301
|
+ `상한을 넘길 것 같거나 에러가 나면 그 자리에서 멈추고 메인 모델이 이어받습니다.`
|
|
301
302
|
: `[sprag] This request matches a delegation rule the user already approved — "${label}": delegate to ${target} (${capText}). `
|
|
302
|
-
+ `Do not ask again; print \`🔀 [sprag] model fit: "${label}" → <agent
|
|
303
|
+
+ `Do not ask again; print \`🔀 [sprag] model fit: "${label}" → <agent> (<model> · effort <effort>)\` first, `
|
|
304
|
+
+ `with the model and effort the agent's phrase above gives, and start the Agent tool's description with \`[<model> · <effort>]\`. `
|
|
303
305
|
+ `If the run is likely to exceed its cap or hits an error, stop there and the main model takes over.`;
|
|
304
306
|
}
|