@arclabs561/ai-visual-test 0.5.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/.secretsignore.example +20 -0
- package/CHANGELOG.md +360 -0
- package/CONTRIBUTING.md +63 -0
- package/DEPLOYMENT.md +80 -0
- package/LICENSE +22 -0
- package/README.md +142 -0
- package/SECURITY.md +108 -0
- package/api/health.js +34 -0
- package/api/validate.js +252 -0
- package/index.d.ts +1221 -0
- package/package.json +112 -0
- package/public/index.html +149 -0
- package/src/batch-optimizer.mjs +451 -0
- package/src/bias-detector.mjs +370 -0
- package/src/bias-mitigation.mjs +233 -0
- package/src/cache.mjs +433 -0
- package/src/config.mjs +268 -0
- package/src/constants.mjs +80 -0
- package/src/context-compressor.mjs +350 -0
- package/src/convenience.mjs +617 -0
- package/src/cost-tracker.mjs +257 -0
- package/src/cross-modal-consistency.mjs +170 -0
- package/src/data-extractor.mjs +232 -0
- package/src/dynamic-few-shot.mjs +140 -0
- package/src/dynamic-prompts.mjs +361 -0
- package/src/ensemble/index.mjs +53 -0
- package/src/ensemble-judge.mjs +366 -0
- package/src/error-handler.mjs +67 -0
- package/src/errors.mjs +167 -0
- package/src/experience-propagation.mjs +128 -0
- package/src/experience-tracer.mjs +487 -0
- package/src/explanation-manager.mjs +299 -0
- package/src/feedback-aggregator.mjs +248 -0
- package/src/game-goal-prompts.mjs +478 -0
- package/src/game-player.mjs +548 -0
- package/src/hallucination-detector.mjs +155 -0
- package/src/helpers/playwright.mjs +80 -0
- package/src/human-validation-manager.mjs +516 -0
- package/src/index.mjs +364 -0
- package/src/judge.mjs +929 -0
- package/src/latency-aware-batch-optimizer.mjs +192 -0
- package/src/load-env.mjs +159 -0
- package/src/logger.mjs +55 -0
- package/src/metrics.mjs +187 -0
- package/src/model-tier-selector.mjs +221 -0
- package/src/multi-modal/index.mjs +36 -0
- package/src/multi-modal-fusion.mjs +190 -0
- package/src/multi-modal.mjs +524 -0
- package/src/natural-language-specs.mjs +1071 -0
- package/src/pair-comparison.mjs +277 -0
- package/src/persona/index.mjs +42 -0
- package/src/persona-enhanced.mjs +200 -0
- package/src/persona-experience.mjs +572 -0
- package/src/position-counterbalance.mjs +140 -0
- package/src/prompt-composer.mjs +375 -0
- package/src/render-change-detector.mjs +583 -0
- package/src/research-enhanced-validation.mjs +436 -0
- package/src/retry.mjs +152 -0
- package/src/rubrics.mjs +231 -0
- package/src/score-tracker.mjs +277 -0
- package/src/smart-validator.mjs +447 -0
- package/src/spec-config.mjs +106 -0
- package/src/spec-templates.mjs +347 -0
- package/src/specs/index.mjs +38 -0
- package/src/temporal/index.mjs +102 -0
- package/src/temporal-adaptive.mjs +163 -0
- package/src/temporal-batch-optimizer.mjs +222 -0
- package/src/temporal-constants.mjs +69 -0
- package/src/temporal-context.mjs +49 -0
- package/src/temporal-decision-manager.mjs +271 -0
- package/src/temporal-decision.mjs +669 -0
- package/src/temporal-errors.mjs +58 -0
- package/src/temporal-note-pruner.mjs +173 -0
- package/src/temporal-preprocessor.mjs +543 -0
- package/src/temporal-prompt-formatter.mjs +219 -0
- package/src/temporal-validation.mjs +159 -0
- package/src/temporal.mjs +415 -0
- package/src/type-guards.mjs +311 -0
- package/src/uncertainty-reducer.mjs +470 -0
- package/src/utils/index.mjs +175 -0
- package/src/validation-framework.mjs +321 -0
- package/src/validation-result-normalizer.mjs +64 -0
- package/src/validation.mjs +243 -0
- package/src/validators/accessibility-programmatic.mjs +345 -0
- package/src/validators/accessibility-validator.mjs +223 -0
- package/src/validators/batch-validator.mjs +143 -0
- package/src/validators/hybrid-validator.mjs +268 -0
- package/src/validators/index.mjs +34 -0
- package/src/validators/prompt-builder.mjs +218 -0
- package/src/validators/rubric.mjs +85 -0
- package/src/validators/state-programmatic.mjs +260 -0
- package/src/validators/state-validator.mjs +291 -0
- package/vercel.json +27 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporal Batch Optimizer
|
|
3
|
+
*
|
|
4
|
+
* Adaptive batching that considers temporal dependencies and human perception.
|
|
5
|
+
*
|
|
6
|
+
* Research context (loosely related):
|
|
7
|
+
* - Efficient Sequential Decision Making (arXiv:2406.12125)
|
|
8
|
+
* * Paper focuses on online model selection (NOT implemented here)
|
|
9
|
+
* * We use temporal dependency concepts inspired by sequential decision aspects
|
|
10
|
+
* * We do NOT implement the paper's core online model selection algorithm
|
|
11
|
+
* - Serving LLM Reasoning Efficiently (arXiv:2505.13326)
|
|
12
|
+
* * Paper discusses "short and right" thinking management (NOT implemented here)
|
|
13
|
+
* * We do adaptive batching with temporal awareness (loosely related concept)
|
|
14
|
+
* * We do NOT implement the paper's specific adaptive batching strategy
|
|
15
|
+
* - Human Time Perception (NN/g, PMC)
|
|
16
|
+
* * 0.1s threshold for direct manipulation (used in time scales)
|
|
17
|
+
* * Attention affects temporal perception (used in weighting)
|
|
18
|
+
*
|
|
19
|
+
* IMPORTANT: This implements temporal-aware batching with dependencies, but does NOT
|
|
20
|
+
* implement the core algorithms from the cited papers. The citations are for loosely
|
|
21
|
+
* related concepts, not direct implementations.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { BatchOptimizer } from './batch-optimizer.mjs';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Temporal Batch Optimizer
|
|
28
|
+
* Extends BatchOptimizer with temporal awareness
|
|
29
|
+
*/
|
|
30
|
+
export class TemporalBatchOptimizer extends BatchOptimizer {
|
|
31
|
+
constructor(options = {}) {
|
|
32
|
+
super(options);
|
|
33
|
+
this.temporalDependencies = new Map(); // Track dependencies
|
|
34
|
+
this.sequentialContext = options.sequentialContext || null;
|
|
35
|
+
this.adaptiveBatching = options.adaptiveBatching !== false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Add request with temporal dependencies
|
|
40
|
+
*/
|
|
41
|
+
async addTemporalRequest(imagePath, prompt, context, dependencies = []) {
|
|
42
|
+
// Track dependencies
|
|
43
|
+
this.temporalDependencies.set(imagePath, {
|
|
44
|
+
dependencies,
|
|
45
|
+
timestamp: Date.now(),
|
|
46
|
+
priority: this.calculatePriority(dependencies, context)
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return this._queueRequest(imagePath, prompt, context);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Calculate priority based on temporal dependencies
|
|
54
|
+
*/
|
|
55
|
+
calculatePriority(dependencies, context) {
|
|
56
|
+
// Higher priority for:
|
|
57
|
+
// - No dependencies (can run in parallel)
|
|
58
|
+
// - Earlier timestamps (sequential order)
|
|
59
|
+
// - Critical evaluations (high importance)
|
|
60
|
+
|
|
61
|
+
let priority = 0;
|
|
62
|
+
|
|
63
|
+
// No dependencies = can run immediately
|
|
64
|
+
if (dependencies.length === 0) {
|
|
65
|
+
priority += 100;
|
|
66
|
+
} else {
|
|
67
|
+
// Dependencies reduce priority (must wait)
|
|
68
|
+
priority -= dependencies.length * 10;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Earlier timestamps get higher priority (decay over time)
|
|
72
|
+
if (context.timestamp) {
|
|
73
|
+
const age = Date.now() - context.timestamp;
|
|
74
|
+
// Earlier requests (older timestamp = larger age) should have higher priority
|
|
75
|
+
// But we want to decay this over time, so very old requests get lower priority
|
|
76
|
+
// For requests within reasonable time window, earlier = higher priority
|
|
77
|
+
if (age < 60000) { // Within 1 minute
|
|
78
|
+
priority += Math.max(0, 30 - age / 1000); // Earlier = higher, but decays
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Critical evaluations get higher priority
|
|
83
|
+
if (context.critical || context.testType === 'critical') {
|
|
84
|
+
priority += 50;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return priority;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Process queue with temporal awareness
|
|
92
|
+
*/
|
|
93
|
+
async _processQueue() {
|
|
94
|
+
if (this.processing || this.queue.length === 0) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.processing = true;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
// Sort by priority and dependencies
|
|
102
|
+
const sortedQueue = this.sortByTemporalDependencies([...this.queue]);
|
|
103
|
+
|
|
104
|
+
// Process in batches, respecting dependencies
|
|
105
|
+
while (sortedQueue.length > 0 && this.activeRequests < this.maxConcurrency) {
|
|
106
|
+
const batch = this.selectTemporalBatch(sortedQueue);
|
|
107
|
+
|
|
108
|
+
// Remove from queue
|
|
109
|
+
batch.forEach(item => {
|
|
110
|
+
const index = this.queue.findIndex(q => q.imagePath === item.imagePath);
|
|
111
|
+
if (index >= 0) this.queue.splice(index, 1);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Process batch
|
|
115
|
+
const promises = batch.map(async ({ imagePath, prompt, context, validateFn, resolve, reject }) => {
|
|
116
|
+
try {
|
|
117
|
+
// Check cache
|
|
118
|
+
if (this.cache) {
|
|
119
|
+
const cacheKey = this._getCacheKey(imagePath, prompt, context);
|
|
120
|
+
if (this.cache.has(cacheKey)) {
|
|
121
|
+
resolve(this.cache.get(cacheKey));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Add sequential context if available
|
|
127
|
+
if (this.sequentialContext) {
|
|
128
|
+
context = {
|
|
129
|
+
...context,
|
|
130
|
+
sequentialContext: this.sequentialContext.getContext()
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const result = await this._processRequest(imagePath, prompt, context, validateFn);
|
|
135
|
+
|
|
136
|
+
// Update sequential context
|
|
137
|
+
if (this.sequentialContext && result.score !== null) {
|
|
138
|
+
this.sequentialContext.addDecision({
|
|
139
|
+
score: result.score,
|
|
140
|
+
issues: result.issues || [],
|
|
141
|
+
assessment: result.assessment,
|
|
142
|
+
reasoning: result.reasoning
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
resolve(result);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
reject(error);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await Promise.allSettled(promises);
|
|
153
|
+
}
|
|
154
|
+
} finally {
|
|
155
|
+
this.processing = false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Sort queue by temporal dependencies
|
|
161
|
+
*/
|
|
162
|
+
sortByTemporalDependencies(queue) {
|
|
163
|
+
return queue.sort((a, b) => {
|
|
164
|
+
const depsA = this.temporalDependencies.get(a.imagePath);
|
|
165
|
+
const depsB = this.temporalDependencies.get(b.imagePath);
|
|
166
|
+
|
|
167
|
+
// No dependencies come first
|
|
168
|
+
if (!depsA && depsB) return -1;
|
|
169
|
+
if (depsA && !depsB) return 1;
|
|
170
|
+
if (!depsA && !depsB) return 0;
|
|
171
|
+
|
|
172
|
+
// Compare priorities
|
|
173
|
+
return depsB.priority - depsA.priority;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Select batch considering temporal dependencies
|
|
179
|
+
*/
|
|
180
|
+
selectTemporalBatch(sortedQueue) {
|
|
181
|
+
if (!this.adaptiveBatching) {
|
|
182
|
+
// Fixed batch size
|
|
183
|
+
return sortedQueue.splice(0, this.batchSize);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Adaptive batch selection
|
|
187
|
+
const batch = [];
|
|
188
|
+
const processed = new Set();
|
|
189
|
+
|
|
190
|
+
for (const item of sortedQueue) {
|
|
191
|
+
if (batch.length >= this.batchSize) break;
|
|
192
|
+
if (processed.has(item.imagePath)) continue;
|
|
193
|
+
|
|
194
|
+
const deps = this.temporalDependencies.get(item.imagePath);
|
|
195
|
+
|
|
196
|
+
// Check if dependencies are satisfied
|
|
197
|
+
if (!deps || deps.dependencies.every(dep => processed.has(dep))) {
|
|
198
|
+
batch.push(item);
|
|
199
|
+
processed.add(item.imagePath);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return batch;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Get temporal statistics
|
|
208
|
+
*/
|
|
209
|
+
getTemporalStats() {
|
|
210
|
+
return {
|
|
211
|
+
...this.getCacheStats(),
|
|
212
|
+
dependencies: this.temporalDependencies.size,
|
|
213
|
+
sequentialContext: this.sequentialContext
|
|
214
|
+
? {
|
|
215
|
+
historyLength: this.sequentialContext.history.length,
|
|
216
|
+
patterns: this.sequentialContext.identifyPatterns()
|
|
217
|
+
}
|
|
218
|
+
: null
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporal Constants
|
|
3
|
+
* Centralized definitions for all temporal decision-making constants
|
|
4
|
+
*
|
|
5
|
+
* Single source of truth for all magic numbers and time scales
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Time Scales (based on research: NN/g, PMC, Lindgaard)
|
|
9
|
+
export const TIME_SCALES = {
|
|
10
|
+
INSTANT: 100, // 0.1s - perceived instant, direct manipulation threshold (NN/g)
|
|
11
|
+
VISUAL_DECISION: 50, // 50ms - visual appeal decision (Lindgaard research)
|
|
12
|
+
QUICK: 1000, // 1s - noticeable delay (NN/g)
|
|
13
|
+
NORMAL: 3000, // 3s - normal interaction
|
|
14
|
+
EXTENDED: 10000, // 10s - extended focus (NN/g)
|
|
15
|
+
LONG: 60000 // 60s - deep evaluation
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Multi-Scale Windows
|
|
19
|
+
export const MULTI_SCALE_WINDOWS = {
|
|
20
|
+
immediate: TIME_SCALES.INSTANT,
|
|
21
|
+
short: TIME_SCALES.QUICK,
|
|
22
|
+
medium: TIME_SCALES.EXTENDED,
|
|
23
|
+
long: TIME_SCALES.LONG
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Reading Speeds (words per minute)
|
|
27
|
+
export const READING_SPEEDS = {
|
|
28
|
+
SCANNING: 300, // Fast scanning
|
|
29
|
+
NORMAL: 250, // Average reading
|
|
30
|
+
DEEP: 200 // Deep reading
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Attention Multipliers
|
|
34
|
+
export const ATTENTION_MULTIPLIERS = {
|
|
35
|
+
focused: 0.8, // Faster when focused (reduced cognitive load)
|
|
36
|
+
normal: 1.0,
|
|
37
|
+
distracted: 1.5 // Slower when distracted (increased cognitive load)
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Complexity Multipliers
|
|
41
|
+
export const COMPLEXITY_MULTIPLIERS = {
|
|
42
|
+
simple: 0.7, // Simple actions are faster
|
|
43
|
+
normal: 1.0,
|
|
44
|
+
complex: 1.5 // Complex actions take longer
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Confidence Thresholds
|
|
48
|
+
export const CONFIDENCE_THRESHOLDS = {
|
|
49
|
+
HIGH_VARIANCE: 1.0, // Variance < 1.0 = high confidence
|
|
50
|
+
MEDIUM_VARIANCE: 2.0, // Variance < 2.0 = medium confidence
|
|
51
|
+
LOW_VARIANCE: 2.0 // Variance >= 2.0 = low confidence
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Time Bounds
|
|
55
|
+
export const TIME_BOUNDS = {
|
|
56
|
+
MIN_PERCEPTION: 100, // Minimum perception time (0.1s)
|
|
57
|
+
MIN_READING_SHORT: 1000, // Minimum reading time for short content
|
|
58
|
+
MIN_READING_LONG: 2000, // Minimum reading time for long content
|
|
59
|
+
MAX_READING_SHORT: 15000, // Maximum reading time for short content
|
|
60
|
+
MAX_READING_LONG: 30000 // Maximum reading time for long content
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Content Length Thresholds
|
|
64
|
+
export const CONTENT_THRESHOLDS = {
|
|
65
|
+
SHORT: 100, // Short content (< 100 chars)
|
|
66
|
+
MEDIUM: 1000, // Medium content (< 1000 chars)
|
|
67
|
+
LONG: 1000 // Long content (>= 1000 chars)
|
|
68
|
+
};
|
|
69
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporal Context Utilities
|
|
3
|
+
* Standardized context creation and merging for temporal components
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create standardized temporal context
|
|
8
|
+
*/
|
|
9
|
+
export function createTemporalContext(options = {}) {
|
|
10
|
+
return {
|
|
11
|
+
sequentialContext: options.sequentialContext || null,
|
|
12
|
+
viewport: options.viewport || null,
|
|
13
|
+
testType: options.testType || null,
|
|
14
|
+
enableBiasMitigation: options.enableBiasMitigation !== false,
|
|
15
|
+
attentionLevel: options.attentionLevel || 'normal',
|
|
16
|
+
actionComplexity: options.actionComplexity || 'normal',
|
|
17
|
+
persona: options.persona || null,
|
|
18
|
+
contentLength: options.contentLength || 0,
|
|
19
|
+
...options
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Merge temporal contexts
|
|
25
|
+
*/
|
|
26
|
+
export function mergeTemporalContext(base, additional) {
|
|
27
|
+
return {
|
|
28
|
+
...base,
|
|
29
|
+
...additional,
|
|
30
|
+
sequentialContext: additional.sequentialContext || base.sequentialContext,
|
|
31
|
+
// Preserve base values if additional doesn't override
|
|
32
|
+
attentionLevel: additional.attentionLevel || base.attentionLevel || 'normal',
|
|
33
|
+
actionComplexity: additional.actionComplexity || base.actionComplexity || 'normal'
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Extract temporal context from options
|
|
39
|
+
*/
|
|
40
|
+
export function extractTemporalContext(options) {
|
|
41
|
+
return {
|
|
42
|
+
sequentialContext: options.sequentialContext,
|
|
43
|
+
attentionLevel: options.attentionLevel,
|
|
44
|
+
actionComplexity: options.actionComplexity,
|
|
45
|
+
persona: options.persona,
|
|
46
|
+
contentLength: options.contentLength
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporal Decision Manager
|
|
3
|
+
*
|
|
4
|
+
* Implements decision logic for WHEN to prompt based on temporal context.
|
|
5
|
+
*
|
|
6
|
+
* Research: arXiv:2406.12125 - "Efficient Sequential Decision Making"
|
|
7
|
+
* - Paper's core finding: Don't prompt on every state change, prompt when decision is needed
|
|
8
|
+
* - Online model selection achieves 6x gains with 1.5% LLM calls
|
|
9
|
+
*
|
|
10
|
+
* This module implements the decision logic (when to prompt) that was missing from
|
|
11
|
+
* our temporal aggregation system. It complements temporal-decision.mjs which handles
|
|
12
|
+
* HOW to aggregate temporal context, while this handles WHEN to use it.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { aggregateTemporalNotes } from './temporal.mjs';
|
|
16
|
+
import { aggregateMultiScale } from './temporal-decision.mjs';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Temporal Decision Manager
|
|
20
|
+
*
|
|
21
|
+
* Decides when context is sufficient to prompt, when to wait, and when to prompt immediately.
|
|
22
|
+
*/
|
|
23
|
+
export class TemporalDecisionManager {
|
|
24
|
+
constructor(options = {}) {
|
|
25
|
+
this.minNotesForPrompt = options.minNotesForPrompt || 3; // Minimum notes before prompting
|
|
26
|
+
this.coherenceThreshold = options.coherenceThreshold || 0.5; // Minimum coherence to prompt
|
|
27
|
+
this.urgencyThreshold = options.urgencyThreshold || 0.3; // Coherence drop triggers urgency
|
|
28
|
+
this.maxWaitTime = options.maxWaitTime || 10000; // Max time to wait (10s)
|
|
29
|
+
this.stateChangeThreshold = options.stateChangeThreshold || 0.2; // Significant state change
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Decide if we should prompt now or wait for more context
|
|
34
|
+
*
|
|
35
|
+
* @param {Object} currentState - Current state
|
|
36
|
+
* @param {Object} previousState - Previous state (if any)
|
|
37
|
+
* @param {import('./index.mjs').TemporalNote[]} temporalNotes - Temporal notes so far
|
|
38
|
+
* @param {Object} context - Additional context
|
|
39
|
+
* @returns {{shouldPrompt: boolean, reason: string, urgency: 'low'|'medium'|'high'}}
|
|
40
|
+
*/
|
|
41
|
+
shouldPrompt(currentState, previousState, temporalNotes, context = {}) {
|
|
42
|
+
// 1. Check if we have minimum notes
|
|
43
|
+
if (temporalNotes.length < this.minNotesForPrompt) {
|
|
44
|
+
return {
|
|
45
|
+
shouldPrompt: false,
|
|
46
|
+
reason: `Insufficient notes (${temporalNotes.length} < ${this.minNotesForPrompt})`,
|
|
47
|
+
urgency: 'low'
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 2. Calculate temporal coherence
|
|
52
|
+
const aggregated = aggregateTemporalNotes(temporalNotes);
|
|
53
|
+
const coherence = aggregated.coherence || 0;
|
|
54
|
+
|
|
55
|
+
// 3. Check for significant state change
|
|
56
|
+
const stateChange = this.calculateStateChange(currentState, previousState);
|
|
57
|
+
|
|
58
|
+
// 4. Check for user action
|
|
59
|
+
const hasUserAction = this.hasRecentUserAction(temporalNotes, context);
|
|
60
|
+
|
|
61
|
+
// 5. Check for decision point
|
|
62
|
+
const isDecisionPoint = this.isDecisionPoint(currentState, context);
|
|
63
|
+
|
|
64
|
+
// 6. Check for coherence drop (urgency signal)
|
|
65
|
+
const coherenceDrop = this.detectCoherenceDrop(temporalNotes, aggregated);
|
|
66
|
+
|
|
67
|
+
// Decision logic (from research: prompt when decision needed, not on every change)
|
|
68
|
+
if (isDecisionPoint) {
|
|
69
|
+
return {
|
|
70
|
+
shouldPrompt: true,
|
|
71
|
+
reason: 'Decision point reached',
|
|
72
|
+
urgency: 'high'
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (coherenceDrop) {
|
|
77
|
+
return {
|
|
78
|
+
shouldPrompt: true,
|
|
79
|
+
reason: 'Coherence drop detected (quality issue)',
|
|
80
|
+
urgency: 'high'
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (hasUserAction && stateChange > this.stateChangeThreshold) {
|
|
85
|
+
return {
|
|
86
|
+
shouldPrompt: true,
|
|
87
|
+
reason: 'User action with significant state change',
|
|
88
|
+
urgency: 'medium'
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (coherence >= this.coherenceThreshold && stateChange > this.stateChangeThreshold) {
|
|
93
|
+
return {
|
|
94
|
+
shouldPrompt: true,
|
|
95
|
+
reason: 'Stable context with significant state change',
|
|
96
|
+
urgency: 'medium'
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Wait for more context
|
|
101
|
+
return {
|
|
102
|
+
shouldPrompt: false,
|
|
103
|
+
reason: `Context not sufficient (coherence: ${coherence.toFixed(2)}, stateChange: ${stateChange.toFixed(2)})`,
|
|
104
|
+
urgency: 'low'
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Calculate state change magnitude
|
|
110
|
+
*/
|
|
111
|
+
calculateStateChange(currentState, previousState) {
|
|
112
|
+
if (!previousState) return 1.0; // First state = maximum change
|
|
113
|
+
|
|
114
|
+
// Simple state change calculation (can be enhanced)
|
|
115
|
+
let change = 0.0;
|
|
116
|
+
let comparisons = 0;
|
|
117
|
+
|
|
118
|
+
// Compare scores if available
|
|
119
|
+
if (currentState.score !== undefined && previousState.score !== undefined) {
|
|
120
|
+
const scoreChange = Math.abs(currentState.score - previousState.score) / 10; // Normalize to 0-1
|
|
121
|
+
change += scoreChange;
|
|
122
|
+
comparisons++;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Compare issues if available
|
|
126
|
+
if (currentState.issues && previousState.issues) {
|
|
127
|
+
const currentIssues = new Set(currentState.issues);
|
|
128
|
+
const previousIssues = new Set(previousState.issues);
|
|
129
|
+
const added = [...currentIssues].filter(i => !previousIssues.has(i)).length;
|
|
130
|
+
const removed = [...previousIssues].filter(i => !currentIssues.has(i)).length;
|
|
131
|
+
const issueChange = (added + removed) / Math.max(currentIssues.size + previousIssues.size, 1);
|
|
132
|
+
change += issueChange;
|
|
133
|
+
comparisons++;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Compare game state if available
|
|
137
|
+
if (currentState.gameState && previousState.gameState) {
|
|
138
|
+
const gameStateChange = this.calculateGameStateChange(currentState.gameState, previousState.gameState);
|
|
139
|
+
change += gameStateChange;
|
|
140
|
+
comparisons++;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return comparisons > 0 ? change / comparisons : 0.0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Calculate game state change
|
|
148
|
+
*/
|
|
149
|
+
calculateGameStateChange(current, previous) {
|
|
150
|
+
// Simple comparison (can be enhanced)
|
|
151
|
+
const currentKeys = Object.keys(current || {});
|
|
152
|
+
const previousKeys = Object.keys(previous || {});
|
|
153
|
+
|
|
154
|
+
const added = currentKeys.filter(k => !previousKeys.includes(k)).length;
|
|
155
|
+
const removed = previousKeys.filter(k => !currentKeys.includes(k)).length;
|
|
156
|
+
const changed = currentKeys.filter(k =>
|
|
157
|
+
previousKeys.includes(k) &&
|
|
158
|
+
JSON.stringify(current[k]) !== JSON.stringify(previous[k])
|
|
159
|
+
).length;
|
|
160
|
+
|
|
161
|
+
const totalKeys = new Set([...currentKeys, ...previousKeys]).size;
|
|
162
|
+
return totalKeys > 0 ? (added + removed + changed) / totalKeys : 0.0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Check for recent user action
|
|
167
|
+
*/
|
|
168
|
+
hasRecentUserAction(temporalNotes, context) {
|
|
169
|
+
if (context.recentAction) return true;
|
|
170
|
+
|
|
171
|
+
// Check last few notes for interactions
|
|
172
|
+
const recentNotes = temporalNotes.slice(-3);
|
|
173
|
+
return recentNotes.some(note =>
|
|
174
|
+
note.step?.includes('interaction') ||
|
|
175
|
+
note.step?.includes('click') ||
|
|
176
|
+
note.step?.includes('action') ||
|
|
177
|
+
note.observation?.includes('user') ||
|
|
178
|
+
note.observation?.includes('clicked')
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Check if this is a decision point
|
|
184
|
+
*/
|
|
185
|
+
isDecisionPoint(currentState, context) {
|
|
186
|
+
// Decision points based on context
|
|
187
|
+
if (context.stage === 'decision' || context.stage === 'evaluation') return true;
|
|
188
|
+
if (context.testType === 'critical' || context.critical) return true;
|
|
189
|
+
if (context.goal && context.goalCompleted) return true;
|
|
190
|
+
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Detect coherence drop (quality issue signal)
|
|
196
|
+
*/
|
|
197
|
+
detectCoherenceDrop(temporalNotes, currentAggregated) {
|
|
198
|
+
if (temporalNotes.length < 4) return false; // Need history to detect drop
|
|
199
|
+
|
|
200
|
+
// Get previous coherence (from notes without last one)
|
|
201
|
+
const previousNotes = temporalNotes.slice(0, -1);
|
|
202
|
+
const previousAggregated = aggregateTemporalNotes(previousNotes);
|
|
203
|
+
const previousCoherence = previousAggregated.coherence || 1.0;
|
|
204
|
+
const currentCoherence = currentAggregated.coherence || 1.0;
|
|
205
|
+
|
|
206
|
+
// Drop detected if coherence decreased significantly
|
|
207
|
+
const drop = previousCoherence - currentCoherence;
|
|
208
|
+
return drop > this.urgencyThreshold;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Calculate prompt urgency
|
|
213
|
+
*/
|
|
214
|
+
calculatePromptUrgency(temporalContext, decision) {
|
|
215
|
+
if (decision.urgency === 'high') return 1.0;
|
|
216
|
+
if (decision.urgency === 'medium') return 0.6;
|
|
217
|
+
|
|
218
|
+
// Low urgency, but check if we should wait
|
|
219
|
+
const coherence = temporalContext.coherence || 0;
|
|
220
|
+
const timeSinceLastPrompt = temporalContext.timeSinceLastPrompt || 0;
|
|
221
|
+
|
|
222
|
+
// Increase urgency if coherence is good and it's been a while
|
|
223
|
+
if (coherence > 0.7 && timeSinceLastPrompt > 5000) {
|
|
224
|
+
return 0.4; // Medium-low
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return 0.2; // Low
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Select optimal timing for requests
|
|
232
|
+
*/
|
|
233
|
+
selectOptimalTiming(requests, temporalContext) {
|
|
234
|
+
const decisions = requests.map(req => ({
|
|
235
|
+
request: req,
|
|
236
|
+
decision: this.shouldPrompt(
|
|
237
|
+
req.currentState,
|
|
238
|
+
req.previousState,
|
|
239
|
+
req.temporalNotes || [],
|
|
240
|
+
req.context || {}
|
|
241
|
+
)
|
|
242
|
+
}));
|
|
243
|
+
|
|
244
|
+
// Separate by urgency
|
|
245
|
+
const urgent = decisions.filter(d => d.decision.urgency === 'high');
|
|
246
|
+
const medium = decisions.filter(d => d.decision.urgency === 'medium');
|
|
247
|
+
const low = decisions.filter(d => d.decision.urgency === 'low');
|
|
248
|
+
|
|
249
|
+
// Urgent: prompt immediately
|
|
250
|
+
// Medium: batch if context stable, otherwise prompt
|
|
251
|
+
// Low: wait or batch
|
|
252
|
+
|
|
253
|
+
const stable = temporalContext.coherence > 0.7;
|
|
254
|
+
const shouldBatch = stable && medium.length + low.length > 1;
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
promptNow: urgent.map(d => d.request),
|
|
258
|
+
batch: shouldBatch ? [...medium, ...low].map(d => d.request) : medium.map(d => d.request),
|
|
259
|
+
wait: shouldBatch ? [] : low.map(d => d.request),
|
|
260
|
+
decisions
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Create a temporal decision manager with default options
|
|
267
|
+
*/
|
|
268
|
+
export function createTemporalDecisionManager(options = {}) {
|
|
269
|
+
return new TemporalDecisionManager(options);
|
|
270
|
+
}
|
|
271
|
+
|