newmark-agent 0.6.3 → 0.6.7

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.
Files changed (72) hide show
  1. package/config.example.json +13 -3
  2. package/dist/cli-commands.d.ts +1 -1
  3. package/dist/cli-commands.js +53 -2
  4. package/dist/cli-discovery.js +3 -0
  5. package/dist/conversation-utility-host.bundle.cjs +3046 -1038
  6. package/dist/conversation-utility-host.js +94 -7
  7. package/dist/core/agent.d.ts +149 -12
  8. package/dist/core/agent.js +843 -176
  9. package/dist/core/agentKernelRunner.d.ts +7 -1
  10. package/dist/core/agentKernelRunner.js +171 -22
  11. package/dist/core/autoRouter.d.ts +49 -44
  12. package/dist/core/autoRouter.js +117 -302
  13. package/dist/core/branchIdentity.d.ts +29 -0
  14. package/dist/core/branchIdentity.js +54 -0
  15. package/dist/core/config.js +27 -11
  16. package/dist/core/continuation/contracts.d.ts +1 -1
  17. package/dist/core/continuation/store.d.ts +119 -1
  18. package/dist/core/continuation/store.js +337 -19
  19. package/dist/core/conversationKernel.d.ts +62 -8
  20. package/dist/core/conversationKernel.js +361 -47
  21. package/dist/core/conversationStateDocument.d.ts +18 -0
  22. package/dist/core/conversationStateDocument.js +88 -0
  23. package/dist/core/conversationVisualBudget.d.ts +74 -0
  24. package/dist/core/conversationVisualBudget.js +153 -0
  25. package/dist/core/electronUtilityAgentClient.d.ts +3 -2
  26. package/dist/core/electronUtilityAgentClient.js +32 -8
  27. package/dist/core/electronUtilityRuntimePool.d.ts +28 -3
  28. package/dist/core/electronUtilityRuntimePool.js +93 -24
  29. package/dist/core/hostRuntimeHooks.d.ts +23 -0
  30. package/dist/core/hostRuntimeHooks.js +17 -0
  31. package/dist/core/jevDecision.d.ts +144 -0
  32. package/dist/core/jevDecision.js +226 -0
  33. package/dist/core/memoryProbe.d.ts +24 -0
  34. package/dist/core/memoryProbe.js +104 -0
  35. package/dist/core/mobilePairing.d.ts +7 -1
  36. package/dist/core/mobilePairing.js +9 -1
  37. package/dist/core/performanceDiagnostics.d.ts +1 -1
  38. package/dist/core/routeDecisionValidator.d.ts +86 -0
  39. package/dist/core/routeDecisionValidator.js +249 -0
  40. package/dist/core/routeEligibility.d.ts +52 -0
  41. package/dist/core/routeEligibility.js +108 -0
  42. package/dist/core/runtimeMemoryBudget.d.ts +6 -0
  43. package/dist/core/runtimeMemoryBudget.js +12 -0
  44. package/dist/core/utilityAgentProtocol.d.ts +8 -10
  45. package/dist/core/utilityHostToolRouter.js +2 -0
  46. package/dist/core/visualDownscale.d.ts +6 -0
  47. package/dist/core/visualDownscale.js +137 -0
  48. package/dist/core/wslAgentClient.d.ts +1 -2
  49. package/dist/core/wslAgentClient.js +0 -8
  50. package/dist/core/wslAgentProtocol.d.ts +1 -10
  51. package/dist/core/wslAgentRuntimePool.d.ts +1 -3
  52. package/dist/core/wslAgentRuntimePool.js +17 -22
  53. package/dist/llm/provider.d.ts +3 -1
  54. package/dist/llm/provider.js +53 -9
  55. package/dist/main.js +108 -31
  56. package/dist/preload.js +3 -1
  57. package/dist/server.js +30 -18
  58. package/dist/tools/computerUse.d.ts +4 -0
  59. package/dist/tools/computerUse.js +202 -4
  60. package/dist/tools/computerUsePowerShellHost.d.ts +6 -1
  61. package/dist/tools/computerUsePowerShellHost.js +105 -31
  62. package/dist/tools/index.js +4 -1
  63. package/dist/tui/src/adapters/core-runtime-adapter.js +13 -1
  64. package/dist/tui/src/i18n.js +12 -1
  65. package/dist/tui/src/render.js +9 -2
  66. package/dist/tui/src/settings-schema.js +19 -0
  67. package/dist/tui/src/state.js +69 -1
  68. package/dist/ui/index.html +994 -277
  69. package/dist/ui/lucide-sprite.svg +0 -8
  70. package/dist/wsl-agent-host.bundle.cjs +2851 -1013
  71. package/dist/wsl-agent-host.js +0 -3
  72. package/package.json +38 -9
