sortie-dogs 0.2.6 → 0.2.8
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 +35 -21
- package/dist/plugin/model-routing.d.ts +40 -12
- package/dist/plugin/model-routing.js +58 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,8 +60,9 @@ to the `plugin` array of the OpenCode configuration the agents run under —
|
|
|
60
60
|
Restart OpenCode afterwards. A `plugin` entry must name the package, not a
|
|
61
61
|
subpath: `sortie-dogs/plugin` is an import specifier, not a plugin specifier.
|
|
62
62
|
|
|
63
|
-
`dog-coordinator`
|
|
64
|
-
|
|
63
|
+
`dog-coordinator` keeps whichever model you select for the session. `dog-scout`
|
|
64
|
+
defaults to `openai/gpt-5.6-luna`. To pin either role to another model, save this
|
|
65
|
+
as `.opencode/sortie-dogs.json`:
|
|
65
66
|
|
|
66
67
|
```json
|
|
67
68
|
{
|
|
@@ -160,7 +161,7 @@ Optional settings in `.opencode/sortie-dogs.json`:
|
|
|
160
161
|
- `readOnlyTools` adds host-specific tool names that never change files, such as
|
|
161
162
|
MCP tools. Unknown tools are denied for a bound session by default.
|
|
162
163
|
- `dedicatedWorkerModel` selects the single model every worker role resolves to.
|
|
163
|
-
It defaults to `openai/gpt-5.6-
|
|
164
|
+
It defaults to `openai/gpt-5.6-luna` with variant `max`; declare your own when
|
|
164
165
|
that model is unavailable or when you want a different worker effort. Worker
|
|
165
166
|
roles always resolve to this one target and cannot be routed per role.
|
|
166
167
|
- `continuation` bounds the batch loop. After a terminal unit and its checkpoint,
|
|
@@ -180,7 +181,7 @@ Optional settings in `.opencode/sortie-dogs.json`:
|
|
|
180
181
|
exactly three bounded scouts before implementation begins.
|
|
181
182
|
- **Writes stay inside the assignment.** Exact source or operation manifests
|
|
182
183
|
gate edits and handoffs.
|
|
183
|
-
- **One accountable implementation path.**
|
|
184
|
+
- **One accountable implementation path.** One dedicated worker handles
|
|
184
185
|
implementation, remediation, and blocker resolution.
|
|
185
186
|
- **Evidence before completion.** Canonical validation, risk-based review, and
|
|
186
187
|
terminal evidence gate coordinator-owned completion and commits.
|
|
@@ -207,8 +208,8 @@ dog-coordinator: completion evidence accepted
|
|
|
207
208
|
criteria, a write manifest, and validation requirements.
|
|
208
209
|
2. **Exactly three scouts** — bounded, read-only investigation collects
|
|
209
210
|
complementary evidence without expanding the write scope.
|
|
210
|
-
3. **Dedicated worker** — the
|
|
211
|
-
and also owns scoped remediation or blocker resolution.
|
|
211
|
+
3. **Dedicated worker** — the dedicated worker implements only the approved
|
|
212
|
+
manifest and also owns scoped remediation or blocker resolution.
|
|
212
213
|
4. **Canonical validation** — the declared test or build command must produce
|
|
213
214
|
acceptable evidence.
|
|
214
215
|
5. **Risk-based review** — high-risk candidates receive independent review;
|
|
@@ -258,19 +259,30 @@ untouched.
|
|
|
258
259
|
|
|
259
260
|
## Model routing
|
|
260
261
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
262
|
+
Every default route is one model at a different reasoning effort, because
|
|
263
|
+
published cost curves put a cheap model at high effort above an expensive model
|
|
264
|
+
at mid effort on both solve rate and price. Sortie-dogs therefore buys effort,
|
|
265
|
+
not model tiers, wherever the work allows it.
|
|
266
|
+
|
|
267
|
+
`dog-coordinator` has no built-in route. It is the one agent you drive directly
|
|
268
|
+
and pick a model for in the session, so it keeps the model you selected; a
|
|
269
|
+
shipped default there would silently revert your choice instead of filling an
|
|
270
|
+
absent one. Declare `modelRouting` for `dog-coordinator` if you do want a fixed
|
|
271
|
+
coordinator model.
|
|
272
|
+
|
|
273
|
+
`dog-scout` defaults to `openai/gpt-5.6-luna` with the `high` variant, since
|
|
274
|
+
gathering bounded evidence is retrieval rather than reasoning and that tier is
|
|
275
|
+
where the curve gives the most per unit of cost. Nobody selects a model for a
|
|
276
|
+
session the loop spawns, which is why delegated roles carry defaults and the
|
|
277
|
+
coordinator does not. Project-local routing can override this default.
|
|
266
278
|
|
|
267
279
|
The `implementation`, `remediation`, `blocker-resolution`, and `dog-worker`
|
|
268
|
-
roles always use the dedicated worker target, `openai/gpt-5.6-
|
|
269
|
-
`
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
280
|
+
roles always use the dedicated worker target, `openai/gpt-5.6-luna` with the
|
|
281
|
+
`max` variant. Worker effort stays at the top of that model's range while review
|
|
282
|
+
effort stays above it on a stronger model, which is what mandatory source review
|
|
283
|
+
is for. Declare `dedicatedWorkerModel` as `openai/gpt-5.6-sol` when you would
|
|
284
|
+
rather pay for the stronger worker model up front; that target stays in the
|
|
285
|
+
built-in catalog for exactly this reason. `modelRouting`
|
|
274
286
|
cannot replace those routes, and only `dedicatedWorkerModel` moves them. For other explicitly
|
|
275
287
|
routed roles, resolution is deterministic: Sortie-dogs tries the preferred
|
|
276
288
|
target, then ordered fallbacks. Roles without either a built-in default or an
|
|
@@ -280,7 +292,9 @@ explicit route keep OpenCode's already selected model.
|
|
|
280
292
|
review and strategy lose their value when they run on the model that produced
|
|
281
293
|
the candidate. Both default to `anthropic/claude-opus-5` when the catalog
|
|
282
294
|
declares it, and otherwise fall back to `openai/gpt-5.6-sol` with the `xhigh`
|
|
283
|
-
variant
|
|
295
|
+
variant. That fallback stays on the stronger model rather than matching the
|
|
296
|
+
worker target, because review has to be able to reject work the worker just
|
|
297
|
+
produced. A host that redeclares
|
|
284
298
|
`dedicatedWorkerModel` keeps that target as its first fallback, since such a
|
|
285
299
|
host may not serve the shipped model at all. Nothing here requires a particular
|
|
286
300
|
vendor: both roles stay fully configurable, so declare whichever model you can
|
|
@@ -290,10 +304,10 @@ actually serve.
|
|
|
290
304
|
{
|
|
291
305
|
"modelRouting": {
|
|
292
306
|
"dog-coordinator": {
|
|
293
|
-
"preferred": { "model": "openai/gpt-5.6-luna", "variant": "
|
|
307
|
+
"preferred": { "model": "openai/gpt-5.6-luna", "variant": "max" }
|
|
294
308
|
},
|
|
295
309
|
"dog-scout": {
|
|
296
|
-
"preferred": { "model": "openai/gpt-5.6-luna", "variant": "
|
|
310
|
+
"preferred": { "model": "openai/gpt-5.6-luna", "variant": "high" }
|
|
297
311
|
},
|
|
298
312
|
"dog-reviewer": {
|
|
299
313
|
"preferred": { "model": "anthropic/claude-opus-5" },
|
|
@@ -306,7 +320,7 @@ actually serve.
|
|
|
306
320
|
"modelCatalog": {
|
|
307
321
|
"project": [
|
|
308
322
|
{ "model": "openai/gpt-5.6-sol", "variants": ["medium", "xhigh"] },
|
|
309
|
-
{ "model": "openai/gpt-5.6-luna", "variants": ["
|
|
323
|
+
{ "model": "openai/gpt-5.6-luna", "variants": ["max", "high"] },
|
|
310
324
|
{ "model": "anthropic/claude-opus-5" }
|
|
311
325
|
]
|
|
312
326
|
}
|
|
@@ -7,28 +7,52 @@ export interface RoleModelRoute {
|
|
|
7
7
|
readonly fallback?: readonly ModelTarget[];
|
|
8
8
|
}
|
|
9
9
|
export type ModelRoutingConfig = Readonly<Record<string, RoleModelRoute>>;
|
|
10
|
-
export declare const DEDICATED_SOL_MODEL = "openai/gpt-5.6-sol";
|
|
11
10
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* The published DeepSWE cost curve settles the worker target: the cheap model at top reasoning effort
|
|
12
|
+
* solves more than the expensive model at mid effort while costing a fraction of it, so paying for the
|
|
13
|
+
* expensive model by default bought a lower solve rate. Worker effort therefore sits at the top of the
|
|
14
|
+
* cheap model's range, and review effort stays above it on a stronger model, which is what the
|
|
15
|
+
* mandatory source review is for.
|
|
15
16
|
*/
|
|
16
|
-
export declare const
|
|
17
|
-
export declare const
|
|
17
|
+
export declare const DEDICATED_WORKER_MODEL = "openai/gpt-5.6-luna";
|
|
18
|
+
export declare const DEDICATED_WORKER_VARIANT = "max";
|
|
19
|
+
/**
|
|
20
|
+
* The stronger, far more expensive worker target a host may still select deliberately. It is no longer
|
|
21
|
+
* a default route: it stays declared so an explicit dedicatedWorkerModel resolves against the catalog
|
|
22
|
+
* without extra host configuration.
|
|
23
|
+
*/
|
|
24
|
+
export declare const ESCALATION_WORKER_MODEL = "openai/gpt-5.6-sol";
|
|
25
|
+
export declare const ESCALATION_WORKER_VARIANT = "medium";
|
|
26
|
+
export declare const DEDICATED_WORKER_ROLES: readonly ["implementation", "remediation", "blocker-resolution", "sol-worker-mk2a2", "dog-worker"];
|
|
18
27
|
/** Ordered last-resort targets used only when the host proves a policy target unavailable. */
|
|
19
28
|
export declare const DEFAULT_FREE_TIER_FALLBACK_MODELS: readonly string[];
|
|
20
29
|
/** The dedicated worker target this build ships with when a host declares no target of its own. */
|
|
21
30
|
export declare const DEFAULT_DEDICATED_WORKER_TARGET: ModelTarget;
|
|
31
|
+
/** The declared escalation target for a host that chooses to pay for the stronger worker model. */
|
|
32
|
+
export declare const ESCALATION_WORKER_TARGET: ModelTarget;
|
|
22
33
|
/**
|
|
23
34
|
* Fixed worker routes for one dedicated target. Which target is dedicated is a host decision, but
|
|
24
35
|
* every worker role resolves to that single target and never to a fallback.
|
|
25
36
|
*/
|
|
26
37
|
export declare function dedicatedWorkerRouting(target?: ModelTarget): ModelRoutingConfig;
|
|
27
|
-
export declare const
|
|
38
|
+
export declare const DEDICATED_WORKER_ROUTING: ModelRoutingConfig;
|
|
28
39
|
export declare const FIXED_MODEL_ROUTING: ModelRoutingConfig;
|
|
29
40
|
export declare const RECOMMENDED_LUNA_MODEL = "openai/gpt-5.6-luna";
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
/**
|
|
42
|
+
* The coordinator has no built-in route on purpose. It is the one agent the user drives directly and
|
|
43
|
+
* picks a model for in the session, so a shipped default here does not choose between models for an
|
|
44
|
+
* undecided user: it silently discards a choice the user already made and cannot see being reverted.
|
|
45
|
+
* Delegated roles are the opposite, because nobody selects a model for a session the loop spawns.
|
|
46
|
+
* A host that does want a fixed coordinator model still declares one through modelRouting.
|
|
47
|
+
*
|
|
48
|
+
* Evidence gathering is retrieval rather than reasoning, so the scout sits one effort tier below the
|
|
49
|
+
* worker, where the published cost curve returns the most per unit of cost.
|
|
50
|
+
*/
|
|
51
|
+
export declare const RECOMMENDED_SCOUT_VARIANT = "high";
|
|
52
|
+
export declare const RECOMMENDED_LUNA_ROLE_VARIANTS: Readonly<{
|
|
53
|
+
readonly "dog-scout": "high";
|
|
54
|
+
}>;
|
|
55
|
+
export declare const RECOMMENDED_LUNA_ROLES: readonly string[];
|
|
32
56
|
/** Configurable MkII defaults. Project-local and global configuration may override these routes. */
|
|
33
57
|
export declare const RECOMMENDED_LUNA_ROUTING: ModelRoutingConfig;
|
|
34
58
|
/**
|
|
@@ -39,8 +63,12 @@ export declare const RECOMMENDED_LUNA_ROUTING: ModelRoutingConfig;
|
|
|
39
63
|
export declare const RECOMMENDED_CONSULTATION_MODEL = "anthropic/claude-opus-5";
|
|
40
64
|
export declare const RECOMMENDED_CONSULTATION_ROLES: readonly string[];
|
|
41
65
|
/** Review and advice carry the reasoning effort the worker target intentionally does not spend. */
|
|
42
|
-
export declare const
|
|
43
|
-
/**
|
|
66
|
+
export declare const CONSULTATION_FALLBACK_VARIANT = "xhigh";
|
|
67
|
+
/**
|
|
68
|
+
* The shipped consultation fallback. Review has to be able to reject work the worker just produced, so
|
|
69
|
+
* it stays on the stronger model even though the worker no longer defaults to it; a fallback equal to
|
|
70
|
+
* the worker target would review that work with exactly the capability that produced it.
|
|
71
|
+
*/
|
|
44
72
|
export declare const DEFAULT_CONSULTATION_FALLBACK_TARGET: ModelTarget;
|
|
45
73
|
/**
|
|
46
74
|
* Consultation prefers the strongest declared reasoning model. A host that relocated the dedicated
|
|
@@ -55,7 +83,7 @@ export declare const RECOMMENDED_CONSULTATION_ROUTING: ModelRoutingConfig;
|
|
|
55
83
|
/** Every configurable role route this build recommends before host configuration is applied. */
|
|
56
84
|
export declare function recommendedRoleRouting(fallbackTarget?: ModelTarget): ModelRoutingConfig;
|
|
57
85
|
export declare const RECOMMENDED_ROLE_ROUTING: ModelRoutingConfig;
|
|
58
|
-
export declare function
|
|
86
|
+
export declare function isDedicatedWorkerRole(role: string): boolean;
|
|
59
87
|
export declare function isFixedModelRole(role: string): boolean;
|
|
60
88
|
export interface CatalogModel {
|
|
61
89
|
readonly model: string;
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { CONSULTATION_ROLE_POLICY } from "../core/consultation.js";
|
|
2
|
-
export const DEDICATED_SOL_MODEL = "openai/gpt-5.6-sol";
|
|
3
2
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* The published DeepSWE cost curve settles the worker target: the cheap model at top reasoning effort
|
|
4
|
+
* solves more than the expensive model at mid effort while costing a fraction of it, so paying for the
|
|
5
|
+
* expensive model by default bought a lower solve rate. Worker effort therefore sits at the top of the
|
|
6
|
+
* cheap model's range, and review effort stays above it on a stronger model, which is what the
|
|
7
|
+
* mandatory source review is for.
|
|
7
8
|
*/
|
|
8
|
-
export const
|
|
9
|
-
export const
|
|
9
|
+
export const DEDICATED_WORKER_MODEL = "openai/gpt-5.6-luna";
|
|
10
|
+
export const DEDICATED_WORKER_VARIANT = "max";
|
|
11
|
+
/**
|
|
12
|
+
* The stronger, far more expensive worker target a host may still select deliberately. It is no longer
|
|
13
|
+
* a default route: it stays declared so an explicit dedicatedWorkerModel resolves against the catalog
|
|
14
|
+
* without extra host configuration.
|
|
15
|
+
*/
|
|
16
|
+
export const ESCALATION_WORKER_MODEL = "openai/gpt-5.6-sol";
|
|
17
|
+
export const ESCALATION_WORKER_VARIANT = "medium";
|
|
18
|
+
export const DEDICATED_WORKER_ROLES = [
|
|
10
19
|
"implementation",
|
|
11
20
|
"remediation",
|
|
12
21
|
"blocker-resolution",
|
|
@@ -17,34 +26,52 @@ export const DEDICATED_SOL_ROLES = [
|
|
|
17
26
|
export const DEFAULT_FREE_TIER_FALLBACK_MODELS = Object.freeze([
|
|
18
27
|
"opencode/deepseek-v4-flash-free",
|
|
19
28
|
]);
|
|
20
|
-
const
|
|
29
|
+
const dedicatedWorkerRoleSet = new Set(DEDICATED_WORKER_ROLES);
|
|
21
30
|
/** The dedicated worker target this build ships with when a host declares no target of its own. */
|
|
22
31
|
export const DEFAULT_DEDICATED_WORKER_TARGET = Object.freeze({
|
|
23
|
-
model:
|
|
24
|
-
variant:
|
|
32
|
+
model: DEDICATED_WORKER_MODEL,
|
|
33
|
+
variant: DEDICATED_WORKER_VARIANT,
|
|
34
|
+
});
|
|
35
|
+
/** The declared escalation target for a host that chooses to pay for the stronger worker model. */
|
|
36
|
+
export const ESCALATION_WORKER_TARGET = Object.freeze({
|
|
37
|
+
model: ESCALATION_WORKER_MODEL,
|
|
38
|
+
variant: ESCALATION_WORKER_VARIANT,
|
|
25
39
|
});
|
|
26
40
|
/**
|
|
27
41
|
* Fixed worker routes for one dedicated target. Which target is dedicated is a host decision, but
|
|
28
42
|
* every worker role resolves to that single target and never to a fallback.
|
|
29
43
|
*/
|
|
30
44
|
export function dedicatedWorkerRouting(target = DEFAULT_DEDICATED_WORKER_TARGET) {
|
|
31
|
-
return Object.freeze(Object.fromEntries(
|
|
45
|
+
return Object.freeze(Object.fromEntries(DEDICATED_WORKER_ROLES.map((role) => [role, Object.freeze({
|
|
32
46
|
preferred: Object.freeze(target.variant === undefined
|
|
33
47
|
? { model: target.model }
|
|
34
48
|
: { model: target.model, variant: target.variant }),
|
|
35
49
|
})])));
|
|
36
50
|
}
|
|
37
|
-
export const
|
|
38
|
-
export const FIXED_MODEL_ROUTING =
|
|
51
|
+
export const DEDICATED_WORKER_ROUTING = dedicatedWorkerRouting();
|
|
52
|
+
export const FIXED_MODEL_ROUTING = DEDICATED_WORKER_ROUTING;
|
|
39
53
|
const fixedModelRoleSet = new Set(Object.keys(FIXED_MODEL_ROUTING));
|
|
40
54
|
export const RECOMMENDED_LUNA_MODEL = "openai/gpt-5.6-luna";
|
|
41
|
-
|
|
42
|
-
|
|
55
|
+
/**
|
|
56
|
+
* The coordinator has no built-in route on purpose. It is the one agent the user drives directly and
|
|
57
|
+
* picks a model for in the session, so a shipped default here does not choose between models for an
|
|
58
|
+
* undecided user: it silently discards a choice the user already made and cannot see being reverted.
|
|
59
|
+
* Delegated roles are the opposite, because nobody selects a model for a session the loop spawns.
|
|
60
|
+
* A host that does want a fixed coordinator model still declares one through modelRouting.
|
|
61
|
+
*
|
|
62
|
+
* Evidence gathering is retrieval rather than reasoning, so the scout sits one effort tier below the
|
|
63
|
+
* worker, where the published cost curve returns the most per unit of cost.
|
|
64
|
+
*/
|
|
65
|
+
export const RECOMMENDED_SCOUT_VARIANT = "high";
|
|
66
|
+
export const RECOMMENDED_LUNA_ROLE_VARIANTS = Object.freeze({
|
|
67
|
+
"dog-scout": RECOMMENDED_SCOUT_VARIANT,
|
|
68
|
+
});
|
|
69
|
+
export const RECOMMENDED_LUNA_ROLES = Object.freeze(Object.keys(RECOMMENDED_LUNA_ROLE_VARIANTS));
|
|
43
70
|
/** Configurable MkII defaults. Project-local and global configuration may override these routes. */
|
|
44
|
-
export const RECOMMENDED_LUNA_ROUTING = Object.freeze(Object.fromEntries(
|
|
71
|
+
export const RECOMMENDED_LUNA_ROUTING = Object.freeze(Object.fromEntries(Object.entries(RECOMMENDED_LUNA_ROLE_VARIANTS).map(([role, variant]) => [role, Object.freeze({
|
|
45
72
|
preferred: Object.freeze({
|
|
46
73
|
model: RECOMMENDED_LUNA_MODEL,
|
|
47
|
-
variant
|
|
74
|
+
variant,
|
|
48
75
|
}),
|
|
49
76
|
})])));
|
|
50
77
|
/**
|
|
@@ -55,11 +82,15 @@ export const RECOMMENDED_LUNA_ROUTING = Object.freeze(Object.fromEntries(RECOMME
|
|
|
55
82
|
export const RECOMMENDED_CONSULTATION_MODEL = "anthropic/claude-opus-5";
|
|
56
83
|
export const RECOMMENDED_CONSULTATION_ROLES = Object.freeze(Object.values(CONSULTATION_ROLE_POLICY));
|
|
57
84
|
/** Review and advice carry the reasoning effort the worker target intentionally does not spend. */
|
|
58
|
-
export const
|
|
59
|
-
/**
|
|
85
|
+
export const CONSULTATION_FALLBACK_VARIANT = "xhigh";
|
|
86
|
+
/**
|
|
87
|
+
* The shipped consultation fallback. Review has to be able to reject work the worker just produced, so
|
|
88
|
+
* it stays on the stronger model even though the worker no longer defaults to it; a fallback equal to
|
|
89
|
+
* the worker target would review that work with exactly the capability that produced it.
|
|
90
|
+
*/
|
|
60
91
|
export const DEFAULT_CONSULTATION_FALLBACK_TARGET = Object.freeze({
|
|
61
|
-
model:
|
|
62
|
-
variant:
|
|
92
|
+
model: ESCALATION_WORKER_MODEL,
|
|
93
|
+
variant: CONSULTATION_FALLBACK_VARIANT,
|
|
63
94
|
});
|
|
64
95
|
function frozenTarget(target) {
|
|
65
96
|
return Object.freeze(target.variant === undefined
|
|
@@ -97,8 +128,8 @@ export function recommendedRoleRouting(fallbackTarget = DEFAULT_DEDICATED_WORKER
|
|
|
97
128
|
});
|
|
98
129
|
}
|
|
99
130
|
export const RECOMMENDED_ROLE_ROUTING = recommendedRoleRouting();
|
|
100
|
-
export function
|
|
101
|
-
return
|
|
131
|
+
export function isDedicatedWorkerRole(role) {
|
|
132
|
+
return dedicatedWorkerRoleSet.has(role);
|
|
102
133
|
}
|
|
103
134
|
export function isFixedModelRole(role) {
|
|
104
135
|
return fixedModelRoleSet.has(role);
|
|
@@ -107,12 +138,13 @@ export function isFixedModelRole(role) {
|
|
|
107
138
|
export const BUILT_IN_MODEL_CATALOG = Object.freeze({
|
|
108
139
|
global: Object.freeze([
|
|
109
140
|
Object.freeze({
|
|
110
|
-
model:
|
|
111
|
-
variants: Object.freeze([
|
|
141
|
+
model: DEDICATED_WORKER_MODEL,
|
|
142
|
+
variants: Object.freeze([DEDICATED_WORKER_VARIANT, RECOMMENDED_SCOUT_VARIANT]
|
|
143
|
+
.filter((variant, index, all) => all.indexOf(variant) === index)),
|
|
112
144
|
}),
|
|
113
145
|
Object.freeze({
|
|
114
|
-
model:
|
|
115
|
-
variants: Object.freeze([
|
|
146
|
+
model: ESCALATION_WORKER_MODEL,
|
|
147
|
+
variants: Object.freeze([ESCALATION_WORKER_VARIANT, CONSULTATION_FALLBACK_VARIANT]),
|
|
116
148
|
}),
|
|
117
149
|
]),
|
|
118
150
|
});
|