@iderouter/index-mcp 0.2.0-beta.1
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 +93 -0
- package/package.json +26 -0
- package/scripts/benchmark-all.mjs +177 -0
- package/scripts/benchmark-auto-continuation.mjs +188 -0
- package/scripts/benchmark-background-fine-resume.mjs +245 -0
- package/scripts/benchmark-background-fine-wait.mjs +76 -0
- package/scripts/benchmark-background-fine.mjs +132 -0
- package/scripts/benchmark-clean-snapshot.mjs +83 -0
- package/scripts/benchmark-coarse-ready-search.mjs +161 -0
- package/scripts/benchmark-deferred.mjs +62 -0
- package/scripts/benchmark-first-semantic-visible.mjs +151 -0
- package/scripts/benchmark-gate.mjs +107 -0
- package/scripts/benchmark-generic-resumed-single-chunk-embed.mjs +104 -0
- package/scripts/benchmark-noop.mjs +24 -0
- package/scripts/benchmark-priority-ready-search.mjs +165 -0
- package/scripts/benchmark-repeat-search.mjs +148 -0
- package/scripts/benchmark-resumed-retry-burst.mjs +187 -0
- package/scripts/benchmark-resumed-single-chunk-success.mjs +154 -0
- package/scripts/benchmark-resumed-single-chunk.mjs +146 -0
- package/scripts/benchmark-single-priority-chunk-embed.mjs +145 -0
- package/scripts/benchmark-small-change.mjs +146 -0
- package/scripts/benchmark-stage-summary.mjs +88 -0
- package/scripts/lib/auto-continuation-state.mjs +34 -0
- package/scripts/lib/benchmark-query-packs.mjs +123 -0
- package/scripts/lib/benchmark-snapshot.mjs +109 -0
- package/scripts/lib/mcp-bench.mjs +455 -0
- package/src/architecture-query-fallback.js +50 -0
- package/src/background-definition-chunks.js +199 -0
- package/src/background-embedding-profile.js +64 -0
- package/src/background-fine-budget.js +18 -0
- package/src/background-fine-runtime.js +179 -0
- package/src/background-fine-selection.js +332 -0
- package/src/checkpoint-policy.js +16 -0
- package/src/conflict-policy.js +17 -0
- package/src/deferred-retry-delay.js +14 -0
- package/src/deferred-retry-status.js +10 -0
- package/src/embedding-attempt-ordinal.js +17 -0
- package/src/embedding-failure-penalty.js +60 -0
- package/src/embedding-failure-policy.js +52 -0
- package/src/embedding-flush-timeout.js +33 -0
- package/src/embedding-inflight-status.js +18 -0
- package/src/embedding-model-policy.js +44 -0
- package/src/embedding-next-switch.js +18 -0
- package/src/embedding-request-status-detail.js +25 -0
- package/src/embedding-request-status.js +22 -0
- package/src/embedding-selection-order.js +23 -0
- package/src/fine-run-queue.js +14 -0
- package/src/index.js +7970 -0
- package/src/job-supersession.js +25 -0
- package/src/priority-progress.js +20 -0
- package/src/priority-ready-anchor-coverage-normalize.js +18 -0
- package/src/priority-ready-anchor-coverage.js +23 -0
- package/src/priority-ready-hotspots.js +344 -0
- package/src/priority-ready-status.js +30 -0
- package/src/priority-ready-targets.js +45 -0
- package/src/priority-usable-attempt-plan.js +44 -0
- package/src/priority-usable-attempt-timeout.js +18 -0
- package/src/priority-usable-fast-path.js +11 -0
- package/src/priority-usable-probe-order.js +34 -0
- package/src/remote-strategy-failure-cache.js +55 -0
- package/src/resume-seed.js +9 -0
- package/src/semantic-first-checkpoint.js +8 -0
- package/src/semantic-slow-path.js +10 -0
- package/src/single-chunk-attempt-timeout.js +13 -0
- package/src/single-chunk-embedding-content.js +26 -0
- package/src/single-chunk-embedding-policy.js +18 -0
- package/src/single-chunk-provider-order.js +12 -0
- package/src/single-chunk-provider-policy.js +63 -0
- package/src/worker-lock-retry.js +24 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export function backgroundEmbeddingProfileForSource({
|
|
2
|
+
source = "",
|
|
3
|
+
batchSize = 1,
|
|
4
|
+
concurrency = 1,
|
|
5
|
+
flushTarget = 1,
|
|
6
|
+
healthLatencyMs = 0,
|
|
7
|
+
observedRequestMs = 0,
|
|
8
|
+
}) {
|
|
9
|
+
const resolved = {
|
|
10
|
+
batchSize: Math.max(1, Number(batchSize) || 1),
|
|
11
|
+
concurrency: Math.max(1, Number(concurrency) || 1),
|
|
12
|
+
flushTarget: Math.max(1, Number(flushTarget) || 1),
|
|
13
|
+
};
|
|
14
|
+
const sourceText = String(source || "");
|
|
15
|
+
const latencyMs = Math.max(0, Number(healthLatencyMs) || 0);
|
|
16
|
+
const observedMs = Math.max(0, Number(observedRequestMs) || 0);
|
|
17
|
+
const effectiveLatencyMs = observedMs > 0 ? observedMs : latencyMs;
|
|
18
|
+
if (sourceText.startsWith("previous_failure_fallback_deferred_resume")) {
|
|
19
|
+
return {
|
|
20
|
+
batchSize: Math.min(resolved.batchSize, 8),
|
|
21
|
+
concurrency: Math.min(resolved.concurrency, 1),
|
|
22
|
+
flushTarget: Math.min(resolved.flushTarget, 8),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (
|
|
26
|
+
!sourceText.startsWith("background_fine_strategy") &&
|
|
27
|
+
!sourceText.startsWith("background_fine_post_priority_strategy")
|
|
28
|
+
) {
|
|
29
|
+
return resolved;
|
|
30
|
+
}
|
|
31
|
+
const isPostPriority = sourceText.startsWith("background_fine_post_priority_strategy");
|
|
32
|
+
if (isPostPriority && effectiveLatencyMs <= 0) {
|
|
33
|
+
return {
|
|
34
|
+
batchSize: Math.min(resolved.batchSize, 24),
|
|
35
|
+
concurrency: Math.min(resolved.concurrency, 3),
|
|
36
|
+
flushTarget: Math.min(resolved.flushTarget, 32),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (isPostPriority && effectiveLatencyMs > 0 && effectiveLatencyMs <= 2200) {
|
|
40
|
+
return resolved;
|
|
41
|
+
}
|
|
42
|
+
if (isPostPriority && effectiveLatencyMs > 0 && effectiveLatencyMs < 3000) {
|
|
43
|
+
return {
|
|
44
|
+
batchSize: Math.min(resolved.batchSize, 24),
|
|
45
|
+
concurrency: Math.min(resolved.concurrency, 3),
|
|
46
|
+
flushTarget: Math.min(resolved.flushTarget, 32),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (effectiveLatencyMs >= 2400) {
|
|
50
|
+
return {
|
|
51
|
+
batchSize: Math.min(resolved.batchSize, 16),
|
|
52
|
+
concurrency: Math.min(resolved.concurrency, 2),
|
|
53
|
+
flushTarget: Math.min(resolved.flushTarget, 24),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (effectiveLatencyMs < 3000) {
|
|
57
|
+
return resolved;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
batchSize: Math.min(resolved.batchSize, 16),
|
|
61
|
+
concurrency: Math.min(resolved.concurrency, 2),
|
|
62
|
+
flushTarget: Math.min(resolved.flushTarget, 24),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function shouldDeferBackgroundBeforeFile({
|
|
2
|
+
priorityFineReady,
|
|
3
|
+
embeddedCount,
|
|
4
|
+
embeddedCountBaseline = 0,
|
|
5
|
+
pendingBatchCount = 0,
|
|
6
|
+
nextFileChunkEstimate,
|
|
7
|
+
incrementalBackgroundChunkBudget,
|
|
8
|
+
}) {
|
|
9
|
+
if (!priorityFineReady) return false;
|
|
10
|
+
const budget = Math.max(1, Number(incrementalBackgroundChunkBudget) || 0);
|
|
11
|
+
const embedded = Math.max(0, (Number(embeddedCount) || 0) - Math.max(0, Number(embeddedCountBaseline) || 0));
|
|
12
|
+
const pending = Math.max(0, Number(pendingBatchCount) || 0);
|
|
13
|
+
const inFlightTotal = embedded + pending;
|
|
14
|
+
if (inFlightTotal >= budget) return true;
|
|
15
|
+
const nextCost = Math.max(0, Number(nextFileChunkEstimate) || 0);
|
|
16
|
+
if (nextCost <= 0) return false;
|
|
17
|
+
return inFlightTotal > 0 && inFlightTotal + nextCost > budget;
|
|
18
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
export function shouldDeferBackgroundAfterPriorityFlushes({
|
|
2
|
+
priorityFineReady,
|
|
3
|
+
backgroundFlushCount = 0,
|
|
4
|
+
maxBackgroundFlushes = 0,
|
|
5
|
+
}) {
|
|
6
|
+
if (!priorityFineReady) return false;
|
|
7
|
+
const limit = Math.max(0, Number(maxBackgroundFlushes) || 0);
|
|
8
|
+
if (limit <= 0) return false;
|
|
9
|
+
return Math.max(0, Number(backgroundFlushCount) || 0) >= limit;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function shouldDeferBackgroundAfterPriorityFiles({
|
|
13
|
+
priorityFineReady,
|
|
14
|
+
completedBackgroundFileCount = 0,
|
|
15
|
+
completedBackgroundFileBaseline = 0,
|
|
16
|
+
maxBackgroundFilesAfterPriorityReady = 0,
|
|
17
|
+
minBackgroundFilesAfterPriorityReady = 0,
|
|
18
|
+
}) {
|
|
19
|
+
if (!priorityFineReady) return false;
|
|
20
|
+
const limit = Math.max(
|
|
21
|
+
0,
|
|
22
|
+
Math.max(
|
|
23
|
+
Number(maxBackgroundFilesAfterPriorityReady) || 0,
|
|
24
|
+
Number(minBackgroundFilesAfterPriorityReady) || 0,
|
|
25
|
+
),
|
|
26
|
+
);
|
|
27
|
+
if (limit <= 0) return false;
|
|
28
|
+
const completed = Math.max(
|
|
29
|
+
0,
|
|
30
|
+
(Number(completedBackgroundFileCount) || 0) - Math.max(0, Number(completedBackgroundFileBaseline) || 0),
|
|
31
|
+
);
|
|
32
|
+
return completed >= limit;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function selectFineEmbeddingSource({
|
|
36
|
+
priorityFineReady,
|
|
37
|
+
initialSource = "",
|
|
38
|
+
}) {
|
|
39
|
+
if (priorityFineReady) return "background_fine_post_priority_strategy";
|
|
40
|
+
return String(initialSource || "");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function shouldDeferBackgroundAfterPriorityRuntime({
|
|
44
|
+
priorityFineReady,
|
|
45
|
+
nowMs = 0,
|
|
46
|
+
postPriorityReadyAtMs = 0,
|
|
47
|
+
maxPostPriorityRuntimeMs = 0,
|
|
48
|
+
}) {
|
|
49
|
+
if (!priorityFineReady) return false;
|
|
50
|
+
const limit = Math.max(0, Number(maxPostPriorityRuntimeMs) || 0);
|
|
51
|
+
if (limit <= 0) return false;
|
|
52
|
+
const readyAt = Math.max(0, Number(postPriorityReadyAtMs) || 0);
|
|
53
|
+
if (readyAt <= 0) return false;
|
|
54
|
+
const elapsed = Math.max(0, Number(nowMs) || 0) - readyAt;
|
|
55
|
+
return elapsed >= limit;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function initializePostPriorityBudgetState({
|
|
59
|
+
priorityFineReady = false,
|
|
60
|
+
nowMs = 0,
|
|
61
|
+
completedBackgroundFileCount = 0,
|
|
62
|
+
}) {
|
|
63
|
+
return {
|
|
64
|
+
postPriorityReadyAtMs: priorityFineReady ? Math.max(0, Number(nowMs) || 0) : 0,
|
|
65
|
+
postPriorityCompletedBackgroundFilesBaseline: Math.max(0, Number(completedBackgroundFileCount) || 0),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function shouldFlushAfterPriorityReadyFile({
|
|
70
|
+
isPriorityReadyFile = false,
|
|
71
|
+
pendingChunkCount = 0,
|
|
72
|
+
remainingPriorityReadyFileCount = 0,
|
|
73
|
+
eagerDeferredResume = false,
|
|
74
|
+
}) {
|
|
75
|
+
if (!isPriorityReadyFile) return false;
|
|
76
|
+
if (Math.max(0, Number(pendingChunkCount) || 0) <= 0) return false;
|
|
77
|
+
if (eagerDeferredResume) return true;
|
|
78
|
+
return Math.max(0, Number(remainingPriorityReadyFileCount) || 0) <= 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function shouldDeferFreshRunAfterPriorityReadyFile({
|
|
82
|
+
isResumePartialSeed = false,
|
|
83
|
+
isPriorityReadyFile = false,
|
|
84
|
+
remainingPriorityReadyFileCount = 0,
|
|
85
|
+
priorityFineReady = false,
|
|
86
|
+
hasRemainingFiles = false,
|
|
87
|
+
}) {
|
|
88
|
+
void isResumePartialSeed;
|
|
89
|
+
void isPriorityReadyFile;
|
|
90
|
+
void remainingPriorityReadyFileCount;
|
|
91
|
+
void priorityFineReady;
|
|
92
|
+
void hasRemainingFiles;
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function shouldDeferFreshRunAfterPriorityReady({
|
|
97
|
+
isResumePartialSeed = false,
|
|
98
|
+
priorityFineReady = false,
|
|
99
|
+
hasRemainingQueuedFiles = false,
|
|
100
|
+
}) {
|
|
101
|
+
void isResumePartialSeed;
|
|
102
|
+
void priorityFineReady;
|
|
103
|
+
void hasRemainingQueuedFiles;
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function postPriorityRuntimeBudgetMsForRun({
|
|
108
|
+
isResumePartialSeed = false,
|
|
109
|
+
freshBudgetMs = 0,
|
|
110
|
+
resumeBudgetMs = 0,
|
|
111
|
+
observedRequestMs = 0,
|
|
112
|
+
maxBackgroundFlushes = 0,
|
|
113
|
+
}) {
|
|
114
|
+
const baseBudgetMs = Math.max(0, Number(isResumePartialSeed ? resumeBudgetMs : freshBudgetMs) || 0);
|
|
115
|
+
if (isResumePartialSeed) return baseBudgetMs;
|
|
116
|
+
const observedMs = Math.max(0, Number(observedRequestMs) || 0);
|
|
117
|
+
const flushes = Math.max(0, Number(maxBackgroundFlushes) || 0);
|
|
118
|
+
if (observedMs <= 0 || observedMs >= 3000 || flushes <= 0) return baseBudgetMs;
|
|
119
|
+
const estimatedBudgetMs = Math.round((observedMs * flushes) + 1000);
|
|
120
|
+
return Math.max(baseBudgetMs, Math.min(12000, estimatedBudgetMs));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function backgroundIncrementalChunkBudgetForRun({
|
|
124
|
+
isResumePartialSeed = false,
|
|
125
|
+
freshBudget = 0,
|
|
126
|
+
resumeBudget = 0,
|
|
127
|
+
}) {
|
|
128
|
+
return Math.max(1, Number(isResumePartialSeed ? resumeBudget : freshBudget) || 0);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function maxBackgroundFilesAfterPriorityReadyForRun({
|
|
132
|
+
isResumePartialSeed = false,
|
|
133
|
+
configuredMaxFiles = 0,
|
|
134
|
+
observedRequestMs = 0,
|
|
135
|
+
}) {
|
|
136
|
+
const configured = Math.max(0, Number(configuredMaxFiles) || 0);
|
|
137
|
+
if (isResumePartialSeed) return configured;
|
|
138
|
+
const observedMs = Math.max(0, Number(observedRequestMs) || 0);
|
|
139
|
+
if (observedMs <= 0 || observedMs >= 3000) return configured;
|
|
140
|
+
if (observedMs <= 2200) return Math.max(configured, 18);
|
|
141
|
+
return Math.max(configured, 16);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function maxBackgroundFlushesAfterPriorityReadyForRun({
|
|
145
|
+
isResumePartialSeed = false,
|
|
146
|
+
configuredMaxFlushes = 0,
|
|
147
|
+
incrementalBackgroundChunkBudget = 0,
|
|
148
|
+
embeddingFlushChunkTarget = 0,
|
|
149
|
+
}) {
|
|
150
|
+
const configured = Math.max(0, Number(configuredMaxFlushes) || 0);
|
|
151
|
+
const flushTarget = Math.max(1, Number(embeddingFlushChunkTarget) || 1);
|
|
152
|
+
const chunkBudget = Math.max(1, Number(incrementalBackgroundChunkBudget) || 1);
|
|
153
|
+
const budgetDrivenMinimum = Math.max(1, Math.floor(chunkBudget / flushTarget));
|
|
154
|
+
if (!isResumePartialSeed) {
|
|
155
|
+
return Math.max(configured, budgetDrivenMinimum);
|
|
156
|
+
}
|
|
157
|
+
return Math.max(configured, budgetDrivenMinimum);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function shouldForceCheckpointAfterPriorityReadyFlush({
|
|
161
|
+
flushedAfterPriorityReadyFile = false,
|
|
162
|
+
}) {
|
|
163
|
+
return Boolean(flushedAfterPriorityReadyFile);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function shouldScheduleDeferredBackgroundContinuation({
|
|
167
|
+
backgroundCompletedFileCount = 0,
|
|
168
|
+
backgroundTargetFileCount = 0,
|
|
169
|
+
continuationPassCount = 0,
|
|
170
|
+
maxContinuationPasses = 0,
|
|
171
|
+
}) {
|
|
172
|
+
const target = Math.max(0, Number(backgroundTargetFileCount) || 0);
|
|
173
|
+
const completed = Math.max(0, Number(backgroundCompletedFileCount) || 0);
|
|
174
|
+
if (target <= 0) return false;
|
|
175
|
+
if (completed >= target) return false;
|
|
176
|
+
const maxPasses = Math.max(0, Number(maxContinuationPasses) || 0);
|
|
177
|
+
if (maxPasses > 0 && Math.max(0, Number(continuationPassCount) || 0) >= maxPasses) return false;
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_BUCKET_ORDER = [
|
|
4
|
+
"relay",
|
|
5
|
+
"service",
|
|
6
|
+
"model",
|
|
7
|
+
"pkg",
|
|
8
|
+
"relay_channel",
|
|
9
|
+
"controller",
|
|
10
|
+
"router",
|
|
11
|
+
"middleware",
|
|
12
|
+
"other",
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
function parseProviderSet(value, fallback) {
|
|
16
|
+
const text = String(value || "").trim();
|
|
17
|
+
if (!text) return new Set(fallback);
|
|
18
|
+
return new Set(
|
|
19
|
+
text
|
|
20
|
+
.split(",")
|
|
21
|
+
.map((item) => item.trim().toLowerCase())
|
|
22
|
+
.filter(Boolean),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const DEFAULT_HOT_RELAY_CHANNEL_PROVIDERS = parseProviderSet(
|
|
27
|
+
process.env.IDEROUTER_BACKGROUND_HOT_RELAY_CHANNEL_PROVIDERS,
|
|
28
|
+
[
|
|
29
|
+
"openai",
|
|
30
|
+
"claude",
|
|
31
|
+
"gemini",
|
|
32
|
+
"ali",
|
|
33
|
+
"aws",
|
|
34
|
+
"vertex",
|
|
35
|
+
],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export function backgroundBucketKey(relativePathInput) {
|
|
39
|
+
const relativePath = String(relativePathInput || "").toLowerCase().split(path.sep).join("/");
|
|
40
|
+
if (relativePath.startsWith("relay/channel/")) {
|
|
41
|
+
const parts = relativePath.split("/");
|
|
42
|
+
return `relay_channel:${parts[2] || "misc"}`;
|
|
43
|
+
}
|
|
44
|
+
if (relativePath.startsWith("relay/")) return "relay";
|
|
45
|
+
if (relativePath.startsWith("service/")) return "service";
|
|
46
|
+
if (relativePath.startsWith("model/")) return "model";
|
|
47
|
+
if (relativePath.startsWith("pkg/")) return "pkg";
|
|
48
|
+
if (relativePath.startsWith("controller/")) return "controller";
|
|
49
|
+
if (relativePath.startsWith("router/")) return "router";
|
|
50
|
+
if (relativePath.startsWith("middleware/")) return "middleware";
|
|
51
|
+
return "other";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function backgroundBucketPriority(bucketKey) {
|
|
55
|
+
if (bucketKey === "relay") return 0;
|
|
56
|
+
if (bucketKey === "service") return 1;
|
|
57
|
+
if (bucketKey === "model") return 2;
|
|
58
|
+
if (bucketKey === "pkg") return 3;
|
|
59
|
+
if (bucketKey.startsWith("relay_channel:")) return 4;
|
|
60
|
+
if (bucketKey === "controller") return 5;
|
|
61
|
+
if (bucketKey === "router") return 6;
|
|
62
|
+
if (bucketKey === "middleware") return 7;
|
|
63
|
+
return 8;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function bucketFamily(bucketKey) {
|
|
67
|
+
return String(bucketKey || "").startsWith("relay_channel:") ? "relay_channel" : String(bucketKey || "other");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function relayChannelSecondaryAllowed(relativePathInput) {
|
|
71
|
+
const relativePath = String(relativePathInput || "").toLowerCase().split(path.sep).join("/");
|
|
72
|
+
if (!relativePath.startsWith("relay/channel/")) return true;
|
|
73
|
+
const fileName = path.basename(relativePath);
|
|
74
|
+
return fileName === "adaptor.go" || fileName.startsWith("relay-") || fileName.includes("bridge");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function relayChannelPreferredProviderFile(relativePathInput) {
|
|
78
|
+
const relativePath = String(relativePathInput || "").toLowerCase().split(path.sep).join("/");
|
|
79
|
+
if (!relativePath.startsWith("relay/channel/")) return false;
|
|
80
|
+
return relayChannelSecondaryAllowed(relativePath);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function relayChannelProviderTier(providerKey, hotRelayChannelProviders = DEFAULT_HOT_RELAY_CHANNEL_PROVIDERS) {
|
|
84
|
+
return hotRelayChannelProviders.has(String(providerKey || "").toLowerCase()) ? "hot" : "warm";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizedCursorIndex(bucketOrder, cursor) {
|
|
88
|
+
if (!cursor) return 0;
|
|
89
|
+
const family = bucketFamily(cursor);
|
|
90
|
+
const index = bucketOrder.indexOf(family);
|
|
91
|
+
return index >= 0 ? index : 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function rotateBucketFamilies(bucketFamilies, cursor) {
|
|
95
|
+
const orderedFamilies = [...bucketFamilies].sort((left, right) => {
|
|
96
|
+
const leftPriority = backgroundBucketPriority(left);
|
|
97
|
+
const rightPriority = backgroundBucketPriority(right);
|
|
98
|
+
if (leftPriority !== rightPriority) return leftPriority - rightPriority;
|
|
99
|
+
return left.localeCompare(right);
|
|
100
|
+
});
|
|
101
|
+
if (!cursor) return orderedFamilies;
|
|
102
|
+
const order = DEFAULT_BUCKET_ORDER.filter((entry) => orderedFamilies.includes(entry));
|
|
103
|
+
if (order.length === 0) return orderedFamilies;
|
|
104
|
+
const startIndex = normalizedCursorIndex(order, cursor);
|
|
105
|
+
return [
|
|
106
|
+
...order.slice(startIndex),
|
|
107
|
+
...order.slice(0, startIndex),
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseBucketCursor(cursor) {
|
|
112
|
+
const text = String(cursor || "").trim();
|
|
113
|
+
if (!text) return { family: "", lastRelativePath: "" };
|
|
114
|
+
if (!text.startsWith("{")) {
|
|
115
|
+
return { family: text, lastRelativePath: "" };
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const parsed = JSON.parse(text);
|
|
119
|
+
return {
|
|
120
|
+
family: String(parsed?.family || ""),
|
|
121
|
+
lastRelativePath: String(parsed?.last_relative_path || ""),
|
|
122
|
+
};
|
|
123
|
+
} catch {
|
|
124
|
+
return { family: text, lastRelativePath: "" };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function formatBucketCursor(family, lastRelativePath = "") {
|
|
129
|
+
const normalizedFamily = String(family || "").trim();
|
|
130
|
+
const normalizedPath = String(lastRelativePath || "").trim();
|
|
131
|
+
if (!normalizedFamily) return "";
|
|
132
|
+
if (!normalizedPath) return normalizedFamily;
|
|
133
|
+
return JSON.stringify({
|
|
134
|
+
family: normalizedFamily,
|
|
135
|
+
last_relative_path: normalizedPath,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function parsePreferredFamilies(value) {
|
|
140
|
+
if (Array.isArray(value)) {
|
|
141
|
+
return value.map((item) => String(item || "").trim()).filter(Boolean);
|
|
142
|
+
}
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function buildBackgroundFineSelection(files, options = {}) {
|
|
147
|
+
const selected = [];
|
|
148
|
+
let estimatedChunks = 0;
|
|
149
|
+
let relayChannelCount = 0;
|
|
150
|
+
const relayChannelProviderCounts = new Map();
|
|
151
|
+
let relayChannelFallbackProviderCount = 0;
|
|
152
|
+
let controllerCount = 0;
|
|
153
|
+
let routerCount = 0;
|
|
154
|
+
let middlewareCount = 0;
|
|
155
|
+
let otherCount = 0;
|
|
156
|
+
const maxFiles = Math.max(1, Number(options.maxFiles) || 1);
|
|
157
|
+
const maxChunks = Math.max(1, Number(options.maxChunks) || 1);
|
|
158
|
+
const minScore = Number(options.minScore) || 0;
|
|
159
|
+
const relayChannelFileCap = Math.max(1, Number(options.relayChannelFileCap) || Number.MAX_SAFE_INTEGER);
|
|
160
|
+
const relayChannelProviderCap = Math.max(1, Number(options.relayChannelProviderCap) || Number.MAX_SAFE_INTEGER);
|
|
161
|
+
const relayChannelFallbackProviderCap = Math.max(0, Number(options.relayChannelFallbackProviderCap) || 0);
|
|
162
|
+
const relayChannelHotProviderSecondaryCap = Math.max(1, Number(options.relayChannelHotProviderSecondaryCap) || relayChannelProviderCap);
|
|
163
|
+
const relayChannelWarmProviderSecondaryCap = Math.max(1, Number(options.relayChannelWarmProviderSecondaryCap) || 1);
|
|
164
|
+
const hotRelayChannelProviders = options.hotRelayChannelProviders instanceof Set
|
|
165
|
+
? options.hotRelayChannelProviders
|
|
166
|
+
: DEFAULT_HOT_RELAY_CHANNEL_PROVIDERS;
|
|
167
|
+
const controllerFileCap = Math.max(1, Number(options.controllerFileCap) || Number.MAX_SAFE_INTEGER);
|
|
168
|
+
const routerFileCap = Math.max(1, Number(options.routerFileCap) || Number.MAX_SAFE_INTEGER);
|
|
169
|
+
const middlewareFileCap = Math.max(1, Number(options.middlewareFileCap) || Number.MAX_SAFE_INTEGER);
|
|
170
|
+
const otherFileCap = Math.max(0, Number(options.otherFileCap) || 0);
|
|
171
|
+
const completedFineFiles = options.completedFineFiles || new Set();
|
|
172
|
+
const relayChannelResumeFamilyCompletedCap = Math.max(0, Number(options.relayChannelResumeFamilyCompletedCap) || 0);
|
|
173
|
+
const resumePreferredFamilies = parsePreferredFamilies(options.resumePreferredFamilies);
|
|
174
|
+
const resumePreferredFamilyFloor = Math.max(0, Number(options.resumePreferredFamilyFloor) || 0);
|
|
175
|
+
const parsedCursor = parseBucketCursor(options.bucketCursor);
|
|
176
|
+
const bucketQueues = new Map();
|
|
177
|
+
const relayChannelPreferredProviders = new Set();
|
|
178
|
+
const completedByFamily = new Map();
|
|
179
|
+
|
|
180
|
+
for (const relativePath of completedFineFiles.values ? completedFineFiles.values() : []) {
|
|
181
|
+
const family = bucketFamily(backgroundBucketKey(relativePath));
|
|
182
|
+
completedByFamily.set(family, (completedByFamily.get(family) || 0) + 1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (const file of [...files].sort((a, b) => {
|
|
186
|
+
const score = Number(b.score || 0) - Number(a.score || 0);
|
|
187
|
+
const chunkEstimateDelta = Number(a.chunkEstimate || 0) - Number(b.chunkEstimate || 0);
|
|
188
|
+
if (score === 0 && chunkEstimateDelta !== 0) return chunkEstimateDelta;
|
|
189
|
+
return score || String(a.relativePath || "").localeCompare(String(b.relativePath || ""));
|
|
190
|
+
})) {
|
|
191
|
+
const score = Number(file.score || 0);
|
|
192
|
+
if (score < minScore) continue;
|
|
193
|
+
const bucket = backgroundBucketKey(file.relativePath);
|
|
194
|
+
if (bucket.startsWith("relay_channel:") && relayChannelPreferredProviderFile(file.relativePath)) {
|
|
195
|
+
relayChannelPreferredProviders.add(bucket.split(":")[1] || "misc");
|
|
196
|
+
}
|
|
197
|
+
if (!bucketQueues.has(bucket)) bucketQueues.set(bucket, []);
|
|
198
|
+
bucketQueues.get(bucket).push(file);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let madeProgress = true;
|
|
202
|
+
while (madeProgress) {
|
|
203
|
+
if (selected.length >= maxFiles) break;
|
|
204
|
+
madeProgress = false;
|
|
205
|
+
const bucketFamilies = Array.from(new Set(Array.from(bucketQueues.keys()).map((bucket) => bucketFamily(bucket))));
|
|
206
|
+
const familyOrder = rotateBucketFamilies(bucketFamilies, parsedCursor.family);
|
|
207
|
+
const bucketOrder = Array.from(bucketQueues.keys()).sort((left, right) => {
|
|
208
|
+
const leftQueue = bucketQueues.get(left) || [];
|
|
209
|
+
const rightQueue = bucketQueues.get(right) || [];
|
|
210
|
+
const leftTotal = leftQueue.length;
|
|
211
|
+
const rightTotal = rightQueue.length;
|
|
212
|
+
const leftCompleted = leftQueue.filter((file) => completedFineFiles.has(file.relativePath)).length;
|
|
213
|
+
const rightCompleted = rightQueue.filter((file) => completedFineFiles.has(file.relativePath)).length;
|
|
214
|
+
const leftRatio = leftTotal > 0 ? leftCompleted / leftTotal : 1;
|
|
215
|
+
const rightRatio = rightTotal > 0 ? rightCompleted / rightTotal : 1;
|
|
216
|
+
if (leftRatio !== rightRatio) return leftRatio - rightRatio;
|
|
217
|
+
const leftFamily = bucketFamily(left);
|
|
218
|
+
const rightFamily = bucketFamily(right);
|
|
219
|
+
const leftFamilyCompleted = completedByFamily.get(leftFamily) || 0;
|
|
220
|
+
const rightFamilyCompleted = completedByFamily.get(rightFamily) || 0;
|
|
221
|
+
if (leftFamilyCompleted !== rightFamilyCompleted) return leftFamilyCompleted - rightFamilyCompleted;
|
|
222
|
+
const leftFamilyIndex = familyOrder.indexOf(bucketFamily(left));
|
|
223
|
+
const rightFamilyIndex = familyOrder.indexOf(bucketFamily(right));
|
|
224
|
+
if (leftFamilyIndex !== rightFamilyIndex) return leftFamilyIndex - rightFamilyIndex;
|
|
225
|
+
const leftPriority = backgroundBucketPriority(left);
|
|
226
|
+
const rightPriority = backgroundBucketPriority(right);
|
|
227
|
+
if (leftPriority !== rightPriority) return leftPriority - rightPriority;
|
|
228
|
+
return left.localeCompare(right);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (parsedCursor.lastRelativePath) {
|
|
232
|
+
bucketOrder.sort((left, right) => {
|
|
233
|
+
const leftFamily = bucketFamily(left);
|
|
234
|
+
const rightFamily = bucketFamily(right);
|
|
235
|
+
if (leftFamily !== parsedCursor.family && rightFamily !== parsedCursor.family) return 0;
|
|
236
|
+
if (leftFamily === parsedCursor.family && rightFamily !== parsedCursor.family) return -1;
|
|
237
|
+
if (rightFamily === parsedCursor.family && leftFamily !== parsedCursor.family) return 1;
|
|
238
|
+
const leftQueue = bucketQueues.get(left) || [];
|
|
239
|
+
const rightQueue = bucketQueues.get(right) || [];
|
|
240
|
+
const leftHasCursorPath = leftQueue.some((file) => String(file.relativePath || "") === parsedCursor.lastRelativePath);
|
|
241
|
+
const rightHasCursorPath = rightQueue.some((file) => String(file.relativePath || "") === parsedCursor.lastRelativePath);
|
|
242
|
+
if (leftHasCursorPath && !rightHasCursorPath) return -1;
|
|
243
|
+
if (rightHasCursorPath && !leftHasCursorPath) return 1;
|
|
244
|
+
return 0;
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
for (const bucket of bucketOrder) {
|
|
249
|
+
const queue = bucketQueues.get(bucket);
|
|
250
|
+
if (!queue || queue.length === 0) continue;
|
|
251
|
+
if (parsedCursor.lastRelativePath && bucketFamily(bucket) === parsedCursor.family) {
|
|
252
|
+
const pivot = queue.findIndex((file) => String(file.relativePath || "") === parsedCursor.lastRelativePath);
|
|
253
|
+
if (pivot >= 0) {
|
|
254
|
+
queue.push(...queue.splice(0, pivot + 1));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
while (queue.length > 0) {
|
|
258
|
+
const file = queue.shift();
|
|
259
|
+
const relativePath = String(file.relativePath || "").toLowerCase().split(path.sep).join("/");
|
|
260
|
+
const family = bucketFamily(bucket);
|
|
261
|
+
if (
|
|
262
|
+
family === "relay_channel" &&
|
|
263
|
+
resumePreferredFamilies.length > 0 &&
|
|
264
|
+
resumePreferredFamilyFloor > 0
|
|
265
|
+
) {
|
|
266
|
+
const selectedFamilies = new Set(selected.map((item) => bucketFamily(backgroundBucketKey(item.relativePath))));
|
|
267
|
+
const preferredCovered = resumePreferredFamilies.filter((preferredFamily) => selectedFamilies.has(preferredFamily)).length;
|
|
268
|
+
if (preferredCovered < resumePreferredFamilyFloor) continue;
|
|
269
|
+
}
|
|
270
|
+
if (
|
|
271
|
+
family === "relay_channel" &&
|
|
272
|
+
relayChannelResumeFamilyCompletedCap > 0 &&
|
|
273
|
+
(completedByFamily.get("relay_channel") || 0) >= relayChannelResumeFamilyCompletedCap
|
|
274
|
+
) {
|
|
275
|
+
const selectedNonRelayFamilies = new Set(selected.map((item) => bucketFamily(backgroundBucketKey(item.relativePath))));
|
|
276
|
+
selectedNonRelayFamilies.delete("relay_channel");
|
|
277
|
+
if (selectedNonRelayFamilies.size < 4) continue;
|
|
278
|
+
}
|
|
279
|
+
if (relativePath.startsWith("relay/channel/")) {
|
|
280
|
+
if (relayChannelCount >= relayChannelFileCap) continue;
|
|
281
|
+
const parts = relativePath.split("/");
|
|
282
|
+
const providerKey = parts.length >= 3 ? parts[2] : relativePath;
|
|
283
|
+
const providerCount = relayChannelProviderCounts.get(providerKey) || 0;
|
|
284
|
+
const providerHasPreferredFiles = relayChannelPreferredProviders.has(providerKey);
|
|
285
|
+
const providerTier = relayChannelProviderTier(providerKey, hotRelayChannelProviders);
|
|
286
|
+
const providerCap = providerTier === "hot" ? relayChannelHotProviderSecondaryCap : relayChannelWarmProviderSecondaryCap;
|
|
287
|
+
if (!providerHasPreferredFiles && providerCount === 0 && relayChannelFallbackProviderCount >= relayChannelFallbackProviderCap) continue;
|
|
288
|
+
if (providerCount === 0 && relayChannelPreferredProviders.has(providerKey) && !relayChannelPreferredProviderFile(relativePath)) continue;
|
|
289
|
+
if (providerCount >= Math.min(relayChannelProviderCap, providerCap)) continue;
|
|
290
|
+
if (providerCount >= 1 && !relayChannelSecondaryAllowed(relativePath)) continue;
|
|
291
|
+
}
|
|
292
|
+
if (relativePath.startsWith("controller/") && controllerCount >= controllerFileCap) continue;
|
|
293
|
+
if (relativePath.startsWith("router/") && routerCount >= routerFileCap) continue;
|
|
294
|
+
if (relativePath.startsWith("middleware/") && middlewareCount >= middlewareFileCap) continue;
|
|
295
|
+
if (family === "other" && otherCount >= otherFileCap) continue;
|
|
296
|
+
const chunkEstimate = Math.max(1, Number(file.chunkEstimate) || 1);
|
|
297
|
+
if (selected.length > 0 && estimatedChunks + chunkEstimate > maxChunks) {
|
|
298
|
+
queue.length = 0;
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
selected.push(file);
|
|
302
|
+
estimatedChunks += chunkEstimate;
|
|
303
|
+
if (relativePath.startsWith("relay/channel/")) {
|
|
304
|
+
relayChannelCount += 1;
|
|
305
|
+
const parts = relativePath.split("/");
|
|
306
|
+
const providerKey = parts.length >= 3 ? parts[2] : relativePath;
|
|
307
|
+
if ((relayChannelProviderCounts.get(providerKey) || 0) === 0 && !relayChannelPreferredProviders.has(providerKey)) {
|
|
308
|
+
relayChannelFallbackProviderCount += 1;
|
|
309
|
+
}
|
|
310
|
+
relayChannelProviderCounts.set(providerKey, (relayChannelProviderCounts.get(providerKey) || 0) + 1);
|
|
311
|
+
}
|
|
312
|
+
if (relativePath.startsWith("controller/")) controllerCount += 1;
|
|
313
|
+
if (relativePath.startsWith("router/")) routerCount += 1;
|
|
314
|
+
if (relativePath.startsWith("middleware/")) middlewareCount += 1;
|
|
315
|
+
if (family === "other") otherCount += 1;
|
|
316
|
+
madeProgress = true;
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
if (selected.length >= maxFiles) break;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return { selected, estimatedChunks };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function advanceBackgroundBucketCursor(lastProcessedRelativePath, files) {
|
|
327
|
+
const lastBucketFamily = bucketFamily(backgroundBucketKey(lastProcessedRelativePath));
|
|
328
|
+
const presentFamilies = Array.from(new Set((files || []).map((file) => bucketFamily(backgroundBucketKey(file.relativePath)))));
|
|
329
|
+
const orderedFamilies = rotateBucketFamilies(presentFamilies, null);
|
|
330
|
+
if (orderedFamilies.length === 0) return "";
|
|
331
|
+
return formatBucketCursor(lastBucketFamily, lastProcessedRelativePath);
|
|
332
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function shouldSaveCheckpoint({
|
|
2
|
+
force = false,
|
|
3
|
+
nowMs = 0,
|
|
4
|
+
lastSavedAtMs = 0,
|
|
5
|
+
fileDelta = 0,
|
|
6
|
+
chunkDelta = 0,
|
|
7
|
+
intervalMs = 0,
|
|
8
|
+
minFileDelta = 0,
|
|
9
|
+
minChunkDelta = 0,
|
|
10
|
+
}) {
|
|
11
|
+
if (force) return true;
|
|
12
|
+
const dueToTime = Math.max(0, Number(nowMs) || 0) - Math.max(0, Number(lastSavedAtMs) || 0) >= Math.max(0, Number(intervalMs) || 0);
|
|
13
|
+
const dueToFiles = Math.max(0, Number(fileDelta) || 0) >= Math.max(0, Number(minFileDelta) || 0);
|
|
14
|
+
const dueToChunks = Math.max(0, Number(chunkDelta) || 0) >= Math.max(0, Number(minChunkDelta) || 0);
|
|
15
|
+
return dueToTime || dueToFiles || dueToChunks;
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function shouldSuppressPartialChunkCountMismatch({ job = null, index = null }) {
|
|
2
|
+
if (!job || !index) return false;
|
|
3
|
+
if (index.complete === true) return false;
|
|
4
|
+
const stage = String(index.indexStage || "");
|
|
5
|
+
if (stage === "coarse_lexical") return true;
|
|
6
|
+
if (job.backgroundFineDeferred && job.semanticRetryDeferred) return true;
|
|
7
|
+
const chunks = Array.isArray(index.chunks) ? index.chunks : [];
|
|
8
|
+
const hasCoarseChunks = chunks.some((chunk) => chunk?.granularity === "coarse");
|
|
9
|
+
const hasFineChunks = chunks.some((chunk) => chunk?.granularity !== "coarse");
|
|
10
|
+
if (hasCoarseChunks && hasFineChunks) return true;
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function shouldSuppressExpectedResumeModelMismatch({ job = null }) {
|
|
15
|
+
const source = String(job?.embeddingModelSource || job?.diagnostics?.embeddingModelSource || "");
|
|
16
|
+
return source.startsWith("previous_failure_fallback_deferred_resume");
|
|
17
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function effectiveDeferredResumeDelayMs({
|
|
2
|
+
job = null,
|
|
3
|
+
failedDelayMs = 0,
|
|
4
|
+
semanticDeferredDelayMs = 0,
|
|
5
|
+
}) {
|
|
6
|
+
if (job?.semanticRetryDeferred) {
|
|
7
|
+
const retryCount = Math.max(0, Number(job?.semanticRetryCount || 0));
|
|
8
|
+
if (retryCount <= 1) return 250;
|
|
9
|
+
if (retryCount === 2) return 500;
|
|
10
|
+
if (retryCount === 3) return 750;
|
|
11
|
+
return Math.max(0, Number(semanticDeferredDelayMs) || 0);
|
|
12
|
+
}
|
|
13
|
+
return Math.max(0, Number(failedDelayMs) || 0);
|
|
14
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function remainingDeferredRetrySeconds({
|
|
2
|
+
deferredAt = "",
|
|
3
|
+
resumeDelayMs = 0,
|
|
4
|
+
nowMs = Date.now(),
|
|
5
|
+
}) {
|
|
6
|
+
const startedMs = Date.parse(String(deferredAt || ""));
|
|
7
|
+
if (!Number.isFinite(startedMs) || startedMs <= 0) return 0;
|
|
8
|
+
const remainingMs = Math.max(0, Number(resumeDelayMs || 0) - (Number(nowMs || 0) - startedMs));
|
|
9
|
+
return Math.ceil(remainingMs / 1000);
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function resolveEmbeddingAttemptOrdinal({
|
|
2
|
+
attemptPlan = [],
|
|
3
|
+
attemptIndex = -1,
|
|
4
|
+
}) {
|
|
5
|
+
const attempts = Array.isArray(attemptPlan) ? attemptPlan : [];
|
|
6
|
+
const index = Number(attemptIndex);
|
|
7
|
+
if (index < 0 || index >= attempts.length) {
|
|
8
|
+
return {
|
|
9
|
+
current: 0,
|
|
10
|
+
total: attempts.length,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
current: index + 1,
|
|
15
|
+
total: attempts.length,
|
|
16
|
+
};
|
|
17
|
+
}
|