snipara-companion 1.4.21 → 2.0.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/README.md +57 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +407 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -127,6 +127,7 @@ snipara-companion brief --task "ship auth hardening" --changed-files src/auth.ts
|
|
|
127
127
|
snipara-companion timeline
|
|
128
128
|
snipara-companion workflow phase-commit build --summary "tests green"
|
|
129
129
|
snipara-companion workflow impact-gate
|
|
130
|
+
snipara-companion workflow run --adaptive-routing-dry-run --route-local-workers "document a scoped change"
|
|
130
131
|
snipara-companion verify --changed-files src/auth.ts --diff-summary "auth hardening"
|
|
131
132
|
snipara-companion run --task "ship auth hardening" --changed-files src/auth.ts --release
|
|
132
133
|
snipara-companion handoff --summary "status command shipped" --next "publish package"
|
|
@@ -141,6 +142,9 @@ snipara-companion workflow resume --include-session-context
|
|
|
141
142
|
- `workflow impact-gate` audits committed local workflow phases that are ahead
|
|
142
143
|
of upstream but not pushed. It does not push, and dirty working-tree files are
|
|
143
144
|
reported separately from the committed diff.
|
|
145
|
+
- `workflow run --adaptive-routing-dry-run` prints an Adaptive Work Routing
|
|
146
|
+
card. Add `--route-local-workers` when a strong planner should keep deep
|
|
147
|
+
reasoning while a local worker handles scoped execution.
|
|
144
148
|
- `verify` builds a transparent verification plan from companion code impact
|
|
145
149
|
signals plus local package scripts. It recommends checks; it does not claim to
|
|
146
150
|
execute them.
|
|
@@ -167,6 +171,59 @@ if the hosted call still times out. A hosted final-commit timeout does not modif
|
|
|
167
171
|
Git state. Custom final-commit categories are namespaced under `final-commit`
|
|
168
172
|
before the hosted call so they stay on the handoff-only path.
|
|
169
173
|
|
|
174
|
+
## Adaptive Work Routing
|
|
175
|
+
|
|
176
|
+
Adaptive Work Routing is a recommendation-first path for routing scoped work to
|
|
177
|
+
the right worker class, endpoint type, and cost profile without hardcoding model
|
|
178
|
+
names in the CLI.
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
snipara-companion workflow run \
|
|
182
|
+
--mode full \
|
|
183
|
+
--adaptive-routing-dry-run \
|
|
184
|
+
--route-local-workers \
|
|
185
|
+
--routing-worker-role coding \
|
|
186
|
+
--routing-preferred-endpoint local \
|
|
187
|
+
--routing-allowed-endpoint local \
|
|
188
|
+
--routing-allowed-endpoint cloud \
|
|
189
|
+
--planner-retains-reasoning \
|
|
190
|
+
"Update documentation for the new gateway"
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The output is a routing card and handoff metadata. Companion calls the hosted
|
|
194
|
+
`snipara_adaptive_routing_catalog` tool when project auth and policy allow it,
|
|
195
|
+
records the sanitized runtime catalog in the handoff, and falls back to local
|
|
196
|
+
dry-run metadata when the hosted gateway is unavailable or omits
|
|
197
|
+
`success: true`. It does not silently spawn workers. The stable contract is
|
|
198
|
+
provider-neutral requirements such as worker role, reasoning depth, context
|
|
199
|
+
budget, endpoint type, write scope, and fallback.
|
|
200
|
+
|
|
201
|
+
Project owners can configure Adaptive Work Routing in Project > Automation:
|
|
202
|
+
`off`, `recommend`, or `catalog`; approval; planner-retained reasoning; allowed
|
|
203
|
+
endpoint types (`cloud`, `local`, `self_hosted`); worker classes; and budget
|
|
204
|
+
hints. `workflow run` reads that hosted policy and CLI flags cannot broaden it.
|
|
205
|
+
Project credentials stay server-side behind the hosted gateway; local endpoints
|
|
206
|
+
such as Ollama, LM Studio, AnythingLLM, or other OpenAI-compatible servers must
|
|
207
|
+
be reachable from the worker execution environment.
|
|
208
|
+
|
|
209
|
+
For the open package without Snipara SaaS, add a local policy file:
|
|
210
|
+
|
|
211
|
+
```json
|
|
212
|
+
{
|
|
213
|
+
"mode": "recommend",
|
|
214
|
+
"plannerRetainsReasoning": true,
|
|
215
|
+
"preferLocalWorkers": true,
|
|
216
|
+
"allowedEndpointTypes": ["local", "cloud"],
|
|
217
|
+
"preferredEndpointTypes": ["local"],
|
|
218
|
+
"allowedWorkerClasses": ["documentation", "tests", "review"],
|
|
219
|
+
"catalogLimit": 8
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Save it at `.snipara/adaptive-routing.json`. Without hosted configuration,
|
|
224
|
+
`workflow run` only emits local Adaptive Work Routing metadata and handoff files:
|
|
225
|
+
it does not query hosted context, call the hosted catalog, or spawn workers.
|
|
226
|
+
|
|
170
227
|
## Verification Plans
|
|
171
228
|
|
|
172
229
|
Use `verify` when an agent asks what to prove before handoff or release:
|
package/dist/index.d.ts
CHANGED
|
@@ -379,6 +379,33 @@ interface AutomationConfigBundle {
|
|
|
379
379
|
files: AutomationConfigFile[];
|
|
380
380
|
instructions: string[];
|
|
381
381
|
}
|
|
382
|
+
interface ProjectAutomationSettings {
|
|
383
|
+
automationClient?: string;
|
|
384
|
+
autoInjectContext?: boolean;
|
|
385
|
+
trackAccessedFiles?: boolean;
|
|
386
|
+
preserveOnCompaction?: boolean;
|
|
387
|
+
restoreOnSessionStart?: boolean;
|
|
388
|
+
enrichPrompts?: boolean;
|
|
389
|
+
maxTokensPerQuery?: number;
|
|
390
|
+
searchMode?: string;
|
|
391
|
+
includeSummaries?: boolean;
|
|
392
|
+
adaptiveRoutingMode?: string;
|
|
393
|
+
adaptiveRoutingRequireApproval?: boolean;
|
|
394
|
+
adaptiveRoutingPlannerRetainsReasoning?: boolean;
|
|
395
|
+
adaptiveRoutingPreferLocalWorkers?: boolean;
|
|
396
|
+
adaptiveRoutingAllowedEndpointTypes?: string[];
|
|
397
|
+
adaptiveRoutingPreferredEndpointTypes?: string[];
|
|
398
|
+
adaptiveRoutingAllowedWorkerClasses?: string[];
|
|
399
|
+
adaptiveRoutingFallback?: string;
|
|
400
|
+
adaptiveRoutingDailyBudgetCents?: number;
|
|
401
|
+
adaptiveRoutingMonthlyBudgetCents?: number;
|
|
402
|
+
adaptiveRoutingCatalogLimit?: number;
|
|
403
|
+
}
|
|
404
|
+
interface ProjectAutomationSettingsResult {
|
|
405
|
+
settings: ProjectAutomationSettings;
|
|
406
|
+
plan?: string;
|
|
407
|
+
featureAvailability?: Record<string, unknown>;
|
|
408
|
+
}
|
|
382
409
|
interface RecentAutomationEvent {
|
|
383
410
|
id: string;
|
|
384
411
|
sessionId: string;
|
|
@@ -986,6 +1013,7 @@ declare class RLMClient {
|
|
|
986
1013
|
limit?: number;
|
|
987
1014
|
}): Promise<RecentAutomationEventsResult>;
|
|
988
1015
|
getAutomationConfigBundle(client: string): Promise<AutomationConfigBundle>;
|
|
1016
|
+
getAutomationSettings(): Promise<ProjectAutomationSettingsResult>;
|
|
989
1017
|
evaluateStuckGuard(args?: EvaluateStuckGuardArgs): Promise<StuckGuardEvaluationResult>;
|
|
990
1018
|
getStuckGuardStatus(args?: {
|
|
991
1019
|
sessionId?: string;
|
|
@@ -2820,6 +2848,7 @@ interface AdaptiveModelRequirements {
|
|
|
2820
2848
|
writeScope: string[];
|
|
2821
2849
|
preferredEndpointTypes?: string[];
|
|
2822
2850
|
allowedEndpointTypes?: string[];
|
|
2851
|
+
catalogLimit?: number;
|
|
2823
2852
|
fallback: "main_agent";
|
|
2824
2853
|
}
|
|
2825
2854
|
interface AdaptiveRoutingCostEstimate {
|
|
@@ -2837,10 +2866,24 @@ interface AdaptiveRoutingCard {
|
|
|
2837
2866
|
reasons: string[];
|
|
2838
2867
|
warnings: string[];
|
|
2839
2868
|
}
|
|
2869
|
+
interface AdaptiveRoutingGatewayStatus {
|
|
2870
|
+
source: "hosted_mcp";
|
|
2871
|
+
success: boolean;
|
|
2872
|
+
resolutionStatus?: string;
|
|
2873
|
+
candidateCount: number;
|
|
2874
|
+
fallback?: string;
|
|
2875
|
+
warnings: string[];
|
|
2876
|
+
}
|
|
2877
|
+
interface AdaptiveRoutingRuntimeCatalog {
|
|
2878
|
+
version?: string;
|
|
2879
|
+
candidates: Array<Record<string, unknown>>;
|
|
2880
|
+
}
|
|
2840
2881
|
interface AdaptiveWorkRoutingRecommendation {
|
|
2841
2882
|
workProfile: AdaptiveWorkProfile;
|
|
2842
2883
|
requirements: AdaptiveModelRequirements;
|
|
2843
2884
|
routingCard: AdaptiveRoutingCard;
|
|
2885
|
+
gateway?: AdaptiveRoutingGatewayStatus;
|
|
2886
|
+
runtimeCatalog?: AdaptiveRoutingRuntimeCatalog;
|
|
2844
2887
|
}
|
|
2845
2888
|
interface AdaptiveWorkRoutingOptions {
|
|
2846
2889
|
query: string;
|
|
@@ -2850,6 +2893,7 @@ interface AdaptiveWorkRoutingOptions {
|
|
|
2850
2893
|
allowedEndpointTypes?: string[];
|
|
2851
2894
|
workerRole?: string;
|
|
2852
2895
|
plannerRetainsReasoning?: boolean;
|
|
2896
|
+
catalogLimit?: number;
|
|
2853
2897
|
}
|
|
2854
2898
|
interface OrchestratorHandoffArtifact {
|
|
2855
2899
|
schemaVersion: "snipara.orchestrator.handoff.v1";
|
|
@@ -2877,6 +2921,8 @@ interface OrchestratorHandoffArtifact {
|
|
|
2877
2921
|
workProfile?: AdaptiveWorkProfile;
|
|
2878
2922
|
requirements?: AdaptiveModelRequirements;
|
|
2879
2923
|
routingCard?: AdaptiveRoutingCard;
|
|
2924
|
+
gateway?: AdaptiveRoutingGatewayStatus;
|
|
2925
|
+
runtimeCatalog?: AdaptiveRoutingRuntimeCatalog;
|
|
2880
2926
|
};
|
|
2881
2927
|
task: {
|
|
2882
2928
|
title: string;
|
package/dist/index.js
CHANGED
|
@@ -1024,6 +1024,44 @@ var RLMClient = class {
|
|
|
1024
1024
|
clearTimeout(timeoutId);
|
|
1025
1025
|
}
|
|
1026
1026
|
}
|
|
1027
|
+
async getAutomationSettings() {
|
|
1028
|
+
if (!this.config.apiKey) {
|
|
1029
|
+
throw new Error("API key not configured. Run 'npx -y snipara-companion@latest init' first.");
|
|
1030
|
+
}
|
|
1031
|
+
const projectIdentifier = this.resolveProjectIdentifier();
|
|
1032
|
+
const projectId = encodeURIComponent(projectIdentifier);
|
|
1033
|
+
const url = `${this.dashboardApiUrl()}/api/projects/${projectId}/automation`;
|
|
1034
|
+
const controller = new AbortController();
|
|
1035
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
1036
|
+
try {
|
|
1037
|
+
const response = await this.fetchWithApiKeyRetry(
|
|
1038
|
+
url,
|
|
1039
|
+
{
|
|
1040
|
+
method: "GET",
|
|
1041
|
+
headers: {
|
|
1042
|
+
"Content-Type": "application/json"
|
|
1043
|
+
},
|
|
1044
|
+
signal: controller.signal
|
|
1045
|
+
},
|
|
1046
|
+
projectIdentifier
|
|
1047
|
+
);
|
|
1048
|
+
clearTimeout(timeoutId);
|
|
1049
|
+
if (!response.ok) {
|
|
1050
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1051
|
+
}
|
|
1052
|
+
const data = await response.json();
|
|
1053
|
+
if (!data.success || !data.data?.settings || typeof data.data.settings !== "object") {
|
|
1054
|
+
throw new Error("Automation settings response was invalid");
|
|
1055
|
+
}
|
|
1056
|
+
return {
|
|
1057
|
+
settings: data.data.settings,
|
|
1058
|
+
plan: typeof data.data.plan === "string" ? data.data.plan : void 0,
|
|
1059
|
+
featureAvailability: data.data.featureAvailability && typeof data.data.featureAvailability === "object" && !Array.isArray(data.data.featureAvailability) ? data.data.featureAvailability : void 0
|
|
1060
|
+
};
|
|
1061
|
+
} finally {
|
|
1062
|
+
clearTimeout(timeoutId);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1027
1065
|
async evaluateStuckGuard(args = {}) {
|
|
1028
1066
|
if (!this.config.apiKey) {
|
|
1029
1067
|
throw new Error("API key not configured. Run 'npx -y snipara-companion@latest init' first.");
|
|
@@ -11144,7 +11182,9 @@ function buildOrchestratorHandoff(options) {
|
|
|
11144
11182
|
...options.adaptiveRouting ? {
|
|
11145
11183
|
workProfile: options.adaptiveRouting.workProfile,
|
|
11146
11184
|
requirements: options.adaptiveRouting.requirements,
|
|
11147
|
-
routingCard: options.adaptiveRouting.routingCard
|
|
11185
|
+
routingCard: options.adaptiveRouting.routingCard,
|
|
11186
|
+
...options.adaptiveRouting.gateway ? { gateway: options.adaptiveRouting.gateway } : {},
|
|
11187
|
+
...options.adaptiveRouting.runtimeCatalog ? { runtimeCatalog: options.adaptiveRouting.runtimeCatalog } : {}
|
|
11148
11188
|
} : {}
|
|
11149
11189
|
},
|
|
11150
11190
|
task: {
|
|
@@ -11223,6 +11263,7 @@ function buildAdaptiveWorkRoutingRecommendation(options) {
|
|
|
11223
11263
|
writeScope: changedFiles,
|
|
11224
11264
|
preferredEndpointTypes,
|
|
11225
11265
|
allowedEndpointTypes,
|
|
11266
|
+
catalogLimit: options.catalogLimit,
|
|
11226
11267
|
fallback: "main_agent"
|
|
11227
11268
|
});
|
|
11228
11269
|
const warnings = [
|
|
@@ -11356,7 +11397,9 @@ function normalizeEndpointTypes(values) {
|
|
|
11356
11397
|
}
|
|
11357
11398
|
function compactObject2(value) {
|
|
11358
11399
|
return Object.fromEntries(
|
|
11359
|
-
Object.entries(value).filter(
|
|
11400
|
+
Object.entries(value).filter(
|
|
11401
|
+
([, item]) => item !== void 0 && !(Array.isArray(item) && item.length === 0)
|
|
11402
|
+
)
|
|
11360
11403
|
);
|
|
11361
11404
|
}
|
|
11362
11405
|
function writeOrchestratorHandoff(options) {
|
|
@@ -14549,6 +14592,8 @@ var FINAL_COMMIT_TIMEOUT_MS = 9e4;
|
|
|
14549
14592
|
var FINAL_COMMIT_RETRY_TIMEOUT_MS = 45e3;
|
|
14550
14593
|
var FINAL_COMMIT_SUMMARY_MAX_CHARS = 1200;
|
|
14551
14594
|
var FINAL_COMMIT_RETRY_SUMMARY_MAX_CHARS = 600;
|
|
14595
|
+
var DEFAULT_ADAPTIVE_ROUTING_CATALOG_LIMIT = 20;
|
|
14596
|
+
var ADAPTIVE_ROUTING_POLICY_RELATIVE_PATH = path18.join(".snipara", "adaptive-routing.json");
|
|
14552
14597
|
var SHARED_CONTEXT_INTENT_PATTERN = /\b(standard|standards|convention|conventions|guideline|guidelines|best practice|best practices|policy|policies|compliance|compliant|security rules|team rules|style guide|playbook|checklist)\b/i;
|
|
14553
14598
|
var DOCUMENT_SYNC_FORMATS = {
|
|
14554
14599
|
".adoc": { kind: "DOC", format: "adoc" },
|
|
@@ -14648,6 +14693,20 @@ function stringValue5(value) {
|
|
|
14648
14693
|
const stringified = String(value).trim();
|
|
14649
14694
|
return stringified || void 0;
|
|
14650
14695
|
}
|
|
14696
|
+
function numberValue5(value) {
|
|
14697
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
14698
|
+
return value;
|
|
14699
|
+
}
|
|
14700
|
+
const text = stringValue5(value);
|
|
14701
|
+
if (!text) {
|
|
14702
|
+
return void 0;
|
|
14703
|
+
}
|
|
14704
|
+
const parsed = Number(text);
|
|
14705
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
14706
|
+
}
|
|
14707
|
+
function uniqueStrings3(values) {
|
|
14708
|
+
return Array.from(new Set(values.filter((value) => value.trim().length > 0)));
|
|
14709
|
+
}
|
|
14651
14710
|
function normalizeEnum(value) {
|
|
14652
14711
|
return stringValue5(value)?.replace(/[-\s]/g, "_").toUpperCase() ?? "";
|
|
14653
14712
|
}
|
|
@@ -17064,6 +17123,12 @@ function printAdaptiveRoutingRecommendation(routing) {
|
|
|
17064
17123
|
if (routing.requirements.plannerRetainsReasoning) {
|
|
17065
17124
|
printKeyValue("Planner retains reasoning:", "yes");
|
|
17066
17125
|
}
|
|
17126
|
+
if (routing.gateway) {
|
|
17127
|
+
printKeyValue(
|
|
17128
|
+
"Hosted catalog:",
|
|
17129
|
+
routing.gateway.success ? `${routing.gateway.candidateCount} candidate(s), ${routing.gateway.resolutionStatus ?? "ready"}` : "unavailable"
|
|
17130
|
+
);
|
|
17131
|
+
}
|
|
17067
17132
|
if (routing.routingCard.reasons.length > 0) {
|
|
17068
17133
|
console.log("Reasons:");
|
|
17069
17134
|
for (const reason of routing.routingCard.reasons) {
|
|
@@ -17077,6 +17142,182 @@ function printAdaptiveRoutingRecommendation(routing) {
|
|
|
17077
17142
|
}
|
|
17078
17143
|
}
|
|
17079
17144
|
}
|
|
17145
|
+
function policyValue(settings, hostedKey, localKey) {
|
|
17146
|
+
const record = settings;
|
|
17147
|
+
return record[hostedKey] ?? record[localKey];
|
|
17148
|
+
}
|
|
17149
|
+
function readLocalAdaptiveRoutingProjectPolicy() {
|
|
17150
|
+
const workspaceRoot = findWorkspaceRoot(process.cwd(), true);
|
|
17151
|
+
if (!workspaceRoot) {
|
|
17152
|
+
return null;
|
|
17153
|
+
}
|
|
17154
|
+
const policyPath = path18.join(workspaceRoot, ADAPTIVE_ROUTING_POLICY_RELATIVE_PATH);
|
|
17155
|
+
if (!fs19.existsSync(policyPath)) {
|
|
17156
|
+
return null;
|
|
17157
|
+
}
|
|
17158
|
+
try {
|
|
17159
|
+
const parsed = JSON.parse(fs19.readFileSync(policyPath, "utf8"));
|
|
17160
|
+
if (!isRecord9(parsed)) {
|
|
17161
|
+
return null;
|
|
17162
|
+
}
|
|
17163
|
+
return normalizeAdaptiveRoutingProjectPolicy(parsed, "local_file");
|
|
17164
|
+
} catch {
|
|
17165
|
+
return null;
|
|
17166
|
+
}
|
|
17167
|
+
}
|
|
17168
|
+
async function loadAdaptiveRoutingProjectPolicy(client, fallbackPolicy = null) {
|
|
17169
|
+
try {
|
|
17170
|
+
if (!client) {
|
|
17171
|
+
return fallbackPolicy;
|
|
17172
|
+
}
|
|
17173
|
+
const result = await client.getAutomationSettings();
|
|
17174
|
+
return normalizeAdaptiveRoutingProjectPolicy(result.settings, "hosted_project") ?? fallbackPolicy;
|
|
17175
|
+
} catch {
|
|
17176
|
+
return fallbackPolicy;
|
|
17177
|
+
}
|
|
17178
|
+
}
|
|
17179
|
+
function normalizeAdaptiveRoutingProjectPolicy(settings, source) {
|
|
17180
|
+
const mode = normalizeAdaptiveRoutingMode(policyValue(settings, "adaptiveRoutingMode", "mode"));
|
|
17181
|
+
if (!mode) {
|
|
17182
|
+
return null;
|
|
17183
|
+
}
|
|
17184
|
+
const allowedEndpointTypes = normalizeRoutingEndpointTypes(
|
|
17185
|
+
normalizeStringArray3(
|
|
17186
|
+
policyValue(settings, "adaptiveRoutingAllowedEndpointTypes", "allowedEndpointTypes")
|
|
17187
|
+
)
|
|
17188
|
+
);
|
|
17189
|
+
const preferredEndpointTypes = normalizeRoutingEndpointTypes(
|
|
17190
|
+
normalizeStringArray3(
|
|
17191
|
+
policyValue(settings, "adaptiveRoutingPreferredEndpointTypes", "preferredEndpointTypes")
|
|
17192
|
+
)
|
|
17193
|
+
);
|
|
17194
|
+
const allowedWorkerClasses = normalizeAdaptiveWorkerClasses(
|
|
17195
|
+
normalizeStringArray3(
|
|
17196
|
+
policyValue(settings, "adaptiveRoutingAllowedWorkerClasses", "allowedWorkerClasses")
|
|
17197
|
+
)
|
|
17198
|
+
);
|
|
17199
|
+
return {
|
|
17200
|
+
source,
|
|
17201
|
+
mode,
|
|
17202
|
+
requireApproval: policyValue(settings, "adaptiveRoutingRequireApproval", "requireApproval") !== false,
|
|
17203
|
+
plannerRetainsReasoning: policyValue(settings, "adaptiveRoutingPlannerRetainsReasoning", "plannerRetainsReasoning") !== false,
|
|
17204
|
+
preferLocalWorkers: policyValue(settings, "adaptiveRoutingPreferLocalWorkers", "preferLocalWorkers") === true,
|
|
17205
|
+
allowedEndpointTypes: allowedEndpointTypes.length > 0 ? allowedEndpointTypes : ["cloud"],
|
|
17206
|
+
preferredEndpointTypes,
|
|
17207
|
+
allowedWorkerClasses: allowedWorkerClasses.length > 0 ? allowedWorkerClasses : ["documentation", "tests", "review"],
|
|
17208
|
+
fallback: "main_agent",
|
|
17209
|
+
dailyBudgetCents: normalizeCents(
|
|
17210
|
+
policyValue(settings, "adaptiveRoutingDailyBudgetCents", "dailyBudgetCents")
|
|
17211
|
+
),
|
|
17212
|
+
monthlyBudgetCents: normalizeCents(
|
|
17213
|
+
policyValue(settings, "adaptiveRoutingMonthlyBudgetCents", "monthlyBudgetCents")
|
|
17214
|
+
),
|
|
17215
|
+
catalogLimit: normalizeAdaptiveRoutingCatalogLimit(
|
|
17216
|
+
policyValue(settings, "adaptiveRoutingCatalogLimit", "catalogLimit")
|
|
17217
|
+
)
|
|
17218
|
+
};
|
|
17219
|
+
}
|
|
17220
|
+
function resolveAdaptiveRoutingIntent(options, policy, hostedConfigured) {
|
|
17221
|
+
const cliRequested = shouldBuildAdaptiveRouting(options);
|
|
17222
|
+
if (!policy) {
|
|
17223
|
+
return {
|
|
17224
|
+
shouldBuild: cliRequested,
|
|
17225
|
+
shouldUseHostedCatalog: cliRequested,
|
|
17226
|
+
warnings: []
|
|
17227
|
+
};
|
|
17228
|
+
}
|
|
17229
|
+
if (policy.mode === "off") {
|
|
17230
|
+
return {
|
|
17231
|
+
shouldBuild: cliRequested,
|
|
17232
|
+
shouldUseHostedCatalog: false,
|
|
17233
|
+
warnings: cliRequested ? ["Project Adaptive Work Routing policy is off; keeping recommendation metadata local."] : []
|
|
17234
|
+
};
|
|
17235
|
+
}
|
|
17236
|
+
return {
|
|
17237
|
+
shouldBuild: true,
|
|
17238
|
+
shouldUseHostedCatalog: hostedConfigured && policy.mode === "catalog",
|
|
17239
|
+
warnings: [
|
|
17240
|
+
...policy.mode === "recommend" ? [
|
|
17241
|
+
"Project Adaptive Work Routing policy is recommendation-only; hosted catalog lookup is disabled."
|
|
17242
|
+
] : [],
|
|
17243
|
+
...!hostedConfigured && policy.mode === "catalog" ? [
|
|
17244
|
+
"Local Adaptive Work Routing policy requested catalog mode; hosted catalog lookup is skipped without Snipara configuration."
|
|
17245
|
+
] : []
|
|
17246
|
+
]
|
|
17247
|
+
};
|
|
17248
|
+
}
|
|
17249
|
+
async function enrichAdaptiveRoutingWithHostedCatalog(client, routing) {
|
|
17250
|
+
try {
|
|
17251
|
+
const result = await client.callTool(
|
|
17252
|
+
"snipara_adaptive_routing_catalog",
|
|
17253
|
+
{
|
|
17254
|
+
work_profile: routing.workProfile,
|
|
17255
|
+
model_requirements: routing.requirements,
|
|
17256
|
+
limit: routing.requirements.catalogLimit ?? DEFAULT_ADAPTIVE_ROUTING_CATALOG_LIMIT
|
|
17257
|
+
}
|
|
17258
|
+
);
|
|
17259
|
+
const catalog = normalizeAdaptiveRoutingCatalog(result.catalog);
|
|
17260
|
+
const gatewaySucceeded = result.success === true;
|
|
17261
|
+
const warnings = normalizeStringArray3(result.warnings) ?? [];
|
|
17262
|
+
const gatewayWarnings = gatewaySucceeded ? warnings : [
|
|
17263
|
+
...warnings,
|
|
17264
|
+
"Hosted adaptive routing catalog did not return success=true; treating gateway as failed closed."
|
|
17265
|
+
];
|
|
17266
|
+
const candidateCount = numberValue5(result.resolution?.candidate_count) ?? numberValue5(result.resolution?.candidateCount) ?? catalog.candidates.length;
|
|
17267
|
+
const gateway = {
|
|
17268
|
+
source: "hosted_mcp",
|
|
17269
|
+
success: gatewaySucceeded,
|
|
17270
|
+
resolutionStatus: stringValue5(result.resolution?.status),
|
|
17271
|
+
candidateCount,
|
|
17272
|
+
fallback: stringValue5(result.fallback) ?? stringValue5(result.resolution?.fallback) ?? "main_agent",
|
|
17273
|
+
warnings: gatewayWarnings
|
|
17274
|
+
};
|
|
17275
|
+
const reasons = [
|
|
17276
|
+
...routing.routingCard.reasons,
|
|
17277
|
+
gatewaySucceeded ? candidateCount > 0 ? `hosted adaptive routing catalog returned ${candidateCount} candidate(s)` : "hosted adaptive routing catalog returned no candidates and will fail closed" : "hosted adaptive routing catalog did not report explicit success and will fail closed"
|
|
17278
|
+
];
|
|
17279
|
+
return {
|
|
17280
|
+
...routing,
|
|
17281
|
+
gateway,
|
|
17282
|
+
runtimeCatalog: catalog,
|
|
17283
|
+
routingCard: {
|
|
17284
|
+
...routing.routingCard,
|
|
17285
|
+
reasons: uniqueStrings3(reasons),
|
|
17286
|
+
warnings: uniqueStrings3([...routing.routingCard.warnings, ...gatewayWarnings])
|
|
17287
|
+
}
|
|
17288
|
+
};
|
|
17289
|
+
} catch (error) {
|
|
17290
|
+
const warning = `Hosted adaptive routing catalog unavailable; keeping local dry-run metadata (${toPreview(error)}).`;
|
|
17291
|
+
return {
|
|
17292
|
+
...routing,
|
|
17293
|
+
gateway: {
|
|
17294
|
+
source: "hosted_mcp",
|
|
17295
|
+
success: false,
|
|
17296
|
+
resolutionStatus: "unavailable",
|
|
17297
|
+
candidateCount: 0,
|
|
17298
|
+
fallback: "main_agent",
|
|
17299
|
+
warnings: [warning]
|
|
17300
|
+
},
|
|
17301
|
+
routingCard: {
|
|
17302
|
+
...routing.routingCard,
|
|
17303
|
+
warnings: uniqueStrings3([...routing.routingCard.warnings, warning])
|
|
17304
|
+
}
|
|
17305
|
+
};
|
|
17306
|
+
}
|
|
17307
|
+
}
|
|
17308
|
+
function normalizeAdaptiveRoutingCatalog(value) {
|
|
17309
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
17310
|
+
return { candidates: [] };
|
|
17311
|
+
}
|
|
17312
|
+
const record = value;
|
|
17313
|
+
const candidates = Array.isArray(record.candidates) ? record.candidates.filter(
|
|
17314
|
+
(candidate) => Boolean(candidate) && typeof candidate === "object" && !Array.isArray(candidate)
|
|
17315
|
+
) : [];
|
|
17316
|
+
return {
|
|
17317
|
+
version: stringValue5(record.version),
|
|
17318
|
+
candidates
|
|
17319
|
+
};
|
|
17320
|
+
}
|
|
17080
17321
|
function printUploadResult(path20, result) {
|
|
17081
17322
|
printKeyValue("Uploaded:", path20);
|
|
17082
17323
|
printCompactObject(result, ["message", "document_id", "documentId", "version", "status"]);
|
|
@@ -18219,15 +18460,15 @@ function hasReferenceProvenance(metadata) {
|
|
|
18219
18460
|
].some(Boolean);
|
|
18220
18461
|
}
|
|
18221
18462
|
function validateFreshnessPolicy(metadata, reasons) {
|
|
18222
|
-
const
|
|
18223
|
-
if (
|
|
18463
|
+
const policyValue2 = metadata.freshnessPolicy ?? metadata.freshness_policy;
|
|
18464
|
+
if (policyValue2 === void 0) {
|
|
18224
18465
|
return { maxAgeDays: void 0 };
|
|
18225
18466
|
}
|
|
18226
|
-
if (!isRecord9(
|
|
18467
|
+
if (!isRecord9(policyValue2)) {
|
|
18227
18468
|
reasons.push("invalid_freshness_policy");
|
|
18228
18469
|
return { maxAgeDays: void 0 };
|
|
18229
18470
|
}
|
|
18230
|
-
const maxAge =
|
|
18471
|
+
const maxAge = policyValue2.maxAgeDays ?? policyValue2.max_age_days;
|
|
18231
18472
|
if (maxAge === void 0) {
|
|
18232
18473
|
return { maxAgeDays: void 0 };
|
|
18233
18474
|
}
|
|
@@ -18548,14 +18789,26 @@ async function sharedContextCommand(options) {
|
|
|
18548
18789
|
printSharedContextResult(result);
|
|
18549
18790
|
}
|
|
18550
18791
|
async function workflowRunCommand(options) {
|
|
18551
|
-
|
|
18552
|
-
const
|
|
18553
|
-
const
|
|
18554
|
-
const
|
|
18792
|
+
const hostedConfigured = isConfigured();
|
|
18793
|
+
const localAdaptiveRoutingPolicy = readLocalAdaptiveRoutingProjectPolicy();
|
|
18794
|
+
const localAdaptiveRoutingRequested = shouldBuildAdaptiveRouting(options);
|
|
18795
|
+
const canRunLocalAdaptiveRouting = !hostedConfigured && options.mode !== "orchestrate" && (localAdaptiveRoutingRequested || localAdaptiveRoutingPolicy !== null && localAdaptiveRoutingPolicy.mode !== "off");
|
|
18796
|
+
if (!hostedConfigured && !canRunLocalAdaptiveRouting) {
|
|
18797
|
+
ensureConfigured();
|
|
18798
|
+
}
|
|
18799
|
+
const client = hostedConfigured ? createClient(2e4) : null;
|
|
18800
|
+
const adaptiveRoutingPolicy = hostedConfigured ? await loadAdaptiveRoutingProjectPolicy(client, localAdaptiveRoutingPolicy) : localAdaptiveRoutingPolicy;
|
|
18801
|
+
const adaptiveRoutingIntent = resolveAdaptiveRoutingIntent(
|
|
18802
|
+
options,
|
|
18803
|
+
adaptiveRoutingPolicy,
|
|
18804
|
+
hostedConfigured
|
|
18805
|
+
);
|
|
18806
|
+
const adaptiveRoutingDryRun = adaptiveRoutingIntent.shouldBuild ? buildWorkflowAdaptiveRouting(options, adaptiveRoutingPolicy, adaptiveRoutingIntent.warnings) : null;
|
|
18807
|
+
const adaptiveRouting = adaptiveRoutingDryRun && adaptiveRoutingIntent.shouldUseHostedCatalog && client ? await enrichAdaptiveRoutingWithHostedCatalog(client, adaptiveRoutingDryRun) : adaptiveRoutingDryRun;
|
|
18555
18808
|
const orchestratorRecommendation = getOrchestratorRecommendation(options.query, options.mode, {
|
|
18556
18809
|
policyAutoRoute: options.autoRouteOrchestrator,
|
|
18557
18810
|
policySource: options.orchestratorPolicySource,
|
|
18558
|
-
adaptiveRoutingDryRun:
|
|
18811
|
+
adaptiveRoutingDryRun: Boolean(adaptiveRouting)
|
|
18559
18812
|
});
|
|
18560
18813
|
const shouldEmitOrchestratorHandoff = options.emitOrchestratorHandoff || options.autoRouteOrchestrator;
|
|
18561
18814
|
const preparedHandoff = shouldEmitOrchestratorHandoff && orchestratorRecommendation ? writeOrchestratorHandoff({
|
|
@@ -18567,6 +18820,38 @@ async function workflowRunCommand(options) {
|
|
|
18567
18820
|
mode: options.mode,
|
|
18568
18821
|
adaptiveRouting
|
|
18569
18822
|
}) : null;
|
|
18823
|
+
if (!hostedConfigured) {
|
|
18824
|
+
const payload2 = {
|
|
18825
|
+
mode: options.mode,
|
|
18826
|
+
effective_mode: effectiveWorkflowMode(options.mode),
|
|
18827
|
+
local_only: true,
|
|
18828
|
+
local_policy_path: localAdaptiveRoutingPolicy ? ADAPTIVE_ROUTING_POLICY_RELATIVE_PATH : null,
|
|
18829
|
+
orchestrator_recommendation: orchestratorRecommendation,
|
|
18830
|
+
orchestrator_handoff: preparedHandoff,
|
|
18831
|
+
adaptive_routing: adaptiveRouting,
|
|
18832
|
+
warnings: [
|
|
18833
|
+
"Hosted Snipara is not configured; workflow run is limited to local Adaptive Work Routing metadata."
|
|
18834
|
+
]
|
|
18835
|
+
};
|
|
18836
|
+
if (options.json) {
|
|
18837
|
+
printJson2(payload2);
|
|
18838
|
+
return;
|
|
18839
|
+
}
|
|
18840
|
+
console.log(import_chalk9.default.bold("Local Adaptive Work Routing"));
|
|
18841
|
+
console.log(
|
|
18842
|
+
"Hosted Snipara is not configured; no context query, hosted catalog, or planner call ran."
|
|
18843
|
+
);
|
|
18844
|
+
if (adaptiveRouting) {
|
|
18845
|
+
printAdaptiveRoutingRecommendation(adaptiveRouting);
|
|
18846
|
+
}
|
|
18847
|
+
if (preparedHandoff) {
|
|
18848
|
+
printPreparedOrchestratorHandoff2(preparedHandoff);
|
|
18849
|
+
}
|
|
18850
|
+
return;
|
|
18851
|
+
}
|
|
18852
|
+
if (!client) {
|
|
18853
|
+
throw new Error("Hosted Snipara client unavailable after configuration check.");
|
|
18854
|
+
}
|
|
18570
18855
|
if (options.mode === "orchestrate") {
|
|
18571
18856
|
const result = await client.orchestrate(options.query, options.maxTokens);
|
|
18572
18857
|
if (options.json) {
|
|
@@ -18745,22 +19030,72 @@ function shouldBuildAdaptiveRouting(options) {
|
|
|
18745
19030
|
options.adaptiveRoutingDryRun || options.routeLocalWorkers || options.routingWorkerRole || options.routingPreferredEndpoints?.length || options.routingAllowedEndpoints?.length || options.plannerRetainsReasoning
|
|
18746
19031
|
);
|
|
18747
19032
|
}
|
|
18748
|
-
function buildWorkflowAdaptiveRouting(options) {
|
|
19033
|
+
function buildWorkflowAdaptiveRouting(options, policy = null, intentWarnings = []) {
|
|
18749
19034
|
const state = readWorkflowState2();
|
|
18750
19035
|
const currentPhase = state ? currentWorkflowPhase(state) : void 0;
|
|
18751
|
-
const
|
|
19036
|
+
const initialPreferredEndpointTypes = normalizeRoutingEndpointTypes([
|
|
18752
19037
|
...options.routingPreferredEndpoints ?? [],
|
|
18753
|
-
...options.routeLocalWorkers ? ["local"] : []
|
|
19038
|
+
...options.routeLocalWorkers ? ["local"] : [],
|
|
19039
|
+
...policy?.preferredEndpointTypes ?? [],
|
|
19040
|
+
...policy?.preferLocalWorkers ? ["local"] : []
|
|
18754
19041
|
]);
|
|
18755
|
-
|
|
19042
|
+
const policyAllowedEndpointTypes = policy?.allowedEndpointTypes ?? [];
|
|
19043
|
+
const requestedAllowedEndpointTypes = normalizeRoutingEndpointTypes(
|
|
19044
|
+
options.routingAllowedEndpoints
|
|
19045
|
+
);
|
|
19046
|
+
const requestedPolicyEndpointIntersection = policyAllowedEndpointTypes.length > 0 && requestedAllowedEndpointTypes.length > 0 ? intersectStrings(requestedAllowedEndpointTypes, policyAllowedEndpointTypes) : [];
|
|
19047
|
+
const allowedEndpointTypes = policyAllowedEndpointTypes.length > 0 ? requestedAllowedEndpointTypes.length > 0 ? requestedPolicyEndpointIntersection.length > 0 ? requestedPolicyEndpointIntersection : policyAllowedEndpointTypes : policyAllowedEndpointTypes : requestedAllowedEndpointTypes;
|
|
19048
|
+
const preferredEndpointTypes = allowedEndpointTypes.length > 0 ? initialPreferredEndpointTypes.filter((type) => allowedEndpointTypes.includes(type)) : initialPreferredEndpointTypes;
|
|
19049
|
+
const removedPreferredEndpointTypes = initialPreferredEndpointTypes.filter(
|
|
19050
|
+
(type) => !preferredEndpointTypes.includes(type)
|
|
19051
|
+
);
|
|
19052
|
+
const policyWarnings = [
|
|
19053
|
+
...intentWarnings,
|
|
19054
|
+
...requestedAllowedEndpointTypes.length > 0 && policyAllowedEndpointTypes.length > 0 && requestedPolicyEndpointIntersection.length === 0 ? [
|
|
19055
|
+
`Project Adaptive Work Routing policy rejected requested allowed endpoint(s): ${requestedAllowedEndpointTypes.join(", ")}.`
|
|
19056
|
+
] : [],
|
|
19057
|
+
...removedPreferredEndpointTypes.length > 0 ? [
|
|
19058
|
+
`Project Adaptive Work Routing policy removed unsupported preferred endpoint(s): ${removedPreferredEndpointTypes.join(", ")}.`
|
|
19059
|
+
] : []
|
|
19060
|
+
];
|
|
19061
|
+
const buildRecommendation = (workerRole) => buildAdaptiveWorkRoutingRecommendation({
|
|
18756
19062
|
query: options.query,
|
|
18757
19063
|
mode: options.mode,
|
|
18758
19064
|
changedFiles: currentPhase?.files ?? [],
|
|
18759
19065
|
preferredEndpointTypes,
|
|
18760
|
-
allowedEndpointTypes
|
|
18761
|
-
workerRole
|
|
18762
|
-
plannerRetainsReasoning: options.plannerRetainsReasoning ?? (options.routeLocalWorkers ? true : void 0)
|
|
19066
|
+
allowedEndpointTypes,
|
|
19067
|
+
workerRole,
|
|
19068
|
+
plannerRetainsReasoning: options.plannerRetainsReasoning ?? policy?.plannerRetainsReasoning ?? (options.routeLocalWorkers ? true : void 0),
|
|
19069
|
+
catalogLimit: policy?.catalogLimit ?? DEFAULT_ADAPTIVE_ROUTING_CATALOG_LIMIT
|
|
18763
19070
|
});
|
|
19071
|
+
const requestedWorkerRole = stringValue5(options.routingWorkerRole);
|
|
19072
|
+
let routing = buildRecommendation(requestedWorkerRole);
|
|
19073
|
+
const allowedWorkerClasses = policy?.allowedWorkerClasses ?? [];
|
|
19074
|
+
if (allowedWorkerClasses.length > 0 && !isAdaptiveWorkerClassAllowed(routing.requirements.workerRole, allowedWorkerClasses)) {
|
|
19075
|
+
const disallowedWorkerClass = canonicalAdaptiveWorkerClass(routing.requirements.workerRole);
|
|
19076
|
+
const fallbackWorkerRole = selectAdaptiveWorkerRoleForPolicy(
|
|
19077
|
+
routing.workProfile.taskType,
|
|
19078
|
+
allowedWorkerClasses
|
|
19079
|
+
);
|
|
19080
|
+
routing = buildRecommendation(fallbackWorkerRole);
|
|
19081
|
+
policyWarnings.push(
|
|
19082
|
+
`Project Adaptive Work Routing policy does not allow worker class ${disallowedWorkerClass}; using ${canonicalAdaptiveWorkerClass(
|
|
19083
|
+
fallbackWorkerRole
|
|
19084
|
+
)}.`
|
|
19085
|
+
);
|
|
19086
|
+
}
|
|
19087
|
+
const policyReasons = policy ? [
|
|
19088
|
+
`project adaptive routing policy mode is ${policy.mode}`,
|
|
19089
|
+
`project adaptive routing allows endpoint type(s): ${policy.allowedEndpointTypes.join(", ")}`
|
|
19090
|
+
] : [];
|
|
19091
|
+
return {
|
|
19092
|
+
...routing,
|
|
19093
|
+
routingCard: {
|
|
19094
|
+
...routing.routingCard,
|
|
19095
|
+
reasons: uniqueStrings3([...routing.routingCard.reasons, ...policyReasons]),
|
|
19096
|
+
warnings: uniqueStrings3([...routing.routingCard.warnings, ...policyWarnings])
|
|
19097
|
+
}
|
|
19098
|
+
};
|
|
18764
19099
|
}
|
|
18765
19100
|
function normalizeRoutingEndpointTypes(values) {
|
|
18766
19101
|
return Array.from(
|
|
@@ -18769,6 +19104,60 @@ function normalizeRoutingEndpointTypes(values) {
|
|
|
18769
19104
|
)
|
|
18770
19105
|
).sort();
|
|
18771
19106
|
}
|
|
19107
|
+
function normalizeAdaptiveRoutingMode(value) {
|
|
19108
|
+
const normalized = stringValue5(value)?.toLowerCase();
|
|
19109
|
+
return normalized === "off" || normalized === "recommend" || normalized === "catalog" ? normalized : null;
|
|
19110
|
+
}
|
|
19111
|
+
function normalizeAdaptiveWorkerClasses(values) {
|
|
19112
|
+
return uniqueStrings3((values ?? []).map(canonicalAdaptiveWorkerClass)).filter(
|
|
19113
|
+
(value) => ["documentation", "tests", "review", "coding"].includes(value)
|
|
19114
|
+
);
|
|
19115
|
+
}
|
|
19116
|
+
function normalizeCents(value) {
|
|
19117
|
+
const parsed = numberValue5(value);
|
|
19118
|
+
if (parsed === void 0 || parsed < 0) {
|
|
19119
|
+
return 0;
|
|
19120
|
+
}
|
|
19121
|
+
return Math.floor(parsed);
|
|
19122
|
+
}
|
|
19123
|
+
function normalizeAdaptiveRoutingCatalogLimit(value) {
|
|
19124
|
+
const parsed = numberValue5(value);
|
|
19125
|
+
if (parsed === void 0 || parsed < 1) {
|
|
19126
|
+
return void 0;
|
|
19127
|
+
}
|
|
19128
|
+
return Math.min(Math.floor(parsed), 100);
|
|
19129
|
+
}
|
|
19130
|
+
function intersectStrings(left, right) {
|
|
19131
|
+
const rightSet = new Set(right);
|
|
19132
|
+
return left.filter((value) => rightSet.has(value));
|
|
19133
|
+
}
|
|
19134
|
+
function canonicalAdaptiveWorkerClass(value) {
|
|
19135
|
+
const normalized = stringValue5(value)?.toLowerCase().replace(/[-\s]/g, "_") ?? "execution";
|
|
19136
|
+
if (normalized === "docs" || normalized === "doc") {
|
|
19137
|
+
return "documentation";
|
|
19138
|
+
}
|
|
19139
|
+
if (normalized === "test" || normalized === "testing") {
|
|
19140
|
+
return "tests";
|
|
19141
|
+
}
|
|
19142
|
+
if (normalized === "validation" || normalized === "reviewer") {
|
|
19143
|
+
return "review";
|
|
19144
|
+
}
|
|
19145
|
+
if (normalized === "code" || normalized === "coder" || normalized === "implementation") {
|
|
19146
|
+
return "coding";
|
|
19147
|
+
}
|
|
19148
|
+
return normalized;
|
|
19149
|
+
}
|
|
19150
|
+
function isAdaptiveWorkerClassAllowed(workerRole, allowedWorkerClasses) {
|
|
19151
|
+
return allowedWorkerClasses.includes(canonicalAdaptiveWorkerClass(workerRole));
|
|
19152
|
+
}
|
|
19153
|
+
function selectAdaptiveWorkerRoleForPolicy(taskType, allowedWorkerClasses) {
|
|
19154
|
+
const preferences = taskType === "documentation" ? ["documentation", "review", "coding", "tests"] : taskType === "tests" ? ["tests", "review", "coding", "documentation"] : taskType === "coding" || taskType === "critical_code" ? ["coding", "review", "tests", "documentation"] : ["review", "coding", "documentation", "tests"];
|
|
19155
|
+
const selected = preferences.find((workerClass) => allowedWorkerClasses.includes(workerClass));
|
|
19156
|
+
return workerRoleFromAdaptiveClass(selected ?? allowedWorkerClasses[0] ?? "review");
|
|
19157
|
+
}
|
|
19158
|
+
function workerRoleFromAdaptiveClass(workerClass) {
|
|
19159
|
+
return workerClass === "tests" ? "testing" : workerClass;
|
|
19160
|
+
}
|
|
18772
19161
|
function startManagedWorkflowState(options) {
|
|
18773
19162
|
const existing = readWorkflowState2();
|
|
18774
19163
|
if (existing && existing.status === "active" && !options.force) {
|
package/package.json
CHANGED