@ikon85/agent-workflow-kit 0.44.2 → 0.45.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/.agents/skills/audit-skills/SKILL.md +7 -4
- package/.agents/skills/code-review/SKILL.md +7 -4
- package/.agents/skills/codebase-design/DESIGN-IT-TWICE.md +7 -4
- package/.agents/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +7 -4
- package/.agents/skills/improve-codebase-architecture/SKILL.md +7 -4
- package/.agents/skills/orchestrate-wave/SKILL.md +1 -1
- package/.agents/skills/orchestrate-wave/references/dispatch-subagents.md +9 -0
- package/.agents/skills/orchestrate-wave/references/dispatch-workflow.md +9 -0
- package/.agents/skills/research/SKILL.md +7 -4
- package/.agents/skills/to-issues/SKILL.md +25 -4
- package/.claude/skills/audit-skills/SKILL.md +7 -4
- package/.claude/skills/code-review/SKILL.md +7 -4
- package/.claude/skills/codebase-design/DESIGN-IT-TWICE.md +7 -4
- package/.claude/skills/codex-build/SKILL.md +13 -0
- package/.claude/skills/codex-review/SKILL.md +13 -0
- package/.claude/skills/grill-me-codex/SKILL.md +14 -0
- package/.claude/skills/grill-with-docs-codex/SKILL.md +14 -0
- package/.claude/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +7 -4
- package/.claude/skills/improve-codebase-architecture/SKILL.md +7 -4
- package/.claude/skills/orchestrate-wave/SKILL.md +1 -1
- package/.claude/skills/orchestrate-wave/references/dispatch-subagents.md +9 -0
- package/.claude/skills/orchestrate-wave/references/dispatch-workflow.md +9 -0
- package/.claude/skills/research/SKILL.md +7 -4
- package/.claude/skills/skill-manifest.json +34 -23
- package/.claude/skills/to-issues/SKILL.md +25 -4
- package/README.md +64 -0
- package/agent-workflow-kit.package.json +149 -45
- package/package.json +1 -1
- package/scripts/doctrine-migration/index.mjs +296 -0
- package/scripts/kit-release.mjs +41 -9
- package/src/cli.mjs +515 -79
- package/src/commands/routing-status.mjs +288 -0
- package/src/lib/bundle.mjs +158 -2
- package/src/lib/dispatchJournal.mjs +300 -0
- package/src/lib/dispatchPlan.mjs +286 -0
- package/src/lib/dispatchReceipt.mjs +226 -89
- package/src/lib/frontendWorkloads.mjs +35 -33
- package/src/lib/routeDispatcher.mjs +367 -70
- package/src/lib/routingAccessGraph.mjs +265 -20
- package/src/lib/routingAccessGraphStore.mjs +300 -0
- package/src/lib/routingAdapters/claude.mjs +104 -4
- package/src/lib/routingAdapters/codex.mjs +132 -7
- package/src/lib/routingAdapters/hostBridge.mjs +291 -0
- package/src/lib/routingCatalog.mjs +201 -24
- package/src/lib/routingDispatchLease.mjs +253 -0
- package/src/lib/routingEvidenceCache.mjs +78 -0
- package/src/lib/routingIntent.mjs +176 -10
- package/src/lib/routingIntentClassifier.mjs +137 -0
- package/src/lib/routingInventory/snapshots/claude.json +49 -0
- package/src/lib/routingInventory/snapshots/codex.json +109 -0
- package/src/lib/routingInventory.mjs +182 -0
- package/src/lib/routingPolicy.mjs +193 -6
- package/src/lib/routingProfile.mjs +1251 -123
- package/src/lib/routingProfilePolicy.mjs +156 -0
- package/src/lib/routingProfileStorage.mjs +299 -0
- package/src/lib/routingResolver.mjs +369 -86
- package/src/lib/routingSources/artificialAnalysis.mjs +19 -7
- package/src/lib/routingSources/benchlm.mjs +5 -0
- package/src/lib/routingSources/codeArena.mjs +13 -3
- package/src/lib/routingSources/deepswe.mjs +19 -5
- package/src/lib/routingSources/openhands.mjs +17 -4
- package/src/lib/routingSources/openhandsFrontend.mjs +13 -3
- package/src/lib/safeText.mjs +26 -0
- package/src/lib/updateCandidate.mjs +36 -3
|
@@ -1,6 +1,51 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The Routing policy schema: the constraint object derived from a Routing
|
|
3
|
+
* profile for one dispatch — allowed surfaces, allowed transports, the
|
|
4
|
+
* authorized Model roster, the effective Standard routes, and switching
|
|
5
|
+
* autonomy. It carries a revision so a Dispatch receipt can prove which
|
|
6
|
+
* constraints applied, and it never changes Evidence catalog facts.
|
|
7
|
+
*
|
|
8
|
+
* v2 removes the optimization dial. What a user is willing to spend
|
|
9
|
+
* is a Routing profile choice, not a per-dispatch constraint, so the roster
|
|
10
|
+
* pairs and the effective Standard routes move in instead: the resolver keeps
|
|
11
|
+
* its four inputs and one receipt names one revision. `unreachable` and
|
|
12
|
+
* `missingInfrastructure` stay — they are the resolver's fallback semantics and
|
|
13
|
+
* have nothing to do with the removed dial.
|
|
14
|
+
*
|
|
15
|
+
* Fail-closed by construction. A policy that authorizes no roster pair and no
|
|
16
|
+
* Standard route is invalid rather than an empty default that would read as a
|
|
17
|
+
* policy; dispatch blocks with that named reason.
|
|
18
|
+
*
|
|
19
|
+
* Authorization only. The policy states what a user authorized; whether a pair
|
|
20
|
+
* is reachable is decided at decision time against the Access graph, never
|
|
21
|
+
* here. The roster vocabulary and the Standard-route classes come from the
|
|
22
|
+
* Routing profile, which owns them — this module never restates them.
|
|
23
|
+
*/
|
|
24
|
+
import {
|
|
25
|
+
STANDARD_ROUTE_CLASSES,
|
|
26
|
+
STANDARD_ROUTE_STATES,
|
|
27
|
+
normalizeRosterModelId,
|
|
28
|
+
} from './routingProfile.mjs';
|
|
29
|
+
|
|
30
|
+
export const ROUTING_POLICY_VERSION = 2;
|
|
31
|
+
export const ROUTING_POLICY_LEGACY_VERSION = 1;
|
|
32
|
+
|
|
33
|
+
/** The reason an authorization-free policy gives, so a dispatch can name it. */
|
|
34
|
+
export const ROUTING_POLICY_NOT_DERIVABLE = 'routing-policy-not-derivable';
|
|
2
35
|
|
|
3
36
|
const POLICY_FIELDS = new Set([
|
|
37
|
+
'schemaVersion',
|
|
38
|
+
'revision',
|
|
39
|
+
'allowedSurfaces',
|
|
40
|
+
'allowedTransports',
|
|
41
|
+
'switching',
|
|
42
|
+
'roster',
|
|
43
|
+
'standardRoutes',
|
|
44
|
+
'unreachable',
|
|
45
|
+
'missingInfrastructure',
|
|
46
|
+
]);
|
|
47
|
+
/** The v1 shape, kept only so the decoder can read one and say what it dropped. */
|
|
48
|
+
const LEGACY_POLICY_FIELDS = new Set([
|
|
4
49
|
'schemaVersion',
|
|
5
50
|
'revision',
|
|
6
51
|
'allowedSurfaces',
|
|
@@ -10,9 +55,12 @@ const POLICY_FIELDS = new Set([
|
|
|
10
55
|
'unreachable',
|
|
11
56
|
'missingInfrastructure',
|
|
12
57
|
]);
|
|
58
|
+
/** What a v1 document cannot carry and a caller must therefore supply. */
|
|
59
|
+
const DERIVATION_FIELDS = new Set(['roster', 'standardRoutes']);
|
|
60
|
+
const PAIR_FIELDS = new Set(['model', 'effort']);
|
|
61
|
+
const STANDARD_ROUTE_FIELDS = new Set(['model', 'effort', 'state']);
|
|
13
62
|
|
|
14
63
|
const SWITCHING = ['automatic', 'ask', 'current-surface-only'];
|
|
15
|
-
const OPTIMIZATION = ['quality', 'balanced', 'cost'];
|
|
16
64
|
const UNREACHABLE = ['handoff', 'inherit', 'block'];
|
|
17
65
|
const MISSING_INFRASTRUCTURE = ['inherit', 'block'];
|
|
18
66
|
|
|
@@ -23,6 +71,12 @@ function object(value, field) {
|
|
|
23
71
|
return value;
|
|
24
72
|
}
|
|
25
73
|
|
|
74
|
+
function assertFields(input, allowed, label) {
|
|
75
|
+
for (const key of Object.keys(input)) {
|
|
76
|
+
if (!allowed.has(key)) throw new TypeError(`unknown ${label} field: ${key}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
26
80
|
function string(value, field) {
|
|
27
81
|
if (typeof value !== 'string' || value.trim() === '') {
|
|
28
82
|
throw new TypeError(`${field} must be a non-empty string`);
|
|
@@ -42,21 +96,106 @@ function oneOf(value, values, field) {
|
|
|
42
96
|
return value;
|
|
43
97
|
}
|
|
44
98
|
|
|
99
|
+
/**
|
|
100
|
+
* A model-and-effort pair, under the roster's own identity rule: effort is
|
|
101
|
+
* always explicit, `null` meaning a model with no effort axis.
|
|
102
|
+
*/
|
|
103
|
+
function policyPair(input, field) {
|
|
104
|
+
object(input, field);
|
|
105
|
+
assertFields(input, PAIR_FIELDS, field);
|
|
106
|
+
const model = normalizeRosterModelId(input.model, `${field}.model`);
|
|
107
|
+
if (!('effort' in input) || (input.effort !== null
|
|
108
|
+
&& (typeof input.effort !== 'string' || input.effort.trim() === ''))) {
|
|
109
|
+
throw new TypeError(`${field}.effort must be a non-empty string or null (no effort axis)`);
|
|
110
|
+
}
|
|
111
|
+
return { model, effort: input.effort === null ? null : input.effort.trim() };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const pairKey = ({ model, effort }) => `${model}\u0000${effort ?? ''}`;
|
|
115
|
+
|
|
116
|
+
/** The positive list this policy authorizes: pairs, deduped, order preserved. */
|
|
117
|
+
function validateRoster(value) {
|
|
118
|
+
if (!Array.isArray(value)) {
|
|
119
|
+
throw new TypeError('roster must be an array of {model, effort} pairs');
|
|
120
|
+
}
|
|
121
|
+
const pairs = new Map();
|
|
122
|
+
value.forEach((input, index) => {
|
|
123
|
+
const pair = policyPair(input, `roster[${index}]`);
|
|
124
|
+
pairs.set(pairKey(pair), Object.freeze(pair));
|
|
125
|
+
});
|
|
126
|
+
return [...pairs.values()];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The effective Standard routes, one per workload class. A `configured` route
|
|
131
|
+
* must name a pair the roster authorizes — the policy is the authorization
|
|
132
|
+
* surface, so an authorized route over an unauthorized pair is a contradiction,
|
|
133
|
+
* not a fallback. An `unresolved` route keeps a knowingly broken nomination
|
|
134
|
+
* instead of invalidating the whole policy; it authorizes nothing.
|
|
135
|
+
*/
|
|
136
|
+
function validateStandardRoutes(value, roster) {
|
|
137
|
+
object(value, 'standardRoutes');
|
|
138
|
+
assertFields(value, new Set(STANDARD_ROUTE_CLASSES), 'standardRoutes');
|
|
139
|
+
const authorized = new Set(roster.map(pairKey));
|
|
140
|
+
const routes = {};
|
|
141
|
+
for (const workload of STANDARD_ROUTE_CLASSES) {
|
|
142
|
+
if (!(workload in value)) {
|
|
143
|
+
throw new TypeError(
|
|
144
|
+
`standardRoutes must name every workload class: ${STANDARD_ROUTE_CLASSES.join(', ')}`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
if (value[workload] === null) {
|
|
148
|
+
routes[workload] = null;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const field = `standardRoutes.${workload}`;
|
|
152
|
+
object(value[workload], field);
|
|
153
|
+
assertFields(value[workload], STANDARD_ROUTE_FIELDS, field);
|
|
154
|
+
const pair = policyPair({ model: value[workload].model, effort: value[workload].effort }, field);
|
|
155
|
+
const state = oneOf(value[workload].state, STANDARD_ROUTE_STATES, `${field}.state`);
|
|
156
|
+
if (state === 'configured' && !authorized.has(pairKey(pair))) {
|
|
157
|
+
throw new TypeError(`${field} must name an authorized roster pair`);
|
|
158
|
+
}
|
|
159
|
+
routes[workload] = Object.freeze({ ...pair, state });
|
|
160
|
+
}
|
|
161
|
+
return routes;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Never a silent default: a policy that authorizes nothing is invalid. */
|
|
165
|
+
function assertDerivable(roster, routes) {
|
|
166
|
+
const configured = STANDARD_ROUTE_CLASSES.some((cls) => routes[cls]?.state === 'configured');
|
|
167
|
+
if (roster.length === 0 && !configured) {
|
|
168
|
+
throw new TypeError('routing policy derives neither a Model roster nor a Standard route: '
|
|
169
|
+
+ ROUTING_POLICY_NOT_DERIVABLE);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Validate a Routing policy. A v1 document is accepted and decoded, so a caller
|
|
175
|
+
* that still holds one gets the named migration reason rather than an opaque
|
|
176
|
+
* schema error.
|
|
177
|
+
*/
|
|
45
178
|
export function validateRoutingPolicy(input) {
|
|
46
179
|
object(input, 'routing policy');
|
|
47
|
-
|
|
48
|
-
|
|
180
|
+
if (input.schemaVersion === ROUTING_POLICY_LEGACY_VERSION) {
|
|
181
|
+
return decodeRoutingPolicy(input).policy;
|
|
49
182
|
}
|
|
183
|
+
assertFields(input, POLICY_FIELDS, 'routing policy');
|
|
50
184
|
if (input.schemaVersion !== ROUTING_POLICY_VERSION) {
|
|
51
|
-
throw new TypeError(
|
|
185
|
+
throw new TypeError('routing policy schemaVersion must be '
|
|
186
|
+
+ `${ROUTING_POLICY_LEGACY_VERSION} or ${ROUTING_POLICY_VERSION}`);
|
|
52
187
|
}
|
|
188
|
+
const roster = validateRoster(input.roster);
|
|
189
|
+
const standardRoutes = validateStandardRoutes(input.standardRoutes, roster);
|
|
190
|
+
assertDerivable(roster, standardRoutes);
|
|
53
191
|
return Object.freeze({
|
|
54
192
|
schemaVersion: ROUTING_POLICY_VERSION,
|
|
55
193
|
revision: string(input.revision, 'routing policy revision'),
|
|
56
194
|
allowedSurfaces: Object.freeze(stringList(input.allowedSurfaces, 'allowedSurfaces')),
|
|
57
195
|
allowedTransports: Object.freeze(stringList(input.allowedTransports, 'allowedTransports')),
|
|
58
196
|
switching: oneOf(input.switching, SWITCHING, 'switching'),
|
|
59
|
-
|
|
197
|
+
roster: Object.freeze(roster),
|
|
198
|
+
standardRoutes: Object.freeze(standardRoutes),
|
|
60
199
|
unreachable: oneOf(input.unreachable, UNREACHABLE, 'unreachable'),
|
|
61
200
|
missingInfrastructure: oneOf(
|
|
62
201
|
input.missingInfrastructure,
|
|
@@ -65,3 +204,51 @@ export function validateRoutingPolicy(input) {
|
|
|
65
204
|
),
|
|
66
205
|
});
|
|
67
206
|
}
|
|
207
|
+
|
|
208
|
+
function droppedOptimization(document) {
|
|
209
|
+
if (!('optimization' in document)) return { dropped: [], notes: [] };
|
|
210
|
+
return {
|
|
211
|
+
dropped: ['optimization'],
|
|
212
|
+
notes: [Object.freeze({ code: 'optimization-removed', value: document.optimization ?? null })],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* The deterministic v1 → v2 decode. The optimization dial is dropped and
|
|
218
|
+
* *recorded*, never silently ignored, and the roster and Standard routes a v1
|
|
219
|
+
* document cannot carry come from the derivation its caller already holds. A
|
|
220
|
+
* decode that can derive neither fails closed with that reason instead of
|
|
221
|
+
* producing an empty authorization that would read as a policy.
|
|
222
|
+
*/
|
|
223
|
+
export function decodeRoutingPolicy(document, derivation = {}) {
|
|
224
|
+
object(document, 'routing policy');
|
|
225
|
+
object(derivation, 'routing policy derivation');
|
|
226
|
+
assertFields(derivation, DERIVATION_FIELDS, 'routing policy derivation');
|
|
227
|
+
if (document.schemaVersion === ROUTING_POLICY_VERSION) {
|
|
228
|
+
return Object.freeze({
|
|
229
|
+
policy: validateRoutingPolicy(document),
|
|
230
|
+
fromVersion: ROUTING_POLICY_VERSION,
|
|
231
|
+
dropped: Object.freeze([]),
|
|
232
|
+
notes: Object.freeze([]),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
if (document.schemaVersion !== ROUTING_POLICY_LEGACY_VERSION) {
|
|
236
|
+
throw new TypeError('routing policy schemaVersion must be '
|
|
237
|
+
+ `${ROUTING_POLICY_LEGACY_VERSION} or ${ROUTING_POLICY_VERSION}`);
|
|
238
|
+
}
|
|
239
|
+
assertFields(document, LEGACY_POLICY_FIELDS, 'routing policy');
|
|
240
|
+
const { dropped, notes } = droppedOptimization(document);
|
|
241
|
+
const { schemaVersion, optimization, ...carried } = document;
|
|
242
|
+
return Object.freeze({
|
|
243
|
+
policy: validateRoutingPolicy({
|
|
244
|
+
...carried,
|
|
245
|
+
schemaVersion: ROUTING_POLICY_VERSION,
|
|
246
|
+
roster: derivation.roster ?? [],
|
|
247
|
+
standardRoutes: derivation.standardRoutes
|
|
248
|
+
?? Object.fromEntries(STANDARD_ROUTE_CLASSES.map((cls) => [cls, null])),
|
|
249
|
+
}),
|
|
250
|
+
fromVersion: ROUTING_POLICY_LEGACY_VERSION,
|
|
251
|
+
dropped: Object.freeze(dropped),
|
|
252
|
+
notes: Object.freeze(notes),
|
|
253
|
+
});
|
|
254
|
+
}
|