@@ -0,0 +1,249 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JevDecisionValidationError = void 0;
4
+ exports.validateJevDecision = validateJevDecision;
5
+ exports.buildRouteDecision = buildRouteDecision;
6
+ exports.resolveAutoRouteDecision = resolveAutoRouteDecision;
7
+ const autoRouter_1 = require("./autoRouter");
8
+ const jevDecision_1 = require("./jevDecision");
9
+ const routeEligibility_1 = require("./routeEligibility");
10
+ class JevDecisionValidationError extends Error {
11
+ code;
12
+ constructor(code, message) {
13
+ super(message);
14
+ this.code = code;
15
+ this.name = 'JevDecisionValidationError';
16
+ }
17
+ }
18
+ exports.JevDecisionValidationError = JevDecisionValidationError;
19
+ function validDeployment(deployment) {
20
+ if (!deployment || typeof deployment !== 'object')
21
+ return false;
22
+ const candidate = deployment;
23
+ return !!String(candidate.providerId || '').trim() && !!String(candidate.modelId || '').trim();
24
+ }
25
+ function normalizeDeployment(deployment) {
26
+ const logicalModelGroupId = String(deployment.logicalModelGroupId || '').trim();
27
+ return {
28
+ providerId: String(deployment.providerId),
29
+ modelId: String(deployment.modelId),
30
+ ...(logicalModelGroupId ? { logicalModelGroupId } : {}),
31
+ };
32
+ }
33
+ /**
34
+ * Validate a decision engine output against the Guard result.
35
+ *
36
+ * The engine may only choose inside the space the Guard allowed. Alternates
37
+ * that leave that space are dropped (they are recovery hints, not primary
38
+ * decisions), while an ineligible primary is a hard failure: routing must fall
39
+ * back deterministically instead of silently executing something the Guard
40
+ * excluded.
41
+ */
42
+ function validateJevDecision(decision, eligible, options = {}) {
43
+ if (!eligible.length) {
44
+ throw new JevDecisionValidationError('no_eligible_candidates', 'No eligible candidate was available for Auto selection');
45
+ }
46
+ if (!decision || typeof decision !== 'object') {
47
+ throw new JevDecisionValidationError('missing_primary', 'The decision engine returned no decision');
48
+ }
49
+ if (!validDeployment(decision.primary)) {
50
+ throw new JevDecisionValidationError('missing_primary', 'The decision engine returned no usable primary deployment');
51
+ }
52
+ const allowed = new Map(eligible.map(candidate => [
53
+ `${candidate.deployment.providerId}\u0000${candidate.deployment.modelId}`,
54
+ candidate,
55
+ ]));
56
+ const primary = normalizeDeployment(decision.primary);
57
+ const allowedPrimary = allowed.get(`${primary.providerId}\u0000${primary.modelId}`);
58
+ if (!allowedPrimary) {
59
+ throw new JevDecisionValidationError('primary_not_eligible', `The decision engine selected ${primary.providerId}/${primary.modelId}, which the eligibility guard excluded`);
60
+ }
61
+ const maxAlternates = Math.max(0, options.maxAlternates ?? jevDecision_1.DEFAULT_JEV_MAX_ALTERNATES);
62
+ const alternates = [];
63
+ const droppedAlternates = [];
64
+ const rawAlternates = Array.isArray(decision.alternates) ? decision.alternates : [];
65
+ for (const raw of rawAlternates) {
66
+ if (!validDeployment(raw)) {
67
+ droppedAlternates.push({ deployment: { providerId: String(raw?.providerId || ''), modelId: String(raw?.modelId || '') }, reason: 'invalid_deployment' });
68
+ continue;
69
+ }
70
+ const alternate = normalizeDeployment(raw);
71
+ if ((0, routeEligibility_1.sameDeploymentRef)(alternate, primary)) {
72
+ droppedAlternates.push({ deployment: alternate, reason: 'primary_duplicate' });
73
+ continue;
74
+ }
75
+ if (!allowed.get(`${alternate.providerId}\u0000${alternate.modelId}`)) {
76
+ droppedAlternates.push({ deployment: alternate, reason: 'not_eligible' });
77
+ continue;
78
+ }
79
+ if (alternates.some(existing => (0, routeEligibility_1.sameDeploymentRef)(existing, alternate))) {
80
+ droppedAlternates.push({ deployment: alternate, reason: 'duplicate' });
81
+ continue;
82
+ }
83
+ if (alternates.length >= maxAlternates) {
84
+ droppedAlternates.push({ deployment: alternate, reason: 'over_limit' });
85
+ continue;
86
+ }
87
+ alternates.push(alternate);
88
+ }
89
+ return {
90
+ primary: { ...allowedPrimary.deployment, ...primary, ...(allowedPrimary.deployment.logicalModelGroupId && !primary.logicalModelGroupId
91
+ ? { logicalModelGroupId: allowedPrimary.deployment.logicalModelGroupId }
92
+ : {}) },
93
+ alternates,
94
+ reasonCodes: Array.isArray(decision.reasonCodes) ? decision.reasonCodes.map(code => String(code)) : [],
95
+ ...(typeof decision.confidence === 'number' && Number.isFinite(decision.confidence) ? { confidence: decision.confidence } : {}),
96
+ modelVersion: String(decision.modelVersion || 'unknown'),
97
+ droppedAlternates,
98
+ };
99
+ }
100
+ function buildRouteDecision(input) {
101
+ return {
102
+ routeId: input.routeId,
103
+ requestedSelection: input.selection,
104
+ policyVersion: input.policyVersion,
105
+ decisionSource: input.decisionSource,
106
+ catalogSnapshotHash: input.catalogSnapshotHash,
107
+ taskClasses: [...input.taskClasses],
108
+ excludedCandidates: input.excluded.map(entry => ({ deployment: { ...entry.deployment }, reasons: [...entry.reasons] })),
109
+ resolvedDeployment: { ...input.decision.primary },
110
+ alternatives: input.decision.alternates.map(deployment => ({ ...deployment })),
111
+ jev: input.jev,
112
+ ...(input.decisionError ? { decisionError: input.decisionError } : {}),
113
+ ...(input.pinReason ? { pinReason: input.pinReason } : {}),
114
+ attempts: input.attempts ? input.attempts.map(attempt => ({ ...attempt })) : [],
115
+ finalStatus: input.finalStatus || 'resolved',
116
+ ...(input.retryBudgetMs === undefined ? {} : { retryBudgetMs: input.retryBudgetMs }),
117
+ };
118
+ }
119
+ /**
120
+ * The single Auto decision pipeline used by the Agent and by the verification
121
+ * suite. Keeping it here (instead of inline in the Agent) is what guarantees
122
+ * that tests exercise the shipping order of stages:
123
+ *
124
+ * guard → decision engine → validator → optional transaction pin
125
+ */
126
+ async function resolveAutoRouteDecision(input) {
127
+ const { selection, policy, taskText, request, candidates, taskClasses, } = input;
128
+ const routeId = input.routeId || (0, autoRouter_1.createRouteId)();
129
+ const policyVersion = input.policyVersion || 'newmark-auto-v2';
130
+ const base = {
131
+ routeId,
132
+ policyVersion,
133
+ taskClasses,
134
+ retryBudgetMs: request.batch ? 15_000 : 5_000,
135
+ };
136
+ const eligibility = (0, routeEligibility_1.evaluateRouteEligibility)({ selection, policy, request, candidates });
137
+ const catalogHash = (0, autoRouter_1.catalogSnapshotHash)(candidates);
138
+ if (!eligibility.eligible.length) {
139
+ return {
140
+ routeId: base.routeId,
141
+ requestedSelection: selection,
142
+ policyVersion: base.policyVersion,
143
+ decisionSource: 'jev',
144
+ catalogSnapshotHash: catalogHash,
145
+ taskClasses,
146
+ excludedCandidates: eligibility.excluded,
147
+ alternatives: [],
148
+ jev: { modelVersion: jevDecision_1.JEV_ROUTE_MODEL_VERSION, reasonCodes: [...taskClasses, 'no_eligible_candidate'], latencyMs: 0 },
149
+ attempts: [],
150
+ finalStatus: 'no_candidate',
151
+ retryBudgetMs: base.retryBudgetMs,
152
+ };
153
+ }
154
+ const timeoutMs = Number.isFinite(input.timeoutMs) && Number(input.timeoutMs) > 0
155
+ ? Math.min(5_000, Number(input.timeoutMs))
156
+ : jevDecision_1.DEFAULT_JEV_TIMEOUT_MS;
157
+ let decisionError = '';
158
+ let failureCode = '';
159
+ let validated = null;
160
+ let jevLatencyMs = 0;
161
+ if (input.engine) {
162
+ const jevRequest = (0, jevDecision_1.buildJevRouteRequest)({
163
+ transactionId: request.transactionId,
164
+ taskText,
165
+ taskClasses,
166
+ estimatedInputTokens: request.estimatedInputTokens,
167
+ expectedOutputTokens: request.expectedOutputTokens,
168
+ requiredCapabilities: request.requiredCapabilities,
169
+ intelligence: input.intelligence || 'default',
170
+ policy,
171
+ request,
172
+ candidates: eligibility.eligible,
173
+ previousDeployment: input.previousDeployment,
174
+ });
175
+ const outcome = await (0, jevDecision_1.decideWithinBudget)(input.engine, jevRequest, timeoutMs);
176
+ jevLatencyMs = outcome.latencyMs;
177
+ try {
178
+ if (!outcome.decision)
179
+ throw new Error(outcome.error?.message || 'The current model returned no JEV decision');
180
+ validated = validateJevDecision(outcome.decision, eligibility.eligible);
181
+ }
182
+ catch (error) {
183
+ failureCode = outcome.error?.code
184
+ || (error instanceof JevDecisionValidationError ? error.code : 'invalid_output');
185
+ decisionError = `${failureCode}: ${error instanceof Error ? error.message : String(error)}`;
186
+ }
187
+ }
188
+ else {
189
+ failureCode = input.unavailableReason || 'runtime_unavailable';
190
+ decisionError = `${failureCode}: current-model JEV routing is not available`;
191
+ }
192
+ if (!validated) {
193
+ return {
194
+ routeId: base.routeId,
195
+ requestedSelection: selection,
196
+ policyVersion: base.policyVersion,
197
+ decisionSource: 'jev',
198
+ catalogSnapshotHash: catalogHash,
199
+ taskClasses,
200
+ excludedCandidates: eligibility.excluded,
201
+ alternatives: [],
202
+ jev: { modelVersion: jevDecision_1.JEV_ROUTE_MODEL_VERSION, reasonCodes: [...taskClasses, `decision_failed:${failureCode || 'unknown'}`], latencyMs: jevLatencyMs },
203
+ decisionError: decisionError || 'current-model decision did not produce a valid JSON route',
204
+ attempts: [],
205
+ finalStatus: 'no_candidate',
206
+ retryBudgetMs: base.retryBudgetMs,
207
+ };
208
+ }
209
+ // Transaction pinning keeps one Build on one deployment. It is execution
210
+ // consistency, not a second routing decision: the pin can only ever hold a
211
+ // deployment the guard already accepted, and a fresh Auto route outside the
212
+ // transaction is decided by JEV alone.
213
+ let primary = validated.primary;
214
+ let alternates = validated.alternates;
215
+ let pinReason;
216
+ const pinned = input.pinnedDeployment;
217
+ if (pinned
218
+ && !(0, routeEligibility_1.sameDeploymentRef)(pinned, primary)
219
+ && eligibility.eligible.some(candidate => (0, routeEligibility_1.sameDeploymentRef)(candidate.deployment, pinned))) {
220
+ const previousPrimary = validated.primary;
221
+ primary = { ...pinned };
222
+ pinReason = 'transaction';
223
+ alternates = [previousPrimary, ...validated.alternates]
224
+ .filter(deployment => !(0, routeEligibility_1.sameDeploymentRef)(deployment, primary))
225
+ .filter((deployment, index, list) => list.findIndex(item => (0, routeEligibility_1.sameDeploymentRef)(item, deployment)) === index)
226
+ .slice(0, jevDecision_1.DEFAULT_JEV_MAX_ALTERNATES);
227
+ }
228
+ return buildRouteDecision({
229
+ routeId: base.routeId,
230
+ selection,
231
+ policyVersion: base.policyVersion,
232
+ decisionSource: 'jev',
233
+ catalogSnapshotHash: catalogHash,
234
+ taskClasses,
235
+ excluded: eligibility.excluded,
236
+ decision: { ...validated, primary, alternates },
237
+ jev: {
238
+ modelVersion: validated.modelVersion,
239
+ ...(validated.confidence === undefined ? {} : { confidence: validated.confidence }),
240
+ reasonCodes: validated.reasonCodes,
241
+ latencyMs: jevLatencyMs,
242
+ },
243
+ ...(pinReason ? { pinReason } : {}),
244
+ attempts: [{ deployment: { ...primary }, kind: 'initial', status: 'planned' }],
245
+ finalStatus: 'resolved',
246
+ retryBudgetMs: base.retryBudgetMs,
247
+ });
248
+ }
249
+ //# sourceMappingURL=routeDecisionValidator.js.map
@@ -0,0 +1,52 @@
1
+ /**
2
+ * RouteEligibilityGuard — Auto Router v2 hard-eligibility stage.
3
+ *
4
+ * This module answers exactly one question: "which candidate deployments may
5
+ * the decision engine even see?". It never answers "which candidate is
6
+ * better". Ranking, scoring, learned preference, cost/speed utility and cache
7
+ * affinity are therefore absent by design; the only decision source downstream
8
+ * of this file is the JEV decision engine.
9
+ *
10
+ * Keep this file free of history: no feedback log, no quality_by_task, no
11
+ * user-rating history, no endpoint statistics. Endpoint health is operational
12
+ * input for the decision engine, not an eligibility rule (an endpoint that is
13
+ * currently failing must stay visible so the system can still probe it).
14
+ */
15
+ import type { AutoRouteCandidate, AutoScope, DeploymentRef, ModelSelection, RoutePolicy, RouteRequest } from './autoRouter';
16
+ export interface RouteExclusion {
17
+ deployment: DeploymentRef;
18
+ reasons: string[];
19
+ }
20
+ export interface RouteEligibilityRequest {
21
+ selection: ModelSelection;
22
+ policy: RoutePolicy;
23
+ request: RouteRequest;
24
+ candidates: AutoRouteCandidate[];
25
+ }
26
+ export interface RouteEligibilityResult {
27
+ eligible: AutoRouteCandidate[];
28
+ excluded: RouteExclusion[];
29
+ }
30
+ export declare function deploymentKey(deployment: DeploymentRef): string;
31
+ export declare function sameDeploymentRef(left: DeploymentRef, right: DeploymentRef): boolean;
32
+ export declare function inAutoScope(deployment: DeploymentRef, scope: AutoScope): boolean;
33
+ export declare function inAutoSubset(deployment: DeploymentRef, subset: DeploymentRef[] | undefined): boolean;
34
+ export declare function expectedRequestCost(candidate: AutoRouteCandidate, request: RouteRequest): number | undefined;
35
+ /**
36
+ * The single authoritative implementation of Auto hard eligibility.
37
+ *
38
+ * `Agent.autoRouteCandidates()` only projects configuration into candidate
39
+ * facts (enabled flag, capability list, prices, context window, privacy and
40
+ * region declarations, observed endpoint health). Every hard rule that can
41
+ * remove a candidate lives here so there is never a second copy of the rules
42
+ * in the Agent or in the execution controller.
43
+ */
44
+ export declare function evaluateRouteEligibility(input: RouteEligibilityRequest): RouteEligibilityResult;
45
+ /**
46
+ * A candidate excluded by the Guard may still be used by provider-local
47
+ * recovery when *only* the fallback-only marker removed it. This mirrors the
48
+ * pre-existing behaviour: fallback-only deployments are never primary
49
+ * candidates but may serve as an explicit recovery pool.
50
+ */
51
+ export declare function excludedOnlyForFallbackPool(exclusion: RouteExclusion | undefined): boolean;
52
+ //# sourceMappingURL=routeEligibility.d.ts.map
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deploymentKey = deploymentKey;
4
+ exports.sameDeploymentRef = sameDeploymentRef;
5
+ exports.inAutoScope = inAutoScope;
6
+ exports.inAutoSubset = inAutoSubset;
7
+ exports.expectedRequestCost = expectedRequestCost;
8
+ exports.evaluateRouteEligibility = evaluateRouteEligibility;
9
+ exports.excludedOnlyForFallbackPool = excludedOnlyForFallbackPool;
10
+ function deploymentKey(deployment) {
11
+ return `${deployment.providerId}\u0000${deployment.modelId}\u0000${deployment.logicalModelGroupId || ''}`;
12
+ }
13
+ function sameDeploymentRef(left, right) {
14
+ return left.providerId === right.providerId && left.modelId === right.modelId;
15
+ }
16
+ function inAutoScope(deployment, scope) {
17
+ return scope.kind === 'global' || deployment.providerId === scope.providerId;
18
+ }
19
+ function inAutoSubset(deployment, subset) {
20
+ if (!subset?.length)
21
+ return true;
22
+ return subset.some(item => sameDeploymentRef(item, deployment));
23
+ }
24
+ function expectedRequestCost(candidate, request) {
25
+ const input = finiteNumber(candidate.expectedInputCostUsdPerM);
26
+ const output = finiteNumber(candidate.expectedOutputCostUsdPerM);
27
+ if (input === undefined || output === undefined)
28
+ return undefined;
29
+ return input * Math.max(0, request.estimatedInputTokens) / 1_000_000
30
+ + output * Math.max(0, request.expectedOutputTokens) / 1_000_000;
31
+ }
32
+ function finiteNumber(value) {
33
+ const number = Number(value);
34
+ return Number.isFinite(number) ? number : undefined;
35
+ }
36
+ /**
37
+ * The single authoritative implementation of Auto hard eligibility.
38
+ *
39
+ * `Agent.autoRouteCandidates()` only projects configuration into candidate
40
+ * facts (enabled flag, capability list, prices, context window, privacy and
41
+ * region declarations, observed endpoint health). Every hard rule that can
42
+ * remove a candidate lives here so there is never a second copy of the rules
43
+ * in the Agent or in the execution controller.
44
+ */
45
+ function evaluateRouteEligibility(input) {
46
+ const { selection, policy, request, candidates } = input;
47
+ const eligible = [];
48
+ const excluded = [];
49
+ if (selection.kind === 'fixed') {
50
+ // Fixed selections bypass Auto eligibility entirely; the caller resolves
51
+ // the deployment directly. Returning an empty guard result keeps this
52
+ // function honest about what it can decide.
53
+ return { eligible, excluded };
54
+ }
55
+ for (const candidate of candidates) {
56
+ const reasons = [];
57
+ if (!candidate.enabled)
58
+ reasons.push('disabled');
59
+ if (candidate.unavailableReason)
60
+ reasons.push(candidate.unavailableReason);
61
+ if (!inAutoScope(candidate.deployment, selection.scope))
62
+ reasons.push('outside_scope');
63
+ if (!inAutoSubset(candidate.deployment, selection.subset))
64
+ reasons.push('outside_subset');
65
+ if (candidate.fallbackOnly)
66
+ reasons.push('fallback_only');
67
+ if (candidate.preview && !policy.allowPreview)
68
+ reasons.push('preview_disallowed');
69
+ if (request.estimatedInputTokens + request.expectedOutputTokens > Math.max(0, candidate.maxContextTokens || 0)) {
70
+ reasons.push('context_too_small');
71
+ }
72
+ if (policy.privacy !== 'default' && !candidate.privacy.includes(policy.privacy))
73
+ reasons.push(`privacy:${policy.privacy}`);
74
+ if (policy.dataRegion) {
75
+ const requiredRegion = policy.dataRegion.toLowerCase();
76
+ const regions = new Set((candidate.dataRegions || []).map(item => String(item).toLowerCase()));
77
+ if (!regions.has(requiredRegion))
78
+ reasons.push(`data_region:${policy.dataRegion}`);
79
+ }
80
+ const supportedParameters = new Set((candidate.supportedProtocolParameters || []).map(item => String(item).toLowerCase()));
81
+ for (const parameter of policy.requiredProtocolParameters || []) {
82
+ const requiredParameter = String(parameter).toLowerCase();
83
+ if (requiredParameter && !supportedParameters.has(requiredParameter))
84
+ reasons.push(`protocol_parameter:${requiredParameter}`);
85
+ }
86
+ const expectedCost = expectedRequestCost(candidate, request);
87
+ if (policy.maxExpectedCostUsd !== undefined && (expectedCost === undefined || expectedCost > policy.maxExpectedCostUsd)) {
88
+ reasons.push(expectedCost === undefined ? 'unknown_cost' : 'budget_exceeded');
89
+ }
90
+ if (reasons.length)
91
+ excluded.push({ deployment: { ...candidate.deployment }, reasons: [...new Set(reasons)] });
92
+ else
93
+ eligible.push(candidate);
94
+ }
95
+ return { eligible, excluded };
96
+ }
97
+ /**
98
+ * A candidate excluded by the Guard may still be used by provider-local
99
+ * recovery when *only* the fallback-only marker removed it. This mirrors the
100
+ * pre-existing behaviour: fallback-only deployments are never primary
101
+ * candidates but may serve as an explicit recovery pool.
102
+ */
103
+ function excludedOnlyForFallbackPool(exclusion) {
104
+ return !!exclusion
105
+ && exclusion.reasons.length > 0
106
+ && exclusion.reasons.every(reason => reason === 'fallback_only');
107
+ }
108
+ //# sourceMappingURL=routeEligibility.js.map
@@ -0,0 +1,6 @@
1
+ /** Physical RAM policy; never evicts an active run or changes model context. */
2
+ export declare function runtimeMemoryBudget(totalBytes?: number): {
3
+ maxResidentRuntimes: number;
4
+ idleTtlMs: number;
5
+ };
6
+ //# sourceMappingURL=runtimeMemoryBudget.d.ts.map
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runtimeMemoryBudget = runtimeMemoryBudget;
4
+ const node_os_1 = require("node:os");
5
+ /** Physical RAM policy; never evicts an active run or changes model context. */
6
+ function runtimeMemoryBudget(totalBytes = (0, node_os_1.totalmem)()) {
7
+ const constrained = !Number.isFinite(totalBytes) || totalBytes <= 8 * 1024 ** 3;
8
+ return constrained
9
+ ? { maxResidentRuntimes: 2, idleTtlMs: 30_000 }
10
+ : { maxResidentRuntimes: 8, idleTtlMs: 300_000 };
11
+ }
12
+ //# sourceMappingURL=runtimeMemoryBudget.js.map
@@ -3,7 +3,7 @@ import { BrowserUseRequest } from './browserUse';
3
3
  import { AgentPromptMessage, ConversationContextCompressOptions, ConversationKernelRunOptions, ConversationKernelRunResult, ConversationQueueAction, ConversationQueueActionInput, ConversationQueueMode, ConversationRuntimeState, ConversationStopResult } from './conversationKernel';
