snipara-companion 1.4.22 → 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 +29 -5
- package/dist/index.d.ts +30 -0
- package/dist/index.js +322 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -191,15 +191,39 @@ snipara-companion workflow run \
|
|
|
191
191
|
```
|
|
192
192
|
|
|
193
193
|
The output is a routing card and handoff metadata. Companion calls the hosted
|
|
194
|
-
`snipara_adaptive_routing_catalog` tool when project auth
|
|
195
|
-
the sanitized runtime catalog in the handoff, and falls back to local
|
|
196
|
-
metadata when the hosted gateway is unavailable
|
|
197
|
-
workers. The stable contract is
|
|
198
|
-
|
|
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.
|
|
199
205
|
Project credentials stay server-side behind the hosted gateway; local endpoints
|
|
200
206
|
such as Ollama, LM Studio, AnythingLLM, or other OpenAI-compatible servers must
|
|
201
207
|
be reachable from the worker execution environment.
|
|
202
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
|
+
|
|
203
227
|
## Verification Plans
|
|
204
228
|
|
|
205
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 {
|
|
@@ -2864,6 +2893,7 @@ interface AdaptiveWorkRoutingOptions {
|
|
|
2864
2893
|
allowedEndpointTypes?: string[];
|
|
2865
2894
|
workerRole?: string;
|
|
2866
2895
|
plannerRetainsReasoning?: boolean;
|
|
2896
|
+
catalogLimit?: number;
|
|
2867
2897
|
}
|
|
2868
2898
|
interface OrchestratorHandoffArtifact {
|
|
2869
2899
|
schemaVersion: "snipara.orchestrator.handoff.v1";
|
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.");
|
|
@@ -11225,6 +11263,7 @@ function buildAdaptiveWorkRoutingRecommendation(options) {
|
|
|
11225
11263
|
writeScope: changedFiles,
|
|
11226
11264
|
preferredEndpointTypes,
|
|
11227
11265
|
allowedEndpointTypes,
|
|
11266
|
+
catalogLimit: options.catalogLimit,
|
|
11228
11267
|
fallback: "main_agent"
|
|
11229
11268
|
});
|
|
11230
11269
|
const warnings = [
|
|
@@ -11358,7 +11397,9 @@ function normalizeEndpointTypes(values) {
|
|
|
11358
11397
|
}
|
|
11359
11398
|
function compactObject2(value) {
|
|
11360
11399
|
return Object.fromEntries(
|
|
11361
|
-
Object.entries(value).filter(
|
|
11400
|
+
Object.entries(value).filter(
|
|
11401
|
+
([, item]) => item !== void 0 && !(Array.isArray(item) && item.length === 0)
|
|
11402
|
+
)
|
|
11362
11403
|
);
|
|
11363
11404
|
}
|
|
11364
11405
|
function writeOrchestratorHandoff(options) {
|
|
@@ -14551,6 +14592,8 @@ var FINAL_COMMIT_TIMEOUT_MS = 9e4;
|
|
|
14551
14592
|
var FINAL_COMMIT_RETRY_TIMEOUT_MS = 45e3;
|
|
14552
14593
|
var FINAL_COMMIT_SUMMARY_MAX_CHARS = 1200;
|
|
14553
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");
|
|
14554
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;
|
|
14555
14598
|
var DOCUMENT_SYNC_FORMATS = {
|
|
14556
14599
|
".adoc": { kind: "DOC", format: "adoc" },
|
|
@@ -17099,6 +17142,110 @@ function printAdaptiveRoutingRecommendation(routing) {
|
|
|
17099
17142
|
}
|
|
17100
17143
|
}
|
|
17101
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
|
+
}
|
|
17102
17249
|
async function enrichAdaptiveRoutingWithHostedCatalog(client, routing) {
|
|
17103
17250
|
try {
|
|
17104
17251
|
const result = await client.callTool(
|
|
@@ -17106,23 +17253,28 @@ async function enrichAdaptiveRoutingWithHostedCatalog(client, routing) {
|
|
|
17106
17253
|
{
|
|
17107
17254
|
work_profile: routing.workProfile,
|
|
17108
17255
|
model_requirements: routing.requirements,
|
|
17109
|
-
limit:
|
|
17256
|
+
limit: routing.requirements.catalogLimit ?? DEFAULT_ADAPTIVE_ROUTING_CATALOG_LIMIT
|
|
17110
17257
|
}
|
|
17111
17258
|
);
|
|
17112
17259
|
const catalog = normalizeAdaptiveRoutingCatalog(result.catalog);
|
|
17260
|
+
const gatewaySucceeded = result.success === true;
|
|
17113
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
|
+
];
|
|
17114
17266
|
const candidateCount = numberValue5(result.resolution?.candidate_count) ?? numberValue5(result.resolution?.candidateCount) ?? catalog.candidates.length;
|
|
17115
17267
|
const gateway = {
|
|
17116
17268
|
source: "hosted_mcp",
|
|
17117
|
-
success:
|
|
17269
|
+
success: gatewaySucceeded,
|
|
17118
17270
|
resolutionStatus: stringValue5(result.resolution?.status),
|
|
17119
17271
|
candidateCount,
|
|
17120
17272
|
fallback: stringValue5(result.fallback) ?? stringValue5(result.resolution?.fallback) ?? "main_agent",
|
|
17121
|
-
warnings
|
|
17273
|
+
warnings: gatewayWarnings
|
|
17122
17274
|
};
|
|
17123
17275
|
const reasons = [
|
|
17124
17276
|
...routing.routingCard.reasons,
|
|
17125
|
-
candidateCount > 0 ? `hosted adaptive routing catalog returned ${candidateCount} candidate(s)` : "hosted adaptive routing catalog returned no candidates and will fail closed"
|
|
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"
|
|
17126
17278
|
];
|
|
17127
17279
|
return {
|
|
17128
17280
|
...routing,
|
|
@@ -17131,7 +17283,7 @@ async function enrichAdaptiveRoutingWithHostedCatalog(client, routing) {
|
|
|
17131
17283
|
routingCard: {
|
|
17132
17284
|
...routing.routingCard,
|
|
17133
17285
|
reasons: uniqueStrings3(reasons),
|
|
17134
|
-
warnings: uniqueStrings3([...routing.routingCard.warnings, ...
|
|
17286
|
+
warnings: uniqueStrings3([...routing.routingCard.warnings, ...gatewayWarnings])
|
|
17135
17287
|
}
|
|
17136
17288
|
};
|
|
17137
17289
|
} catch (error) {
|
|
@@ -18308,15 +18460,15 @@ function hasReferenceProvenance(metadata) {
|
|
|
18308
18460
|
].some(Boolean);
|
|
18309
18461
|
}
|
|
18310
18462
|
function validateFreshnessPolicy(metadata, reasons) {
|
|
18311
|
-
const
|
|
18312
|
-
if (
|
|
18463
|
+
const policyValue2 = metadata.freshnessPolicy ?? metadata.freshness_policy;
|
|
18464
|
+
if (policyValue2 === void 0) {
|
|
18313
18465
|
return { maxAgeDays: void 0 };
|
|
18314
18466
|
}
|
|
18315
|
-
if (!isRecord9(
|
|
18467
|
+
if (!isRecord9(policyValue2)) {
|
|
18316
18468
|
reasons.push("invalid_freshness_policy");
|
|
18317
18469
|
return { maxAgeDays: void 0 };
|
|
18318
18470
|
}
|
|
18319
|
-
const maxAge =
|
|
18471
|
+
const maxAge = policyValue2.maxAgeDays ?? policyValue2.max_age_days;
|
|
18320
18472
|
if (maxAge === void 0) {
|
|
18321
18473
|
return { maxAgeDays: void 0 };
|
|
18322
18474
|
}
|
|
@@ -18637,14 +18789,26 @@ async function sharedContextCommand(options) {
|
|
|
18637
18789
|
printSharedContextResult(result);
|
|
18638
18790
|
}
|
|
18639
18791
|
async function workflowRunCommand(options) {
|
|
18640
|
-
|
|
18641
|
-
const
|
|
18642
|
-
const
|
|
18643
|
-
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;
|
|
18644
18808
|
const orchestratorRecommendation = getOrchestratorRecommendation(options.query, options.mode, {
|
|
18645
18809
|
policyAutoRoute: options.autoRouteOrchestrator,
|
|
18646
18810
|
policySource: options.orchestratorPolicySource,
|
|
18647
|
-
adaptiveRoutingDryRun:
|
|
18811
|
+
adaptiveRoutingDryRun: Boolean(adaptiveRouting)
|
|
18648
18812
|
});
|
|
18649
18813
|
const shouldEmitOrchestratorHandoff = options.emitOrchestratorHandoff || options.autoRouteOrchestrator;
|
|
18650
18814
|
const preparedHandoff = shouldEmitOrchestratorHandoff && orchestratorRecommendation ? writeOrchestratorHandoff({
|
|
@@ -18656,6 +18820,38 @@ async function workflowRunCommand(options) {
|
|
|
18656
18820
|
mode: options.mode,
|
|
18657
18821
|
adaptiveRouting
|
|
18658
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
|
+
}
|
|
18659
18855
|
if (options.mode === "orchestrate") {
|
|
18660
18856
|
const result = await client.orchestrate(options.query, options.maxTokens);
|
|
18661
18857
|
if (options.json) {
|
|
@@ -18834,22 +19030,72 @@ function shouldBuildAdaptiveRouting(options) {
|
|
|
18834
19030
|
options.adaptiveRoutingDryRun || options.routeLocalWorkers || options.routingWorkerRole || options.routingPreferredEndpoints?.length || options.routingAllowedEndpoints?.length || options.plannerRetainsReasoning
|
|
18835
19031
|
);
|
|
18836
19032
|
}
|
|
18837
|
-
function buildWorkflowAdaptiveRouting(options) {
|
|
19033
|
+
function buildWorkflowAdaptiveRouting(options, policy = null, intentWarnings = []) {
|
|
18838
19034
|
const state = readWorkflowState2();
|
|
18839
19035
|
const currentPhase = state ? currentWorkflowPhase(state) : void 0;
|
|
18840
|
-
const
|
|
19036
|
+
const initialPreferredEndpointTypes = normalizeRoutingEndpointTypes([
|
|
18841
19037
|
...options.routingPreferredEndpoints ?? [],
|
|
18842
|
-
...options.routeLocalWorkers ? ["local"] : []
|
|
19038
|
+
...options.routeLocalWorkers ? ["local"] : [],
|
|
19039
|
+
...policy?.preferredEndpointTypes ?? [],
|
|
19040
|
+
...policy?.preferLocalWorkers ? ["local"] : []
|
|
18843
19041
|
]);
|
|
18844
|
-
|
|
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({
|
|
18845
19062
|
query: options.query,
|
|
18846
19063
|
mode: options.mode,
|
|
18847
19064
|
changedFiles: currentPhase?.files ?? [],
|
|
18848
19065
|
preferredEndpointTypes,
|
|
18849
|
-
allowedEndpointTypes
|
|
18850
|
-
workerRole
|
|
18851
|
-
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
|
|
18852
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
|
+
};
|
|
18853
19099
|
}
|
|
18854
19100
|
function normalizeRoutingEndpointTypes(values) {
|
|
18855
19101
|
return Array.from(
|
|
@@ -18858,6 +19104,60 @@ function normalizeRoutingEndpointTypes(values) {
|
|
|
18858
19104
|
)
|
|
18859
19105
|
).sort();
|
|
18860
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
|
+
}
|
|
18861
19161
|
function startManagedWorkflowState(options) {
|
|
18862
19162
|
const existing = readWorkflowState2();
|
|
18863
19163
|
if (existing && existing.status === "active" && !options.force) {
|
package/package.json
CHANGED