@sayknow-cli/coding-agent 0.5.25 → 0.5.26
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/CHANGELOG.md +13 -1
- package/dist/types/config/settings-schema.d.ts +26 -0
- package/dist/types/config/task-model-specialties.d.ts +55 -0
- package/dist/types/decisions/task-routing.d.ts +96 -11
- package/dist/types/i18n/messages/en.d.ts +15 -0
- package/dist/types/lsp/index.d.ts +1 -1
- package/dist/types/lsp/types.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +11 -0
- package/dist/types/task/index.d.ts +1 -1
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/types.d.ts +114 -18
- package/dist/types/tools/subagent.d.ts +2 -2
- package/package.json +7 -7
- package/src/config/settings-schema.ts +37 -7
- package/src/config/task-model-specialties.ts +131 -0
- package/src/decisions/task-routing.ts +382 -66
- package/src/i18n/messages/de.ts +16 -0
- package/src/i18n/messages/en.ts +16 -0
- package/src/i18n/messages/es.ts +16 -0
- package/src/i18n/messages/fr.ts +16 -0
- package/src/i18n/messages/ja.ts +16 -0
- package/src/i18n/messages/ko.ts +16 -0
- package/src/i18n/messages/zh.ts +16 -0
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/main.ts +1 -1
- package/src/modes/components/model-selector.ts +275 -34
- package/src/modes/controllers/selector-controller.ts +50 -2
- package/src/modes/shared/agent-wire/command-dispatch.ts +1 -1
- package/src/prompts/tools/task.md +1 -0
- package/src/slash-commands/builtin-registry.ts +11 -9
- package/src/task/index.ts +98 -40
- package/src/task/receipt.ts +3 -0
- package/src/task/types.ts +44 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
## [0.5.
|
|
5
|
+
## [0.5.26] - 2026-09-23
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
- `/model` role rows now open a second level so a model can be assigned to a *detailed use* rather than only to a whole role. `planner`/`architect` offer backend architecture and frontend design, `executor` offers implementation and testing, `critic` offers review; `default` has none and still assigns in one keystroke. The two bulk rows deliberately stay canonical-only — an action that also rewrote five detailed-use overrides could not be undone from the same menu. Assignments persist to the new `task.modelRouting.specialtyModels` record, whose keys are allowlisted to those five ids. Saving one while `task.modelRouting.enabled` is off persists it and says so instead of silently enabling a feature you did not ask for. First-level rows are now stable descriptors rather than positional index arithmetic, which is what previously made every added row a chance to assign a model to the wrong role.
|
|
9
|
+
- Subagent model routing now runs **one classification per child task** instead of one per `task` call. A batch shares an agent but not a workload, so an implementation slice and a test slice in the same call no longer have to share a single verdict. Each child dispatches its own ordered chain — detailed-use model, then tier model, then the role's configured chain — so a detailed-use model that cannot authenticate still falls through to something the role can actually run. A high-risk assignment skips the detailed-use axis entirely, because a lateral swap can move sideways into something weaker.
|
|
10
|
+
- Receipts and progress now carry what the router **requested** separately from what the spawn ran on. Because the dispatched value is a fallback chain, conflating the two would let a receipt claim a detailed-use model was used when the chain actually fell through to the baseline. When the decision backend is an ordinary LLM it returns no probabilities at all, so the record carries an ordinal clarity score and `calibrated: false` rather than a fabricated confidence.
|
|
11
|
+
- The `task` tool accepts `.specialty` per task. Declaring the kind of work is the deterministic path to a detailed-use model: no classifier runs, `task.modelRouting.enabled` is not consulted, and no confidence bar applies — if the user assigned a model to that specialty under `/model`, the child runs on it and leaves it only on a transport error (429, 5xx, auth, quota), when the child session's fallback chain advances to the role baseline composed behind it. A declared specialty ignores the menu's role grouping on purpose: the setting is one flat map, and a frontend build delegated to `executor` should get the frontend model. Receipts mark these `declared: true` so they cannot be mistaken for a classifier verdict. Auto-detection from assignment text is unchanged and still opt-in.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Model-selector suites that assert UI copy now pin the locale — and restore it. They previously inherited whatever `language` the developer had configured, because a sibling suite restores the real agent directory and reloads real settings, and one suite pinned Korean without putting it back — so whether they passed depended on test file ordering.
|
|
16
|
+
- The first-level `/model` role rows (`Set as EXECUTOR (Executor)`) were hardcoded English inside an otherwise localized menu. They now go through i18n in all seven locales; the redundant `(Name)` suffix is dropped where the locale does not need it, and the role tag stays verbatim because it is the same identifier receipts and `/model` arguments use.
|
|
17
|
+
- Login, `--list-models`, `/model`, and `/model provider/id` now refresh provider catalogs online so newly published Kimi/Grok (and other OpenAI-compatible) ids appear without waiting for a bundled `models.json` regen or a 2h cache TTL.
|
|
6
18
|
|
|
7
19
|
## [0.5.22] - 2026-09-19
|
|
8
20
|
|
|
@@ -104,6 +104,11 @@ interface RecordDef<T> {
|
|
|
104
104
|
type: "record";
|
|
105
105
|
default: Record<string, T>;
|
|
106
106
|
valueSchema?: RecordValueDef;
|
|
107
|
+
/**
|
|
108
|
+
* Closed key set. When present, reconciliation rejects any other key instead of
|
|
109
|
+
* silently carrying a typo that no consumer will ever read.
|
|
110
|
+
*/
|
|
111
|
+
keys?: readonly string[];
|
|
107
112
|
ui?: UiBase;
|
|
108
113
|
}
|
|
109
114
|
type SettingDef = BooleanDef | StringDef | NumberDef | EnumDef<readonly string[]> | ArrayDef<unknown> | RecordDef<unknown>;
|
|
@@ -2471,6 +2476,27 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
2471
2476
|
readonly type: "string";
|
|
2472
2477
|
readonly default: "";
|
|
2473
2478
|
};
|
|
2479
|
+
/**
|
|
2480
|
+
* Per-specialty models — the **work-kind** axis.
|
|
2481
|
+
*
|
|
2482
|
+
* Keys are the bounded ids in `config/task-model-specialties.ts`; values use the
|
|
2483
|
+
* same selector grammar as any other model setting, so a chain is allowed. A
|
|
2484
|
+
* specialty with no entry inherits the role's resolved model unchanged, which is
|
|
2485
|
+
* why the default is empty rather than pre-populated.
|
|
2486
|
+
*
|
|
2487
|
+
* These are routing hints, never agents: nothing here widens the canonical role
|
|
2488
|
+
* roster, model-profile role keys, tool grants, or spawn permissions. Saving one
|
|
2489
|
+
* does not enable `task.modelRouting.enabled`; the assignment surface reports the
|
|
2490
|
+
* disabled state instead of silently turning routing on.
|
|
2491
|
+
*/
|
|
2492
|
+
readonly "task.modelRouting.specialtyModels": {
|
|
2493
|
+
readonly type: "record";
|
|
2494
|
+
readonly default: Record<string, ModelSelectorValue>;
|
|
2495
|
+
readonly valueSchema: {
|
|
2496
|
+
readonly type: "model-selector-value";
|
|
2497
|
+
};
|
|
2498
|
+
readonly keys: readonly ["backendArchitecture", "frontendDesign", "implementation", "testing", "review"];
|
|
2499
|
+
};
|
|
2474
2500
|
readonly "ttsr.enabled": {
|
|
2475
2501
|
readonly type: "boolean";
|
|
2476
2502
|
readonly default: true;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Stable configuration/routing identifiers. Display strings are localized separately. */
|
|
2
|
+
export declare const TASK_MODEL_SPECIALTY_IDS: readonly ["backendArchitecture", "frontendDesign", "implementation", "testing", "review"];
|
|
3
|
+
export type TaskModelSpecialty = (typeof TASK_MODEL_SPECIALTY_IDS)[number];
|
|
4
|
+
/**
|
|
5
|
+
* Which canonical role agents may receive each specialty.
|
|
6
|
+
*
|
|
7
|
+
* Planning roles share the two design specialties on purpose: the user's intent is
|
|
8
|
+
* "a backend-strong model plans backend work", not "planner and architect each get
|
|
9
|
+
* their own private backend model". Implementation roles never take a design
|
|
10
|
+
* specialty, mirroring the existing frontend-domain rule.
|
|
11
|
+
*/
|
|
12
|
+
export declare const TASK_MODEL_SPECIALTY_ROLES: Readonly<Record<TaskModelSpecialty, readonly string[]>>;
|
|
13
|
+
/** Neutral classifier outcome: the work does not clearly belong to one specialty. */
|
|
14
|
+
export declare const TASK_MODEL_SPECIALTY_NONE: "none";
|
|
15
|
+
/** Bounded-key predicate for the `task.modelRouting.specialtyModels` record. */
|
|
16
|
+
export declare function isTaskModelSpecialty(value: unknown): value is TaskModelSpecialty;
|
|
17
|
+
/** Specialties a given canonical role agent is allowed to receive, in declaration order. */
|
|
18
|
+
export declare function specialtiesForRole(agentName: string): TaskModelSpecialty[];
|
|
19
|
+
/** Whether a specialty may be applied to work routed to this canonical role agent. */
|
|
20
|
+
export declare function specialtySupportsRole(specialty: TaskModelSpecialty, agentName: string): boolean;
|
|
21
|
+
/** Where a composed candidate came from, so a receipt never overstates what was selected. */
|
|
22
|
+
export type TaskRoutingSource = "specialty" | "legacy-frontend" | "tier" | "baseline";
|
|
23
|
+
export interface TaskRoutingCandidate {
|
|
24
|
+
/** The configured selector, normalized but otherwise untouched. */
|
|
25
|
+
selector: string;
|
|
26
|
+
source: TaskRoutingSource;
|
|
27
|
+
/** Set when `source` is `specialty` or `legacy-frontend`. */
|
|
28
|
+
specialty?: TaskModelSpecialty;
|
|
29
|
+
/** Set when `source` is `tier`. */
|
|
30
|
+
tier?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Identity used for deduplication.
|
|
34
|
+
*
|
|
35
|
+
* An explicit thinking suffix is part of the identity: `provider/model:high` and
|
|
36
|
+
* `provider/model:low` are different configured intents, and collapsing them would
|
|
37
|
+
* silently drop the user's effort choice from a chain.
|
|
38
|
+
*/
|
|
39
|
+
export declare function specialtySelectorIdentity(selector: string): string;
|
|
40
|
+
/** The `provider/model` part, ignoring any thinking suffix. Used for "same model" checks. */
|
|
41
|
+
export declare function specialtySelectorHead(selector: string): string;
|
|
42
|
+
/** Expand a configured selector value into normalized candidates tagged with their origin. */
|
|
43
|
+
export declare function toRoutingCandidates(value: string | readonly string[] | undefined, source: TaskRoutingSource, extra?: {
|
|
44
|
+
specialty?: TaskModelSpecialty;
|
|
45
|
+
tier?: string;
|
|
46
|
+
}): TaskRoutingCandidate[];
|
|
47
|
+
/**
|
|
48
|
+
* Concatenate candidate segments, keeping the first occurrence of each identity.
|
|
49
|
+
*
|
|
50
|
+
* A selector that also exists in the baseline segment is attributed to `baseline`
|
|
51
|
+
* even when an earlier specialty segment introduced it. Resolving that entry proves
|
|
52
|
+
* only that the role's own configured model was usable — reporting it as a specialty
|
|
53
|
+
* hit would claim a routing decision that never happened.
|
|
54
|
+
*/
|
|
55
|
+
export declare function dedupeRoutingCandidates(segments: readonly TaskRoutingCandidate[][]): TaskRoutingCandidate[];
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { ModelSelectorValue } from "../config/model-selector-value";
|
|
2
|
+
import type { Settings } from "../config/settings";
|
|
3
|
+
import { type TaskModelSpecialty, type TaskRoutingCandidate, type TaskRoutingSource } from "../config/task-model-specialties";
|
|
1
4
|
import type { DecisionService } from "./index";
|
|
2
5
|
/** Ordered cheapest to most capable. The order *is* the policy's direction. */
|
|
3
6
|
export declare const TASK_TIERS: readonly ["fast", "balanced", "deep"];
|
|
@@ -22,26 +25,57 @@ export interface TaskRoutingPolicy {
|
|
|
22
25
|
/**
|
|
23
26
|
* Model for frontend planning, when the assignment reads as frontend work.
|
|
24
27
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* planning roles ever take it, and only laterally — the implementation roles
|
|
28
|
-
* stay on the difficulty ladder.
|
|
28
|
+
* Superseded by `specialtyModels.frontendDesign`; kept as the fallback source
|
|
29
|
+
* so an existing configuration keeps working untouched until it is migrated.
|
|
29
30
|
*/
|
|
30
31
|
frontendModel?: string;
|
|
31
32
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
33
|
+
* Per-specialty models — the work-kind axis.
|
|
34
|
+
*
|
|
35
|
+
* Absent entries inherit the role's own chain, which is why an unset specialty
|
|
36
|
+
* is not an error and does not suppress the difficulty ladder.
|
|
37
|
+
*/
|
|
38
|
+
specialtyModels?: Partial<Record<TaskModelSpecialty, ModelSelectorValue>>;
|
|
39
|
+
/**
|
|
40
|
+
* Bar for a lateral swap on a **calibrated** backend. Directional bars do not
|
|
41
|
+
* apply here because neither direction is "spending more": being wrong either
|
|
42
|
+
* way costs quality, symmetrically, so one bar is the whole story.
|
|
35
43
|
*/
|
|
36
44
|
minDomainConfidence: number;
|
|
45
|
+
/**
|
|
46
|
+
* Bar for a lateral swap on an **uncalibrated** backend.
|
|
47
|
+
*
|
|
48
|
+
* The ordinary logged-in model cannot report a probability, and asking it for
|
|
49
|
+
* one measurably degrades the answer, so its choice carries no `confidence`.
|
|
50
|
+
* What it *can* report is an ordinal strength. Requiring a high ordinal is not
|
|
51
|
+
* the same guarantee as a calibrated threshold, and the result is recorded as
|
|
52
|
+
* uncalibrated — but refusing to route at all would make a user's explicit
|
|
53
|
+
* specialty selection silently inert on the default backend.
|
|
54
|
+
*/
|
|
55
|
+
minSpecialtyOrdinal: number;
|
|
37
56
|
}
|
|
38
57
|
export declare const DEFAULT_TASK_ROUTING_POLICY: Omit<TaskRoutingPolicy, "tiers">;
|
|
58
|
+
/**
|
|
59
|
+
* The settings surface this module reads.
|
|
60
|
+
*
|
|
61
|
+
* Narrowed to `get` so the router cannot quietly start writing settings, and so
|
|
62
|
+
* a caller only has to supply a reader rather than a whole `Settings` instance.
|
|
63
|
+
*/
|
|
64
|
+
export type TaskRoutingSettingsReader = Pick<Settings, "get">;
|
|
65
|
+
/**
|
|
66
|
+
* Build the routing policy from settings, or null when routing must not run.
|
|
67
|
+
*
|
|
68
|
+
* Null is returned for two distinct reasons that both mean "leave the configured
|
|
69
|
+
* model alone": the feature is off, or it is on but nothing is configured to
|
|
70
|
+
* route *to*. A lone tier is not an axis — there is nowhere to move from it —
|
|
71
|
+
* so two tiers is the floor unless a specialty or the legacy frontend model
|
|
72
|
+
* supplies a lateral target instead.
|
|
73
|
+
*/
|
|
74
|
+
export declare function buildTaskRoutingPolicyFromSettings(settings: TaskRoutingSettingsReader): TaskRoutingPolicy | null;
|
|
39
75
|
/**
|
|
40
76
|
* Roles whose output is a plan or a design review.
|
|
41
77
|
*
|
|
42
|
-
*
|
|
43
|
-
* "a design-strong model *plans* the frontend; implementation stays where it
|
|
44
|
-
* is". Executor keeps the difficulty ladder regardless of domain.
|
|
78
|
+
* Derived from the specialty compatibility map so the two never drift apart.
|
|
45
79
|
*/
|
|
46
80
|
export declare const PLANNING_ROLES: ReadonlySet<string>;
|
|
47
81
|
export interface TaskRoutingRequest {
|
|
@@ -50,14 +84,65 @@ export interface TaskRoutingRequest {
|
|
|
50
84
|
assignment: string;
|
|
51
85
|
/** Whatever the role is configured to use today, used as the direction baseline. */
|
|
52
86
|
currentModel: string | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* The role's fully resolved chain, in order. The composed candidate list ends
|
|
89
|
+
* with this, so a specialty or tier that cannot be authenticated falls through
|
|
90
|
+
* to the model the role would have used anyway.
|
|
91
|
+
*/
|
|
92
|
+
baselineChain?: readonly string[];
|
|
53
93
|
signal?: AbortSignal;
|
|
54
94
|
}
|
|
55
95
|
export interface TaskRoutingResult {
|
|
96
|
+
/** Head of the composed chain — what the spawn runs on if it authenticates. */
|
|
56
97
|
model: string;
|
|
57
|
-
/** Null when the move was a
|
|
98
|
+
/** Null when the move was a specialty swap — that axis has no ladder. */
|
|
58
99
|
tier: TaskTier | null;
|
|
59
100
|
reason: string;
|
|
101
|
+
/** Ordered, provenance-tagged chain for the existing auth-aware resolver. */
|
|
102
|
+
candidates: TaskRoutingCandidate[];
|
|
103
|
+
/** What the classifier asked for. The *effective* source is only known after resolution. */
|
|
104
|
+
requestedSource: TaskRoutingSource;
|
|
105
|
+
requestedSpecialty?: TaskModelSpecialty;
|
|
106
|
+
requestedTier?: TaskTier;
|
|
107
|
+
/**
|
|
108
|
+
* True when the caller named the specialty on the spawn itself. No classifier
|
|
109
|
+
* ran, so `calibrated`/`confidence`/`ordinalStrength` describe nothing here.
|
|
110
|
+
*/
|
|
111
|
+
declared: boolean;
|
|
112
|
+
/** False means `ordinalStrength` ranks, and no probability was available. */
|
|
113
|
+
calibrated: boolean;
|
|
114
|
+
confidence?: number;
|
|
115
|
+
ordinalStrength?: number;
|
|
116
|
+
}
|
|
117
|
+
export interface DeclaredSpecialtyRequest {
|
|
118
|
+
agentName: string;
|
|
119
|
+
specialty: TaskModelSpecialty;
|
|
120
|
+
/** Whatever the role is configured to use today. */
|
|
121
|
+
currentModel: string | undefined;
|
|
122
|
+
/** The role's fully resolved chain; always the tail so a dead specialty model falls through. */
|
|
123
|
+
baselineChain?: readonly string[];
|
|
60
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Route a spawn whose caller *declared* the kind of work.
|
|
127
|
+
*
|
|
128
|
+
* This is the deterministic half of the specialty axis. Nothing here asks a
|
|
129
|
+
* classifier, reads `task.modelRouting.enabled`, or applies a confidence bar:
|
|
130
|
+
* the user put a model on this specialty in `/model`, the caller says this is
|
|
131
|
+
* that work, and the only remaining reason not to run on it is that it fails —
|
|
132
|
+
* which the child session's fallback chain handles at the transport boundary
|
|
133
|
+
* (429, 5xx, auth, quota) by advancing to the role's baseline behind it.
|
|
134
|
+
*
|
|
135
|
+
* Role eligibility is deliberately not checked. The menu groups specialties
|
|
136
|
+
* under the roles that usually do that work, but the setting is one flat map:
|
|
137
|
+
* a frontend model the user chose for design is the same frontend model they
|
|
138
|
+
* expect when the *implementation* of that frontend is delegated. Refusing
|
|
139
|
+
* here would make "frontend uses a different model" false for exactly the
|
|
140
|
+
* spawns where it matters most.
|
|
141
|
+
*
|
|
142
|
+
* Returns null only when nothing is configured for the specialty, or when the
|
|
143
|
+
* configured model is already what the role would run on anyway.
|
|
144
|
+
*/
|
|
145
|
+
export declare function resolveDeclaredSpecialtyRouting(settings: TaskRoutingSettingsReader, request: DeclaredSpecialtyRequest): TaskRoutingResult | null;
|
|
61
146
|
/**
|
|
62
147
|
* Decide the model for one subagent spawn, or null to leave the configured one alone.
|
|
63
148
|
*
|
|
@@ -76,6 +76,21 @@ export declare const en: {
|
|
|
76
76
|
readonly "modelSelector.noMatching": "No matching models.";
|
|
77
77
|
readonly "modelSelector.modelName": "Model Name: {value}";
|
|
78
78
|
readonly "modelSelector.actionFor": "Action for: {id}";
|
|
79
|
+
readonly "modelSelector.setAsTarget": "Set as {tag} ({name})";
|
|
80
|
+
readonly "modelSelector.setForAllRoleAgents": "Set for all role agents";
|
|
81
|
+
readonly "modelSelector.setForAllTargets": "Set for all targets";
|
|
82
|
+
readonly "modelSelector.hasDetailedUses": "(detailed uses)";
|
|
83
|
+
readonly "modelSelector.detailedUseFor": "Detailed use for {target}: {id}";
|
|
84
|
+
readonly "modelSelector.generalRole": "General (whole role)";
|
|
85
|
+
readonly "modelSelector.resetSpecialties": "Clear detailed-use overrides";
|
|
86
|
+
readonly "modelSelector.specialty.backendArchitecture": "Backend architecture";
|
|
87
|
+
readonly "modelSelector.specialty.frontendDesign": "Frontend design";
|
|
88
|
+
readonly "modelSelector.specialty.implementation": "Implementation";
|
|
89
|
+
readonly "modelSelector.specialty.testing": "Testing";
|
|
90
|
+
readonly "modelSelector.specialty.review": "Review";
|
|
91
|
+
readonly "modelSelector.specialtySaved": "{specialty} will use {value}.";
|
|
92
|
+
readonly "modelSelector.specialtySavedRoutingOff": "{specialty} will use {value} whenever a task declares that work. Auto-detection from assignment text stays off until you enable task.modelRouting.enabled.";
|
|
93
|
+
readonly "modelSelector.specialtyCleared": "Cleared detailed-use overrides for {target}.";
|
|
79
94
|
readonly "modelSelector.reasoningFor": "Reasoning for {target}: {id}";
|
|
80
95
|
readonly "modelSelector.temporaryModel": "temporary model";
|
|
81
96
|
};
|
|
@@ -95,6 +95,7 @@ export declare class LspTool implements AgentTool<typeof lspSchema, LspToolDetai
|
|
|
95
95
|
readonly description: string;
|
|
96
96
|
readonly parameters: import("zod").ZodObject<{
|
|
97
97
|
action: import("zod").ZodEnum<{
|
|
98
|
+
implementation: "implementation";
|
|
98
99
|
status: "status";
|
|
99
100
|
diagnostics: "diagnostics";
|
|
100
101
|
definition: "definition";
|
|
@@ -105,7 +106,6 @@ export declare class LspTool implements AgentTool<typeof lspSchema, LspToolDetai
|
|
|
105
106
|
rename_file: "rename_file";
|
|
106
107
|
code_actions: "code_actions";
|
|
107
108
|
type_definition: "type_definition";
|
|
108
|
-
implementation: "implementation";
|
|
109
109
|
reload: "reload";
|
|
110
110
|
capabilities: "capabilities";
|
|
111
111
|
request: "request";
|
|
@@ -3,6 +3,7 @@ import * as z from "zod/v4";
|
|
|
3
3
|
import type { OwnedProcess } from "../runtime/process-lifecycle";
|
|
4
4
|
export declare const lspSchema: z.ZodObject<{
|
|
5
5
|
action: z.ZodEnum<{
|
|
6
|
+
implementation: "implementation";
|
|
6
7
|
status: "status";
|
|
7
8
|
diagnostics: "diagnostics";
|
|
8
9
|
definition: "definition";
|
|
@@ -13,7 +14,6 @@ export declare const lspSchema: z.ZodObject<{
|
|
|
13
14
|
rename_file: "rename_file";
|
|
14
15
|
code_actions: "code_actions";
|
|
15
16
|
type_definition: "type_definition";
|
|
16
|
-
implementation: "implementation";
|
|
17
17
|
reload: "reload";
|
|
18
18
|
capabilities: "capabilities";
|
|
19
19
|
request: "request";
|
|
@@ -5,6 +5,7 @@ import type { ModelRegistry, SkcModelAssignmentTargetId } from "../../config/mod
|
|
|
5
5
|
import { type ScopedModelSelection } from "../../config/model-resolver";
|
|
6
6
|
import type { ModelProfileConfig } from "../../config/models-config-schema";
|
|
7
7
|
import type { Settings } from "../../config/settings";
|
|
8
|
+
import { type TaskModelSpecialty } from "../../config/task-model-specialties";
|
|
8
9
|
type ScopedModelItem = ScopedModelSelection;
|
|
9
10
|
export type ModelSelectorSelection = {
|
|
10
11
|
kind: "assignment";
|
|
@@ -17,6 +18,16 @@ export type ModelSelectorSelection = {
|
|
|
17
18
|
kind: "profile";
|
|
18
19
|
profileName: string;
|
|
19
20
|
setDefault: boolean;
|
|
21
|
+
} | {
|
|
22
|
+
kind: "specialtyAssignment";
|
|
23
|
+
model: Model;
|
|
24
|
+
role: SkcModelAssignmentTargetId;
|
|
25
|
+
specialty: TaskModelSpecialty;
|
|
26
|
+
thinkingLevel?: ThinkingLevel;
|
|
27
|
+
selector?: string;
|
|
28
|
+
} | {
|
|
29
|
+
kind: "specialtyReset";
|
|
30
|
+
role: SkcModelAssignmentTargetId;
|
|
20
31
|
} | {
|
|
21
32
|
kind: "createProfile";
|
|
22
33
|
profile: ModelProfileConfig;
|
|
@@ -12,7 +12,7 @@ export { isValidAllocatedTaskId, isValidTaskId, TASK_ID_DESCRIPTION, TASK_ID_PAT
|
|
|
12
12
|
export { AgentOutputManager } from "./output-manager";
|
|
13
13
|
export type { TaskResultReceipt } from "./receipt";
|
|
14
14
|
export { assertNoRawTaskFields, buildTaskReceipt, buildTaskRoi, buildTaskRoiSummary, findRawTaskLeakKeys, sanitizeTaskToolDetails, } from "./receipt";
|
|
15
|
-
export type { AgentDefinition, AgentProgress, SingleResult, SubagentLifecyclePayload, SubagentProgressPayload, TaskParams, TaskToolDetails, } from "./types";
|
|
15
|
+
export type { AgentDefinition, AgentProgress, SingleResult, SubagentLifecyclePayload, SubagentProgressPayload, TaskParams, TaskRoutingAttribution, TaskToolDetails, } from "./types";
|
|
16
16
|
export { TASK_SUBAGENT_EVENT_CHANNEL, TASK_SUBAGENT_LIFECYCLE_CHANNEL, TASK_SUBAGENT_PROGRESS_CHANNEL, taskSchema, } from "./types";
|
|
17
17
|
export declare function resolveForkContextMaxTokens(configured: number, model: Model | undefined): number;
|
|
18
18
|
/**
|
|
@@ -28,6 +28,8 @@ export interface TaskResultReceipt {
|
|
|
28
28
|
contextTokens?: number;
|
|
29
29
|
contextWindow?: number;
|
|
30
30
|
modelOverride?: string | string[];
|
|
31
|
+
/** What the router asked for, kept separate from what the spawn ran on. */
|
|
32
|
+
routing?: SingleResult["routing"];
|
|
31
33
|
modelSubstitutionWarning?: SingleResult["modelSubstitutionWarning"];
|
|
32
34
|
usage?: SingleResult["usage"];
|
|
33
35
|
cost?: number;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@sayknow-cli/agent-core";
|
|
2
2
|
import type { Usage } from "@sayknow-cli/ai";
|
|
3
3
|
import * as z from "zod/v4";
|
|
4
|
+
import { type TaskModelSpecialty, type TaskRoutingSource } from "../config/task-model-specialties";
|
|
5
|
+
import type { TaskTier } from "../decisions/task-routing";
|
|
4
6
|
import type { TaskResultReceipt } from "./receipt";
|
|
5
7
|
import type { SpawnRoiReconciliation } from "./roi-reconciliation";
|
|
6
8
|
import { type TaskSimpleMode } from "./simple-mode";
|
|
@@ -49,12 +51,19 @@ export declare const taskItemSchema: z.ZodObject<{
|
|
|
49
51
|
default: "default";
|
|
50
52
|
"ultragoal-red-team": "ultragoal-red-team";
|
|
51
53
|
}>>;
|
|
54
|
+
specialty: z.ZodOptional<z.ZodEnum<{
|
|
55
|
+
backendArchitecture: "backendArchitecture";
|
|
56
|
+
frontendDesign: "frontendDesign";
|
|
57
|
+
implementation: "implementation";
|
|
58
|
+
testing: "testing";
|
|
59
|
+
review: "review";
|
|
60
|
+
}>>;
|
|
52
61
|
inheritContext: z.ZodOptional<z.ZodEnum<{
|
|
53
62
|
none: "none";
|
|
54
|
-
|
|
63
|
+
full: "full";
|
|
55
64
|
"last-turn": "last-turn";
|
|
65
|
+
receipt: "receipt";
|
|
56
66
|
bounded: "bounded";
|
|
57
|
-
full: "full";
|
|
58
67
|
}>>;
|
|
59
68
|
repositoryBinding: z.ZodOptional<z.ZodObject<{
|
|
60
69
|
schema: z.ZodLiteral<"skc.repository_binding.v1">;
|
|
@@ -77,12 +86,19 @@ export declare const taskSchema: z.ZodObject<{
|
|
|
77
86
|
default: "default";
|
|
78
87
|
"ultragoal-red-team": "ultragoal-red-team";
|
|
79
88
|
}>>;
|
|
89
|
+
specialty: z.ZodOptional<z.ZodEnum<{
|
|
90
|
+
backendArchitecture: "backendArchitecture";
|
|
91
|
+
frontendDesign: "frontendDesign";
|
|
92
|
+
implementation: "implementation";
|
|
93
|
+
testing: "testing";
|
|
94
|
+
review: "review";
|
|
95
|
+
}>>;
|
|
80
96
|
inheritContext: z.ZodOptional<z.ZodEnum<{
|
|
81
97
|
none: "none";
|
|
82
|
-
|
|
98
|
+
full: "full";
|
|
83
99
|
"last-turn": "last-turn";
|
|
100
|
+
receipt: "receipt";
|
|
84
101
|
bounded: "bounded";
|
|
85
|
-
full: "full";
|
|
86
102
|
}>>;
|
|
87
103
|
repositoryBinding: z.ZodOptional<z.ZodObject<{
|
|
88
104
|
schema: z.ZodLiteral<"skc.repository_binding.v1">;
|
|
@@ -112,12 +128,19 @@ export declare const taskSchemaNoIsolation: z.ZodObject<{
|
|
|
112
128
|
default: "default";
|
|
113
129
|
"ultragoal-red-team": "ultragoal-red-team";
|
|
114
130
|
}>>;
|
|
131
|
+
specialty: z.ZodOptional<z.ZodEnum<{
|
|
132
|
+
backendArchitecture: "backendArchitecture";
|
|
133
|
+
frontendDesign: "frontendDesign";
|
|
134
|
+
implementation: "implementation";
|
|
135
|
+
testing: "testing";
|
|
136
|
+
review: "review";
|
|
137
|
+
}>>;
|
|
115
138
|
inheritContext: z.ZodOptional<z.ZodEnum<{
|
|
116
139
|
none: "none";
|
|
117
|
-
|
|
140
|
+
full: "full";
|
|
118
141
|
"last-turn": "last-turn";
|
|
142
|
+
receipt: "receipt";
|
|
119
143
|
bounded: "bounded";
|
|
120
|
-
full: "full";
|
|
121
144
|
}>>;
|
|
122
145
|
repositoryBinding: z.ZodOptional<z.ZodObject<{
|
|
123
146
|
schema: z.ZodLiteral<"skc.repository_binding.v1">;
|
|
@@ -147,12 +170,19 @@ declare const ALL_TASK_SCHEMAS: readonly [z.ZodObject<{
|
|
|
147
170
|
default: "default";
|
|
148
171
|
"ultragoal-red-team": "ultragoal-red-team";
|
|
149
172
|
}>>;
|
|
173
|
+
specialty: z.ZodOptional<z.ZodEnum<{
|
|
174
|
+
backendArchitecture: "backendArchitecture";
|
|
175
|
+
frontendDesign: "frontendDesign";
|
|
176
|
+
implementation: "implementation";
|
|
177
|
+
testing: "testing";
|
|
178
|
+
review: "review";
|
|
179
|
+
}>>;
|
|
150
180
|
inheritContext: z.ZodOptional<z.ZodEnum<{
|
|
151
181
|
none: "none";
|
|
152
|
-
|
|
182
|
+
full: "full";
|
|
153
183
|
"last-turn": "last-turn";
|
|
184
|
+
receipt: "receipt";
|
|
154
185
|
bounded: "bounded";
|
|
155
|
-
full: "full";
|
|
156
186
|
}>>;
|
|
157
187
|
repositoryBinding: z.ZodOptional<z.ZodObject<{
|
|
158
188
|
schema: z.ZodLiteral<"skc.repository_binding.v1">;
|
|
@@ -181,12 +211,19 @@ declare const ALL_TASK_SCHEMAS: readonly [z.ZodObject<{
|
|
|
181
211
|
default: "default";
|
|
182
212
|
"ultragoal-red-team": "ultragoal-red-team";
|
|
183
213
|
}>>;
|
|
214
|
+
specialty: z.ZodOptional<z.ZodEnum<{
|
|
215
|
+
backendArchitecture: "backendArchitecture";
|
|
216
|
+
frontendDesign: "frontendDesign";
|
|
217
|
+
implementation: "implementation";
|
|
218
|
+
testing: "testing";
|
|
219
|
+
review: "review";
|
|
220
|
+
}>>;
|
|
184
221
|
inheritContext: z.ZodOptional<z.ZodEnum<{
|
|
185
222
|
none: "none";
|
|
186
|
-
|
|
223
|
+
full: "full";
|
|
187
224
|
"last-turn": "last-turn";
|
|
225
|
+
receipt: "receipt";
|
|
188
226
|
bounded: "bounded";
|
|
189
|
-
full: "full";
|
|
190
227
|
}>>;
|
|
191
228
|
repositoryBinding: z.ZodOptional<z.ZodObject<{
|
|
192
229
|
schema: z.ZodLiteral<"skc.repository_binding.v1">;
|
|
@@ -215,12 +252,19 @@ declare const ALL_TASK_SCHEMAS: readonly [z.ZodObject<{
|
|
|
215
252
|
default: "default";
|
|
216
253
|
"ultragoal-red-team": "ultragoal-red-team";
|
|
217
254
|
}>>;
|
|
255
|
+
specialty: z.ZodOptional<z.ZodEnum<{
|
|
256
|
+
backendArchitecture: "backendArchitecture";
|
|
257
|
+
frontendDesign: "frontendDesign";
|
|
258
|
+
implementation: "implementation";
|
|
259
|
+
testing: "testing";
|
|
260
|
+
review: "review";
|
|
261
|
+
}>>;
|
|
218
262
|
inheritContext: z.ZodOptional<z.ZodEnum<{
|
|
219
263
|
none: "none";
|
|
220
|
-
|
|
264
|
+
full: "full";
|
|
221
265
|
"last-turn": "last-turn";
|
|
266
|
+
receipt: "receipt";
|
|
222
267
|
bounded: "bounded";
|
|
223
|
-
full: "full";
|
|
224
268
|
}>>;
|
|
225
269
|
repositoryBinding: z.ZodOptional<z.ZodObject<{
|
|
226
270
|
schema: z.ZodLiteral<"skc.repository_binding.v1">;
|
|
@@ -249,12 +293,19 @@ declare const ALL_TASK_SCHEMAS: readonly [z.ZodObject<{
|
|
|
249
293
|
default: "default";
|
|
250
294
|
"ultragoal-red-team": "ultragoal-red-team";
|
|
251
295
|
}>>;
|
|
296
|
+
specialty: z.ZodOptional<z.ZodEnum<{
|
|
297
|
+
backendArchitecture: "backendArchitecture";
|
|
298
|
+
frontendDesign: "frontendDesign";
|
|
299
|
+
implementation: "implementation";
|
|
300
|
+
testing: "testing";
|
|
301
|
+
review: "review";
|
|
302
|
+
}>>;
|
|
252
303
|
inheritContext: z.ZodOptional<z.ZodEnum<{
|
|
253
304
|
none: "none";
|
|
254
|
-
|
|
305
|
+
full: "full";
|
|
255
306
|
"last-turn": "last-turn";
|
|
307
|
+
receipt: "receipt";
|
|
256
308
|
bounded: "bounded";
|
|
257
|
-
full: "full";
|
|
258
309
|
}>>;
|
|
259
310
|
repositoryBinding: z.ZodOptional<z.ZodObject<{
|
|
260
311
|
schema: z.ZodLiteral<"skc.repository_binding.v1">;
|
|
@@ -283,12 +334,19 @@ declare const ALL_TASK_SCHEMAS: readonly [z.ZodObject<{
|
|
|
283
334
|
default: "default";
|
|
284
335
|
"ultragoal-red-team": "ultragoal-red-team";
|
|
285
336
|
}>>;
|
|
337
|
+
specialty: z.ZodOptional<z.ZodEnum<{
|
|
338
|
+
backendArchitecture: "backendArchitecture";
|
|
339
|
+
frontendDesign: "frontendDesign";
|
|
340
|
+
implementation: "implementation";
|
|
341
|
+
testing: "testing";
|
|
342
|
+
review: "review";
|
|
343
|
+
}>>;
|
|
286
344
|
inheritContext: z.ZodOptional<z.ZodEnum<{
|
|
287
345
|
none: "none";
|
|
288
|
-
|
|
346
|
+
full: "full";
|
|
289
347
|
"last-turn": "last-turn";
|
|
348
|
+
receipt: "receipt";
|
|
290
349
|
bounded: "bounded";
|
|
291
|
-
full: "full";
|
|
292
350
|
}>>;
|
|
293
351
|
repositoryBinding: z.ZodOptional<z.ZodObject<{
|
|
294
352
|
schema: z.ZodLiteral<"skc.repository_binding.v1">;
|
|
@@ -317,12 +375,19 @@ declare const ALL_TASK_SCHEMAS: readonly [z.ZodObject<{
|
|
|
317
375
|
default: "default";
|
|
318
376
|
"ultragoal-red-team": "ultragoal-red-team";
|
|
319
377
|
}>>;
|
|
378
|
+
specialty: z.ZodOptional<z.ZodEnum<{
|
|
379
|
+
backendArchitecture: "backendArchitecture";
|
|
380
|
+
frontendDesign: "frontendDesign";
|
|
381
|
+
implementation: "implementation";
|
|
382
|
+
testing: "testing";
|
|
383
|
+
review: "review";
|
|
384
|
+
}>>;
|
|
320
385
|
inheritContext: z.ZodOptional<z.ZodEnum<{
|
|
321
386
|
none: "none";
|
|
322
|
-
|
|
387
|
+
full: "full";
|
|
323
388
|
"last-turn": "last-turn";
|
|
389
|
+
receipt: "receipt";
|
|
324
390
|
bounded: "bounded";
|
|
325
|
-
full: "full";
|
|
326
391
|
}>>;
|
|
327
392
|
repositoryBinding: z.ZodOptional<z.ZodObject<{
|
|
328
393
|
schema: z.ZodLiteral<"skc.repository_binding.v1">;
|
|
@@ -402,6 +467,33 @@ export interface ModelSubstitutionWarning {
|
|
|
402
467
|
effective: string;
|
|
403
468
|
reason: "auth_unavailable" | "assistant_model_mismatch";
|
|
404
469
|
}
|
|
470
|
+
/**
|
|
471
|
+
* What the model router *asked for* on one child — deliberately not what it ran on.
|
|
472
|
+
*
|
|
473
|
+
* The dispatched value is a fallback chain, so the head can lose to a later
|
|
474
|
+
* candidate when it fails to authenticate. Recording the request separately is
|
|
475
|
+
* what keeps a receipt from claiming a specialty model was used when the spawn
|
|
476
|
+
* actually fell through to the role's baseline. `ModelSubstitutionWarning`
|
|
477
|
+
* covers the disagreement; this covers the intent.
|
|
478
|
+
*/
|
|
479
|
+
export interface TaskRoutingAttribution {
|
|
480
|
+
/** Axis the head came from. `baseline` means the router declined to move. */
|
|
481
|
+
source: TaskRoutingSource;
|
|
482
|
+
/** Bounded specialty id, present only when the specialty axis won. */
|
|
483
|
+
specialty?: TaskModelSpecialty;
|
|
484
|
+
/** Tier the classifier settled on. Null for a specialty swap, which has no ladder. */
|
|
485
|
+
tier?: TaskTier;
|
|
486
|
+
/** True when the caller declared the specialty on the spawn; no classifier ran. */
|
|
487
|
+
declared: boolean;
|
|
488
|
+
/** False for ordinary LLM backends, which return no probabilities at all. */
|
|
489
|
+
calibrated: boolean;
|
|
490
|
+
/** Probability. Present only when `calibrated` is true — never synthesised. */
|
|
491
|
+
confidence?: number;
|
|
492
|
+
/** Ordinal clarity in [0,1], recorded in place of a probability when uncalibrated. */
|
|
493
|
+
ordinalStrength?: number;
|
|
494
|
+
/** Router's own explanation, surfaced verbatim on the receipt. */
|
|
495
|
+
reason: string;
|
|
496
|
+
}
|
|
405
497
|
/** Progress tracking for a single agent */
|
|
406
498
|
export interface AgentProgress {
|
|
407
499
|
index: number;
|
|
@@ -439,6 +531,8 @@ export interface AgentProgress {
|
|
|
439
531
|
durationMs: number;
|
|
440
532
|
modelOverride?: string | string[];
|
|
441
533
|
modelSubstitutionWarning?: ModelSubstitutionWarning;
|
|
534
|
+
/** What the router asked for on this child. See {@link TaskRoutingAttribution}. */
|
|
535
|
+
routing?: TaskRoutingAttribution;
|
|
442
536
|
/** Data extracted by registered subprocess tool handlers (keyed by tool name) */
|
|
443
537
|
extractedToolData?: Record<string, unknown[]>;
|
|
444
538
|
/**
|
|
@@ -500,6 +594,8 @@ export interface SingleResult {
|
|
|
500
594
|
/** Model's context window in tokens, when known. */
|
|
501
595
|
contextWindow?: number;
|
|
502
596
|
modelOverride?: string | string[];
|
|
597
|
+
/** What the router asked for on this child. See {@link TaskRoutingAttribution}. */
|
|
598
|
+
routing?: TaskRoutingAttribution;
|
|
503
599
|
modelSubstitutionWarning?: ModelSubstitutionWarning;
|
|
504
600
|
error?: string;
|
|
505
601
|
aborted?: boolean;
|
|
@@ -19,8 +19,8 @@ declare const subagentSchema: z.ZodObject<{
|
|
|
19
19
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
20
20
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
21
21
|
verbosity: z.ZodOptional<z.ZodEnum<{
|
|
22
|
-
receipt: "receipt";
|
|
23
22
|
full: "full";
|
|
23
|
+
receipt: "receipt";
|
|
24
24
|
preview: "preview";
|
|
25
25
|
}>>;
|
|
26
26
|
}, z.core.$strip>;
|
|
@@ -88,8 +88,8 @@ export declare class SubagentTool implements AgentTool<typeof subagentSchema, Su
|
|
|
88
88
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
89
89
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
90
90
|
verbosity: z.ZodOptional<z.ZodEnum<{
|
|
91
|
-
receipt: "receipt";
|
|
92
91
|
full: "full";
|
|
92
|
+
receipt: "receipt";
|
|
93
93
|
preview: "preview";
|
|
94
94
|
}>>;
|
|
95
95
|
}, z.core.$strip>;
|