4
4
  import { ConversationRuntimeTarget, NormalizedConversationTarget } from './conversationTarget';
5
5
  import { AgentMode, AgentWorkEvent, ConversationInputEnvelope, GuideReceipt } from './types';
6
- import type { AutoRouteRatingResult, ConversationSnapshot } from './agent';
6
+ import type { ConversationSnapshot } from './agent';
7
7
  export interface UtilityPromptRequest {
8
8
  message: string | AgentPromptMessage;
9
9
  target: ConversationRuntimeTarget;
@@ -78,9 +78,16 @@ export type UtilityAgentRequest = {
78
78
  method: 'snapshot';
79
79
  params: {
80
80
  target: ConversationRuntimeTarget;
81
+ /**
82
+ * dev-0.6.6: `transcript: 'force'` always returns the full transcript;
83
+ * `transcriptRevision` lets a caller that already holds that exact
84
+ * revision receive a slim snapshot instead of a full re-transfer.
85
+ */
81
86
  options?: {
82
87
  window?: number;
83
88
  before?: number;
89
+ transcript?: 'auto' | 'force';
90
+ transcriptRevision?: string;
84
91
  };
85
92
  };
86
93
  } | {
@@ -125,14 +132,6 @@ export type UtilityAgentRequest = {
125
132
  target: ConversationRuntimeTarget;
126
133
  options?: ConversationContextCompressOptions;
127
134
  };
128
- } | {
129
- id: string;
130
- method: 'rate_auto_route';
131
- params: {
132
- target: ConversationRuntimeTarget;
133
- score: number;
134
- routeId?: string;
135
- };
136
135
  } | {
137
136
  id: string;
138
137
  method: 'set_work_run_expanded';
@@ -240,7 +239,6 @@ export interface UtilityAgentSnapshotResult {
240
239
  [key: string]: unknown;
241
240
  }
242
241
  export type UtilityGuideResult = GuideReceipt;
243
- export type UtilityAutoRouteRatingResult = AutoRouteRatingResult;
244
242
  export type UtilityConversationRewindResult = ConversationSnapshot;
245
243
  export {};
246
244
  //# sourceMappingURL=utilityAgentProtocol.d.ts.map
@@ -175,6 +175,8 @@ function createUtilityHostToolHandler(options) {
175
175
  dryRun: args.dry_run === true || args.dryRun === true,
176
176
  captureMaxWidth: Number(args.capture_max_width || args.captureMaxWidth),
177
177
  captureMaxHeight: Number(args.capture_max_height || args.captureMaxHeight),
178
+ sparseWaitMs: Number(args.sparse_wait_ms ?? args.sparseWaitMs),
179
+ signal,
178
180
  gradientColors: Array.isArray(args.gradient_colors) ? args.gradient_colors : undefined,
179
181
  gradientSpeed: Number(args.gradient_speed || 0) || undefined,
180
182
  gradientWidth: Number(args.gradient_width || 0) || undefined,
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Returns a data URL within `maxBytes`, or `null` when the input is not a
3
+ * decodable image (callers keep their existing path-based fallback then).
4
+ */
5
+ export declare function downscaleDataUrlToBytes(dataUrl: string, maxBytes: number): string | null;
6
+ //# sourceMappingURL=visualDownscale.d.ts.map
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.downscaleDataUrlToBytes = downscaleDataUrlToBytes;
37
+ const pngjs_1 = require("pngjs");
38
+ const jpeg = __importStar(require("jpeg-js"));
39
+ const conversationVisualBudget_1 = require("./conversationVisualBudget");
40
+ const MAX_SOURCE_PIXELS = 12 * 1024 * 1024;
41
+ const MIN_SCALE = 0.08;
42
+ function decodeDataUrl(dataUrl) {
43
+ const match = /^data:(image\/(?:png|jpe?g));base64,([A-Za-z0-9+/=\r\n]+)$/i.exec(String(dataUrl || ''));
44
+ if (!match)
45
+ return null;
46
+ const mimeType = /^image\/png$/i.test(match[1]) ? 'image/png' : 'image/jpeg';
47
+ let bytes;
48
+ try {
49
+ bytes = Buffer.from(match[2].replace(/\s/g, ''), 'base64');
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ try {
55
+ if (mimeType === 'image/png') {
56
+ const decoded = pngjs_1.PNG.sync.read(bytes);
57
+ if (!decoded?.width || !decoded?.height)
58
+ return null;
59
+ if (decoded.width * decoded.height > MAX_SOURCE_PIXELS)
60
+ return null;
61
+ return { width: decoded.width, height: decoded.height, data: decoded.data, mimeType };
62
+ }
63
+ const decoded = jpeg.decode(bytes, { useTArray: true, formatAsRGBA: true, maxResolutionInMP: 24, maxMemoryUsageInMB: 192 });
64
+ if (!decoded?.width || !decoded?.height)
65
+ return null;
66
+ if (decoded.width * decoded.height > MAX_SOURCE_PIXELS)
67
+ return null;
68
+ return { width: decoded.width, height: decoded.height, data: decoded.data, mimeType };
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ function resample(source, scale) {
75
+ const width = Math.max(1, Math.round(source.width * scale));
76
+ const height = Math.max(1, Math.round(source.height * scale));
77
+ const output = Buffer.alloc(width * height * 4);
78
+ const sourceData = Buffer.isBuffer(source.data) ? source.data : Buffer.from(source.data);
79
+ const xRatio = source.width / width;
80
+ const yRatio = source.height / height;
81
+ for (let y = 0; y < height; y += 1) {
82
+ const sourceY = Math.min(source.height - 1, Math.floor(y * yRatio));
83
+ for (let x = 0; x < width; x += 1) {
84
+ const sourceX = Math.min(source.width - 1, Math.floor(x * xRatio));
85
+ const from = (sourceY * source.width + sourceX) * 4;
86
+ const to = (y * width + x) * 4;
87
+ output[to] = sourceData[from];
88
+ output[to + 1] = sourceData[from + 1];
89
+ output[to + 2] = sourceData[from + 2];
90
+ output[to + 3] = sourceData[from + 3];
91
+ }
92
+ }
93
+ return { width, height, data: output };
94
+ }
95
+ function encodePng(frame) {
96
+ const png = new pngjs_1.PNG({ width: frame.width, height: frame.height });
97
+ frame.data.copy(png.data);
98
+ return `data:image/png;base64,${pngjs_1.PNG.sync.write(png, { deflateLevel: 6, colorType: 6 }).toString('base64')}`;
99
+ }
100
+ function encodeJpeg(frame, quality) {
101
+ const encoded = jpeg.encode({ data: frame.data, width: frame.width, height: frame.height }, quality);
102
+ return `data:image/jpeg;base64,${Buffer.from(encoded.data).toString('base64')}`;
103
+ }
104
+ /**
105
+ * Returns a data URL within `maxBytes`, or `null` when the input is not a
106
+ * decodable image (callers keep their existing path-based fallback then).
107
+ */
108
+ function downscaleDataUrlToBytes(dataUrl, maxBytes) {
109
+ // No artificial floor: a severe budget may legitimately ask for a very small
110
+ // image, and the caller decides what is usable.
111
+ const limit = Math.max(1024, Math.floor(Number(maxBytes) || 0));
112
+ if (!limit)
113
+ return null;
114
+ if ((0, conversationVisualBudget_1.visualDataUrlBytes)(dataUrl) <= limit)
115
+ return dataUrl;
116
+ const source = decodeDataUrl(dataUrl);
117
+ if (!source)
118
+ return null;
119
+ // Density scales with the allowed pixel area for this budget, then the result
120
+ // is verified by re-encoding (encoder overhead differs between formats).
121
+ let scale = Math.min(1, Math.sqrt(limit / Math.max(1, (0, conversationVisualBudget_1.visualDataUrlBytes)(dataUrl))));
122
+ for (let attempt = 0; attempt < 4 && scale >= MIN_SCALE; attempt += 1) {
123
+ const frame = resample(source, scale);
124
+ const asPng = encodePng(frame);
125
+ if ((0, conversationVisualBudget_1.visualDataUrlBytes)(asPng) <= limit)
126
+ return asPng;
127
+ const asJpeg = encodeJpeg(frame, attempt === 0 ? 72 : 60);
128
+ if ((0, conversationVisualBudget_1.visualDataUrlBytes)(asJpeg) <= limit)
129
+ return asJpeg;
130
+ scale *= 0.6;
131
+ }
132
+ // Last resort: a very small JPEG still gives the model a usable overview.
133
+ const tiny = resample(source, MIN_SCALE);
134
+ const encoded = encodeJpeg(tiny, 45);
135
+ return (0, conversationVisualBudget_1.visualDataUrlBytes)(encoded) <= limit ? encoded : null;
136
+ }
137
+ //# sourceMappingURL=visualDownscale.js.map
@@ -1,7 +1,7 @@
1
1
  import { AgentMode, AgentWorkEvent, ConversationInputEnvelope, GuideReceipt } from './types';
2
2
  import { ConversationQueueAction, ConversationQueueActionInput } from './conversationKernel';
3
3
  import { TerminalTakeoverEvent, TerminalTakeoverOwnerFilter, TerminalTakeoverState } from '../tools/terminalTakeover';
4
- import { WslAgentPromptRequest, WslAgentPromptResult, WslAutoRouteRatingResult, WslAgentWorkspace, WslAgentStopResult, WslConversationRewindResult, WslHostToolRequest } from './wslAgentProtocol';
4
+ import { WslAgentPromptRequest, WslAgentPromptResult, WslAgentWorkspace, WslAgentStopResult, WslConversationRewindResult, WslHostToolRequest } from './wslAgentProtocol';
5
5
  import { ConversationRuntimeTarget, NormalizedConversationTarget } from './conversationTarget';
6
6
  import { AsyncProcessResult } from './asyncProcess';
7
7
  type WorkListener = (event: AgentWorkEvent) => void;
@@ -89,7 +89,6 @@ export declare class WslAgentClient {
89
89
  keepRecent?: number;
90
90
  force?: boolean;
91
91
  }): Promise<Record<string, unknown>>;
92
- rateAutoRoute(target: ConversationRuntimeTarget, score: number, routeId?: string): Promise<WslAutoRouteRatingResult>;
93
92
  setWorkRunExpanded(target: ConversationRuntimeTarget, runId: string, expanded: boolean): Promise<boolean>;
94
93
  setMode(target: ConversationRuntimeTarget, mode: AgentMode): Promise<AgentMode>;
95
94
  setModel(target: ConversationRuntimeTarget, model: string): Promise<string>;
@@ -348,14 +348,6 @@ class WslAgentClient {
348
348
  await this.start();
349
349
  return await this.request('context_compress', { target: await this.mapTarget(target), options }, 120_000);
350
350
  }
351
- async rateAutoRoute(target, score, routeId = '') {
352
- await this.start();
353
- return await this.request('rate_auto_route', {
354
- target: await this.mapTarget(target),
355
- score,
356
- routeId: String(routeId || ''),
357
- }, 5_000);
358
- }
359
351
  async setWorkRunExpanded(target, runId, expanded) {
360
352
  await this.start();
361
353
  return !!await this.request('set_work_run_expanded', { target: await this.mapTarget(target), runId, expanded }, 5_000);